java学习(十一)——多态、方法调用

多态

对于某一个对象(事物),在不同的时刻体现出来的不同状态叫做多态

如:    水的液态、固态和气态
            学生在上课时间上课,在吃饭时间吃饭,在休息的时候睡觉

在java中,对象变量是多态的,一个超类对象除了可以引用一个自身类对象,还可以引用它的派生类对象。通过继承和方法重写来实现多态。

对象间的转型问题

在java中,每个对象变量都属于一个类型,类型描述了这个变量所引用的以及能够引用的对象类型, 将一个值存入变量时,编译器会检查是否允许该操作

1.向上转型将一个子类的引用赋给一个超类变量,编译器是允许的,不用进行强制类型转换。

                格式:    超类 超类变量=new 子类();

2. 向下转型但是将一个超类的引用赋给 一个子类变量,必须进行强制类型转换,这样才能够通过运行时的检查

                格式:    子类 子类对象变量名=(子类)父类对象引用

class Animal
{
	public int age=40;

	public void eat()
	{
		System.out.println("吃东西");
	}
	public void sleep()
	{
		System.out.println("睡觉");
	}
} 

class Cat extends Animal
{
	public int age=5;
	//重写了父类的方法
	public void eat()
	{
		System.out.println("猫吃老鼠");	
	}
	public void playGame()
	{
		System.out.println("玩毛线球");
	}
}
class Dog extends Animal
{
	public int age=3;
	//重写了父类的方法
	public void eat()
	{
		System.out.println("狗啃骨头");	
	}
}
class AnimalTest
{
	public static void main(String[] args)
	{
		
		//向上转型:不需要强制类型转换
		Animal a=new Cat();
		System.out.println(a.age);	//调用的是父类的成员变量
		a.eat();					//调用的是子类Cat的方法
		//a.playGame();				//多态的弊端:无法访问子类特有的方法		error:类型为Animal的变量a找不到方法playGame()
		a.sleep();					//调用的是子类Cat的方法

		/*
			结论:当超类类变量引用子类对象
				访问成员变量时:访问的是父类的成员变量
				访问成员方法时:先访问的是超类的此方法,如果此方法被子类覆盖,则调用子类的方法。并且不能访问子类特有的方法
		*/
		System.out.println("------------");
		//向下转型:需要强制类型转换
		Cat cat=(Cat)a;
		System.out.println(cat.age);	//访问的是子类Cat的成员变量
		cat.eat();					//访问的是子类Cat的方法
		cat.playGame();				//可以访问子类Cat特有的方法
		//Cat cat2=a;				//error:Animal无法转换为Cat

		System.out.println("------------");
		//Dog dog=(Dog)a;		
		/*上条语句在编译时并不会出现错误,这是因为编译器认为超类变量a可能是Dog类,但是在运行时会抛出一个ClassCastException(类型转换异常)
		如果没有捕获这个异常,那么程序就会异常终止,如何解决这个问题呢?
			在进行类型转换之前,先查看是否能够成功的转换,通过使用instanceof操作符来实现。如下
		*/
		if(a instanceof Dog)
		{
			Dog dog=(Dog)a;	
			System.out.println("可以进行转换");
		}
		else
		{
			System.out.println("不可以进行转换");	
		}

        System.out.println("-----------instanceof 操作符-----------------");
        System.out.println(a instanceof Dog);
        System.out.println(a instanceof Cat);
        System.out.println(null instanceof Cat);
        System.out.println(a instanceof Animal);
	}
}

 instanceof (二元)操作符:

 boolean result=Object instanceof class

 参数:

  • Object: 必选。任意对象表达式
  • class: 必选。任意已定义的对象类

含义如果Object是class的一个实例,则返回true,如果Object不是class的一个实例或者为null时,返回false

运行结果

 多态的好处:

1. 应用程序不必为每一个派生类编写功能调用,只需要对抽象超类进行处理即可。提高了程序的可复用性。(通过继承来实现)
2. 派生类的功能可以被超类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。

多态的弊端:不能使用子类特有的方法

下面的例子可以看出使用多态时,在使用不同的Animal子类时,都利用了同一个Tool.eat()方法,提高了程序的复用性。同时,在创建新的子类时,也可以继续使用Tool.eat()方法而不用进行修改,因此使用多态可以提高代码的可扩充性和可维护性。

但是当使用父类对象变量引用子类对象时,派生类Cat特有的playGame()方法不能被调用。

class Animal
{
	public int age=40;

	public void eat()
	{
		System.out.println("吃东西");
	}
	public void sleep()
	{
		System.out.println("在晚上睡觉");
	}
} 

class Cat extends Animal
{
	public int age=5;
	//重写了父类的方法
	public void eat()
	{
		System.out.println("猫吃老鼠");	
	}
	public void playGame()
	{
		System.out.println("猫喜欢玩毛线球");
	}
}
class Dog extends Animal
{
	public int age=3;
	//重写了父类的方法
	public void eat()
	{
		System.out.println("狗啃骨头");	
	}
}
class Pig extends Animal
{
	public void eat()
	{
		System.out.println("猪吃白菜");
	}
}
class Tool
{
	public static void eat(Animal animal)
	{
		animal.eat();
		animal.sleep();
	}
}
class AnimalTest2
{
	public static void main(String[] args)
	{
		Cat cat=new Cat();
		Tool.eat(cat);

		Dog dog=new Dog();
		Tool.eat(dog);

		Pig pig=new Pig();
		Tool.eat(pig);

		System.out.println("-------------------");
		Animal a=new Cat();
		//a.playGame();		//error:类型为Animal 的变量a 找不到方法 playGame();
		Tool.eat(a);
	}
}

运行结果 

多态的其他实现方式:

  1. 接口
  2. 抽象类和抽象方法

方法调用

如何调用方法呢?假设要调用x.f(args),Son是Father的子类

1.编译器查看对象的声明类型(即隐式参数类型)和方法名,获得所有可能被调用的候选方法。注:有可能存在着方法的重载,即有多个参数类型不一样的方法编译器会列举Son类中所有名为f的方法和其超类中所有的非私有的名为f方法,(超类的私有方法不可访问)

2.编译器查看调用方法时提供的参数类型。获得方法名字和参数类型。如果在所有名为f的方法中存在一个方法的参数类型与调用方法提供的实参类型匹配,就选择这个方法,这个过程称为重载解析。这个过程是比较复杂的,如要考虑到参数类型转换,如int 转换为double,Son转换为Father。如果编译器没有找到与参数类型相匹配的方法,或者发现经过类型转换后有多个方法与之匹配,就会报告一个错误。

静态绑定:如果是private方法、static方法、final方法或者构造器。那么编译器将可以准确的知道应该调用那个方法,我们将这种调用方式称为静态绑定。

动态绑定:调用的方法依赖于隐式参数的实际类型,并且在运行时实现调用那个方法,我们称这种调用方式为动态绑定。

当程序运行,并采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实际类型最合适那个类的方法,假设x的实际类型是Son,它是Father的子类,如果Son类定义了方法f(String),就直接调用它,否则将在Son类的超类Father中寻找f(String),依次类推

方法表(method table):每次调用方法都要进行搜索,时间开销非常大,因此,虚拟机事先为每个类创建了一个方法表(method table)。其中列出了所有方法的签名和实际调用的方法。这样一来,在真正的调用方法的时候,虚拟机仅查找这个表就行了。

完整的过程;

Father s=new Father的子类();       //这里Son还可以替换为其他Father的子类

s.show();

        1.编译器查看s的声明类型为Father
        2
.编译器将Father类方法表和其超类中所有的名为show的方法列举出来
        3.
编译器查看调用方法时提供的参数类型。进行重载解析,如果在所有名为show的方法中存在一个与提供的参数类型完全匹配, 就选择这个方法。至此这个方法就已经找到了。
        4.判断该方法是静态绑定还是动态绑定。
          
 如果该方法是private方法、final方法或构造器,java虚拟机就调用这个方法。调用方法结束。
            否则,在程序运行过程中,虚拟机提取s引用对象的实际类型的方法表。有可能是Son的方法表,也有可能是Father其他子类的方法表
        5.假设s引用对象的实际类型是Son.
      
     如果Son类定义了该方法,虚拟机就直接调用它。
            否则在Son类的超类中寻找show()方法,依次类推。
  

 

返回目录

  • 7
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值