目录
一.接口的概念
说到接口,大家第一反应也许就是USB接口,或者是插座的接口。
对于电脑的USB接口可以插U盘,鼠标,键盘等
对于插座的接口可以插电饭煲,冰箱,吹风机等等。
其实这和java的接口是类似的,就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。
在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
二.接口的语法
接口的关键字:implements 和 interface
public interface InterfaceName {
// 常量(可选,必须是`public static final`,可以省略修饰符)
int CONSTANT = 42;// 抽象方法(默认是`public abstract`,可以省略修饰符)
void method1();
String method2(String param);
}要点:
- 接口中的变量默认是
public static final
,表示常量,不能更改。- 接口中的方法默认是
public abstract
,实现类必须实现这些方法。- Java 8 引入了默认方法和静态方法,允许接口包含有实现的方法。
注意:之所以默认方法是
public abstract
,就跟我一开始在概念处说的,接口可以看做是多个类的公共规范,有了这一层关系,实现该接口的类就必须重写这些方法。
ps:
1. 创建接口时, 接口的命名一般以大写字母 I 开头.
2. 接口的命名一般使用 "形容词" 词性的单词.
3. 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性.
三.接口的简单创建
四.接口的简单使用
下面是一个动物的例子:
public interface IAnimal {
//抽象方法:默认修饰符为public abstract,没有具体实现
//睡觉
void sleep();
//吃饭
void eat();
//发出声音
void sound();
}
//狗子
//关键字implements,实现IAnimal接口
class dog implements IAnimal{
//实现了IAnimal接口就必须重写接口的抽象方法
@Override
public void sleep() {
System.out.println("狗子睡大觉...");
}
@Override
public void eat() {
System.out.println("狗子炫饭...");
}
@Override
public void sound() {
System.out.println("狗子汪汪汪....");
}
}
//猫猫
class cat implements IAnimal{
@Override
public void sleep() {
System.out.println("咪咪睡觉....");
}
@Override
public void eat() {
System.out.println("咪咪吃猫粮...");
}
@Override
public void sound() {
System.out.println("咪咪喵喵喵....");
}
}
五.接口的特性
1.接口中的方法默认是抽象的(默认修饰符为public abstract),即只有方法声明(方法名和参数),没有具体实现。
如:
解释:可以看到methodA没有具体实现也没有报错,即默认的修饰符为public abstract,
在methodB中我们给其加上public abstract,但是public abstract是灰色的,一样可以说明接口的方法默认修饰符是public abstract。
2.接口中的属性默认是public static final
,即常量,实现类不能修改它们的值。
报错:无法为最终变量“num”赋值
3.接口类型是一种引用类型,但是不能直接new接口的对象
如:
报错:“InterfaceA”是抽象的;无法实例化
4.重写接口中方法时,不能使用默认的访问权限
这里说一下:默认修饰符是default ,虽然没有直接写出来,但是不写编译器默认就是default。
ps:重写方法时访问权限修饰符不能比父类(接口)方法更严格。
- 例如:父类方法是
protected
,重写的方法不能是private
。而这里接口的方法修饰符原本是public,你使用默认default,范围变小了,自然不符。
5.接口中不能有静态代码块和构造方法
报错:
6.如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
如:
这个特性其实就是抽象类的特性。
7.接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class
这是因为在 Java 中,接口和类都属于 Java 字节码的一种表示形式,最终都会被编译成 JVM(Java 虚拟机)可以理解的
.class
文件。
8. jdk8中:接口中还可以包含default方法。
六.实现多个接口
定义两个接口IFlyable
和ISwimmable
,分别表示会飞和会游泳的能力。
// Flyable 接口
public interface IFlyable {
void fly();
}
// Swimmable 接口
public interface ISwimmable {
void swim();
}
定义一个类Duck
,它同时实现了Flyable
和Swimmable
接口,并提供具体的实现。
注意多个接口用 ' , '隔开
// Duck 类实现多个接口
public class Duck implements Flyable, Swimmable {
@Override
public void fly() {
System.out.println("Duck is flying.");
}
@Override
public void swim() {
System.out.println("Duck is swimming.");
}
}
在主类中,通过不同的接口调用实现类的方法,体现接口的多态性。
public class Main {
public static void main(String[] args) {
// 创建 Duck 对象
Duck duck = new Duck();
// 使用 Flyable 接口类型调用 fly 方法
Flyable flyingDuck = duck;
flyingDuck.fly();
// 使用 Swimmable 接口类型调用 swim 方法
Swimmable swimmingDuck = duck;
swimmingDuck.swim();
// 直接使用 Duck 类型调用方法
duck.fly();
duck.swim();
}
}
注意:一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类。
ps:IDEA 中使用 ctrl + i 快速实现接口
七.接口之间的继承
定义两个接口:
- 父接口
Animal
,声明两个通用的方法:eat()
和sleep()
。- 子接口
Bird
,继承Animal
并增加一个特定方法:fly()
。
// 父接口
public interface Animal {
void eat();
void sleep();
}
// 子接口继承父接口
public interface Bird extends Animal {
void fly();
}
创建一个类
Sparrow
(麻雀),实现子接口Bird
。
由于Bird
继承了Animal
,因此实现Bird
接口时,也需要实现Animal
中定义的所有方法。
// 实现子接口的类
public class Sparrow implements Bird {
@Override
public void eat() {
System.out.println("麻雀吃种子.");
}
@Override
public void sleep() {
System.out.println("麻雀在鸟巢睡觉.");
}
@Override
public void fly() {
System.out.println("麻雀在空中飞翔.");
}
}
定义一个测试类。
public class Main {
public static void main(String[] args) {
// 创建实现类对象
Bird sparrow = new Sparrow();
// 调用父接口中的方法
sparrow.eat();
sparrow.sleep();
// 调用子接口中定义的方法
sparrow.fly();
}
}
八.接口使用案例
1.Comparable 接口
Comparable
接口是 Java 提供的一个用来定义对象自然排序的接口。它位于java.lang
包中,并且常用于排序算法或排序集合(如TreeSet
、TreeMap
等)中。
我们在一个学生类中实现它:
然后按住Ctrl 点击 Comparable,可以看到Comparable
接口有一个单一的方法:
我们来翻译一下绿色字体:
简单来说:
compareTo()
方法用于比较当前对象与指定对象的大小关系。- 返回值:
- 负数:当前对象小于指定对象。
- 零:当前对象等于指定对象。
- 正数:当前对象大于指定对象。
在实现Comparable接口之前:
import java.util.*;
// 定义 Student 类
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + "}";
}
}
-----------------------------------------------------------------
//测试类
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
// 数组存储对象
Student[] students={
new Student("Alice", 20),
new Student("Bob", 18),
new Student("Charlie", 22)
};
// 使用 Arrays.sort 排序
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}
输出报错,意思就是不能直接使用Arrays.sort排序:
实现Comparable接口之后:
import java.util.*;
// 定义 Student 类
public class Student implements Comparable<Student> {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// 实现 compareTo 方法,按年龄排序(升序)
@Override
public int compareTo(Student other) {
return Integer.compare(this.age, other.age);
}
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + "}";
}
}
-------------------------------------------------------------------
//测试类
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
// 数组存储对象
Student[] students={
new Student("Alice", 20),
new Student("Bob", 18),
new Student("Charlie", 22)
};
// 使用 Arrays.sort 排序
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}
输出:
ps:
为什么Arrays.sort() 会在排序时调用每个对象的 compareTo() 方法?
它适用于数组中的对象,而非集合。对于实现了 Comparable
接口的对象数组,Arrays.sort()
会使用这些对象的 compareTo()
方法来进行排序。而且Arrays.sort()
的部分源码内部实现了调用 compareTo() 方法。
2.Clonable 接口和深拷贝
Cloneable
是一个标记接口,用于指示一个类的对象可以通过调用Object
类的clone()
方法来进行字段级别的拷贝。
特性:
实现
Cloneable
接口
一个类实现了Cloneable
接口,表明它的对象可以被克隆,否则调用clone()
方法时会抛出CloneNotSupportedException
。重写
clone()
方法
默认情况下,Object
的clone()
方法是受保护的(protected
),需要在子类中重写为public
并调用super.clone()
。浅拷贝
clone()
方法默认执行浅拷贝,即对对象的基本数据类型字段进行值拷贝,对引用类型字段仅拷贝引用,而不是创建新的对象。
我们来看一段浅拷贝的代码:
class Money {
public double m = 99.99;
}
class Person implements Cloneable{
public Money money = new Money();
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
--------------------------------------------------------------------
//测试类
public class TestDemo3 {
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person();
Person person2 = (Person) person1.clone();
System.out.println("通过person2修改前的结果");
System.out.println(person1.money.m);
System.out.println(person2.money.m);
person2.money.m = 13.6;
System.out.println("通过person2修改后的结果");
//修改后,打印输出我们发现两个值都改了,没有达到我们想要的效果
//这是因为在我们上面进行克隆操作的时候对象animal的对象m的地址也克隆过去了,
// 两个对象(person1和person2)的对象m的地址都一样,所以进行修改的时候两个值都修改了
//所以我们得再次重写一下clone方法
System.out.println(person1.money.m);
System.out.println(person2.money.m);
}
}
输出结果为:
代码解释:代码中会发生 浅拷贝,导致
person1
和person2
的money
字段引用的是同一个Money
对象。因此,通过person2.money.m
修改的值,会反映到person1.money.m
上。这是因为clone()
方法默认只是复制对象的字段引用,而不是深度复制它们。
ps小细节:
那么我们应该怎么修改呢?这就用到深拷贝了,如下代码:
class Money implements Cloneable{
public double m=99.99;
//实现了 Cloneable 接口以支持克隆。
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Animal implements Cloneable{
public Money money;
private String name;
public Animal(String name, double money) {
this.money=new Money();
this.name = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
//进行修改后
//调用 super.clone() 克隆当前对象,复制基本字段。
//手动克隆引用类型字段 money,确保克隆后的 money 是一个新的对象,而不是指向原对象的引用( 实现深拷贝)。
Animal tmp=(Animal)super.clone();
tmp.money=(Money)this.money.clone();
return tmp;
}
@Override
public String toString() {
return "Animal{" +
"name='" + name + '\'' +
'}';
}
}
-------------------------------------------------------------------------
//测试类
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Animal animal=new Animal("小黑",99.99);
Animal animal1= (Animal) animal.clone();
System.out.println(animal);
System.out.println(animal1);
System.out.println(animal==animal1);
System.out.println("-------------------------");
System.out.println("修改前");
System.out.println(animal.money.m);
System.out.println(animal1.money.m);
animal.money.m=122.99;
System.out.println("修改后");
System.out.println(animal.money.m);
System.out.println(animal1.money.m);
}
}
输出结果为:
九.接口和抽象类的区别
特性 | 接口 | 抽象类 |
多继承支持 | 支持,类可实现多个接口 | 不支持,一个类只能继承一个抽象类 |
字段 | 只能定义常量(public static final ) |
可定义任意字段 |
构造方法 | 不允许 | 可以有构造方法 |
方法 | 默认是抽象方法,可有默认、静态和私有方法 | 可定义抽象方法和具体方法 |
子类使用 | 使用implements实现接口 | 使用extends继承抽象类 |
权限 | public | 各种权限 |