【Java学习—(13)看完这篇文章,这些继承、多态、抽象、接口就是小儿科】

面向对象编程

前言

关于访问修饰符

从小到大依次为:

private(私有,当前类的内部可见) < default(啥也不写就是包权限,当前包的内部可见,不包含子包,同级目录可见) < protected(继承,不同包的有继承关系的类之间可见) < public(公有,当前项目可见)

Java中的“包” 就是操作系统的文件夹。

声明一个包使用 package关键字,在实际中,创建package 包,在包的命名时,若存在多个文件夹的嵌套,使用“.”分隔,就会产生嵌套的“包”。(需要在旁边的设置的符号中,取消 Compact Middle Packages 这个选项)

包的存在就是为了解决类同名的问题

如何导入某个包中的某个类

使用import 导入类,import语句只能导入相关包中的某个具体的类


此时若还要用到这个包中的类,不需要一行行写,

import java.util.*;

此时将整个util包下的所有类按需加载,此时导入的还是类,而不是把util文件夹导入!!!

一般不推荐 import .*;,会有歧义,若程序中用到了两个相同名称的类,并且两个类的包都使用 import .*; 直接导入类的包。此时会报错。


在程序中用到了两个相同名称的类,有两种方法:

  1. 使用类的全名称, 包名.类名
  2. import 明确指定导入的是哪个包下的哪个类,例如:import java.util.Date;

静态导入:

import static 可以导入包中的静态方法和静态属性


常见的系统包:

java.lang :JDK的基础类,System,String,Object都在这个包下,JDK1.1之后,这个包下的所有类自动导入

java.lang.reflect: 反射开发包

java.util: 工具包(集合类都在这个包下,Arrays,Linked List,HashMap)

java.io: I/O开发包,文件读取和写入

java.net: 网络编程开发包,Socket

java.sql: 数据库开发需要的包


继承

面向对象一共有三大特性:封装,继承和多态。

封装:具有保护性和易用性(封装就有很多种表达形式)。private实现属性或方法的封装只是封装的其中一种。

那么什么是方法重载呢

方法重载就是在同一个类中定义了多个方法名称相同,参数列表不同,与返回值无关的一组方法,这样的一组方法就被称之前方法重载。


来看这几个代码:

public class Vehicle {
    public String name;
    public void speed(int num){
        System.out.println(name + "的速度是" + num + "Km/h");
    }
}
public class Car {
    public String name;
    public void speed(int num){
        System.out.println(name + "的速度是" + num + "Km/h");
    }
}
public class Airplane {
    public String name;
    public void speed(int num){
        System.out.println(name + "的速度是" + num + "Km/h");
    }
}

这三个类的代码完全相同,正常来说,所有的交通工具都具有name属性,以及他的speed(速度)属性。

在这三类中,汽车是交通工具,飞机也是交通工具。所以,car 和 airplane 都应该是Vehicle 的子类

当类和类之间满足一个类(a) is a 另一个类(b),一定存在继承关系。a 继承了b

当一个类继承了另一个类,另一个类中的所有属性和方法(包括静态的属性和方法) 子类就天然具备了。


在java中使用extends表示类的继承

Car extends Vehicle

Car 就是子类/派生类, Vehicle 就是父类/基类。

那么上面的代码就可以变为

public class Vehicle {
    public String name;
    public void speed(int num){
        System.out.println(name + "的速度是" + num + "Km/h");
    }
}
public class Car extends Vehicle {

}
public class Airplane extends Vehicle{
    
}

注意:不能为了省略代码就简单的使用继承,要想使用继承必须要满足继承的关系,不满足继承关系的类之间千万不能使用继承。


继承的规则

  1. 要能使用继承,必须满足类之间的 is a 关系
  2. 一个子类只能使用extends 继承一个父类。(单继承)

Java中不允许多重继承,extends 后面只能跟父类,但是允许多层继承,没办法当儿子,可以当孙子。

满足继承关系的类之间一定是逻辑上垂直的关系

  1. 子类会继承父类的所有属性和方法,显示继承 :public属性 和方法可以直接使用。 隐式继承: private 属性和方法,子类其实继承了这个属性和方法,但是无法直接使用。要看父类是否会提供set get 权限给子类。

    子类继承父类的所有属性和方法,包括静态的属性和方法,区别就在于是通过子类对象访问,还是直接通过类名称进行访问。

    就好比你现在要让你爸告诉你 他银行卡的密码是什么,你作为儿子,你可以知道,但是需要你爸自己愿意告诉你,你才能知道。否则你肯定不会知道。


关于protected(继承权限)访问权限

protected 访问权限

  1. protected 作用域 :当前类的内部可见,不同包中的有继承关系的类之间可见。

    在不同包的子类中访问protected 权限修饰的方法和属性时,只能通过该子类对象进行访问,其他子类对象和父类对象均无法访问

  2. 同包下的没有关系的类可见

    那为什么protected 继承权限在同包下的没有关系的类中可见呢?

    因为

    ​ private < default(包权限) < protected < public , 既然 protected权限大于包权限,那么包权限可见的范围,protected继承权限一样可见。

注意protected 权限的属性和方法 在不同包中 没有继承关系的类中 是不可见的


super 关键字

复习一下 this关键字

  1. 修饰属性,表示直接从当前类中调用同名属性 this.name

  2. 修饰方法,表示直接调用当前类的方法

    调用普通成员方法——this.方法()写不写this都一样,因为不写的话,编译器编译之后会默认加this

    构造方法的互相调用——不同参数的构造方法之间出现了重复的调用,那么这个时候就可以使用this(参数) 调用其他的构造方法

    • this 调用其他的构造方法必须放在当前构造方法的首行

    • this 调用构造方法不能成”环“。(就是这样规定的)

  3. 表示当前对象的引用

super关键字:

  1. 修饰属性,表示直接从父类中去寻找同名属性
  2. 修饰方法,表示直接从父类中去寻找方法

在学习super关键字之前,我们要了解:

当我们要产生一个子类对象,默认首先产生父类对象

父类

package InheritTest;
//父类
public class Vehicle {
    public  Vehicle(){
        System.out.println("1 Vehicle 的无参构造");
    }
}

子类

package InheritTest;

public class Car extends Vehicle {
    public Car() {
        System.out.println("2 Car的无参构造");
    }
}

测试类

package InheritTest;

public class Test {
    public static void main(String[] args) {
        Car car = new Car(); // 产生一个子类对象
    }
}

此时的输出结构是:

1 Vehicle 的无参构造
2 Car的无参构造

当调用new Car 无参构造一个子类对象之前,默认先调用父类的构造方法产生父类对象,才会执行子类的构造方法


来看这个代码应该输出多少?

父类:

package Question;

public class B {
    public B(){
        System.out.println("1 B 的构造方法");
    }
    {
        System.out.println("2 B 的构造块");
    }
    static {
        System.out.println("3 B 的静态块");
    }
}

子类:

package Question;

public class D extends B{
    public D(){
        System.out.println("4 D 的构造方法");
    }
    {
        System.out.println("5 D 的构造块");
    }
    static {
        System.out.println("6 D 的静态块");
    }

    public static void main(String[] args) {
        System.out.println("7 main开始执行");
        new D();
        new D();
        System.out.println("8 main 结束执行");
    }
}

输出结果是:

3 B 的静态块   
6 D 的静态块
7 main开始执行
2 B 的构造块
1 B 的构造方法
5 D 的构造块
4 D 的构造方法
2 B 的构造块
1 B 的构造方法
5 D 的构造块
4 D 的构造方法
8 main 结束执行

解答:

由于main方法在子类中,要执行main方法就要加载子类,

加载子类时 要首先加载父类,此时执行父类的静态块(3),

然后加载子类,执行子类的静态块(6),

main方法开始执行,打印7

new D(); 产生一个子类,产生子类对象先要产生一个父类对象,产生父类对象时,先调用构造块(2

因为静态块只在类加载的时候执行一次,后面不在执行

构造块每次在产生一个对象是都要执行

然后再调用构造方法(1),父类对象产生

产生子类对象时,也是先调用构造块(5),再调用构造方法(4),子类对象产生。

又new D(); 重复上面的步骤,2 1 5 4

最后打印8

所以结果就是 3 6 7 2 1 5 4 2 1 5 4 8


super 修饰属性

super修饰属性:表示在父类中寻找同名属性

当在有继承关系的类中,this关键字默认先在当前类中寻找同名属性,若没有找到,继续向上寻找父类中是否有同名属性。


若此时有继承关系的子类中有一个name属性为 儿子,父类中也有一个name属性为父亲,那么在子类中直接打印name, 打印的是子类中的儿子,那我们怎么能够打印父类中的父亲呢,这时就可以使用 super 关键字,直接在父类中寻找name属性。

看看代码会更好理解:

父类

package SuperTest;

public class Father {
    protected String name = "父亲";
}

子类

package SuperTest;

public class Son extends Father{
    public void fun(){
        System.out.println(this.name);
    }
    public static void main(String[] args) {
        Son son = new Son();
        son.fun();
    }
}

此时的子类使用this关键字来打印子类中的name属性,但是子类中没有name属性,就从父类中去寻找name属性,输出结果就是

父亲

再看这个代码:

父类

package SuperTest;

public class Father {
    protected String name = "父亲";
}

子类

package SuperTest;

public class Son extends Father{
    public String name = "儿子";
    public void fun(){
        System.out.println(this.name);
    }
    public static void main(String[] args) {
        Son son = new Son();
        son.fun();
    }
}

此时的子类代码中已经有了name属性,就直接从子类中找到了name属性,输出结果就是

儿子

那此时我们想在这个子类在访问到父类的name属性,我们就是使用super关键字,把this.name 改为 super.name,此时 子类就访问到了 父类的name属性。


注意

super 先从直接父类中寻找同名属性,若不存在再向上寻找

this 直接从当前类中直接寻找,若不存在再向上寻找


super 修饰构造方法

用法

  1. super(父类构造方法的参数);
  2. super();
    super 的位置和this 一样,也要处于构造方法的首行

若父类中不存在无参构造,则子类构造方法的首行必须使用 super(有参构造)

但是若父类存在无参构造,则子类构造方法不需要再写 super(); 调用无参构造,JVM会默认调用(隐式调用)

还要注意,在一个构造方法中,无法显式的使用 this()和super() 同时出现。两个都要在构造方法的首行,会产生矛盾。

当产生子类对象时,默认先产生父类对象,若父类对象还有父类,继续向上先产生祖类对象。


当我们在父类中写一个有参的构造方法,此时父类的无参构造就不在产生。那我们在在new 一个只有无参构造的子类对象时,先要产生父类对象,但是找不到父类的无参构造,此时就会出现编译报错

那我们此时就要在子类无参构造的首行写入 super(参数); ,让子类能够找到父类的构造方法,产生父类对象


看正确的代码

父类

package SuperTest;

public class Father {
    protected String name = "父亲";
    public Father(String name){
        this.name = name;
        System.out.println("Father 的有参构造");
    }
}

子类

package SuperTest;

public class Son extends Father{
    public String name = "儿子";
    public Son(){
        super("爸爸");
        System.out.println("Son 的无参构造");
    }
    public static void main(String[] args) {
        Son son = new Son();
    }
}

如果不加 super(“爸爸”); 就会编译报错,


super 修饰普通方法

super 修饰普通方法和修饰属性一样,直接从父类中寻找同名方法。

当在有继承关系的类中,this关键字默认先在当前类中寻找同名方法,若没有找到,继续向上寻找父类中是否有同名方法。

父类

package SuperTest;

public class Father {
    protected String name = "父亲";
    public Father(String name){
        this.name = name;
        System.out.println("Father 的有参构造");
    }
    public void fun(){
        System.out.println("Father 的fun 方法");
    }
}

子类

package SuperTest;

public class Son extends Father{
    public String name = "儿子";
    public void fun(){
        System.out.println("Son 的fun方法");
    }
    public void test(){
        super.fun(); //调用父类的 fun方法
        this.fun();  //调用子类的 fun方法,若子类没有
        System.out.println(this);  //打印当前对象的引用
        System.out.println(super); //会报错,必须是 super.name  直接使用super 是错误的
    }
    public Son(){
        super("爸爸");
        System.out.println("Son 的无参构造");
    }
    public static void main(String[] args) {
        Son son = new Son();
        son.test();
    }
}

super 可以访问父类的属性和方法,必须是super.name, 不能直接使用super


final 关键字和继承的联系

final 关键字:

  1. 修饰属性,表示属性值不能变,常量,值定义之后无法被修改
  2. 修饰类,表示这个类无法被继承。

类和类的关系

类和类直接除了继承关系(狗 is a 动物),还有组合关系(has a),以及没有任何关系。



多态

多态:一个引用可以表现出多种行为或者特性就是多态性

最主要的多态:继承 + 方法重写

向上转型

正常我们new一个对象时,都是 类名称 类引用 = new 该类对象();

但是当我们在有继承关系的类之间时,我们可以 这样 父类名称 父类引用 = new 子类对象();,因为子类继承了父类,那么父类就可以指代所有的子类。就是向上转型。

向上转型发生在有继承关系的类之间,父类名称 父类引用 = new 子类实例();,不一定是直接子类,也可以是孙子类,子子孙孙无穷尽。

向上转型最大的意义在于参数统一化,降低使用者的使用难度


父类

package InheritTest;

public class Vehicle {
    protected String name = "123";
    public void fun(){
        System.out.println("Vehicle的fun方法");
    }
}

子类

package InheritTest;

public class Car extends Vehicle {
    public void fun(){
        System.out.println("Car的fun方法");
    }
}

子类

package InheritTest;

public class Airplane extends Vehicle {
    public void fun(){
        System.out.println("Airplane的fun方法");
    }
}

测试类:

package InheritTest;

public class Test {
    public static void main(String[] args) {
        funs(new Vehicle());
        funs(new Car());
        funs(new Airplane()); //扩展的,添加子类后,直接在使用时 用就即可,fun方法不需要做任何改变,因为最顶层的父类引用可以指代所有的子类对象
    }
    //只要是Vehicle 及其子类,都是天然的Vehicle对象,都满足 is a 关系
    //通过Vehicle最顶层的父类引用,可以指代所有的子类对象
    public static void funs(Vehicle vehicle){
        vehicle.fun();
    }
}

有向上转型后,最顶端的父类引用可以指代所有的子类对象。当父类有新的子类时,是非常容易扩展的!!!

那这个代码的输出是:

Vehicle的fun方法
Car的fun方法
Airplane的fun方法

funs中vehicle局部变量的引用调用fun方法时,当传入不同的对象时,表现出来了不同的fun方法行为,就是多态性。


同一个引用(变量名称),同一个方法名称,根据对象的不同表现出来了不同的行为,就是多态


那为何会出现多态这种情况呢?

就是方法覆写的问题


方法重写

方法重载(overload):在一个类的内部,有多个方法名称相同的,参数列表不同,与返回值无关的一组方法。

方法重写(覆写)(override):发生在有继承关系的类之间,子类定义了和父类除了权限不同,其他全部相同的方法,这样的一组方法就是方法重写。

	Vehicle ve1 = new Car();
	Vehicle ve2 = new Airplane();

	fun(ve1);
	fun(ve2);

    public static void fun(Vehicle vehicle){
        vehicle.fun();
    }

在调用时 调用的是谁的方法呢?

不需要看前半部分,只需要看当前new 的是哪个类的对象,new Car()就是new 了一个Car类的对象,若Car类重写了相关的方法,则调用的是Car类中重写后的方法,若没有重写,则调用的是直接父类的方法。就近匹配原则。


当发生重写时, 子类权限 >= 父类权限才可以重写。 小于的话编译时就会报错

private < default < protected < public

那么父类方法中使用了private 权限,子类方法在使用public 权限,public > private,是否可以重写呢

不能重写。private权限不包含在内。出了这个类,子类根本不知道有这个方法。


Java中有一个注解 @Override ,使用这个注解写在重写方法之前,帮你校验你的方法是否符合重写规则。


能否重写static 方法呢?

多态的本质就是因为调用了不同子类对象,这些子类对象所属的类 覆写相应的方法,才能表现出不同的行为。 而static 方法和对象无关,不能重写

方法重写只发生在普通方法中


重写的方法返回值类型完全相同或者至少是向上转型类的返回值。


向上转型

向上转型发生的时机:

  1. 方法传参
  2. 直接赋值
Vehicle ve = new Car();
  1. 方法返回值
public static Vehicle test(){  //返回值为父类
	Car car = new Car();
	return car;  //实际返回的类型是子类
}

当此时子类对象时,要先产生父类对象,若产生父类对象时,调用父类的构造方法内存在 子类覆写的方法,此时调用的是子类覆写后的方法,而不是子类覆写前的方法。


总结:

向上转型

父类名称 父类引用 = new 子类对象();

这是天然发生的向上转型,使用父类引用调用普通方法时,若子类重写了该方法,则调用该对象所在子类覆写后的方法。

注意类名称 引用名称 = new 类实例();

引用名称.方法名称();

能通过"."访问的方法是由 类名称说了算,能访问的这些方法必须在类中定义过,编译器会先在类中查找是否包含指定方法,若没有,会报错。

这个通过"." 访问的方法到底表现出哪个类的样子,是由new 的类实例所在的类说了算。

简单来说就是,能 . 哪些方法 前面说了算,至于 . 之后这方法有什么行为,后面new的对象所在的类说了算。


向下转型

来看这个例子:

Animal 是父类, Dog是子类

Animal animal = new Dog();

animal.play();

此时父类中的没有play()方法,在子类中拓展一个play()方法,直接使用父类引用是无法调用子类的拓展方法。

我们要使用子类的拓展方法就要把他还原为子类引用,通过子类引用来访问子类的拓展方法,就是向下转型。

//子类名称 子类引用 = (子类名称)父类引用
Dog dog = (Dog)animal; 

将父类引用强制类型转化为子类引用,就是向下转型。

要发生向下转型,先要发生向上转型。

Animal animal = new Animal(); 
//animal 就是一个 Animal类产生的对象的引用,和其他的类毫无关系
Dog dog = (Dog)animal;

这个是会报错的,毫无关系的两个类直接没办法强制类型转换。

ClassCastException 就是类型转换异常。


        Vehicle vehicle1 = new Car();
        System.out.println(vehicle1);
        Car car = (Car)vehicle1;
        System.out.println(car);

打印父类引用 和 向下转型后的子类引用,会发现他们的输出地址都一样,说明只有一个对象,发生改变的只是引用的名字。


instanceof 关键字

当发生向下转型时会有风险,出现类型转换异常,那我们就可以使用 instanceof关键字。

引用名称 instanceof 类 -> 就会返回一个布尔值,表示该引用名称指向的本质是不是该类的对象。

例子:

使用instanceof关键字的返回值 搭配的分支语句进行类型转换。

package InheritTest;

public class Test {
    public static void main(String[] args) {
        Vehicle vehicle1 = new Car();//Car 继承了 Vehicle
        Vehicle vehicle2 = new Vehicle();
        if(vehicle1 instanceof Car){
            Car car = (Car) vehicle1;
            System.out.println("转换成功");
        }else {
            System.out.println("vehicle1 本质指向的不是Car这个类的引用");
        }
        if(vehicle2 instanceof Car){
            Car car = (Car) vehicle1;
            System.out.println("转换成功");
        }else {
            System.out.println("vehicle2 本质指向的不是Car这个类的引用");
        }
    }
}

输出结果:

转换成功
vehicle2 本质指向的不是Car这个类

总结:

只有在某个特殊的情况下,需要使用子类拓展的方法,才需要将原本向上转型的引用 进行向下转型 还原为子类引用。


抽象类

若需要强制要求子类覆写方法,就要用到抽象类。

其实现实生活中有很多的 抽象类,这些类都是概念化的,没法具体到某个实例,描述这一类对象共同的属性和行为。

交通工具 —> 抽象,没法对应具体的某个或者某一类的工具,汽车、自行车、飞机、火车等等

抽象类是普通类的"超集",只是比普通类多了一些抽象方法而已。普通类有的抽象类全都有!

抽象方法所在的类必须是抽象类,子类若继承了抽象类,必须覆写所有的抽象方法。(子类是普通类,子类若是抽象类,就不需要进行覆写)

Java中定义抽象类或者抽象方法使用 abstract关键字。


抽象的使用 1

抽象方法所在的类必须使用abstract声明为抽象类.

抽象方法指的是使用abstract关键字声明,只有函数声明,没有函数实现的方法,称之为抽象方法。

package Abstract;

public abstract class Vehicle {  //声明为抽象类
    public abstract String name(); //抽象方法
}

抽象方法:使用关键字abstract声明,只有方法声明,没有方法体( “{}”)的方法。

抽象方法在抽象类中没有具体实现,延迟到子类实现。


来做一个判断题

java中,没有方法体的方法就是抽象方法。 error

因为本地方法也没有方法体,但他不是抽象方法。


抽象类的使用 2

若一个类使用abstract 关键字声明为抽象类,无法直接通过该类 实例化对象,哪怕该类中一个抽象方法也没有。

当一个类是抽象类,不管他有没有抽象方法,这个类本身就是一个抽象的概念,就没有办法具体到某个特定的实例,只能通过 子类 向上转型变为抽象父类的引用

抽象类虽然没有办法直接实例化对象,子类仍然满足 is a 原则, 子类和抽象父类直接仍然是满足 继承树的关系

//此时Vehicle 是抽象类
Vehicle vehicle = new Car(); //这是正确的
Vehicle vehicle = new Vehicle(); // 这是错误的

抽象类的使用 3

子类继承了抽象类,就必须强制子类(子类是普通类)覆写 抽象类中的所有抽象方法(包括抽象类继承另一个抽象类中的方法),也满足单继承局限,一个子类只能继承一个抽象类。

package Abstract;
abstract class A{
    public abstract void add();
}
//抽象类 可以不覆写或者选择性的覆写父类中的抽象方法
abstract  class B extends A{
    public abstract void del();
}
//普通类,必须覆写B中的所有抽象方法(包括继承来的抽象方法)
//若B中选择性覆写了他继承的抽象类的抽象方法,则B 覆写了的抽象方法,B的子类可以不进行覆写。!!!!!!!
public class Vehicle extends B{
    @Override
    public void del() {
        
    }
    @Override
    public void add() {
        
    }
}

若B中选择性覆写了他继承的抽象类的抽象方法,则B 覆写了的抽象方法,B的子类可以不进行覆写!!!!!!!

抽象类的使用 4

抽象类是普通方法的超集(普通类有的内容,抽象类都有),只是比普通类多了一些抽象方法而已,抽象类虽然没有办法直接实例化对象,但是也可以存在构造方法,子类在实例化对象时,仍然遵从继承的规则,先调用父类(抽象类)的构造方法,而后调用子类的构造方法!!

package Abstract;

abstract class A{
    public abstract void add();
    public A(){
        System.out.println("A 的构造方法");
        this.add();

    }
}
public class Vehicle extends A{
    @Override
    public void add() {
        System.out.println("num = " + num);
    }
    private int num = 10;

    public static void main(String[] args) {
        new Vehicle();
    }
}

输出是:

A 的构造方法
num = 0

在主方法中new Vehicle(); 时,先调用父类(抽象类)的构造方法,构造方法中又调用add()方法,子类中覆写了add方法,所以这个add 方法是子类的,由于还没有执行子类的构造方法,所以num 还没有初始化 为 10。


若一个需求既可以使用抽象类的,也可以使用接口,那我们优先使用接口。抽象类仍然是单继承局限。


接口

一般来说,接口的使用表示两种场景

1 接口表示具备某种能力或行为,子类实现接口时 不是 is a, 而是具备这种行为或者能力。

”跑“ —> 是能力或者行为,狗满足跑接口,猫也满足跑接口。

2 接口表示一种规范或标准。USB接口、5G标准


接口中只有全局常量和抽象方法 —> 更加纯粹的抽象概念。其他东西统统没有

接口使用关键字 interface声明接口(创建新的 java Class 文件时,选择interface ),子类(普通类)使用implements实现接口。


接口和类的关系:
1 接口和接口之间也存在继承关系,接口坚决不能继承一个类。

若一个接口同时继承 了多个父接口,则这个接口也继承了所有的抽象方法,子类在实现这个接口时,必须覆写所有的抽象方法。

2 如果一个类既需要继承一个类,同时实现多个接口时, 先使用 extends 继承一个类,而后使用 implements 实现多个父接口。

class China extend 类名称 implements 接口名称{}

USB接口

package Interface_test;
//接口使用interface 关键字定义,只有全局常量(1%接口才有)和抽象方法(99%)
public interface USB {
    //插入
    public abstract void plugIn();
    //工作
    public abstract void work();
}

子类使用 implements实现接口, 必须覆写所有的抽象方法

鼠标、键盘这些都属于USB接口的子类。

package Interface_test;

public class Mouse implements USB{
    @Override
    public void plugIn() {
        System.out.println("鼠标驱动正在安装...");
    }

    @Override
    public void work() {
        System.out.println("鼠标驱动安装成功!!!");
    }
}

package Interface_test;

public class KeyBoard implements USB{
    @Override
    public void plugIn() {
        System.out.println("键盘驱动正在安装...");
    }

    @Override
    public void work() {
        System.out.println("键盘驱动安装成功!!!");
    }
}

那么电脑这个类算不算USB接口的子类呢?

所有带USB线插入到电脑的设备都应该满足USB规范。 电脑应该叫做USB规范的使用者。

package Interface_test;

public class Computer {
    public static void main(String[] args) {
        Computer computer = new Computer();
        Mouse mouse = new Mouse();
        computer.fun(mouse);
        KeyBoard keyBoard = new KeyBoard();
        computer.fun(keyBoard);
    }
    
    public void fun(USB usb){
        usb.plugIn();
        usb.work();
    }
}

此时为何方法的参数用的是USB接口引用

fun方法就模拟电脑的接口,对于电脑的使用者和生产者来说,根本不关心到底哪个具体设备插到电脑上,只要满足了USB接口,就能够被电脑识别。就可以实现,一个接口可以接收无数种设备。兼容所有的USB对象。

假如fun方法的参数是(Mouse mouse),那么电脑的这个接口就只能插鼠标,电脑总不能设置很多个接口吧。


若此时电脑多了一个新的USB的子类,对于电脑这个类来说,一点都不影响,一行代码都不会改变。

这就是开闭原则,所有的设计模式的核心思想:程序应该对扩展开放,对修改关闭。 方便扩展,不能影响已经写好的程序。


接口允许多实现

接口表示能力,允许多实现,一个类可能具备多个能力,同时实现多个父接口

若实现多个父接口,子类(普通类)需要覆写所有的抽象方法。

package Interface_test.RunJump;
//跑的接口
public interface Run {
    public abstract void run();
}
package Interface_test.RunJump;
//跳的接口
public interface Jump {
    public abstract void jump();
}

子类实现多个父接口,多个父接口直接使用","分开

package Interface_test.RunJump;
//子类 猫 具有 跑和跳的能力,实现多个父接口,多个父接口直接使用","分开
public class Cat implements Run,Jump{
    @Override
    public void jump() {
        System.out.println("猫会跳");
    }

    @Override
    public void run() {
        System.out.println("猫会跑");
    }
}
package Interface_test.RunJump;
//测试
public class Test {
    public static void main(String[] args) {
        Run catRun = new Cat();  //向上转型
        catRun.run(); //通过父类接口引用访问子类对象方法
        Jump catJump = new Cat();
        catJump.jump();
    }
}

接口也不能直接实例化对象,需要向上转型


由于接口中只有全局常量和抽象方法,因此在接口中 public abstract 抽象方法

static final 全局常量,这些关键字都可以省略。

以后在接口声明中,这些关键字都不需要写,只保留最核心的方法返回值,方法参数列表,名称即可。


要是对大家有所帮助的话,请帮我点个赞吧。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值