java8新特性lambda表达式

Lambda表达式

简介

Lambda表达式可以取代大部分匿名内部类,可以优化代码结构。

可以取代匿名内部类?什么意思呢?

在以前如果我们需要对集合排序,我们是这样做:

Integer[] arr= {3,2,1};
Arrays.sort(arr, new Comparator<Integer>() {

	@Override
	public int compare(Integer o1, Integer o2) {
		return o1-o2;
	}
});
System.out.println(Arrays.toString(arr));

使用Arrays类提供的sort方法传入一个指定排序规则的Comparator,如果我们使用匿名内部类的话,可以看到整个内部类中只用return o1-o2;语句是有用的,其他的都是多余的,为了这一句话我们多写了很多代码。那么,有了Lambda表达式后我们就可以很轻松的解决这个问题了。

java8中新增了Lambda表达式,现在我们可以这样做:

Integer[] arr= {3,2,1};
Arrays.sort(arr, (x,y)->x-y);
System.out.println(Arrays.toString(arr));

那么Lambda是如何实现的呢?我们知道sort方法需要传入一个Comparator,而Comparator是一个接口,那么我们来看看Comparator接口是怎样定义的:

@FunctionalInterface
public interface Comparator<T> {

Comparator能够支持Lambda表达式的秘密就是类上标注的@FunctionalInterface注解,被这个注解标注的接口只能有一个抽象方法,我们知道我们写Lambda表达式时并没有指定方法,那么当使用Lambda表达式时我们重新的就是这个方法。

Lambda表达式基本语法

语法形式为 () -> {},其中 () 用来描述参数列表,{} 用来描述方法体,-> 为 lambda运算符

  1. 接口无参无返回

    @FunctionalInterface
    interface NoParamNoReturn {
    	void lambda();
    }
    
    @Test
    public void test() {
      NoParamNoReturn noParamNoReturn=()->{System.out.println("No param No return");};
      noParamNoReturn.lambda();
    }
    //如果方法内只有一个语句那么{}可以省略
    @Test
    public void test() {
      NoParamNoReturn noParamNoReturn=()->System.out.println("No param No return");
      noParamNoReturn.lambda();
    }
    
  2. 接口有一个或多个参数无返回

    @FunctionalInterface
    interface OneParamNoReturn{
    	void lambda(int x);
    }
    @Test
    public void test() {
      OneParamNoReturn oneParamNoReturn=(int x)->System.out.println(x);
      oneParamNoReturn.lambda(10);
    }
    //如果方法只有一个参数那么()可以省略
    //方法参数的类型也可以省略,编译器会根据方法参数类型推断
    @Test
    public void test() {
      OneParamNoReturn oneParamNoReturn=x->System.out.println(x);
      oneParamNoReturn.lambda(10);
    }
    
  3. 接口无参数有返回值

    @FunctionalInterface
    interface NoParamHasReturn{
    	int lambda();
    }
    @Test
    public void test() {
      NoParamHasReturn noParamHasReturn=()->{return 10;};
      noParamHasReturn.lambda();
    }
    //当方法只有return语句时,可以省略{}和return
    @Test
    public void test() {
      NoParamHasReturn noParamHasReturn=()->10;
      noParamHasReturn.lambda();
    }
    
  4. 接口有一个或多个参数有返回值

    @FunctionalInterface
    interface HasParamHasReturn{
    	int lambda(int x,int y);
    }
    @Test
    public void test() {
      HasParamHasReturn hasParamHasReturn=(x,y)->x+y;
      hasParamHasReturn.lambda(10, 20);
    }
    

    Lambda表达式引用方法

    我们可以使用lambda表达式把接口快速指向一个已经实现了的方法

    语法:方法归属者::方法名 静态方法的归属者为类名,普通方法归属者为对象

    @FunctionalInterface
    interface HasParamHasReturn{
    	int lambda(int x,int y);
    }
    public class LambdaTest{
      public int add(int x,int y) {
        return x+y;
      }
    //lambda表达式指向对象方法
      @Test public void test() {
        LambdaTest lt=new LambdaTest();
        HasParamHasReturn hasParamHasReturn=lt::add;
        hasParamHasReturn.lambda(10, 20);
      }
      
      public static int sub(int x,int y) {
        return x-y;
      }
    
      //lambda表达式引用静态方法
      @Test
      public void test12() {
        HasParamHasReturn hasParamHasReturn=LambdaTest::sub;
        hasParamHasReturn.lambda(10, 20);
      } 
    }
    
    //类名::实例方法 特殊情况,只有当参数列表为一个参数并且这个参数是方法调用者或多个参数并且第一个参数是调用者其他参数是参数列表
    @Test
    public void test() {
      BiPredicate<String,String> bp=(x,y)->x.equals(y);
      //也可以简写为
      BiPredicate<String,String> bp2=String::equals;
    }
    

    lambda表达式引用构造函数创建对象

    语法:类名::new;

    class User{
    	String name;
    	int age;
    	
    	public User() {}
    	
    	public User(String name, int age) {
    		super();
    		this.name = name;
    		this.age = age;
    	}
    	
    }
    
    @FunctionalInterface
    interface UserCreatorBlankConstruct{
    	User getUser();
    }
    
    //使用lambda表达式引用构造器
    @Test
    public void test() {
      UserCreatorBlankConstruct creator1=User::new;
      creator1.getUser();
    }
    
    @FunctionalInterface
    interface UserCreatorParamConstruct{
    	User getUser(String name,int age);
    }
    
    @Test
    public void test13() {
      UserCreatorParamConstruct creator2=User::new;
      creator2.getUser("tom", 20);
    }
    

    java8为我们提供了4个核心的函数式接口

    1. 消费型接口
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
  1. 供给型接口

    @FunctionalInterface
    public interface Supplier<T> {
        T get();
    }
    
  2. 函数型接口

    @FunctionalInterface
    public interface Function<T, R> {
        R apply(T t);
    }
    
  3. 断言型接口

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

    怎么用呢?

    遍历数组

    @Test
    	public void test() {
    		List<Integer> list=new ArrayList<>();
    		list.add(1);
    		list.add(3);
    		list.add(5);
    		list.add(7);
    		list.forEach(System.out::println);
    	}
    

    创建对象

    @Test
    public void test() {
      Supplier<User> supplier=User::new;
      User user = supplier.get();
    }
    

    去除前后空格并转为大写

    @Test
    public void test16() {
      Function<String, String> fun=s->{s=s.trim();s=s.toUpperCase();return s;};
      String apply = fun.apply(" abCd");
    }
    

    删除集合元素

    @Test
    public void test() {
      List<User> list=new ArrayList<>();
      list.add(new User("tom",20));
      list.add(new User("jack",18));
      list.add(new User("marry",22));
    
      list.removeIf(e->e.getName()=="jack");
    }
    

    那如果我们要用内置函数式接口创建对象,怎么做呢?

    @Test
    public void test() {
      Supplier<User> supplier=User::new;
      User user = supplier.get();
      System.out.println(user);//User [name=null, age=0]
    }
    

    那到底使用的是哪个构造器呢?通过输出创建的对象可以发现调用的是无参构造器,即调用构造器参数个数对应Supplier中get方法的参数个数的构造器

    那么问题又来了,如果我们要使用两个参数的构造器,那Supplier也不行啊,Function的apply方法也只有一个参数,怎么办?那我们去java.util.function包下找找有没有可以用的接口

    @FunctionalInterface
    public interface BiFunction<T, U, R> {
        R apply(T t, U u);
    }
    //使用内置函数式接口创建对象
    @Test
    public void test20() {
      BiFunction<String,Integer,User> biFunction=User::new;
      User user = biFunction.apply("tom", 20);
      System.out.println(user);//User [name=tom, age=20]
    }
    

    四个基本接口参数个数不够用也可以类似的去java.util.function包下找找有没有申明好的函数式接口

最后一个问题,我发现

list.forEach(System.out::println);

遍历List时,使用的forEach的参数Consumer的accept方法是这么写的,我第一个想到的是,out是System的一个内部类,但是当我点进去,发现是这样的

public final static PrintStream out = null;

out是一个静态的成员变量,那我的理解就是System.out其实是一个对象

这样System.out::println的写法其实也就是 对象名::方法名

我是这样理解的,如果错了还请赐教!

posted @ 2020-03-30 21:51  moyuduo  阅读(118)  评论(0编辑  收藏  举报