第五章 继承
1. 类、超类和子类
●实例认识
关键字super调用超类
使用super调用构造器的语句必须是子类构造器的第一条语句
如果子类的构造器没有显式的调用超类的构造器,则将自动调用超类默认的构造器。如果残类没有不带参数的构造器,并且在子类中没有显式的调用超类的其他构造器,则编译器将报错
多态:一个对象变量可以引用多种类型的现象
动态绑定:在运行时能够自动的选择调用适当的方法的现象
●多态:
“is-a”规则:表明子类的每个对象也是超类的对象
置换规则:程序中出现超类对象的任何地方都可以用子类对象置换
这里看com.valentine.lesson.manager包中的一个例子,将受益匪浅
●动态绑定:
方法调用过程的详细描述:
(1) 编译器查看对象声明的类型和方法名
(2) 编译器将查看调用方法时提供的参数类型,
调用参数类型和提供的参数完全匹配的方法,这个过程称为重载解析
方法和名字和参数列表被称为方法的签名
(3) 如果方法是private、static、final或者构造器,那么编译器将可以准确地知道应该调用哪个方法,这种调用方法称为静态绑定
(4) ★当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实际类型最合适的那个方法。
假设x的实际类型是D,它是C的子类。如果D类定义了方法就直接调用它,否则将在D类的超类中寻找方法,以此类推
注:此处说明了多态方法的调用是一个自下而上的调用顺序。
虚拟机为每个类创建了一个方法表,在运行时调用e.getsalary()过程为:
(1) 虚拟机提供对象e实际类型的方法表
(2) 虚拟机搜索定义getSalary签名的类,此时虚拟机知道应该调用那个方法
(3) 虚拟机调用方法
在覆盖一个方法的时候,子类方法不能低于超类方法的可见性。特别是,如果超类方法是public,那么子类的方法声明一定要是public。否则编译器将会把它解释为试图减弱访问权限
●阻止继承:final类和final方法
不允许扩展的类被称为final类,阻止人们利用某个类定义子类。
类中的方法也可以被声明为final,final方法子类不能覆盖这个方法
将方法或类声明为final的主要原因:确保他们不会在子类中改变语义
内联:
如果一个方法没有被覆盖而且很短,编译器就能够对它进行优化处理,这个过程称为内联。如果方法很简短、被频繁调用且没有真正的被覆盖,那么即时编译器就会将这个方法进行内联处理
●强制类型转换:将某个类的对象引用转换成另外一个类的对象引用
唯一原因:在暂时忽视对象的实际类型以后,使用对象的全部功能
将子类的引用赋给一个超类变量编译器是允许的,但是将超类的引用赋给一个子类变量,必须进行强制类型转换.
应养成在转换之前适用instanceof运算符的好习惯
在一般情况下应尽量少用类型转换和instanceof运算符
●抽象类:
包含一个或多个抽象方法的类本身必须被声明为抽象的
不管是不是抽象的,建议尽量将功能放置在超类中。尤其应该将大家都有的域和方法放置在抽象超类中。
抽象方法充当着占位的角色,它们的具体实现在子类中。
即使不含抽象方法,也可以将类声明为抽象类。
抽象类不能被实例化
可以定义一个抽象类的对象变量,但是它只能引用非抽象子类的对象(多态)
2. 所有类的超类:Object
●equals方法:
在Object类中,这个方法仅仅判断两对象是否具有相同的引用
●相等测试与继承:
Java规范要求equals方法具有下面的特性:
(1) 自反性
(2) 对称性
(3) 传递性
(4) 一致性
(5) 一个非空对象一定不等于一个空对象
注意在覆盖时参数的参数类型要和覆盖的方法一致,否则就变成了一个重载
●hashCode方法:
散列码:由对象导出的一个整形值
每个对象都有一个默认的散列码,其值为对象的存储地址
字符串的散列码是由内容导出的
●toString方法:返回表示对象值得字符串
格式:类的名字,随后是一对方括号括起来的域值
toString调用:只要对象与一个字符串通过+号连接起来,编译器就会自动地调用toString方法,以便获得这个对象的字符串描述。在println方法中也直接调用toString方法
★强烈建议为自己编写的每一个类增加toString方法
●
3. 泛型数组列表
●
泛型数组列表
ArrayList是一个采用类型参数的泛型类,为了指定数组列表保存的元素对象类型,需要用一对尖括号将类名括起来夹在后面
如果已经清楚或能够估计出数组可能存储的元素数量,就可以在填充数组之前调用ensureCapacity方法
一旦能够确认数组列表的大小不再发生变化,就可以调用trimToSize方法
●访问数组列表元素:
数组列表自动扩展容量的便利增加了访问元素语法的复杂程度
数组元素列表依然使用for循环来遍历
有时候我们可以使用toArray方法将数组元素拷贝到一个数组中
4. 对象包装器与自动打包
●包装器:Integer,Long,Float,Double,Short,Byte,Character,Void,Boolean
一旦构造了包装器,就不允许更改在包装在其中的值
对象包装器类还是final的,因此不能定义他们的子类
自动打包与自动拆包
==运算符也可以应用于对象包装器对象,只不过检测的是对象是否指向同一个存储区域
打包和拆包是编译器认可的,而不是虚拟机。编译器在生成类的字节码时,插入必要的方法调用,虚拟机只是执行这些字节码
●参数数量可变的方法
省略号…是java代码的一部分,它表明这个方法可以接收任意数量的对象
5. 反射
●反射:能够分析类能力的程序被称为反射
可以用反射:
在运行时分析类的能力
在运行时查看对象
实现数组的操作代码
利用Method对象
●Class类
程序运行期间,java运行时系统始终为所有的对象维护一个被称为运行时的类型标识,这个信息保存着每个对象所属的类足迹。虚拟机利用运行时信息选择相应的方法执行
可以通过专门的java类访问这些信息,保存这些信息的类被称为Class
⑴ 使用Object类中的getClass()方法返回一个Class类型的实例
⑵ 利用静态方法forName获得字符串对应的Class对象
⑶ 如果T是任意的java类型,那么T.class将代表匹配的类对象
一个class对象实际上表示的是一个类型,而这个类型未必一定是一种类。
方法newInstance()可以用来快速地创建一个类的实例,newInstance方法调用默认的构造器,如果不存在默认的构造器就会抛出一个异常
●使用反射分析类的能力
6. 枚举类
在比较两个枚举类型时,永远不要调用equals,而直接使用==就可以了
最有用的方法:toString 能够返回枚举常量名
7. 继承设计技巧
●将公共操作和域放置在超类
●不要使用受保护域
任何人都可以有某个类派生一个子类,并编写代码直接访问protected的实例域,从而破坏了封装性
在同一个包中的所有类都可以访问,失去封装性
●使用继承实现“is-a”关系,非is-a关系不要使用继承
●除非所有继承的方法都有意义,否则不要使用继承
●在覆盖方法时,不要改变预期行为(不要违反置换原则)
●使用多态,而非类型信息
不要动不动就是用instanceof
●不要过多地使用反射
反射功能对于编写系统程序来说极其实用,但是通常不实用于编写应用程序。反射机制很脆弱,编译器很难帮助人们发现程序中的错误。任何错误只能在运行时才能被发现,并导致异常