龙空技术网

Java8 - 新特性之lambda表达式,函数式接口和方法引用

laolang学java 197

前言:

而今我们对“java 传入函数”大致比较着重,同学们都想要学习一些“java 传入函数”的相关文章。那么小编在网上网罗了一些对于“java 传入函数””的相关资讯,希望姐妹们能喜欢,小伙伴们快快来学习一下吧!

一、什么是lambda表达式

lambda表达式是Java8的新特性,也被称为箭头函数,或者闭包,它所体现的是一种轻量级函数式的编程思想,核心是行为参数化,可以把一段代码像值一样传递给方法,传入不同的代码实现不同的功能。

Lambda就是匿名的行为参数化的一种语法实现,它没有名称,但它有参数列表、函数主体、返回类型,甚至是抛出异常。它的语法格式如下:

(parameters) -> expression

或 (parameters) ->{ statements; }

以下是lambda表达式的重要特征:

可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。可选的大括号:如果主体包含了一个语句,就不需要使用大括号。可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

下面是一些简单的例子:

(parameters) -> expression默认Return的,expression只能是一句代码(parameters) -> (statements)没有默认Retrun,statements可以是多行代码下面是一些// 1. 不需要参数,返回值为 5  () -> 5    // 2. 接收一个参数(数字类型),返回其2倍的值  x -> 2 * x    // 3. 接受2个参数(数字),并返回他们的差值  (x, y) -> x – y    // 4. 接收2个int型整数,返回他们的和  (int x, int y) -> x + y    // 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  (String s) -> {System.out.print(s)

使用lambda来定义函数接口的实现,和Java8之前的匿名函数相比,代码看起来更加紧凑和优美。

例如:

        /**         * Java8之前的通过匿名函数来定义接口实现         */        Runnable runnable1  = new Runnable() {            @Override            public void run() {                System.out.println("Just for test1");            }        };        /**         * Lambda来定义函数接口实现         */        Runnable runnable2 = ()->{            System.out.println("Just for test2");        };

但是如果statements很长,我们不应该用Lambda,而应该单独定义一个方法,然后使用方法引用的方式可以提高代码可读性。

二、函数式接口

函数式接口是指只定义一个抽象方法的接口,它允许有default方法。

为了更好的描述该接口是函数式接口,你可以在接口上添加@FunctionalInterface注解。

例如:

private static void testDefineFunctionInterfaceByLambda() {        Runnable runnable = ()->{            System.out.println("hello, run");        };        runnable.run();}

函数式的接口的实现可以用lambda表达式来定义,每一个该类型的lambda表达式都会被匹配到接口中唯一的抽象方法。

例如:

private static void testDefineFunctionInterfaceByLambda() {        Runnable runnable = ()->{            System.out.println("hello, run");        };        runnable.run();}

为了更好的推进java的函数式编程,Java8在 java.util.function 包下为我们提供了大量好用的函数式,由于基本数据类型不能抽象成对象,所以在java.util.function你可以看到有大量Double,Int,Long等前缀的函数式接口。

三、lambda使用外层局部变量

1. lambda 表达式能引用标记了 final 的外层局部变量,但不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。

2. lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)。

3. lambda表达式内部可以使用类的实例变量,并且可以修改非final的实例变量的值。

例如:

    String hello = "Hello,";    private void testLambdaVariableScope(){        /**         * 例子1:引用并修改类的非fina内部l变量         */        //输出 'instance.hello_var:hello'        System.out.println("instance.hello_var:" + hello);        Consumer<String> consumer1 = (t) -> {            System.out.println(hello + t);            hello = "Nihao";        };        //输出'Hello,Jean K'        consumer1.accept("Jean K");        //输出 'instance.hello_var:nihao'        System.out.println("instance.hello_var:" + hello);        /**         * 例子2:引用方法内lambda外的final变量         */        final String name = "Tom H";        Consumer<String> consumer2 = (t) -> {            System.out.println(t + name);            //这里会报错,报错信息:Variable used in lambda expression should be final or effectively final.            //name = "KK One";        };        //输出'Hello,Tom H'        consumer2.accept("Hello,");    }

输出结果:

instance.hello_var:Hello,Hello,Jean Kinstance.hello_var:NihaoHello,Tom H
四、lambda表达式的异常处理

函数式接口的抽象方法可以声明受检异常(checked exception),调用函数式接口对象的方法必须catch这个异常,或者在throws中声明抛出的异常类。

例如:

@FunctionalInterface interface ClientService{     void sayHello(String name) throws Exception; }private static void testLambdaCheckedException() {     /**     * 例子1:自定义函数式接口,在抽象方法中定义受检异常    */     ClientService clientService = (s)->{         System.out.println("Hello, " + s);         throw new Exception("break it manually");     };     try {        clientService.sayHello("Jim L");     } catch (Exception e) {        e.printStackTrace();     }}

Java8的java.util.function包下定义的很多函数式接口,但这些函数式接口清一色不支持受检异常(checked exception),因此不能把抛出受检异常的lambda表达式赋予给对应的函数式接口实例,必须在lambda表达式内部try/catch来处理受检异常(checked exception),如果需要继续抛出异常,必须自己把受检异常(checked exception)转换成非受检异常(unchecked exception),然后重新抛出。

例如:

private  static  void testLambdaUncheckException(){        Function<String, URL> function = (s)->{            try {                return new URL(s);            }catch (MalformedURLException e){                throw new RuntimeException("Malformed URL:" + s, e);            }        };        try {            URL url = function.apply("http//abc");            System.out.println("url:" + url);        }catch (RuntimeException e){            e.printStackTrace();        } }

输出结果:

java.lang.RuntimeException: Malformed URL:http//abc	at org.laolang.jdk8.lambda.LambdaTest.lambda$testLambdaUncheckException$4(LambdaTest.java:91)	at org.laolang.jdk8.lambda.LambdaTest.testLambdaUncheckException(LambdaTest.java:95)	at org.laolang.jdk8.lambda.LambdaTest.main(LambdaTest.java:17)Caused by: java.net.MalformedURLException: no protocol: http//abc	at java.base/java.net.URL.<init>(URL.java:672)	at java.base/java.net.URL.<init>(URL.java:568)	at java.base/java.net.URL.<init>(URL.java:515)	at org.laolang.jdk8.lambda.LambdaTest.lambda$testLambdaUncheckException$4(LambdaTest.java:89)	... 2 more

Tips: 受检异常(checked exception)是指Exception及其子类,必须在方法定义的throws中声明或者方法体内使用try/catch进行捕获处理;非受检异常(unchecked exception)是指RuntimeException及其子类,不需要在方法定义的throws中声明或者方法体内使用try/catch进行捕获处理。

五、方法引用

对于函数式接口,除了可以通过lambda进行赋值,还可以通过方法引用来进行赋值。方法引用的目的是为了有效利用已经存在的方法。被引用的方法,必须和函数式接口中的抽象方法保持相同的的输入参数个数,类型和顺序,相同返回类型,以及相同的异常声明列表。

方法引用的表示形式:

1. 实例对象名::实例方法名

2. 类名::静态方法名

3. 类名::实例方法名

4. 类名::new

例如:

private static void testMethodReference(){        /**         * 例子1:引用类的静态方法         */        Function<String,Integer> function = Integer::parseInt;        Integer integer = function.apply("12345");        /**         * 例子2:引用实例方法         */        Random random = new Random();        Supplier<Long> supplier = random::nextLong;        System.out.println("supplier.get:" + supplier.get());        List<String> list1 = Arrays.asList("A","B");        list1.forEach(System.out::println);        List<String> lowCaseList1 = list1.stream().map(String::toLowerCase).collect(Collectors.toList());        /**         * 例子3:引用无参数构造函数         */        Supplier<Person> personSupplier = Person::new;        Person person1 = personSupplier.get();        /**         * 例子4:引用只有一个参数的构造函数         */        Function<String,Person> personFunction = Person::new;        Person person2 = personFunction.apply("Jack K");        /**         * 例子5:引用两个参数的构造函数         */        BiFunction<String,Integer,Person> personBiFunction = Person::new;        Person person3 = personBiFunction.apply("Jean Y", 30);    }    public static class Person{        private String name;        private Integer age;        public Person(){}        public Person(String name){            this.name = name;        }        public Person(String name, Integer age){            this.name = name;            this.age = age;        }        public String getName() {            return name;        }        public void setName(String name) {            this.name = name;        }        public Integer getAge() {            return age;        }        public void setAge(Integer age) {            this.age = age;        } }

lambda及其等效方法引用的例子:

(Person a) -> a.getWeight() 等效于 Person::getWeight

() -> Thread.currentThread().getName() 等效于 Thread.currentThread()::getName

(str, i) -> str.substring(i) 等效于 String::substring

(String s) -> System.out.println(s) 等效于 System.out::println

六、复合lambda

复合lambda是指可以把多个lambda表达式进行合并,从而形成一个新的lambda表达式。在java8中,一般都是通过在函数式接口中提供一系列的默认方法来对多个lambda进行合并形成新的lambda。

例如:

private static void testCombinedLambda() {        List<Person> personList = new ArrayList<>();        /**         * 例子1:Comparator(比较器复合)         */        personList.add(new Person("Jack B",40));        personList.add(new Person("Sam J", 50));        personList.add(new Person("Wayne D", 34));        personList.add(new Person("Wayne D", 20));        Comparator<Person> personComparator = Comparator.comparing(Person::getName);        personList.sort(personComparator.reversed().thenComparing(Person::getAge));        /**         * 例子2:Predicate(谓词复合)         */        Predicate<Person> personPredicate = (p) -> p.getName().equals("Wayne D");        personPredicate = personPredicate.and(p->p.getAge()>21);        personList.stream().filter(personPredicate).forEach(System.out::println);        /**         * 例子3:Function(函数复合)         * 假设有一个函数f给数字加1 (x -> x + 1),另一个函数g给数字乘2,你可以将它们组合成一个函数h,先给数字加1,再给结果乘2         */        Function<Integer, Integer> f = x -> x + 1;        Function<Integer, Integer> g = x -> x * 2;        Function<Integer, Integer> h = f.andThen(g);        //输出5        System.out.println(h.apply(1));    }
七、参考资料

Java 8 Lambda 表达式

Java 8 Functional Interfaces and Checked Exceptions

Throwing Checked Exceptions From Lambdas

标签: #java 传入函数