第6章 接口、lambda表达式与内部类

目录

6.1 接口

6.1.1 接口的概念

6.1.2 接口的属性

6.1.3 接口与抽象类

6.1.4 静态和私有方法

6.1.5 默认方法

6.1.6 解决默认方法冲突

6.1.7 接口与回调

6.1.8 Comparator 接口

6.1.9 对象克隆

6.2 lambda表达式

6.2.1 为什么引入lambda表达式

6.2.2 lambda表达式的语法

6.2.3 函数式接口

6.2.4 方法引用

6.2.5 构造器引用

6.2.6 变量作用域

6.2.7 处理lambda表达式

6.2.8 再谈Comparator

6.3 内部类

6.3.1 使用内部类访问对象状态

6.3.2 内部类的特殊语法规则

6.3.3 内部类是否有用、必要和安全

6.3.4 局部内部类

6.3.5 由外部方法访问变量

6.3.6 匿名内部类

6.3.7 静态内部类

6.4 服务加载器

6.5 代理


6.1 接口

6.1.1 接口的概念

接口不是类,而是对希望符合这个接口的类的一组需求

public interface Comparable{
    int compareTo(Object other);
}
//任何实现Comparable接口的类都需要包含compareTo方法

接口中所有的方法都自动是public方法。因此,在接口中声明方法时,不必提供关键字public

接口中绝不会有实例字段,也绝不会实现方法

提供实例字段和方法实现的任务应该由实现接口的那个类来完成,接口可以看作是没有实例字段的抽象类,但是与抽象类有一定的区别

让类实现一个接口:

  1. 将类声明为实现给定的接口
  2. 对接口中所有的方法提供定义
class Employee implements Comparable

6.1.2 接口的属性

接口不是类,不能使用new运算符实例化一个接口

可以声明接口的变量,接口变量必须引用实现了这个接口的类对象

接口可以和类一样,实现继承和扩展

虽然在接口中不能包含实例字段,但是可以包含常量

尽管每个类只能有一个超类,但却可以是实现多个接口

6.1.3 接口与抽象类

使用抽象类来表示通用属性存在一个严重的问题,每个类只能扩展一个类,但是每个类可以实现多个接口

6.1.4 静态和私有方法

6.1.5 默认方法

可以为接口方法提供一个默认实现,必须用default修饰符标记这个一个方法

public interface Comparable{
    default int compareTo(T other){return 0;}
}

6.1.6 解决默认方法冲突

  1. 超类优先。如果超类提供了一个具体的方法,同名而且有相同参数类型的默认方法会被忽略
  2. 接口冲突。如果一个接口实现了一个默认方法,另一个接口提供了一个同名而且参数类型相同的方法,必须覆盖这个方法来解决冲突
  3. 类优先。当一个超类的方法和一个接口冲突时

6.1.7 接口与回调

回调(callback)是一种常见的程序设计模式,在这种模式中,可以指定某个特定事件发生时应该采取的动作

在 java.swing 包中有一个Timer类,如果希望经过一定时间间隔就得到通知,Timer类就很有用。构造定时器时,需要设置一个时间间隔,并告诉定时器经过这个时间间隔时需要做些什么。由于Java标准库采用的时面向对象的语言,可以向定时器传入某个类的对象,然后,定时器调用这个对象的方法。

定时器需要知道调用哪一个方法,并要求传递的对象所属的类实现了 java.awt.event包的ActionListener接口。

public interface ActionListener{
    void actionPerformed(ActionEvent event);
}

6.1.8 Comparator 接口

可以对一个字符串数组进行排序,因为String类实现了Comparable,按字典的顺序比较字符串。假设希望按长度递增的顺序对字符串进行排序,而不是按字典顺序进行排序,肯定不能让String类用两种不同的方式实现compareTo方法。

要处理这种矛盾的情况,Arrays.sort方法还有第二个版本,有一个数组和一个比较器(comparator)作为参数,比较器是实现了Comparator接口的类的实例。

public interface Comparator{
    int compare(T first,T second);
}

6.1.9 对象克隆

对于每一个类,需要确定:

  1. 默认的clone方法是否满足要求
  2. 是否可以在可变的子对象上调用clone来修补默认的clone方法
  3. 是否不该使用clone

实际上,第三个选项是默认选项。如果选择第1项或第2项,类必须:

  1. 实现clone接口
  2. 重新定义clone方法,并指定为public访问修饰符

cloneable接口的出现与接口的正常使用并没有关系,它没有指定方法,这个方法是从object类中继承来的。这个接口只是作为一个标记,指示类的设计者了解克隆的过程(“标记接口”)。如果对象请求一个克隆,但是没有实现这个接口,就会生成一个检查型异常

6.2 lambda表达式

6.2.1 为什么引入lambda表达式

到目前为止,在Java中传递一个代码段并不容易,你不能直接传递代码段。Java时一种面向对象语言,所以必须构造一个对象,这个对象的类需要一个方法包含所需的代码

6.2.2 lambda表达式的语法

lambda表达式就是一个代码块,以及必须传入代码的变量规范

一个lambda表达式的形式:参数,箭头(->)以及一个表达式

(String first,String second)
   -> first.length()-second.length()

//当一条语句放不下时,可以将其放在{}中
(String first,String second) ->
   {
       if(first.length()-second.length()) return -1;
       else if(first.length()>second.length()) return 1;
       else return 0;
}

//即使lambda表达式没有参数,仍然要提供空括号,就像无参数方法一样
() -> { for(int i=100;i>=0;i--) System.out.println(i) }

//如果可以推导出一个lambda表达式的参数类型,则可以忽略其类型
Comparator comp
   = (first,second)
      -> first.length()-second.length()

//无须指定lambda表达式的返回类型,lambda表达式的返回类型总是会由上下文推导得出

6.2.3 函数式接口

Java中有很多封装代码块的接口,lambda表达式与这些接口是兼容的

对与只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式。这种接口称为函数式接口

6.2.4 方法引用

var timer = new Timer(1000,System.out::println);

表达式System.out::println是一个方法引用,它指示编译器生成一个函数式接口的实例,覆盖这个接口的抽象方法来调用给定的方法

要用"::"运算符分隔方法名与对象或类名,主要有3种情况

1、object::instanceMethod

方法引用等价于向方法传递参数的lambda表达式。对于System.out::println,对象是System.out,所以方法表达式等价于 x -> System.out.println(x)

2、Class::instanceMethod

第1个参数会成为方法的隐式参数。String::compareToIgnoreCase等同于 (x,y) -> x.compareToIgnoreCase(y)

3、Class::staticMethod

所有参数都传递到静态方法。Math::pow等价于 (x,y) -> Math.pow(x,y)

只有当lambda表达式的体只调用一个方法而不做其他操作时,才能把lambda表达式重写为方法引用

6.2.5 构造器引用

构造器引用与方法引用很类似,只不过方法名为new。例如,Person::new是Person构造器的一个引用

6.2.6 变量作用域

lambda表达式有三部分:

  1. 一个代码块
  2. 参数
  3. 自由变量的值,这是指非参数而且不在代码中定义的变量

lambda表达式可以捕获外围作用域中变量的值,只能引用值不会改变的变量。如果在lambda表达式中引用一个变量,而这个变量可能在外部改变,这也是不合法的

lambda表达式中捕获的变量必须实际上是事实最终变量(这个变量初始化之后局不会再为它赋新值)。lambda表达式的体与嵌套块有相同的作用域。

在一个lambda表达式中使用this关键字时,是指创建这个lambda表达式的方法的this参数

6.2.7 处理lambda表达式

6.2.8 再谈Comparator

6.3 内部类

内部类是定义在另一个类中的类

  • 内部类可以对同一个包中的其他类隐藏
  • 内部类方法可以访问定义这个类的作用域中的数据,包括原本私有的数据

6.3.1 使用内部类访问对象状态

内部类的对象总有一个隐式引用,指向创建它的外部类对象

外部类的引用在构造器中设置。编译器会修改所有的内部类构造器,添加一个对应外围类引用的参数

可以把内部类声明为私有的只有内部类可以是私有的,而常规类可以有包可见性或公共可见性

6.3.2 内部类的特殊语法规则

OuterClass.this :表示外围类引用

OuterClass.InnerClass :在外围类的作用域之外,引用内部类

6.3.3 内部类是否有用、必要和安全

6.3.4 局部内部类

若某个内部类只是在在某个方法中创建这个类型的对象时使用了一次,可以在一个方法中局部地定义这个类

声明局部类时不能有访问说明符(即private或public)。局部类的作用被限定在声明这个局部类的块中

局部类有一个很大的优势,即对外部世界完全隐藏,甚至TalkingClock类中的其他代码也不能访问它

6.3.5 由外部方法访问变量

与其他内部类相比,局部类还有一个优点:它们不仅能够访问外部类的字段,还可以访问局部变量(局部变量必须为事实最终变量)

6.3.6 匿名内部类

使用局部内部类时,通常还可以再进一步。假如只能创建这个类的一个对象,甚至不需要为这个类指定名字,这样一个类被称为匿名内部类

new SuperType(construction parameters){
    inner class methods and data;
}

其中,SuperType可以是接口,如果是这样,内部类就要实现这个接口;SuperType也可以是一个类,如果是这样,内部类就要扩展这个类

匿名类不能有构造器,但是可以提供一个对象初始化块

6.3.7 静态内部类

使用内部类只是为了把一个类隐藏在另一个类的内部,并不需要内部类有外围类对象的一个引用。为此,可以将内部类声明为static,这样就不会生成那个引用

  • 与常规内部类不同,静态内部类可以有静态字段和方法
  • 在接口中声明的内部类自动是static和public

6.4 服务加载器

6.5 代理

720. 词典中最长的单词 - 力扣(LeetCode) (leetcode-cn.com)

class Solution {
    public String longestWord(String[] words) {
        Arrays.sort(words,(a,b)->
        {
            if(a.length()!=b.length())return a.length()-b.length();
            return b.compareTo(a);
        });
        String s="";
        HashSet set = new HashSet();
        set.add("");
        for(int i=0;i

你可能感兴趣的:(Java,java,开发语言,后端)