1. 封装 Encapsulation
1.1. 什么是封装
在面向对象程式设计方法中, 封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部分包装, 隐藏起来的方法.
封装可以被认为是一个保护屏障, 防止该类的代码和数据被外部类定义的代码随机访问.
要访问该类的代码和数据, 必须通过严格的接口控制.
封装最主要的功能在于我们能修改自己的实现代码, 而不用修改那些调用我们代码的程序片段.
适当的封装可以让程式码更容易理解与维护, 也加强了程式码的安全性.
1.2. 封装的优点
1. 良好的封装能够减少耦合。
2. 类内部的结构可以自由修改。
3. 可以对成员变量进行更精确的控制。
4. 隐藏信息,实现细节。<br />
1.3. 封装的应用
2. 继承 Inheritance
2.1. 继承
从现有类创建子类, 现有类称为父类(体现顺序), 基类(子类以父类为基础), 超类(在子类中统一使用 super
来标识父类).
子类一旦继承父类, 就会继承父类的所有成员(构造器除外). 进而可以简化子类的写法, 并且让类和类之间关系更紧密.
Java 中只支持单继承: 一个子类只能有一个直接父类.
java 中是支持多层继承: 一个子类可以有多个间接父类.
子类可以继承父类的私有成员 private
, 但是只是有所有权, 没有直接使用权.
在父类中提供公共的 get/set
方法就可以让子类间接访问了.
2.2. 重写
方法覆盖(override), 也称为重写或重置.
要求:
- 子类的覆盖方法和父类的被覆盖方法的方法签名完全一致:
返回值类型, 方法名, 参数列表(参数数据类型, 参数的个数, 参数的顺序)
. - 子类的覆盖方法的访问权限修饰要大于等于父类的方法的访问权限修饰, 原因是子类是父类的扩展, 不可以缩小.
所谓重写, 不是指在子类中, 而是在第三方类中看待子类时, 子类重写父类的方法后只能看到子类方法
@Override
注解的作用 : 请求编译器作方法覆盖条件的检查, 提高可读性和安全性.
2.3. super/this
super
表示在当前类中使用时, 它代表的是父类的标识.super.
表示属性和方法. 向上追朔, 如果直接父类中没有此成员, 会继续向上搜索, 直到找到为止.super(...)
表示父类构造器.this
表示当前对象(整体).
如果子类中有和父类重名的属性, 对于子类和第三方类来说, 都一样, 都是有2个. 所有属性是共存的.
2.4. 访问控制修饰符
修饰符 | 类内部 | 同包不同类 | 非同包的子类 | 任何地方 |
---|---|---|---|---|
public | 1 | 1 | 1 | 1 |
protected | 1 | 1 | 1 | |
default | 1 | 1 | ||
private | 1 |
2.5. 类的创建过程
详细的创建过程请见 Java 类的创建过程
所有类必须有构造器:
- 如果类中没有提供任何构造器, 编译会自动添加一个缺省构造器;
- 如果类中有提供了构造器, 编译器就不添加缺省构造器了.
在子类的所有构造器中, 编译器会默认隐式的硬加一个语句 super()
并且必须在第一行. 为了保证父类的无参构造器必须先执行.
子类构造器中硬加的 super()
作用就是调用父类无参构造器. 如果父类没有无参构造器, 子类就出错!!!
如果父类中没有无参构造器, 子类的构造器中必须显式的调用父类有参构造.
子类构造器中的第一行 要么是 this(...)
要么是 super(...)
, super(...)
是对父类构造的直接调用, this(...)
是对父类构造的间接调用:
- 如果第一行什么也没有, 就是
super()
; super(...)
作用是直接的显式的调用父类构造器this(...)
作用是显式调用本类重载构造器, 而重载的构造器也是如此, 最终效果它也一定会调用到父类构造器.
结论: 子类构造中必须要先调用父类构造. 体现的逻辑永远是先父后子.
3. 多态 Polymorphism
3.1 多态
本类: 子类对象的子类形态; 本态引用: 子类对象赋值于子类类型的引用变量.
多态:
- 从右面看 -> 子类对象的多种父类形态(运行时类型)
- 从左面看 -> 父类类型包含多种子类对象(编译时类型)
多态引用: 父类引用指向子类变量.
- 子类对象赋值于父类类型的引用变量
- 父类类型引用变量指向不同子类对象
若编译时类型和运行时类型不一致,就出现多态(Polymorphism)
3.2 实例
编译时看左边, 运行时看右边.
public class Person {
public String name1 = "name1";
public String name2 = "name2";
public String say() {
return name1 + "+" + name2;
}
public String say2() {
return name1 + "+" + name2;
}
}
class Chinese extends Person {
public String name2 = "Chinese name2"; //相当于新添加了一个变量
public String name3 = "Chinese name3";
private int i = 1;
public int j = 2;
public String say() {
sayChinese();
return name1 + "+" + name2 + "+" + name3;
}
public void sayChinese(){
System.out.println("I'm Chinese");
}
}
class TestPolymorphism {
@Test
void testPolymorphism() {
Person p1 = new Person();
System.out.println(p1.name1); // name1
System.out.println(p1.name2); // name2
System.out.println(p1.say()); // name1+name2
System.out.println(p1.say2()); //name1 + name2
Chinese p2 = new Chinese();
System.out.println(p2.name1); // name1
System.out.println(p2.name2); // Chinese name2
System.out.println(p2.name3); // Chinese name3
System.out.println(p2.say()); // I'm Chinese \n name1+Chinese name2+Chinese name3
System.out.println(p2.say2()); // name1+name2
p2.sayChinese(); // I'm Chinese
Person p3 = new Chinese();
System.out.println(p3.name1); // name1
/*
得到 Person 的 name2 属性值
*/
System.out.println(p3.name2); // name2
/*
由于多态, 此时是父类 Person 态, 所以无法直接访问子类的特有属性 name3
System.out.println(p3.name3); // 无法访问
*/
/*
Chinese 重写的方法可以访问 Chinese 特有的成员(方法和变量)
*/
System.out.println(p3.say()); // I'm Chinese \n name1+Chinese name2+Chinese name3
/*
Chinese 未重写的方法访问 name2 时会调用 Person 类的 name2, 此时 Chinese 类的 name2 在
Person 类的 name2 存在的情况下相当于新的一个属性.
*/
System.out.println(p3.say2()); // name1+name2
}
}
3.3 动态绑定 / 虚拟方法调用
编译看左边, 运行看右边.
正常的方法调用:
Person e = new Person();
e.getInfo();
Student e = new Student();
e.getInfo();
虚拟方法调用(多态情况下):
Person e = new Student();
e.getInfo(); //调用Student类的getInfo()方法
编译时类型和运行时类型: 编译时 e
为 Person
类型, 而方法的调用是在运行时确定的, 所以调用的是 Student
类的 getInfo()
方法. ——动态绑定
成员变量:不具备多态性,只看引用变量所属的类。
作用: 通过动态绑定, 多态参数方法. 可以实际接收参数类及其子类的对象, 使方法的兼容性更好.
3.4 造型
对象类型转换 Casting
注意:
- 造型有风险, 必须加判断;
a instanceof B
作用是判断左面的引用a
指向的对象实体是否是右侧类型B
的实例(对象);- 对象的类型判断必须从最子类到最父类.
3.5 错题
@Test
public void test(){
Person person = new Person();
Chinese chinese = new Chinese();
American american = new American();
if (chinese instanceof Person) {
american = (American) chinese; // 编译不通过, 显示: Inconvertible types; cannot cast 'com.atguigu.javase.polymorphism.Chinese' to 'com.atguigu.javase.polymorphism.American'
// 编译失败; 不合法;.
}
}