本篇会加入个人的所谓‘鱼式疯言’
❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言
而是理解过并总结出来通俗易懂的大白话,
小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的.
🤭🤭🤭可能说的不是那么严谨.但小编初心是能让更多人能接受我们这个概念 !!!
前言
上一篇文章中我们主要讲解了抽象类和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 方法.
鱼式疯言
接口间的继承相当于把多个接口合并在一起.
三. 接口的实际运用
如果我们需要对一个对象进行排序,是不是可以通过接口来实现呢,答案的毋庸置疑的 ❣️ ❣️ ❣️
按照常规的操作小伙伴们肯定会这样做
<1>. 常见做法
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<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("========排序前========");
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 方法并抛出异常
-
main 方法中也需要抛出异常
-
接收拷贝数据需要强转类型 (父类转化为子类需要向下转型)
以上四种实现细节 缺一不可
如果还对 向下转型 概念还是很模糊的小伙们可以参考下面文章哦
向下转型详解链接
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 的实例, 编译器会及时提醒我们.
鱼式疯言
概念太抽象了,我们有图有真相 💥 💥 💥
总结
- 接口:我们熟悉了接口的语法,和如何继承和实现这些接口的方法
- 多接口:接口的特性就在多接口的独特
- 接口的实际运用:排序是实际运用让我们更深入理解接口的益处
- 深拷贝与浅拷贝:深浅不同的拷贝让我们更理解拷贝的深浅不同,层次也有可能造成差别
- 抽象类与接口:我们主要讲解了抽象类和接口的差异之处在哪
如果觉得小编写的还不错的咱可支持 三连 下 (定有回访哦) , 不妥当的咱请评论区 指正
希望我的文章能给各位宝子们带来哪怕一点点的收获就是 小编创作 的最大 动力 💖 💖 💖