Java中多态的理解
继承的基础知识
Java中有三大特性:继承、封装、多态
多态可以说是建立在继承的基础上的,所以我们先来理解一下继承。
Java中继承的概念是:继承是指一个对象直接使用另一对象的属性和方法(此处引用百度百科)。也就是当一个子类继承了一个父类时,子类也同时继承了父类的非静态属性和方法(可以继承父类的private方法、变量、只是无法通过子类对象修改),那么意思就是子类对象可以调用父类的方法并且可以访问父类的变量(只能改变非私有变量)。
如下例子:
class Father{
public String name;
private String id_num;
public static int age;
public void say(){
System.out.println("父类说...");
}
}
class Son extends Father{
public void say(){
System.out.println("子类说...");
}
}
public class Main{
public static void main(String[] argv){
Son son = new Son();
//可以正确访问并修改
son.name = "zdy";
//可以正确访问但不能修改
son.id_num = 04152017 //此时编译器会报错,因为子类只能访问不能修改父类的private属性
//无法从父类继承static的属性和方法(因为静态属性和方法是初始化的时候就已经存在的)
//会执行子类的say方法,因为我们对父类的方法进行了重写(多态)
son.say(); //会输出“子类说...”
}
}
Java中多态的理解
简单了解完了Java中的继承,我们就来看看多态吧,什么是多态?从字面意思上理解多态就是有多种形态。在编程中我们也可以将多态理解为有多种形态,具体是如何体现多种形态的呢?一般通过两种方式实现多态:覆盖(重写)、重载。
这两种方式有什么区别又有什么联系?
首先区别:
1.覆盖要求子类中的某一方法名和参数和父类中的某一方法名和参数完全一致。而重载只要求方法名一样而参数名必须不 一致
2.覆盖要求子类中的方法与父类中被覆盖的方法返回类型必须一致。而重载可以不一致(参数列表必须不一致)。
3.覆盖要求子类覆盖父类的方法中不能有新抛出的异常类型。而重载允许有新抛出的异常类型。
4.覆盖要求子类覆盖父类的方法的访问权限必须比父类的对应方法大(public > protected > default > private)。而重载 则没有这种限制。
联系:
1.都是Java中多态性的典型体现。
2.都基于Java的继承机制。
理解了上面的基本概念后我们应该对多态有了一个初步的认识,但是多态真正的概念是:指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
为了更好地理解多态,我们引入一个概念:“向上转型”。我们先来看如下示例代码:
public class Main{
public static void main(String[] argv){
Father f = new Son();
f.say();
}
}
Father类和Son类仍沿用上面示例的类。这种写法可能很多初学者都理解不了了,本应指向Father类的对象怎么能指向Son类呢?这时就是向上转型了。Son类的对象向上转型为Father类的对象,然后被Father类的变量引用了。这就是我们所说的运行时多态,特点就是:父类对象在运行时可以表现子类特征。所以这里调用say方法时看子类中是否重写了say方法,如果重写了say方法那么调用子类中重写的say方法,反之调用父类中的say方法。
看一个很经典的案例:
class A{
public void say(A obj){
System.out.println("A and A");
}
public void say(D obj){
System.out.println("A and D");
}
}
class B extends A{
public void say(B obj){
System.out.println("B and B");
}
public void say(A obj){
System.out.println("B and A");
}
}
class C extends B{
}
class D extends D{
}
public class Main{
public static void main(String[] args){
A a = new B();
B b = new B();
a.say(b);
}
}
我们分析一下这段代码的运行结果,首先我们创建了一个A类的变量a指向B类的对象,然后正常实例化一个B类的引用变量b,最后用a调用say方法将b作为参数传进去。用a调用say方法,由于对象a在运行时体现的是子类的特征所以我们在子类中找say方法,在看传入的参数,是一个B类的实例对象,看到这里很多人会认为答案是“B and B”(包括几天前的我),那么就错了,首先我们定义的是一个A类的引用变量,这个引用变量能调用的方法只有父类的方法和子类中重写父类的方法,say(B obj)方法明显是重载父类的方法,所以我们是不能调用的,那为什么会调用say(A obj)方法呢?首先我们知道,运行时多态是会向上转型的,B类的对象实例会转为A的对象实例,那么参数就满足了,由于A类中的say(A obj)方法被子类B重写所以只会调用子类的方法,所以,最终的输出结果是“B and A”。
实际上多态中有一个方法调用的优先级:this.fun(obj) > super.fun(obj) > this.fun(super(obj)) > super.fun(super(obj))
我们可以从内存的角度来解释一下,首先A类的引用变量可以指向成员变量和成员方法的数目是不能变的,所以多态中父类的引用变量只能引用父类与子类共有的部分(总数不变),共有的部分指父类的所有属性和方法,如果子类中有重写父类的属性和方法则包括重写的属性和方法,无法引用子类中特有的属性和方法。