Java接口详解

接口

接口的概念

在现实生活中,接口的例子比比皆是,比如:笔记本上的USB口,电源插座等。

电脑的USB口上,可以插:U盘,鼠标,键盘等所有符合USB协议的设备

电源插座插孔上,可以插:电脑,电视机,电饭煲等所有符合规范的设备

通过上述例子可以看出:接口就是公共行为的规范标准 ,大家在实现时,只要符合规范标准 ,就可以通用。在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型

语法规则

接口的定义格式与类的定义格式基本相同,将class关键字换成interface关键字,就定义了一个接口。

java 复制代码
public interface 接口名称 {
    //抽象方法
    public abstract void method1();//public abstract是固定搭配,可以不写
    public void method2();
    abstract void method3();
    void method4();
    
    //注意:在接口中上述的写法虽然都是抽象方法,但是更推荐method4,更加简洁
}

提示:

1.创建接口时,接口的命名一般是以大写字母I开头

2.接口的命名一般使用"形容词"词性的单词

3.阿里编码规范中约定,接口中的方法和属性不要加任何修饰符号,保持代码的简洁性

接口使用

接口不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法

java 复制代码
//类和接口的关系:类 implements 接口
public class 类名称 implements 接口名称 {
    //...
}

注意:子类和父类之间是extends继承关系,类和接口之间是implements实现关系。

举个接口使用的小栗子:

请实现笔记本电脑使用USB鼠标,USB键盘的例子

1.USB接口:包含打开设备,关闭设备的功能

2.笔记本类:包含开机功能,关机功能,使用USB设备的功能

3.鼠标类:实现USB接口,并具备点击功能

4.键盘类:实现USB接口,并具备输入功能

java 复制代码
//定义USB接口
interface USB {
    void openDevice();
    void closeDevice();
}

class Mouse implements USB {
    @Override
    public void openDevice() {
        System.out.println("打开鼠标");
    }

    @Override
    public void closeDevice() {
        System.out.println("关闭鼠标");
    }

    public void click() {
        System.out.println("鼠标点击");
    }
}

class KeyBoard implements USB {
    @Override
    public void openDevice() {
        System.out.println("打开键盘");
    }

    @Override
    public void closeDevice() {
        System.out.println("关闭键盘");
    }

    public void input() {
        System.out.println("键盘输入");
    }
}

class Computer {
    public void powerOn() {
        System.out.println("打开电脑");
    }

    public void powerOff() {
        System.out.println("关闭电脑");
    }

    //定义电脑使用USB设备的方法,通过多态性,根据传入的USB设备的不同完成不同操作
    public void useDevice(USB usb) {
        usb.openDevice();
        //instanceof:比较左右实例的等操作符
        if(usb instanceof Mouse) {
            //向下转型:将 USB 类型的参数 usb 转换为 Mouse 类型的引用 mouse。这样就可以在这个方法中使用 mouse 对象的特有方法
            Mouse mouse = (Mouse)usb;
            mouse.click();
        } else if (usb instanceof KeyBoard) {
            KeyBoard keyboard = (KeyBoard)usb;
            keyboard.input();
        }

        usb.closeDevice();
    }
}

public class TestUSB {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.powerOn();

        Mouse mouse = new Mouse();
        KeyBoard keyboard = new KeyBoard();
        computer.useDevice(mouse);
        System.out.println("===============");
        computer.useDevice(keyboard);

        computer.powerOff();
    }
}

注:instanceof 是 Java 中的一个关键字,用于检查一个对象是否是指定类或其子类的实例。它的语法是:

object instanceof Class

其中,object 是要检查的对象,Class 是指定的类名。

instanceof 返回一个布尔值,如果对象是指定类的实例或其子类的实例,则返回 true,否则返回 false

接口特性

1.接口类型是一种引用类型,但是不能直接new接口的对象

java 复制代码
public class TestUSB {
    public static void main(String[] args) {
        USB usb = new USB();
    }
}

//Error:USB是抽象的,无法实例化

2.接口中的每一个方法的类型都是public的抽象方法,即接口中方法会被隐式的指定为public abstract(只能是public abstract,其他修饰符都会报错)

java 复制代码
public interface USB {
    private void closeDevice();
}

//Error:此处不允许使用修饰符private

3.接口中的方法是不能在接口中实现的(两个方法除外:static 和 abstract修饰的方法),只能由实现接口中的类实现

java 复制代码
public interface USB {
    void openDevice();
    void closeDevice() {
        System.out.println("关闭USB设备");
    }
}

//编译失败:因为接口中的方式默认为抽象方法
//Error:接口中的抽象方法不能带有主体

4.重写接口中的方法时,不能使用默认的访问权限

java 复制代码
public interface USB {
    void openDevice();
    void closeDevice();
//这两个默认都是public修饰的
}

public class Mouse implements USB {
    @Override
    void openDevice() {
        System.out.println("打开鼠标");
    }//这里的方法是默认修饰符
    
    //...
}

//编译报错:Error:重写USB中的方法时,不能使用默认修饰符
//正在尝试分配更低的访问权限:以前为public

5.接口中可以含有变量,但是接口中的变量会被隐式指定为public static final变量

java 复制代码
interface USB {
    double brand = 3.0;//默认被public static final修饰
    void openDevice();
    void closeDevice();
}

public class TestUSB {
    public static void main(String[] args) {
        System.out.println(USB.brand);//可以直接通过接口访问,说明是静态的
        //USB = 2.0;
        //编译报错:无法为最终变量brand分配值
        //说明brand具有final属性
    }
}

6.接口中不能有静态代码块和构造方法

7.接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class

8.如果类中没有实现接口中的所有抽象方法,则必须设置为抽象类

9.jdk8中:接口中还可以包含default方法

实现多个接口

在Java中,类和类之间是可以单继承的。一个类只能有一个父类,即Java中不支持多继承,但是一个类可以实现多个接口。下面通过类来表示一组动物

java 复制代码
class Animal {
    protected String name;
    
    public Animal(String name) {
        this.name = name;
    }
}

另外我们再提供一组接口,分别表示"会飞的","会跑的","会游泳的"。

java 复制代码
interface IFlying {
    void fly();
}

interface IRunning {
    void run();
}

interface ISwimming {
    void swim();
}

接下来创建几个具体的动物

猫,是会跑的。

java 复制代码
public class Cat extends Animal implements IRunning{
    public Cat(String name) {
        super(name);
    }
    
    @Override
    public void run() {
        System.out.println(this.name + "正在用四条腿跑");
    }
}

鱼,是会游的。

java 复制代码
public class Fish extends Animal implements ISwimming{
    public Fish(String name) {
        super(name);
    }
    
    @Override
    public void swim() {
        System.out.println(this.name + "正在用鱼鳍游泳");
    }
}

青蛙,既能跑,又能游(两栖动物)

java 复制代码
​
public class Frog extends Animal implements IRunning, ISwimming{
    public Frog(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(this.name + "正在往前跳");
    }

    @Override
    public void swim() {
        System.out.println(this.name + "正在蹬腿游泳");
    }
}

​

注意,一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类。

还有一种动物,水陆空三栖,叫做"鸭子"。

java 复制代码
public class Duck extends Animal implements IRunning, ISwimming, IFlying {
    public Duck(String name) {
        super(name);
    }
    
    @Override
    public void fly() {
        System.out.println(this.name + "正在用翅膀飞");
    }

    @Override
    public void run() {
        System.out.println(this.name + "正在用两条腿跑");
    }

    @Override
    public void swim() {
        System.out.println(this.name + "正在漂在水上");
    }
}

上面的代码展示了Java中面向对象编程最常见的用法:一个类继承一个父类,同时实现多个接口

我们之前学过:继承具有is-a的语义,而接口表达的含义是:具有xxx特性

猫是一种动物,具有会跑的特性

青蛙也是一种动物,既能跑,也能游泳

鸭子也是一种动物,既能跑,也能游,还能飞

这样设计有什么好处呢?时刻牢记多态的好处,让程序员忘记类型。有了接口之后,类的使用者就不用具体关注具体哪种类型,而只关注某个类是否有某种能力。

例如,现在实现一个方法,叫跑步

java 复制代码
public static void walk(IRunning running) {
    System.out.println("我带着伙伴去跑步");
    running.run();
}

在这个walk方法内部,我们并不关注到底是哪种动物,只要是会跑的就行。

java 复制代码
Cat cat = new Cat("小猫");
//这里只关注猫是否有跑步能力
walk(cat);

Frog frog = new Frog("小青蛙");
walk(frog);

//执行结果
我带着伙伴去散步
小猫正在用四条腿跑
我带着伙伴去散步
小青蛙正在向前跳

​

接口间的继承

在Java中,类和类是单继承的,而一个类可以实现多个接口,接口和接口之间可以多继承。即:用接口可以达到多继承的目的

注:

类和接口之间的关系 -> implements 实现

接口和接口之间的关系 -> extends 拓展

java 复制代码
interface A {
    testA();
}

interface B extends A {
    testB();
}

//B中具有A的功能

接口之间的继承相当于把多个接口合并到一起

接口的使用实例

对对象进行比较

java 复制代码
public class Student {
    String name;
    int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

给定两个学生对象,对它们进行比较(年龄)

java 复制代码
 Student student1 = new Student("zhangsan",12);
       Student student2 = new Student("lisi",18);

仔细思考,不难发现,和普通的整数不一样,两个整数是可以直接比较的,因为其大小关系明确。而两个学生对象的大小关系怎么确定?需要额外指定

让我们的Student类实现Comparable接口,并实现其中的compareTo方法

java 复制代码
//引入Comparable接口表明当前类可以比较,并实现其中的compareTo方法
//尖括号内写的是要比较的类型
public class Student implements Comparable<Student>{
    String name;
    int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(Student o) {
        if(this.age > o.age) {
            return 1;
        } else if(this.age == o.age) {
            return 0;
        } else {
            return -1;
        }
    }
}

但上面的写法具有一定的缺点,比如更改了需求:不是比较年龄而是比较姓名,上面的方法显然是写死的,只能比较年龄,其他需求无法满足。

所以这时,我们可以另辟蹊径,在类外面实现比较器

java 复制代码
public class Student{
    String name;
    int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

  /*  @Override
    public int compareTo(Student o) {
        if(this.age > o.age) {
            return 1;
        } else if(this.age == o.age) {
            return 0;
        } else {
            return -1;
        }
    }*/
}

//比较器(年龄)
class AgeComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.age - o2.age;
    }
}

//比较器(姓名)
class NameComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.name.compareTo(o2.name);
    }
}


======================
public class Test1 {
    public static void main(String[] args) {
       Student student1 = new Student("zhangsan",12);
       Student student2 = new Student("lisi",18);

       AgeComparator agecomparator = new AgeComparator();
       NameComparator namecomparator = new NameComparator();
        System.out.println(agecomparator.compare(student1, student2));
        System.out.println(namecomparator.compare(student1, student2));
    }
}

这样就可以同时比较年龄和姓名了。

那么回顾上述两种比较的方法,可以得出以下结论:

1.两种方法都可以适用于对象的比较

2.方法一侵入性较强:一旦写好了规定的比较方式,那么以后只能以这种方式比较了

3.方法二可以灵活比较,只需要传递需要比较的两个对象就可以了

Cloneable接口和深拷贝

Java中内置了一些很有用的接口,Cloneable就是其中之一。

Object类中存在一个clone方法,调用这个方法可以创建一个对象的"拷贝"。但是要想合法调用clone方法,必须先实现Cloneable接口,否则就会抛出CloneNotSupportedException异常

java 复制代码
public interface Cloneable {

}

//空接口/标记接口->表明当前类是可以被克隆的

如何使用 ?

1.首先,在你想要实现克隆功能的类中,实现 Cloneable 接口。例如:

java 复制代码
public class MyClass implements Cloneable {
    // 类的成员和方法定义
}

2.然后,重写 Object 类中的 clone 方法,该方法在实现克隆时会被调用。在重写的方法中,调用 super.clone() 来创建一个对象的浅拷贝

java 复制代码
public class MyClass implements Cloneable {
    // 类的成员和方法定义
    
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

3.最后,当你需要克隆一个对象时,调用对象的 clone 方法即可。需要注意的是,clone 方法返回的是 Object 类型,所以你可能需要进行强制类型转换。例如:

java 复制代码
MyClass original = new MyClass();
try {
    MyClass cloned = (MyClass) original.clone();
    // 对克隆对象进行操作
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}

需要注意的是,clone 方法默认进行的是浅拷贝,即只复制了对象的引用。如果你需要实现深拷贝(复制对象的内容),你可能需要在 clone 方法中自行实现复制对象内容的逻辑。

那么什么又是浅拷贝和深拷贝呢?

让我们来看以下代码,这是一个浅拷贝的举例:

java 复制代码
class Money {
    public double m = 99.99;
}

class Person implements Cloneable {
    public Money money = new Money();

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class TestDemo3 {
    public static void main(String[] args) throws CloneNotSupportedException{
        Person person1 = new Person();
        Person person2 = (Person)person1.clone();
        System.out.println("通过person2修改前的结果");
        System.out.println(person1.money.m);
        System.out.println(person2.money.m);
        person2.money.m = 13.6;
        System.out.println("通过person2修改后的结果");
        System.out.println(person1.money.m);
        System.out.println(person2.money.m);
    }
}

执行结果如下:

如上代码,我们可以看到,通过clone,我们只是拷贝了Person对象。但是Person对象中的Money对象,并没有拷贝。通过person2这个引用修改了 m这个值后,person1这个引用访问m的值时,值也发生了改变。这里就是发生了浅拷贝。

举个栗子:这就相当于家里两个人看一个电视,一个人换了台,两个人就一起看另一个台

那怎么实现深拷贝呢?

深拷贝是指在复制对象时,不仅复制对象本身,还要递归地复制对象内部的所有引用对象。

来看一下代码:

java 复制代码
class Money implements Cloneable {
    public double m = 99.99;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone(); // 这里进行浅拷贝,因为 'm' 是基本类型
    }
}

class Person implements Cloneable {
    public Money money = new Money();

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person tmp = (Person)super.clone();
        tmp.money = (Money)this.money.clone();
        return tmp;
    }
}

public class TestDemo4 {
    public static void main(String[] args) throws CloneNotSupportedException{
        Person person1 = new Person();
        Person person2 = (Person)person1.clone();
        System.out.println("通过person2修改前的结果");
        System.out.println(person1.money.m);
        System.out.println(person2.money.m);
        person2.money.m = 13.6;
        System.out.println("通过person2修改后的结果");
        System.out.println(person1.money.m);
        System.out.println(person2.money.m);
    }
}

运行结果:

显然深拷贝使得改动过后只有person2中的m发生了变化,这就是深拷贝。

还是举个栗子:这就相当于两个人不同看两个电视,一个人换台,另一个人看的并没有变化

为了更容易理解,让我们看一下深拷贝和浅拷贝的内存情况吧:

浅拷贝:

深拷贝:

抽象类和接口的区别

抽象类和接口都是Java中多态的最常见的使用方式。都需要重点掌握,同时必须认清两者的区别。

核心区别:抽象类中可以包含普通方法和普通字段,这样的普通方法和普通字段可以被子类直接使用(不必重写),而接口中不能包含普通方法,子类必须重写所有的抽象方法。
再次提醒:抽象类存在的意义是为了让编译器更好的校验。

好了这一期就到这,这应该是作者字数最多的文章,敲得累死,下了。

相关推荐
MediaTea6 分钟前
Python:_sentinel 命名约定
开发语言·python·sentinel
茉莉玫瑰花茶8 分钟前
C++17 详细特性解析(中)
开发语言·c++
shehuiyuelaiyuehao16 分钟前
String的杂七杂八方法
java·开发语言
开发者小天21 分钟前
python返回随机数
开发语言·python
木井巳21 分钟前
【递归算法】计算布尔二叉树的值
java·算法·leetcode·深度优先
21 分钟前
java关于时间类
java·开发语言
小新软件园23 分钟前
PrintPage 打印 绿色版发票打印工具 批量打印 多维度设置更实用
windows·电脑·开源软件
老蒋每日coding23 分钟前
FISCO BCOS 部署Solidity投票智能合约并基于Java SDK 调用智能合约详细指南
java·区块链·智能合约
lly20240631 分钟前
C 标准库 - <stdlib.h>
开发语言
少控科技31 分钟前
QT新手日记035
开发语言·qt