【JAVA核心技术】第6章 接口、lambda表达式与内部类

 

接口

  • 在Java中,接口不是类,而是对希望符合这个接口的类的一组需求,因而也不能使用new实例化一个接口,但是却能声明接口的变量,接口变量必须引用实现了这个接口的类对象。eg: 

1 Comparable x;    //ok
2 x = new Employee;    //Employee 是 Comprable接口的一个实现类
  • 接口绝不会有实例字段,但是可以允许存在常量。 Java 8 之后允许在接口中提供简单方法,但这些方法不允许引用实例字段——接口没有实例。

  • 接口允许扩展。

1 public interface Powered extends Moveable{
2         double milesPerGallon();
3         double SPEED_LIMIT = 90;    //接口中字段默认是public static final,不必多余标记。
4 }
  • 接口与抽象类的区别:  

   使用抽象类表示通用属性存在一个严重的问题:每个类只能扩展一个类,如:

1 class Employee extends Person, Comparable    //Error
2 class Employee extends Person implements Comparable    //Ok

lambda表达式

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

  • Lambda 规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法。

    jdk 8 中有另一个新特性:default, 被 default 修饰的方法会有默认实现,不是必须被实现的方法,所以不影响 Lambda 表达式的使用。

  • lambda 表达式只能引用值不会改变的变量。

  • 如果在lambda中引用一个变量,而这个变量可能在外部改变,这也是不合法的。如:

1 for ( int i = 1; i <= 100; i++){
2     ActionListener listener = event ->
3         {
4                 System.out.println( i );    //错误,无法引用变化的i
5         };
6         new Timer(1000, listener).start();
7 }
  • lambda表达式形式:参数,箭头(->),表达式。如果无法用一个表达式完成,可以将要实现的代码放在{}中,并包含显式的return语句,如:

1 (String first, String second) -> 
2     {
3         if (first.length() < second.length())    return -1;
4         else if (first.length() > second.length())    return 1;
5         else    return 0;
6 }

        即使lambda表达式没有参数,也要提供空括号,eg:

1 ( ) -> { for (int i = 0; i < 100; i++) System.out.println( i ); }
        如果可以推断出lambda表达式的参数类型,则可以忽略其类型,eg:
1 Comprator<String> comp = (first, second) //编译器可以推导出first和second必然是字符串
2     -> first.length() - second.length();

        如果lambda表达式只有一个参数,并且可以推断出参数类型,那么甚至可以省略小括号,eg:

1 ActionListener listener = event ->
2         System.out.println("This is a listener");

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

函数式接口

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

  • 如Arrays.sort方法,其第二个参数需要一个Comparator实例,Comparator就是一个只有一个方法的接口,所以可以提供一个lambda表达式,eg:

1 Arrays.sort(words, (first, second) -> first.length() - second.length() );
Java API中提供的常用的函数式接口

image.png

方法引用

方法引用可以在某些条件成立的情况下,更加简化lambda表达式的声明方法引用语法格式有以下三种:

  • objectName::instanceMethod

  • ClassName::staticMethod

  • ClassName::instanceMethod

前两种方式类似,等同于把 lambda 表达式的参数直接当成 instanceMethod/staticMethod 的参数来调用。比如 System.out::println 等同于 x->System.out.println(x);Math::max 等同于 (x, y)->Math.max(x,y) 。

最后一种方式,等同于把lambda表达式的第一个参数当成 instanceMethod 的目标对象,其他剩余参数当成该方法的参数。比如 String::toLowerCase 等同于 x -> x.toLowerCase()。

 1 //Funciton
 2 Lambda表达式: (Apple a) -> a.getWeight() 
 3 等价的方法引用: Apple::getWeight
 4 //Conusmer
 5 Lambda表达式: () -> Thread.currentThread().dumpStack() 
 6 等价的方法引用: Thread.currentThread()::dumpStack
 7 //BiFunction
 8 Lambda表达式: (str, i) -> str.substring(i)
 9 等价的方法引用: String::substring
10 //Function
11 Lambda表达式: (String s) -> System.out.println(s)
12 等价的方法引用: System.out::printl

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

1 s -> s.length == 0

这里有一个方法调用,但是还有一个比较,因而这里不能使用方法引用。

构造器引用

 

构造器引用语法如下:ClassName::new,把lambda表达式的参数当成ClassName构造器的参数 。例如BigDecimal::new等同于x->new BigDecimal(x)。

1 //无参构造函数
2 Supplier<Apple> c1 = () -> new Apple(); 
3 Supplier<Apple> c1 = Apple::new;
4 //一元构造函数
5 Function<Integer, Apple> c2 = (weight) -> new Apple(weight);
6 Function<Integer, Apple> c2 = Apple::new;
7 //二元构造函数
8 BiFunction<Integer, String, Apple> c3 =(weight, color) -> new Apple(weight, color);
9 BiFunction<Integer, String, Apple> c3 = Apple::new

内部类

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

  • 内部类可以对同一个包中的其他类隐藏

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

  • 使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。在实际问题中我们会遇到一些接口无法解决或难以解决的问题,此时我们可以使用内部类继承某个具体的或抽象的类,间接解决类无法多继承引起的一系列问题。

  • 内部类可以用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立。

  • 内部类并没有令人迷惑的“is-a”关系,他就是一个独立的实体。

  • 内部类提供了更好的封装,除了该外围类,其他类都不能访问

  • 创建内部类对象的时刻并不依赖于外围类对象的创建。

image.png

  • 方法(局部)内部类访问局部变量的限制:

        1. 直接被final修饰的变量。

        2. 已被赋值且始终未改变的变量(有且仅有赋值一次),引用指向不能改变。JDK8以前(不包括8)只能访问被final修饰的变量。

  • 匿名内部类没有类名,所以没有构造器。实际上,构造参数要传递给超类构造器。

            【注意】只要内部类实现一个接口,就不能有任何构造参数

  • 尽管匿名内部类不能有构造器,但可以提供一个对象初始化块。

 1 /**
 2 *匿名内部类
 3 */
 4 public class Outer {
 5 
 6     public static IAnimal getInnerInstance(String speak){
 7         return new IAnimal(){
 8             @Override
 9             public void speak(){
10                 System.out.println(speak);
11             }};
12         //注意上一行的分号必须有
13     }
14     
15     public static void main(String[] args){
16     //调用的speak()是重写后的speak方法。
17         Outer.getInnerInstance("小狗汪汪汪!").speak();
18     }
19 }
  • 接口中声明的内部类自动是static和public。

  • 与常规内部类不同,静态内部类可以有静态方法和字段。


其他参考资料:

[1] Lambda表达式详解

[2] 浅谈Java内部类

posted @ 2020-08-19 11:19  林深处见鹿  阅读(202)  评论(0编辑  收藏  举报