美文网首页
java8 lambda表达式(二)

java8 lambda表达式(二)

作者: lconcise | 来源:发表于2018-07-04 20:13 被阅读12次

    继续上一篇

    三 lambda表达式的类型

    我们都知道,Java是一种强类型语言。所有的方法参数都有类型,那么lambda表达式是一种什么类型呢?

    View.OnClickListener listener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //...
        }
    };
            
    button.setOnClickListener(listener);
    

    如上所示,以往我们是通过使用单一方法的接口来代表一个方法并且重用它。
    在lambda表达式中,仍使用的和之前一样的形式。我们叫做函数式接口(functional interface)。如我们之前button的点击响应事件使用的View.OnClickListener就是一个函数式接口。

    public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
        ...
        
        public interface OnClickListener {
            void onClick(View v);
        }
        ...
    }
    

    那究竟什么样的接口是函数式接口?
    函数式接口是只有一个抽象方法的接口,用作表示lambda表达式的类型。 比如Java标准库中的java.lang.Runnable和java.util.Comparator都是典型的函数式接口。java 8提供 @FunctionalInterface作为注解,这个注解是非必须的,只要接口符合函数式接口的标准(即只包含一个方法的接口),虚拟机会自动判断,但最好在接口上使用注解@FunctionalInterface进行声明,以免团队的其他人员错误地往接口中添加新的方法。举例如下:

    @FunctionalInterface
    public interface Runnable { void run(); }
    
    public interface Callable<V> { V call() throws Exception; }
    
    public interface ActionListener { void actionPerformed(ActionEvent e); }
    
    public interface Comparator<T> { 
        int compare(T o1, T o2); 
        
        boolean equals(Object obj); 
    }
    

    注意最后这个Comparator接口。它里面声明了两个方法,貌似不符合函数接口的定义,但它的确是函数接口。这是因为equals方法是Object的,所有的接口都会声明Object的public方法——虽然大多是隐式的。所以,Comparator显式的声明了equals不影响它依然是个函数接口。

    Java中的lambda无法单独出现,它需要一个函数式接口来盛放,lambda表达式方法体其实就是函数接口的实现。即Lambda表达式不能脱离目标类型存在,这个目标类型就是函数式接口
    看下面的例子:

    String []datas = new String[] {"peng","zhao","li"};
    Comparator<String> comp = (v1,v2) -> Integer.compare(v1.length(), v2.length());
    Arrays.sort(datas,comp);
    Stream.of(datas).forEach(param -> {System.out.println(param);}); 
    

    根据上面代码,Lambda表达式被赋值给了comp函数接口变量。

    你可以用一个lambda表达式为一个函数接口赋值:

    Runnable r1 = () -> {System.out.println("Hello Lambda!");};
    

    然后再赋值给一个Object:

    Object obj = r1;
    

    但却不能这么干

    Object obj = () -> {System.out.println("Hello Lambda!");}; // ERROR! Object is not a functional interface!
    

    必须显式的转型成一个函数接口才可以:

    Object o = (Runnable) () -> { System.out.println("hi"); }; // correct
    

    一个lambda表达式只有在转型成一个函数接口后才能被当做Object使用。所以下面这句也不能编译:

     System.out.println( () -> {} ); //错误! 目标类型不明
    

    必须先转型

    System.out.println( (Runnable)() -> {} ); // 正确
    

    假设你自己写了一个函数接口,长的跟Runnable一模一样:

    @FunctionalInterface
    public interface MyRunnable {
        public void run();
    }
    

    那么

    Runnable r1 =    () -> {System.out.println("Hello Lambda!");};
    MyRunnable2 r2 = () -> {System.out.println("Hello Lambda!");};
    

    都是正确的写法。这说明一个lambda表达式可以有多个目标类型(函数接口),只要函数匹配成功即可。但需注意一个lambda表达式必须至少有一个目标类型
    JDK预定义了很多函数接口以避免用户重复定义。最典型的是Function:

    @FunctionalInterface
    public interface Function<T, R> {  
        R apply(T t);
    }
    

    还有一个Predicate,用来判断某项条件是否满足。经常用来进行筛滤操作:

    @FunctionalInterface
    public interface Predicate<T> {
        boolean test(T t);
    }
    

    综上所述,一个lambda表达式其实就是定义了一个匿名方法,只不过这个方法必须符合至少一个函数接口。

    lambda表达式可使用的变量

    举例

    @Test
    public void test1(){
        //将为列表中的字符串添加前缀字符串
        String waibu = "lambda :";
        List<String> proStrs = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
        List<String>execStrs = proStrs.stream().map(chuandi -> {
            Long zidingyi = System.currentTimeMillis();
            return waibu + chuandi + " -----:" + zidingyi;
        }).collect(Collectors.toList());
        
        execStrs.forEach(System.out::println);
    }
    

    输出

    lambda :Ni -----:1498722594781
    lambda :Hao -----:1498722594781
    lambda :Lambda -----:1498722594781
    

    变量waibu :外部变量

    变量chuandi :传递变量

    变量zidingyi :内部自定义变量

    lambda表达式可以访问给它传递的变量,访问自己内部定义的变量,同时也能访问它外部的变量。不过lambda表达式访问外部变量有一个非常重要的限制:变量不可变(只是引用不可变,而不是真正的不可变)

    当在表达式内部修改waibu = waibu + " ";时,IDE就会提示你:

    Local variable waibu defined in an enclosing scope must be final or effectively final
    

    编译时会报错。因为变量waibu被lambda表达式引用,所以编译器会隐式的把其当成final来处理。

    以前Java的匿名内部类在访问外部变量的时候,外部变量必须用final修饰。现在java8对这个限制做了优化,可以不用显示使用final修饰,但是编译器隐式当成final来处理

    lambda表达式作用域

    总体来说,Lambda表达式的变量作用域与内部类非常相似,只是条件相对来说,放宽了些,以前内部类要想引用外部类的变量,必须像下面这样

    final String[] datas = new String[] { "peng", "Zhao", "li" };
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(datas);
        }
    }).start();
    

    将变量声明为final类型的,现在不用了,在Java 8中可以这样写代码

    String []datas = new String[] {"peng","Zhao","li"};
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(datas);
        }
    }).start();
    

    也可以这样写

    new Thread(() -> System.out.println(datas)).start();
    

    看了上面的两段代码,能够发现一个显著的不同,就是Java 8中内部类或者Lambda表达式对外部类变量的引用条件放松了,不要求强制的加上final关键字了,但是Java 8中要求这个变量是effectively final。What is effectively final?

    Effectively final就是有效只读变量,意思是这个变量可以不加final关键字,但是这个变量必须是只读变量,即一旦定义后,在后面就不能再随意修改,如下代码会编译出错,如下
    ···
    String []datas = new String[] {"peng","Zhao","li"};
    datas = null;
    new Thread(() -> System.out.println(datas)).start();
    ···
    Java中内部类以及Lambda表达式中也不允许修改外部类中的变量,这是为了避免多线程情况下的race condition

    lamdba 表达式中的this概念

    在lambda中,this不是指向lambda表达式产生的那个对象,而是声明它的外部对象。
    例如:

    package com.demo;
    
    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Collectors;
    
    public class WhatThis {
        
        public void whatThis(){
            //转全小写
             List<String> proStrs = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
            List<String> execStrs = proStrs.stream().map(str -> {
                 System.out.println(this.getClass().getName());
                 return str.toLowerCase();
            }).collect(Collectors.toList());
            
            execStrs.forEach(System.out::println);
        }
        
        public static void main(String[] args) {
            WhatThis wt = new WhatThis();
            wt.whatThis();
        }
    
    }
    

    输出:

    com.wzg.test.WhatThis
    com.wzg.test.WhatThis
    com.wzg.test.WhatThis
    ni
    hao
    lambda
    

    相关文章

      网友评论

          本文标题:java8 lambda表达式(二)

          本文链接:https://www.haomeiwen.com/subject/rjsyuftx.html