多态与抽象类(Java)

说明:

运行环境: JDK8

1. 多态

1.1 多态的概念

通俗来说,就是指完成某个行为,当不同的对象去完成时就会产生出不同的状态;

(1)看个简单的例子:
在这里插入图片描述
同样是打印机打印,当彩色打印机去打印时,就会出现彩色的效果,当黑白打印机打印时,就是黑白效果;

(2)动物吃食物的行为:
在这里插入图片描述
都是吃饭,发生在不同对象身上,就会产生不同的行为;

1.2 多态的分类

多态分为(1)静态多态(2)动态多态

静态多态:也称为早绑定,即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。
典型代表:函数重载;

动态多态:也称为晚绑定,即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法;

1.3 多态实现的条件

在java中要实现多态,必须要满足以下几个条件:

(1)必须在继承体系下
(2)子类必须要对父类中想要实现多态的方法进行重写
(3) 通过父类的引用调用被重写的方法

范例1:动物吃食物

//定义一个动物类
public class Animal {
    protected  String name;
    protected String gender;
    protected int age;

         //定义一个构造方法
    public Animal(String name, String gender, int age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }
      // 定义一个吃的方法
    public void eat(){
        System.out.println(name+"吃东西");
    }
}
//再定义一个猫类,来继承动物类
public class Cat extends Animal{
    public Cat(String name, String gender, int age) {
        super(name, gender, age);
    }

    // 对基类中的eat()方法进行重写
    public void eat(){
        System.out.println(name+"吃鱼");
    }
}

//同样的,定义一个狗类,用来继承动物类
public class Dog extends Animal{
    public Dog(String name, String gender, int age) {
        super(name, gender, age);
    }

    // 对基类中eat()方法进行重写
    public void eat(){
        System.out.println(name+"吃骨头");
    }
}

最后,写一个测试吃的代码,如下所示

public class TestEat {
        public static void eat(Animal a){
            a.eat();
        }
    public static void main(String[] args) {
    // New 两个对象
           Dog dog =new Dog("小七","公",1);
           Cat cat=new Cat("花花","女",1);

        eat(dog);
        eat(cat);
    }
}

运行结果:
小七吃骨头
花花吃鱼

当编译器在编译代码时,并不知道要调用Cat还是Dog中的eat()方法,只有在程序运行起来后,形参a引用的具体对象确定后,才知道调用那个方法;

1.4 再次认识重写

重写(override):也称为覆盖,它是子类对父类非静态非private修饰非final修饰非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写

重写的优点: 子类可以根据自己的需要实现父类的方法;

方法重写的规则

(1)子类在重写父类的方法时,一般要与父类被重写的方法原型一致;

体现在:修饰符 返回值类型 方法名(参数列表) 要完全一致;如上面代码CatDog重写Animaleat()方法一样;

在这里插入图片描述
在这里插入图片描述

(2)JDK7以后,被重写的方法返回值类型可以不同,但是必须是具有父子关系的;

public class Base {
    Base a;
    //返回值类型是:Base
    public Base methodA(){
       return a;
    }
}
// Derived用来继承Base
public class Derived extends Base{
    Derived c;
     //返回值类型是:Derived
    public Derived methodA() {
         return c;
    }
}

(3) 访问权限不能比父类中被重写的方法的访问权限低;

例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为 protected;

(4)重写的方法, 可以使用 @Override 注解来显式指定,进行一些合法性校验;

(5) 父类被staticprivatefinal修饰的方法以及构造方法都不能被重写;

// 以下方法是不能被重写的
    //被static修饰的
     @Override
    public static void  methodB(){
        System.out.println("Derived-methodB()");
    }
    
    //被private修饰的
    
    @Override
    private void methodC(){
        System.out.println("Derived-methodC()");
    }
    
    
    //被final修饰的
  
   @Override
    public final void methodD(){
        System.out.println("Derived-methodD()");
    }
   

1.5 重写与重载的区别

参考如下表格:
在这里插入图片描述
【重写的设计原则】:

对于已经投入使用的类,尽量不要进行修改;

最好的方式:重新定义一个新类,来重复利用其中共性的内容,添加或者改动新的内容;

2. 向上转型与向下转型

2.1 向上转型

向上转型:就是让基类的引用去引用子类的对象;

语法格式

父类类型 对象名 = new 子类类型()

Animal a1 = new Cat();
Animal a2 = new Dog();

Animal是父类类型,可以引用一个子类对象;
原因:子类对象是一个父类对象,即可以将一个子类对象当成父类对象来应用。因此,向上转型是安全的,因为是从小范围向大范围的转换;

使用场景

(1)直接赋值

    Animal a1 = new Cat ("元宝","男",2);
       
   }

(2)方法传参

public class TestEat {
 // 2. 方法传参:形参为父类型引用,可以接收任意子类的对象
 public static void eat(Animal a){
 a.eat();
 }

(3)方法返回


//作为方法的返回,返回任意子类对象
    public static Animal buyAnimal(String name){
        if(name.equals("狗")) {
            return new Dog("狗狗", "公", 1);
        } else if(name.equals("猫")){
            return new Cat("猫猫", "男", 1);
         }else{
            return null;
        }
    }
      public static void main(String[] args) {
       Animal a1= buyAnimal("猫");
}

向上转型的优点:让代码实现更简单灵活;
向上转型的缺陷:不能调用到子类特有的方法;

2.2 向下转型

向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入了 instanceof 关键字,如果该表达式为true,则可以安全转换;

范例2:

public class Animal {
    String name;
    String gender;
    int age;

    public Animal(String name, String gender, int age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }
    public void eat(){
        System.out.println(name+"吃东西");
    }
}

//定义一个Cat类,用来继承Animal
public class Cat extends Animal{
    public Cat(String name, String gender, int age) {
        super(name, gender, age);
    }

    @Override
    public void eat() {
        System.out.println(name+"吃鱼");
    }

    public void mew(){
        System.out.println(name+"喵喵喵~~");
    }
}

//定义一个Dog类,用来继承Animal
public class Dog extends Animal{
    public Dog(String name, String gender, int age) {

        super(name, gender, age);
    }
    @Override
    public void eat() {
        System.out.println(name+"吃骨头");;
    }

    public void bark(){
        System.out.println(name+"旺旺旺~~");
    }
}

写一个Test类来测试

public  class Test {
    public static void testEat(Animal a) {
        a.eat();
    }

    public static Animal buyAnimal(String name) {
        if (name.equals("狗")) {
            return new Dog("狗狗", "公", 1);
        } else if (name.equals("猫")) {
            return new Cat("猫猫", "男", 1);
        } else {
            return null;
        }
    }

    public static void main(String[] args) {
        Animal a1 = buyAnimal("猫");
        a1.eat();

        Animal a2 = buyAnimal("狗");
        a2.eat();
        //通过a1调用,编译器会在Animal中去查找
        //a1.mew();// 编译报错
        //实际上a1指向的是一个cat对象
        //要调用则要
        Cat cat = (Cat) a1;    //进行强制转换
        cat.mew();
        //a1 实际指向的是一个Cat对象,但此处将其转换成Dog类的对象来使用
        //代码运行时就会抛异常:java.lang.ClassCastException
       // Dog d = (Dog) a1;   // 抛异常
        //d.bark();
        //一般采用以下方式来使用
        if (a1 instanceof Dog) {
            Dog d1 = (Dog) a1;
            d1.bark();
        }
        if (a2 instanceof Dog) {
            Dog d2 = (Dog) a2;
            d2.bark();
        }

    }
}

:一般不用向下转型;

3. 使用多态的优点

(1)能够降低代码的 "圈复杂度", 避免大量使用 if-else

所谓圈复杂度:就是一种描述一段代码复杂程度的方式, 一段代码如果平铺直叙, 那么就比较简单容易理解, 而如果有很多的条件分支或者循环语句, 就认为理解起来更复杂;
(2) 可扩展能力强

4. 抽象类

4.1 抽象类的概念

一个类中没有包含足够的信息来描述一个具体的对象,把这样的类称为抽象类;

4.2 抽象类的语法格式

在Java中,如果一个类被 abstract 修饰,就称为抽象类,public abstract class Test {...};
在这里插入图片描述

抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法不用给出具体的实现体;

如下所示:

// 下面就是抽象方法
// 抽象方法不用添加方法体
 abstract public void eat();
 abstract void bark();

注意:抽象类也是类,内部可以包含普通方法和属性,甚至构造方法;

范例3: 定义一个图形类

//抽象类
public abstract class Shape {
    protected double area;
    // 抽象方法
    abstract public void draw();
    //普通方法
    public double getArea(){
        return area;
    }
}

4.3 抽象类的特性

(1)抽象类不能直接实例化对象;

public abstract class Animal {
    string name;

    public Animal(string name) {
        this.name = name;
    }

    public static void main(String[] args) {
      //  Animal a=new Animal(); 报错,抽象类不能实例化对象
    }
}

(2)抽象方法不能被 private 修饰;

 abstract private void eat(); //报错

原因:要在子类中被重写,而private修饰的,子类不能访问;

(3)抽象方法不能被finalstatic修饰,因为抽象方法要被子类重写;

   abstract final void drink(); //报错

原因final修饰的方法不能被子类重写

    abstract static void eat(); //报错

原因:抽象方法不能是静态方法,其内部没有this引用

(4)抽象类必须被继承,并且继承后子类要重写父类中所有的抽象方法,否则子类也是抽象类,必须要使用 abstract修饰;

定义一个 Animal类

public abstract class Animal {
    protected String name;

    public Animal(String name) {
        this.name = name;
    }
    //抽象方法
  abstract public void eat();
  abstract public void bark();

}

定义一个Dog类,用来继承Animal类

//Dog实现了Animal中的所有的抽象方法,
//Dog就是一个具体的类
//就可以创建对象
public class Dog extends Animal{
    public Dog(String name) {
        super(name);
    }

    public void eat(){
        System.out.println(name+"吃骨头");
    }

    public void bark(){
        System.out.println(name+"旺旺旺");
    }

    public static void main(String[] args) {
        Dog dog=new Dog("小七");
        dog.eat();
        dog.bark();
    }
}

运行结果:
小七吃骨头
小七旺旺旺

(5)抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值