07-接口
接口
- 接口中所有方法都是
public
- 接口允许多重继承
- 实现接口必须声明为
public
Comparable接口
- Arrays.sort(Comparable obj)方法要求一个实现了Comparable接口的对象
- 因为这个排序会调用Comparable的comparaTo方法
- 通过这个comparaTo方法可以自定义排序规则
comparaTo的实现规则:
- this<other,返回负整数
- this=other,返回0
- this>other,返回正整数
比如,如果想要按照员工的薪水进行排序
//首先,这个类要实现Comparable接口
public class Employee implements Comparable{
...;
//实现comparaTo方法,则会按照compareTo方法定义的规则排序
@override
public int comparaTo(Object obj){
Employee other = (Employee) obj;
return Double.compare(salary, obj.salary);
}
}
更优的方法,实现泛型接口
//首先,这个类要实现Comparable接口
public class Employee implements Comparable<Employee>{
...;
//实现comparaTo方法,则会按照compareTo方法定义的规则排序
@override
public int comparaTo(Employee em){
return Double.compare(salary, em.salary);
}
}
Comparator接口
Arrays.sort()有一个重载方法,可以接受一个Comparator
Comparator
使用场景:有的类没有继承Comparable
接口,并且也不能再继承这个接口,可以传入一个Comparator
来定义排序规则
//自定义按字符串长度排序
public LengthComparator implements Comparator<String>{
@override
public int compare(String first, String second){
return first.length-sencond.length;
}
}
//使用这个比较器
String[] fields = {"asdh","asda","asdasdf"};
Arrays.sort(fields, new LengthComparator);
Cloneable接口
clone()
方法是Object
类的一个protected
方法- 直接调用这个
clone()
方法得到的是一个浅拷贝(对象引用是一样的,并没构造新对象 Cloneable
是一个标记接口,表示这个类可以调用copy()
方法
创建深拷贝:
class Employee implements Cloneable{
@override
public Employee clone() throws CloneNotSupportedException{
//调用Object.clone()得到一个浅拷贝
Employee cloned = (Employee) super.clone();
//对于引用对象,再次调用这个对象自己的clone()方法
cloned.hireDay = (hireDay) cloned.hireDay.clone();
return cloned;
}
}
注意到CloneNotSupportedException
这个异常,这种深拷贝有很多限制
其中的引用对象也必须是实现了Cloneable
接口的类,否则则会抛出异常,拷贝失败
数组有一个clone
方法,可以建立一个深拷贝的副本
int[] a={1,2,3,4,5};
int[] cloned = a.clone();
cloned[0]=100;//a数组中的值不会变
接口的属性
-
接口中不能包含实例字段,但可以包含常量
-
接口中的常量总是
public static final
-
接口不能实例化
-
接口变量必须引用实现了这个接口的类对象(多态),可以使用
instanceof
检验是否实现了某个接口
接口的默认实现
使用default
为接口方法提供默认实现
默认方法可以事先调用还没实现的抽象方法
//默认方法
public interface Comparable<T>{
default int compareTo(T other){return 0;}
}
//默认方法调用抽象方法
public interface Collection{
//抽象方法
int size();
default boolean isEmpty{
return this.size() == 0;
}
}
方法冲突
-
超类优先:超类中有同名和相同参数类型的方法优先
-
接口冲突:多个接口中有同名同参数的默认方法,需要自行解决
- 多个接口中的同名同参数方法都会被继承,如果需要调用到超类的方法,必须要显式给出
- 只要有一个接口的同名同参数方法是默认方法,就需要显式继承
- 非默认方法,直接
@override
完事
interface Person{ default String getName(){return "1"}; } interface Named{ default String getName(){return "2"}; } //如果要调用到父类的该方法,应显式给出 class Student implements Person,Named{ @override public String getName(){ return Person.super.getName(); } }
回调
一种常见的程序设计模式,指等待某个事件发生后采取的动作
面向过程的语言一般采用函数传递的方法
但java一般是把某方法封装在接口内,然后要求一个实现了该接口的对象,将这个对象传递
比如,java.swing.Timer
希望一定事件间隔得到通知,一定时间间隔后触发通知函数,即回调
这个Timer的构造方法则需要接收一个函数式的对象(这个对象实现了某种接口,重写了其中的方法,Timer会调用这个方法
//实现ActionListener的actionPerformed()方法
public TimerPrinter implements ActionListener{
@override
public void actionPerformed(ActionEvent event){
System.out.println("time is"+Instant.ofEpochMilli(event.getWhen()));
Toolkit.getDefaultToolkit().beep();
}
}
//传给Timer进行调用
Timer t = new Timer(1000, new TimerPrinter);
t.start;
lambda表达式
参考上面回调的例子,我们仅仅是想要传入某个方法进去,但是却不得不实现一个接口然后传入他的对象
这样其实不太方便,lambda
表达式实际上就是一段代码块,但是本质却可以生成一个实现接口的对象传传递到需要的地方
lambda表达式的语法
(Param1,Param2,...) ->{expressions;}
- 参数如果可以从代码块中推断出类型,则前面可以不给出类型(最好还是给出来
- 即使不传参数,也要写
()
- 无需指定返回值,lambda表达式会根据上下文进行推导
- 写在代码块中则需要指定返回值
//使用lambda表达式实现一个Comparator
Comparator<String> com = (String first,String second) -> first.length()-second.length();
Comparator<String> com = (String first,String second) -> {return first.length()-second.length();};
函数式接口
所谓函数式接口,就是只有一个抽象方法的接口,唯一的用处就是用来传递方法
这种接口使用lambda表达式非常便捷
仅能把lambda表达式赋给函数式接口
//自定义实现ArrayList中的removeIf()
list.removeIf(e -> e==null);
//原本的函数式接口
public interface Predicate<T>{
boolean test(T t);
}
方法引用
使用场景不多,不好用
仅在lambda表达式只调用一个方法时才能使用
使用::
分隔方法和对象名/类名
- object::intanceMethod
- Class::instanceMethod
- Class::staticMethod
//以下几种表达式等价
Timer timer = new Timer(1000, event -> System.out.println(event));
Timer timer = new Timer(1000, System.out::println);
String::trim
x -> x.trim
使用this
和super
也是合法的
比如,this::equals等价于x -> this.equals(x)
构造器引用
- Class::new
比如new Person()等同与Person::new
具体调用哪个构造器由上下文决定
变量作用域
-
lambda表达式可以捕获外部变量的值,但是只能引用值不会改变的变量(事实最终变量
//这样是可以的,text不会被修改 public void repeat(String text){ new Timer(1000, event -> { System.out.println(text); //text被捕获 }).start(); } //捕获变量后在lambda表达式修改是非法的 public void repeat(String text, int i){ new Timer(1000, event -> { System.out.println(text); System.out.println(i); i--; //非法,不允许修改 }).start(); } //捕获在外部会修改的变量也是非法的 public void repeat(String text){ for(int i=10; i>0; i--){ new Timer(1000, event -> { System.out.println(text); System.out.println(i); //非法,捕获了外部会修改的值 }).start(); } }
-
参数与局部变量同名也是非法的
Path first = Path.of("/usr/bin"); Comparator<String> comp = (first, second) -> first.length()-second.length(); //非法,first重名了
-
lambda表达式子中的
this
指的是类对象class test{ public void repeat(String text){ //this指的是test对象引用而不是ActionListener ActionListener listener = event -> {System.out.println(this.toString);} new Timer(1000, listener).start(); } }
处理lambda表达式
在需要用到函数式接口的地方使用
使用lambda表达式的重点是延迟执行,将这个方法传递进去,具体什么时候执行不知道
使用@FunctionalInterface
来标注一个函数式接口
比如,要写一个repeat方法重复执行某些操作
public static void repeat(int n, Runnable ation){
for(int i=0; i<n;i++){
action.start();
}
}
//可以这样使用repeat
repeat(10, () -> System.out.println("balabla"));
Comparator进阶
comparing方法返回的还是一个Comparator对象
但是comparing方法接收的是一个比较函数的接口(Function
比如,要按照人名进行排序
//返回一个按照人名排序的比较器
Arrays.sort(people, Comparator.comparing(Person::getName));
相应的对于int类型的数据有comparaInt()方法,对于double类型的数据compareDouble()等
还可以使用thenCompare()指定第二条件、第三条件...
//优先按照名字排序,名字相同按照年龄排序
Arrays.sort(people, Comparator.comparing(Person::getName).thenCompareInt(Person::getAge));
内部类
- 仅内部类所属的外部类可见
- 内部类可以访问其外部类的数据,包括private数据
使用内部类访问外部字段
观察下面的例子,只要不调用start()
方法,则内部类不会被实例化
使用内部类直接就能访问类的私有字段,很好理解,因为private
就是仅本类可见的意思,而内部类恰好在这个类中
底层:内部类在实例化时,就会默认构造一个指向外部对象的引用,Class.this
指的就是外围类的对象
public class TimeClock {
private boolean isBeep;
public TimeClock(boolean isBeep) {
this.isBeep = isBeep;
}
public void start(){
ActionListener listener = new InternalTimerPrinter();
Timer t = new Timer(1000, listener);
t.start();
}
public class InternalTimerPrinter implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("time is:"+e.getWhen());
if(isBeep) //可以写成TimeClock.this.isBeep
{
Toolkit.getDefaultToolkit().beep();
}
}
}
}
-
设置内部类为
private
仅内部类可以设置为
private
,此时只能被该类的内部访问和实例化 -
设置内部类为
public
外面的其他类或者其他对象也可以访问这个内部类
比如,如果想把内部类的对象那出来到外部
TimeClock tc = new TimeClock(false); //拿到内部类对象 TimeClock.InternalTimerPrinter listener = tc.new InternalTimerPrinter();
内部类的属性
-
索引静态字段必须是
final
,且必须初始化 -
不能有
static
方法 -
内部类是一个编译器现象,对于JVM来说看不出来他是一个内部类
编译时,会自动在内部类添加对外围类的引用和获取外围类字段的方法
但是可能不安全
局部内部类
如果仅仅在某个地方只使用一个对象一次,则可以局部定义这个类
比如,上面的例子中InternalTimerPrinter
只在start方法中出现,可以改写为:
//将内部类写在了方法内部而已
public void start(){
class InternalTimerPrinter implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("time is:"+e.getWhen());
if(isBeep) //可以写成TimeClock.this.isBeep
{
Toolkit.getDefaultToolkit().beep();
}
}
}
ActionListener listener = new InternalTimerPrinter();
Timer t = new Timer(1000, listener);
t.start();
}
- 注意:局部变量不能有访问修饰符,作用域为这个块中,对块以外完全隐藏
局部内部类可以访问局部变量(块作用域,但是和lambda一样只能访问不可变的变量
public void start(boolean isBeep){
class InternalTimerPrinter implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("time is:"+e.getWhen());
if(isBeep) //isBeep为局部变量isBeep,由start方法传入
{
Toolkit.getDefaultToolkit().beep();
}
}
}
ActionListener listener = new InternalTimerPrinter();
Timer t = new Timer(1000, listener);
t.start();
}
匿名内部类
场景:只想使用某个类的对象,暂时用一下然后其他地方不用了,懒得单独去定义,甚至名字都不需要
//语法
new SuperType(parameters[]){
data and methods of inner class...
}
也就是说,内部类是一个继承类,是实现了某个接口或者类的类
- 匿名内部类也是内部类,所以具有访问外围类字段的作用
- 匿名内部类在方法内部,那也一样具备局部内部类的性质
- 匿名内部类没有类名,也就没有构造器
public void start(boolean isBeep){
ActionListener listener = new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("time is:"+e.getWhen());
if(isBeep)
{
Toolkit.getDefaultToolkit().beep();
}
}
}; //这是一个实现了ActionListener的类
Timer t = new Timer(1000, listener);
t.start();
}
静态内部类
场景:只是为了隐藏某个类,不需要对外围类有引用,则可以定义为static
在static
方法中调用内部类对象,则内部类必须是static
的