1.lambda表达式的介绍?
1.1 什么是lambda表达式
lambda表达式是一种新的语法糖,可以使代码更加简洁。写代码时需要在idea指定java8或以上版本。lambda表达式跟js ES6版本的箭头函数的用法是类似的,如果会使用箭头函数那么lambda表达式学不学无所谓。lambda表达式的本质是函数式接口的实例。函数式接口是指在一个接口中只包含一个抽象方法的接口,例如Runnbale、Consumer等接口,函数式接口会使用[@FunctionalInterface](https://github.com/FunctionalInterface)
注解标识是否是函数式接口,如果一个函数式接口拥有超过一个的抽象方法,使用@FunctionalInterface注解则会在提示错误
Runnable的接口源码(很明显加了[@FunctionalInterface](https://github.com/FunctionalInterface)
注解):
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
1.2 lambda表达式的组成
lambda表达式由抽象方法的形参列表、箭头符、方法主体3个部分组成,使用lambda表示式实际上是对函数式接口抽象方法的重写。以下面例子为例(看method02)
抽象方法的形参列表:用于传递形参参数给方法主体,如果形参参数只有一个时()
号可以省略不写,否则都要用()
号将形参参数包裹起来
箭头符号:->
方法主体:跟普通的方法没有什么差别,一般情况下都是使用一对{}
号包裹方法体,当方法体只有一条语句(不包括return 语句)时,{}
号可以省略,例如:()->System,out.println("我很好!");
,当方法体有且只有一条return语句时,可以省略{}
和return
使用lambda表达式,当方法体中只有一条语句(不包括return 语句)时可省略一对{}号:
public class Test01 {
public static void main(String[] args) {
Test01 t=new Test01();
// 不使用lambda表达式
t.method01();
// 使用lambda表达式
t.method02();
}
public void method01(){
Runnable r1=new Runnable() {
@Override
public void run() {
System.out.println("我叫zxp");
}
};
r1.run();
}
public void method02(){
// lambda表达式的写法
Runnable r2=()->System.out.println("我叫zxp");
r2.run();
}
}
分析:上面method01使用了内部类的方式调用了Runnable接口实例的run方法,相比较method02使用lambda表达式的写法,method02写法更加简洁。
使用lambda表达式,当形参参数只有一个时可省略()号,当方法体中只有语句且是return语句时可省略return关键字和一对{}号:
// 普通写法
public void method01(){
Predicate<String> p=new Predicate<String>() {
// test 方法用于条件判断
@Override
public boolean test(String s) {
return s.length()>5;
}
};
System.out.println(p.test("zxpHello")); // true
}
// lambda的写法
public void method04(){
Predicate<String> p=s->s.length()>5;
System.out.println(p.test("zxpHello")); // true
}
分析:Predicate是一个泛型,在使用lambda表达式时会进行一个类型推断。
使用lambda表达式:多个形参参数,方法体只有一条语句且该条语句是return语句可省略一对{}号:
public void method05(){
BinaryOperator<String> b=new BinaryOperator<String>() {
// 字符串拼接
@Override
public String apply(String s, String s2) {
return s+s2;
}
};
System.out.println(b.apply("zxp", "很帅"));
}
public void method06(){
// 使用lambda表达式:多个形参参数
BinaryOperator<String> b=(s,s2)-> s+s2;
System.out.println(b.apply("zxp","很帅"));
}
使用lambda表达式:多个形参参数,方法体有多条语句,啥也不省略:
public void method05(){
BinaryOperator<String> b=new BinaryOperator<String>() {
// 字符串拼接
@Override
public String apply(String s, String s2) {
System.out.println("那是真的强");
return s+s2;
}
};
System.out.println(b.apply("zxp", "很帅"));
}
public void method06(){
// 使用lambda表达式:多个形参参数
BinaryOperator<String> b=(s,s2)-> {
System.out.println("那是真的强");
return s+s2;
};
System.out.println(b.apply("zxp","很帅"));
}
2.方法引用与构造器引用
2.1 方法引用
方法引用是指当传递给Lambda表达式的操作,已经有了实现的方法,就可以使用方法引用,方法引用本质上是Lambda的语法糖,是函数式接口的实例。方法引用有对象::非静态方法名、类::静态方法名、类::非静态方法名3种形式。
注意:使用方法引用的要求为函数式接口的抽象方法的参数列表和返回值需要和引用方法的参数列表和返回值相同(只适合对象::非静态方法名和类::静态方法名的方式)。
对象::非静态方法名的例子:
/**
* Consumer:void accept(Object obj)
* System.out:void println(Object obj)
* 它们的返回值类型和参数列表相同故可以使用方法引用
*/
public static void m01(){
Consumer c1=(s)-> System.out.println(s);
c1.accept("zxp"); // ”zxp“
// 使用方法引用 对象::非静态方法名
PrintStream ps = System.out;
Consumer c2=ps::println;
c2.accept("zxp"); // ”zxp“
}
分析:结果都打印:zxp
,为什么都打印zxp呢?因为Consumer的accept方法的参数列表和返回值与PrintStream的println()方法的参数列表和返回值相同,这2个方法的返回值都是void,参数列表都是只有一个且类型相同,当执行c2.accept("zxp");
这段代码时本质是执行的PrintStream的println()方法(accept引用了println方法),因为accept和prinlnt的参数列表相同,所以参数不需要显示的传递到println方法。
类::静态方法名的例子:
/**
* 类::静态方法
* Conparator: int compare(int x,int y)
* Integer: int compare(int x,int y)
* 说明:Integer.compare(int x,int y):用于第一个参数和第二个参数的比较,
* 第一个参数大于第二个参数返回1,如果小于则返回-1,等于返回0
*/
public static void m02(){
Comparator<Integer> c1=(x,y)-> Integer.compare(x,y);
System.out.println(c1.compare(21, 12)); // 1
Comparator<Integer> c2=Integer::compare;
System.out.println(c2.compare(21, 12)); // 1
}
/**
* 类::静态方法
* Function<T,R>:R apply(T t)
* Math:long round(Double d)
*/
public static void m03(){
Function<Double,Long> f1=(d)->Math.round(d);
System.out.println(f1.apply(1.1)); // 1
Function<Double,Long> f2=Math::round;
System.out.println(f2.apply(1.1)); // 1
}
类::非静态方法例子:
例子1:
/**
* Comparator:int compare(T t1,T t2)
* String:int t1.compareTo(t2)
*/
public static void m04(){
Comparator<String> c1=(s1,s2)->s1.compareTo(s2);
System.out.println(c1.compare("abc", "abd"));//-1
Comparator<String> c2=String::compareTo;
System.out.println(c2.compare("abc","abd"));//-1
}
分析:类::非静态方法
的方式跟其他2种方式有些不同,看看上面的例子,Comparator的compare的返回值与String的compareTo返回值相同,但他们的参数列表不同,Comparator的compare方法有2个参数,String的compareTo方法只有一个参数,那为什么能使用方法引用呢?类::非静态方法
这样方式允许参数列表不一致,但是函数式接口抽象方法的第一个参数会被方法引用的方法当做调用者,例如Comparator:int compare(T t1,T t2)
与String:int t1.compareTo(t2)
相比较,Comparator中compare方法的参数t1会被当作String中带调用compareTo方法的调用者(也就是t1.compareTo(t2)中的t1)。
例子2:
/**
* BiPredicate:boolean test(T s1,U s2)
* String: boolean s1.equals(s2);
*/
public static void m05(){
BiPredicate<String,String> bp1=(s1,s2)->s1.equals(s2);
System.out.println(bp1.test("abc","abc")); // true
BiPredicate<String,String> bp2=String :: equals;
System.out.println(bp2.test("abc","abc")); // true
}
2.2 构造器引用
构造器引用使用的要求:函数式接口的抽象方法的返回值和参数列表需要与构造器引用的方法的返回值和参数列表相同。
public class User {
private Integer id;
private String name;
private Integer age;
public User(){
}
public User(int id){
this.id=id;
}
public User(int id,String name){
this.name=name;
this.id=id;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
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;
}
}
使用构造器构造一个无参构造:
/**
* 使用构造器构造一个无参User构造
* Supplier: T get()
* User: User User()
*/
public static void m06(){
Supplier<User> s1=()->new User();
System.out.println(s1.get().getName()); // null
// 使用构造器构造一个无参User构造
Supplier<User> s2=User::new;
System.out.println(s2.get().getName()); // null
}
使用构造器引用构造有参构造:
/**
* 使用构造器引用构造一个有参User构造
*/
public static void m07(){
Function<Integer,User> f1=(n)->new User(n);
User u1=f1.apply(18);
System.out.println(u1.getId()); // 18
Function<Integer,User> f2=User::new;
User u2=f2.apply(20);
System.out.println(u2.getId()); // 20
}
使用构造器引用构造多个有参构造:
/**
* 使用构造器引用构造多个有参构造
* BiFunction: R apply(T t, U u)
* User: User User(Integr id, String name)
*/
public static void m08(){
BiFunction<Integer,String,User> bf1=(id,name)->new User(id,name);
User u1 = bf1.apply(20, "zxp");
//id:20,name:”zxp“
System.out.println("id:"+u1.getId()+",name:"+u1.getName());
BiFunction<Integer,String,User> bf2=User::new;
User u2 = bf2.apply(100, "zxp123");
//id:100,name:”zxp123“
System.out.println("id:"+u2.getId()+",name:"+u2.getName());
}
数组引用:跟构造器引用用法一样:
// 数组引用
public static void m09(){
Function<Integer,String[]> f1=(len)->new String[len];
String[] s1=f1.apply(3);
System.out.println(Arrays.toString(s1)); // [null, null, null]
Function<Integer,String[]> f2=String[] :: new;
String[] s2=f2.apply(3);
System.out.println(Arrays.toString(s2)); // [null, null, null]
}