Java学习历程十《多态,接口,内部类》

馋奶兔 提交于 2019-11-26 21:59:09

Java的多态

多种形态,同一种方式在不同的场景中得到不同的结果.

  • 编译时多态[少部分]
    • 通过方法的重载实现
  • 运行时多态[大部分]
    • 决定了调用哪个类里边定义的方法

多态的必要条件:

  • 满足继承关系
  • 父类应用执行子类对象

设计:


Animal:
    name 
    month
    eat()
    getName()
    getMonth()
    getWeight()
    
Cat: -> Animal
    weight
    eat()
    run()
    Cat()
    
Dog: -> Animal
    sex
    eat()
    sleep()
    Dog()

向上转型

Animal

package com.animal;

public class Animal {
    private String name;
    private int month;

    public Animal() {

    }

    public Animal(String name, int month) {
        this.setName(name);
        this.setMonth(month);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public void eat(){
        System.out.println("动物都有吃的能力");
    }
}

Cat

package com.animal;

public class Cat extends Animal {
    private double weight;

    public Cat() {
    }

    public Cat(String name, int month, double weight) {
        super(name, month);
        this.setWeight(weight);
    }

    public void run() {
        System.out.println("猫在跑~~~~");
    }

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }

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

    public static void say(){
        System.out.println("Cat say()");
    }
}


Dog

package com.animal;

public class Dog extends Animal {
    private String sex;

    public Dog() {
    }

    public Dog(String name, int month, String sex) {
        super(name, month);
        this.setSex(sex);
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public void eat() {
        System.out.println("狗吃肉~~~");
    }

    public void sleep() {
        System.out.println("小狗睡觉~~~~");
    }

    public static void say() {
        System.out.println("Dog say()");
    }
}

AnimalTest

package com.test;

import com.animal.Animal;
import com.animal.Cat;
import com.animal.Dog;

public class AnimalTest1 {
    public static void main(String[] args) {
        Animal one = new Animal();
        // 子类对象转型为父类对象<向上转型>   父类的引用执行子类实例
        Animal two = new Cat();
        Animal three = new Dog();
        // 虽然都是Animal实例,但是调用的是相应类的eat()方法
        one.eat();
        two.eat();
        three.eat();
        one.say();
        two.say();
        three.say();
    }
}

父类的的引用指向子类的实例对象,父类是小范围,子类对父类进行了扩展,one two three三个实例都可以调用
父类Animal的所有公开接口,但是不可以调用超出父类范围,子类特有的方法

注意: Animal的静态方法say在多态的情况下,调用的是Animal的say()而不是相应子类的say,静态方法无法展现多态

注意:父类中的 static 方法在子类中是不能被重写的,只能被继承调用,所以才会出现上述的结果,如果想用子类的静态方法,那么只能使用
向下转型的方式,来实现调用可以被继承的,重写的方法

向下转型

package com.test;

import com.animal.Animal;
import com.animal.Cat;
import com.animal.Dog;

public class AnimalTest2 {
    // 向下转型,强制类型转换
    // 子类引用指向父类实例,可以调用子类中继承父类的方法,也可以调用相应子类中的特有方法
    public static void main(String[] args) {
        Animal one = new Cat();
        Animal two = new Dog();
        Cat tmp_1 = (Cat) one;
        Dog tmp_2 = (Dog) two;
        tmp_1.eat();
        tmp_1.getWeight();
        one.eat();

        tmp_2.eat();
        tmp_2.getSex();
        two.eat();

    }

}

instanceof运算符,判断左侧的对象是否是右侧类的实例,返回Boolean

Cat: --> Animal:

Cat one = new Cat();
System.out.println(one instanceof Cat); --> true
System.out.println(one instanceof Animal); --> true

向下转型的例子,现在有Master类,master可以feed Animal

  • 方案一 方法重载,重载多个feed方法
  • 方案二 多态,向下转型

AnimalMaster类

package com.animal;

/**
 * 动物的主人类
 * 喂养猫咪的时候,吃完跑了
 * 喂养狗的时候,吃完去睡觉
 * <p>
 * 空闲时间多,养狗
 * 空闲时间少,养猫
 */
public class AnimalMaster {
    // 通过类型判断  向下转型
    public void feed(Animal obj) {
        obj.eat();
        if (obj instanceof Cat) {
            Cat tmp = (Cat) obj;
            tmp.run();
        } else if (obj instanceof Dog) {
            Dog tmp = (Dog) obj;
            tmp.sleep();
        }
    }

    // 向上转型
    public Animal raiseAnimal(Boolean isManyTime) {
        if (isManyTime) {
            System.out.println("休闲时间重做,适合养狗");
            return new Dog();
        } else {
            System.out.println("休闲时间少,适合养猫");
            return new Cat();
        }
    }

}

AnimalMasterTest

package com.test;

import com.animal.Animal;
import com.animal.AnimalMaster;
import com.animal.Cat;
import com.animal.Dog;

public class AnimalMasterTest {
    public static void main(String[] args) {
        AnimalMaster people = new AnimalMaster();
        Cat one = new Cat();
        Dog two = new Dog();
        // 向下转型
        people.feed(one);
        people.feed(two);
        Boolean isManyTime = true;
        // 向上转型
        Animal am;
        am = people.raiseAnimal(isManyTime);
        System.out.println(am);

    }
}

抽象类

在上述代码中Animal类可以被实例化,但是并没有实际的意义,可以实例化猫,狗,不能说实例化一个动物

现在有一种解决方案,可以实现某个类只能被继承,而不能被实例化

abstract 关键子修饰的类就是抽象类,这样这个类就不能被实例化了.

public abstract class Animal{
    //
}

Animal不能被实例化,但是还是可以向上转型的 , 它的引用可以指向子类的实例

当我们需要某些类必须具有某些方法,而不关注子类实现这个方法的具体逻辑的时候,我们就可以将父类定义成为抽象类,子类去实现抽象类中定义的
方法,那么抽象类中只需要定义方法,而不需要去实现,这个时候,这种方法可以被定义成为抽象方法,抽象方法也是需要abstract修饰而且没
有大括号方法体.

注意: 如果父类中定义了抽象方法,那么子类必须去重写实现这个方法,否则子类会报类型错误.

如果子类不需要实现方法,那么子类也必须抽象掉.

抽象类的价值:

不使用定义抽象类,定义抽象方法的方式也能实现整个项目的业务逻辑,
当整个项目变得巨大的时候,会随时提示编程人员,哪些方法是需要重写的.非常的友好,让代码更有意义.

曾经提过:static final private修饰的方法是不能被子类重写的,那么abstract修饰符就不能与前三者搭配使用,因为抽象方法必须要被子类重写.

问题

已知在Java中,一个类只能有一个父类[单继承]

  • 如果我们需要某个子类要兼容多个类的特征,如何做?
  • 多个不同的类,要具有某些相同的特征,如何做?

接口

接口 interface

案例:描述手机发展史

  • 一代
    • 打电话
  • 二代
    • 增加发短信
  • 三代
    • 增加看视频
    • 增加听音乐
  • 四代
    • 增加玩游戏
    • 增加上网
    • 增加拍照

现在有: 电脑 智能手表 数码相机 需要拥有上述手机相同功能的部分

无法抽象出一个公共的父类,因为他们之间的功能具有交叉性

  • 接口定义了某一批类所需要遵守的规范
  • 接口不关心这些类的内部数据,也不关心这些类里方法的实现细节,只规定这些类必须提供某些方法

IPhoto 定义拍照的接口

package com.phone;

/**
 * 具有照相能力的接口
 */
public interface IPhoto {
    // 拍照的方法 接口中的抽象方法可以不添加abstract
    public void photo();
}

第四代手机

package com.phone;

/**
 * 四代手机
 */
public class FourGenerationsPhone extends ThreeGenerationsPhone implements IPhoto {
    public FourGenerationsPhone() {
    }

    public FourGenerationsPhone(String brand, int price) {
        super(brand, price);
    }

    /**
     * 拍照功能
     */
    @Override
    public void photo() {
        System.out.println("手机可以拍照");
    }

    /**
     * 上网功能
     */
    public void network() {
        System.out.println("手机可以上网");
    }

    /**
     * 玩游戏功能
     */
    public void game() {
        System.out.println("手机可以玩游戏");
    }
}

数码相机

package com.phone;

public class Camera implements IPhoto{
    @Override
    public void photo() {
        System.out.println("相机可以拍照");
    }
}

第四代手机和数码相机都使用了拍照的接口

PhoneTest

package com.test;

import com.phone.Camera;
import com.phone.FourGenerationsPhone;
import com.phone.IPhoto;

public class PhoneTest {
    public static void main(String[] args) {
        IPhoto iPhoto1 = new  FourGenerationsPhone("三星",2000);
        IPhoto iPhoto2 = new Camera();
        iPhoto1.photo();
        iPhoto2.photo();
    }
}

通过接口实例化的对象只能访问接口中定义的公共内容

上网功能的接口

package com.phone;

public interface INetWork {
    // 接口中的抽象方法可以不写abstract关键字
    public void network();
}

接口中的方法都是public方法,常量默认是public static final.

接口中除了public方法外,还有default static方法.后两者是携带方法体的,这两种方法可以在使用接口的类中,不重写.

默认方法可以通过接口实例去调用,但是接口静态方法需要通过接口去调用.

如果某些接口中的方法没必要然使用接口的类去实现,那么就可以选择默认方法或者接口静态方法去定义这些方法.<jdk1.8+>

默认方法是可以被重写的,重写的时候就不需要default修饰符了,而是使用public

接口名.super.方法名();表示调用接口原定义的方法,可以被扩展

接口中的接口静态方法,不可以在使用类中重写,只能通过接口名去调用

多接口中相同名的方法冲突的解决方法,在实用类中定义自己的方法(重写)

  • network接口中有default void connection(){}
  • game接口中有default void connection(){}

Computer使用了上述两个接口,那么需要重写connection方法来实现自己的方法

如果Computer中从父类继承了一个connection方法,使用的两个接口中也都具有connection方法,那么都会使用类继承下来的connection方法
经过子类重写后,子类有了自己的方法,那么会调用自己的connection方法.

多个接口的使用需要有,隔开

多借口中的冲突命名常量,在使用的时候需要使用相应的接口名去准确调用常量,否则无法识别使用的是哪一个接口的常量

如果从父类中继承了同名常量,也会因为无法分辨报错,这与继承方法不同,继承方法会以继承方法为首,我们只有在子类中定义
冲突的常量(属性),定义自己的数据,那么就会以自己定义的数据为主.

接口相关代码:


public interface ICall {
    public void call();
}
public interface IGame {
    void game();

    default void connection() {
        System.out.println("游戏建立连接");
    }
}
public interface IMessage {
    public void message();
}
public interface IMusic {
    public void music();
}
public interface INetWork {
    // 接口中的抽象方法可以不写abstract关键字
    void network();

    String PORT = "8080";
    String HOST = "127.0.0.1";

    default void connection() {
        System.out.println("接口默认连接");
    }

    static void stop() {
        System.out.println("接口静态方法");
    }
}
/**
 * 具有照相能力的接口
 */
public interface IPhoto {
    // 拍照的方法
    public void photo();
}
public interface IVideo {
    public void video();
}

手机

public class OriginalPhone implements ICall {
    public OriginalPhone() {
    }

    public OriginalPhone(String brand, int price) {
        this.setBrand(brand);
        this.setPrice(price);
    }

    private String brand;
    private int price;

    public void call() {
        System.out.println("手机能打电话");
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}
public class SecondGenerationPhone extends OriginalPhone implements IMessage {
    public SecondGenerationPhone() {
    }

    public SecondGenerationPhone(String brand, int price) {
        super(brand, price);
    }

    /**
     * 发短信的功能
     */
    public void message() {

        System.out.println("我可以发短信");
    }
}
public class ThreeGenerationsPhone extends SecondGenerationPhone implements IMusic, IVideo {
    public ThreeGenerationsPhone() {
    }

    public ThreeGenerationsPhone(String brand, int price) {
        super(brand, price);
    }

    /**
     * 增加看视频的功能
     */
    @Override
    public void video() {
        System.out.println("我可以看视频");
    }

    /**
     * 增加听音乐的功能
     */
    @Override
    public void music() {
        System.out.println("我可以听音乐");
    }
}
public class FourGenerationsPhone extends ThreeGenerationsPhone implements IPhoto, INetWork, IGame {
    public FourGenerationsPhone() {
    }

    public FourGenerationsPhone(String brand, int price) {
        super(brand, price);
    }

    /**
     * 拍照功能
     */
    @Override
    public void photo() {
        System.out.println("手机可以拍照");
    }

    /**
     * 上网功能
     */
    @Override
    public void network() {
        System.out.println("手机可以上网");
    }

    @Override
    public void connection() {
        System.out.println("手机连接到" + INetWork.HOST + ":" + INetWork.PORT);
    }

    /**
     * 玩游戏功能
     */
    public void game() {
        System.out.println("手机可以玩游戏");
    }
}

电脑

public class Computer implements INetWork, IVideo, IMusic, IGame {
    public Computer() {
    }

    @Override
    public void network() {
        System.out.println("电脑可以上网");
    }

    @Override
    public void connection() {
        System.out.println("电脑连接到" + INetWork.HOST + ":" + INetWork.PORT);
    }

    @Override
    public void music() {
        System.out.println("电脑可以听音乐");
    }

    @Override
    public void video() {
        System.out.println("电脑可以看视频");
    }

    @Override
    public void game() {
        System.out.println("电脑可以玩游戏");
    }
}

数码相机

public class Camera implements IPhoto{
    @Override
    public void photo() {
        System.out.println("相机可以拍照");
    }
}

智能手表

public class SmartWatch implements IMessage, ICall {

    @Override
    public void call() {
        System.out.println("智能手表打电话");
    }

    @Override
    public void message() {
        System.out.println("智能手表发短信");
    }
}

内部类

Java中,可以将一个类定义到另一个类的内部,或者一个方法体里面,
这样的类称为内部类,包含内部类的类,称为外部类

例如:

public class People {
    private int age;
    private String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Heart getHeart() {
        return new Heart();
    }

    public class Heart {
        public String beat() {
            return "The heart is beating";
        }
    }
}

内部类体现更好的封装,这样内部类的信息在外部就无法被获取到,保护内部类的信息

内部类:

  • 成员内部类
  • 静态内部类
  • 方法内部类
  • 匿名内部类

成员内部类<普通内部类>

上述的People-Heart就是成员内部类,

如果内部类Heartpublic的情况下,可以这样得到Heart的实例

import com.internalClass.People;

public class PeopleTest {
    public static void main(String[] args) {
        People p1 = new People();
        p1.setAge(12);
        p1.setName("小明");
        // 方式一
        People.Heart heart = new People().new Heart();
        System.out.println(heart.beat());
        // 方式二
        heart = p1.new Heart();
        System.out.println(heart.beat());
        // 方式三
        heart = p1.getHeart();
        System.out.println(heart.beat());
    }
}

内部类的访问修饰符可以任意,但是需要注意访问范围,默认不写范围不能出包,public可以跨包,private不可以出包

内部类可以直接使用外部类中定义的时间和方法

  • 当以People.Heart heart = new People().new Heart();方式去获取内部类的时候,外部类的某些属性哈没有被初始化,那么内部类只能获取外部类中默认的属性
  • 当以heart = p1.new Heart();方式去获取内部类,而且在获取内部类之前对外部类的实例p1的属性进行了初始化,那么内部类就可以获取外部类准确的属性
  • 当内部类中有和外部类同名的属性或者方法的时候,内部类的实例会选择使用自己的属性和方法,如果非要访问外部的,那么People.this.age获取准确外部类的数据
  • 如果外部类想获取内部类的数据,那么只能通过内部类的实例获取,无法直接获取.

静态内部类

成员内部类使用public修饰的,现在换为static修饰,那么这个内部类就是静态内部类

静态内部类可以直接使用外部类的静态数据,注意要使用静态的方式去调用,如果需要调用外部类的其他属性或者方法,需要通过对象实例去调用

public class Out{
    public String name = "张三";
    
    public static String SN = "SN0001";
    
    public static void stsay(){
        System.out.print("static hello");
    }
    
    public void say(){
        System.out.print("hello");
    }
    
    
    static class inner{
        public static int age = 10;
        public void doit () {
            Out.stsay();
            System.out.println(Out.SN); //静态调用
            new Out().say();
            System.out.println(new Out().name); //其他调用
        }
    
    }

}

获取静态内部类的实例的方法是:

Out.Inner myinner = new Out.Inner();

获取静态内部类的静态成员

Out.Inner.age

方法内部类

定义在方法里的内部类(局部内部类),按照方法中成员变量的方式来使用

  • 局部变量的使用范围仅仅在块内
  • 方法内不能定义静态成员
  • class 关键字前面 不能使用 public private protected static
  • 内部类的成员或者方法均不能使用static但是可以使用final,也可以使用抽象方法抽象类,一般没人去抽象

方法内部类往往会直接调用内部类实例化后的某个方法,而不是直接返回内部类的实例,也就是说,方法内部类会实例化内部类后调用内部类的方法

public class Outer{
    public String getInner(){
        class Inner{
            public int age = 10;
            private String message = "hello Inner";
            public String innerSay(){
                return "Innersay:"+this.message;
            }
        }
        return new Inner().innerSay();
    }
}

匿名内部类

没有名字的内部类,其他的内部类都是通过class声明类new ClassName();获取实例,当对某些类使用仅仅是去实例化一次,而且类名有没有
都毫无意义的时候,就可使用匿名内部类.

将定义类和实例类放在一起,简化代码和操作

案例:
    abstract Person 具有 say() 抽象方法
    Man extends Person say(){...}
    Woman extends Person say(){...}
    
    一个方法实现传入不同对象调用各自的say()
    1. 重载<略>
    2. 多态
    3. 匿名内部类

2.多态

abstract Person

package com.internalClass;

public abstract class Person {
    private String name = "张三";

    public void setName(String Name) {
        this.name = Name;
    }

    public String getName() {
        return this.name;
    }

    public Person() {
    }

    public abstract void say();
}

Man

package com.internalClass;

public class Man extends Person {
    private String gender = "男";

    public Man() {
    }

    @Override
    public void say() {
        System.out.println("(" + this.gender + ")My name is " + this.getName());
    }
}

Woman

package com.internalClass;

public class Woman extends Person {
    private String gender = "女";

    public Woman() {
    }

    @Override
    public void say() {
        System.out.println("(" + this.gender + ")My name is " + this.getName());
    }
}

test

package com.test;

import com.internalClass.Man;
import com.internalClass.Person;
import com.internalClass.Woman;

public class PersonTest {
    // 通过传入男人或者女人调取相应的say方法
    // 1. 重载方案
    // 2. 多态方案
    public void getSay(Person obj) {
        obj.say();
    }

    public static void main(String[] args) {
        PersonTest pt = new PersonTest();
        Man m1 = new Man();
        Woman w1 = new Woman();
        m1.setName("猪坚强");
        w1.setName("王漂亮");
        pt.getSay(m1);
        pt.getSay(w1);

    }
}

3.匿名内部类

abstract Person

public abstract class Person {
    private String name = "张三";

    public void setName(String Name) {
        this.name = Name;
    }

    public String getName() {
        return this.name;
    }

    public Person() {
    }

    public abstract void say();
}

不需要Man 或者 Woman类

直接操作PersonTest

public class PersonTest {
    // 通过传入男人或者女人调取相应的say方法
    public void getSay(Person obj) {
        obj.say();
    }

    public static void main(String[] args) {
        PersonTest pt = new PersonTest();
        pt.getSay(
                    new Person() {
                        @Override
                        public void say() {
                            System.out.println("男人的say");
                        }
                    }
                );
        pt.getSay(
                    new Person() {
                        @Override
                        public void say() {
                            System.out.println("女人的say");
                        }
                    }
        );
    }
}

如果上述匿名使用的方法只是使用一次,就不必要去创建子类,省内存,省资源,但是缺点就是,如果使用多次的话,无法提高代码复用性

匿名内部类一般是用来直接实现某个抽象方法的,所以无法使用public private protected static abstract 修饰符去定义类,
也无法在匿名内部类中编写构造方法(没类名怎么写?)但是可以使用构造代码块,无法在匿名内部类中定义静态属性或方法.

抽象内部类可以:

1.Person抽象类say抽象方法
2.IMessage接口中的say方法
  • 可以通过抽象内部类直接实现Person抽象类中的say方法的重写调用
  • 可以通过抽象内部类直接实现IMessage接口中say方法的重写和调用

但是同一个函数中,二者只能选其一,分别定义在两个函数中互不影响

public void getSay(Person obj) {
        obj.say();
    }
public void getISay(IMessage api) {
    api.say();
    }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!