java lambda表达式

本文深入探讨Java 8的Lambda表达式,讲解其基本语法、如何简化代码及提高程序灵活性。通过实例演示Lambda表达式在过滤、映射和消费操作中的应用,以及与函数接口、Stream API的结合使用。
摘要由CSDN通过智能技术生成

参考文档:https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html

为了对lambda表达式熟悉点,特地去官网找了资料,进行学习。

官方首先给出了一个类,Persion类,我稍微改了一下。

package cn.itouchtv.java8;

public class Person {
    public enum Sex {
        MALE, FEMALE
    }
    //名字
    String name;
    //年龄
    int age;
    //性别
    Sex gender;
    //邮件地址
    String emailAddress;
    public Person(String name, int age, Sex gender, String emailAddress) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.emailAddress = emailAddress;
    }

    public Person() {
        //do nothing
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Sex getGender() {
        return gender;
    }

    public void setGender(Sex gender) {
        this.gender = gender;
    }

    public String getEmailAddress() {
        return emailAddress;
    }

    public void setEmailAddress(String emailAddress) {
        this.emailAddress = emailAddress;
    }
}

然后它举了一个满足特定条件的列子,但是它没有给相应的数据,我写一个测试的方法,对这些场景都进行实现,官网中,假设有一个装有Person的list集合。这里就不用假设了,直接new一些来测试下吧。

首先它是假设在集合中需要寻找某个特定条件的数据。

package cn.itouchtv.java8;

import java.util.Arrays;
import java.util.List;

public class MyTest {
    //(String name, int age, Sex gender, String emailAddress)
    List<Person> persons = Arrays.asList(
            new Person("Tom",16,Person.Sex.MALE,"T1"),
            new Person("Lili",26,Person.Sex.FEMALE,"L1"),
            new Person("Mike",36,Person.Sex.MALE,"M1"),
            new Person("John",46,Person.Sex.MALE,"J1")
    );

    @org.junit.Test
    public void test(){
        printPersonsOlderThan(persons,30);
    }

    public  void printPersonsOlderThan(List<Person> roster, int age) {
        for (Person p : roster) {
            if (p.getAge() >= age) {
               System.out.println("name : "+ p.getName()+ ", "+ "age : "+p.getAge() +",
 "+"sex : "+p.getGender()+", " + "email : "+ p.getEmailAddress());
            }
        }
    }
}

这里的代码是比较基础的,就是寻找集合中年龄大于某个值的数据。

然后它就说了,这种代码是很脆弱的,它很有可能不能用了,如果你需要进行升级,比如说,你需要改变Person的结构,计算age的方法变了,就比如现在我不使用上述的age这个属性了,跟官网一样,我改为另外一个属性生日birth。那么计算的方式就变了,必须重写代码来兼容这种改变,结构我们就不改了,比如我们现在要找年龄在某个范围内的数据:以下就直接写成一个方法的形式:

 public  void printPersonsWithinAgeRange(
            List<Person> roster, int low, int high) {
        for (Person p : roster) {
            if (low <= p.getAge() && p.getAge() < high) {
                System.out.println("name : "+ p.getName()+ ", "+ "age : "+p.getAge() +",
 "+"sex : "+p.getGender()+", " + "email : "+ p.getEmailAddress());
            }
        }
    }

这个其实是一样的,多了一个条件而已。然后它指出,假设我要找男的数据,或者女的数据,又或者某个年龄段男的啥的,每一个变的条件就得加一个方法,每一个条件就得加一个方法,也同样导致脆弱的代码,然后给出用类来划分 这些条件。

加一个接口,接口里只有一个抽象方法,这个方法某个条件,你需要哪些条件,你只管实现就好啦。


interface CheckPerson {
    boolean test(Person p);
}

给出要求,需要找在美国具有选择性服务资格的数据:(这个是什么东东,不太懂,反正就是某个条件了)

然后就是创建一个类来管理这些条件,这个类实现上述接口并提供具体的实现。


class CheckPersonEligibleForSelectiveService implements CheckPerson {
    public boolean test(Person p) {
        return p.gender == Person.Sex.MALE &&
            p.getAge() >= 18 &&
            p.getAge() <= 25;
    }
}

然后就是使用这些条件了。

 public void filterPerson(List<Person> persons,CheckPerson checkPerson){
        for (Person p : persons) {
            if (checkPerson.test(p)) {
                System.out.println("name : "+ p.getName()+ ",
 "+ "age : "+p.getAge() +", "+"sex : "+p.getGender()+", " + "email : "+ p.getEmailAddress());
            }
        }
    }

这个其实是设计模式中的策略模式,就是你需要什么策略来处理这些数据,直接实现CheckPerson这个策略接口就可以了,不过官网又说了,虽然这样代码不是很脆弱,但是每次都要加一个类,麻烦哦,于是匿名内部类登场了,假设我要找年龄大于19的所有数据,直接调用filterPerson(List<Person> persons,CheckPerson checkPerson)方法,给内部类即可:

  filterPerson(persons, new CheckPerson() {
           @Override
           public boolean test(Person p) {
               return p.getAge() > 19;
           }
       });

哇,又少了一个类,可是它又说这样不好懂,又笨重,同时发现这个接口中只有一个方法,在这种情况下,刚好可以使用lambda表达式。

  filterPerson(persons,(Person p) -> {
           return p.getAge() > 19;
       });

发现没有,传了一段代码过来:

(Person p) -> {
           return p.getAge() > 19;
 }

其实这个跟匿名内部类一样,都是一段代码,居然当成参数传给方法了。其实lambda表达式就是为了实现函数编程,把代码块或者方法当成参数传给方法。

这个够简单了吧,还不够,因为这个接口CheckPerson就只有一个方法,不值得在运用中定义它。然后就给出了jdk自带的一个接口:这个是函数接口,进入源码可以看到:

@FunctionalInterface这个注解,这个注解是来检查函数接口是否合格,不合格会编译不通过。函数接口满足条件:

1、有且只有一个抽象方法

2、可以有default修饰的方法和静态方法

3、可以有从父类从写的抽象方法。

当不满足上述条件时,编译不通过。

接口如下

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

哇,有那么巧没那么巧,刚好有test方法,而且只有一个参数,boolean返回值。不过好一点的是,它提供了泛型,这样就更通用了。使用下:
 

 public void filterPerson(List<Person> persons,Predicate<Person> predicate){
        for (Person p : persons) {
            if (predicate.test(p)) {
                System.out.println("name : "+ p.getName()+ ", "+ "age : "+p.getAge() +",
 "+"sex : "+p.getGender()+", " + "email : "+ p.getEmailAddress());
            }
        }
    }

调用:就是对Predicate提供实现:

 filterPerson(persons, new Predicate<Person>() {
           @Override
           public boolean test(Person person) {
               return person.getAge() > 19;
           }
       });

少了一个接口,但是这里不又用了匿名内部类吗?当然这个时候,还是可以使用lambda表达式的:

filterPerson(persons, p ->  p.getAge() > 19);

类型Person没有了,连大括号和return都没有了;这是不用指定类型是因为在运行时,调用方法的时候,会推断出方法的参数的类型。右边当大括号中的代码只有一行的时候,可以省略,同时此时如果是有返回值,那么return也可以省略。如果要有ruturn的话,那么大括号也要有,同时因为大括号中的代码要有;结束符号。即 

 p -> { return p.getAge() > 19;}

接下来出场的是第二个函数接口:Consumer<T> 一看这个是个消费者,老夫掐指一算,这个方法应该没有返回值:

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);
}

然后进入到accept方法中,你想干啥就干啥。代码:

 public void filterPerson(List<Person> persons, Predicate<Person> predicate, Consumer<Person> consumer){
        for (Person p : persons) {
            if (predicate.test(p)) {
               consumer.accept(p);
            }
        }
    }

使用方法:

  filterPerson(persons,p-> p.getAge() > 19,p->{
            System.out.println(p.getAge());
        });

这里只是打印年龄大于19岁数据的年龄,里面你要做其他啥都可以了。

接下来出场的是:

@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
}

  使用:这个Function是个功能接口,可以返回某个值,这里是拿出Person的某个信息给Consumer消费。

  public void filterPerson(List<Person> persons, Predicate<Person> predicate, 
Consumer<String> consumer, Function<Person,String> function){
        for (Person p : persons) {
            if (predicate.test(p)) {
                String temp = function.apply(p);
                consumer.accept(temp);
            }
        }
    }
filterPerson(persons,p -> p.getAge() > 19,email -> System.out.println(email),p -> p.getEmailAddress());

这个调用是真的不好理解,第一个是满足某个条件的数据就往下执行,最后一个是获取满足第一个条件中数据的email地址,第二个是对email地址进行消费,这里仅仅打印出来而已。email是随便取的名字。

然后给出了一个高度抽象的方法:

public static <X, Y> void processElements(
    Iterable<X> source,
    Predicate<X> tester,
    Function <X, Y> mapper,
    Consumer<Y> block) {
    for (X p : source) {
        if (tester.test(p)) {
            Y data = mapper.apply(p);
            block.accept(data);
        }
    }
}

使用:

processElements(persons,p -> p.getAge() > 19,p -> p.getEmailAddress(),email -> System.out.println(email));

这个是一样的,不过可以迭代更多的集合,不单单是List了。刚开始我有点萌,这个是为啥咧。看这个名字Iterable,可迭代的,然后我发现List是可以迭代的,难道??对了,Iterable是List的父类。这个是向上转型,当然可以传List的对象进去罗。

 

接下来到了真正stream流时刻:代码如下:

        persons.stream()
                .filter(p -> p.getAge() > 19)
                .map(p -> p.getAge())
                .forEach(age -> System.out.println(age));

哇,怎么这么像的,其实看看方法就知道了:点进去:

Stream<T> filter(Predicate<? super T> predicate);
Stream<R> map(Function<? super T, ? extends R> mapper);
​​​​​​​void forEach(Consumer<? super T> action);

参数都是接口函数,自然可以进行lambda表达式的使用了。

 

lambda表达式的语法

1、括号中,用逗号分隔的参数,你可以省略数据类型,如果只有一个参数,你可以省略括号。

(Peson p ) -> { return p.getAge() >19; }  ==》  p -> {return p.getAge() > 19;}

这里把Person和相应的括号都给省略了。

2、箭头 ->

3、实体内容,可以是一个表达式或者是一个代码块,也可以是方法

如上述的 {return p.getAge() >19;} return 不是必须的,如果是方法没有返回值,就不用,或者实体中只有一个方法,也可以省略return,同时可以省略{}和分号。

即: {return p.getAge() > 19;} ===》 p.getAge() > 19 

如果是代码块,{}就必须要用,同时每个语句也要有分号。

接下来给出官方给的一段代码:

public class Calculator {
  
    interface IntegerMath {
        int operation(int a, int b);   
    }
  
    public int operateBinary(int a, int b, IntegerMath op) {
        return op.operation(a, b);
    }
 
    public static void main(String... args) {
    
        Calculator myApp = new Calculator();
        IntegerMath addition = (a, b) -> a + b;
        IntegerMath subtraction = (a, b) -> a - b;
        System.out.println("40 + 2 = " +
            myApp.operateBinary(40, 2, addition));
        System.out.println("20 - 10 = " +
            myApp.operateBinary(20, 10, subtraction));    
    }
}

输出结果:
40 + 2 = 42
20 - 10 = 10

关于局部变量的访问。lambda表达式不会从超类型继承任何名称,和引入新的级别范围。lambda表达式声明和封闭环境的声明一样被解释。这个是谷歌翻译的,感觉好抽象。

上代码:官网给的:

import java.util.function.Consumer;

public class LambdaScopeTest {

    public int x = 0;

    class FirstLevel {

        public int x = 1;

        void methodInFirstLevel(int x) {
            
            // The following statement causes the compiler to generate
            // the error "local variables referenced from a lambda expression
            // must be final or effectively final" in statement A:
            //
            // x = 99;
            
            Consumer<Integer> myConsumer = (y) -> 
            {
                System.out.println("x = " + x); // Statement A
                System.out.println("y = " + y);
                System.out.println("this.x = " + this.x);
                System.out.println("LambdaScopeTest.this.x = " +
                    LambdaScopeTest.this.x);
            };

            myConsumer.accept(x);

        }
    }

    public static void main(String... args) {
        LambdaScopeTest st = new LambdaScopeTest();
        LambdaScopeTest.FirstLevel fl = st.new FirstLevel();
        fl.methodInFirstLevel(23);
    }
}

输出结果:

x = 23
y = 23
this.x = 1
LambdaScopeTest.this.x = 0

然后你尝试下将上述的Consumer<Integer> myConsumer = (y) -> {}的y改成x,你会发现编译出错,而且提示说这个范围内的变量x已经存在了。说明lambda表达式,前面的括号的参数是和lambda表达所处的环境是一样声明的,所以y就相当于在方法中声明一样,因为你输入参数都有x了,自然不能再声明一个一样的x。这只解释了那个翻译的后一句话。

另外一句,不能从超类型中继承名字。

上代码:改一下:

package cn.itouchtv.java8;

import java.util.function.Consumer;

public class LambdaScopeTest {

    public int x = 0;

    class FirstLevel {



        public int x = 1;
        public int k = 2;



        void methodInFirstLevel() {
            
            // 这个x,不会拿到上述的x的值
            Consumer<Integer> myConsumer = (x) ->
            {
                System.out.println("x = " + x); // Statement A

            };

            myConsumer.accept(k);

        }
    }

    public static void main(String... args) {
        LambdaScopeTest st = new LambdaScopeTest();
        LambdaScopeTest.FirstLevel fl = st.new FirstLevel();
        fl.methodInFirstLevel();
    }
}

输出结果:

x = 2

说明lambda表达式的参数并不会去集成该类中x的值,也就是不会继承名称了,那个产生薪级别范围,不懂。

 

目标类型,lambda表达式可以省略参数的类型是因为它去调用相应的方法时,能检查出参数的类型。比如说p我怎么知道它是什么类型,很简单,去调用一下那个方法,参数是Person,那么就是Person罗。这是在java运行时推断的。

 

最后是一个判断题:

public interface Runnable {
    void run();
}

public interface Callable<V> {
    V call();
}


void invoke(Runnable r) {
    r.run();
}

<T> T invoke(Callable<T> c) {
    return c.call();
}

Which method will be invoked in the following statement?

String s = invoke(() -> "done");

看看会调用那个方法,很明显有返回结果,那么肯定是Callable那个。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值