自学内容网 自学内容网

Java--接口

目录

语法规则

例子

实现多个接口

接口之间的继承

抽象类和接口的区别

接口使用实例--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方法中的代码不用改变,我们再来运行,结果就正确了

深拷贝和浅拷贝看的是代码的实现过程。


原文地址:https://blog.csdn.net/kkkkkkk6666666/article/details/140576249

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!