一、类,超类和子类
1.覆盖方法
- 由于超类中的有些方法对子类不一定适用,因此需要提供一个新的方法覆盖override
- 我们可以使用super关键字调用超类的方法或构造器
- s u p e r 不是一个对象的引用,只是一个指示编译器调用超类方法的关键字。 \textcolor{CarnationPink}{super不是一个对象的引用,只是一个指示编译器调用超类方法的关键字。} super不是一个对象的引用,只是一个指示编译器调用超类方法的关键字。
2.子类构造器
- 使用super/this调用的构造器语句必须是子类构造器的第一条语句
- 由于子类的构造器不能访问超类的私有字段,所以必须通过一个构造器来初始化这些私有字段
- 如果子类构造器没有显示的调用超类的构造器,将自动的调用超类的无参构造器
- 如果超类没有无参构造器,并且子类的构造器中又没有显示的调用超类的其它构造器,Java编译器会报错
3.多态
一个对象变量可以指示多种实际类型的现象称为 多态 \textcolor{CarnationPink}{多态} 多态
在运行时能够自动的选择适当的方法称为 动态绑定 \textcolor{CarnationPink}{动态绑定} 动态绑定,使用动态绑定可以无需对现有代码进行修改就可以对程序进行扩展。
判断数据是否应该设计为继承关系:"is-a"规则
- 子类的每个对象也是超类的对象
- 程序中出现超类对象的任何地方都可以使用子类的对象替换
警告⚠: \textcolor{CarnationPink}{警告⚠:} 警告⚠:子类引用的数组可以转换成超类引用的数组,而不需要使用强制类型转换
- 所有数组都要牢记创建时的元素类型,仅将类型兼容的引用存储到数组中
Manager[] managers=new Manager[10]; Employee[] staff=managers; staff[0]=new Employee("mraz",1,2,3,4);//ArrayStoreException
4.理解方法调用
假设要调用x.f(args),隐式参数x声明为类C的一个对象,下面是详细调用过程:
编译器查看对象的声明类型和方法名。编译器会一一列举C类中所有名为f的方法及其超类中所有名为f且可访问的方法
编译器要确定方法调用中提供的参数类型。
如果存在一个与所提供参数类型完全匹配的方法,就选择这个方法。这个过程称为 重载解析。 \textcolor{CarnationPink}{重载解析。} 重载解析。
由于允许类型转换,如果编译器没有找到与参数类型匹配的方法,或发现经过类型转换后有多个方法与之匹配,编译器就会报错。
方法的名字和参数列表称为 方法的签名, \textcolor{CarnationPink}{方法的签名,} 方法的签名,返回类型不是签名的一部分。
覆盖一个方法时,要保证返回类型的兼容性,子类方法不能低于超类方法的可见性。 允许子类将覆盖方法的返回类型改为原返回类型的子类型。 \textcolor{CarnationPink}{允许子类将覆盖方法的返回类型改为原返回类型的子类型。} 允许子类将覆盖方法的返回类型改为原返回类型的子类型。我们说这两个getBuddy()方法有可协变的返回类型。
//Employee类 public Employee getBuddy() { return this; } //Manager类 @Override public Manager getBuddy() { return this; }
5.至此,编译器已经知道需要调用的方法的名字和参数类型了
如果是private方法、static方法、final方法或构造器,那么编译器可以准确知道要调用哪个方法,这称为静态绑定。
如果要调用的方法依赖于隐式参数的实际类型,那么必须在运行时使用动态绑定。
程序运行并且采用动态绑定调用方法时,虚拟机必须调用与x所引用对象的实际类型对应的那个方法。
虚拟机预先为每个类计算了一个方法表,其中列出了所有的方法签名和要调用的实际方法
真正调用方法的时候,虚拟机只要查找这个表就行了。
注意:如果调用是super.f(param),那么编译器将对隐式参数超类的方法表进行搜索。
5.阻止继承:final类和方法
- 不允许被扩展的类称为final类
- final类中的所有方法自动成为final方法,不包括字段,子类不能覆盖final方法
- 将方法或类声明为final的主要原因是:确保它们不会在子类中改变语义
- 如果一个方法没有被覆盖并且很短,编译器能够对它进行优化处理,这个过程称为 内联 \textcolor{CarnationPink}{内联} 内联
- 虚拟机中的 即时编译器 \textcolor{CarnationPink}{即时编译器} 即时编译器可以准确的知道类之间了继承关系,并能够检测出是否有类确实覆盖了给定的方法
- 如果虚拟机加载了另一个子类,而这个子类覆盖了一个内联方法,优化器将取消对这个方法的内联。
6.强制类型转换
- 只能在继承层次内进行强制类型转换
- 在将超类转换成子类之前,应当使用instanceof进行检查
- 尽量少用强制类型转换和instanceof运算符
7.抽象类
- 扩展抽象类有两种选择:
- 在子类中保留抽象类中的部分或所有抽象方法仍未定义,这样就必须将子类也标记为抽象类
- 定义全部方法,这样子类就不是抽象的了
- 抽象类不能实例化,可以定义一个抽象类的对象变量,这个变量只能引用非抽象子类的对象
二、Object类:所有类的超类
1.Object类型的变量
Object类型的变量只能用于作为各种值的一个泛型容器,要想对其中内容进行具体操作,还需要清楚对象的原始类型,并进行相应的强制类型转换
所有的数组类型,不管是对象数组还是基本类型数组都扩展了object类
Employee[] staff=new Employee[10]; Object obj=staff; Object o=new int[10];
2.equals方法
Onject类中的equals方法用于检测一个对象是否等于另外一个对象
为了防备name和hireDay可能为空的情况,需要使用使用Objects.equals方法
//Employee类 @Override public boolean equals(Object o) { if (this == o) return true;//先判断是不是同一个对象 if (o == null || getClass() != o.getClass()) return false;//如果o为空或者它们类型不一致,返回false Employee employee = (Employee) o;//将o强制类型转换 //防备name和hireDay可能为null的情况 return Double.compare(employee.salary, salary) == 0 && Objects.equals(name, employee.name) && Objects.equals(hireDay, employee.hireDay); } //Manager类 @Override public boolean equals(Object o) { //首先通过super关键字调用父类的方法,检测父类字段是否相等 if (!super.equals(o)) return false; //强制类型转换为子类对象,然后检测子类字段的值是否相等 Manager manager = (Manager) o; return Double.compare(manager.bonus, bonus) == 0; }
3.相等测试与继承
如果显示参数和隐式参数不属于同一个类,equals方法如果发现类不匹配,就会返回false
许多程序员使用instanceof进行检测:
//Employee public boolean equals(Object o) { if (this == o) return true;//先判断是不是同一个对象 // if (o == null || getClass() != o.getClass()) return false;//如果o为空或者它们类型不一致,返回false if(!(o instanceof Employee)) return false; Employee employee = (Employee) o;//将o强制类型转换 //防备name和hireDay可能为null的情况 return Double.compare(employee.salary, salary) == 0 && Objects.equals(name, employee.name) && Objects.equals(hireDay, employee.hireDay); }
这样就允许o属于一个子类,但是这种方法可能会招致一些麻烦,不建议采用这种方式处理
Java语言规范要求equals方法具有以下特性:
自反性
对于任意非空引用x,x.equals(x)应该返回true
对称性
对于任何应用x和y,y.equals(x)返回true时,x.equals(y)也返回true
传递性
对于任何引用x,y和z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也应该返回true
一致性
如果x和y的引用对象没有发生变化,反复调用x.equals(y)应该返回同样的结果
对于任意非空引用x,x.equals(null)应该返回false
比较this和o类时:
如果 e q u a l s 的语义在子类中可以改变时 \textcolor{ForestGreen}{如果equals的语义在子类中可以改变时} 如果equals的语义在子类中可以改变时, 就使用 g e t C l a s s 检测 \textcolor{CarnationPink}{就使用getClass检测} 就使用getClass检测
如果所有的子类都有相同的相等性语义 \textcolor{ForestGreen}{如果所有的子类都有相同的相等性语义} 如果所有的子类都有相同的相等性语义, 可以使用 i n s t a n c e o f 检测 \textcolor{CarnationPink}{可以使用instanceof检测} 可以使用instanceof检测
使用==比较基本类型字段,使用Objects.equals比较对象字段
对于数组类型的字段,可以使用静态的 A r r a y s . e q u a l s 方法检测相应的数组元素是否相等 \textcolor{CarnationPink}{对于数组类型的字段,可以使用静态的Arrays.equals方法检测相应的数组元素是否相等} 对于数组类型的字段,可以使用静态的Arrays.equals方法检测相应的数组元素是否相等
常见错误:
这个方法声明的显示参数类型是Employee,它没有覆盖Object类的equals方法,而是定义了一个完全无关的方法
要避免这种错误,可以添加@Override注解
public boolean equals(Employee other) { return true; }