【Java】生动例子详解接口

一、接口是什么

我的电脑上有 USB 接口。当我想要使用无线鼠标的时候,只需要将无线鼠标的 USB 连接头插入其中,无线鼠标就能使用了;当我想要给我的手机充电时,只需要将充电线的 USB 头插入其中,手机就开始充电了;当我想要获取 U 盘中的资料信息时,将其插入 USB 接口中,U 盘就连接上我的电脑了。

在这里插入图片描述

这就是我们生活中的接口,试想如果电脑上没有这样的一个 USB 接口,是不是就意味着,每当想要实现以上的某种功能时,就需要大费周章的在电脑上造一个新的口来实现这一项功能,想要实现的功能太多太多,电脑迟早变成蜂窝,上面全是为了实现某一项功能而特地创造的口。

如果是在代码中举例,就应该是这样的。。。

某机器人公司发明了两种机器人,机器人A和机器人B,这俩机器人都有共同点,那就是都会说话,都拥有自己的名字,不同功能是A会走路,B会唱歌,如果用代码就应该是这样的。。。

📑代码示例:

class Robot {
    public String name;
    public Robot(String name) {
        this.name = name;
    }
    public void talking() {
        System.out.println(this.name + "会说话!");
    }
}
class RobotA extends Robot {
    public RobotA(String name) {
        super(name);
    }
    public void walking() {
        System.out.println(this.name + "会走路!");
    }
}
class RobotB extends Robot {
    public RobotB(String name) {
        super(name);
    }
    public void singing() {
        System.out.println(this.name + "会唱歌!");
    }
}
public class TestDemo {
    public static void main(String[] args) {
        RobotA robotA = new RobotA("机器人A");
        robotA.talking();
        robotA.walking();
        RobotB robotB = new RobotB("机器人B");
        robotB.talking();
        robotB.singing();
    }
}

🏸 代码结果:

在这里插入图片描述

问题:

  • 如果有一天,机器人A也会唱歌了,那么 RobotA 这个类是不是要进行修改,加个唱歌功能?

  • 如果有一天,不想要机器人B有说话这一功能了,那么 Robot 这个类是不是要进行修改,删了说话功能,可是机器人A是会说话的,那么连同 RobotA 这个类岂不是也要进行修改?

在这里插入图片描述

  • 既然功能这么多,干脆将这些功能都写成类吧,统统继承!统统继承!!!问题是一个机器人有好几项功能,但是 Java 并不支持多继承啊。。。

在这里插入图片描述

这种关键时候,就将隆重介绍出本节的主题对象:接口

接口就是一种解决 Java 不支持多继承这一问题的技术,它是用来描述一个类应该应该会做些什么,实现什么功能,具体这个功能是怎么实现的是不指定的,而是由实现接口的类来实现接口中的方法

一个类是可以实现一个或多个接口的,有些情况可能要求符合这些接口,只要有这种要求,就可以使用实现了这个接口的类的对象。

二、初识接口

接口的声明:

[可见度] interface 接口名称 [extends 其他的接口名] {
        // 声明变量
        // 抽象方法
}

💬代码解释:

  • 接口是由 interface关键字来修饰的
  • 接口中的方法一般来说不能有普通方法,方法只能是抽象方法,且接口中的方法默认是 public abstract 修饰的
  • 接口中的成员变量,只能是静态常量,默认是 public static final 修饰的
  • 在 JDK 1.8 开始,接口中的方法也可以是普通的方法,且这些方法被 default 关键字修饰,但是这种用法可以存在,但很少用
  • 创建接口的时,接口名称一般以大写字母I开头,一般用形容词词性进行命名
  • 为了保持代码的简洁明了,默认修饰的符号就不要添加在代码当中

机器人代码的改进:

class Robot {
    public String name;
    public Robot(String name) {
        this.name = name;
    }
}
interface IWalking {
    void walking();
}
interface ISinging {
    void singing();
}
interface ITalking {
    void talking();
}
class RobotA extends Robot implements IWalking,ITalking {
    public RobotA(String name) {
        super(name);
    }
    @Override
    public void walking() {
        System.out.println(this.name + "有走路功能");
    }
    @Override
    public void talking() {
        System.out.println(this.name + "有说话功能");
    }
}
class RobotB extends Robot implements ISinging {
    public RobotB(String name) {
        super(name);
    }
    @Override
    public void singing() {
        System.out.println(this.name + "有唱歌功能");
    }
}
public class TestDemo {
    public static void main(String[] args) {
        IWalking iWalking = new RobotA("机器人A");
        iWalking.walking();
        ISinging iSinging = new RobotB("机器人B");
        iSinging.singing();
    }
}

🏸 代码结果:

在这里插入图片描述

💬代码解释:

  • 接口是不能进行实例化的,且它们不是被类继承了,而是要被类实现

  • 让类实现一个接口通常有两个步骤:

  1. 将类声明为实现给定的接口

  2. 对接口中的所有抽象方法提供定义(提供具体实现)

要将类声明为实现某个接口,需要用到 implements 关键字

  • 一个类可以使用 implements 关键字实现多个接口,接口之间用逗号进行隔开,由此来实现类似多继承的效果
  • 接口和接口之间,使用 extends 关键字来维护,说明该接口扩展了其他接口的功能,在类中都需要将这些接口进行具体实现
  • 接口也可以发生向上转型多态

总结:

  • 接口表达的含义就是 XXX 具有 XXX特性(功能)
  • 接口带来的好处是,让程序员不需要知道什么具体类型,只需要知道这个类是否具有某种特性(功能)。比如,不止机器人,人也有说话,走路,唱歌这样的特性,因此拿人这个类实现这些接口就行

三、接口使用实例

3.1 Comparable 和 Comparator 接口

我们知道想要将一个基本类型的数组进行排序可以使用 Arrays 这个类底下的 sort() 方法,使用该方法进行排序后数组里的数据将会从小到大进行排序。

📑代码示例:

public class TestDemo {
    public static void main(String[] args) {
        int[] array = {3,20,15,4,1};
        System.out.println("排序前:" + Arrays.toString(array));
        Arrays.sort(array);
        System.out.println("排序后:" + Arrays.toString(array));
    }
}

🏸 代码结果:

在这里插入图片描述

那么如果想要用该方法将一个自定义类型进行排序,结果又当如何,实际上就算没有进行排序我们也应该可以预料的到,结果是不可行的,因为自定义的类型中会有很多可以排序的依据。。。

📑代码示例:

class Book {
    public double price;
    public String name;
    public Book(double price, String name) {
        this.price = price;
        this.name = name;
    }
    @Override
    public String toString() {
        return "Book{" +
                "price=" + price +
                ", name='" + name + '\'' +
                '}';
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Book[] books = new Book[3];
        books[0] = new Book(32.3,"C语言");
        books[1] = new Book(53.4,"Java详解");
        books[2] = new Book(23.6,"神奇的C++");
        System.out.println("排序前:" + Arrays.toString(books));
        Arrays.sort(books);
        System.out.println("排序后:" + Arrays.toString(books));
    }
}

🏸 代码结果:

在这里插入图片描述

结果显示在类型转换上出现了异常,那么为了正确的排序,应该怎么做呢?

这里就涉及到 ComparableComparator 接口,这两种接口分别对应着两种排序的方法。

首先,介绍 Comparable 接口

📑代码示例:

class Book implements Comparable<Book>{
    private double price;
    private String name;
    public Book(double price, String name) {
        this.price = price;
        this.name = name;
    }
    @Override
    public String toString() {
        return "Book{" +
                "price=" + price +
                ", name='" + name + '\'' +
                '}';
    }
    @Override
    /*public int compareTo(Book o) {
        //从小到大排序
        return (int) (this.price - o.price);
        //从大到小排序
        //return (int) (o.price - this.price);
    }*/
    public int compareTo(Book o) {
        return this.name.compareTo(o.name);
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Book[] books = new Book[3];
        books[0] = new Book(32.3,"C语言");
        books[1] = new Book(53.4,"Java详解");
        books[2] = new Book(23.6,"神奇的C++");
        System.out.println("排序前:" + Arrays.toString(books));
        Arrays.sort(books);
        System.out.println("排序后:" + Arrays.toString(books));
    }
}

🏸 代码结果:

在这里插入图片描述

💬代码解释:

  • Book 为自定义类型,books 为 Book 类型的数组
  • Comparable 是一个接口,拥有排序的功能,使 Book 类型实现该接口,该类型就拥有了排序的功能
  • <> 中放的是需要拥有排序功能的类型名称,此处是 Book 类型
  • 实现了接口之后需要在 Book 类中对该接口中的 compareTo 方法进行具体的实现。用当前对象与 o 进行比较,如果这个对象小于 o 则返回一个负整数;如果相等则返回0;否则的话返回一个正整数。
  • 如果以 price 进行排序,需要将比较的结果强制类型转换成 int 类型,如果以 name 进行排序,则需要用到 compareTo 方法

显而易见,通过Comparable 接口,我们成功地将自定义类型的数组按照我们想要的结果进行排序,并且和基本类型的数组排序相比较,它可以更加自由的决定从小到大排序还是从大到小排序;但是缺点也非常的明显,该接口对类的侵入性比较强,每次想要换一种比较的方式,都需要对类内部的 compareTo 方法进行修改,制定新的比较规则,局限性很大,因此该接口相当于“内部比较器”

为了解决这一问题,现在隆重向大家推出另一种使类具有排序功能的接口——Comparator 接口。

在这里插入图片描述

📑代码示例:

class Book {
    public double price;
    public String name;
    public Book(double price, String name) {
        this.price = price;
        this.name = name;
    }
    @Override
    public String toString() {
        return "Book{" +
                "price=" + price +
                ", name='" + name + '\'' +
                '}';
    }
}
//按照价格进行排序的比较器
class PriceComparator implements Comparator<Book> {
    @Override
    public int compare(Book o1, Book o2) {
        return (int)(o1.price - o2.price);
    }
}
//按照书名进行排序的比较器
class NameComparator implements Comparator<Book> {
    @Override
    public int compare(Book o1, Book o2) {
        return o1.name.compareTo(o2.name);
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Book[] books = new Book[3];
        books[0] = new Book(32.3,"C语言");
        books[1] = new Book(53.4,"Java详解");
        books[2] = new Book(23.6,"神奇的C++");
        System.out.println("排序前:" + Arrays.toString(books));
        PriceComparator priceComparator = new PriceComparator();
        //NameComparator nameComparator = new NameComparator();
        Arrays.sort(books,priceComparator);
        System.out.println("排序后:" + Arrays.toString(books));
    }
}

🏸 代码结果:

在这里插入图片描述

💬代码解释:

  • 为了避免制造出太多代码量(提供 Getter 和 Setter方法),这里将两个成员变量用 Public 来修饰,一般来说为了加强封装,应该用 Private 来修饰
  • 当某个类并未实现 Comparable 接口时,我们建立比较器来进行排序,该比较器只需要实现 Comparator 接口即可
  • 实现该接口需要重写其中的 compare 方法,来比较其中的两个参数 o1 和 o2 的大小, o1 比 o2 小,返回负数;o1 和 o2 相等,返回0;否则的话返回一个正整数。
  • 在调用 Arrays.sort 时,传参不仅要传自定义类型数组,也要传比较器

可以发现使用 Comparator 接口不仅拥有 Comparable 接口的优点,也避免了其缺点即对类的侵入较为严重,想要哪种比较方法,直接可以在类的外面写一个新的比较器,因此该接口相当于“外部比较器”

3.2 Cloneable 接口

Cloneable 接口是 Java 中用于拷贝对象的标记接口,该接口是一个空接口,某个类实现了该接口说明该类是可以被拷贝的 。

📑代码示例:

class Book implements Cloneable{
    public double price = 9.9;
    public String name = "神奇的JAVA";
   /* @Override
    public String toString() {
        return "Book{" +
                "price=" + price +
                ", name='" + name + '\'' +
                '}';
    }*/
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class TestDemo {
    public static void main(String[] args) throws CloneNotSupportedException {
        Book book = new Book();
        System.out.println(book);
        Book book1 = (Book)book.clone();
        System.out.println(book1);
    }
}

🏸 代码结果:
在这里插入图片描述

重写 toString() 方法后
在这里插入图片描述

💬代码解释:

  • Book 类实现了 Cloneable 接口,拥有了被拷贝的能力,然后需要在类中重写 Object 类中的 clone 方法,然后调用父类(Object类)的 clone 方法,对象才会克隆成功
  • 如果没有实现接口就会抛出异常:CloneNotSupportedException
  • 通过结果可以看出 book1 的地址和 book 的地址是不一样的,但是内容是一样的,说明克隆成功
  • 此时的拷贝是完全拷贝的,通过 book1 修改其中的值是不会影响 book 中的值

在这里插入图片描述

关于拷贝,就不得不提到深拷贝和浅拷贝了,在 Java 详解数组这一节中有讲到数组的拷贝,其中就涉及这个。
【Java】剖析数组的使用
那么对于对象拷贝也会有这样的问题,如果拷贝的对象中还存在着另一个对象,那么用之前的 clone 方法是没有办法将对象中的对象的内容进行拷贝的,此时的拷贝只是浅拷贝。

📑代码示例:

class Number{
    public int number = 23;
}
class Book implements Cloneable{
    public double price = 9.9;
    public String name = "神奇的JAVA";
    public Number num = new Number();
    @Override
    public String toString() {
        return "Book{" +
                "price=" + price +
                ", name='" + name + '\'' +
                ", num.number=" + num.number +
                '}';
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class TestDemo {
    public static void main(String[] args) throws CloneNotSupportedException {
        Book book = new Book();
        Book book1 = (Book) book.clone();
        System.out.println("修改前");
        System.out.println(book);
        System.out.println(book1);
        book1.num.number = 44;
        System.out.println("修改后");
        System.out.println(book);
        System.out.println(book1);
    }
}

🏸 代码结果:

在这里插入图片描述

💬代码解释:

从结果中可以看出,由于未进行完全的拷贝,导致可以通过新拷贝的 book1 将 book 中的 num 指向的对象的内容改变

在这里插入图片描述

想要进行深拷贝,将对象进行完全的拷贝,就需要将 clone 方法再次进行重写,这回要将对象中的对象也拷贝一份

📑代码示例:

class Number implements Cloneable{
    public int number = 23;
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
class Book implements Cloneable{
    public double price = 9.9;
    public String name = "神奇的JAVA";
    public Number num = new Number();
    @Override
    public String toString() {
        return "Book{" +
                "price=" + price +
                ", name='" + name + '\'' +
                ", num.number=" + num.number +
                '}';
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Book bookClone = (Book)super.clone();
        bookClone.num = (Number)this.num.clone();
        return bookClone;
    }
}
public class TestDemo {
    public static void main(String[] args) throws CloneNotSupportedException {
        Book book = new Book();
        Book book1 = (Book) book.clone();
        System.out.println("修改前");
        System.out.println(book);
        System.out.println(book1);
        book1.num.number = 44;
        System.out.println("修改后");
        System.out.println(book);
        System.out.println(book1);
    }
}

🏸 代码结果:

在这里插入图片描述

💬代码解释:

  • 从结果中可以看出,由于进行了完全的拷贝,通过新拷贝的 book1 是没有办法将 book 中的 num 指向的对象的内容改变
  • 所做的修改:
  1. 使 Number 类型也具有被拷贝的能力,实现 Cloneable 接口,然后需要在类中重写 Object 类中的 clone 方法,然后调用父类(Object类)的 clone 方法
  2. 重写 Book 类型中 clone 方法,使对象中成员指向的对象也被拷贝

在这里插入图片描述
完!

  • 44
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 20
    评论
Java泛型接口是指在接口中定义泛型类型的接口,可以在接口中使用泛型类型来定义方法参数类型、返回值类型或者接口自身的类型。下面是一个简单的泛型接口示例: ```java public interface MyList<T> { void add(T element); T get(int index); int size(); } ``` 上面的示例中,定义了一个 MyList 接口,使用泛型类型 T 来表示列表中元素的类型,包含了三个方法:add、get 和 size,分别用于向列表中添加元素、获取列表中指定位置的元素和获取列表的大小。 下面是一个实现 MyList 接口的示例代码: ```java public class ArrayList<T> implements MyList<T> { private T[] array; private int size; public ArrayList() { array = (T[]) new Object[10]; size = 0; } @Override public void add(T element) { if (size == array.length) { T[] newArray = (T[]) new Object[array.length * 2]; System.arraycopy(array, 0, newArray, 0, array.length); array = newArray; } array[size++] = element; } @Override public T get(int index) { if (index < 0 || index >= size) { throw new IndexOutOfBoundsException(); } return array[index]; } @Override public int size() { return size; } } ``` 上面的示例中,定义了一个 ArrayList 类,实现了 MyList 接口,使用泛型类型 T 来表示列表中元素的类型。在 add 方法中,如果列表已满,则扩容数组;在 get 方法中,如果索引超出列表范围,则抛出 IndexOutOfBoundsException 异常。 使用泛型接口可以使代码更加通用和灵活,可以适用于各种不同类型的数据结构和算法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

富春山居_ZYY(已黑化)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值