目录
语法规则
例子
实现多个接口
接口之间的继承
抽象类和接口的区别
接口使用实例--Comparable接口
Clonable接口
浅拷贝
深拷贝
在现实生活中,接口的例子比比皆是,比如:电源插座,主机上的USB接口等。这些插口中可以插所有符合规范的设备。通过这个例子我们知道,接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。在java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
语法规则
通过关键字interface来定义一个接口。在创建接口时,接口的命名一般以大写字母I开头。

接口中的成员默认都是public static final的,接口中的成员方法默认都是public abstract的,所以一般都不写,保持代码简洁性。
interface IShape {
    //public static final int a = 10;
    int a = 0;
    //public abstract void draw();
    void draw();
} 
在接口中,不可以有普通成员方法

java8开始,允许在接口当中定义一个default方法,这个方法可以有具体的实现
interface IShape {
    int a = 0;
    void draw();
    default public void test() {
        System.out.println("test()");
    }
} 
接口当中的方法,如果是static修饰的方法,那么可以有具体的实现
    public static void test2() {
        System.out.println("static方法");
    } 
接口当中不能有构造方法和代码块


接口不可以通过new关键字进行实例化

类和接口之间可以通过关键字implements来实现接口,且类中要重写接口中的抽象方法
class Rect implements IShape {
    @Override
    public void draw() {
        System.out.println("矩形");
    }
} 
当一个类实现接口当中的方法之后,当前类中的方法不能不加public

接口可以发生向上转型,也可以发生动态绑定
public class Test8_4 {
    public static void drawMap(IShape iShape) {
        iShape.draw();
    }
    public static void main(String[] args) {
        IShape iShape = new Rect();
        drawMap(iShape);
        drawMap(new Flower());
    }
} 
输出结果:
一个接口也会产生独立的字节码文件
![]()
例子
我们来看一个具体的例子:
新建一个名为IUSB的接口
public interface IUSB {
    void openDevice();//打开服务
    void closeDevice();//关闭服务
} 
新建一个类Mouse,通过implements实现IUSB
public class Mouse implements IUSB{
    @Override
    public void openDevice() {
        System.out.println("打开鼠标");
    }
    @Override
    public void closeDevice() {
        System.out.println("关闭鼠标");
    }
    public void click() {
        System.out.println("点击鼠标");
    }
} 
新建一个类keyBoard类,通过implements实现IUSB
public class keyBoard implements IUSB{
    @Override
    public void openDevice() {
        System.out.println("打开键盘");
    }
    @Override
    public void closeDevice() {
        System.out.println("关闭键盘");
    }
    public void inPut() {
        System.out.println("输入");
    }
} 
新建一个Computer类,类中分别有三个方法,分别是打开电脑、关闭电脑、使用服务
public class Computer {
    public void powerOn() {
        System.out.println("打开笔记本电脑");
    }
    public void powerOff() {
        System.out.println("关闭笔记本电脑");
    }
    public void useDevice(IUSB iusb) {
        //启动服务
        iusb.openDevice();
        if (iusb instanceof Mouse) {
            //如果是鼠标调用 那么将iusb强转成Mouse类并调用click方法
            Mouse mouse = (Mouse) iusb;
            mouse.click();
        } else if (iusb instanceof keyBoard) {
            //如果是键盘调用 那么将iusb强转成keyBoard类并调用inPut方法
            keyBoard keyBoard = (demo4.keyBoard) iusb;
            keyBoard.inPut();
        }
        //关闭服务
        iusb.closeDevice();
    }
} 
下面我们来测试一下useDevice方法
public static void main(String[] args) {
        Computer computer = new Computer();
        computer.powerOn();//打开电脑
        
        computer.useDevice(new Mouse());//鼠标
        computer.useDevice(new keyBoard());//键盘
        computer.powerOff();//关闭电脑
    } 
你可以自己分析一下程序的输出结果是什么。
这里我直接给出运行结果:
实现多个接口
在java中,类和类之间是单继承的,一个类只能有一个父类,不支持多继承,但是一个类可以实现多个接口。我们认识了很多动物,有会飞的,会跑的,会游的,现在我们写一个代码来实现它。
新建一个IFlying接口
public interface IFlying {
    void fly();
} 
新建一个ISwimming接口
public interface ISwimming {
    void swim();
} 
新建一个IRunning接口
public interface IRunning {
    void run();
} 
新建一个Animal类,这是一个抽象类
public abstract class Animal {
    public String name;
    public int age;
    public abstract void eat();
    //构造方法
    public Animal(String name,int age) {
        this.name = name;
        this.age = age;
    }
} 
新建一个Dog类,继承Animal,并重写Animal中的抽象方法
public class Dog extends Animal{
    @Override
    public void eat() {
        System.out.println(this.name+"吃狗粮");
    }
    //构造方法
    public Dog(String name,int age) {
        super(name, age);
    }
}
 
对于狗来说,他会狗刨(游泳),会跑,他的功能不止一个,通过implements实现IRunning和ISwimming,并重写接口中的方法
public class Dog extends Animal implements IRunning,ISwimming{
    @Override
    public void eat() {
        System.out.println(this.name+"吃狗粮");
    }
    //构造方法
    public Dog(String name,int age) {
        super(name, age);
    }
    @Override
    public void run() {
        System.out.println(this.name+"用狗腿跑");
    }
    @Override
    public void swim() {
        System.out.println(this.name+"正在狗刨");
    }
} 
【注】一定是先继承,再实现;在java中只能继承一个类,实现多个接口。
新建一个Bird类,继承Animal,实现IRunning,IFlying接口
public class Bird extends Animal implements IFlying,IRunning{
    @Override
    public void eat() {
        System.out.println(this.name+"吃鸟食");
    }
    public Bird(String name,int age) {
        super(name, age);
    }
    @Override
    public void run() {
        System.out.println(this.name+"用鸟腿跑");
    }
    @Override
    public void fly() {
        System.out.println(this.name+"正在飞");
    }
} 
新建一个测试类Test10_1
public class Test10_1 {
    public static void test1(Animal animal) {
        animal.eat();
    }
    public static void test2(IRunning iRunning) {
        iRunning.run();
    }
    public static void test3(ISwimming iSwimming) {
        iSwimming.swim();
    }
    public static void test4(IFlying iFlying) {
        iFlying.fly();
    }
    public static void main(String[] args) {
        Dog dog = new Dog("小狗",2);
        Bird bird = new Bird("小鸟",1);
        test1(bird);
        test1(dog);
        System.out.println("==========================");
        //dog和bird都实现了IRunning
        test2(bird);
        test2(dog);
        System.out.println("==========================");
        //只有dog实现了ISwimming
        test3(dog);
        System.out.println("==========================");
        //只有bird实现了IFlying
        test4(bird);
        System.out.println("==========================");
    }
} 
你可以自己分析一下输出结果是什么。
这里我直接给出结果:
现在我们再新建一个Robot类,它不属于动物,但有跑的功能
public class Robot implements IRunning{
    @Override
    public void run() {
        System.out.println("机器人在跑");
    }
} 
    public static void main(String[] args) {
        test2(new Robot());
    } 
输出结果为:![]()
只有具备功能即可,十分灵活。
通过上面这个例子,我们知道,继承表达的是is-a的语义,而接口表达的是具有xxx特性。
接口之间的继承
现在我们定义三个接口A、B、C
interface A {
    void testA();
}
interface B {
    void testB();
}
interface C {
    void testC();
} 
现有一个接口具备B和C接口的功能,难道要再新建一个接口D,把B、C的功能放进去吗?
interface D {
    void testB();
    void testC();
} 
这样写不太合适,不能解决长久的问题,我们可以用关键字extends,此时extends意为拓展
interface D extends B,C{
   //D这个接口具备了B和C的功能,同时D可以也定义属于自己的功能
    void testD();
} 
那么当一个类实现D接口时,要重写B、C、D的方法
public class Test10_2 implements D{
    @Override
    public void testB() {
        System.out.println("B");
    }
    @Override
    public void testC() {
        System.out.println("C");
    }
    @Override
    public void testD() {
        System.out.println("D");
    }
} 
接口间的继承相当于把多个接口合并在一起。
抽象类和接口的区别
| 区别 | 抽象类(abstract) | 接口(interface) | 
| 结构组成 | 普通类+抽象方法 | 抽象方法+全局常量 | 
| 权限 | 各种权限 | public | 
| 子类使用 | 使用extends关键字继承抽象类 | 使用implements关键字实现接口 | 
| 关系 | 一个抽象类可以实现若干接口 | 接口不能继承抽象类,但是接口可以使用extends关键字继承多个父接口 | 
| 子类权限 | 一个子类只能继承一个抽象类 | 一个子类可以实现多个接口 | 
接口使用实例--Comparable接口
现在我们有一个Student类
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 +
                '}';
    }
} 
思考一下,如果想比较两个学生的年龄大小应该如何比较呢?难道跟以前学的一样直接比较吗?我们来试一下:

我们会发现代码报错了,因为我们的student1和student2不是基本数据类型,那么此时要想比较年龄大小,我们就需要借助接口Comparable了,让Student类实现Comparable接口
![]()
我们发现代码报错了,现在我们看一下Comparable的代码:

Comparable后面的<T>叫做泛型,这里你先记为固定写法,所以我们应该这样写:
class Student implements Comparable<Student>{
…… 
并在Student类中重写compareTo方法:
    @Override
    public int compareTo(Student o) {
        //return this.age - o.age;
        //谁调用compareTo方法 谁就是this
        if (this.age > o.age) {
            return 1;
        }else if (this.age < o.age) {
            return -1;
        }else {
            return 0;
        }
    } 
此时我们就可以在main方法中进行比较了
public static void main(String[] args) {
        Student student1 = new Student("张三",10);
        Student student2 = new Student("李四",15);
        System.out.println(student1.compareTo(student2));//student1这个对象和student2这个对象 
                                                         //比较
    } 
自定义类型要想比较大小,要实现Comparable接口,并重写compareTo方法来实现比较的逻辑。
再举个例子,现在我们有一个Student数组,我们想要数组按照年龄大小排序,排序我们可以调用sort()方法,因为要进行比较,所以必须要实现Comparable接口,代码如下:
    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("张三",10);
        students[1] = new Student("李四",5);
        students[2] = new Student("王五",18);
        Arrays.sort(students);
        System.out.println(Arrays.toString(students));
    } 
输出结果:![]()
没有实现Comparable接口就运行代码的情况在这里就不做展示了,感兴趣的朋友可以自己动手试一下。
实现Comparable接口后,我们也可以重写一个冒泡排序方法:
    public static void bubbleSort(Comparable[] comparable) {
        for (int i = 0; i < comparable.length; i++) {
            for (int j = 0; j < comparable.length-1-i; j++) {
                if (comparable[j].compareTo(comparable[j+i]) > 0) {
                    Comparable temp = comparable[j];
                    comparable[j] = comparable[j+1];
                    comparable[j+1] = temp;
                }
            }
        }
    } 
这个接口对类的侵入性比较强,如果我们现在想根据姓名排序,方法改了之后,前面实现的根据年龄比较的方法可能就实现不了了,因此我们也有另外一种比较方式---比较器。
如果我们先要根据年龄排序,可以定义一个类AgeComparator,这个类实现Comparator接口,Comparator中有一个方法,叫做compara,你只需要在AgeComparator类中重写这个方法即可。
class AgeComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.age - o2.age;
    }
} 
在main方法中我们需要实例化一个AgeComparator的对象,通过这个对象引用compara方法,比较学生年龄
    public static void main(String[] args) {
        Student student1 = new Student("张三",10);
        Student student2 = new Student("李四",15);
        AgeComparator ageComparator = new AgeComparator();
        System.out.println(ageComparator.compare(student1, student2));
    } 
根据姓名比较就定义一个NameComparator类,实现Comparator接口,那么重写方法时,name是一个String类型,应该如何比较呢?我们来看一下String这个类

我们发现在源码中String实现的是Comparable接口,那么它一定实现了comparaTo方法

所以我们就可以通过String变量直接调用这个comparaTo方法
class NameComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.name.compareTo(o2.name);
    }
} 
    public static void main(String[] args) {
        Student student1 = new Student("zhangsan",10);
        Student student2 = new Student("lisi",15);
        NameComparator nameComparator = new NameComparator();
        System.out.println(nameComparator.compare(student1, student2));//14
    } 
通过这个方法实现,两种比较方法互不干扰,非常灵活。
Clonable接口
我们新建一个Person类
class Person {
    public int age;
    public Person(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                '}';
    }
} 
并在main方法中实例化一个对象
    public static void main(String[] args) {
        Person person1 = new Person(10);
    } 

现在我们的要求是,能不能把person1指向的这个对象克隆一份呢?我们需要调用一个方法clone方法。但是如果我们直接在main方法中调是会报错的

因为我们现在没有clone()这个方法。那谁有呢?Object有。(可以看这篇博客简单了解一下http://t.csdnimg.cn/qKuOZ)

我们知道,Object是所有类的父类,那现在为什么不能调用呢?因为访问权限,protected修饰的只能在同一个包同一个类、同一个包不同类以及不同包中的子类中访问。且要使用super来访问父类的属性。那现在怎么办,我们只能重写clone方法:在Person类中右键-->Generate-->Override Methods-->![]()
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    } 
重写了方法之后,你会发现仍然是报错的,![]()
这是什么原因呢,我们看到重写的clone()方法上![]()
它抛出了一个异常叫做编译时异常,解决它很简单,鼠标放在报红的地方,Alt+Enter
点击这个此时代码是这样的

现在你会发现报红变长了,这又是另一个错误了。clone()方法的返回值类型是Object,但是你用Person类接受了,向下转型范围变小了,要进行强转,强转成Person。
现在代码就不报错了。
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person(10);
        Person person2 = (Person) person1.clone();
        System.out.println(person1);
        System.out.println(person2);
    } 
此时当你运行你的代码时,你会发现又报错了。
![]()
他说不支持克隆,如果你自定义的类型想要进行克隆,那么一定要实现Cloneable接口
class Person implements Cloneable{
…… 
我们点开接口的代码会发现
没有任何东西,那这个接口有什么用呢?这个Cloneable接口叫做空接口或者标记接口:证明当前类是可以克隆的。
这个时候我们再运行代码发现可以了。

此时,clone()方法就帮你完成了克隆。当然这里也会存在一个问题,就是深拷贝和浅拷贝。什么是深拷贝,什么是浅拷贝呢?
浅拷贝
现在我们添加一个Money类,将Money类和Person类组合
class Money {
    public double money = 19.9;
}
class Person implements Cloneable{
    public int age;
    public Money m;
    public Person(int age) {
        this.age = age;
        this.m = new Money();
    }
…… 
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person(10);
        Person person2 = (Person) person1.clone();
        System.out.println(person1.m.money);
        System.out.println(person2.m.money);
    } 
此时我们运行代码输出的是两个19.9。
        person2.m.money = 99.99;
        System.out.println(person1.m.money);
        System.out.println(person2.m.money); 
现在我们把克隆之后的money值改成99.99,再来输出结果,理论上我们希望输出19.9和99.99,但是我们发现程序输出并不是这样的
两个money的值都改了,这种情况就叫做浅拷贝。下面我们来分析一下他的原因。

现在这个现象就是浅拷贝,并没有将对象中的对象进行克隆。
深拷贝
深拷贝就是,我们希望把对象中的对象也拷贝一份。如何做到深拷贝呢?回到代码上,跟刚刚一样,我们需要在Money类中重写clone方法并实现cloneable接口
class Money implements Cloneable{
    public double money = 19.9;
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
} 
接下来我们需要调用这个clone()方法,那么怎么调用呢?person2是在Person person2 = (Person) person1.clone();这条语句执行时克隆出来的,那么我们回到Person类的clone()方法中,我们定义一个temp接受克隆出来的对象。


现在我们想让person1所指向的m这个对象也克隆一份出来,怎么调用它的clone()方法呢?很简单,谁调用方法谁就是this,
现在我们已经把m克隆了一份

把克隆的这份给temp.m


最后return temp把temp给person2
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person temp = (Person) super.clone();
        temp.m = (Money) this.m.clone();
        return temp;
    } 

temp是一个局部变量,当调用完clone()方法temp就被自动回收了。

因此main方法中的代码不用改变,我们再来运行,结果就正确了
深拷贝和浅拷贝看的是代码的实现过程。












![[MySQL][深入理解隔离性][上][MVCC]详细讲解](https://i-blog.csdnimg.cn/direct/5b7e85adf41b4fa684639ccb64a405df.png)





