文章有部分内容选自:http://blog.csdn.net/chenssy/article/details/12786385,感谢博主。
多态:
1.private方法被自动认定为是final方法,并且对子类是屏蔽的。
2.编译器强制每个子类(都继承自Object)必须调用构造器的原因:可以确保我们想要使用的成员对象都得到正确的初始化,在下一层子类的时候可以正确调用。
3.关于自定义特殊清理动作顺序:如果我们对清理垃圾有着特殊的要求,比如命名为dispose()方法的清理动作,关于它的清理顺序,有2个关注点(依赖对象的销毁和继承结构的销毁):
a.)依赖对象的时候,销毁的顺序与初始化的顺序相反。
A a =new A();
B b =new B();
b.dispose();
a.dispose();
B.)拥有继承结构的时候,先子类进行清理动作,然后再用super.dispose()进行清理动作。因为子类可能借助父类的1些成员完成销毁,如果先销毁父类,那么子类则完不成销毁。
4.多态不作用于域,只作用于普通的方法(父类方法非private,非final,非static的),因为任何域访问操作将由编译器解析。---------------------------------------------------------多态只作用于普通的方法调用。
5.引用计数的应用:当你有1个对象,共享于很多其他对象中的时候,关于清理动作,你必须小心谨慎的处理。我们使用引用计数来跟踪仍旧访问着共享对象的对象数量了。
private static long counter = 0; (long类型可以防止溢出)
private final long id = counter++
6.关于构造器中调用方法的问题:
public static void main(String[]args) {
new Son(5);
}
}
class Father{
public Father(){
System.out.println("Before");
draw();
System.out.println("After");
}
public void draw(){
System.out.println("Father draw");
}
}
class Sonextends Father{
private int radius = 1 ;
public Son(int r ){
radius =r;
System.out.println("Son "+radius);
}
public void draw(){
System.out.println("Son.draw "+radius);
}
结果: Before
Son.draw 0 (注意,此时的radius为0)
After
Son 5
原因分析:在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的0。
结论:如果你要在构造器中进行方法的调用,由于多态的原因,你很可能使用的是子类覆盖父类过后的方法,而这个覆盖过后的方法有可能对子类的域进行访问,然而在初始化父类的构造器的时候,子类的属性都被初始化为了0,所以你可能得不到预期的效果,而编译器还不会报错。所以你应该知道,在构造器中唯一能够安全调用的那些方法是基类中的final方法(包括private方法,默认未final的方法)。这些方法不能被覆盖,可以避免很多问题,例如多态。
7.协变返回类型:Java允许在子类的被覆盖方法可以返回父类方法的返回类型的某种子类。
class A {
}
class aextends A{
}
class B {
public A get(){
return new A();
}
}
class bextends B{
public a get(){
return new a();
}
}
8.用继承表达行为间的差异,用字段(可以理解为组合)表达状态的变化。
9.向下转型和类型识别(RTTI):
在继承层次结构中,父类一般没有子类的接口,因为父类是包含在子类当中的,如果用父类调用子类的方法,会出错。这时候可以进行向下转型,在java中,所有转型都会得到检查,在运行期间也会对类型进行检查。运行时的类型检查被称作“运行时类型识别”(RTTI)。
多态总结:
节选自:
http://blog.csdn.net/chenssy/article/details/12786385
所谓多态就是指程序中定义的引用变量(1)所指向的具体类型和(2)通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
Java实现多态有三个必要条件:继承、重写、向上转型。
向上转型的缺陷:
父类类型的引用可以调用父类中定义的所有属性和方法,对于只存在与子类中的方法和属性它就望尘莫及了
指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)。
当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。
当子类重写父类的方法被调用时,只有对象继承链中的最末端的方法才会被调用。
实现形式
在Java中有两种形式可以实现多态。继承和接口。
2.2.1、基于继承实现的多态
所以基于继承实现的多态可以总结如下:对于引用子类的父类类型,在处理该引用时,它适用于继承该父类的所有子类,子类对象的不同,对方法的实现也就不同,执行相同动作产生的行为也就不同。
如果父类是抽象类,那么子类必须要实现父类中所有的抽象方法,这样该父类所有的子类一定存在统一的对外接口,但其内部的具体实现可以各异。这样我们就可以使用顶层类提供的统一接口来处理该层次的方法。
2.2.2、基于接口实现的多态
继承是通过重写父类的同一方法的几个不同子类来体现的,那么就可就是通过实现接口并覆盖接口中同一方法的几不同的类体现的。
在接口的多态中,指向接口的引用必须是指定这实现了该接口的一个类的实例程序,在运行时,根据对象引用的实际类型来执行对应的方法。
继承都是单继承,只能为一组相关的类提供一致的服务接口。但是接口可以是多继承多实现,它能够利用一组相关或者不相关的接口进行组合与扩充,能够对外提供一致的服务接口。所以它相对于继承来说有更好的灵活性。
public class Wine {
public void fun1(){
System.out.println("Wine 的Fun.....");
fun2();
}
public void fun2(){
System.out.println("Wine 的Fun2...");
}
}
public class JNC extends Wine{
/**
* @desc 子类重写父类方法
* 父类中不存在该方法,向上转型后,父类是不能引用该方法的
* @param a
* @return void
*/
public void fun1(String a){
System.out.println("JNC 的 Fun1...");
fun2();
}
/**
* 子类重写父类方法
* 指向子类的父类引用调用fun2时,必定是调用该方法
*/
public void fun2(){
System.out.println("JNC 的Fun2...");
}
}
public class Test {
public static void main(String[] args) {
Wine a = new JNC();
a.fun1();
}
}
-------------------------------------------------
Output:
Wine 的Fun.....
JNC 的Fun2...
多态最经典案例:
public class A {
public String show(D obj) {
return ("A and D");
}
public String show(A obj) {
return ("A and A");
}
}
public class B extends A{
public String show(B obj){
return ("B and B");
}
public String show(A obj){
return ("B and A");
}
}
public class C extends B{
}
public class D extends B{
}
public class Test {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println("1--" + a1.show(b));
System.out.println("2--" + a1.show(c));
System.out.println("3--" + a1.show(d));
System.out.println("4--" + a2.show(b));
System.out.println("5--" + a2.show(c));
System.out.println("6--" + a2.show(d));
System.out.println("7--" + b.show(b));
System.out.println("8--" + b.show(c));
System.out.println("9--" + b.show(d));
}
}
结果:
1--A and A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--A and D
第4,5个看起来有点莫名其妙是吗?下面我们来分析。
其实我们在多态中,是有1个“继承链”的,它的优先级是(1)this.show(O)、(2)super.show(O)、(3)this.show((super)O)、(4)super.show((super)O)。,这里的this指的是引用而不是对象。从1到4逐渐降低。
分析5: A a2 = new B();这里this为A,A无父类(除了Object),所以现在只考虑(1)this.show(O)和(3)this.show((super)O),在A中没有show(C c)(注意,这里不要相当然的把方法参数多态了,在这里,参数是什么,就理解成什么),所以只有this.show((super)O)符合条件,即A.show(A a),然而因为多态性,在B中我们覆盖了A.show(A a)方法,同时由于a2是B类的一个引用且B类重写了show(A obj),调用的是b.show(A),所以是 B and A。
理解点:(1)this的理解。 (2)继承链和多态的混合理解。
分析4:在b中已经有了B.show(B b),应该显示B and B才对,为什么不是呢?
因为多态特性的要求是:多态方法是要求在父类中定义过的,并在子类重写。(可以理解为在A中只有show(A a )和show(D d),没有show(B b),而子类多了1个show(B b),这是子类自己的状态,而不是父类的基状态被子类重写成了多状态,所以这不是多态,从而无法使用多态的性质来进行判定。)
很显然在A类中没有A.show(B b),那么继续遵循继承链,同分析5。
接口:
1.抽象类中可以有具体实现,即非abstract方法,然而,接口是比抽象类更新一步的抽象形式,完全没有具体实现。(可有有方法名,返回类型,参数列表)
2.组合关系: has--a继承关系:is--a接口实现关系:is--like--a;
3.接口中的方法和域:
a) 接口中可以有域,但是他们都是隐式public、static和final的,且不能是空final的。
b) 接口中的方法隐式是public的。
3.interface很重要的特性:可以通过接口创建1个可以向上转型成多种基类的类型,来实现多重继承的特性,并仍然有多态特性。
5.实现接口类的实现方法的public特性:在实现类中会实现接口中的方法,我简称它为“实现方法”,因为在接口中的方法默认是public的,实现方法必须声明是public的,因为当你不声明public的时候,这个实现方法只具有包访问权限,这样在方法被继承中,它的可访问性就降低了,这是java编译器所不允许的。
6.完全解耦,如果你有个方法,它的方法参数是1个类的话,那么方法会过于耦合,因为在你向这个方法传参数的时候你只能往里面传这个类或者这个类的子类。如果把这个参数换成接口的话,任何类都可以实现这个接口并且可以拥有很多的继承结构,这样就实现了完全解耦。
7.使用接口的核心原因:
a) 第一个原因是为了可以向上转型为多个基类型。
b) 第二个原因是为了确保客户端程序员不能生成该类的对象,从而确保它仅仅是1个接口.
8.接口的常用方法就是"策略模式",”工厂模式”。
9.因为在接口中的域都是static和final的,所以在Java SE5之前没有引入enum的时候,接口可以用来创建常量组。
public interface Maths{
int
ONE = 1,TWO = 2,THREE = 3;
}
10.接口嵌套相关:
a) 在类中嵌套接口:如果在A类中我们有1个B的接口(public或者包访问权限),那么我们可以用A.B的方式访问它。如果我们的接口是private的,则我们不能在定义它的类之外实现它。
b) 在接口中嵌套接口:在接口中所有字段都是public的,所以接口也默认是public的,而不能声明是private的。
11.接口的滥用:
你不能所有东西都直接使用接口,接口应该是应真正的需求而产生的,当必须时,你应该重构接口而不是到处添加额外级别的间接性,因为这会让你的程序附加上额外的复杂性。