初学Java-15-多态

        考虑一个问题,一头牛能不能被定义成一个动物?显然是可以的,一只羊呢?显然也是可以的,一只老虎,一只狮子,都可以定义成一个动物。如果用Java的语言,那就是可以这样写

        Animal one = new Cow();

        Animal two = new Sheep();

        这样应该是可以理解的,比如我们在电影里可能会看到一个场景:在一个夜晚,主角使用热成像仪,观察到远方的森林里有热感应成像,应该是一种动物,但是并不知道是什么动物,等到靠近了才发现是一只猫,虚惊一场。这就是先有了一个动物的概念,然后才确定了动物的种类

        Animal  three;

        three = new Cat();

        我们可以使用一个父类或者一个接口类型的引用变量,来引用一个子类或者一个实现类,这就是多态。多态是可以使用同一指令,对不同的对象做出不同的响应的能力。

        看一下上一篇笔记中的例子

package com.dyz.test;
interface HelloWorld{
	void sayWhat();
}
package com.dyz.test;
interface HiWorld{
	void sayHi();
}
package com.dyz.test;
public abstract class AbstractSuper{
	protected abstract void sayGood();
}
package com.dyz.test;
public class Child extends AbstractSuper implements HelloWorld,HiWorld{
	
	public void sayWhat(){
		System.out.println("good good study,day day up");
	}

	public void sayHi(){
	System.out.println("hi");
	}

	public void sayGood(){
		System.out.println("good");
	}
}
package com.dyz.test;

public class ChildTest{
	public static void main(String[] args){
			HelloWorld hello = new Child();
				hello.sayWhat();
			HiWorld hi = new Child();
				hi.sayHi();	
			AbstractSuper as = new Child();
				as.sayGood();		  
		}
	
}
        在最后的测试类中,我们可以发现,我们可以使用不同的引用变量来引用相同的对象实例。但是由于我们引用变量的类型本身限定,所以我们只能使用该引用变量类型有的方法,比如HelloWorld hello = new Child();由于我们规定了它的引用变量类型是HelloWorld的,这是一个接口,里面只有一个sayWhat()方法,所以虽然实例对象是Child类型的,但是我们只能用它的sayWhat()方法。

        强制类型转换、instanceof

              在我们介绍运算的时候,我们提到了强制类型转换这个概念,来看当时的例子

              short  s = 1;

              s = s+1;

              s += 1;

              我们知道,s = s + 1;这个是无法编译通过的,因为此时后面的值已经变成了int类型,我们通过强制类型转换 s = (short)(s+1);来实现。s += 1;是虚拟机自动为我们进行了强制类型转换。

               由于我们有了多态,自然的也存在一个类型转换的情况,比如上面的例子中,我们希望引用变量能使用Child的其他方法,我们可以使用强制类型转换来实现我们的目的。

package com.dyz.test;

public class ChildTest{
	public static void main(String[] args){
			HelloWorld hello = new Child();
				hello.sayWhat();
			HiWorld hi = new Child();
				hi.sayHi();	
			AbstractSuper as = new Child();
				as.sayGood();		 
			((Child) hello).sayHi(); 
		}
	
}

                但是强制类型转换存在一个风险,万一它不是这个类型怎么办?为了规避这个风险,java中提供了instancof这个操作来判断一个对象是不是某一个类型。

                引用变量 instanceof  类标识符

                这是一个关系表达式,它返回一个布尔值,所以它和> ,>= ,==,!=,<=,<这些关系运算符是相同类型的,但是它的优先级很高,仅仅在[]、()、++、--、! ~之后,比乘除加减位移和其他关系型运算符都要高 。

                在上面例子中,我们使用instanceof来判断hello是不是Child类,如果是,就可以使用强制类型转换来调用其他方法

package com.dyz.test;

public class ChildTest{
	public static void main(String[] args){
			HelloWorld hello = new Child();
				hello.sayWhat();
			HiWorld hi = new Child();
				hi.sayHi();	
			AbstractSuper as = new Child();
				as.sayGood();		 
			if(hello instanceof Child){
				((Child) hello).sayHi(); 
				((Child) hello).sayGood();
			}	
		}
	
}

            考虑一下,根据多态的描述,对同一指令,做出不同的响应,在我们已知的知识里还有这样的例子吗?显然是有的,比如我们的方法重写(Overriding)和方法重载(Overloading).

            同一个指令,父类的方法被子类重写以后,它就具备了多态性;同一个指令,它重载以后,就具备了多态性。我们考虑一下,对于动物类,我们给定一个指令吃,那么在牛这个子类里,它可能就吃草,而在猫这个子类里,它可能就吃鱼。而对于人呢?可能吃零食,吃蔬菜,吃其他动物...

             为什么Java会具有多态这个特性?答案已经呼之欲出了,继承、重载和重写、以及我们允许父类(接口类)的引用指向子类(实现类)对象

            再回到我们上面的这个例子,我们知道如果我们使用父类引用变量或者接口来声明子类(实现类),此时这个引用变量是不能调用其他的非父类(接口类)的方法的,这个类虽然是一个子类,但是它基本上相当于是一个父类了,对于这种定义方式,我们称之为向上转型。即,父类(接口类)的引用指向子类(实现类)这个操作,我们可以称之为向上转型,它的缺点就是它会丢失它的部分属性和方法。而我们使用强制类型转换这个操作显然就是向下转型了,它可以弥补向上转型的缺点。另外,需要明确的一点是,如果子类重写了父类的方法,那么引用时如果对象是子类类型,那么它会执行子类的方法,即使它被一个父类的引用变量引用着。

package com.dyz.test;
public class SuperClass{
	public void print(){
		System.out.println("父类的print方法");
	}
}
package com.dyz.test;
public class SonClass extends SuperClass{
	public void print(){
		System.out.println("子类的print方法");
	}
	
	public static void main(String[] args){
		SuperClass sc = new SonClass();
		sc.print();
	}
}



            请注意,我们在做强制类型转换时,一定要使用instanceof来判断这个转换是不是能成功,能成功才能做强制类型转换,否则程序运行时会抛出异常!

            可能你会问,我为什么一定要用多态?父类引用指向子类,反而会丢失掉一些属性和方法,这样做有什么好处?我们来看一下下面的例子:

package com.dyz.test;
public class Animal{
	public void eat(){
		System.out.println("进食");
	}
}
package com.dyz.test;
public class Cat extends Animal{
	public void eat(){
		System.out.println("我吃鱼");
	}
}
package com.dyz.test;
public class Cow extends Animal{
	public void eat(){
		System.out.println("我吃草");
	}
}
package com.dyz.test;
public class AnimalUse{
	public void eat(Animal x){
		x.eat();
	}
	public static void main(String[] args){
		Animal a = new Cat();
		Animal b = new Cow();
		Animal c = new Animal();
		a.eat();
		b.eat();
		c.eat();
		
		AnimalUse user = new AnimalUse();
		user.eat(a);
		user.eat(b);
		user.eat(c);
	}
}

            我们定义了一个动物类,然后为它扩展了两个子类:猫和牛,并且都重写了eat()方法。在使用类AnimalUse中,我们定义了一个eat(Animal x)的方法,当我们调用这个方法时,我们不关心对象是原始动物还是猫还是牛,只要你有eat方法,你按照你自己的eat实现来进行就可以了。试想如果没有多态,我们这个方法要怎么写?要引用不同的动物,就要写不同的引用方法,大自然的动物种类千千万万,那真是累都要累死了。

           我们考虑一下,如果我们把父类改成接口,是不是也是一样的?因为我们虽然定义了动物,动物是一个很宽泛的概念,哦动物类的eat其实并没有一个具体的实现,这是一个抽象方法。

    

package com.dyz.test;
public interface Animal{
	public void eat();
}
package com.dyz.test;
public class Cat implements Animal{
	public void eat(){
		System.out.println("我吃鱼");
	}
}
package com.dyz.test;
public class Cow implements Animal{
	public void eat(){
		System.out.println("我吃草");
	}
}
package com.dyz.test;
public class AnimalUse{
	public void eat(Animal x){
		x.eat();
	}
	public static void main(String[] args){
		Animal a = new Cat();
		Animal b = new Cow();
		a.eat();
		b.eat();
		AnimalUse user = new AnimalUse();
		user.eat(a);
		user.eat(b);
	}
}

            这样显然也是可以的,但是我考虑一下动物这个概念,除了它的行为,还有它的属性,它的种类、纲目、性别、长度、重量等等等等,从这些考虑,它应该是一个类而不是一个接口,接口应该是一个行为的集合,我们在做设计时,对继承和接口如何使用,一定要考虑清楚。


            现在,让我们看一个著名的关于多态的面试题:

class A{
		public String show(D obj){
			return ("A and D");
		}
		public String show(A obj){
			return("A and A");
		}
}
class B extends A{
	public String show(B obj){
		return ("B and B");
	}
	public String show(A obj){
		return ("B and A");
	}
}
class C extends B{
}
class D extends B{
}
public class E{
	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.a1.show(b): a1是一个A的实例对象,b是一个B的实例对象,B是A的子类,所以这个调用的是A的show(A obj)方法,它的输出是 "A and A"

            2.a1.show(c): 同样的,虽然此时调用的是c,但是C是B的子类,B是A的子类,所以调用的还是A的show(A obj)方法,输出还是"A and A"

            3.a1.show(d):d是D的实例对象,在A中有明确的关于show(D obj)的定义,所以此时调用的show(D obj)方法,它的输出是"A and  D"

            4.a2.show(b):a2是一个A的引用变量,它指向一个B的实例对象,那么a2.show(b)调用的哪个方法?显然应该是调用了show(A obj)方法,上面我们已经介绍过,实际对象是子类对象时,调用的方法是子类的方法,所以它调用了B的show(A obj)方法,输出的是“B and A”

            5.a2.show(c);a2是一个A的引用变量,它指向一个B的实例对象,那么a2.show(c)调用哪个方法?我们知道c的B的子类,那么理论上它应该是调用了B的show(B obj)方法,但是由于它是A类型的引用变量,我们知道在A里面是没有这个方法的,这也使我们在上面说的向上转型的缺点,即使它指向的类B有这个方法,但是引用变量类型中没有,它就无法引用,所以只能将C再做进一步转型转成A类型,然后调用show(A obj)方法,这个时候由于方法被重写,所以还是执行了B的show(A obj)方法。输出还是“B and A”

            6.a2.show(d);这个没什么好说的,B继承了show(D obj)方法,但是没重写,所以还是输出是“A and D”

            7.b.show(b);  B对象的show(B obj),结果是"B and B"

            8.b.show(c);  B对象的show(B obj),只是把C转型成了B,结果还是"B and B"

            9.b.show(d);  B从A那继承了show(D obj)方法,所以输出是"A and D"

            

            这个题让我们充分的了解了继承、重写和重载的关系,同时也让我们知道,当虚拟机调用重载方法时,总是调用最与之匹配的方法,没有直接匹配的方法时,会把参数向上转型,找与之次匹配的方法,直到最后找到能调用的方法。


            好了,到这里关于java的基本的知识我们都已经都知道了,java语言的三大特性:封装、继承、多态我们也全部了解了,再来回顾一下这三大特性。

            封装:将对象的信息隐藏起来,只向外部提供必要的接口来提供服务。它让我们可以在类的内部修改代码,而不必改外部调用方的代码。

            继承:继承让我们对一个事物有了更大的扩展,让我们对事物有了更具体更清晰的表达。继承是多态的必要条件,没有继承就无从谈起多态。然而继承的缺点是明显的:

            1.如果父类发生改变,子类必然发生改变

            2.继承破坏了封装,它让我们不得不把它的实现细节提供给它的子类

            3.继承是一种强耦合性关系,它让类和类之前紧密的联系在一起,大大降低了代码的独立性

            由于这些问题,我们在使用继承关系的时候要十分小心,一句话:慎用继承!!

            多态:根据不同对象情况,对同一指令能够做出不同响应的能力称之为多态。多态存在的必要条件是:继承、重写和向上转型。它让我们的代码更加灵活简洁,有了多态,面向对象才是真的面向对象。 

        

                上面这三个特性是java语言的三个特性,是面试中经常被问到的。如果你是初学者出去找工作,绝对会被问到的就是这三大特性,当然,这并不是java语言独有的特性,而是所有面对象语言的特性,或者说,这就是面向对象的特性。在面试中可能也会提到另一个我们十分常见的词:抽象。

                我们之前已经多次提到过这个词,抽象。抽象是什么?很多人会把抽象看成是面向对象的特性,其实抽象是一种能力,是面向对象的基础。我们最早已经说过,根据具体对象,提取它们共同的特点,将其概念化(类化),这个能力就是抽象。

                







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值