文章目录
- 一、接口是什么?
- 1.1 接口的概念
- 1.2 接口与类的相似点和区别
- 1.3 接口的特性
- 二、接口的使用
- 2.1 接口的实现
- 2.2 实现多个接口
- 2.3 接口之间的扩展(继承)
- 三、接口的使用实例
- 3.1 Comparable接口
- 3.2 Comparator接口
- 3.3 Clonable接口
- 3.3.1 使用Clonable接口
- 3.3.2 浅拷贝
- 3.3.3 深拷贝
- 四、抽象类和接口的区别
- 总结
一、接口是什么?
接下来涉及到的很多概念,来源于
>概念来源<大家也可以直接点击进入,进行学习查看
1.1 接口的概念
接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类(但在Java8以后,加入了默认方法和静态方法,这两个方法可以不被重写)。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。
注意:虽然抽象类无需实现接口的所有方法但是,继承它的类必须将抽象类实现的接口的所有方法全部实现
public interface IShape {//接口的定义
}
1.2 接口与类的相似点和区别
接口与类相似点:
-
一个接口可以有多个方法。
-
接口文件保存在 .java 结尾的文件中,文件名使用接口名。
-
接口的字节码文件保存在 .class 结尾的文件中。
兄弟们可能会发现自己在编写了IShape的Java源代码之后没有在文件夹中找到.class文件,这是因为源代码还没有被编译,可能是编译器没有将源代码编写后直接进行编译打开(即自动编译),如果需要打开可以用这个步骤来设置,或者你可以将这个接口运行一下。
- 接口相应的字节码文件必须在与包名称相匹配的目录结构中。
接口与类的区别:
-
接口不能用于实例化对象。
同样的我们还是可以通过提示发现,我们可以像抽象类一样通过匿名内部类,实现用匿名内部类来实现这个接口,再将这个类赋值于iShape对象。
iShape是IShape类型的变量。它被声明为接口IShape类型的引用变量,这个引用可以指向任何实现了IShape接口的对象。在这段代码中,通过创建一个匿名内部类实现了IShape接口,并将这个匿名内部类的实例赋值给了iShape变量。所以,iShape可以调用IShape接口中定义的方法(这里的draw方法)。
-
接口没有构造方法。
-
接口中所有的方法必须是抽象方法,Java 8 之后 接口中可以使用 default 关键字修饰的非抽象方法。
-
接口不能包含成员变量,除了 static 和 final 变量。
当方法被static修饰后便可以通过接口名直接调用了,与类相似。
-
接口不是被类继承了,而是要被类实现。
-
接口支持多继承。
接口特性:
-
接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
-
接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
-
接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
抽象类和接口的区别:
- 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
- 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
注:
JDK 1.8 以后,接口里可以有静态方法和方法体了。
JDK 1.8 以后,接口允许包含具体实现的方法,该方法称为"默认方法",默认方法使用 default 关键字修饰。
JDK 1.9 以后,允许将方法定义为 private,使得某些复用的代码不会把方法暴露出去。
1.3 接口的特性
接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。
接口中的抽象方法都是公有的。
二、接口的使用
2.1 接口的实现
public class Dog extends Animal implements IRunning{
//继承抽象类 实现接口
}
2.2 实现多个接口
我们知道,类是无法实现多继承的,但是类可以实现很多的接口,比如,猫会跑步也会爬树,那么它可以实现两个接口分别是跑步和爬树
public interface IFlying {
void flying();
}
public abstract class Animal {
String name;
int age;
public Animal(String name,int age){
this.name = name;
this.age = age;
}
}
public class Cat extends Animal implements IRunning,IClimbTree{
public Cat(String name,int age) {
super(name,age);
}
public void running(){
System.out.println("猫猫"+name+"在跑......");
}
public void climbTree(){
System.out.println("猫猫"+name+"在爬树.....");
}
}
2.3 接口之间的扩展(继承)
接口的继承使用extends关键字,子接口继承父接口的方法
在Java中,类的多继承是不合法,但接口允许多继承。
同时实现接口的类也同时要将接口扩展的方法实现。
public interface ISport {
void sport();
}
public interface IRunning extends ISport{
void running();
}
public class Cat extends Animal implements IRunning,IClimbTree{
public Cat(String name,int age) {
super(name,age);
}
public void running(){
System.out.println("猫猫"+name+"在跑......");
}
public void climbTree(){
System.out.println("猫猫"+name+"在爬树.....");
}
@Override
public void sport() {
System.out.println("猫猫"+name+"实现了IRunning扩展的ISport接口");
}
}
public class Dog extends Animal implements IRunning{
public Dog(String name,int age) {
super(name,age);
}
public void running(){
System.out.println("狗狗"+name+"在跑.....");
}
@Override
public void sport() {
System.out.println("狗狗"+name+"实现了IRunning扩展的ISport接口");
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog("修狗",3);
Cat cat = new Cat("修猫",4);
dog.running();
dog.sport();
cat.running();
cat.sport();
}
}
在接口的多继承中extends关键字只需要使用一次,在其后跟着继承接口
public interface Hockey extends Sports, Event
以上的程序片段是合法定义的子接口,与类不同的是,接口允许多继承,而 Sports及 Event 可以定义或是继承相同的方法>>代码来源
三、接口的使用实例
3.1 Comparable接口
同时我们也可以想到,我们应该通过什么来比较两个学生谁大谁小呢?是通过名字还是年龄呢?所以我们要确定一个我们比较的规则——实现Comparable接口,重写compareTo方法。
public class Student implements Comparable<Student>{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
if(this.age > o.age){
return 1;
}else if(this.age == o.age){
return 0;
}else {
return -1;
}
}
}
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
Student[] students = new Student[]{
new Student("zhangsan",21),
new Student("lisi",19),
new Student("wangwu",23),
};
System.out.println("排序前"+ Arrays.toString(students));
Arrays.sort(students);
System.out.println("排序后"+ Arrays.toString(students));
}
}
可以简单理解一下什么是泛型,下面这个是AI的一段代码
类型安全:
- 泛型确保在编译时进行类型检查,避免了在运行时出现类型转换错误。例如,使用泛型集合(如List)可以确保只存储特定类型的元素,而不会意外地混入其他不兼容的类型。
- 在没有泛型的情况下,从集合中取出元素时需要进行显式的类型转换,这可能导致ClassCastException异常。而使用泛型后,编译器会自动进行类型检查和转换,提高了代码的安全性。
代码复用:
- 通过使用泛型,可以编写通用的代码,适用于多种不同的类型。例如,可以定义一个泛型方法,该方法可以对不同类型的参数进行相同的操作,而无需为每种类型都编写重复的代码。
- 泛型类和泛型接口也可以实现代码的复用,使得相同的逻辑可以应用于不同的类型。
如果一个类没有实现Comparable接口,那么它无法通过Arrays.sort()方法来实现排序
接下来我用自己的方法来进行冒泡排序,这样更方便理解
public static void mySort(Comparable<Student>[] comparables){
for (int i = 0; i < comparables.length-1; i++) {
for (int j = 0; j < comparables.length-i-1; j++) {
if(comparables[j].compareTo((Student) comparables[j+1])>0){
Comparable<Student> tmp = comparables[j];
comparables[j] = comparables[j+1];
comparables[j+1] = tmp;
}
}
}
}
所以,这也是为什么要实现Comparable这个接口才能用sort这个方法的原因
3.2 Comparator接口
当需要对已经存在的类进行排序,但这些类没有实现Comparable接口,或者需要使用不同的排序规则时,可以创建一个实现了Comparator接口的比较器类。
可以在不同的地方使用不同的比较器,实现多种排序方式,而无需修改被比较对象的类定义。
Comparable是通过让类自身实现接口来定义比较逻辑,而Comparator是通过创建独立的比较器类来实现比较逻辑。
当我们想通过不同的属性决定不同的比较方式时可以使用Comparator这个接口,比如我们想通过年龄来比较,或者通过名字来比较用这个接口,只需要将对应的比较器传给sort就行。
import java.util.Comparator;
public class AgeComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge()-o2.getAge();
}
}
import java.util.Comparator;
public class NameComparator implements Comparator<Student> {
public int compare(Student o1,Student o2){
return o1.getName().compareTo(o2.getName());
}
}
import java.util.Arrays;
public class Test {
public static void mySort(Comparable<Student>[] comparables){
for (int i = 0; i < comparables.length-1; i++) {
for (int j = 0; j < comparables.length-i-1; j++) {
if(comparables[j].compareTo((Student) comparables[j+1])>0){
Comparable<Student> tmp = comparables[j];
comparables[j] = comparables[j+1];
comparables[j+1] = tmp;
}
}
}
}
public static void main(String[] args) {
Student[] students = new Student[]{
new Student("zhangsan",21),
new Student("lisi",19),
new Student("wangwu",23),
};
AgeComparator ageComparator = new AgeComparator();
NameComparator nameComparator = new NameComparator();
System.out.println("排序前"+ Arrays.toString(students));
Arrays.sort(students,ageComparator);
System.out.println("排序后"+ Arrays.toString(students));
}
}
同时,兄弟们,我在写这个博客的时候发现了一个小问题,然后我用我在AI上找到的内容解释一下,如果有人觉得不对,有更准确的解释,也请告诉我一下,谢谢~~
我们点进Comparator的源码中可以看到,有compare方法和equals方法,但我们只需要重写compare方法,而我们知道当方法被default修饰或者被static修饰是可以不被重写的,但这里的equls方法并没有被重写,这是为什么呢?
因为equals这个方法是在Object类中定义的,所有的类都继承这个类,既然继承了这个类,我们的类就默认拥有了这个方法,所以也不需要重写这个方法。所以我当时的困惑在于,既然已经继承了这个方法,为什么还要在接口中定义一遍。
一、方法功能描述
equals这个方法用于判断另一个对象是否与当前的比较器 “相等”。它必须遵循Object.equals(Object)的通用约定。此外,只有当指定的对象也是一个比较器并且它与当前比较器施加相同的排序顺序时,这个方法才能返回true。
也就是说,如果两个比较器comp1和comp2,当comp1.equals(comp2)为true时,对于任何对象引用o1和o2,都有signum(comp1.compare(o1, o2)) == signum(comp2.compare(o1, o2))。
二、关于重写的说明
注意,不重写Object.equals(Object)总是安全的。然而,在某些情况下,重写这个方法可能会通过允许程序确定两个不同的比较器施加相同的顺序来提高性能。
三、参数解释
obj:要与之比较的引用对象。
四、返回值解释
只有当指定的对象也是一个比较器并且它与当前比较器施加相同的排序顺序时,返回true。
在Comparator接口中定义equals方法主要有以下几个原因:
一、明确语义和约束
- 虽然Object类中的equals方法提供了一种通用的相等性判断机制,但对于Comparator接口来说,其相等性的含义更加特定。在Comparator的上下文中,相等性不仅仅是两个对象是否为同一实例,更重要的是它们是否产生相同的比较结果。
- 通过在Comparator接口中定义equals方法,可以明确地传达出对于比较器对象相等性的特定要求,即两个比较器相等当且仅当它们对任意两个对象的比较结果的符号相同。
二、灵活性和可扩展性 - 不同的实现者可以根据具体需求重写Comparator接口中的equals方法。例如,在某些复杂的应用场景中,可能需要更严格或更宽松的比较器相等性判断标准。
- 这使得开发人员可以根据具体情况定制比较器的相等性判断逻辑,而不仅仅依赖于Object类中默认的引用相等性判断。
三、一致性和可读性 - 当处理比较器对象时,明确在Comparator接口中定义equals方法有助于提高代码的一致性和可读性。开发人员可以清楚地知道对于比较器对象应该如何进行相等性判断,而不需要去猜测或依赖于通用的Object类的equals方法的行为。
- 特别是在涉及到使用比较器的复杂数据结构和算法中,明确的相等性定义可以减少错误和提高代码的可维护性。
综上所述,尽管Object类中已经有了equals方法,但在Comparator接口中定义equals方法可以为比较器对象提供更明确、更灵活和更具语义的相等性判断标准,以满足不同的应用需求。
3.3 Clonable接口
3.3.1 使用Clonable接口
在写这个的时候,突然被protected这个修饰符给搞了,我想的是,不同包的子类既然可以用,那我用这个子类实例化对象为什么不可以用
protected方法:子类可以在任何地方访问父类的protected方法,这是为了支持继承体系中的方法调用。但是,对于子类实例化的对象,只有在与父类或子类处于同一包中时,才能直接访问父类protected方法;如果不在同一包中,则无法直接访问。
这也是为什么我们在子类Person中如果不重写clone方法就无法通过对象调用的原因
如果没有这个接口,会报下面的错误
3.3.2 浅拷贝
当我们克隆person1时,是将person1在堆中所创建的对象拷贝一份,然后将这个被克隆的对象的地址赋值给person2引用变量,但我们发现,这个拷贝中也把我们用Money类实例化的m引用变量也拷贝了,也就是这两个对象中的m引用变量所指向的地址是一样的,这也是为什么我们改了person1中的money却同时改变了person2中的money,这就是浅拷贝。
class Money{
public int money;
}
public class Person implements Cloneable{
private int age;
private String name;
public Money m = new Money();
public Person(Money m) {
this.m = m;
}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
", money=" + m.money +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Test {
public static void main(String[] args)
throws CloneNotSupportedException {
Person person1 = new Person(10,"zhangsan");
Person person2 = (Person) person1.clone();
System.out.println("修改前");
System.out.println(person1);
System.out.println(person2);
person1.m.money = 10;
System.out.println("修改后");
System.out.println(person1);
System.out.println(person2);
}
}
3.3.3 深拷贝
当我们给Money中也加入clone方法,这样我们在克隆时就可以将m所引用的对象也拷贝一份,这样两个对象中的m所指向的的Money类的对象就是不同的,这样我们改变person1中的m就不会改变person2的,这也就是深拷贝
class Money implements Cloneable{
public int money;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Person implements Cloneable{
private int age;
private String name;
public Money m = new Money();
public Person(Money m) {
this.m = m;
}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
", money=" + m.money +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
Person tmp = (Person) super.clone();
tmp.m = (Money) this.m.clone();
return tmp;
}
}
public class Test {
public static void main(String[] args)
throws CloneNotSupportedException {
Person person1 = new Person(10,"zhangsan");
Person person2 = (Person) person1.clone();
System.out.println("修改前");
System.out.println(person1);
System.out.println(person2);
person1.m.money = 10;
System.out.println("修改后");
System.out.println(person1);
System.out.println(person2);
}
}
四、抽象类和接口的区别
总结
本篇文章,介绍了有关接口的内容,包括什么是接口,如何使用接口,以及常用的接口的使用,包括Comparable、Comparator、Clonable接口,如果有什么地方有错,还望指正,我会尽快更正。