参考文档: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那个。