java三大特性之多态

java三大特性之多态

通过上两篇对封装的简介的博客,相信大家对java三大特性的封装和继承有了一定的了解。那么除了上一篇博客讲到的封装和继承之外,多态就是java三大特性的最后一大特性。接下来我就谈谈我对java三大特性中的多态的一些看法。


多态的概念

什么是多态呢?
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
笼统来讲,一个行为具有多种表现形式称为多态。
还是很难理解的话,我们先简单打个比方,同样是弹这个动作,弹钢琴和弹吉他却是是两种不同的效果,我们这里就可以将“弹钢琴”和“弹吉他”理解为“弹”这个动作的多态。
多态主要体现在引用多态和方法多态两个方面

多态的优点

Java为什么要有多态这个特点呢?
多态使代码更加简化、使代码具有接口性、提高代码的可维护性、消除类型之间的耦合关系(关于耦合关系的概念我们在封装那篇博客中有讲到)、对已存在代码有可替换性、使代码具有可扩充性,增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作等等。
这么直白的讲或许大家有点听不懂,那么我们用代码让来大家体会一下多态的强大吧

// 假设有一个类 叫 鸟类,它拥有属性翅膀,拥有方法鸣叫,如下
public class Bird{
   private Wing wing;
   public void moo(){
          System.out.println("鸟叫声");
   }
}
//鸟类封装了 翅膀类和moo方法;另外有两个类都继承鸟类并重写了moo方法,分别是鹦鹉和麻雀如下:
/**
 * 鹦鹉类:
 */
public class Parrot extends Bird{
   public void moo(){
         System.out.println("鹦鹉的叫声");
   }
}
/**
 * 麻雀类:
 */
public class Sparrow extends Bird{
   public void moo(){
         System.out.println("麻雀的叫声");
   }
}
//假设你有个妻子,她想听鸟叫,然后建一个妻子类
public class Wife{
     public void listen(Bird bird){
        bird.moo(); 
   }
  /** 这时多态就很好的体现了,你妻子想听鸟叫,无论什么鸟都可以给她,但是你想让她和鹦鹉
   *  说话,你就买了一只鹦鹉传给listen方法,结果你妻子听到了鹦鹉的叫声,程序输出:鹦
   *  鹉的叫声
   */
  public static void main(String[] args) {
       new Wife().listen(new Parrot());	
  }
}

这样是不是很容易就能够理解多态的好处了呢?

多态的前提

不少小伙伴们发现了,多态好像和继承有着莫名的联系。事实上,继承为多态打下了基础;继承还是体现多态的一个前提条件。除此之外,多态还有两个前提,一共三个:

  1. 存在继承或者实现关系
  2. 子类或实现类必须重写父类方法
  3. 父类引用指向子类对象

1.接口

一提到多态,我们就必须提到接口这个概念
上一篇博客讲继承的时候我们就提到了接口这个概念,但没有详细讲解,现在是讲解这个接口的时候了

<1.>什么是接口?

接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

<2.>接口与类

接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。下面我们归纳一下接口与类的异同:

a. 接口与类相似点:
  1. 一个接口可以有多个方法。
  2. 接口文件保存在 .java 结尾的文件中,文件名使用接口名。
  3. 接口的字节码文件保存在 .class 结尾的文件中。
  4. 接口相应的字节码文件必须在与包名称相匹配的目录结构中。
b. 接口与类的区别:
  1. 接口不能用于实例化对象。
  2. 接口没有构造方法。
  3. 接口中所有的方法必须是抽象方法。
  4. 接口不能包含成员变量,除了 static 和 final 变量。
  5. 接口不是被类继承了,而是要被类实现。
  6. 接口支持多继承。

< 3.> 接口特性:

  1. 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
  2. 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
  3. 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。

< 4.>接口的声明

[可见度] interface 接口名称 [extends 其他的接口名] {
        // 声明变量
        // 抽象方法
}

例如:


/* 文件名 : NameOfInterface.java */
import java.lang.*;
//引入包
 
public interface NameOfInterface
{
   //任何类型 final, static 字段
   //抽象方法
}

< 5.>接口的实现:

当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。
类使用implements关键字实现接口。在类声明中,Implements关键字放在class声明后面。
实现一个接口的语法,可以使用这个公式:
…implements 接口名称[, 其他接口名称, 其他接口名称…, …] …
老办法,实例说话:


/* 文件名 : MammalInt.java */
public class MammalInt implements Animal{
 
   public void eat(){
      System.out.println("Mammal eats");
   }
 
   public void travel(){
      System.out.println("Mammal travels");
   } 
 
   public int noOfLegs(){
      return 0;
   }
 
   public static void main(String args[]){
      MammalInt m = new MammalInt();
      m.eat();
      m.travel();
   }
}

运行结果:
Mammal eats
Mammal travels
作为一名合格的程序猿,编程规则是很重要很重要的,在实现接口的时候,也要注意一些规则:

  1. 一个类可以同时实现多个接口。
  2. 一个类只能继承一个类,但是能实现多个接口。
  3. 一个接口能继承另一个接口,这和类之间的继承比较相似。

< 6.> 接口的继承

一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。实例来一个:


// 文件名: Sports.java
public interface Sports
{
   public void setHomeTeam(String name);
   public void setVisitingTeam(String name);
}
 
// 文件名: Football.java
public interface Football extends Sports
{
   public void homeTeamScored(int points);
   public void visitingTeamScored(int points);
   public void endOfQuarter(int quarter);
}
 
// 文件名: Hockey.java
public interface Hockey extends Sports
{
   public void homeGoalScored();
   public void visitingGoalScored();
   public void endOfPeriod(int period);
   public void overtimePeriod(int ot);
}
//Hockey接口自己声明了四个方法,从Sports接口继承了两个方法,这样,实现Hockey接口的类需要实现六个方法。

我们讲继承的时候说过,类的多继承是不合法,但接口允许多继承。
在接口的多继承中extends关键字只需要使用一次,在其后跟着继承接口。 如:
public interface Hockey extends Sports, Event
以上的程序片段是合法定义的子接口,与类不同的是,接口允许多继承,而 Sports及 Event 可能定义或是继承相同的方法

抽象类

抽象类与接口基本相似。
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。
父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。
在Java中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。

抽象类与接口的区别

  1. 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
  2. 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
  3. 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
  4. 一个类只能继承一个抽象类,而一个类却可以实现多个接口。

抽象方法

如果你想设计这样一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法。
Abstract 关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。
(关于这部分的知识,我们之后会有博客详细介绍,大家先了解一下)

实例分析

接下来我们一起看一个多态的代码实例来加深理解一下多态:
先创建一个Animal父类

public class Animal{
    private String name;
    private int age;
 
    public void eat (){
        System.out.println("动物吃饭");
    }
}

再创建一个Dog类继承Animal类,且重写父类方法

public class Dog extends Animal{
    private String name;
    private int age;
 
    @Override
    public void eat(){
        System.out.pringln("狗正在吃饭");
    }
 
    public void sleep(){
        System.out.println("狗正在睡觉");
    }
}

最后我们写出主方法

    Animal a = new Dog();
    a.eat();        //输出:狗正在吃饭
    a.sleep();      //编译出错,无法执行

注意:使用父类引用指向子类对象(多态),也就是将Dog类向上转型成Animal类(小的数据类型变成大的数据类型)
重写了父类成员方法后,父类引用指向子类对象调用成员方法,则会运行输出子类的方法体,但是,不能调用子类特有成员方法,若想调用子类特有成员方法,则需要使用到一个instanceof 判断对象类型

图片.png

instanceof的格式为
++对象名 instanceof 类名++
如:

    Animal a = new Dog();
    if(a instanceof Dog){
        System.out.println("此对象属于Dog类");
    }else{
        System.out.println("不属于");       
    }

当判断属于Dog类后,可以使用向下转型(大的数据类型强转成小的数据类型)
子类类型 变量名 = (子类类型) 父类变量名;
向下转型后的变量数据类型转换回子类,可以调用子类特有方法

    Animal a = new Dog();
    if(a instanceof Dog){
        Dog dog = (Dog) a;
    }
    dog.sleep();        //输出:狗正在睡觉

现实开发使用中,直接使用父类指向子类对象,然后调用重写方法使用性很低
但是一般使用多态的场景
使用父类作为方法参数 ,便可以将所有子类传至方法中,如:

public class Cat extends{
    private String name;
    private int age;
    
    @Override
    public void eat(){
        System.out.println("猫正在吃饭");
    }
 
    public void jump(){
        System.out.println("猫正在跳");
    }
}

接下来创建一个方法参数为父类Animal:
创建Cat、Dog两个对象,然后传入方法中
实际上这就是多态:

Cat c = new Cat();
//c也就是 new Cat();
Animal a = c;     
Animale a = new Cat();

图片.png

并且在此类方法体中,还可以通过instanceof判断数据类型
然后向下转型,调用子类特有方法

public static void (String [] args){
Cat c = new Cat();
Dog d = new Dog();
whoEat(c);        //输出:猫正在吃饭
whoEat(d);        //输出:狗正在吃饭
}
 
public static void whoEat(Animal a){
    a.eat();
    if(a instanceof Cat){
        Cat c1 = (Cat)a;
        c1.jump();         //输出:猫正在跳
    }else if (a instanceof Dog){
        Dog d1 = (Dog)a;
        d1.sleep();        //输出:猫正在睡觉
    }
}
// 本案例转自https://blog.csdn.net/weixin_42022555/article/details/81407202

通过这个小例子,可以把java多态基本理解了吧?


作者水平有限,如果有地方书写不当的话,请大家在评论下留言,大家共同进步。


上一章:java三大特性之继承

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值