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

6.1 接口

  1. 接口中所有的方法都自动是public,所有的常量字段都是public static final。接口中不会有实例字段,即不会有未初始化的属性,必须都是常量属性。

  2. Java8以后,接口中可以简单的实现一些方法,但是这些方法不能引用实例字段。

  3. Java5中,Comparable接口提升成了一个泛型类型。

    public interface Comparable<T> {
        public int compareTo(T o);
    }
    

    在未使用泛型之前,实现Comparable接口需要进行强制类型转换。

    class Dog implements Comparable{
        @Override
        public int compareTo(Object o) {
            Dog dog = (Dog)o;
            ...
        }
    }
    

    使用泛型之后可以直接在实现接口的时候声明泛型,这样就不用进行类型转换了。

    class Dog implements Comparable<Dog>{
        @Override
        public int compareTo(Dog o) {
            //直接进行比较逻辑
            ...
        }
    }
    
  4. Java8中,允许在接口中增加静态方法。但是这有违将接口作为抽象规范的初衷。

    目前位置,通常的做法都是将静态方法放到伴随类中。在标准库中有大量的成对存在的接口和实用工具类。例如Collection/Collections、Path/Paths等。

    在Java11中,伴随类的方法都整合到了接口的静态方法中了。类似的,实现你自己的接口的时候,没有理由再为实用工具方法另外提供一个伴随类。

  5. 在Java9中,接口中的方法可以主动声明成private。private方法可以是静态方法或实例方法。由于私有方法只能在接口本身的方法中使用,所以它们的用法非常有限,只能作为接口中其他方法的辅助方法。

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

    public interface Comparable<T>{
        default int compareTo(T o){
            return 0;  //默认所有对象都是相等的
        }
    }
    

    当默认方法相互冲突时,Java依靠以下规则解决。

    1. 超类优先。

      如果一个类继承一个超类,同时实现了一个接口。若接口和超类中有同名,同参数方法。那么子类优先考虑超类中的方法的实现,而忽略接口中的实现。

    2. 接口冲突。

      如果一个类同时实现两个接口,且两个接口中有同名同参数的方法,其中至少有一个方法有默认实现。那么编译器会报错,要求用户在类中重新实现一遍该方法。

  7. Comparator接口

    Arrays.sort还有一个可以传入比较器的方法。只需要比较器实现Comparator接口就可以了。

  8. Cloneable接口

    Object默认的clone是一种浅拷贝(即如果被拷贝对象中含有对其他对象的引用,则该引用不会进行clone,会直接赋值给克隆后的对象。结果就是克隆后的对象和被克隆对象会共同持有一个对一个相同对象的引用)。因此想实现深拷贝,就要重写父类的clone方法,并把该方法的访问修饰符改成public,然后实现Cloneable接口。该接口是一种标记接口,内部没有任何方法。

6.2 lambda表达式

6.2.1 语法

lambda表达式是一个可以传递的代码块,可以在以后执行一次或者多次。

(参数列表) -> {表达式}  //表达式起辅助作用,后面没有分号

如果参数类型可以推导出来,那么参数列表可以省略,只需要一个括号即可。(first, second) -> first.length()-second.length

如果方法只有一个参数,而且这个参数类型可以推导出来,那么连括号都可以省略。event -> event.getWhen()

即使表达式没有参数,仍然要提供空括号,就像无参方法一样。() -> {for(int i=0;i<10;i++ )out.print("*");}

下面是一个可以按照字符串长度排序的代码段:

var array = new String[]{"a", "abe", "abdcd", "ab"};
Arrays.sort(array, (String first, String second) -> Integer.compare(first.length(), second.length()));
System.out.println(Arrays.toString(array));

因为可以根据array类型推导出lambda表达式的类型,因此String可以省略。

var array = new String[]{"a", "abe", "abdcd", "ab"};
Arrays.sort(array, (first, second) -> Integer.compare(first.length(), second.length()));
System.out.println(Arrays.toString(array));

无需指定lambda表达式的返回值,它总可以根据上下文确定。如果要写返回值,要保证每一个分支都要有返回语句。

var array = new String[]{"a", "abe", "abdcd", "ab"};
Arrays.sort(array, (first, second) -> {
    var f = first.length();
    var se = second.length();
    if(f > se) return 1;
    else if(f < se) return -1;
    else return 0;
});
System.out.println(Arrays.toString(array));

6.2.2 函数式接口

Java有很多封装代码块的接口,如ActionListener或Comparator。lambda表达式与这些接口是兼容的。对于只有个抽象方法的接口,需要这种接口对象时,就可以提供一个lambda表达式。这种接口称为函数式接口。

Conceptually, a functional interface has exactly one abstract method. Since default methods have an implementation, they are not abstract. If an interface declares an abstract method overriding one of the public methods of java.lang.Object, that also does not count toward the interface’s abstract method count since any implementation of the interface will have an implementation from java.lang.Object or elsewhere.
Note that instances of functional interfaces can be created with lambda expressions, method references, or constructor references.

lambda表达式使用的时候注意参数列表要和函数式接口里面的抽象函数保持一致

6.2.3 方法引用

方法引用类似于lambda表达式,不是一个对象。它为一个类型为函数式接口的变量赋值时会生成一个对象。方法引用提供了可以重用别的类的方法可能性。

方法引用要用::运算符分隔方法名和对象或类名。主要有3中情况:

  1. object::instanceMethod:等价于向方法传递参数的lambda表达式
  2. Class::instanceMethod:第1个参数会成为方法的隐式参数
  3. Class:staticMethod:所有参数都传递到静态方法

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

在第2种情况下,第1个参数会成为方法的隐式参数。例如,String::compareToIgnoreCase等价于(x,y)->x.compareToIgnoreCase(y)。

在第3种情况下,所有参数都传递到静态方法。例如,Math::pow等价于(x,y)->Math.pow(x, y)。

6.2.4 构造器引用

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

构造器引用多用于流中……(待补充)

6.2.5 变量作用域

lambda表达式,可以访问他所在域有效的变量。但是要求变量必须是事实最终变量,即这个变量的值在初始化之后不会再改变了。

public static void repeatMessage(String text, int delay) {
    for (int i = 1; i <= delay; i++) {
        ActionListener listener = event -> {
        	System.out.println(text); //这里text是String类型,一旦引用就不会改变了,因此lambda表达式可以捕获。但是i是改变的,因此不可以捕获。
            System.out.println(this.toString());   //这里的this指的是创建repeatMessage这个方法的类。
        ...
    }
    }
}

注意,lambda表达式捕获之后也不可以修改值,它只是可以读取变量。

6.2.6 处理lambda表达式

lambda表达式主要是用在函数式接口的传递上。函数式接口多用在接口回调。所以在编写程序的时候自己写接口回调的时候都可以定义一个函数式接口,然后在调用这个接口的时候传递lambda表达式。除此之外也可以用系统提供的函数式接口,但是注意自己写函数式接口的时候要加上@FunctionInterface注解,告诉编译器这是一个函数式接口。

6.3 内部类

  1. Java的非static内部类对象会有一个外部类实例化后的对象的指针,通过这个指针Java内部类可以直接访问外部类的数据。

  2. 只有内部类可以声明成private,常规类只有包可见或者公共可见性。

  3. 内部类中声明的所有静态字段必须都是final,并初始化为一个编译时常量。

  4. 内部类不能有static方法。

  5. 比普通内部类更进一步的局部内部类不仅可以访问外部类的字段,还可以访问局部变量。但是要求那些局部变量必须是事实最终变量

  6. 除了普通内部类和局部内部类,Java还有匿名内部类。匿名内部类不能有构造器,但是可以提供一个对象初始化块。

    var count = new Person("Dracula"){	//这创建的是一个Person的匿名子类
        { initialization }
        ...
    }
    
  7. 只要内部类不需要访问外围类对象,就应该声明成静态(static)内部类。与常规内部类不同,静态内部类可以有静态字段和方法。在接口中声明的内部类自动是static和public。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值