一,接口的概念
在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实现关系
代码实例:





四,接口特性
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类型的对象
然后比较当前对象和参数对象的大小关系(按分数来算)
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,注意事项:
compareTo
应该与equals
方法保持一致:如果x.compareTo(y)==0
,那么x.equals(y)
应该为true比较时应考虑null值情况(通常null被认为小于非null值)
对于可能溢出的数值比较,使用
Integer.compare()
或Double.compare()
等静态方法
6,代码实例
为了进一步加深对接口的理解,我们可以尝试自己实现一个sort方法来完成刚才的排序过程(使用冒泡排序)
代码执行结果:
冒泡排序部分也可以这么写
7,compareTo的缺陷
compareTo比较方法,一般用于固定的比较,不适合非常灵活的比较
8,用途
用在默认的比较上
九,Comparator的使用
在 Java 中,Comparator
是一个接口,用于定义自定义的对象比较规则,主要用于排序(如 Collections.sort()
或 Arrays.sort()
)或有序集合(如 TreeSet
、TreeMap
)。
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)
只复制对象的基本类型字段(如
int
,String
)和引用地址(如对象、数组)。不会复制引用指向的实际对象,新旧对象共享同一份引用数据。
修改其中一个对象的引用字段,会影响另一个对象!
深拷贝(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 种基本类型:
byte
,short
,int
,long
float
,double
char
,boolean
2,引用数据类型(Reference Types)
存储的是对象的引用(内存地址),而不是对象本身。
引用类型包括:
类(Class)(如
String
,Person
,List
)接口(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;
}
}