Java 多态

<span style="font-family: Arial, Helvetica, sans-serif;">Java 多态机制解析</span>

面向对象编程有三个特点:封装、继承和多态

封装:隐藏了类的内部实现机制,从而可是实现在不影响使用者的前提下改变类的内部结构,保护了内部数据。

继承:为了重用父类代码,同时为多态做准备。

多态:向对象发送消息,让该对象自行决定采取何种行为。

方法的重写、重载和动态连接构成多态性。

class Animal{
public String name;
public void eat(String food){
System.out.println("用嘴吸着吃");
}
}
 
class Dog extends Animal{
public int age;
public void eat(String food){
System.out.println("用嘴嚼着吃");
}
}


我们可以这样定义:

Dog dog = new Dog();

Animal a = new Dog();

 

子类是父类的改进和扩充,所以子类在功能上比父类强大,属性也比父类更独特。

定义一个父类的引用指向一个子类对象,既可以调用使用子类强大的功能又可以抽取父类的共性。

所以,父类类型的引用可以调用父类中所有的非私有的属性和方法,对于子类中定义而父类中没有的成员,它是无可奈何的。

同时,对于一个在父类中定义,且在子类中没有被重写的方法,才可以被父类类型的引用调用。

如果在子类中重写(Override)了父类中定义的方法,在父类类型的引用变量只能调用子类中的方法,不能调用父类中的方法,这就是动态连接。

</pre></p></td></tr></tbody></table><p><pre name="code" class="java">public class Java_Poly {
public static void main(String[] args) {
Father f = new Father();
f.f(); // AAAA
f.g(); // AAAA
Child c = new Child();
//调用从父类继承过来的方法,在内部调用了被子类重写的g()方法
c.f(); //CCCC 
c.f(123); //BBBB
c.g(); //CCCC
c.h(); //DDDD
Father fa = new Child();
//调用从父类继承过来的方法,在内部试用了被子类重写的g方法。
fa.f(); //CCCC
//调用被子类重写的g方法
fa.g(); //CCCC
//fa.h(); //error 因为父类中没有此方法,无法调用
//fa.f(123); //error 因为父类中没有此方法,无法调用
 
}
}
class Father{
public void f(){
g();
}
//这是父类中的g()方法,下面的子类重写了该方法,
//父类类型的引用调用g()方法时,次方法不再有效,而会使用子类中被重写的方法
//父类类型的引用变量调用f()方法,其内部也会调用子类中被改写的g()方法
public void g(){
System.out.println("AAAA");
}
}
 
class Child extends Father{
//重载父类中的方法,由于父类中没有该方法,所以父类对象无法调用
public void f(int i){
System.out.println("BBBB");
}
//重写父类中的方法
//如果父类类型的引用变量调用该方法,自然会调用被子类重写后的g()方法
public void g(){
System.out.println("CCCC");
}
//新增加自己特有的方法
public void h(){
System.out.println("DDDD");
}
}

对于多态,总结如下:

1、使用父类类型的引用指向子类对象

2、该引用只能调用父类中定义的方法和变量

3、如果子类中重写了父类中的一个方法,那么调用这个方法的时候,将会调用子类中的这个方法。(动态连接,动态调用)

4、变量不能被重写(或覆盖),“重写”的概念只针对方法,若重写父类中的变量编译时将会报错。

 

再解多态:

多态是通过一下两种方式实现的:

1、接口和实现接口的并覆盖接口中同一方法的几个不同类实现的

2、父类和继承父类的并覆盖父类中同一方法的几个不同子类实现的

多态性:

发送消息给某个对象,让该对象自行决定该调用何种行为。

通过将子类对象引用变量赋值给超类对象引用变量来实现动态方法调用。

 

JAVA里没有多继承,一个类之能有一个父类。而继承的表现就是多态。一个父类可以有多个子类,而在子类里可以重写父类的方法(例如方法print()),这样每个子类里重写的代码不一样,自然表现形式就不一样。这样用父类的变量去引用不同的子类,在调用这个相同的方法print()的时候得到的结果和表现形式就不一样了,这就是多态,相同的消息(也就是调用相同的方法)会有不同的结果。

多态性应遵守的原则:

当父类对象引用变量引用子类对象时,被引用对象的类型而不是引用引用变量的类型决定了调用谁的方法,即子类对象的类型决定了调用谁的方法,但是这个被调用的方法必须是在父类中定义过的,并且被子类覆盖了的方法。

 

Java多态性实现机制:

SUN目前的JVM实现机制,类实例的引用就是指向一个句柄(handle)的指针,这个句柄是一对指针: 

一个指针指向一张表格,实际上这个表格也有两个指针(一个指针指向一个包含了对象的方法表,另外一个指向类对象,表明该对象所属的类型); 

另一个指针指向一块从java堆中为分配出来内存空间。

 

1、通过将子类对象引用赋值给超类对象引用,来实现动态方法调用。

class Automobile{

public void play(){……}

……

}

class Car extends Automobile{

//重写了父类中的方法

public void play(){……} 

......

}

我们可以使用:

Car c = new Car();

Automobile a = c; //将子类对象引用赋值给父类对象引用

或者写成: Automobile a = new Car();

a.play(); //将调用子类中被重写过的方法play

 

分析:

1、为什么子类类型的对象的引用可以赋值给父类类型对象的引用?

自动实现向上转型。

通过该语句,编译器会自动将子类实例向上移动,成为通用类型Automobile。

2、a.play()将执行子类还是父类中的方法?

子类的。

在运行期间,将根据a对象这个实例的类型来获取相应的方法,所有才有多态性。一个基类的对象引用被赋予不同的子类对象引用,执行该方法时将表现出不同的行为。

在a=c执行后,仍然存在两个句柄,a和c,但是他们拥有同一块数据内存和不同的函数表。

3、不能把父类类型引用变量赋值给子类类型引用变量。

因为Java中可以自动进行向上转型,但是如果需要向下转型的话,需要进行强制类型转换。

Car c = new Car();

Automobile a = new Automobile();

a = c; //OK

c = a; //Error

c = (Car)a; //OK

所以,要想调用子类含有而父类不含有的方法,就需要向下转型,进行强制类型转换。

4、记住一个简单而又复杂的规则:一个类型的引用变量只能引用该类型自身含有的属性和方法。

你可能说这个规则不对的,因为父类引用指向子类对象的时候,最后执行的是子类的方法的。 
其实这并不矛盾,那是因为采用了后期绑定,动态运行的时候又根据型别去调用了子类的方法。而假若子类的这个方法在父类中并没有定义,则会出错。 

Java的所有函数,除了被声明为final的,都是用后期绑定。

对于抽象类的多态:

如果子类继承的超类是一个抽象类,虽然抽象类不能通过new操作符实例化, 但是可以创建抽象类的对象引用指向子类对象,以实现运行时多态性。不过,抽象类的子类必须覆盖实现超类中的所有的抽象方法,否则子类必须被abstract修饰符修饰,当然也就不能被实例化了。 

 

从对象的内存角度来理解试试.

假设现在有一个父类Father,它里面的变量需要占用1M内存.有一个它的子类Son,它里面的变量需要占用0.5M内存.

现在通过代码来看看内存的分配情况:

Father f = new Father();//系统将分配1M内存.

Son s = new Son();//系统将分配1.5M内存!因为子类中有一个隐藏的引用super  

会指向父类实例,所以在实例化子类之前会先实例化一个父类,也就是说会先执行父类的构造函数.由于s中包含了父类的实例,所以s可以调用父类的方法.

Son s1 = s;//s1指向那1.5M的内存.

Father f1 = (Father)s;//这时f1会指向那1.5M内存中的1M内存,即是说,f1只是指向了s中实例的父类实例对象,所以f1只能调用父类的方法(存储在1M内存中),而不能调用子类的方法(存储在0.5M内存中).

Son s2 = (Son)f;//这句代码运行时会报ClassCastException.因为f中只有1M内存,而子类的引用都必须要有1.5M的内存,所以无法转换.

Son s3 = (Son)f1;//这句可以通过运行,这时s3指向那1.5M的内存.由于f1是由s转换过来的,所以它是有1.5M的内存的,只是它指向的只有1M内存.

示例:

class Father{
void print(){};
}
class Son extends Father{
void print(){System.out.println("子类中!");}
void show(){System.out.println("show 中!");}
}
class Demo{
public static void main(String args[]){
Father obj=new Son();
obj.print();
obj.show();  //这个调用会报错!
}
}
如果你想实现多态,那么必须有三个条件,父类引用,子类对象,方法覆盖

这里如果Fathor类有一个show()方法,那么形成方法覆盖,此时就可以这么写:obj.show(),此刻形成了多态. 

没有方法覆盖,那你这里只能解释为父类引用去访问一个子类的方法,当然,父类引用没有这么大范围的权限,当然会报错

 

PS:多态实际上是一种机制,在编译时刻,会生成一张虚拟表,来记录所有覆盖的方法,没有被覆盖的方法是不会记录到这张表的.若一个父类引用调用了没有覆盖的子类方法,那么是不符合该表的,那么编译时刻就会报错. 在执行程序的时候,虚拟机会去这张虚拟表中找覆盖的方法,比如引用中实际上存的是一个子类对象引用,那么就会去找子类中的相应的覆盖的方法来执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值