多态polymorphic

前面在知识点的穿插中,分别介绍了封装,继承,再加上多态,JAVA的三大特性就全部介绍完了。但是多态作为最后一个介绍的,越往后面的一定是越难的,在博主曾经学习的时候,也是最开始在多态这一块的学习中,开始有点迷糊了。时过境迁,希望这次,能把多态通过自己的描述,来介绍清楚。


多态

 在讲多态之前,我们先引入一个泛化的概念,什么是泛化呢?简单来说,泛化就是一个再次提取一个类似的特征。我们之前再讲面向对象的时候,说道,面向对象是对具体事物的抽象化,就像是所有猴子抽取出了猴子类,所有老虎抽取出了老虎类。从生物学上来分,这些类就又有一个共同的特征。就是这些都属于动物,都是动物类。这样就像是把类又抽取出来了一个类,它的对象就是类。而泛化就差不多是这样的含义。我们可以叫老虎叫做老虎,也可以把它叫做动物,都没有错,但是真实的它还是老虎。从老虎到动物,这就是一个向上转型的过程,而这个过程,就是通往多态的入口。

多态描述的就是这样一个形式:事物有多种形态和描述:我们可以说老虎是一只老虎,也可以说老虎是一只动物。同一个对象,有着不同的特征:我们可以看到老虎在吃肉然后说看一只动物在吃肉,然后旁边的人问你,是什么动物再吃肉,然后你回答,是一只老虎。从本质上来说,就是同一个物体有着不同的名称和描述。

我们再来举一个例子,在超市的后勤仓库,一个工人大喊:进货啦!然后一堆销售阿姨过去排着队领“货”,其中这个货这个类,在工人那里就是同样的东西,但是分到超市的阿姨手里,就又是不一样的东西了,可能有的领的牙刷,有的领的毛巾等等。但是总体上他们又都属于货物。所以,同样的货物,在工人那里就是货物,到阿姨这里就是具体的商品。总结来说就是:同一个名字和描述,可以在不同的场景下有不同的真实体现。

继承上面的例子,货物是货物类,毛巾类又是毛巾类,牙刷类又是牙刷类,很明显,这里面有着继承的一层关系。只有继承了之后,我们的“货物”才会有各自在超市阿姨展现出他们真实的状态。所以,继承是多态的前提。而且要有能用父类的名称就可以描述一个子类的对象这种性质。我们平常创建对象是:

                    Huo h = new Huo();
                    Maojin mj = new Maojin();

前面的h是一个父类的引用,后面一new,就在内存中开辟了一块空间,标识着,这个对象真实的存在了。所以这种形式创建对象的过程中,前面的h只是一个引用,存放后面真实的对象的地址。后面的才是创建出来的真实的对象。那我们想了,我们可以用货物来描述货物,也可以说毛巾,牙刷啊都是货物。那么我们可不可以,在创建一个父类的引用,后面new的时候,创建一个子类的真实对象,这样的话,就可以说它是货物,但其实他是毛巾或者牙刷。形式就类似:

                Huo h = new YaShua();

这样看起来貌似是不规范,说了那么多,我们去代码里验证一下:

多态polymorphic_第1张图片这里并没有报错。

所以说,JAVA是支持这种格式的。所以这就是事物的多态性:同一个事物在不同情况下有不同的描述或者真实体现。事物有着多种状态。


多态的分类:

JAVA中的多态,分为静态多态和动态多态两种,其实,静态多态我们已经讲过了,它其实就是方法的重载,同样的概念,同一个事物在不同情况下有着不同的描述或者真实体现:同样都是一个功能的方法,我们都可以说它是A方法,但是其实它的作用和真实的功能都各有差异,本质上不同,但是大体又相同。又体现了事物有着多种状态的性质。。。
         动态多态就像是上面描述的那样,在类与类之间,有着多态的这种关系和思想。


一个类中最不可缺少的无非就是,成员变量,成员方法,构造方法三部分,我们来看一下这些在多态中有着怎样的关系。


多态中成员变量的访问特点:

代码示例:

class 简单的多态介绍 {
	public static void main(String[] args) {
		//向上转型
		Man m = new SuperMan();	
		System.out.println(m.age);
	}

}
class Man{
	int age = 20;
	public void run() {
		System.out.println("走路");
	}
}
class SuperMan extends Man{
	int age = 200;              //超人嘛,活到200很正常
	public void run() {
		System.out.println("走路走得飞快");
	}
	 public void fly() {
		 System.out.println("超人会飞");
	 }
}

我们在主方法中查看了一个人类其实是超人的对象的属性。结果会是多少呢:

20

我们看到答案,怎么会是20呢,超人不是活200岁么?这里的特点如下:

  • 编译看左边,运行看左边
  • 编译的时候,要看赋值号左边的引用的类型中,是否有该变量的定义,如果有,就编译成功,否则报错。
  • 运行的时候,要看赋值号左边的引用数据类型中,真正如何给变量赋值,获取的是引用父类的赋值数据。

这里的特点就是编译和运行的机制,可以先记住。等到下面会在内存中进行解释。我们把父类的应用指向子类的对象称之为绑定,这里的这种绑定称之为静态绑定,是指在编译期间就确定调用的是父类中的成员。对于成员变量静态变量静态方法private修饰的方法,采用的是静态绑定。可以总结为:成员变量静态变量静态方法private修饰的方法都是跟随前面的引用。


多态中成员方法的访问特点:

代码示例:

class 简单的多态介绍 {
	public static void main(String[] args) {
		//向上转型
		Man m = new SuperMan();	
		m.run();
	}

}
class Man{
	int age = 20;
	public void run() {
		System.out.println("走路");
	}
}
class SuperMan extends Man{
	int age = 200;              //超人嘛,活到200很正常
	public void run() {
		System.out.println("走路走得飞快");
	}
	 public void fly() {
		 System.out.println("超人会飞");
	 }
}

打印结果:

走路走得飞快

这里调用的就又是子类也就是等号后面的创建的真实的对象的方法。

特点如下:

  • 编译看左边,运行看右边
  • 编译的时候,要看父类中是否存在该方法,如果存在,编译通过,否则编译报错。
  • 运行的时候,要运行子类的重写的方法,如果没有重写的方法,执行父类的方法。

这种绑定就称为动态绑定,是在运行的时候,才知道要调用哪个方法。而成员方法就是这种机制。

可以理解为:成员方法是跟随着=后面的真实的对象的。


介绍了成员变量和成员方法, 而构造方法是不用介绍的,因为构造方法是不参与继承的。没有了继承,也就没有了多态。

上面说了类中的成员在多态中的特点,我们会想,为什么呢。我们下面在内存中进行解释。

多态polymorphic_第2张图片

在上面的图中,我们可以知道,属性是随着类的创建一同过去了。但是方法还在原来的方法区里。所以在调用成员变量的时候,因为前面是m,而m是什么,m是一个父类的对象,它直接可以去堆内存中找到属于自己的属性,所以可以直接调用出来。而在调用成员方法的时候,我们需要用对象的实体去发出请求,去向他的方法区中的文件申请调用,而这个时候,是堆内存中的对象实体在发生调用,而这个时候,方法区才不会管你最开始的引用是什么,它只认堆内存里面的实体对象是谁,所以这个时候调用的就是对面的对象实体中的方法。


向下转型:

我们把又父类的引用指向子类的对象称之为向上转型,那么怎么把父类的引用重新转化为子类的引用呢。这里就引入了向下转型的概念。
我们在这里再介绍一个关键字:instanceof  它用来判断前面的对象是否为后面所属的类。

示例代码:

class 简单的多态介绍 {
	public static void main(String[] args) {
		//向上转型
		Man m = new SuperMan();
		m.run();
		//向下转型
		if(m instanceof SuperMan) {
			SuperMan sm = (SuperMan)m;
            System.out.println(sm.age);
			sm.fly();
			
		}
	}

}
class Man{
	int age = 20;
	public void run() {
		System.out.println("走路");
	}
}
class SuperMan extends Man{
	int age = 200;
	public void run() {
		System.out.println("走路走得飞快");
	}
	 public void fly() {
		 System.out.println("超人会飞");
	 }
}

输出结果:

走路走得飞快
超人会飞
200

说了这么多的概念,那么多态到底有什么应用呢。我们来介绍两种:

①多态作为形参

public class 多态作为形参 {
	public static void main(String[] args) {
		Zoo zoo = new Zoo();
		
		Tiger tiger = new Tiger();
		zoo.feed(tiger);
		
		Monkey monkey = new Monkey();
		zoo.feed(monkey);
		
		Cat cat = new Cat();
		zoo.feed(cat);
		
		Dog dog = new Dog();
		zoo.feed(dog);
	}
}
class Zoo {
	public void feed(Animal a){
		a.eat();
	}
}

	 class Animal {
		public void eat(){
			System.out.println("吃");
		}
	}
	 class Tiger extends Animal{
		public void eat(){
			System.out.println("吃肉###");
		}
	}
	class Monkey extends Animal{
		public void eat(){
			System.out.println("吃桃...");
		}
	}
	 class Cat extends Animal{
		public void eat(){
			System.out.println("吃鱼");
		}
	}
	 class Dog extends Animal{
		public void eat(){
			System.out.println("啃骨头");
		}
	}

输出结果:

吃肉###
吃桃...
吃鱼
啃骨头

这段代码中多态的运用在主方法中,我们定义了一个像Dog,Cat这样的类,但是Zoo里的feed方法的参数是Animal类的对象。但是一样能够调用,这里,就是用了多态。

②多态做为返回值类型(简单工厂模式)

代码示例:

import java.util.Scanner;

public class 多态作为返回值 {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		System.out.println("你想要什么车:");
		String s = sc.next();
		CarFactory cf = new CarFactory();
		Car c = cf.makeCar(s);//多态在这里体现
		c.run();
	}
}

 class CarFactory {
	public Car makeCar(String name){
		if("bmw".equals(name)){
			return new BMW();
		}
		if("benz".equals(name)){
			return new Benz();
		}
		if("ferrari".equals(name)){
			return new Ferrari();
		}
		return null;
	}
}
 class Car {
	public void run(){
		System.out.println("run");
	}
}
 class BMW extends Car{
	public void run(){
		System.out.println("宝马在路上飞驰...");
	}
}
 class Benz extends Car{
   
	 public void run(){
		 System.out.println("奔驰在路上奔驰###");
	 }
}
 class Ferrari extends Car{
	  public void run(){
		  System.out.println("法拉利跑的很快");
	  }
}

输出结果:

你想要什么车:
bmw
宝马在路上飞驰...

这里的多态在主方法中有注释的那一行可以体现出来。这里是一个简单的工厂模式。在这里我们可以简单的理解为,用汽车工厂来创建了汽车然后让汽车运行了起来。


多态的好处

  • 提高了代码的可扩展性。
  • 在该当的参数列表中,可以定义父类类型的引用,将来调用的时候,所有的子类类型的对象,都可以当作方法的实际参数。
  • 在代码中也可以直接写父类的引用指向子类对象,也可以提高代码的复性

希望这篇文章能让你对多态有一个简单的了解。

你可能感兴趣的:(#JAVA思维框架,JAVA,多态)