天天看点

java8新特性之Lambda表达式java8新特性之Lambda表达式

@[java8新特性]

java8新特性之Lambda表达式

java8新特性包括lambda表示式,Stream流、Optional和全新的日期时间API,其中最好用的必须是lambda表达式和操作各种数据的Stream流了,本章节主要介绍Lambda表达式的使用

lambda表达式的使用

lambda表达式其实是一个匿名函数,我们可以将表达式理解为一段可以当做参数进行传递的代码,通过lambda表达式,可以将java程序变得更加简洁和灵活。

来看一段程序

@Test
public void test() {
    Comparator<Integer> comparator = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2 - o1;
        }
    };
    Set<Integer> set = new TreeSet<>(comparator);
    set.add(3);
    set.add(2);
    set.add(1);
    System.out.println(set);
}
           

在java8之前,我们想实现对TreeSet的自定义排序,我们可以创建匿名的比较器类将其传入TreeSet构造方法中,然而在java8之后,使用lambda表达式可以简洁匿名内部类的代码,如下:

@Test
public void test() {
    Comparator<Integer> comparator = (o1, o2) -> {o2 - o1};
    Set<Integer> set = new TreeSet<>(comparator);
    set.add(3);
    set.add(2);
    set.add(1);
    System.out.println(set);
}
           
原本的匿名内部类被替换成为了有->的一行代码。
           

Lambda表达式的语法

java8中引入一个‘ -> ’操作符,被称之为lambda操作符。该操作符将表达式拆分为左右两个部分,左边的部分为表达式的参数列表(使用括号),右边的部分为方法体使用{}包裹方法的具体实现。

基于接口中方法声明的不同,Lambda表达式的编写方式也有有所不同。大体可以分为以下几个方面:
·无参无返回值:Runnable runnable = ()->System.out.println("Hello World")

·有一个参数无返回值:Consumer consumer = (x) -> System.out.println(x)
	若是方法只有一个参数,则可以省略小括号不写
		Consumer consumer = x->System.out.println(x)
		
·有多个参数有返回值,且lambda体中有多条语句:
	Compartor<Integer> compartor = (o1,o2)->{
			System.out.println("从小到大排列");
			return o1 - o2;
}
**若是lambda体中有多条语句,则lambda体必须使用大括号包裹起来,若是只有一条语句,则可以省略大括号。**
·有多个参数有返回值,但是lambda体中只有一条语句:
	Compatar compatar = (o1,o2) -> o1-o2;
		对于以上这种情况,lambda体中只有一条返回语句,则return可以省略不写。
           

我们发现在所有的lambda表达式中,均没有声明参数的类型。这是因为JVM编译器可以根据上下文自动推理出参数的类型。

需要注意的是,并不是所有的接口实现都可以使用Lambda表达式,它需要 函数型接口 的支持。

函数型接口

只含有一个抽象方法的接口被称为函数型接口
           

假设接口中存在多个抽象方法,那么Lambda表达式根本无法知道我们到底是需要实现哪个抽象方法,所以Lambda表达式的使用必须是基于函数型接口。

在JDK1.8中,为此专门提供了一个注解@FuncationalInterface来声明一个函数型接口,倘若在一个接口上使用了@FuncationalInterface注解,那么该接口中只能包含一个抽象方法。若是不满足要求编译器则会报错。

@FunctionalInterface
public interface TestFunctionalInterface {
    
    void method();
    
}
           

有些同学可能会发现在使用Lambda表达式实现一些功能时,还需要自己去额外编写一个函数式接口,而事实上,JDK1.8已经为我们内置了四大核心函数式接口,分别是:

1、Consumer:消费型接口,抽象方法为:void accept(T t);

2、Supplier:供给型接口,抽象方法为:T get();

3、Function<T,R>:函数型接口,抽象方法为:R apply(T t);

4、Rredicate:断言型接口,抽象方法为:boolean test(T t);

通过它们就已经能解决大部分的问题了,具体使用哪个接口可以根据自己的实际需求决定,比如若是需要实现的功能带参数而无返回值,则应该使用消费型接口。再比如若是要实现的功能无参数但是有返回值,则需要使用供给型接口。

下面是一个供给型接口的例子:

public class Test1 {

    public static void main(String[] args) {

        Test1 test1 = new Test1();
        List<Integer> list = test1.getList(5, () -> new Random().nextInt(20));
        list.stream()
                .forEach(System.out::println);

    }

    public List<Integer> getList(int length, Supplier<Integer> supplier) {

        ArrayList<Integer> list = new ArrayList<>();

        for (int i = 0; i < length; i++) {
            list.add(supplier.get());
        }

        return list;
    }
}
           

再来一个消费型接口的例子:

public class ConsumerTest {

    public static void main(String[] args) {
        ConsumerTest consumerTest = new ConsumerTest();
        consumerTest.testConsumer((x) -> System.out.print(x));
    }
    
    public void testConsumer(Consumer<String> consumerTest) {
        consumerTest.accept("测试消费型接口");
    }
}
           

方法引用

若是Lambda体中的内容已经有方法实现了,那么就可以使用方法引用,可以认为方法引用是Lambda体的另一种表达形式。
           

方法引用主要有以下几种方式:

1、对象::实例方法名

2、类::静态方法名

3、类::实例方法名

相关示例如下:

/**
     * 测试对象的实例方法引用
     */
    public void test1() {

        Consumer<String> consumer = (x) -> System.out.println(x);
        consumer.accept("测试对象方法引用");

        Consumer<String> consumer1 = System.out::println;
        consumer1.accept("测试对象方法应用");
    }

    /**
     * 测试类的静态方法引用
     */
    public void test2() {

        Comparator<Integer> comparator = (o1, o2) -> o1 - o2;
        int compare = comparator.compare(1, 2);
        System.out.println(compare);

        Comparator<Integer> comparator1 = Integer::compare;
        int compare1 = comparator1.compare(1, 2);
        System.out.println(compare1);
    }

    /**
     * 测试类的实例方法
     */
    public void test3() {
        BiPredicate<String,String> result = (s1 ,s2) -> s1.equals(s2);
        System.out.println(result.test("abc", "abc"));

        User user = new User();
        user.setName("abc");
        BiPredicate<User, String> result2 = User::compareName;
        boolean abc = result2.test(user, "abc");
        System.out.println(abc);
    }
           
需要注意的是:
	使用对象的实例方法和类的静态方法引用时,需保证被引用的方法与使用的函数型接口的返回值和形参一致。
	使用类的实例方法时,需要保证第一个参数为方法的调用者,第二个参数为方法的实参。当存在多个参数时,则需要根据情况编写自定义的函数型接口和在相应的类中编写方法的实现。
           

构造器引用

与方法引用类似,它通过类名::new 实现,我们知道一个类可以有多个重载的构造器,那么构造器引用是怎么知道我们需要调用哪个构造器的呢?同样,还是根据我们使用的函数型接口的参数和返回值进行判断我们需要调用哪个构造器。下面是测试举例:

public class ConstructorRefTest {

    public static void main(String[] args) {
        ConstructorRefTest constructorRefTest = new ConstructorRefTest();
        constructorRefTest.test1();
        constructorRefTest.test2();

    }

    public void test1() {

        Supplier<User> supplier = () -> new User();
        Supplier<User> supplier1 = User::new;
        User user = supplier.get();
        User user1 = supplier1.get();
        System.out.println(user.name);
        System.out.println(user1.name);
    }

    public void test2() {

        Function<String, User> function = User::new;
        User user = function.apply("测试构造器引用");
        System.out.println(user.name);
    }

    class User {
        private String name;

        public User() {

        }

        public User(String name) {
            this.name = name;
        }
    }
}
           

Lambda表达式的使用就到这里了,Lambda主要与函数型接口搭配使用,当没有合适的函数型接口时,需要自定义一些满足需求的函数型接口达到目的。

Lambda表达式的使用就介绍到这里了,下一篇文章将详细介绍JDK1.8中Stream流的使用。