Lambda表达式

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);
}

 

posted on 2016-06-12 14:27  多看多学  阅读(2912)  评论(0编辑  收藏  举报

导航