Java——接口

209 阅读9分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

一、基本概念


接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

  • 接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
  • 除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
  • 接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。

1、接口与类

相似:

  • 一个接口可以有多个方法。
  • 接口文件保存在.java结尾的文件中,文件名使用接口名。
  • 接口的字节码文件保存在.class结尾的文件中。
  • 接口相应的字节码文件必须在与包名称相匹配的目录结构中。

区别:

  • 接口不能用于实例化对象。
  • 接口没有构造方法。
  • 接口中所有方法必须是抽象方法。Java8以后接口中可以使用default关键字修饰的非抽象方法。
  • 接口不能包含成员变量,除了static和final变量。
  • 接口不是被类继承了,而是要被类实现。
  • 接口支持多继承。

抽象类和接口的区别:

  • 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
  • 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
  • 一个类只能继承一个抽象类,而一个类却可以实现多个接口。

核心区别: 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法.

注: 在JDK1.8以后 1、接口中可以有静态方法和方法体。 2、接口允许包含具体实现的方法,该方法称为“默认方法”,默认方法使用default关键字修饰。 在JDK1.9以后允许将方法定义为 private,使得某些复用的代码不会把方法暴露出去。

2、特性

  • 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
  • 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报a编译错误)。
  • 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。

二、语法


声明

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

接口的命名: 一般以大写字母I开头。一般使用“形容词”词性的单词。阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性。

三、使用


1、单个接口

//IAnimal.java
public interface IAnimal {
    void eat();
    void bark();
}
//Dog.java
public class Dog implements IAnimal {
    @Override
    public void eat() {
        System.out.println("Dog.eat");
    }

    @Override
    public void bark() {
        System.out.println("Dog.bark");
    }
}
//Test.java
public class Test {
    public static void main(String[] args) {
        IAnimal animal = new Dog();
        animal.bark();
        animal.eat();
    }
}

在这里插入图片描述

2、实现多个接口

//Animal.java
public abstract class Animal {
    String breed;
    String name;

    public Animal(String breed, String name) {
        this.breed = breed;
        this.name = name;
    }
}
//IFly.java
public interface IFly {
    void fly();
}
//IRun.java
public interface IRun {
    void run();
}
//ISwim.java
public interface ISwim {
    void swim();
}
//Cat.java
public class Cat extends Animal implements IRun,ISwim{ //猫既能跑又能游泳
    public Cat(String breed, String name) {
        super(breed, name);
    }

    @Override
    public void run() {
        System.out.println("Cat.run");
    }
    
    @Override
    public void swim() {
        System.out.println("Cat.swim");
    }
}

注: 一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类。使用ctrl+i可以快速实现接口。

3、接口间的继承

在Java中,类和类之间是单继承的,一个类只有一个父类,类和接口之间可以多实现,一个类实现多个接口,接口和接口之间也可以多继承。所以可以使用接口来达到多继承的目的。 如下:

//两栖动物既能跑又能游泳
public interface IAmphibious extends IRun,ISwim{ 

}

//青蛙既能跑又能游泳
class Frog implements IAmphibious { 
 
}

接口间继承功能就是将多个接口合并到了一起。

四、实例


//不实现comparable方法,使用sort排序对象会报错
class Student{
    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString() {
        return "{" +"name='" + name + '\'' + ", score=" + score + '}';
    }

    public static void main(String[] args) {
        Student[] students = new Student[]{
                new Student("张三",90),
                new Student("李四",96),
                new Student("王五",91),
                new Student("赵六",79),
        };
        Arrays.sort(students);
        for (int i = 0; i < students.length; i++) {
            // println:在打印对象时,会默认调用对象的toString方法
            System.out.println(students[i]);
        }
    }
}

在这里插入图片描述

//实现接口Comparable中的compareTo方法,sort正常排序对象
class Student implements Comparable{
    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString() {
        return "{" +"name='" + name + '\'' + ", score=" + score + '}';
    }

    //默认规则,Java中所有的类都继承自Object
    @Override
    //这里的Object就是传入的Student类型的对象
    //然后比较当前对象和参数对象的大小关系(我这里实现的是按分数来算)
    //1.如果当前对象应排在参数对象之前, 返回小于 0 的数字;
    //2.如果当前对象应排在参数对象之后, 返回大于 0 的数字;
    //3.如果当前对象和参数对象不分先后, 返回 0;    
    public int compareTo(Object o) { 
        Student s = (Student)o;
        if (this.score>s.score){
            return -1;
        }else if (this.score>s.score){
            return 1;
        }else{
            return 0;
        }
    }

    public static void main(String[] args) {
        Student[] students = new Student[]{
                new Student("张三",90),
                new Student("李四",96),
                new Student("王五",91),
                new Student("赵六",79),
        };
        Arrays.sort(students); //实现了comparable即可正常排序
        for (int i = 0; i < students.length; i++) {
            // println:在打印对象时,会默认调用对象的toString方法
            System.out.println(students[i]);
        }
    }
}

在这里插入图片描述

五、实用的接口和方法


1、Object类

第五节开始想先介绍下Object类。Object类是Java默认提供的一个类,它是所有类的基类,和所有类都是继承关系,即所有类的对象都可以使用Object的引用进行接收。 示例(使用Object接收所有类的对象):

class Student{}
class Class{}

public class Test {
    public static void main(String[] args) {
        function(new Class());
        function(new Student());
    }
    public static void function(Object obj) {
        System.out.println(obj);
    }
}

在这里插入图片描述

1.1 使用Object接收接口对象

interface IMessage {
    public void getMessage() ;
}
class MessageImpl implements IMessage {
    @Override
    public String toString() {
        return "Test Code 1024" ;
    }
    public void getMessage() {
        System.out.println("Code 1024");
    }
}
public class Test {
    public static void main(String[] args) {
        IMessage msg = new MessageImpl() ; // 子类向父接口转型
        Object obj = msg ; // 接口向Obejct转型
        System.out.println(obj);
        IMessage temp = (IMessage) obj ; // 强制类型转换
        temp.getMessage();
    }
}

在这里插入图片描述 Object真正实现了参数的统一,如果一个类希望接收所有的数据类型,那么就用Object类完成。

1.2 使用Object接收数组对象

public class Test {
    public static void main(String[] args) {
        Object obj = new int[]{1, 2, 3, 4, 5, 6}; // Object接收数组对象,向上转型
        int[] data = (int[]) obj; // 向下转型,需要强转
        for (int i : data) {
            System.out.print(i + " ");
        }
    }
}

在这里插入图片描述

2、toString()方法

  • 在使用对象直接输出的时候,默认输出的是一个地址编码。
  • 使用的是String类,该类对象直接输出的是内容。
class Student{
    private String name;
    private int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
public class Test {
    public static void main(String[] args) {
        function(new Student("李华",20));
    }
    public static void function(Object obj) {
        System.out.println(obj.toString());
    }
}

在这里插入图片描述

toString()方法的源码为:

    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

默认的toString()方法功能不足时,可以覆写该方法。

class Student{
    private String name;
    private int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
	
	//使用快捷键alt+insert选择toString()方法
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class Test {
    public static void main(String[] args) {
        function(new Student("李华",20));
    }
    public static void function(Object obj) {
        System.out.println(obj.toString());
    }
}

在这里插入图片描述 该方法的核心在于获取对象的信息。

3、equals()方法

对比两个对象是否相同,可以使用equals()方法 显然在对比对象的时候不能使用==来判断对象是否相同。因为对象名是引用变量,里面保存的是所指向对象的“地址”,两个不同的对象“地址”显然不同。 示例(使用==判断):

class Student{
    private String name;
    private int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class Test {
    public static void main(String[] args) {
        Student student = new Student("小明",20);
        Student student1 = new Student("小明",20);
        System.out.println(student == student1);
    }
}

在这里插入图片描述 使用equals()方法:

class Student{
    private String name;
    private int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class Test {
    public static void main(String[] args) {
        Student student = new Student("小明",20);
        Student student1 = new Student("小明",20);
        System.out.println(student.equals(student1));
    }
}

在这里插入图片描述 还是false,咱们来看下equals()源码:

    public boolean equals(Object obj) {
        return (this == obj);
    }

equals()方法中的实现使用的也是==,本质依然是比较两个引用是否相同。 比较引用类型是否相同的时候,需要重写equals方法。

class Student{
    private String name;
    private int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
	
	////使用快捷键alt+insert重写方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age &&
                name.equals(student.name);
    }

}
public class Test {
    public static void main(String[] args) {
        Student student = new Student("小明",20);
        Student student1 = new Student("小明",20);
        System.out.println(student.equals(student1));
    }
}

在这里插入图片描述

4、hashcode()方法

先看源码:

public native int hashCode();

该方法是一个native方法,底层是由C/C++代码写的。具体内容我们看不到。 示例:

class Student{
    private String name;
    private int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class Test {
    public static void main(String[] args) {
        Student student = new Student("小明",20);
        Student student1 = new Student("小明",20);
        System.out.println(student.hashCode());
        System.out.println(student1.hashCode());
    }
}

在这里插入图片描述hashCode()方法进行重写:

class Student{
    private String name;
    private int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

	//equals()和hashCode()方法一般一起重写
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age &&
                name.equals(student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

public class Test {
    public static void main(String[] args) {
        Student student = new Student("小明",20);
        Student student1 = new Student("小明",20);
        System.out.println(student.hashCode());
        System.out.println(student1.hashCode());
    }
}

在这里插入图片描述 可以看出是否重写hashCode()方法对产生的结果是会产生影响的。 ==对象的物理地址跟这个hashcode地址不一样,hashcode代表对象的地址说的是对象在hash表中的位置。== hashCode()的存在主要是为了查找的快捷性,hashCode()是用来在散列存储结构中。 hashCode()是用于查找使用,应用在散列表中,而equals()是用于比较两个对象的是否相等的。

5、Clonable接口

Clonable是一个很有用的接口。 Object类中存在一个clone方法,调用这个方法可以创建一个对象的 "拷贝"。但是要想合法调用 clone 方法,必须要先实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常。

class Student{
    public double score = 100;
}

class Stu implements Cloneable{
    public Student student = new Student();

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Stu stu1 = new Stu();
        Stu stu2 = (Stu) stu1.clone();
        System.out.println("stu1的成绩:"+stu1.student.score);
        System.out.println("stu2的成绩:"+stu2.student.score);
        stu2.student.score = 96;  //只修改stu2种得score值
        System.out.println("==============修改后的成绩===============");
        System.out.println("stu1的成绩:"+stu1.student.score+"  stu1的hashcode:"+stu1.hashCode());
        System.out.println("stu2的成绩:"+stu2.student.score+"  stu2的hashcode:"+stu2.hashCode());
    }
}

在这里插入图片描述 只修改了stu2score值,但是stu1score值也修改了,说明两个score是同一个scorestu1stu2hashcode不同,说明clone只拷贝了Stu的对象,但是Stu中的Student对象并没有发生拷贝。在这块就是发生了浅拷贝。 注: clone只能实现浅拷贝,想要实现深拷贝就需要对clone再进行重写。