类 、超类和子类
定义子类
关键字extends表示正在构造的新类派生于一个已存在的类,在通过扩展超类定义子类的时候,仅需要指出子类与超类的不同之处。
覆盖方法
子类可以定义新的方法来覆盖超类中的这个方法,由于子类不能直接访问超类的私有域,因此可以借助特定的关键字super来调用超类中公有接口。
子类构造器
在超类中可以通过super对超类构造器进行调用,从而实现对超类私有域的初始化,使用super调用构造器的语句必须是子类构造器的第一条语句。
如果子类的构造器没有显式调用超类的构造器,则将自动调用超类默认(没有参数)的构造器。如果超类没有默认构造器,并且在子类的构造器中没有显式地调用超类的其他构造器,则Java编译器将报告错误。
关键字this用途:1.引用隐式参数;2.调用该类其他的构造器;关键字super用途:1.调用超类的方法;2.调用超类的构造器;
- 多态:一个对象变量可以指示多种实际类型的现象;
- 动态绑定:在运行时能够自动选择调用哪个方法的现象;
多态
“is-a”规则表明程序中出现超类对象的任何地方都可以用子类对象置换,不能将一个超类的引用赋予子类变量。
理解方法调用
- 静态绑定:对于private方法、static方法、final方法或者构造器,编译器可以准确知道应该调用哪个方法,这种调用方式称为静态绑定;
- 动态绑定:依赖于隐式参数的实际类型,并且在运行时实现动态绑定。
每次调用方法都要进行搜索,时间消耗大,因此虚拟机预先为每个类创建了一个方法表,其中列出所有方法的签名和实际调用的方法。
组织继承:final类和方法
- final类:表示类不允许扩展,其中方法自动成为final,而不包括域;
- final方法:表示子类不能覆盖这个方法;
- final域:构造对象之后就不允许改变它们的值;
虚拟机中的即时编译器可以准确知道类之间的继承关系,并能够检测出类中是否真正地存在覆盖给定的方法。如果方法很简短,被频繁调用且没有真正地覆盖,那么即时编译器就会将这个方法进行内联处理。如果虚拟机加载了另外一个子类,而这个子类中包含了对内联方法的覆盖,则优化器将取消对覆盖方法的内联。
强制类型转换
将一个子类的引用赋给一个超类变量,编译器是允许的;将一个超类的引用赋给一个子类变量,必须进行类型转换,这样才能通过运行时的检查。
- 只能在继承层次内进行类型转换;
- 在将超类转换成子类之前,应该使用instanceof进行检查;
抽象类
使用abstract关键字可声明抽象方法,包含一个或多个抽象方法的类本身必须被声明为抽象的。除了抽象方法外,抽象类还可以包含具体数据和具体方法。
类即使不含抽象方法,也可以将类声明为抽象类。抽象类不能被实例化,即不能创建这个类的对象。可以创建一个抽象类的对象变量,但是它只能引用非抽象子类的对象。
受保护访问
Java中的受保护部分对所有子类及同一个包中的所有其他类都可见,比C++的保护机制安全性差。
Object:所有类的超类
在Java中,只有基本类型不是对象。所有的数组类型,不管是对象数组还是基本类型的数组都扩展了Object类。
equals方法
Object类中的equals方法用于检测一个对象是否等于另一个对象。在Object类中,这个方法将判断两个对象是否具有相同的引用。
在子类中定义equals方法时,首先调用超类的equals。如果检测失败,对象就不可能相等。如果超类中的域都相等,就需要比较子类中的实例域。
相等测试与继承
编写equals方法的建议:
1.显式参数名为otherObject,需要将它转换为另一个变量other;
2.检测this与otherObject是否引用同一个对象;
3.检测otherObject是否为null,如果为null,返回false;
4.比较this与otherObject是否属于同一类,如果equals的语义在每个子类中有所改变,就使用getClass检测,如果所有子类都属于统一的语义,就使用instanceof检测;
5.将otherObject转换为相应的类类型变量:
6.对所有需要比较的于进行比较,使用==比较基本类型域,使用equals比较对象域,如果所有的域都匹配,就返回true,否则返回false。如果在子类中重新定义equals,就要在其中包含调用super.equals(other)。
hashCode方法
散列表是由对象导出的一个整型值。由于hashCode方法定义在Object类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址。
如果重新定义equals方法,就必须重新定义hashCode方法,以便用户可以将对象插入到散列表中,equals与hashCode的定义必须一致。需要组合多个散列值时,可以调用Object.hash并提供多个参数。
若用相同的参数构造不同对象,则对象的散列码相同,若后续对对象进行修改,则对象散列码随之发生改变。
toString方法
用于返回表示对象值的字符串。建议通过调用getClass().getName()获得类名的字符串。
随处可见toString方法的主要原因是:只要对象与一个字符串通过操作符“+x”相连,Java编译就会自动调用toString方法,以便获得这个对象的字符串描述;如果x为任意对象,并调用System.out.println(x),println方法就会直接调用x.toString(),并打印输出得到的字符串。
Object类定义了toString方法,用来打印输出对象所属的类名和散列码。数组继承了Object类的toString方法,若要输出数组内容,需要调用静态方法Arrays.toString.
泛型数组列表
ArrayList是一个采用类型参数的泛型类,类型参数不允许是基本类型。
- add:使用add方法可以将元素添加到数组列表中,如果调用add且数组已经满了,数组列表就将自动创建一个更大的数组,并将所有的对象从较小的数组中拷贝到较大的数组中;
- ensureCapacity:如果已经清楚或能够估计出数组可能存储的元素数量,就可以在填充数组之前调用,也可以把初始容量传递给ArrayList构造器;
- size:返回数组列表中包含的实际元素数目;
- trimToSize:当确定数组列表大小不再发生变化时,调用函数,将存储区域的大小调整为当前元素数量所需的存储空间数目,垃圾回收器将回收多余的存储空间;
C++的vector向量是值拷贝,即赋值操作会构建一个新向量;Java的ArrayList的赋值语句,将两个向量引用同一个数组列表。
访问数组列表元素
使用get和set方法实现访问或改变数组元素的操作,只有i小于或等于数组列表的大小时,才能调用set函数,如下代码错误:
对象包装器与自动装箱
包装器(wrapper)用于将基本类型如int转换为对象Integer。对象包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值,且对象包装器类是final, 因此不能定义它们的子类。
装箱和拆箱是编译其认可的,而不是虚拟机,编译器在生成类的字节码时,插入必要的方法调用,虚拟机知识执行字节码。
参数数量可变的方法
…表示这个方法可以接受任意数量的对象,Object…参数类型相当于Objec[]。
枚举类
在比较两个枚举类型的值时,永远不需要调用equals,而直接使用“==”。
反射
能够分析类能力的程序称为反射,反射机制可以用来:
- 在运行时分析类;
- 在运行时查看对象;
- 实现通用的数组操作代码;
- 利用Method对象,这个对象类似C++中的函数指针;
Class类
在程序运行期间,Java运行时系统始终为所有对象维护一个运行时的类型标志,这个信息跟踪每个对象所属的类,虚拟机利用运行时类型信息选择相应的方法执行。保存这些信息的类称为Class。
捕获异常
将可能抛出已检查异常的一个或多个方法调用代码放在try块中,然后在catch子句中提供处理器代码。