9、Lambda表达式
java是强类型语言,必须指定类型
(String first, String second)->first.length()-second.length();
如果lambda表达式的表达体执行一个无法用一个表达式表示的计算,那么用{}包裹代码并明确些上return语句。
1 (String first, String second) -> {
2 int difference = first.length() < second.length(); 3 if(difference <0) return -1; 4 else if(difference >0) return 1; 5 else return 0; 6 }
如果lambda表达式没有参数,则写一个空的小括号。
1 Runnable task = () -> { for(int i=0;i<1000;i++) doWork();}
如果lambda表达式的参数类型可以被对到出来,则可以省略类型。
Comparator<String> comp = (first, second) -> first.length() - second.lenght();
这里因为lambda表达式被赋值给String比较器,编译器可以推断出类型。如果某方法只有一个参数,且参数类型可推导,甚至可以省略小括号。
1 EventHandler<ActionEvent> listener = event -> System.out.println();
永远不要为lambda表达式指定返回类型,编译器会从lambda表达式推断出类型,并检查返回类型是否与期望的类型匹配。
10、函数式接口
无论何时,当你期望只有一个抽象方法的接口对象时,就可以提供一个lambda表达式。这样的接口被称为函数式接口
Arrays.sort方法。该方法的第二个参数要求一个Comparator接口的实例。直接提供一个lambda:
1 Array.sort(words,(first, second) -> first.length()-second.length());
表达式背后,Arrays.sort方法的第二个参数变量接受一个实现了Comparator<String>接口的类的实例。调用该对象的compare方法会执行lambda表达式中的代码。这些对象和类的管理完全依赖于实现,并是高度优化的。
在Java中,lambda表达式只能将其放入类型为函数式接口的变量中,这样他就被转换为该接口的实例。
不能将lambda表达式复制给Object的变量,因为Object是类,不是函数式接口。
11、方法引用
当传递给其他代码的操作已经有实现的方法了,可以使用方法引用
1 Arrays.sort(Strings, (x,y) -> x.compareToIgnoreCase(y));
2 //也可以传入方法表达式:
3 Arrays.sort(strings, String::compareToIgnoreCase);
Object定义了inNull方法,返回x==null的值。
list.removeIf(Object::isNull);
1 list.forEach(x-> System.out.println(x));
2 //可以写成
3 list.forEach(System.out::println);
操作符::将方法名与类或对象分隔开
1、类::实例方法 第一个参数变成方法的接受者,其他的参数也传递给该方法。
String::compareToIgnoreCase 等同于 (x,y)->x.compareToIgnoreCase(y)
2、类::静态方法 所有的参数传递给静态方法。
Objects::isNull 等同于 x->Objects.isNull(x)
3、对象::实例方法 在给定的对象上调用方法,并且参数传递给实例方法。
System.out::println 等同于 x->System.out.println(x)
在内部类中,可以用"外部类.this::方法名称"捕获外部类的this引用。
使用数组构造函数可以绕过java的限制,构造一个泛型数组。
Employee[] buttons = stream.toArray(Employee[]::new)
12、使用lambda表达式
1、实现延迟执行
使用lambda表达式就在于延迟执行。如果想立即执行一段代码,无需将代码封装进lambda,可以直接调用。
延迟执行的原因:(其实就是支持函数式变成的接口有哪些)
在另一个单独的线程中运行代码
多次运行代码
在算法的恰当时刻运行代码(排序中的比较操作)
当某些情况发生时运行代码(按钮被点击,数据到达)
只有在需要时才运行的代码
如:想将一个行为重复10次
repeat(10,()->System.out.println("a"));
//要选择一个函数式接口来接受lambda表达式
public static void repeat(int n, Runnalbe action){ for(int i=0;i<n;i++) action.run(); } //当action.run()被调用时,lambda表达式体被执行。
2、常用的函数式接口
大多数标准的函数式接口都有用来产生或者组装函数的非抽象方法。Predicate.isEqual(a)与a::equals相同
表3-2 为原始类型提供的函数式接口:p、q 为int、long、double 类型,P、Q 为Int、Long、Double 类型
比如可以用IntConsumer代替Consumer<Integer>
3、实现自己的函数式接口
如果想用颜色填充一张图片,用户可以提供为每个像素产生颜色的函数,标准类型中没有(int,int)->color。这时可以用BiFunction<Integer,Integer,Color>但是这样会自动装箱。
或者定义一个新的接口:
1 @FunctionalInterface
2 public interface PixelFunction{ 3 Color apply(int x, int y) 4 }
用@FunctionalInterface注解标记函数式接口,这样编译器会检查出被注释的实体是一个带有单个抽象方法的接口。JavaDoc页面也会有函数式接口的声明。
实现如下:
1 BufferedImage createImage(int width, int height, PixelFunction f){
2 BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB); 3 for(int x =0;x<width;x++){ 4 for(int y =0;y<height;y++){ 5 Color color = f.apply(x,y)//这里f就是传入的lambda表达式 6 image.setRGB(x,y,color.getRGB()); 7 } 8 retrun image; 9 } 10 } 11 12 //调用 13 BufferedImage frenchFlag = createImage(150,100, 14 (x,y)->x<50?Color.Blue:x<100?Color.WHITE:Color.Red);
13、lambda表达式的作用域
lambda表达式的方法体与嵌套代码块有相同的作用域。在lambda中不允许声明一个与局部变量同名的参数或局部变量。
int first = 0; Comparator<String> comp =(first,second) ->first.length() - second.length(); //错误:变量first已经定义了
lambda中的this代表的是创建lambda表达式方法的this参数。
1 public class Application(){ 2 public void dowork(){ 3 Runnable runner = () ->{...;Sysout(this.toString);} 4 } 5 }
this.toString是调用Application对象的toString()方法,lambda的作用域被嵌套在dowork方法中。
14、访问来自闭合作用域的变量
在lambda中访问来自闭合方法或类的变量。
1 public static void repeatMessage(String text,int count){ 2 Runnable r = () -> { 3 for(int i =0;i<count;i++){ 4 System.out.println(text); 5 } 6 }; 7 new Thread(r).start(); 8 }
lambda访问了定义在闭合域,而不是定义在表达式自身中的参数标量text
如果repeatMessage(“A”,1000)//在单独的线程中将A打印1000次
如果lambda在repeatMessage返回之后很久才运行,此时参数变量已经消失了。
15、lambda表达式的理解
lambda表达式分为三个部分:1、代码块 2、参数 3、自由变量的值(既不是参数标量,也不是代码内部定义的变量)
在repeatMessage中lambda由两个自由变量,text和count。这些变量其实已经被lambda捕获了(通过一个具体的实现细节来完成捕获工作,将lambda转变为带有一个方法的对象,这样自由变量的值就可以复制到对象的实例变量中)
闭包(closure)描述带有自由变量值得代码块的技术,在java中lambda就是闭包
lambda只能引用哪些值不会改变的变量,只能捕获变量的值,而不是变量的引用。
for(int i=0;i<n;i++){ new Thread(()->System.out.println(i)).start(); //错误,不能捕获i }
lambda只能访问来自闭合作用域的final局部变量。(同样的规则适用于被局部内部类捕获的变量)
注意:增强的for循环中的变量是有效final的,因为他的作用域是单个迭代。
for(String arg: args){ new Thread(()->System.out.println(arg)).start(); //可以捕获 }
作为“有效final”规则的结果,lambda不能改变任何捕获的变量。可以有效的防止多线程下的冲突。
禁止修改变量只是针对局部变量。如果count是实例变量或者外部类的静态变量,及时并发时结果不确定,也不会有错误报告。
//可以用长度为1的数组绕过限制 int[] counter = new int[1]; button.setOnAction(event->counter[0]++); //counter是有效final的,一直引用同意个数组。但是是线程不安全的。
16、高阶函数
处理货返回函数的函数称为高阶函数。
1、返回函数的方法
假如想对字符串进行升序或降序排序。创建一个会产生正确比较器(comparator)的方法
1 public static Comparator<String> compareInDirection(int direction){ 2 return (x,y) -> direction * x.compareTo(y); 3 }
可以传递给另一个期望这种借口的方法
Arrays.sort(friends, comparaInDirection(-1));
2、修改函数的方法
1 list.sort(reverse(compare())); 2 3 public static Comparator<Integer> compare() { 4 return (x,y) -> x.compareTo(y); 5 } 6 7 public static Comparator<Integer> reverse(Comparator<Integer> comp){ 8 return (x,y) -> comp.compare(y, x); 9 }
3、Comparator
Comparator接口有很多有用的静态方法,他们是产生comparator的高阶函数。
comparing方法接受“key提取器”函数,将类型T映射到可比较的类型。函数可以引用到被比较的对象,并且是在返回的key上进行的比较。
1 Arrays.sort(people, Comparator.comparing(Person::getName)); 2 Arrays.sort(people, Comparator.comparing(Person::getName).thenComparing(Person::getFirstName)); 3 // 根据名字长度进行排序 4 Arrays.sort(people, Comparator.comparing(Person::getName),(s,t)->s.lenght-t.length()));
为了避免装箱可以使用comparingInt
1 Arrays.sort(people,Comparator.comparingInt(p->p.getName().length()));
为了防止空指针异常,当有空对象时可以使用nullsFirst和nullsLast。使用naturalOrder和reserveOrder可以返回正序和逆序两种比较器(自动推断类型)。
1 Arrays.sort(people, comparing(Person::getMiddleName, nullsFirst(naturalOrder())));
17、局部内部类
在方法里定义的类为局部类。
//产生给定范围的无限序列随机整数 public static IntSequence randomInts(int low, int high) //因为IntSequence是个接口,所以必须返回实现该接口的某个类的对象 private static Random generator = new Random(); public static InSequence randomInts(int low, int high){ Class RandomSequence implements IntSequence{ public int next() {return low+generator.nextInt(high-low+1);} public boolean hasNext() {return true;} } return new RandomSequence(); }
局部类没有声明为public或private,因为对方法外部而言它是永远不可访问的。如果将RandomSequence转变为嵌套类,将不得不提供一个显示的构造函数,接受闭合作用域变量,但是用内部类就不用。
//现在可以使用lambda表达式 public static IntSequence randomInts(int low, int high){ return ()->low + generator.nextInt(high - low + 1); }