java8 lambda表达式

来源:互联网 发布:ubuntu ssh服务安装包 编辑:程序博客网 时间:2024/06/14 22:25

     为什么要用lambda表达式呢,有什么用,其实就是简化代码。

     举个例子:对List排序

class A{private int num=0;A(int a){num=a;}public int getNum(){return num;}}

   List<A> list  = new ArrayList<A>();   list.add(new A(5));   list.add(new A(4));   list.add(new A(8));   list.add(new A(2));<!--定义list-->   Comparator<A> com1 = new Comparator<A>() {<!--第一种方法-->public int compare(A o1, A o2) {return o1.getNum()-o2.getNum();}   };   list.sort(com1);   list.sort(      <!--第二种方法-->new Comparator<A>() {public int compare(A o1, A o2) {return o1.getNum()-o2.getNum();}   }   );   list.sort((A o1,A o2)->o1.getNum()-o2.getNum());<!--lambda表达式-->   list.sort((o1,o2)->o1.getNum()-o2.getNum());<!--根据类型推断可以去掉参数的类型-->   list.sort(Comparator.comparing(A::getNum));<!--方法引用-->   import static java.util.Comparator.comparing;   list.sort(comparing(A::getNum));<!---静态导入后可以更简单->   list.forEach(e->System.out.println(e.getNum()));<!--如果参数只有一个类型推断时,可以去掉()-->
      通过上面可以看到lambda表达式可以简化代码,那么怎么用呢?

      lambda表达式可以理解为一个匿名函数 ,没有名称,有参数,函数主题,返回值,可能还有一个可以抛出的异常列表 。

     lambda表达式基本语法:([parameters,...]) -> expression 或者 ([parameters,...]) -> {statements;...}

     例如:

     () -> {}  <!--表示没有参数,返回void,类似 public void run(){}-->     (String s) -> s.length   <!--表示有参数s,返回s的长度。return被隐藏了。-->     (int a,int b) -> {return a+b;} <!--表示有两个参数a,b,返回a+b的值,有花括号返回时一定要有return-->     () -> {      System.out.println("hello");      System.out.println("hi");     }   <!--表示没有参数,但是有两个输出语句,返回void-->

     注意:有花括号时,返回一定要有return,不然报错。没有花括号返回时不要加return,不然报错。

     lambda表达式可以在函数式接口上使用,函数式接口就是只定义一个抽象方法的接口(哪怕有很多默认方法,只要接口只定义了一个抽象方法,它就仍然是一个函数式接口。)

public interface Comparator<T> {        <!--是函数式接口-->int compare(T o1, T o2);}public interface Runnable{   <!--是函数式接口-->void run();}public interface Callable<V>{   <!--是函数式接口-->V call();}public interface R extends Runnable{   <!--不是函数式接口,因为有两个抽象方法-->void r();}public interface A {       <!--是函数式接口,默认方法不影响,-->void a();default void b(){System.out.println("bbbbb");}}
      Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。

      Runnable r1 = ()-> System.out.println("r1");      Runnable r2 = new Runnable() {@Overridepublic void run() {System.out.println("r2");}      };        new Thread(r1).start();      new Thread(r2).start();      new Thread(()->System.out.println("runnable")).start();
     函数式接口上都有@FunctionalInterface 标注。例如:

@FunctionalInterfacepublic interface Runnable {    /**     * When an object implementing interface <code>Runnable</code> is used     * to create a thread, starting the thread causes the object's     * <code>run</code> method to be called in that separately executing     * thread.     * <p>     * The general contract of the method <code>run</code> is that it may     * take any action whatsoever.     *     * @see     java.lang.Thread#run()     */    public abstract void run();}

       简单说明一下lambda表达式怎么具体使用,首先lambda表达式是函数式接口的一个实例,是实现接口的。选择要实现的接口后,看那个接口的抽象方法,把方法的参数写到()里面,没有就不写直接一个()。然后看方法有什么返回值,把具体实现代码写到->的后面,返回方法的返回值。实现代码很多可以用{}。所以就可以写出(A a,B b)->{...}这样的lambda表达式。


       函数式接口的抽象方法的签名叫做函数描述符。例如runnable接口的方法public void run(){}, 这个方法没有参数,没有返回值,所以可以用()->void表示。

      lambda表达式的签名也是一样,()->System.out.println("hello"),表示没有参数,没有返回值,也就是()->void。

      java自带的java.util.function里面有很多函数式接口。例如Predicate<T>,Consumer<T>,Function<T,R>等。他们的函数描述符为:

Predicate<T>    (T)->boolean   //参数T,返回booleanConsumer<T>   (T)->void      //参数T,没有返回Function<T,R>    (T)->R        //参数T,返回RSupplier<T>        ()->T       //没有参数,返回TUnaryOperator<T>   (T)->T  //传入T类型,返回T类型BinaryOperator<T> (T,T)->T  //传入两个T类型,返回一个T类型。BiPredicate<L,R> (L,R)->boolean //传入L,R,返回booleanBiConsumer<T,U> (T,U)->void  //传入T,U,没有返回BiFunction<T,U,R> (T,U)->R   //传入T,U,返回R
      如果传入的参数是基本类型,那就只能使用基本类型的包装类型了,但是这样会装箱拆箱。如果输入输出都是基本类型,那么使用包装类型很不好,所以就有了IntPredicate,IntConsumer,IntFunction<R>这种接口,使用这些接口不用装箱拆箱。Function接口还有针对输出参数类型的变种: ToIntFunction<T>、 IntToDoubleFunction等。

IntPredicate   LongPredicate   DoublePredicateIntConsumer    LongConsumer    DoubleConsumerIntFunction<R>  IntToDoubleFunction  IntToLongFunction  LongFunction<R>   LongToDoubleFunctionLongToIntFunction  DoubleFunction<R>  ToIntFunction<T>  ToDoubleFunction<T>   ToLongFunction<T>BooleanSupplier  IntSupplier   LongSupplier  DoubleSupplierIntUnaryOperator   LongUnaryOperator    DoubleUnaryOperatorIntBinaryOperator  LongBinaryOperator   DoubleBinaryOperatorObjIntConsumer<T>  ObjLongConsumer<T>ObjDoubleConsumer<T>ToIntBiFunction<T,U>  ToLongBiFunction<T,U>  ToDoubleBiFunction<T,U>
     使用上面的接口可以解决参数是基本类型的问题,避免装箱拆箱。下面是使用函数式接口的一些例子:

布尔表达式 (List<String> list) -> list.isEmpty()  Predicate<List<String>>创建对象     () -> new Apple(10)            Supplier<Apple>消费一个对象   (Apple a) ->System.out.println(a.getWeight())    Consumer<Apple>从一个对象中选择/提取  (String s) -> s.length()   Function<String, Integer>或ToIntFunction<String>合并两个值       (int a, int b) -> a * b         IntBinaryOperator比较两个对象 (Apple a1, Apple a2) ->a1.getWeight().compareTo(a2.getWeight())  Comparator<Apple>或BiFunction<Apple, Apple, Integer>或 ToIntBiFunction<Apple, Apple>
      好的,现在就可以根据自己的实际情况来选择接口实现,或者自定义函数式接口来实现。例如要得到list里面A的num值大于5的A,组合成另一个list返回。分析一下只要判断A的num是否大于5就行了,那就是(A a)->boolean,所以选择predicate<A>这个接口。

public class Test {   public static void main(String args[]) throws Exception {     List<A> list  = new ArrayList<A>();   list.add(new A(5));   list.add(new A(4));   list.add(new A(8));   list.add(new A(6));   List<A> list2 = getList(list, a->a.getNum()>5);   list2.forEach(e->System.out.println(e.getNum()));    }     public static List<A> getList(List<A> list,Predicate<A> p){   List<A> list2 = new ArrayList<A>();   for(A a:list){   if(p.test(a)){   list2.add(a);   }   }   return list2;     }}class A{private int num=0;A(int a){num=a;}public int getNum(){return num;}}
     通过上面的写法就可以得到num大于5的集合了,而且这样写的好处就是以后如果判断条件改了,不是num大于5而是num大于5,小于10,直接在lambda表达式里改代码就行了,不用改getList()这个方法。

       已知的函数式接口都不允许抛出受检异常(checked exception)。如果你需要Lambda表达式来抛出异常,有两种办法:定义一个自己的函数式接口,并声明受检异常,或者把Lambda包在一个try/catch块中。   

@FunctionalInterfacepublic interface A {<!--自定义函数式接口-->String process(B b) throws IOException;}Function<String, Integer> f = b -> { <!--因为Function没有抛出异常,所以可以使用try/catch-->try {return b.length()/0;}catch(Exception e) {throw e;}};

     通过上面可以知道lambda 表达式怎么用,但是怎么知道lambda表达式是不是正确的呢?

     类型检查,这个是通过上下文来检查的。例如上面getList(list,a->a.getNum()>5)。

    1.首先你要找出getList方法的声明。

    2.知道了它是Predicate<A>类型(目标类型)的对象。
    3.Predicate<A>是一个函数式接口,定义了一个叫作test的抽象方法。

    4.test方法描述了一个函数描述符,它可以接受一个A,并返回一个boolean。

    5. 然后判断lambda表达式是不是符合接受一个A,并返回一个boolean。

    注意:如果Lambda表达式抛出一个异常,那么抽象方法所声明的throws语句也必须与之匹配。

    有可能一种lambda表达式可以匹配多种接口的情况,例如(Integer a,Integer b)->a-b;匹配所有函数描述符为(Integer,Integer)->int的函数式接口。

   特殊的void兼容规则:

          如果一个Lambda的主体是一个语句表达式, 它就和一个返回void的函数描述符兼容。

// Predicate返回了一个booleanPredicate<String> p = s -> list.add(s);//相当于{return list.add(s)}// Consumer返回了一个voidConsumer<String> b = s -> list.add(s); //相当于{list.add(s); return;}

           上面两中都是合法的,尽管Listadd方法返回了一个boolean 。如果不是语句表达式就不行了,例如:

Consumer<String> b = s -> "hello"; 。

   

    类型推断:

           Java编译器会从上下文(目标类型)推断出用什么函数式接口来配合Lambda表达式,这样做的好处在于,编译器可以了解Lambda表达式的参数类型,这样就可以在Lambda语法中省去标注参数类型。 

           例如: Comparator<Integer> comparator = (Integer a,Integer b)->a-b;
               Comparator<Integer> comparator2 = (a,b)->a-b;

     注意:Lambda仅有一个类型需要推断的参数时,参数名称两边的括号也可以省略。 例如要输出一个String类型的值。(String s)->System.out.println(s)等价于s->System.out.println(s)。

     lambda表达式还可以使用外部的变量。

public class Test {static int num=5;   public static void main(String args[]) throws Exception {    A a = new A(10);  new Thread(()->System.out.println(a.getNum())).start();<!--使用实例变量-->  new Thread(()->System.out.println(num)).start();<!--使用静态变量-->  int n =10;  new Thread(()->System.out.println(n)).start();  <!--使用局部变量-->   }  }class A{private int num=0;A(int a){num=a;}public int getNum(){return num;}}
      如果访问局部变量,那么局部变量必须显式声明为final,或者实际上是final 

    

原创粉丝点击