java面向对象--继承与多态
可以为一个变异单元中的每个类创建一个main方法,只有命令行所调用的那个类的main方法才会被调用,这样方便进行单元测试。
继承时,一般将所有的数据成员都指定为private,将所有的方法指定为public。
当创建一个子类的对象时,该对象内部包含了一个父类的子对象(subobject??)。这个对象和用父类直接创建的对象是一样的。
关键字super并不是一个对象的引用,因为不能将super赋值给另外一个对象变量。super只是一个只是编译器调用直接父类成员变量的特殊关键字。有两种用途:一是调用父类的方法;二是调用父类的构造器。
如果父类构造器为无参构造器,子类构造器中可以不用写调用父类构造器的语句,编译器会自动加上。但是如果父类构造器有参数时,在子类构造器中必须显式的通过super(arg)调用父类的构造器,否则会报错。
子类可以override父类方法,也可以overload父类的方法。
所有的override都使用@Override注解,可以防止不想重载时而意外的进行了重载。
重写(Override)
重写是子类对父类允许访问的方法的实现过程进行重新编写。好处在于子类可以根据需要,定义特定于自己的行为。
当父类中的方法被覆盖了后,除非用super关键字,否则就无法再调用父类中的方法。
重写规则:两同两小一大。
方法名、参数列表必须相同;返回值类型和抛出的异常更小或相等;访问权限更大或相等。
声明为final的方法不能被重写;
声明为static的方法不能被重写,但是能够被再次声明;
重写的方法能够抛出任何非强制异常,不能抛出新的或更广泛的强制性异常;
构造方法不能被重写;
父类的成员方法只能被它的子类重写,如果不能继承一个方法,则不能重写这个方法;
子类和父类在同一个包中,声明为private和final的方法不可被子类重写;
子类和父类不在同一个包中,子类只能重写父类的声明为public和protected的非final方法;
被覆盖的方法不能为static,因为静态方法是在编译的时候把静态方法和类的引用类型进行匹配。
组合和继承
组合是”has-a”的关系,在新类中嵌入一个现有类的private对象,新类的用户看到的是为新类所定义的接口,而不能看到嵌入对象的方法。
extend关键字的英文意思是扩展,表明子类对父类的扩展,子类是一种特殊的父类。
继承是”is-a”的关系,使用某个现有类,并开发一个它的特殊版本。通常使用的场景:
1. 子类需要额外增加属性,而不仅仅是属性值的改变。
2. 子类需要增加自己独有的行为方式(包括增加新的方法或重写父类的方法)。
只有必须向上转型时才用继承,否则优先考虑使用组合。
向上转型
为什么需要?
子类不仅是子类的类型,而且也是父类的类型。向上转型是从一个较专用类型向较通用类型转换,一般比较安全,唯一可能反生的事情是接口变小丢失子类对父类扩展的方法。因为编译期虚拟机会预先为每个类生成方法表(父类不会具有子类的方法,但是子类会具有父类的方法),向上造型的引用变量调用子类特有的方法,会报错。
final关键字:一旦(在初始化过程中)被设置为初始值之后,就不能再改变。
final数据:static final编译时期常量,全用大写字母表示,单词间用下划线’_’分割。
对于基本类型,final使数字恒定不变;而对于对象引用,final使引用恒定不变,但引用的对象可以发生改变。
空白final:被声明为final但又未给定初值的成员变量。需确保在使用前初始化。
final参数:无法在方法中更改参数引用所指向的对象。
final方法:确保在继承中方法行为保持不变,并且不会被覆盖或修改。
类中所有的 private 方法都隐式地是 final 的。
final类:不允许做任何变动,没有子类,所以方法也隐式地为final的。
不可变类(immutable):创建该类的实例之后,该实例的属性是不可变的。如果不可变类包含引用类型的属性,可以在内部重新构建一个一模一样的对象赋值给引用,使引用脱离外部影响。
多态
相同类型的父类引用变量,执行同一个方法时呈现出不同的行为,这就是多态。
“封装”通过合并特征和行为来创建新的数据类型。“实现隐藏”则通过细节私有化将接口和实现分离开来。而多态的作用则是消除类型之间的耦合关系,将多种子类型视为同一父类型进行处理(父类型的引用变量可以引用不同子类型的对象),这样同一份代码可以毫无差别地运行在这些不同子类型上,不用为每一个子类型重复定义相同逻辑的处理方法。
多态用父类型引用调用相应的方法,如何确定是哪个具体子类型呢?
绑定指的是将一个方法的调用与方法的主体关联起来java中,绑定分为静态绑定和动态绑定(dynamic binding),或者叫做前期绑定(early binding)和后期绑定(late binding)。
静态绑定:在程序执行前方法已经被绑定(也就是说在编译过程中就已经知道这个方法到底是哪个类中),此时由编译器或其它连接程序实现。
在java中可以简单理解为程序编译期的绑定,只有被final,static,private修饰的方法和构造方法是前期绑定的,由声明的类型决定。
java的编译过程是将java源文件编译成字节码(jvm可执行代码,即.class文件)的过程,在这个过程中java是不与内存打交道的,编译器会进行语法的分析,如果语法不正确就会报错(比如编译器只允许对象调用在类中声明的函数)。
Java的运行过程是指jvm(java虚拟机)装载字节码文件并解释执行,在这个过程才创立内存空间,执行java程序。
动态绑定:在运行时根据对象的类型进行绑定。之所以存在动态绑定,就是因为有些方法需要实例化对象后才能确定是哪个类的。
若一种语言实现了后期绑定,必须提供一些机制,可在运行期间判断对象的类型,并分别调用适当的方法。也就是说,编译器一直不知道对象的类型,但方法调用机制能找到正确的方法主体并加以调用。
动态绑定的过程:
虚拟机提取对象的实际类型的方法表(虚拟机预先为每个类创建的表,列出了所有方法的签名,以后方法调用时就无需搜索,只要查表就性);虚拟机搜索方法签名;调用方法。
动态绑定的特性:无需对现存的方法代码进行重新编译就可以对程序进行扩展。
动态绑定的典型发生在父类和子类的转换声明之下:Parent p = new Child();
注意:运行时(动态)绑定针对的范畴只是对象的方法,类中的成员变量(实例变量和类变量)是一般意义上的静态绑定,由编译器解析,只能访问引用变量类型所具有的属性。所以在向上转型后,父类的引用调用哪个子类型的方法由子类型对象决定,而访问对象属性时,系统总是访问它编译时类所定义的属性,即由向上造型后的父类型引用决定(子类对父类成员变量的隐藏)。
重载的方法签名是完全不同的,所以静态绑定就可以确定到底是哪个方法了;而重写的方法签名是可以和超类一样的,需要实例化对象之后才能确定是哪个类中的方法。
super.getClass().getName() //返回当前子类的类名
package demo3; class Super { public int field = 0; public int getField() { return field; } } class Sub extends Super { public int field = 1; public int getField() { return field; } public int getSuperField() { return super.field; } } public class FieldAccess { public static void main(String[] args) { Super sup = new Sub(); // Upcast System.out.println("sup.field = " + sup.field + ", sup.getField() = " + sup.getField()); Sub sub = new Sub(); System.out.println("sub.field = " + sub.field + ", sub.getField() = " + sub.getField()+ ", sub.getSuperField() = " + sub.getSuperField()); } } /* * Output: sup.field = 0, sup.getField() = 1 sub.field = 1, sub.getField() = 1, * sub.getSuperField() = 0 */// :~
向下转型
父类型的引用变量转向更具体的子类型,不安全,有可能会导致ClassCastExecption。转型之前先用instanceof 关键字判断一下。
instanceof:is a runtime check,判断前者引用变量是否是后者实现类或接口的实例,通常instanceof前后的操作数有实现或继承关系,否则在编译时就会出错,而不会等运行时再返回false,效率更高。
new Date() instanceof String;// Incompatible conditional operand types Date and String
super和this的区别
1)super(参数):子类调用父类的某一个构造函数(应该为子类构造函数中的第一条语句)
2)this(参数):调用本类中重载的构造函数(应该为构造函数中的第一条语句)
3)super: 基类与派生类中有相同成员定义时,用来访问当前对象的直接父类中的成员(即访问直接父类中被隐藏/覆盖的成员数据或方法)。如:super.属性名,super.方法名。
4)this:它代表当前对象的引用(如果函数的形参与类的属性同名,这时需用this来指明对象的属性)
5)调用super()必须写在子类构造方法的第一行,否则编译不通过。每个子类构造方法的第一条语句,都是隐含地调用super()。
6)尽管可以用this调用一个构造器,但却不能调用两个。
7)this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
8)this()和super()都指的是对象,所以都不可以在static环境中使用。包括static变量,static方法和static语句块。
9)从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。