本章目录:
- 一、类、超类和子类
- 1.多态
- 2.动态绑定
- 3.阻止继承:final类和方法
- 4.抽象类
- 5.访问修饰符总结
- 二、Object类:所有类的父类
- 1.equals方法
- 2.hashcode方法
- 3.toString方法
- 三、对象包装器与自动装箱
- 四、反射
- 1.Class类
- 2.捕获异常
- 3.利用反射分析类的能力
- 4.在运行时使用反射分析对象
一、类、超类和子类
- 子类继承父类,二者之间是is-a关系,子类拥有父类的全部资源,并且可以添加特有的域和方法。
- 子类无法删除。
- java不支持多继承。
- 子类可重写父类的方法,注意,重写和方法重载是不同的概念。 在覆盖(重写)一个方法的时候,子类方法不能低于超类方法的可见性。特别是,如果超类方法是 public,子类方法一定要声明为 public。
- 子类如何调用父类方法:super.父类方法名()。
- 子类构造器中如何调用父类构造器:super.父类名(参数列表),注意,这句代码必须在子类构造器中的第一句。
如果子类的构造器没有显式地调用超类的构造器,则将自动地调用超类默认(没有参数)的构造器。
如果超类没有不带参数的构造器,并且在子类的构造器中又没有显式地调用超类的其他构造器,则 Java 编译器将报告错误。
- 多态:一个对象变量可以指示多种实际类型的现象被称为多态。在运行时能够自动地选择调用哪个方法的现象称为动态绑定。
1.多态
- 在 Java程序设计语言中,对象变量是多态的。一个父类变量既可以引用一个父类对象,也可以引用任何一个子类的对象。
- 然而,不能将一个父类的引用赋给子类变量。
- 警告∶ 在 Java中,子类数组的引用可以转换成超类数组的引用,而不需要采用强制类型转换。例如,下面是一个经理数组
2.动态绑定
前面已经说过了,一个对象变量可以指示多种实际类型的现象被称为多态,在运行时能够自动地选择调用哪个方法的现象称为动态绑定。
下面请看调用方法的过程:
- 编译器查看对象的声明类型和方法名。假设调用x.f(param),且隐式参数x声明为C类的对象。需要注意的是∶ 有可能存在多个名字为f,但参数类型不一样的方法。编译器将会一一列举所有C类中名为f的方法和其超类中访问属性为 public 且名为f的方法(超类的私有方法不可访问)。至此,编译器已获得所有可能被调用的候选方法。
- 重载解析:接下来,编译器将查看调用方法时提供的参数类型。如果在所有名为f的方法中存在一个与提供的参数类型完全匹配,就选择这个方法。这个过程被称为重载解析。由于允许类型转换(int 可以转换成 double,Manager类可以转换成 Employee;类,等等),所以这个过程可能很复杂。如果编译器没有找到与参数类型匹配的方法,或者发现经过类型转换后有多个方法与之匹配,就会报告一个错误。至此,编译器已获得需要调用的方法名字和参数类型。
不过,返回类型不是签名的一部分,因此,在覆盖方法时,一定要保证返回类型的兼容性。允许子类将覆盖方法的返回类型定义为原返回类型的子类型。例如,假设
- 如果是 private 方法、static方法、final方法或者构造器,那么编译器将可以准确地知道应该调用哪个方法,我们将这种调用方式称为静态绑定。
- 当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实际类型最合适的那个类的方法。否则,将在D类的超类中寻找该方法,以此类推。
每次调用方法都要进行搜索,时间开销相当大。因此,虚拟机预先为每个类创建了一个方法表,其中列出了所有方法的签名和实际调用的方法。
这里需要提醒一点,如果调用super.f(param),编译器将对隐式参数超类的方法表进行搜索。
3.阻止继承:final类和方法
用 final修饰的类是禁止继承的;用final修饰的方法,则表明该方法不允许类的子类重写;final类中的所有方法默认为是final方法。
在早期的Java中,有些程序员为了避免动态绑定带来的系统开销而使用final关健字。如果一个方法没有被覆盖并且很短,编译器就能够对它进行优化处理,这个过程为称为内联。例如,内联调用e.getName()将被替换为访问e.name域。然而,如果getName()在另外一个类中被覆盖,那么编译器就无法知道覆盖的代码将会做什么操作,因此也就不能对它进行内联处理了。
幸运的是,虚拟机中的即时编译器比传统编译器的处理能力强得多。这种编译器可以准确地知道类之间的继承关系,并能够检测岀类中是否真正地存在覆盖给定的方法。如果方法很简短、被频繁调用且没有真正地被覆盖,那么即时编译器就会将这个方法进行内联处理。如果虚拟机加载了另外一个子类,而在这个子类中包含了对内联方法的覆盖,那么将会发生什么情况呢?优化器将取消对覆盖方法的内联。这个过程很慢,但却很少发生。
4.抽象类
为了提高程序的清晰度,包含一个或多个抽象方法的类本身必须被声明为抽象的。
abstract class Person{……public abstract String getDescription();
}
- 除了抽象方法之外,抽象类还可以包含具体数据和具体方法。但尽量使得抽象类中不包含具体方法。
- 抽象方法充当着占位的角色,它们的具体实现在子类中。不过,在子类中定义部分抽象方法或抽象方法也可以不定义,这样就必须将子类也标记为抽象类。
- 类即使不含抽象方法,也可以将类声明为抽象类。
- 抽象类不能被实例化;可以定义一个抽象类的对象变量,但是它只能引用非抽象子类的对象。
5.访问修饰符总结
- 对本类可见private
- 对所有类可见public
- 对本包和所有子类可见protected
- 默认本包可见
二、Object类:所有类的父类
可以使用Object类型的变量引用任何类型的对象。
1.equals方法
检测一个对象和另一个对象是否相等,用于判断两个对象是否具有相同的引用。
在子类中定义 equals 方法时,首先调用超类的 equals。如果检测失败,对象就不可能相等。如果超类中的域都相等,就需要比较子类中的实例域。
Java中的equals方法具有以下特性:
- 自反性∶ 对于任何非空引用x,x.equals(x)应该返回 true。
- 对称性∶ 对于任何引用x和y,当且仅当yequals(x)返回true,x.equals(y)也应该返回 true。如果x和y不属于同个类,可能不适用。
- 传递性∶对于任何引用x、y和z,如果x.cquals(y)返回true,yequals(z)返回true, x.equals(z)也应该返回 true。
- 一致性∶如果x和y引用的对象没有发生变化,反复调用x.equals(y)应该返回同样的结果。
- 对于任意非空引用x,x.equals(null)应该返回 false。
2.hashcode方法
散列码是由对象导出的一个整型值。散列码是没有规律的。如果x和y是两个不同的对象,x.hashCode()与y.hashCode()基本上不会相同。在表5-1中列出了几个通
3.toString方法
三、对象包装器与自动装箱
- 对象包装器:
所有的基本类型都有一个与之对应的类。这些类称为包装器∶Integer、Long、Float、Double、Short、Byte、Character、Void和 Boolean(前 6个类派生于公共的超类 Number)。对象包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。同时,对象包装器类还是 final,因此不能定义它们的子类。
泛型是能是引用类型而不能是基本类型。
- 自动装箱:
list是一个集合对象,list.add(3)将自动地变换成list.add(Integer.valueOf(3)。这一过程就是自动装箱。
int n= list.get(i)将自动被翻译成int n=list.get(i).intValue()。这一过程就是自动拆箱。
最后强调一下,装箱和拆箱是编译器认可的,而不是虚拟机。编译器在生成类的字节码时,插入必要的方法调用。虚拟机只是执行这些字节码。
四、反射
1.Class类
在程序运行期间,保存对象的运行时类型信息的类被称为 Class。
Employee e;……Class c1=e.getClass();//获取Class对象String classType=c1.getName();//获取e的类名,包含了包前缀String name=e.getName();//获取e的对象名Class c2=Class.forName(classType);//也可通过已知的类名获取Class对象Class c3=Date.class;//也可通过这种方式获取类对象Class c4=int.class;CLass c5=Double[].class;Object o=e.getClass().new Instance();//创建一个和e同类型的实例,调用的是默认构造器
2.捕获异常
try{//可能抛出异常的语句}catch(Exception e){//处理器语句}
一旦try中出现异常,将跳过剩余的部分,直接进入catch中进行处理;若try中没有出现异常,则跳过catch中的部分。
3.利用反射分析类的能力
说白了就是通过调用Class类中的方法得到关于所涉及对象的域、方法、构造器、访问权限修饰符相关的信息,这部分学习下Java API中有关Class的部分就好。
4.在运行时使用反射分析对象
在代码运行时获取到对象的状态等。好好看看API。