封装性
体现
- 把一个事物的数据描述,行为描述封装到一个类中
- 属性的封装:在属性的前面加private修饰,公共的get/set方法
- 方法的封装:不对外暴露私有的方法
- 构造器的封装:单例模式
- 包:如果某个类或成员的修饰符是“缺省”的,这个类或成员就仅限于本包使用
- 组件的封装:例如:网银支付,支付宝等等,提供给使用者的是“接口”
作用
- 提高代码的安全性,不能直接操纵类中属性
- 提高代码的复用性
- 增强代码的可维护性
- “高内聚”:封装细节,便于修改内部代码,提高可维护性
“低耦合”:简化外部调用,便于调用者使用,便于扩展和协作。修改一个属性只需要改本类就行
权限修饰符
public: 属性 方法 构造器 类(外部类,内部类)
- 缺省: 属性 方法 构造器 类
- protected: 属性 方法 构造器 内部类
- private: 属性 方法 构造器 内部类
继承
为什么要继承
- 当多个类之间具有共同的特征,那么可以把这个共同的部分抽取成一个父类,这些类就可以通过继承这个父类,而达到“代码复用”,这样就不用在多个类中重复声明相同的部分
模拟、表现现实世界中一个“is-a”的关系
修饰符 class 子类名 extends 父类名 {}
好处
提高了代码的复用性
- 有利于功能的扩展
- 让类与类之间产生了关系,是多态的前提
特点
- 子类会继承父类的所有的属性和方法,但是不会继承父类的构造器
- 子类中不能直接使用父类的私有的属性与方法
- Java只支持单继承和多层继承,不允许多重继承
- 根父类是java.lang.Object
- 子类一定会调用父类的构造器
- 如果子类构造器中,没有写super()或super(实参列表)语句,那么默认就是调用父类“无参构造”
- 子类的构造器中可以手动调用父类的构造器,super()或super(实参列表)
- 当父类没有无参构造时,子类的构造中必须手动调用父类的有参构造super(实参列表)
- super()或super(实参列表),它必须在子类构造器的首行
弊端
- 类的耦合性增强,这样某个类的改变,就会影响其他和该类相关的类
- 开发的原则:低耦合,高内聚
- 耦合:类与类的关系
- 内聚:就是自己完成某件事情的能力
- 打破了封装性
-
方法重写
子类通过重写父类的方法,可以用自身的行为替换父类的行为。方法的重写是实现多态的必要条件
注意 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
- 子类重写的方法的权限修饰符>=父类被重写的方法的权限修饰符
- 特殊情况:子类不能重写父类中声明为private权限的方法
- 返回值类型:
- 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
- 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A的子类
- 父类被重写的方法的返回值类型是基本数据类型,则子类重写的方法的返回值类型必须是相同的基本数据类型
- 子类重写的方法抛出的异常类型<=父类被重写的方法抛出的异常类型
- 子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法
🔴重载与重写区别
- 重载,是指允许存在多个同名方法,参数不同的方法。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。所以重载在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”
而对于多态,只有等到方法调用的那一刻,才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”
继承树追溯
属性/方法查找顺序
查找当前类中有没有属性h
- 依次上溯每个父类,查看每个父类中是否有h,直到Object
- 如果没找到,则出现编译错误
- 只要找到,则这个过程终止
构造方法调用顺序
- 因为子类会继承、使用父类的数据,所以子类初始化之前一定要先完成父类数据的初始化
- 子类的每一个构造方法的第一句语句默认都是:super()
- 流程就是:先向上追溯到Object,然后再依次向下执行类的初始化块和构造方法,直到当前类为止
- 注:静态初始化块调用顺序,与构造方法调用顺序一样,不再重复
- 一个类的静态代码块 、代码块、构造方法的执行流程
- 静态代码块 > 代码块 > 构造方法
- 静态的内容是随着类的加载而加载
- 静态代码块的内容会优先执行
- 子类初始化之前先会进行父类的初始化
🔴明确
- 虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象
多态
多态指的是同一个方法调用,由于对象不同可能会有不同的行为
- 现实生活中,同一个方法,具体实现会完全不同
- 比如同样是调用人的“休息”方法,张三是睡觉,李四是旅游,程序员是敲代码,数学教授是做数学题
- 同样是调用人“吃饭”的方法,中国人用筷子吃饭,英国人用刀叉吃饭,印度人用手吃饭
要点
- 多态是方法的多态,不是属性的多态(多态与属性无关)
- 编译时类型与运行时类型不一致
- 前提
- 继承+重写
- 多态引用:父类引用指向子类对象
- 最终表现:编译时按照父类的类型进行编译检查,运行时,执行的是子类“重写”的代码
- 前提
- 是实现多态绑定技术的前提
对象转型
向上转型:自动完成
父类类型 变量 = 子类对象;
- 父类的变量指向了子类对象,编译时呈现出子类的对象向上转型成父类
- 父类引用变量只能调用它编译类型的属性方法,不能调用它运行时类型的属性方法
向下转型:强制转换
子类类型 变量 = (子类的类型)父类对象;
成功
- 这个父类的变量必须是指向该子类的对象
- 在转换之前加判断
if(变量 instanceof 子类的类型) {
才可以向下转型成这个子类
}
失败
- 父类的变量指向父类的对象
- 父类的变量指向其他子类的对象
- 失败异常:java.lang.ClassCastException类型转换异常
多态的使用:虚拟方法调用
- 有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法
- 此时父类的方法就是虚拟方法
- 总结:编译,看左边;运行,看右边
- 编译时类型:由声明该变量时使用的类型决定
- 运行时类型:由实际赋给该变量的对象决定
- “看左边”:看的是父类的引用(父类中不具备子类特有的方法)
- “看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)
成员访问特点
- 构造方法:创建子类对象的时候,访问父类的构造方法,对父类的数据进行初始化
- 成员变量:编译看左边,运行看左边
- 成员方法:编译看左边,运行看右边。因为成员方法有重写
- 静态方法没有多态
分类
- 具体类多态(几乎不用)
- 抽象类多态(常用)
- 接口多态(最常用)
面试题
static方法:static的方法不能被重写,因此也不符合多态的特征 ```java public class TestStatic { public static void main(String[] args) {
Father.method(); //执行的是父类的方法
Son.method(); //子类的方法
System.out.println("------------------");
Father f = new Father(); //本态引用
f.method(); //可以这么调用执行父类
System.out.println("------------------");
Son s = new Son(); //本态引用
s.method(); //执行子类
System.out.println("------------------");
Father father = new Son();//多态引用
father.method(); //按照多态,应该执行的是子类的重写的方法,此处不符合多态的特点
} }
class Father{ public static void method(){ System.out.println(“父类的静态方法”); } }
class Son extends Father{ // 尝试重写父类的静态方法,但是其实是子类自己的方法,不是重写父类的方法 // @Override注解的作用,是按照重写的标准来检查该方法是否符合重写的要求 public static void method(){ System.out.println(“子类的静态方法”); } }
- 属性:属性不能被覆盖,因此也不符合多态的特征
```java
属性没有重写一说,没有多态
public class TestField {
public static void main(String[] args) {
Dad d = new Dad();
System.out.println(d.name); // Dad
Baby b = new Baby();
System.out.println(b.name); // baby
Dad dd = new Baby();
System.out.println(dd.name); // Dad
}
}
class Dad{
String name = "Dad";
}
class Baby extends Dad{
String name = "baby";
}
- 可变参数
```java
public class TestSub {
public static void main(String[] args) {
} }Base base = new Sub(); // 多态引用
base.add(1,2,3); // sub_1 2,3已经按照数组去编译
class Base { public void add(int a, int… arr) { System.out.println(“base”); } }
class Sub extends Base{
public void add(int a, int[] arr) {
System.out.println(“sub_1”);
}
public void add(int a , int b, int c ){
System.out.println(“sub_2”);
}
}
```
多态的意义是为了实现代码的通用性
- 如Object的equals方法,JDBC操作不同数据库,抽象类、接口的使用