一、接口

1.1 接口概述

1.1.1 接口引入

继承抽取了类的共性,使得其子类都具备了父类的功能,提高了代码的复用性,但是有些情况下,并不是所有的子类都应该具备父类的全部功能,有些功能只是当做与"扩展功能",并不是与生俱备的;

例如:吃饭、睡觉、走路等本就是人们具备的功能,我们可以将其定义在"人类"的继承体系中,只要是一个"人类",那么就应该具备这些功能;但是有些功能是只有一部分人会的,如打篮球、敲代码等,如果将这些功能也定义在"人类"这个继承体系中,那么所有的人就必定要会打篮球、敲代码球等;这样做显然不符合逻辑;

类似于打篮球、敲代码这样独特的功能,我们可将其定义为"扩展功能",并不是所有都会,而是部分人会;在Java中,我们可以将扩展功能定义在接口中;

1.1.2 接口概述

接口(英文:Interface),在Java编程语言中是一个抽象类型,接口中定义的全都是抽象方法,使用Interface来声明。一个类通过实现接口的方式,从而来继承接口的抽象方法。

一个类继承接口则需要实现该接口的所有的抽象方法,除非继承接口的类是抽象类,因此一个类继承接口我们往往称为某个类实现了(implements)某个接口,关键字从extends更换为implements

tips:在JDK7及以前,接口中只含有抽象方法;在JDK8中,接口可以含有默认方法以及静态方法;JDK9中,接口可以含有私有方法

1.1.3 接口的特点

Java中定义接口采用interface关键字,关于接口有如下特点:

  • 1):接口中的所有成员变量都默认是由public static final修饰的;
  • 2):接口中的所有方法都默认是由public abstract修饰的;
  • 3):接口没有构造方法(不能实例化对象);
  • 4):类继承(实现)接口时,必须重写接口中的所有抽象方法,否则该类是抽象类;
  • 5):接口与接口之间采用多继承机制
  • 6):接口中没有静态代码块。
  • 7):接口的静态方法不能被继承下来;(但被static修饰的成员变量能被继承下来)

1.2 接口语法

public interface 接口名称 {
    // 抽象方法
    // 静态方法
    // 默认方法
    // 私有方法
}

1.2.1 含有抽象方法

1)语法:

抽象方法:使用 abstract 关键字修饰,可以省略,没有方法体。该方法供子类实现使用。

示例代码:

public interface InterFaceName{
    public abstract void method();
}

由于接口中只能含有抽象方法,因此方法可以省略abstract关键字,并且默认都是public修饰的方法:

public interface InterFaceName{
    void method();
}
2)示例代码:

定义接口:

package com.dfbz.demo01;
public interface A {
    
    // 定义一个抽象方法
//    public abstract void method();
    
    // 等价上面的写法
    void method();
}

定义实现类:

package com.dfbz.demo01;
/**
 * @author lscl
 * @version 1.0
 * @intro: 类与接口采用实现(implements)
 */
public class B implements A {
    
    // 重写A接口中的所有抽象方法
    @Override
    public void method() {
        System.out.println("Hello Interface~");
    }
}

测试类:

package com.dfbz.demo01;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        B b = new B();
        
        b.method();
    }
}

1.2.2 接口的静态方法和默认方法

1)语法:

在JDK7及以前,接口中只允许存在抽象方法,在JDK8中,接口允许存在默认方法静态方法,两种方法可以为接口提供一些功能性的代码,也可以让子类选择性的重写方法,而不是强制性重写接口中的所有方法;

Tips:默认方法的出现可以让子类实现接口时选择性的重写方法,而不是强制性重写所有的方法;

  • 默认方法:使用 default 修饰,不可省略,供子类调用或者子类重写。
  • 静态方法:使用 static 修饰,供接口直接调用(接口中,被static修饰的方法不能被继承到子类)。

Tips:接口中默认方法的default修饰符不可省略,这点和我们之前学习的权限修饰符不一样;

示例代码:

public interface InterFaceName {
    public default void method() {
        // 执行语句
    }
    public static void method2() {
        // 执行语句    
    }
}
2)示例代码:

定义接口:

package com.dfbz.demo02;
public interface A {
    default void method1() {
        System.out.println("A 的默认方法~");
    }
    static void method2(){
        System.out.println("A 的静态方法~");
    }
}

定义实现类:

package com.dfbz.demo02;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class B implements A {
}

Tips:

  • 1)默认方法可以重写也可以不重写,不重写默认被继承下来;
  • 2)在接口中,静态方法不会被继承下来;

测试类:

package com.dfbz.demo02;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        // 使用接口名调用方法
        A.method2();
        // 调用B继承A接口的方法
        B b = new B();
        b.method1();
//        b.method2();          // 错误,接口中static修饰的方法不会被继承
//        B.method2();          // 错误,接口中static修饰的方法不会被继承
    }
}
3)IDEA语法检测环境

我们装的IDEA开发工具具备Java语法检测环境,并提供不同版本的JDK语法检测;

06【接口、多态】_接口

当前语法检测环境为JDK8,我们可以手动切换为7(不支持默认方法和静态方法);

06【接口、多态】_JavaSE_02

更改完语法检测之后,再查看源代码:

06【接口、多态】_默认方法_03

注意:上述更改只是改变了Java编译时的环境,并不能真正的运行代码;例如我上述编译环境改为8,我电脑上安装的JDK也是1.8,如果硬要安装9的编译环境来运行则出现如下错误:

06【接口、多态】_JavaSE_04

如果真的想用9的JDK来运行的话,我们可以安装一个9的JDK,配置好环境变量之后并且在IDEA中配置他:

06【接口、多态】_默认方法_05

1.2.3 接口的私有方法

1)语法:

在JDK9中,接口除了运行存在默认方法、静态方法之外(JDK8新特性),还允许存在私有方法,让接口本身可以封装某段逻辑但又不想被子类继承的方法,私有方法是不可以使用类名调用的;

示例代码:

public interface InterFaceName {
    private void method(){
    }
    
    private static void method2(){
    }
}
2)示例代码:

Tips:注意将IDEA语法检测环境改为JDK9;

定义接口:

package com.dfbz.demo03;
public interface A {
    default void method3(){
        System.out.println("A 的默认方法~");
        // 调用a的私有方法
        method();
    }
    private void method() {
        System.out.println("A 的私有方法~");
    }
    private static void method2() {
        System.out.println("A 的静态私有方法~");
    }
}

定义实现类:

package com.dfbz.demo03;
/**
 * @author lscl
 * @version 1.0
 * @intro: 类B 实现 接口A 
 */
public class B implements A{
}

测试代码:

package com.dfbz.demo03;
/**
 * @author lscl
 * @version 1.0
 * @intro: 
 */
public class Demo01 {
    public static void main(String[] args) {
        B b=new B();
        b.method3();
    }
}

Tips:私有方法的存在一般是给默认方法提供某段代码复用的逻辑而又不想被子类继承;

1.3 接口案例

1.3.1 需求分析

需求:学生会游泳,歌手会跳舞;

小灰是一名学生,除了学习之外他还会游泳;小蓝是一名歌手,除了唱歌之外他还会跳舞

1)小灰是一个学生,小蓝是一个歌手,两者都是人,都具备人的一些基本属性和行为,如姓名、年龄、吃饭、睡觉等;

2)学生会游泳,歌手会跳舞,这些都是独立的扩展功能;

06【接口、多态】_JavaSE_06

1.3.2 代码实现

  • 定义人类:
package com.dfbz.demo01;
/**
 * @author lscl
 * @version 1.0
 * @intro: 人类
 *  属性: 姓名、年龄
 *  行为:吃饭、睡觉
 */
class Person {
    private String name;
    private int age;
    public Person() {
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
  • 游泳接口:只要实现该接口代表具备游泳功能;
package com.dfbz.demo01;
/**
 * @author lscl
 * @version 1.0
 * @intro: 游泳接口,实现该接口代表具备游泳功能
 */
public interface Swimmable {
//    public abstract void swim();
    // 等价于上面的写法
    void swim();
}
  • 跳舞接口:只要实现该接口代表具备跳舞功能;
package com.dfbz.demo01;
/**
 * @author lscl
 * @version 1.0
 * @intro: 跳舞接口, 实现该接口代表具备跳舞功能
 */
public interface Danceable {
    //    public abstract void dance();
    // 等价于上面的写法
    void dance();
}
  • 定义学生类:
package com.dfbz.demo01;
/**
 * @author lscl
 * @version 1.0
 * @intro: 学生类, 是一个人, 并且会游泳
 */
public class Student extends Person implements Swimmable {
    public Student() {
    }
    public Student(String name, int age) {
        // 调用父类的构造方法初始化
        super(name, age);
    }
    // 实现抽象方法
    @Override
    public void swim() {
        System.out.println(getName() + "在游泳!");
    }
    // 自己独特的方法
    public void study() {
        System.out.println(getName() + "在学习!");
    }
}
  • 定义歌手类:
package com.dfbz.demo01;
/**
 * @author lscl
 * @version 1.0
 * @intro: 歌手类, 是一个人, 并且会跳舞
 */
public class Singer extends Person implements Danceable {
    public Singer() {
    }
    public Singer(String name, int age) {
        // 调用父类的构造方法初始化
        super(name, age);
    }
    @Override
    public void dance() {
        System.out.println(getName() + "在跳舞~");
    }
    // 自己独特的方法
    public void sing() {
        System.out.println(getName() + "在唱歌~");
    }
}

1.3.3 测试类

package com.dfbz.demo01;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        // 实例化学生类
        Student student = new Student("小灰", 22);
        // 学生学习
        student.study();
        // 学生游泳
        student.swim();
        // 实例化歌手类
        Singer singer = new Singer("小蓝", 20);
        // 歌手唱歌
        singer.sing();
        // 歌手跳舞
        singer.dance();
    }
}

执行结果:

06【接口、多态】_抽象方法_07

1.4 接口的多实现

在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现。并且,一个类能继承一个父类,同时实现多个接口。

实现格式:

class 类名 [extends 父类名] implements 接口名1,接口名2,接口名3... {
    // 重写接口中抽象方法【必须】
    // 重写接口中默认方法【不重名时可选】
}

1.4.1 普通抽象方法

接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次。代码如下:

定义多个接口:

interface A{
    public abstract void showA();
    public abstract void show();
}
interface B{
    public abstract void showB();
    public abstract void show();
}

实现类:

class C implements A,B{
    @Override
    public void showA() {
        System.out.println("A");
    }
    @Override
    public void showB() {
        System.out.println("B");
    }
    //只需要重写一次
    @Override
    public void show() {
        System.out.println("C");
    }
}

Tips:由于接口中定义的都是抽象方法,多实现时由子类来决定方法的具体功能,因此不会出现多继承(实现)时的"钻石问题";

1.4.2 默认方法

在JDK8中,允许存在默认方法,子类实现接口时,子类可以选择重写默认方法,也可以不重写(继承下来),那么在实现多个接口时,存在同名方法就会出现钻石问题

Tips:在实现多个接口中,如果多个接口中包含有同名方法,子类必须实现该方法。

  • 定义两个接口:
interface A {
    public default void methodA() {
        System.out.println("method A..");
    }
    public default void method() {
        System.out.println("methodA");
    }
}
interface B {
    public default void methodB() {
        System.out.println("method B..");
    }
    public default void method() {
        System.out.println("methodB");
    }
}
  • 定义实现类:
class C implements A, B {
    @Override
    public void methodA() {
        System.out.println("A");
    }
    // 如果实现多个接口中有相同的默认方法,必须实现
    @Override
    public void method() {
        System.out.println("必须要实现的方法");
    }
    @Override
    public void methodB() {
        System.out.println("B");
    }
}

1.4.3 静态方法

在接口中,继承时存在同名的静态方法并不会冲突,原因是只能通过各自接口名访问静态方法且接口中静态方法不会被继承下去;

1.4.4 方法优先级

当一个类,既继承一个父类,又实现若干个接口并且该接口(默认方法)存在和类同名方法时,子类就近选择执行父类的成员方法

package com.dfbz.demo04;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        C c=new C();
        c.show();		
    }
}
class B{
    public void show(){
        System.out.println("类中的show方法");
    }
}
interface A{
    public default void show(){
        System.out.println("接口中的show方法");
    }
}
class C extends B implements A{
}

1.5 接口的其他特性

1.5.1 接口的多继承

一个接口能继承另一个或者多个接口,这和类之间的继承比较相似。接口的继承使用 extends 关键字,子接口继承父接口的方法。如果父接口中的默认方法有重名的,那么子接口需要重写一次

示例代码:

package com.dfbz.demo05;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
    }
}
interface A {
    void methodA();
    void method();
}
interface B {
    void methodB();
    void method();
}
interface C extends B, A {
    void methodC();
    void method();
}
class D implements C {
    @Override
    public void methodA() {
        System.out.println("methodA");
    }
    @Override
    public void methodB() {
        System.out.println("methodB");
    }
    @Override
    public void methodC() {
        System.out.println("methodC");
    }
    @Override
    public void method() {
        System.out.println("我是D");
    }
}

1.5.2 接口的成员变量

接口中成员变量默认加上public static final修饰,接口中被static修饰的方法不能被继承,但被static修饰的变量(常量)是可以被继承的;

  • 示例代码:
package com.dfbz.demo06;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        System.out.println(B.NUM);
        System.out.println(B.SUM);
        System.out.println(new B().NUM);
        System.out.println(new B().SUM);
//        B.NUM=30;     // 错误,接口的成员变量都被final修饰,不可更改
    }
}
interface A {
    // 等价于 public static final int NUM
    int NUM = 20;
    // 等价于上面的写法
    public static final int SUM = 100;
}
class B implements A {
}

1.5.3 接口与类的继承

我们都知道,当一个类实现一个接口时,必须重写这个接口的所有抽象方法;但如果这个类继承与某个类,该父类中包含了接口中的方法,那么子类就不需要再次重写接口中的方法了;

  • 示例代码:
package com.dfbz.demo09;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        C c = new C();
        c.method();
    }
}
interface A {
    void method();
}
class B {
    public void method() {
        System.out.println("bbb");
    }
}
class C extends B implements A {
    // B类中存在method方法,因此不需要C类再去编写method方法
}

二、多态

2.1 多态概述

面向对象的三大特性——封装、继承、多态;前两个我们都学习过了,多态也是面向对象中一项重大的特征,多态体现了程序的可扩展性、代码的复用性等;

  • 多态:即事物(对象)存在的多种形态,简称多态;

在生活中,多态处处看见;如"我要吃水果",这个水果有很多种形态,苹果、梨子、香蕉等都是水果;再比如说话,猫的叫声是喵喵,狗的叫声是汪汪,苍蝇的叫声蝇蝇等;同一行为,通过不同的事物,可以体现出来的不同的形态;

2.2 多态案例

需求:现在有一个宠物店,宠物店有寄存宠物的功能,但是运营之初,我们只能寄存狗,我们针对狗这个类,设计了吃饭、睡觉等功能;

2.2.1 功能实现

1)定义狗类:

package com.dfbz.demo01;
/**
 * @author lscl
 * @version 1.0
 * @intro: 狗类
 */
public class Dog {
    public void eat() {
        System.out.println("狗吃骨头");
    }
    public void sleep() {
        System.out.println("狗趴着睡");
    }
}

2)定义宠物店类:

package com.dfbz.demo01;
/**
 * @author lscl
 * @version 1.0
 * @intro: 宠物店类
 */
public class PetStore {
    // 寄存狗
    public void storeDog(Dog dog){
        dog.eat();
        dog.sleep();
    }
}

3)测试类:

package com.dfbz.demo01;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        // 创建宠物店
        PetStore petStore=new PetStore();
        
        // 创建一只狗
        Dog dog=new Dog();
        // 存入一只狗
        petStore.storeDog(dog);
    }
}

上述代码没有什么问题,但随着宠物店运营一段时间之后,决定要扩展宠物品类了,又引进了猫,我们需要对猫设计一套吃饭、睡觉的功能,于是可能就变成了这样:

定义猫类:

package com.dfbz.demo01;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Cat {
    public void eat() {
        System.out.println("猫吃鱼");
    }
    public void sleep() {
        System.out.println("猫躺着睡");
    }
}

修改宠物店代码:

package com.dfbz.demo01;
/**
 * @author lscl
 * @version 1.0
 * @intro: 宠物店类
 */
public class PetStore {
    // 寄存狗
    public void storeDog(Dog dog){
        dog.eat();
        dog.sleep();
    }
    // 寄存猫
    public void storeCat(Cat cat){
        cat.eat();
        cat.sleep();
    }
}

测试代码:

package com.dfbz.demo01;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        // 创建宠物店
        PetStore petStore=new PetStore();
        
        // 创建一只狗
        Dog dog=new Dog();
        // 存入一只狗
        petStore.storeDog(dog);
        // 创建一只狗
        Cat cat=new Cat();
        // 存入一只猫
        petStore.storeCat(cat);
    }
}

2.2.2 使用多态改进

我们很快就能发现上面代码的问题了,如果以后宠物店需要增加新宠物呢?如兔子、小白鼠、小猪呢?我们必须在宠物店扩展N多个收纳这些宠物的方法,这样显然不合理;我们在想不管是猫、狗、兔子等不都是动物吗?我们干嘛不定义一个收纳动物的方法呢?这样一来只要是动物都可以收纳进宠物店!

那么如何将其定义为动物呢?通过继承!我们可以定义一个顶层父类Animal,在Animal中规定好继承体系的共性功能,比如吃饭、睡觉等;让猫、狗、兔子等继承Animal;此时多态就体现出来了,那么我可以将猫称之为动物、狗也可以称为动物...;

06【接口、多态】_JavaSE_08

代码实现:

1)定义动物类:

package com.dfbz.demo02;
/**
 * @author lscl
 * @version 1.0
 * @intro: 动物类
 */
public class Animal {
    public void eat() {
        System.out.println("动物吃饭");
    }
    public void sleep() {
        System.out.println("动物睡觉");
    }
}

2)定义狗类:

package com.dfbz.demo02;
/**
 * @author lscl
 * @version 1.0
 * @intro: 狗类,继承Animal
 */
public class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }
    @Override
    public void sleep() {
        System.out.println("狗趴着睡");
    }
}

3)定义猫类:

package com.dfbz.demo02;
/**
 * @author lscl
 * @version 1.0
 * @intro: 猫类, 继承Animal
 */
public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
    @Override
    public void sleep() {
        System.out.println("猫躺着睡");
    }
}

4)宠物店类:收纳宠物

package com.dfbz.demo02;
/**
 * @author lscl
 * @version 1.0
 * @intro: 宠物店类, 收纳宠物(Animal)
 */
public class PetStore {
    /**
     * 收纳动物方法,只要是动物都可以收纳
     *
     * @param animal
     */
    public void store(Animal animal) {		// Animal animal=new Dog();  Animal animal=new Cat();
        // 调用动物的吃饭方法
        animal.eat();
        // 调用动物的睡觉方法
        animal.sleep();
    }
}

5)测试类:

package com.dfbz.demo02;
import com.dfbz.demo01.Cat;
import com.dfbz.demo01.Dog;
import com.dfbz.demo01.PetStore;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        // 创建宠物店
        PetStore petStore = new PetStore();
        // 创建一只狗
        Dog dog = new Dog();
        // 存入一只狗
        petStore.storeDog(dog);         // 传入Dog给Animal
        // 创建一只狗
        Cat cat = new Cat();
        // 存入一只猫
        petStore.storeCat(cat);         // 传入Cat给Animal
    }
}

运行结果:

06【接口、多态】_抽象方法_09

2.3 多态的用法

2.3.1 多态的条件

  1. 继承或者实现
  2. 父类引用指向子类对象

一句话概括多态就是父类引用指向子类对象,在上一章案例中就是父类引用(Animal)指向了子类对象(Dog、Cat),多态一般伴随着重写的出现,调用者是父类,具体执行的方法则是子类(子类重写了父类的方法运行的是子类),因为只有子类的功能才能凸显多态性,不同的子类具备的功能是不一样的,那么有重写肯定就会有继承或者实现;

  • 多态的格式:
父类类型 变量名 = new 子类对象;
变量名.方法名();
  • 示例代码:
Fu fu=new Zi();
f.method();

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写方法。

2.3.2 多态示例

每个水果都有自己的功效,定义一个水果类,提供功效方法;由于每个水果的功效是不同的,因此该方法是抽象方法;定义苹果、香蕉类继承水果类,重写功效方法,展示各自的功效;

1)水果类:

/**
 * 定义水果类
 */
abstract class Fruit {
    // 水果的功效(具体的功效要看水果的种类)
    public abstract void benefit();
}

2)定义苹果类:

/**
 * 苹果类
 */
class Apple extends Fruit {
    public void benefit() {
        System.out.println("苹果功效:生津止渴、健脾养心、补血安神");
    }
}

3)定义香蕉类:

/**
 * 香蕉类
 */
class Banana extends Fruit {
    public void benefit() {
        System.out.println("香蕉功效:降血压、保护心血管、维持酸碱平衡、解酒");
    }
}

4)测试类:

package com.dfbz.demo03;
import com.dfbz.demo02.Animal;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        Apple apple=new Apple();    // 创建一个苹果
        apple.benefit();        	// 水果的功效
                
        Banana banana=new Banana();     // 创建一个香蕉
        banana.benefit();               // 梨子的功效
        
        Fruit appleFruit=new Apple();        // 创建一个水果
        appleFruit.benefit();                // 具体水果的功效(要看new的是什么水果)
        
        Fruit bananaFruit=new Banana();     // 创建一个水果
        bananaFruit.benefit();              // 具体水果的功效(要看new的是什么水果)
    }
}

2.3.3 多态的扩展性

我们在Demo类中提供showBenefit方法,用于查看水果的功效:

package com.dfbz.demo03;
import com.dfbz.demo02.Animal;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        // 创建苹果
        Apple apple = new Apple();
        
        // 创建香蕉
        Banana banana = new Banana();
        
        // 查看苹果功效
        showBenefit(apple);     // Fruit fruit=new Apple();
        
        // 查看香蕉功效   
        showBenefit(banana);    // Fruit fruit=new Banana();    
    }
    public static void showAppleBenefit(Apple apple){            // 查看苹果功效 Apple apple=new Apple();
        apple.benefit();
    }
    
    public static void showBananaBenefit(Banana banana){            // 查看苹果功效 Banana banana=new Banana();
        banana.benefit();
    }
    public static void showBenefit(Fruit fruit){            // 查看苹果功效 Fruit fruit=new Banana()/new Apple();
        fruit.benefit();
    }
}

2.4 多态的转型

2.4.1 基本数据类型的自动转换

  1. 自动转换:范围小的赋值给范围大的.自动完成:double d = 10;
  2. 强制转换:范围大的赋值给范围小的,强制转换:int i = (int)6.88;

2.4.2 向上转型

  • 向上转型也叫自动类型提升;多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的。当父类引用指向一个子类对象时,便是向上转型。

示例:

父类类型  变量名 = new 子类类型();
如:Animal a = new Cat();

2.4.3 向下转型

1)向下转型概念
  • 向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。

示例:

子类类型 变量名 = (子类类型) 父类变量名;
Animal animal=new Cat();
如: Cat c =(Cat) animal;

示例代码:

package com.dfbz.demo04;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        // 向上转型(自动类型提升)
        Animal animal = new Cat();
        animal.eat();           // 猫吃鱼
        // 向下转型
        Cat cat = (Cat) animal;
        cat.eat();
    }
}
class Animal {
    public void eat() {
        System.out.println("动物吃饭");
    }
}
class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }
}
class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}
2)注意事项

注意:在向下转型时,必须保证源类型就是需要转换的类型,如上述中的animal原本就是Cat类型,只不过向上提升为了Animal类型;不能强制把一个Animal转换为Cat,或者把其他类型(Dog)转换为猫;

示例代码:

public class Demo01 {
    public static void main(String[] args) {
        // 向上转型(自动类型提升)
        Animal animal = new Dog();
        Cat cat = (Cat) animal;       // 错误
    }
}

这上述代码可以通过编译,但是运行时,却报出了 ClassCastException错误(异常);这是因为,明明创建了Dog类型对象,运行时,当然不能转换成Cat对象的。这两个类型并没有任何继承关系,不符合类型转换的定义。为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验;

出现如下错误:

06【接口、多态】_JavaSE_10

2.4.4 instanceof关键字

instanceof 是 Java 的一个二元操作符,类似于 ==,>,< 等操作符,它的作用是测试它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型。

语法如下:

boolean result = 变量名 instanceof 数据类型;

为了防止java.lang.ClassCastException异常的出现,我们可以在进行类型转换时先判断一下

public class Demo01 {
    public static void main(String[] args) {
        // 向上转型(自动类型提升)
        Animal animal = new Dog();
        if(animal instanceof Cat){
            Cat cat = (Cat) animal;       // 错误
        }else{
            System.out.println("不是该类型!");
        }
    }
}

三、综合案例

3.1 案例需求分析

我国地大物博,各地区的人口味也不一样,例如赣、湘、川、渝、黔喜吃辣;黔、滇、桂喜欢吃酸;晋、陕喜吃醋;鲁、辽、黑、吉喜欢重咸、重辣、重咸;粤、闽、浙则偏淡;各地方的风景名胜也非常多,如三大名楼、三山五岳、兵马俑、西湖、故宫等;

现需设计一套程序:

江西人吃辣,风景名胜有:南昌滕王阁景区、千年瓷都景德镇、庐山风景区...

贵州人吃辣也吃酸,风景名胜有:黄果树瀑布、赤水丹霞旅游区、镇远古城风景区...

广东人吃酸也吃甜,风景名胜有:广州白云山景区、韶关丹霞山景区、惠州罗浮山景区...

3.2 案例分析

  • 1)描述人类,提供姓名和年龄属性,提供自我介绍方法(intro),具备旅游方法travel,可以去不同的地方旅游,不同地方的景点也不一样;
  • 2)定义江西人、贵州人、广东人,继承人类都会旅游
  • 3)定义吃辣(Chili)、吃酸(Sour)、吃甜(Sweet)接口,扩展江西人吃辣(实现吃辣接口)、贵州人吃酸吃辣(实现吃酸、吃辣接口)、广东人吃酸吃甜(实现吃酸、吃甜接口);
  • 4)定义旅游接口(Travel),具备旅行方法(journey)
  • 5)定义江西、贵州、广东三个城市类,实现旅游接口,具备旅游功能;

3.3 代码实现

1)定义旅行接口:

package com.dfbz.demo;
/**
 * @author lscl
 * @version 1.0
 * @intro: 旅游接口
 */
public interface Travel {
    // 旅行方法,每个城市的风景名胜都不一样
    void journey();
}

2)定义江西、贵州、广东三个省份类,实现旅行接口,提供各自的风景名胜:

  • 江西类:
package com.dfbz.demo;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class JiangXi implements Travel {
    /**
     * 江西风景名胜
     */
    @Override
    public void journey() {
        System.out.println("南昌滕王阁~");
        System.out.println("千年瓷都景德镇~");
        System.out.println("庐山风景区~");
        System.out.println("八一纪念碑~");
        System.out.println("婺源风景区~");
    }
}
  • 贵州类:
package com.dfbz.demo;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class GuiZhou implements Travel {
    /**
     * 贵州风景名胜
     */
    @Override
    public void journey() {
        System.out.println("黄果树瀑布~");
        System.out.println("赤水丹霞旅游区~");
        System.out.println("镇远古城风景区~");
        System.out.println("梵净山旅游区~");
        System.out.println("安顺龙宫风景区~");
    }
}
  • 广东类:
package com.dfbz.demo;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class GuangDong implements Travel {
    /**
     * 广东风景名胜
     */
    @Override
    public void journey() {
        System.out.println("广州白云山景区~");
        System.out.println("韶关丹霞山景区~");
        System.out.println("惠州罗浮山景区~");
        System.out.println("广州长隆旅游度假区~");
        System.out.println("佛山西樵山景区~");
    }
}

3)定义人类,具备旅游方法,具体去哪旅游需要根据传递的省份决定:

package com.dfbz.demo;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public abstract class People {
    
    // 提供姓名和年龄
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void intro(){
        System.out.println("姓名: "+name+",年龄: "+age);
    }
    
    // 各自家乡的风景名胜
    public abstract void travel(Travel travel);
}

4)定义吃辣、吃酸、吃甜接口:

  • 吃辣接口:
package com.dfbz.demo;
/**
 * @author lscl
 * @version 1.0
 * @intro: 吃辣接口
 */
public interface Chili {
    void chili();
}
  • 吃酸接口:
package com.dfbz.demo;
/**
 * @author lscl
 * @version 1.0
 * @intro: 吃酸接口
 */
public interface Sour {
    void sour();
}
  • 吃甜接口:
package com.dfbz.demo;
/**
 * @author lscl
 * @version 1.0
 * @intro: 吃甜接口
 */
public interface Sweet {
    void sweet();
}

5)定义江西人、贵州人、广东人类,江西人吃辣、贵州人吃酸吃辣、广东人吃酸吃甜:

  • 江西人:
package com.dfbz.demo;
/**
 * @author lscl
 * @version 1.0
 * @intro: 江西人吃辣
 */
public class JiangXiPeople extends People implements Chili {
    @Override
    public void chili() {
        System.out.println("江西人吃辣");
    }
    /**
     * 去不同城市旅游
     *  Travel travel=new JiangXi();
     *  Travel travel=new GuiZhou();
     *  Travel travel=new GuangDong();
     * @param travel
     */
    @Override
    public void travel(Travel travel) {
        travel.journey();
    }
}
  • 贵州人:
package com.dfbz.demo;
/**
 * @author lscl
 * @version 1.0
 * @intro: 贵州人吃辣也吃酸
 */
public class GuiZhouPeople extends People implements Chili, Sour {
    @Override
    public void chili() {
        System.out.println("贵州人吃辣");
    }
    @Override
    public void sour() {
        System.out.println("贵州人吃酸");
    }
    
    /**
     * 去不同城市旅游
     *  Travel travel=new JiangXi();
     *  Travel travel=new GuiZhou();
     *  Travel travel=new GuangDong();
     * @param travel
     */
    @Override
    public void travel(Travel travel) {
        travel.journey();
    }
}
  • 广东人:
package com.dfbz.demo;
/**
 * @author lscl
 * @version 1.0
 * @intro: 广东人吃酸和吃甜
 */
public class GuangdongPeople extends People implements Sour, Sweet {
    @Override
    public void sour() {
        System.out.println("广东人吃酸");
    }
    @Override
    public void sweet() {
        System.out.println("广东人吃甜");
    }
    /**
     * 去不同城市旅游
     *  Travel travel=new JiangXi();
     *  Travel travel=new GuiZhou();
     *  Travel travel=new GuangDong();
     * @param travel
     */
    @Override
    public void travel(Travel travel) {
        travel.journey();
    }
}

定义如下:

06【接口、多态】_抽象方法_11

测试类:

package com.dfbz.demo;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        GuiZhouPeople guiZhouPeople = new GuiZhouPeople();
        guiZhouPeople.travel(new JiangXi());            // 贵州人去江西旅游
        guiZhouPeople.chili();                          // 贵州人吃辣
        guiZhouPeople.sour();                           // 贵州人吃酸
        System.out.println("--------------");
        JiangXiPeople jiangXiPeople=new JiangXiPeople();
        jiangXiPeople.chili();                          // 江西人吃辣
        jiangXiPeople.travel(new GuangDong());          // 江西人去广东旅游
    }
}

运行结果如下:

06【接口、多态】_默认方法_12