1.什么是Lambda表达式
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
2.那么,什么是函数式接口
定义:接口中只存在一个抽象方法的接口为函数式接口
- 该接口可以使用
@FunctionalInterface
进行修饰,未添加该注解的接口不一定就不是函数式接口,添加该注解可以辅助检查,但是添加该注解的接口一定就是函数式接口 - 继承了另一个函数式接口的接口不是函数式接口
@FunctionalInterface
public interface MyConsumer<T> {
void accept(T t);
}
//这个接口不是函数式接口,且无法编译通过,除非将@FunctionalInterface注解移除
@FunctionalInterface
public interface MyConsumer2<T> extends MyConsumer {
void accept2(T t);
}
3.有哪些常用的函数式接口
Runnable 函数式接口
这个接口没有形参,没有返回值
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
使用Runnable 接口
Runnable runnable = () -> {
System.out.println("easy to start a thread");
};
new Thread(runnable).start();
//简写,和上面的效果一致,由于这个函数只有一行,所以可以省略大括号
new Thread(()->System.out.println("easy to start a thread")).start();
Consumer 函数式接口
这个接口只有形参,没有返回值
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
使用场景
List<String> languages = Arrays.asList("java","goland","python");
//由于Consumer接口需要一个形式参数,所以此处使用item来代表这个接受的参数
Consumer consumer = (item) -> System.out.println(item);
languages.forEach(consumer);
//简写,和上面的效果一致,由于只有一个形式参数,所以可以将item的括号省略
languages.forEach(item -> System.out.println(item));
接口名称 | 方法名称 | 方法签名 |
---|---|---|
Supplier | get | () -> T |
Consumer | accept | (T) -> void |
BiConsumer | accept | (T, U) -> void |
Predicate | test | (T) -> boolean |
Function | apply | (T) -> R |
BiFunction | apply | (T, U) -> R |
4.Lambda表达式语法
4.1语法结构
(形式参数) -> {抽象方法的实现}
4.2当只有一行代码的时候可以省略大括号
Runnable runnable = () -> { System.out.println("easy to start a thread"); };
Runnable runnable = () -> System.out.println("easy to start a thread");
4.3当只有一个形参的时候可以省略括号
Consumer consumer = (item) -> System.out.println(item);
Consumer consumer = item -> System.out.println(item);
4.4当只有一行代码且有返回值时,return可以省略
Predicate<Integer> predicate= (num) -> { return num > 100; };
Predicate<Integer> predicate= num -> num > 100;
4.5Lambda表达式的数据类型可以省略不写
JVM编译器可以通过上下文推断出数据类型
BiFunction<Integer, Integer, Integer> biFunction = (Integer t,Integer u) -> t + u;
BiFunction<Integer, Integer, Integer> biFunction = (t, u) -> t + u;
5.方法引用
类型 | 语法 | 对应的Lambda表达式 |
---|---|---|
静态方法引用 | 类名::staticMethod | (args) -> 类名.staticMethod(args) |
实例方法引用 | inst::instMethod | (args) -> inst.instMethod(args) |
对象方法引用 | 类名::instMethod | (inst,args) -> 类名.instMethod(args) |
构建方法引用 | 类名::new | (args) -> new 类名(args) |
5.1静态方法引用案例
List<Integer> list = Arrays.asList(100,77,26,73,17);
//sort(Comparator)
//Comparator接口的抽象方法int compare(T o1, T o2);
//需要传入两个形式参数,然后有一个返回值
list.sort(Integer::compare);
//对应的代码是
list.sort((x, y) -> (x < y) ? -1 : ((x == y) ? 0 : 1));
System.out.println(list);
5.2实例方法引用案例
String name = "ABC";
Supplier<String> supplier = name::toLowerCase;
//对应的代码是
Supplier<String> supplier = () -> name.toLowerCase();
System.out.println("Lambda表达式输出结果:" + supplier.get()); // Lambda表达式输出结果:abc
5.3对象方法引用案例
当第一个参数是实例方法的参数调用者,而第二个参数是实例方法的参数时,才可以使用对象方法引用
BiPredicate<String,String> bp = String::equals;
//对应的代码是
BiPredicate<String,String> bp = (x, y) -> x.equals(y);
boolean test = bp1.test("xy", "xx");
System.out.println(test); // false
5.4构造方法引用案例
构造方法的参数列表要与函数式接口中抽象方法的参数列表一致才可以使用构造方法引用
//要获取一个空的User列表:此处new ArrayList<>()的参数列表与Supplier的get()一致
Supplier<List<User>> userSupplier = ArrayList<User>::new;
//对应的代码是
Supplier<List<User>> userSupplier = () -> new ArrayList<>();
List<User> user = userSupplier.get();
//一个参数的构造方法
public class MyInteger {
MyInteger(Integer i) {
super();
}
}
//Function apply (T) -> R
Function<Integer, MyInteger> function = i -> new MyInteger(i);
MyInteger apply = function.apply(100);
6.静态方法和默认方法
以往接口只可以定义抽象方法,在Java8之后,接口可以定义静态方法和默认方法
6.1静态方法案例
private interface DefaulableFactory {
// Interfaces now allow static methods
static Defaulable create(Supplier< Defaulable > supplier ) {
return supplier.get();
}
}
public static void main( String[] args ) {
Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
System.out.println( defaulable.myDefalutMethod() );
defaulable = DefaulableFactory.create( OverridableImpl::new );
System.out.println( defaulable.myDefalutMethod() );
}
6.2默认方法案例
优点:在Java8之前,Java的类只支持多重继承不支持多继承。现在有了默认方法, 你可以以另一种方式来实现类的多继承行为, 即一个类实现多个接口, 而这几个接口都有声明自己的默认方法,可以在不破坏代码的前提下扩展原有库的功能,消除冗余。
缺点:这使得接口作为协议,类作为具体实现的界限开始变得有点模糊
public interface A {
default void hello(){
System.out.println("this is A");
}
}
public interface B extends A {
default void hello() {
System.out.println("this is B");
}
}
public class C implements A, B {
public static void main(String[] args) {
//多重继承的冲突说明:
//由于同一个方法可以从不同接口引入,自然而然的会有冲突的现象,规则如下:
//1)一个声明在类里面的方法优先于任何默认方法
//2)优先选取最具体的实现
new C().hello(); //this is B
}
}
注意:当一个类实现了两个接口,这两个接口存在两个相同默认方法时,这个类必须重写该方法,否则编译器会报错
public interface A {
default void hello() { }
}
public interface B {
default void hello() { }
}
public class C implements A, B {
@Override
public void hello() { }
}
注意:不能用默认方法来重载equals,hashCode和toString!
每一个java类都是Object的子类,也都继承了它类中的equals/hashCode/toString方法,那么在类的接口上包含这些默认方法是没有意义的,同时编译器也是无法编译通过的!
7.小练习
public static void main(String[] args) {
String data = ResponseDataUtils.getData(() -> "ABC");
}
请问:
1) 下列选项哪些符合 getData 的定义?
public class ResponseDataUtils {
public final String getData(Supplier<String> expr) { }
public static String getData(Supplier<String> expr) { }
public final String getData(Function<String, String> expr) { }
public static String getData(Function<String, String> expr) { }
}
2) 为什么?
答案:
public class ResponseDataUtils {
public static String getData(Supplier<String> expr) {
return expr.get();
}
public static void main(String[] args) {
String data = ResponseDataUtils.getData(() -> "ABC");
}
}
- 因为
ResponseDataUtils.getData()
,是使用类名进行调用的,所以必须存在关键字static
-
() -> "ABC"
这个函数是没有参数,有返回值,所以只有Supplier
符合,Function
是需要传入一个参数,有返回值