java 接口

发布于:2025-07-14 ⋅ 阅读:(13) ⋅ 点赞:(0)

一,接口的概念

在java中,接口可以被看作是多个类的公共规范,是一种引用数据类型

二,语法规则

接口的定义格式与类基本相同,将class关键字换成interface关键字就定义了一个接口

public interface 接口名称{
//抽象方法
public abstract void method1();//public abstract是固定搭配,可以不写
public void method2();
abstract void method3();
void method4();
//注意:在接口中上述写法都是抽象方法

提示:

1,创建接口时,接口的命名一般以大写字母I开头

2,接口的命名一般使用"形容词"词性的单词

3,接口的方法和属性不要加任何修饰符号,保持代码的简洁性

三,接口的使用

接口不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法

public class 类名称 implements 接口名称{
  //...
}

注意:子类和父类之间是extends继承关系,类与接口之间是inplements实现关系

代码实例:

请实现笔记本电脑使用USB鼠标、USB键盘的例子
1. USB接口:包含打开设备、关闭设备功能
2. 笔记本类:包含开机功能、关机功能、使用USB设备功能
3. 鼠标类:实现USB接口,并具备点击功能
4. 键盘类:实现USB接口,并具备输入功能



四,接口特性

1,接口是一种引用类型,但是不能直接new接口的对象(不能直接实例化)

要使用接口,必须有一个具体类实现该接口,然后通过该类的实例来操作接口。

2,接口中每一个方法都是public的抽象方法,即接口中的方法会被隐式的指定为public abstract(只能是public abstract,其他的修饰符都会报错)

public interface USB{
  //Error:(4,18)java:此处不允许使用修饰符private
  private void openDevice();
  void closeDevice();
}

3,接口中的方法是不能在接口中实现的,只能由实现接口的类来实现

4,重写接口方法的时候不能使用默认的访问限制

所以重写USB中方法的时候,不能使用默认修饰符

5,接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量

这说明了brand具有final属性

6,接口中不能有静态代码块和构造方法

1)为什么不能有静态代码块?

  • 静态代码块用于在类加载时执行初始化逻辑,但接口本身不能被实例化,也没有实例化过程。

  • 接口中的静态变量默认是 public static final 的常量,必须在声明时直接赋值,不需要(也不允许)通过静态代码块初始化。

替代方案:

如果需要初始化接口的静态成员,可以用静态方法

2) 为什么接口不能有构造方法?

  • 构造方法用于初始化对象实例,但接口不能被实例化(没有对象的概念)。

  • 接口的实现类会由JVM自动生成默认构造方法(除非显式定义),与接口本身无关。

替代方案

如果需要在实现类初始化时执行逻辑,可以在实现类中定义构造方法

7,接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class

8,如果类没有实现接口中的所有抽象方法,则类必须设置为抽象类

9,jdk8中:接口中还可以包含default方法和static方法

五,实现多个接口

在java中,类和类之间是单继承的,一个类只能有一个父类,即java中不支持多继承,但是一个类可以实现多个接口.

注意:一个类实现多个接口的时候,每个接口中的抽象方法都要实现,否则类必须设置为抽象类

上述都讲述了一个知识:一个类只能继承一个父类,但是一个类可以有多个接口.

此外,继承表达的含义是:"is-a",而接口表达的含义是"具有xxx特性"

这样设计的好处是可以让程序员不必关注类型,而只是关注某个类是否具备某种能力

六,接口之间的继承

经过之前的学习,我们知道java类与类之间是单继承的,一个类可以实现多个接口.但是接口与接口之间可以实现多继承.即:用接口可以达到多继承的目的

interface IRuning{
  void run();
}
interface ISwimming{
  void swim();
//两栖的动物,既能跑,也能游.
interface IAmphibious extends IRunning,ISwimming{
 ... ... ...
}
class Frog implements IAmphibious{
 ... ... ...
}

通过接口继承创建了一个新的接口 IAmphibious表示两栖的,此时创建的Frog类,就继续要实现run方法,也需要实现swim方法

注:接口之间的继承相当于把多个接口合在一起

七,接口使用实例

1,对对象数组进行排序:

按照我们之前的理解,数组我们有一个现成的sort方法,能否直接使用这个方法呢

代码出错了,所以和普通的整数不一样,两个整数是可以直接进行比较的,大小关系明确.而两个学生对象的大小关系怎么确定,需要我们额外指定

让我们的Student类实现Comparable接口,并实现其中的compareTo方法

从上述代码中我们可以看到,在sort代码中会自动调用commpareTo方法.compareTo的参数是Object,其实传入的就是Student类型的对象

然后比较当前对象和参数对象的大小关系(按分数来算)

如果当前对象应排在参数对象之前, 返回小于 0 的数字;
如果当前对象应排在参数对象之后, 返回大于 0 的数字;
如果当前对象和参数对象不分先后, 返回 0;
再次执行程序, 结果就符合预期了.
注意事项:对于sort数组来讲,需要传入的数组的每个对象都是"可比较"的,需要具备compareTo这样的能力,通过重写compareTo方法的方式,就可以定义比较规则

2,如何使用compareTo

这个问题比较重要,因此我们将他的地位提高一点

八,compareTo的使用

compareTo方法是Java中用于比较对象顺序的重要方法,主要定义在Comparable接口中。以下是关于如何在Java中使用compareTo的详细说明

1,compareTo的基本语法:

public int compareTo(T o)

它返回:

  • 负整数:当前对象小于参数对象

  • :当前对象等于参数对象

  • 正整数:当前对象大于参数对象

2,实现comparable接口

要使类可比较,需要实现Comparable接口:

分析:

1)comprable是java当中的一个接口,用于定义对象之间的自然顺序

2)当一个类实现了Comparable接口时,他必须重写compareTo方法,该方法用于比较当前对象与另一个对象的顺序

3)在这个例子中,person类实现了Comparable<person>,表示person类的对象可以与其他person类的对象进行比较

4)person有两个私有成员变量:name(姓名,字符串类型)和age(年龄,整数类型)

5)重写compareTo方法:

compareTo方法是comparable接口中定义的方法,用于比较当前对象与另一个对象的顺序

首先比较两个person对象的name,使用string类的compareTo方法来比较两个姓名字符串

姓名相同就用age来比较

3,常用类的compareTo用法

1)string类:

String str1 = "apple";
String str2 = "banana";
int result = str1.compareTo(str2); // 返回负值,因为"apple"字典序在"banana"之前

2)integer类:

Integer num1 = 10;
Integer num2 = 20;
int result = num1.compareTo(num2); // 返回负值,因为10 < 20

3)自定义比较逻辑:

注:只要是自定义的类型,涉及到了大小的比较,目前的一个结论是:一定要实现Comparable接口

4,使用comparator

除了实现Comparable接口,还可以使用Comparator

List<String> names = Arrays.asList("John", "Alice", "Bob");

// 自然顺序排序
Collections.sort(names); // 使用String的compareTo

// 自定义排序
Collections.sort(names, (s1, s2) -> s2.compareTo(s1)); // 降序排序

5,注意事项:

  1. compareTo应该与equals方法保持一致:如果x.compareTo(y)==0,那么x.equals(y)应该为true

  2. 比较时应考虑null值情况(通常null被认为小于非null值

  3. 对于可能溢出的数值比较,使用Integer.compare()Double.compare()等静态方法

6,代码实例

为了进一步加深对接口的理解,我们可以尝试自己实现一个sort方法来完成刚才的排序过程(使用冒泡排序)

代码执行结果:

冒泡排序部分也可以这么写

7,compareTo的缺陷

compareTo比较方法,一般用于固定的比较,不适合非常灵活的比较

8,用途

用在默认的比较

九,Comparator的使用

在 Java 中,Comparator 是一个接口,用于定义自定义的对象比较规则,主要用于排序(如 Collections.sort() 或 Arrays.sort())或有序集合(如 TreeSetTreeMap)。

1,Comparator的基本用法:

1)integer.compare()方法

integer.compare(int x, int y)是integer类的一个静态方法,用于比较两个int类型的值x和y,它的返回值遵循以下规则:

如果 x<y ,返回 -1;

如果 x == y ,返回 0 ;

如果 x > y ,返回1;

2)为什么使用 integer.compare()

在java中,直接比较两个  int  类型的值可以使用 关系运算符 (如  <  ==  >  )但使用  integer.compare()   方法可以使代码更加  简洁  ,并且  integer.compare()  方法的  返回值  与  Comparator  接口的compare  方法的要求一致,即返回   -1   0   1  ,这样使代码具有一致性

2,代码实例:

十,Clonable接口和深拷贝

1,概念理解

java内置了一些很有用的接口,Clonable就是其中之一

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

在 Java 中,Cloneable 是一个标记接口(没有方法)表示一个类允许被克隆默认的 Object.clone() 方法是浅拷贝只复制基本类型和引用,不复制引用对象)。要实现深拷贝,需要重写 clone() 方法并手动复制引用对象。

2,浅拷贝与深拷贝的区分

  • 浅拷贝(Shallow Copy)

    • 只复制对象的基本类型字段(如 intString)和引用地址(如对象、数组)。

    • 不会复制引用指向的实际对象,新旧对象共享同一份引用数据。

    • 修改其中一个对象的引用字段,会影响另一个对象!

  • 深拷贝(Deep Copy)

    • 完全复制对象及其所有引用指向的对象,生成一个完全独立的副本。

    • 修改副本的引用字段,不会影响原对象。

    • 一句话理解

  • 浅拷贝是“复制钥匙”(多个钥匙开同一把锁)。

  • 深拷贝是“复制钥匙 + 复制锁”(每个钥匙开自己的锁)。

3,代码实例

代码输出结果:

为了方便理解,我们把上述代码分成两部分来看

第一部分:

1)implements Cloneable:

这是java的一个  "标记接口"   ,  没有实际方法  ,只是告诉JVM:这个类的对象可以被克隆,如果不实现它,调用super.clone()会抛出 CloneNotSupportedException

2)clone( )方法

先调用  supper.clone( )(即Object的clone方法),它会创建一个新对象,复制原对象所有的字段值(如果字段是基本类型,直接拷贝值;如果是引用类型,拷贝的是引用地址,这也是"浅克隆的特点)"

因为Object.clone(  )返回的是Object类型,所以需要强转为Animal

第二部分:

3)animal.clone(  ):

调用Animal重写的clone方法,生成一个新的Animal对象(和原对象内容一样,但是内存地址不同)

4) == 的作用

在java中, == 比较的是对象的内存地址,如果地址相同,才会返回true,否则返回false

5)为什么结果输出的是false?

因为animal和animal2是两个不同的对象,他们在堆内存占用不同的空间,地址不一样.所以逻辑运算符的结果是false

6)拓展小知识(深克隆vs浅克隆)

当前代码是  浅克隆  :如果  animal  类里有  引用字段  类型,  super.clone( )  只会拷贝  引用地址  (比如  private List<String> habits;),克隆后的对象和原对象会共享这个引用.如果修改其中一个对象的  habits  ,那么另一个也会受到影响.

如果需要深克隆(完全独立,修改互不影响)就需要手动处理引用类型字段,比如在clone方法里对引用类型也进行克隆或新对象赋值

4,前提回顾,变量的类型

1,基本数据类型(Primitive Types)

存储的是实际的值,直接分配在栈内存中。
Java 有 8 种基本类型:

  • byteshortintlong

  • floatdouble

  • charboolean

2,引用数据类型(Reference Types)

存储的是对象的引用(内存地址),而不是对象本身。
引用类型包括:

  • 类(Class)(如 StringPersonList

  • 接口(Interface)

  • 数组(Array)

  • 枚举(Enum)

5,代码实例(浅拷贝的地址有关性)

代码比较长,我们依旧分两部分

第一部分:

第二部分

代码运行结果:

throws:

当一个方法使用throws声明某个异常时,意思是这个方法不打算在内部处理该异常(比如不用 try - catch捕获),而是把"处理异常的责任"抛给调用这个方法的上层代码

1)举例说明

就像 Person 的 clone 方法,它调用了 super.clone()(即 Object 类的 clone 方法 ),而 Object 类的 clone 方法本身就声明了会抛出 CloneNotSupportedException 。如果当前类(Person)不处理这个异常,就通过 throws 告诉调用 Person.clone() 的代码:“我可能会抛出这个异常,你得处理”

2)和 Cloneable 接口的关联

Cloneable 是 Java 的一个标记接口,一个类实现了 Cloneable 接口,才 “允许” 被克隆 。如果一个类没实现 Cloneable 接口,却调用了 clone 方法,Object 类的 clone 逻辑就会抛出 CloneNotSupportedException 。所以在重写 clone 方法时,为了遵循异常处理规则,需要通过 throws 声明这个潜在的异常,让调用者知晓并处理。

3)对调用者的要求

以 TestDemo3 的 main 方法为例,它调用了 p1.clone() ,而 Person.clone() 声明了会抛出 CloneNotSupportedException 。此时 main 方法有两种选择:

  • 自己捕获异常:用 try-catch 包裹调用代码,像这样:
  • 继续向上抛异常:也就是代码里的写法,用 throws 把异常抛给更上层(这里 main 方法是程序入口,抛给 JVM ,JVM 遇到未处理的受检异常会终止程序并提示 )。不过实际开发中,对于 main 方法这种程序入口,一般建议用 try-catch 处理,让程序更健壮。

4). 总结一下 throws 的作用

  • 声明异常:告诉调用者当前方法可能抛出的异常类型,让调用者决定如何处理(捕获或者继续抛 )。
  • 遵循规则:对于受检异常,要么处理(try-catch ),要么声明(throws ),否则代码编译不通过,这是 Java 编译器对异常处理的强制约束,能帮我们提前规避潜在的运行时错误,让程序逻辑更严谨。

简单说,throws 就是一种 “甩锅” 式的异常声明,把处理异常的任务传递给调用方,保证代码在编译阶段就对异常有清晰的处理规划 。

6上述浅拷贝代码改为深拷贝代码

十一,抽象类和接口的区别

抽象类和接口都是java中多态的常见使用方法

核心区别:

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

如之前写的Aniaml的例子,此处的Animal中包含一个name这样的属性,这个属性在任何子类中都是存在的,因此此处的Animal只能作为一个抽象类而不能成为一个接口

class Animal {
  protected String name;

  public Animal(String name) {
    this.name = name;
  }
}

再次提醒:

抽象类存在的意义是为了让编译器更好的校验, Animal 这样的类我们并不会直接使用, 而是使用它的子类.
万一不小心创建了 Animal 的实例, 编译器会及时提醒我们.

大纲总结


网站公告

今日签到

点亮在社区的每一天
去签到