文章目录
前言
记录一下学习java核心卷1中第六章(接口、lambda表达式与内部类)中接口的内容。按照惯例,先绘制脑图。本文也会按照自己的脑图思路进行介绍,其中我感觉接口的特性和两个常用接口是重点需要掌握的内容。
1、接口概念
接口用来描述类应该做什么, 而不指定他们该具体怎么做。
为啥要用接口呢?我个人觉得,接口也是一种抽象,是对类的行为的抽象,java没有多继承,可以用接口实现多继承。使用接口类型引用实现该接口的类,也可以有利于代码的维护、安全。最后,大型项目可能需要定义一些明确地接口。
接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
接口中不能提供什么?
- 接口中不会有实例对象,接口不能被实例化,但是可以被实现。
- 在java8之前,接口不实现方法
看完上面这些如果有点懵的话,那就先有个印象就好了。总之接口就是一个大抽象,里面有一堆没有定义如何实现的方法,也不能被new 成对象,作用就是定义一组类的某些公共行为,然后这个接口类型可以用来引用实现则合格接口的类。
具体看这个吧。
2、接口属性
接口类型可以用来声明对象,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。这个性质是不是超像父类。
同样,接口能够使用instanceof函数来判断某个类是否实现了具体的接口
if(anObject instanceOf Comparable){...}
接口也能进行扩展(继承其他的接口)
// 文件名: Sports.java
public interface Sports
{
public void setHomeTeam(String name);
public void setVisitingTeam(String name);
}
// 文件名: Football.java
public interface Football extends Sports
{
public void homeTeamScored(int points);
public void visitingTeamScored(int points);
public void endOfQuarter(int quarter);
}
// 文件名: Hockey.java
public interface Hockey extends Sports
{
public void homeGoalScored();
public void visitingGoalScored();
public void endOfPeriod(int period);
public void overtimePeriod(int ot);
}
Hockey接口自己声明了四个方法,从Sports接口继承了两个方法,这样,实现Hockey接口的类需要实现六个方法。相似的,实现Football接口的类需要实现五个方法,其中两个来自于Sports接口.
接口不能包含实例字段,但是能够包含常量。接口中的方法都会被自动设置为public,接口中的字段总是public static final的。
每个类只能有一个超类,但是能实现多个接口。使用逗号将各个接口分开。
class Employee implements Cloneable, Comparable
3、接口方法
3.1、静态和私有方法
java8之后允许在接口中添加静态方法,通常的做法是将静态方法放在伴随类中。总之这种功能用的比较少啦。
3.2、默认方法
接口中可以为接口方法提供一个默认的实现,但必须用default
修饰符来修饰
public interface Comparable<T>{
default int compareTo(T other) {return 0;}
}
但是这种其实也没啥意义,实现接口的时候都会被覆盖。
给出两个用途:
- 可以用默认方法调用其他方法
public interface Collection{
int size();
default boolean isEmpty(){return size()==0;}
}
- 可以实现“接口演化”:意思是接口添加了一个新方法之后,原来实现这个接口的类就必须要重新实现这个方法再编译,但是如果将新填进来的方法声明为default,那可以不用重新编译,并且使用实现这个接口的类的时候,调用新的方法将调用接口的默认实现方法。
3.3、解决默认方法冲突
冲突为啥会发生?就有两种情况:
1、接口中方法与超类中方法的签名相同。
默认超类优先。毕竟超类大大可能有方法的实现,接口中可是一定没有方法实现的。
2、两个接口中的方法的签名相同。
- 如果两个方法中至少有一个提供了default的实现,那程序员需要选一个
- 如果都没有提供实现,那相当于没有冲突,直接实现就好了。
其实很好理解的,“类优先” ,然后冲突了你就写明白到底用谁的?
4、接口特性
4.1、接口与抽象类
- 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
- 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
注:JDK 1.8 以后,接口里可以有静态方法和方法体了。
注:JDK 1.8 以后,接口允许包含具体实现的方法,该方法称为"默认方法",默认方法使用 default 关键字修饰。更多内容可参考
Java 8 默认方法。注:JDK 1.9 以后,允许将方法定义为 private,使得某些复用的代码不会把方法暴露出去。更多内容可参考 Java 9
私有接口方法。
4.2、接口与回调
回调是啥呢?就是发生某个行为时需要运行一个动作,也就是需要传入一个函数。java中没有传入函数的方法,但是可以传入一个对象呐,对象里面不是有函数嘛!那这个对象里面有好多函数,怎么知道该运行哪个函数呢?这就是接口的作用了,接口中指定了该调用哪个函数。
举个例子:
package interf;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Instant;
import javax.swing.JOptionPane;
import javax.swing.Timer;
public class TimerTest {
public static void main(String[] args) {
var listener = new TimerPrinter();
var timer = new Timer(1000, listener);
timer.start();
JOptionPane.showMessageDialog(null, "Quit Program?");
System.exit(0);
}
}
class TimerPrinter implements ActionListener{
public void actionPerformed(ActionEvent event) {
System.out.println("At the tone, the time is "+ Instant.ofEpochMilli(event.getWhen()));
Toolkit.getDefaultToolkit().beep();
}
}
5、常用接口
5.1、Comparator接口
1、使用Comparable接口实现比较功能。
public class Employee implements Comparable<Employee>{
...
public int compareTo(Employee other){
return Double.compare(salary, other.salary);
}
}
2、使用Comparator接口实现比较功能。
class LengthComparator implements Comparator<String>{
public int compare(String first, String second){
return first.length()-second.length();
}
}
显然与Comparable接口不同点在于参数的个数。
具体完成比较的时候
String[] fields = {"Peter", "hhh", "Mary"};
Arrays.sort(fields, new LengthComparator());
3、基于lambda表达式使用Comparator来实现比较功能。
Comparator中包含很多静态方法,使用lambda表达式可以简化创建的过程,十分方便。
Arrays.sort(people, Comparator.comparing(Person::getName));
变体:
Arrays.sort(people, Comparator.comparing(Person::getName).thenComparing(Persion::getLastName));
Arrays.sort(people, Comparator.comparing(p -> p.getName().length()));
其中lambda表达式的语法见我另外的blog。
5.2、对象克隆
浅拷贝:并没有克隆对象中其他引用的对象。如果原对象和前对象共享的子对象是不可变的,那么这种恭喜那个就是安全的。
举个例子,String类就是不可变的。
String类的值是保存在value数组中的,并且是被private final修饰的
1、private修饰,表明外部的类是访问不到value的,同时子类也访问不到,当然String类不可能有子类,因为类被final修饰了
2、final修饰,表明value的引用是不会被改变的,而value只会在String的构造函数中被初始化,而且并没有其他方法可以修改value数组中的值,保证了value的引用和值都不会发生变化所以我们说String类是不可变的。
深拷贝:就是将引用类型都拷贝一下,因为不可变的子对象是很少的,需要对引用也拷贝。
进行之前需要考虑
- 默认的clone是否满足要求
- 是否可以在可变子对象中调用clone方法修补默认的clone方法
- 是否不该使用clone
所实现的类必须满足:
- 实现Cloneable
- 重新定义clone方法,并制定public来修饰。
注意这里Cloneable接口并没有clone方法,这个clone方法是从Object中继承的,这个借口只是一个标记的作用,用于检查是不是有实现继承,这是为了允许在类型查询中用到instanceof。
写个简单的例子吧:
class Employee implements Clonable{
...
public Employee clone() throws CloneNoetSupportedException{
Employee cloned = (Employee) super.clone();//
cloned.hireDay = (Date)hireDay.clone();
return cloned
}
}
上面这个例子写的就是,先调用父类的clone把不可变的字段复制了,再重新克隆一个可变对象来生成最终的克隆对象。
数组克隆:
所有的数组类型都有一个公共的克隆方法,而不是受保护的,可以用这个方法创建一个新数组。
int[] luckyNumbers = {2,3,5,7,11,13};
int[] cloned = luckyNumbers.clone();
具体可以看看这个blog。
总结
总是在焦虑、彷徨。担心走的慢了、担心卷、担心好多好多,内耗严重。总之,还在路上,一直走下去吧。