更待何时【编程】

第二章 lambda表达式,lambda表达式

一、处理匿名内部类

1、Runnable接口

1         new Thread(new Runnable() {
2             public void run() {
3                 System.out.println("hello world!!!");
4             }
5         }).start();
6         
7         // lambda
8         new Thread(()->System.out.println("hello lambda!!!")).start();

说明:

  • 上边的方式是原本匿名内部类方式
  • 下边的方法是lambda表达式方式

lambda基本语法:(形参列表)->{方法体}

注意:

  • 形参列表:(String x, String y)这样的基本形式
    • 如果没有形参,使用”()”,如上
    • 如果参数类型可以被推断出来,使用(x,y)
    • 如果是单个参数且类型可以被推断出来,直接使用x
  • 方法体:如果只有一句话,可以去掉”{}”,如上

2、Comparator接口

 1         List<String> strList = Arrays.asList("zhaojigang","nana","tianya");
 2         //原来的方式
 3         Collections.sort(strList, new Comparator<String>() {
 4             @Override
 5             public int compare(String s1, String s2) {
 6                 return s1.compareTo(s2);
 7             }
 8         });
 9         
10         //lambda
11         Collections.sort(strList, (s1, s2)->s1.compareTo(s2));

说明:这里的s1和s2就被根据comparator的泛型和strList的泛型推断出了类型为String,可以省略参数类型。

二、方法引用

1         //lambda
2         Collections.sort(strList, (s1, s2)->s1.compareTo(s2));
3         //方法引用
4         Collections.sort(strList, String::compareTo);
5         //lambda
6         strList.forEach(x->System.out.println(x));
7         //方法引用
8         strList.forEach(System.out::println);

说明:

  • 前两个语句效果一样,后两个效果一样,自己比对方法引用与lambda的哪些部分等价
  • 集合类的foreach方法就是增强型for循环的再增强。

注意:

  • 方法引用的语法
    • 对象::实例方法=>等价于”提供方法参数的lambda表达式”
    • 类::静态方法=>等价于”提供方法参数的lambda表达式”
      • eg. System.out::println等价于x->System.out.println(x)
    • 类::实例方法=>第一个参数是执行方法的对象
      • eg. String::compareTo等价于(s1, s2)->s1.compareTo(s2)

三、局部变量

lambda操作的局部变量必须是final型的,即:在lambda表达式所使用到的局部变量(方法体内的变量或形参)只能被读取,不能被改变。

注意:列出这条约束的原因是防止线程不安全,可能会有疑问,局部变量是方法私有的,怎么会有线程安全问题?

解释:假设我在该方法体内,启动了一个线程并使用lambda表达式去操作一个局部变量count(注意该变量并没有在lambda中进行声明,但是lambda却可以用,这就是”闭包“),而在该线程外且在该方法体内,该方法也操作了count,这时可能就会有线程安全问题了。

四、接口的改变

java的接口也可以写实现default级别的实例方法和静态方法了。

 1 public interface LambdaInterface {
 2     //default方法
 3     default void defaultMethod(){
 4         System.out.println("xxxx");
 5     }
 6     //static方法
 7     static void staticMethod(){
 8         System.out.println("xxxx");
 9     }
10 }

1 public class TestInterface implements LambdaInterface{
2     public static void main(String[] args) {
3         LambdaInterface test = new TestInterface();
4         test.defaultMethod();//default方法测试
5         
6         LambdaInterface.staticMethod();//static方法测试
7     }
8 }

用途:当改造老的项目时,想在旧的接口中添加一些方法,但是又不想让该接口的旧的实现类去实现这些方法时,可以使用这个技巧。

lambda表达式,lambda表达式
一、处理匿名内部类 1、Runnable接口 1 new Thread( new Runnable() { 2
public void run() { 3 System.out.println(“hello world!!!…

一、处理匿名内部类

为什么要使用lambda表达式

1、Runnable接口

关于这点,网上的说法一大堆,但是这个就说一个最浅显的理由,就是简洁,使用Lambda表达式可以让你的代码看起来很简洁,去掉了一堆没有意义的代码,只留下核心的逻辑。也许你会说,我看了Lambda表达式,不但不觉得简洁,反而觉得更乱,看不懂了。那是因为我们还没有习惯,用的多了,看习惯了,就好了。

1         new Thread(new Runnable() {
2             public void run() {
3                 System.out.println("hello world!!!");
4             }
5         }).start();
6         
7         // lambda
8         new Thread(()->System.out.println("hello lambda!!!")).start();

从函数式接口说起

说明:

理解Functional Interface(函数式接口)是学习Java8
lambda表达式的关键所在。

  • 上边的方式是原本匿名内部类方式
  • 下边的方法是lambda表达式方式

函数式接口的定义其实很简单:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。

lambda基本语法:(形参列表)->{方法体}

为了让编译器帮助我们确保一个接口满足函数式接口的要求,也就是说有且仅有一个抽象方法。Java8提供了@FunctionalInterface注解。举个简单的例子,Runnable接口就是一个FI,下面是它的源代码:

注意:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
  • 形参列表:(String x, String y)这样的基本形式
    • 如果没有形参,使用”()”,如上
    • 如果参数类型可以被推断出来,使用(x,y)
    • 如果是单个参数且类型可以被推断出来,直接使用x
  • 方法体:如果只有一句话,可以去掉”{}”,如上

使用@FunctionalInterface注解并不强制要求。但是使用注解会让代码看上去更清楚。

2、Comparator接口

lambda表达式的语法糖

 1         List<String> strList = Arrays.asList("zhaojigang","nana","tianya");
 2         //原来的方式
 3         Collections.sort(strList, new Comparator<String>() {
 4             @Override
 5             public int compare(String s1, String s2) {
 6                 return s1.compareTo(s2);
 7             }
 8         });
 9         
10         //lambda
11         Collections.sort(strList, (s1, s2)->s1.compareTo(s2));

语法糖

说明:这里的s1和s2就被根据comparator的泛型和strList的泛型推断出了类型为String,可以省略参数类型。

Java中lambda表达式的格式:参数、箭头->、一个表达式。
为了演示lambda表达式的语法糖,我们通过Comparator作为例子。在Java8之前,可以借助匿名内部类来实现。

二、方法引用

public static List<String> compareTest1(){
    List<String> wordList = Arrays.asList("lianggzone", "spring", "summer", "autumn", "winter");
    wordList.sort(new Comparator<String>() {
        @Override
        public int compare(String w1, String w2) {
            return Integer.compare(w1.length(), w2.length());
        }
    });
    return wordList;        
}
1         //lambda
2         Collections.sort(strList, (s1, s2)->s1.compareTo(s2));
3         //方法引用
4         Collections.sort(strList, String::compareTo);
5         //lambda
6         strList.forEach(x->System.out.println(x));
7         //方法引用
8         strList.forEach(System.out::println);

Comparator是个函数式接口,我们可以用Lambda表达式来实现。Lambda表达式,很像一个匿名的方法,只是小括号内的参数列表和花括号内的代码被->分隔开了。

说明:

public static List<String> compareTest2(){
    List<String> wordList = Arrays.asList("lianggzone", "spring", "summer", "autumn", "winter");
    wordList.sort((String w1, String w2) -> {
        return Integer.compare(w1.length(), w2.length());
    });
    return wordList;        
}
  • 前两个语句效果一样,后两个效果一样,自己比对方法引用与lambda的哪些部分等价
  • 集合类的foreach方法就是增强型for循环的再增强。

如果一个lambda表达式的参数类型是可以被推导的,那么就可以省略它们的类型。

注意:

public static List<String> compareTest3(){
    List<String> wordList = Arrays.asList("lianggzone", "spring", "summer", "autumn", "winter");
    wordList.sort((w1, w2) -> {
        return Integer.compare(w1.length(), w2.length()); 
    });
    return wordList;        
}
  • 方法引用的语法
    • 对象::实例方法=>等价于”提供方法参数的lambda表达式”
    • 类::静态方法=>等价于”提供方法参数的lambda表达式”
      • eg. System.out::println等价于x->System.out.println(x)
    • 类::实例方法=>第一个参数是执行方法的对象
      • eg. String::compareTo等价于(s1, s2)->s1.compareTo(s2)

如果一个lambda表达式的代码块只是return后面跟一个表达式,那么还可以进一步简化。

三、局部变量

public static List<String> compareTest4(){
    List<String> wordList = Arrays.asList("lianggzone", "spring", "summer", "autumn", "winter");
    wordList.sort((w1, w2) ->  Integer.compare(w1.length(), w2.length()));
    return wordList;        
}

编程,lambda操作的局部变量必须是final型的,即:在lambda表达式所使用到的局部变量(方法体内的变量或形参)只能被读取,不能被改变。

如果某个方法只含有一个参数,并且该参数的类型可以被推导出来,你甚至可以省略小括号。

注意:列出这条约束的原因是防止线程不安全,可能会有疑问,局部变量是方法私有的,怎么会有线程安全问题?

public static List<String> compareTest5(){
    List<String> wordList = Arrays.asList("lianggzone", "spring", "summer", "autumn", "winter");
    wordList.forEach(word ->  System.out.println(word));
    return wordList;        
}

解释:假设我在该方法体内,启动了一个线程并使用lambda表达式去操作一个局部变量count(注意该变量并没有在lambda中进行声明,但是lambda却可以用,这就是”闭包“),而在该线程外且在该方法体内,该方法也操作了count,这时可能就会有线程安全问题了。

当我们要在另外一个独立线程中执行一些逻辑时,通常会将代码放在一个实现Runable接口的类的run方法中。

四、接口的改变

public static void runnableTest1(){
    Executors.newSingleThreadExecutor().execute(new Runnable() {    
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(i);
            }
        }
    });    
}

java的接口也可以写实现default级别的实例方法和静态方法了。

如果lambda表达式没有参数,你仍可以提供一对空的小括号,如同不含参数的方法。

 1 public interface LambdaInterface {
 2     //default方法
 3     default void defaultMethod(){
 4         System.out.println("xxxx");
 5     }
 6     //static方法
 7     static void staticMethod(){
 8         System.out.println("xxxx");
 9     }
10 }

1 public class TestInterface implements LambdaInterface{
2     public static void main(String[] args) {
3         LambdaInterface test = new TestInterface();
4         test.defaultMethod();//default方法测试
5         
6         LambdaInterface.staticMethod();//static方法测试
7     }
8 }
public static void runnableTest2(){
    Executors.newSingleThreadExecutor().execute(() -> {
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
        }
    });    
}

用途:当改造老的项目时,想在旧的接口中添加一些方法,但是又不想让该接口的旧的实现类去实现这些方法时,可以使用这个技巧。

注意点

  • lambda表达式执行返回类型,会根据上下文推到出来,我们不需要设置它的返回类型。
  • lambda表达式中,只有某些分支中返回值,这样是错误的。

方法引用

对象::实例对象

有些时候,lambda表达式的代码就只是一个简单的方法调用而已,遇到这种情况,lambda表达式还可以进一步简化为方法引用->操作符将方法名和对象或类的名字分隔开来。

在Java8之前,我们打印List内容,正常是这样做的。

public static void print1(){
    List<String> wordList = Arrays.asList("liang", "spring", "summer", "autumn", "winter");
    wordList.forEach(new Consumer<String>() {
        @Override
        public void accept(String word) {
            System.out.println(word);
        }
    });    
}

因为Consumer是函数式接口,我们通过lambda表达式改造下。

public static void print2(){
    List<String> wordList = Arrays.asList("lian", "spring", "summer", "autumn", "winter");
    wordList.forEach(word -> System.out.println(word));        
}

如果改造成方法引用,表达式 System.out::println, 等价于word ->
System.out.println(word)。

public static void print3(){
    List<String> wordList = Arrays.asList("lian", "spring", "summer", "autumn", "winter");
    wordList.forEach(System.out::println);    
}

类::实例对象

例如,我们不区分大小写对字符串进行排序。

public static String[] sort1(){
    String[] words = new String[]{"lianggzone", "spring", "summer", "autumn", "winter"};
    Arrays.sort(words, (x, y) -> x.compareToIgnoreCase(y));
    return words;
}

如果改造成方法引用,表达式: String::compareToIgnoreCase,等价于(x, y)
-> x.compareToIgnoreCase(y),第一参数会成为执行方法的对象。

public static String[] sort2(){
    String[] words = new String[]{"lianggzone", "spring", "summer", "autumn", "winter"};
    Arrays.sort(words, String::compareToIgnoreCase);
    return words;
}

对象::静态方法

例如,我们对集合进行排序。

public static List<Integer> sortList1(){
    List<Integer> wordList = Arrays.asList(21, 53);
    wordList.sort((w1, w2) -> Integer.compare(w1, w2));
    return wordList;
}

如果改造成方法引用,表达式: Integer::compare,等价于(w1, w2) ->
Integer.compare(w1, w2),第一参数会成为执行方法的对象。

public static List<Integer> sortList2(){
    List<Integer> wordList = Arrays.asList(21, 53);
    wordList.sort(Integer::compare);
    return wordList;
}

构造器引用

// lambda
words.stream().map(word -> {
    return new StringBuilder(word);
});
// constructor reference
words.stream().map(StringBuilder::new);

变量作用域

在Java8之前, 内部类只能访问final的局部变量
,为了适应lambda表达式,Java8放宽了这种限制,只要变量实际上不可变(effectively
final)就可以。

public static void effectivelyFinal(){
    int a = 100;
    Executors.newSingleThreadExecutor().execute(() -> {
        System.out.println(a);
    });    
}

在lambda表达式中,被引用的变量的值不能被修改。做出这个约束的是有原因的,因为lambda表达式中的变量不是线程安全的。

接口的静态方法

从Java8开始,接口也可以有静态方法了。有了这个语法,我们就可以把和接口相关的帮助方法直接定义在接口里。

比如Function接口就定义了一个工厂方法indentity()。表示,
一个功能接口,可以作为赋值的目标一个lambda表达式或方法参考。

T -函数输入的类型
R -函数的结果的类型

public interface Function<T, R> {
  static <T> Function<T, T> identity() {
      return t -> t;
  }
}

事实上,Java8中,很多接口已经添加了静态方法,例如,Java8中的一个使用案例

public interface Path {
  public static Path get(String first, String... more) {
      return FileSystems.getDefault().getPath(first, more);
  }
}

所以,重要的事情,再说一遍哟,从Java8开始,接口也可以有静态方法了。