Java学习之==>面向对象编程 Part2
一、封装
封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别;将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和方法都是类的成员。封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口,以特定的访问权限来使用类的成员。
- 理解
- 开车时,我们不需要知道发动机的运转原理;
- 开飞机时,我们不需要知道是什么驱动螺旋桨来转动的;
- 思想
- 高内聚,低耦合;
- 原则
- 把尽可能多的东西藏起来.对外提供简捷的接口,尽量降低对外的暴露;
- 把所有的属性藏起来;
如何实现封装:
1、权限修饰符
- public
- 公开的,作用范围一个工程内;
- protected
- 保护的,作用范围一个体系内;
- 默认
- 默认的,作用范围一个包内;
- private
- 私有的,作用范围一个类内;
作用点:
- 修饰类
- 修饰方法
- 修饰属性
- 修饰构造器
总结:
- 在不同的包内,只能调用修饰符为public的属性和方法;
- 在不同的包内,如果是继承父类,则可调用修饰符为public和protected的属性和方法;
- 在同一个包内,可以调用修饰符为public、protected和默认的属性和方法;
- 在同一个包内,如果是继承父类,则可调用修饰符为public、protected和默认的属性和方法;
2、getter&setter方法
我们先来看下面一个类:
public class User { private Integer id; private String name; private Integer age; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } }
再来看下这个类中getter和setter方法的调用
public class Test { public static void main(String[] args) { User user = new User(); user.setId(1); System.out.println("id = " + user.getId()); user.setName("jack"); System.out.println("name = " + user.getName()); user.setAge(25); System.out.println("age = " + user.getAge()); System.out.println("user = " + user); } }
private的属性只有对象自己才可以访问,其他任何对象不行,包括它的子类和父类。安全性高,其他对象只能通过它的public方法(getter&setter)来获取或设置原对象的private属性。如果属性设置为public,其他对象可以直接访问,安全性就不高了。定义为private,实现数据的隐藏和封装;
3、静态工厂方法(of)
我们再来看下下面这个类:
public class User { private Integer id; private String name; private Integer age; private User() { } private User(String name) { this(); this.name = name; } private User(Integer id, String name) { this(name); this.id = id; } private User(Integer id, String name, Integer age) { this(id,name); this.age = age; } public static User of() { return new User(); } public static User of(String name) { return new User(name); } public static User of(Integer id, String name) { return new User(id, name); } public static User of(Integer id, String name, Integer age) { return new User(id, name, age); } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } }
可以看到,我们在类中提供了多个 public 的方法 of(),返回的是该类的对象,这样我们使用时就不用再 new 一个对象出来,只要使用 User.of() 方法就可以了,如下:
public class Demo { public static void main(String[] args) { User user = User.of(1,"jack", 22); System.out.println("user = " + user); } }
类中的 of() 是我们自己定义的名称,可以根据传入的参数不同来自定义,其中除了可以返回当前类的对象,还可以返回其子类的对象。
二、继承
- 优点
- 减少了重复代码的编写,提高了代码的复用性;
- 语法
- extends关键字
- class Sub extends Super {}
- 注意
- Java中不支持多继承,一个类只能继承自一个父类;
- 可以多个子类继承同一个父类;
- 多层继承时,继承依然有效;
- 思考
- 实例化子类时,父类会被实例化吗?
super关键字:
- 定义
- 指向父类的指针;
- 作用
- 在子类中,调用父类的属性、方法、构造器;
- 语法
- super.name
- super.run()
- super("jack", 35)
- 注意
- super查找顺序是先找自己的直接父类;
- super调用父类的构造器必须在首行;
总结:
- 语法
- 基于关键字extends实现类和类之间的继承关系;
- 实现继承之后,属性、方法等都会被子类继承;
- 在子类中如果有和父类相同的属性和方法,且想调父类的实现时,可使用关键字super;
- 思想
- 把相同的东西抽出来,don't copy yourself;
- 继承的弊端
- 破坏了类的封装性;
三、多态
多态,简单解释就是一种事物的多种形态,我们先来看下以下几段代码:
public class Car { /** * 品牌 */ private String brand; /** * 排量 */ private String output; /** * 速度 */ private int speed; public Car(String brand, String output) { this.brand = brand; this.output = output; } public int getSpeed() { return speed; } public void setSpeed(int speed) { this.speed = speed; } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public String getOutput() { return output; } public void setOutput(String output) { this.output = output; } }
public class Benz extends Car { public Benz() { super("奔驰", "1.0"); } }
public class Bmw extends Car { public Bmw() { super("宝马", "2.0"); } @Override public String getBrand() { return "宝马mini"; } }
public class George extends Car { public George() { super("乔治巴顿", "7.5L"); } }
以上四段代码中,Car 是父类,Benz、Bmw 和 George 是子类,三个子类当中都传入了自己的品牌和排量调用了一次父类 Car 的构造方法,然后我们再来看下入口程序:
public class App { public static void main(String[] args) { // 以下写法就是多态的表现,将子类的实例化结果指向父类 Car car1 = new Bmw(); Car car2 = new Benz(); Car car3 = new George(); run(car1); run(car2); run(car3); } /** * 体现多态的写法,是下面的run方法, run方法在面向的是一个抽象的Car * 在run方法看来, 它只看Car这个抽象的东西, 它可以是Bmw、Benz或者George都可以,run不在乎 */ private static void run(Car car) { System.out.println("路上跑的是:" + car.getBrand() + "车"); } }
可以看到,run 方法传入的参数是一个抽象的 car,它可以是 Bmw、Benz、George,也可以是 Car 的其他子类的对象,因为我们把子类的实例化结果都指向了父类 Car,以上就是多态的最简单的表现形式,我们来看一下运行的结果:
理解
- 专业说法:一个事物的多种形态;
- 通俗说法:我要找个人来帮我‘
现象
- 将子类的实现方法指向父类的引用;
- Car car = new Bmw();
注意
- 多态针对的是方法,不是属性;
instanceof关键字:
作用
- 用于判断对象是否是某个类的实例;
语法
- obj instanceof Class;
使用场景
- 多态实现时用于前置判断,避免类转换异常;
我们将以上 run 方法加入 instanceof 判断,再看一下运行结果:
private static void run(Car car) { if (car instanceof George) { System.out.println("不行不行,速度太快"); return; } System.out.println("路上跑的是:" + car.getBrand() + "车"); }
判断 car 对象如果是 George 类的实例,则输出“不行不行,速度太快",而如果是其他类的实例,则还是输出“路上跑的是XX车”。
通过以上对多态的介绍,我个人感觉,多态其实是对继承的一种更深入表现形式。
面向对象编程再拓展一下可以理解为面向抽象编程。
多态中还有两种比较经典的表现形式:抽象类和接口。
抽象类
抽象类,我们可以理解为:面向模板编程
定义
- 使用 abstract 关键字修饰的类就为抽象类;
- 使用 abstract 关键字修饰的方法就为抽象方法;
特点
- 定义一个抽象方法, 使用abstract关键字, 但是方法不能有实现;
- 子类继承抽象类的时候, 必须实现抽象方法;
- 抽象类不可以被实例化;
举例:
public abstract class Person { private String type; public Person(String type) { this.type = type; } public String getType() { return type; } /** * 跑和跳,任何人都一样 * 不用抽象 */ public void run() { System.out.println("两条腿跑"); } public void jump() { System.out.println("两条腿跳"); } /** * 讲话,不同国家的人语言不一样 * 抽象,由具体的子类去实现 */ protected abstract void talk(); /** * 不抽象, 代码显得很乱 */ // if (this instanceof China) { // System.out.println("先来个开场白"); // System.out.println("一顿吹牛逼"); // System.out.println("人种:" + this.getType() + ",讲中文"); // } else if (this instanceof English) { // System.out.println("上来就一顿鸟语花香"); // System.out.println("人种:" + this.getType() + ",讲英文"); // } }
public class Chinese extends Person { public Chinese() { super("中国人"); } @Override protected void talk() { System.out.println("先来个开场白"); System.out.println("一顿吹牛逼"); System.out.println("人种:" + this.getType() + ",讲中文"); } }
public class English extends Person { public English() { super("英国人"); } @Override protected void talk() { System.out.println("上来就一顿鸟语花香"); System.out.println("人种:" + this.getType() + ",讲英文"); } }
定义了一个抽象类 Person,其中定义了三个方法:run()、jump() 和 talk(),对于不同国家的人来说,跑和跳都是一样,的不用抽象,而说话可能使用的是不同的语言,则可以用代表不同国家的人 Chinese 和 English 具体来实现;再来看一下入口程序和运行结果:
public class App { public static void main(String[] args) { Person china = new Chinese(); Person english = new English(); speak(china); System.out.println("--------------"); speak(english); System.out.println("--------------"); china.run(); System.out.println("--------------"); english.jump(); } private static void speak(Person person) { person.talk(); } }
接口
接口,我们可以理解为:面向规范编程
定义
- 接口是对类的一组需求的描述;
- 接口所描述的方法不能有实现;
- 接口所描述的方法都是 public,不写也是 public;
- 接口的“继承”叫实现,使用implements关键字
特性
- 接口不是类,不能进行实例化;
- 接口运行多实现,使用逗号隔开;
举例:
public interface Person { /** * 普通类中方法的定义方式: public void talk(){ } * * 抽象类中抽象方法的定义方式: protected abstract void talk(); * * 接口的方法的定义方式: void talk(); */ void talk(); }
public interface Study { void english(); }
/** * 子类是实现接口, 使用关键字. implements * * 可以实现多个接口, 多个接口之间使用逗号进行分割 */ public class Student implements Person, Study { @Override public void talk() { System.out.println("Student talk"); } @Override public void english() { System.out.println("Student study english"); } }
public class Teacher implements Person { @Override public void talk() { System.out.println("teacher talk"); } }
定义了两个接口 Person 和 Study,子类Student实现了接口 Person 和 Study 中的 talk() 和 study() 方法,而子类 Teacher 只实现了Person 中的 talk() 方法。再来看一下入口程序和运行结果:
public class App { public static void main(String[] args) { Person stu = new Student(); Study study = new Student(); Person tea = new Teacher(); speak(stu); study(study); speak(tea); } private static void speak(Person person) { person.talk(); } private static void study(Study study) { study.english(); } }
抽象类 VS 接口
相同点
- 都可以用来描述抽象;
- 都不能进行实例化;
不同点
- 抽象类还说类,其中定义的普通方法(非抽象方法)可以有实现;
- 接口中的方法不能有实现;
区别
- 抽象类一般是抽象能力;
- 接口一般是抽象规范;
举例:
public abstract class Person { /** * 模板 * 打电话的一套流程 */ public void call() { pickUp(); talk(); hello(); goodbye(); putDown(); } protected void pickUp() { System.out.println("拿起电话"); } protected abstract void talk(); protected abstract void hello(); protected abstract void goodbye(); protected void putDown() { System.out.println("挂了电话"); } }
Person类是一个抽象类,启动定义了一套打电话的流程,拿起电话和挂断电话对所有人来说都是一样的,所以在抽象类当中使用普通方法实现,但是打电话是使用什么语言、开始打招呼和结束语怎么说都因人而异,所以这三个方法使用抽象方法进行定义,但不实现。在一套模板中既有固定的步骤,又有不固定的步骤,这周情况下我们使用抽象类。
public class Chinese extends Person { @Override protected void talk() { System.out.println("中文语言"); } @Override protected void hello() { System.out.println("你好!!"); } @Override protected void goodbye() { System.out.println("再见!!"); } }
Chinese类继承自 Person 类,根据中国人的特性实现了 Person 类当中的 talk()、hello()、goodbye() 三个方法。
public class Englishi extends Person { @Override protected void talk() { System.out.println("englishi"); } @Override protected void hello() { System.out.println("hello!!"); } @Override protected void goodbye() { System.out.println("goodbye!!"); } }
同样,English 类根据英国人的特性实现了 Person 类当中的 talk()、hello()、goodbye() 三个方法。
/** * 文学素养 */ public interface Literature { /** * 写诗 */ void writePoet(); }
Literature 是一个关于文学素养的接口,里面定义了一个写诗的方法,但没有实现。文学素养不是每个人都有,写诗也不是每个人都会,所以这种情况我们使用接口来实现。
/** * 诗人 */ public class Poet extends Person implements Literature { @Override protected void talk() { System.out.println("诗情画意"); } @Override protected void hello() { System.out.println("轻轻的我来了"); } @Override protected void goodbye() { System.out.println("轻轻的我走了"); } @Override public void writePoet() { System.out.println("诗人写诗"); } }
Poet 定义为一个诗人类,它既有人的所有特征(能打电话),又有诗人独特的气质(能写诗),所以它继承自 Person 类、实现自 Literature 类,其中实现了Person 类当中的 talk()、hello()、goodbye() 三个方法和 Literature 接口中的 writePoet() 方法。最后我们再来看下入口程序和运行结果:
public class App { public static void main(String[] args) { Person p1 = new Chinese(); Person p2 = new Englishi(); Person p3 = new Poet(); Literature li = new Poet(); p1.call(); System.out.println("================="); p2.call(); System.out.println("================="); p3.call(); li.writePoet(); } }