羡青山有思,Java有接口
本篇会加入个人的所谓‘鱼式疯言’
❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言
而是理解过并总结出来通俗易懂的大白话,
小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的.
🤭🤭🤭可能说的不是那么严谨.但小编初心是能让更多人能接受我们这个概念 !!!
前言
上一篇文章中我们主要讲解了抽象类和Object 类的重点内容
而在本篇文章中我们又引申出 接口 这个概念
那么 接口 是什么呢?
接口 有什么用呢?
和我们的 抽象类又有什么关系和不同呢?
小伙们可以带着这些问题来学习我们今天的 接口 内容哦 💥 💥 💥
目录
- 接口
- 多接口
- 接口的实际运用
- 深拷贝与浅拷贝
- 抽象类与接口
一. 接口
1. 接口的简介
在现实生活中,接口的例子比比皆是,比如:笔记本上的USB口,电源插座等。
电脑的USB口上,可以插:U盘、鼠标、键盘…所有符合USB协议的设备
电源插座插孔上,可以插:电脑、电视机、电饭煲…所有符合规范的设备
通过上述例子可以看出:接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。
在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
2. 接口的语法
接口的定义格式与定义类的格式基本相同,将class 关键字换成 interface 关键字,就定义了一个接口。
interface USB { public static final double x=10.4; int i=10; void openDevice(); void closeDevice(); static void put() { } default void get() { } private void set() { } }
小伙伴们是不是有点看不懂
提示一下
- 创建接口时,接口的命名一般以 大写字母 I 开头
- 接口的命名一般使用 “形容词” 的单词开头
- 阿里编码规范中约定,接口中的方法和属性不要加任何修饰符号,保持代码的简洁性
鱼式疯言
- 我们接口中成员方法是 抽象方法
- 接口中的成员变量默认是 public static final 限定的 (当我们不写限定符时),这时我们就需要赋值的
- 以上前四种的定义的成员方法都是 public abstract 限定的抽象方法,需要子类实现其方法
- 后三种由 static,default,private 限定的成员方法可以在其接口中定义
3.举个栗子
实际操作时我们需要用一个类 implement 来实现接口
public class 类名称 implements 接口名称{ // ... }
只有 继承后重写才能实现我们接口的每个功能
// USB接口 interface USB { void openDevice(); void closeDevice(); } // 鼠标类,实现USB接口 class Mouse implements USB { @Override public void openDevice() { System.out.println("打开鼠标"); } @Override public void closeDevice() { System.out.println("关闭鼠标"); } public void click(){ System.out.println("鼠标点击"); } } // 键盘类,实现USB接口 class KeyBoard implements USB { @Override public void openDevice() { System.out.println("打开键盘"); } @Override public void closeDevice() { System.out.println("关闭键盘"); } public void inPut(){ System.out.println("键盘输入"); } } // 笔记本类:使用USB设备 class Computer { public void powerOn(){ System.out.println("打开笔记本电脑"); } public void powerOff(){ System.out.println("关闭笔记本电脑"); } public void useDevice(USB usb){ usb.openDevice(); if(usb instanceof Mouse){ Mouse mouse = (Mouse)usb; mouse.click(); }else if(usb instanceof KeyBoard){ KeyBoard keyBoard = (KeyBoard)usb; keyBoard.inPut(); } usb.closeDevice(); } } // 测试类: class TestUSB { public static void main(String[] args) { Computer computer = new Computer(); computer.powerOn(); // 使用鼠标设备 computer.useDevice(new Mouse()); // 使用键盘设备 computer.useDevice(new KeyBoard()); computer.powerOff(); } }
上面的代码主要实现了笔记本电脑使用USB鼠标,USB键盘的栗子
-
USB接口:包含打开设备、关闭设备功能
-
笔记本类:包含开机功能、关机功能、使用USB设备功能
-
鼠标类:实现USB接口,并具备点击功能
-
键盘类:实现USB接口,并具备输入功能
从上面的栗子表明
接口不能直接使用, 必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法。
鱼式疯言
子类和父类之间是 extends 继承关系
类和接口之间是 implements 实现关系
通过这个栗子我们认识了接口的实现,那么接口有什么特性呢 💥 💥 💥
4. 接口的特性
- 接口类型是一种引用类型,但是不能直接new接口的对象
- 接口中每一个方法都是 public的抽象方法 ,
即接口中的方法会被隐式的指定为 public abstract(只能是public abstract,其他修饰符都会报错)
- 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现
- 重写接口中方法时,不能使用默认的访问权限
- 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量
- 接口中不能有 静态代码块 和 构造方法
- 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是 .class
- 如果类没有实现接口中的所有的抽象方法,则类必须设置为 抽象类
- jdk17中:接口中还可以包含default,方法 ,static 方法,private 方法。
鱼式疯言
最后小编用张图总体整体下我们接口的特性吧
小爱同学就有疑惑了,我们的类是不能继承多个抽象类的
那么我们的类是否可以实现多个接口呢
答案是肯定的 💫 💫 💫
二. 多接口
1. 接口的简介
在Java中,类和类之间是单继承的,
一个类只能有一个父类,即Java中不支持多继承,
但是一个类可以实现多个接口。
下面通过类来表示一组动物.
2. 举个栗子
interface IFlying { void fly(); } interface IRunning { void run(); } interface ISwimming { void swim(); } class Animal { protected String name; public Animal(String name) { this.name = name; } } class Cat extends Animal implements IRunning { public Cat(String name) { super(name); } @Override public void run() { System.out.println(this.name + "正在用四条腿跑"); } } class Fish extends Animal implements ISwimming { public Fish(String name) { super(name); } @Override public void swim() { System.out.println(this.name + "正在用尾巴游泳"); } } 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 + "正在蹬腿游泳"); } } 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 + "正在漂在水上"); } } class Test { public static void walk(IRunning running) { System.out.println("我带着伙伴去散步"); running.run(); } public static void main(String[] args) { Cat cat = new Cat("小猫"); walk(cat); Frog frog = new Frog("小青蛙"); walk(frog); } }
猫是一种动物, 具有会跑的特性.
青蛙也是一种动物, 既能跑, 也能游泳
鸭子也是一种动物, 既能跑, 也能游, 还能飞
上面的代码展示了 Java 面向对象编程中最常见的用法:一个类继承一个父类,同时实现多种接口
继承的表示含义是 is-a
这样设计有什么好处呢? 时刻牢记多态的好处, 让程序猿忘记类型.
有了接口之后, 类的使用者就不必关注 具体类型,
而只关注某个类是否 具备某种能力 .
甚至参数可以不是 “动物”,只要会跑就行
interface IFlying { void fly(); } interface IRunning { void run(); } interface ISwimming { void swim(); } class Robot implements IRunning { private String name; public Robot(String name) { this.name = name; } @Override public void run() { System.out.println(this.name + "正在用轮子跑"); } } class Test { public static void walk(IRunning running) { System.out.println("我带着伙伴去散步"); running.run(); } public static void main(String[] args) { Robot robot=new Robot("小飞机"); walk(robot); } }
这就是我们 不需要关注类型的好处
3. 接口的继承
如果接口和接口之间该怎么处理呢? 下面咱们瞧瞧吧
这里小伙伴们只需要简单了解下即可
在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到
多继承的目的。
接口可以继承一个接口, 达到复用的效果. 使用 extends 关键字.
interface IRunning { void run(); } interface ISwimming { void swim(); } // 两栖的动物, 既能跑, 也能游 interface IAmphibious extends IRunning, ISwimming { } class Frog implements IAmphibious { ... }
通过接口继承创建一个新的接口 IAmphibious 表示 “两栖的”.
此时实现接口创建的 Frog 类,
就继续要实现 run 方法,
也需要实现 swim 方法.
鱼式疯言
接口间的继承相当于把多个接口合并在一起.
三. 接口的实际运用
如果我们需要对一个对象进行排序,是不是可以通过接口来实现呢,答案的毋庸置疑的 ❣️ ❣️ ❣️
按照常规的操作小伙伴们肯定会这样做
. 常见做法
class Student { public String name; public int age; public Student(String name, int age) { this.name = name; this.age = age; } // @Override // public String toString() { // return "Student{" + // "name='" + name + '\'' + // ", age=" + age + // '}'; // } public static void main(String[] args) { Student[] stus=new Student[]{ new Student("张三", 95), new Student("李四", 96), new Student("王五", 97), new Student("赵六", 92), }; System.out.println(Arrays.toString(stus)); Arrays.sort(stus); System.out.println(Arrays.toString(stus)); } }
按照我们之前的理解,我们是否能直接使用这个 sort 方法,能否
仔细思考不难发现我们可以给一个整数进行比较排序,
但当我们进行给一个学生对象排序时,是无法确定对象比较的类型
那么两个学生对象的大小关系该怎么确定? 这时就需要 额外指定成员变量
3. 优化实栗
class Student implements Comparable{ public String name; public int age; public Student(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } public static void main(String[] args) { Student[] stus=new Student[]{ new Student("张三", 95), new Student("李四", 96), new Student("王五", 97), new Student("赵六", 92), }; System.out.println("========排序前========"); System.out.println(Arrays.toString(stus)); System.out.println("========排序后========"); Arrays.sort(stus); System.out.println(Arrays.toString(stus)); } @Override public int compareTo(Student o) { return this.age-o.age; } }
是的,当我们需要排序一个对象时,就需要重写一个对象的排序
实现步骤
先继承 Comparable 接口,并实现其中的 compare TO 方法
鱼式疯言
原理说明
- 调用细节
在 sort 方法中会自动调用 compareTo 方法. compareTo 的参数是 Object , 其实传入的就是 Student 类型的对象.
- 比较细节
如果当前对象应排在参数对象之前, 返回 小于 0 的数字;
如果当前对象应排在参数对象之后, 返回 大于 0 的数字;
如果当前对象和参数对象不分先后, 返回 0 ;
- 注意继承
implements 继承 接口 compareable 接口
有以上两个细节当我们再次执行程序, 结果就 符合预期 了.
四. 深拷贝与浅拷贝
1. 浅拷贝
当我们需要把一个对象的数据复制拷贝到另外一个对象时,我们称为浅拷贝,那么浅拷贝小伙伴们会怎么做呢 🤔 🤔 🤔
我猜下你们可能会这样做
2. 常规栗子
class Animal implements Cloneable{ public String name; public Animal(String name) { this.name = name; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } public static void main(String[] args) { Animal a1=new Animal("小黄"); Animal a2=(Animal) a1.clone(); } }
当小伙伴们继承 Cloneable 这个接口时,我们就会出现并且实现这个方法 Clone 方法时,我们可能就会出现这样的 异常
那该怎么解决呢
答案很简单
3. 优化栗子
class Animal implements Cloneable{ public String name; public Animal(String name) { this.name = name; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public String toString() { return "Animal{" + "name='" + name + '\'' + '}'; } public static void main(String[] args) throws CloneNotSupportedException{ Animal a1=new Animal("小黄"); Animal a2=(Animal) a1.clone(); System.out.println(a2); } }
是的,这个就是和我们的 异常 有关系了,
当我们的调用的成员方法含有抛出异常时,调用方法也需要同时抛出该异常 💫 💫 💫
如果想深入了解的友友们可以参考小编写的异常文章哦,点击下面链接即可
异常详解链接
鱼式疯言
实现浅拷贝的细节说明
- 需要继承 Cloneable 接口
- 需要实现 Clone 方法并抛出异常
以上四种实现细节 缺一不可
如果还对 向下转型 概念还是很模糊的小伙们可以参考下面文章哦
向下转型详解链接
2. 深拷贝
浅拷贝 VS 深拷贝
Cloneable 拷贝出的对象是一份 “浅拷贝”
浅拷贝一个Money
栗子one
class Money { public int money=100; } class Animal implements Cloneable{ public String name; public Money m=new Money(); public Animal(String name) { this.name = name; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public String toString() { return "Animal{" + "name='" + name + '\'' +"money="+this.m.money+ '}'; } public static void main(String[] args) throws CloneNotSupportedException{ Animal a1=new Animal("小黄"); Animal a2=(Animal) a1.clone(); System.out.println("a2="+a2); a1.m.money=23; System.out.println("=======修改a1的值后========"); System.out.println("a1="+a1); System.out.println("a2="+a2); } }
这里我们居然能通过 a1 的值来修改 a2 的值,这说明我们未达到 拷贝的效果
如上代码,我们可以看到,通过 clone,我们只是拷贝了 Person 对象。但是 Person 对象中的 Money 对象,并
没有拷贝。通过 person2 这个引用修改了m的值后,person1 这个引用访问m的时候,值也发生了改变。这里就是发生了 浅拷贝。那么小伙伴们想一下如何实现深拷贝呢?
那我们该怎么办呢,不妨看看下面这个栗子吧
栗子two
class Money implements Cloneable{ public int money=100; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } class Animal implements Cloneable{ public String name; public Money m=new Money(); public Animal(String name) { this.name = name; } @Override protected Object clone() throws CloneNotSupportedException { Animal animal=(Animal) super.clone(); animal.m=(Money)this.m.clone(); return animal; } @Override public String toString() { return "Animal{" + "name='" + name + '\'' +"money="+this.m.money+ '}'; } public static void main(String[] args) throws CloneNotSupportedException{ Animal a1=new Animal("小黄"); Animal a2=(Animal) a1.clone(); System.out.println("a1="+a1); System.out.println("a2="+a2); a1.m.money=23; System.out.println("=======修改a1的值后========"); System.out.println("a1="+a1); System.out.println("a2="+a2); } }
通过这样 一层嵌套一层并把我们内部的对象也拷贝出来的过程我们称之为 : 深拷贝
深拷贝 具体怎么实现细节有哪些呢 💥 💥 💥
鱼式疯言
实现细节1:
内部对象成员的类中也要继承 Cloneable 接口实现 Clone 方法
实现细节2:
外部类中的实现的 Clone方法中需要添加内部的拷贝
底层逻辑图:
五. 抽象类与接口
我们学完了抽象类和接口
抽象类详解链接
小爱同学就发现了抽象类和接口之间的异同
抽象类和接口都是 Java 中多态的 常见使用方式 . 都需要重点掌握.
同时又要认清两者的区别(重要 ! ! ! 常见面试题).
核心区别:
抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写),
而接口中 不能包含普通方法, 子类必须重写所有的抽象方法.
如之前写的 Animal 例子. 此处的 Animal 中包含一个 name 这样的属性, 这个属性在任何子类中都是存在的.
class Animal { protected String name; public Animal(String name) { this.name = name; } }
因此此处的 Animal 只能作为一个 抽象类, 而不应该成为一个接口.
抽象类存在的意义是为了让编译器更好的校验,
像 Animal 这样的类我们 并不会直接使用 , 而是使用 它的子类.
万一不小心创建了 Animal 的实例, 编译器会及时提醒我们.
鱼式疯言
概念太抽象了,我们有图有真相 💥 💥 💥