我们先假设一个场景想象一下,当一个项目出现bug的时候,恰巧这个时候需要你去修改,而当你打开项目之后,眼前的代码让你有一种特别严重的陌生感,你会不会慌?心里是不是瞬间就会喷涌而出各种想法:我这是打开的啥语言的项目?还是我眼花看错了?难道是我过时了?这写的是个啥子玩意儿…
java8在14年就出来了,已经很久了,但是还是有很多人没用过,包括我之前的同事都对这个不太熟悉,原因可能是多样的,可能是老程序员觉得没必要;也可能是性格使然,拒绝接受新的东西,一切守旧,能用就行;也可能是项目太老了,还在用JDK1.7,或者更老的版本,平时根本就接触不到java8的写法,也不需要去接触。
无论是什么原因,在新事物出现之后,没有一股探险精神,不去尝试,不去结合自己的处境去思考,这样下去就算天上掉馅饼也轮不到你啊。
这篇短文说下Lambda表达式,有一定的编程基础的小伙伴简单看下应该就会明白,不仅仅写着舒服,更能提供你的工作效率,让你有更多的时间带薪划水,自我提高,走向人生巅峰。
Lambda表达式
Lambda表达式可以理解为一种匿名函数:没有名称、有参数列表、函数主体、返回类型,可能还会有异常的列表。
参数 -> 主体
lambda表达式:(parameters) -> expression 或者是 (parameters) -> { statements; }
函数式接口
什么是函数式接口?
仅仅定义了一个抽象方法的接口,类似于Predicate、Comparator和Runnable。
@FunctionalInterface 函数式接口都带有这个注解,这个注解表示这个接口会被设计为函数式接口。
行为参数化
一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不同行为的能力。
函数式接口可以做些什么?
Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并且把整个表达式作为函数式接口的实例,也就是说,Lambda是函数式接口的一个具体实现。函数式接口和Lambda会在项目中写出更加简洁易懂的代码。
接下来我们看下几种函数式接口:
这么说实在是太生涩了,还是贴点代码,让大家都看看:
@FunctionalInterface public interface Predicate<T> { //我只截取了部分代码,test是这个接口唯一的抽象方法,话说从java8开始,接口中不仅 //仅只能有抽象方法了,实现的方法也可以存在,用default和static来修饰。 boolean test(T t); default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } 。
接下来,看下Lambda和函数式接口是怎么配合,一起快乐的工作的: 首先定义一个方法,这个方法的参数中有函数式接口:
private static <T> List<T> filter(List<T> list, Predicate<T> predicate) { List<T> result = new ArrayList<>(); for (T e : list) { if (predicate.test(e)) { result.add(e); } } return result; }
接下来你就可以这么写:
List<Apple> apples = filter(list, (Apple apple) -> "red".equals(apple.getColor()));
以上,filter方法的参数是一个泛型集合和Predicate,这个函数式接口中的抽象方法是接受一个对象并返回一个布尔值,所以Lambda我们可以写成参数是一个实体对象Apple,主体是一个返回boolean值的表达式,将这段Lambda作为参数传给filter()方法,这也是java8的行为参数化特性。以上我们就可以挑选出红苹果。
使用了泛型,就代表着我们还可以复用这段代码做些别的事情,挑选出你想要东东的:
List<String> stringList = filter(strList, StringUtils::isNoneBlank);
抽象方法的方法签名和Lambda表达式的签名是一一对应的,如果你要应用不同的Lambda表达式,就需要多个函数式接口,当然了我也是可以自己定义的。
在java中只有引用类型或者是原始类型,这是由泛型内部的实现方式造成的。因此,在Java里有一个将原始类型转换为对应的引用类型的机制,这个机制叫作装箱(boxing)。相反的操作,也就是将引用类型转换为对应的原始类型,叫作拆箱(unboxing)。
Java还有一个自动装箱机制,也就是说装箱和拆箱操作是自动完成的,但这在性能方面是要付出代价的。装箱后的值本质上就是把原始类型包裹起来,并保存在堆里。因此,装箱后的值需要更多的内存,并需要额外的内存来搜索获取被包裹的原始值。
针对于这一点,java8中的函数式接口提供了单独的接口,就是为了在输入和输出的时候避免自动装箱拆箱的操作,是不是很贴心。
一般情况下,在名称上我们就能看得出来,一目了然。在原来的名称上会有原始类型前缀。像Function接口针对输出参数类型的变形。比如说:ToIntFunction、IntToDoubleFunction等。
在必要的情况下,我们也可以自己定义一个函数式接口,请记住,(T,U) -> R的表达方式展示了对一个函数的简单描述,箭头的的左侧代表了参数类型,右侧代表着返回类型,这里它代表一个函数,具有两个参数,分别为泛型T和U,返回类型为R。
函数式接口是不允许抛出 受检异常(checked exception),但是有两个方法可以抛出异常:
类型检查
java7是通过泛型从上下文推断类型,lambda的类型检查是通过它的上下文推断出来的。lambda会找到它所在的方法的方法签名,也就是它的参数,也就是他们说的目标类型,再找到这个方法中定义的抽象方法,这个方法描述的函数描述符是什么?也就是这个方法是个什么样的,接受什么参数,返回什么。lambda也必须是符合这样的。当lambda抛出异常的时候,那个抽象方法也必须要抛出异常。
有了目标类型,那么同一个lambda就可以与不同的函数式接口联系起来。只要他们的抽象方法签名是一样的。
例如:
Callable<Integer> c = () -> 42; PrivilegedAction<Integer> p = () -> 42;
这两个接口都是没有参数,且返回一个泛型T的函数。 void兼容规则 lambda的主题是一个语句表达式,和一个返回void的函数描述符兼容,包括参数列表, 比如下面:
// Predicate返回了一个boolean Predicate<String> p = s -> list.add(s); // Consumer返回了一个void Consumer<String> b = s -> list.add(s);
在lambda中使用局部变量
final int local_value = 44; Consumer<String> stringConsumer = (String s) -> { int new_local_value = s.length() + local_value; };
在lambda中可以无限制的使用实例变量和静态变量,但是只能是final的,如果在表达式里面给变量赋值,就会编译不通过。为什么会有这样的呢?
因为实例变量存储在堆中,局部变量存储在栈中,lambda是在一个线程中,如果lambda可以直接访问局部变量,lambda的线程可能会在分配该变量的线程将这个变量回收之后,再去访问该变量。在访问局部变量的时候,实际上是访问他的副本,而不是原始变量。
方法引用
方法引用,方法目标实体放在::的前面,方法名放在后面。比如 Apple::getWeight,不需要括号。
构造函数是可以利用它的名称和关键字 new来创建一个引用。
//Supplier也是一个函数式接口,唯一的抽象方法不接受参数,直接返回一个对象 Supplier<Apple> sup = Apple::new; Apple apple = sup.get();
但是如果是有参数的呢?
//一个参数 Function<Long, Apple> fun = Apple::new; Apple apple1 = fun.apply(110L); //两个参数 BiFunction<Long, String, Apple> biFunction = Apple::new; Apple biApple = biFunction.apply(3L, "red");
但是如果有三个参数、四个参数呢?我们上面说了怎么样可以自定义一个自己需要的函数式接口。
@FunctionalInterface public interface AppleWithParam<T, U, V, R> { R apply(T t, U u, V v); }
总结:
以上所述是小编给大家介绍的Lambda表达式详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对亿速云网站的支持!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。