public interface ApplePredicate{
boolean test (Apple apple);
}
lambda复合
简洁 无需像匿名类那样写很多模板代码。
() -> {return "success";} 这个lambda没有参数,并返回String(利用显式返回语句)
上面的例子可以看出,lambda主体 有两种,一种为表达式,一种为显式返回语句 ,显示返回语句一般用于大于一条语句的代码块,并用大括号包围,其实就是一般方法体内的代码块,写法和代码块相同,所以一旦使用大括号就要按正常写法编码。
函数式接口就是只定义一个抽象方法的接口。
如果接口有很多默认方法(default声明),但是只要接口只定义一个抽象方法,那这个接口还是一个函数式接口。
lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式(其实就是方法体内的语句)作为函数式接口的实例。
函数式接口的抽象方法叫做函数描述符,抽象方法的签名基本上就是lambda表达式的签名。
()-> void
表示参数为空,返回为voidboolean test(Apple apple);
这个为函数式接口的抽象方法,那么签名便为(Apple apple) -> boolean。
lambda表达式的签名需要和函数式接口的抽象方法一样。
public void process(Runnable r){
r.run();
}
该process方法以Runable为参数类型,方法体内调用了Runnable.run的抽象方法,总结就是,process方法使用Runnable.run()方法做最终操作,该方法没有返回值,如果有返回值就是Runnable.run()方法处理后的返回值。由于Runnable为函数式接口,run方法为抽象方法。所以调用process方法只需要像这样写:
process(() -> System.out.println("This is awesmoe!!");
从这里可以体会到什么???
如果方法入参使用了函数式接口作为参数,那么方法体里面如果使用该函数式接口调用相应的抽象方法,那么就可以在调用process方法式,在原本属于函数式接口参数的位置,直接用抽象方法实现即可,这个实现需要以lambda表达式书写,即参数列表、箭头、lambda主体。
所以回头看看函数式接口的定义,只有一个抽象方法的接口,为什么只有一个抽象方法,如果这个runnable接口的抽象方法有多种的话,按照上述的写法还能合理吗?
如果你想写一个函数式接口,建议加上这个注解,如果不符合函数式接口的规范的话,编译器会提示错误。这个注解不是必要的,但是很有用有没有,一是提示你自己,而是提示其他程序员。
@FunctionalInterface
public interface UserService {
void testDemo();
default String sayHello(FoodService r) {
return r.sayHello();
}
}
@FunctionalInterface
public interface FoodService {
String sayHello();
}
@RunWith(SpringRunner.class)
@SpringBootTest
@WebAppConfiguration
public class LogbackServiceImplTest {
@Before
public void setUp() throws Exception {
System.out.println("测试开始。。。。");
}
@After
public void tearDown() throws Exception {
System.out.println("测试结束。。。。");
}
public String execute(UserService userService, FoodService foodService) {
userService.testDemo();
return userService.sayHello(foodService);
}
@Test
public void test() {
String execute = this.execute(() -> {
System.out.println("this is my demo!");
}, () -> {
return "this is my second demo!";
});
System.out.println(execute);
}
}
定义了两个函数式接口,UserService、FoodService,其中UserService还定义了一个默认方法,这个测试用例是我当时对函数式接口的一些理解而做的一些验证。
抽象方法:boolean test(T t);
使用场景: 当你的方法中涉及到一个布尔类型的判断条件,而且这个判断条件是动态化或者说是多样性的,那么你完全可以使用这个接口,在你的方法入参里加上一个Predicate类型的参数即可。
示例:
public <T> List<T> filter(List<T> list,Predicate<T> p){
List<T> results = new ArrayList<>();
for(T s : list){
if(p.test(s)){
results.add(s);
}
}
return results;
}
这个示例侧重点是,对于实体s过滤条件的多样化,如果有该种场景,只需对该多样化的行为进行抽象,并将定义的函数式接口放到入参里面即可,所以上面的例子调用方式如下:
@Test
public void test() {
List<String> strings = Arrays.asList("1", "2", "", "3", "4");
List<String> list = this.filter(strings, (String s) -> !s.isEmpty());
System.out.println(list.toString());
}
其实从调用来看只需关注函数式接口具体实现那一部分,所以上述的filter方法内部也可以做一些环绕执行的方式。Predicate函数式接口没有指定类型,用的是泛型,所以在编写lambda表达式时,参数列表需要指定类型,如果有的函数式接口已经指定类型,那么(String s)中的String可以忽略,直接一个s -> !s.isEmpty()即可。
抽象方法: void accept(T t);
使用场景: 对于多样化行为如果没有返回值,便可以使用该函数式接口。
示例:
public <T> void forEach(List<T> list, Consumer<T> c){
for(T i: list){
c.accept(i);
}
}
调用:
@Test
public void test() {
List<Integer> integers = Arrays.asList(1, 2, 3, 4);
this.forEach(integers, (Integer i) -> System.out.println(i));
}
抽象方法: R apply(T t);
使用场景: 如果你的方法是一个入参为一种类型,返回值为另一种类型,那么这个函数式接口完全满足你的需求。
示例:
public <T,R> List<R> map(List<T> list, Function<T,R> f){
List<R> result = new ArrayList<>();
for(T s : list){
result.add(f.apply(s));
}
return result;
}
调用:
@Test
public void test() {
List<String> strings = Arrays.asList("lambdas", "in", "action");
List<Integer> list = this.map(strings, (String s) -> s.length());
System.out.println(list.toString());
}
抽象方法: T get();
使用场景: 如果你的方法是一个入参为空,返回值为另一种类型,可以使用该函数式接口
示例:
一般新建一个实体用的就是Supplier,如 Student::new,这里返回的是一个Supplier
抽象方法: R apply(T t, U u);
使用场景: 入参为两种类型,返回值为第三种类型。
示例:
略
上述五个函数式接口应该涵盖了我们平时用到方法类型,返回布尔值、返空回值、返回指定类型等方法,所以说很有用。
Java类型要么使用引用类型(如Byte、Integer、Object、List),要么是原始类型(如byte、double、int、char)。但是泛型只能绑定到引用类型。这是泛型内部实现方式造成的。因此涉及到两个操作:
示例
List<Integer> list = new ArrayList<>();
for(int i = 300;i<400;i++){
list.add(i);
}
i放进到list中就涉及到装箱操作,装箱后的值本质上就是把原始类型包裹起来,并保存在堆里。因此装箱后的值需要更多的内存,并需要额外的内存搜索来获取被包裹的原始值。
Java 8为我们前面所说的函数式接口带来了一个专门版本,以便在输入和输出都是原始类型时避免自动装箱的操作。
函数式接口 | 函数描述符 | 原始类似型特化 |
---|---|---|
Predicate |
T -> boolean | IntPredicate、LongPredicate、DoublePredicate |
Consumer |
T -> void | IntConsumer、LongConsumer、DoubleConsumer |
Funcation<T,R> | T -> R | IntFunction |
IntToDoubleFunction(入参为int类型,返回值为double类型) | ||
IntToLongFunction(入参为int类型,返回值为long类型) | ||
LongFunction |
||
LongToDoubleFunction(入参为long类型,返回值为double类型) | ||
LongToIntFunction(入参为long类型,返回值为int类型) | ||
DoubleFunction |
||
ToIntFunction |
||
ToDoubleFunction |
||
ToLongFunction |
||
Supplier |
() -> T | BooleanSupplier,IntSupplier,LongSupplier,DoubleSupplier |
UnaryOperator |
T -> T | IntUnaryOperator,LongUnaryOperator,DoubleUnaryOperator |
BinaryOperator | (T,T) -> T | IntbinaryOperator,LongBinaryOperator,DoubleBinaryOperator |
BiPredicate<L,R> | <L,R> -> boolean | |
BiConsumer<T,U | (T,U) -> void | |
BiFunction<T,U,R> | (T,U) -> R | ToIntBiFunction<T,U> |
ToLongBiFunction<T,U> | ||
ToDoubleBiFunction<T,U> |
任何函数式接口都不允许抛出受检异常。
lambda表达式的类型是通过lambda的上下文推断出来的。
检查顺序
以上就是检查顺序,有赋值上下文、方法调用上下文、类型转换上下文这三种方式可以获取目标类型。
**自由局部变量**:不是函数式接口抽象方法参数,而是在外层作用域中定义的变量,它们被称为 **捕获lambda**,lambda没有限制捕获实例变量(我认为是在lambda表达式内创建的新的实例,而不是一个可变的引用)和静态变量。但是局部变量必须显示声明为final或事实上是final。原因如下:
所以如果这个自由变量一旦被赋值后便不再变化那么就能保证副本和真实原始变量一样。
其他特性:
闭包: 是一个函数实例(是一种能被调用的对象,它保存了创建它的作用域的信息),且它可以无限制地访问该函数的非本地变量。Java并不能显式的支持闭包,一般是通过非静态内部类支持。
原文:https://www.cnblogs.com/bibibao/p/11427221.html