java8新特性专题之三、lambda四大内置函数式接口
lambda表达式
什么是Lambda表达式
可以把Lambda表达式理解为可传递的匿名函数的一种方式
它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。这个定义够大的,让我们慢慢道来。
学习lambda表达式就要先知道函数式接口是什么?
函数式接口
函数式接口(Functional Interfaces):如果一个接口定义个唯一一个抽象方法,那么这个接口就成为函数式接口。同时,引入了一个新的注解:@FunctionalInterface。可以把他它放在一个接口前,表示这个接口是一个函数式接口。这个注解是非必须的,只要接口只包含一个方法的接口,虚拟机会自动判断,不过最好在接口上使用注解 @FunctionalInterface 进行声明。在接口中添加了 @FunctionalInterface 的接口,只允许有一个抽象方法,否则编译器也会报错。
/** * 函数式接口 */ @FunctionalInterface public interface Show { //只有有个没有返回值,没有参数的方法 void print(); }
Lambda表达式:可以让你的代码更加的简洁。lambda无法单独出现,需要一个函数式接口来盛放,可以说lambda表达式方法体是函数式接口的实现,lambda实例化函数式接口,可以将函数作为方法参数,或者将代码作为数据对待。
主要优点:
1.代码变得更加简洁紧凑
2.可读性强,
3.并行操作大集合变得很方便,可以充分发挥多核cpu的优势,更加便于多核处理器编写代码等.
下是lambda表达式的重要特征:(对比案例,回过头,看下面的解释)
可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
我们看来看看,如何实际实用lambda表达式.
1使用案例
//先定义一个函数式接口 @FunctionalInterface public interface Show { void print(); } //建立一个测试类. 定义方法,参数是接口 public void test(Show show){ show.print(); } //1111正常情况下,我们是这样使用的 test(new Show() { @Override public void print() { System.out.println("哈哈哈哈"); } }); //2222换成Lambda表达式以后,变成下面这个样子. test(() ->{ System.out.println("哈哈哈哈"); });
以上两个输出语句是完全等价的.
但是第二个看起来就简单了很多.
2来一个带参数的用法
@FunctionalInterface public interface Show2 { void print(String s); }
仔细看 s 变量是没有括号的.
一个变量可以省略括号. 两个以上必须有.
test2(s -> { System.out.println(s); }); //和上面等价 test2((s) -> { System.out.println(s); });
3来一个带参数和返回值的
@FunctionalInterface public interface Show3 { int add(int a ,int b); } //调用 //定义方法,参数是接口 public void test3(Show3 show3){ int add = show3.add(1, 1); System.out.println(add); } //正常用法 test3(new Show3() { @Override public int add(int a, int b) { return a+b; } }); //Lambda写法 test3((a,b) -> a+b); //贼简单
方法引用
你为什么应该关心方法引用?方法引用可以被看作仅仅调用特定方法的Lambda的一种快捷写法。它的基本思想是,如果一个Lambda代表的只是“直接调用这个方法”,那最好还是用名称来调用它,而不是去描述如何调用它。事实上,方法引用就是让你根据已有的方法实现来创建Lambda表达式。但是,显式地指明方法的名称,你的代码的可读性会更好。它是如何工作的呢?
当你需要使用方法引用时,目标引用放在分隔符::前,方法的名称放在后面。例如,Apple::getWeight就是引用了Apple类中定义的方法getWeight。请记住,不需要括号,因为你没有实际调用这个方法。方法引用就是Lambda表达式(Apple a) -> a.getWeight()的快捷写法。
下图给出了Java 8中方法引用的其他一些例子。
我们把test2的代码用方法引用写一下:
//原来的 test2((s) -> { System.out.println(s); }); //改变后 test2(System.out::println);
你可能觉得奇怪,这是什么玩意呢.两个冒号就能输出了.
仔细看System.out::println 两个冒号之前是一个类, 冒号之后是一个方法.
如何构建方法引用
方法引用主要有三类。
(1) 指向静态方法的方法引用(例如Integer的parseInt方法,写作Integer::parseInt)。
(2) 指 向 任意类型实例方法 的方法引用(例如 String 的 length 方法,写作String::length)。
(3) 指向现有对象的实例方法的方法引用(假设你有一个局部变量expensiveTransaction用于存放Transaction类型的对象,它支持实例方法getValue,那么你就可以写expensiveTransaction::getValue)。
第二种和第三种方法引用可能乍看起来有点儿晕。类似于String::length的第二种方法引用的思想就是你在引用一个对象的方法,而这个对象本身是Lambda的一个参数。例如,Lambda表达式(String s) -> s.toUppeCase()可以写作String::toUpperCase。但第三种方法引用指的是,你在Lambda中调用一个已经存在的外部对象中的方法。例如,Lambda表达式()->expensiveTransaction.getValue()可以写作expensiveTransaction::getValue。依照一些简单的方子,我们就可以将Lambda表达式重构为等价的方法引用,
请注意,还有针对构造函数、数组构造函数和父类调用(super-call)的一些特殊形式的方法
引用。
跳过理论看案例
让我们举一个方法引用的具体例子吧。
比方说你想要对一个字符串的List排序,忽略大小写。List的sort方法需要一个Comparator作为参数。你在前面看到了,Comparator描述了一个具有(T, T) -> int签名的函数描述符。你可以利用String类中的compareToIgnoreCase方法来定义一个Lambda表达式(注意compareToIgnoreCase是String类中预先定义的)。
使用正常的流程来实现这个功能
//使用正常的流程来实现这个功能 List<String> list = Arrays.asList("a", "b", "C", "A", "B"); list.sort(new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.compareToIgnoreCase(o2); } }); for (String s : list) { System.out.println(s); }
使用Lambda表达式完成
List<String> list = Arrays.asList("a", "b", "C", "A", "B"); list.sort((s1,s2)-> s1.compareToIgnoreCase(s2)); list.forEach(s -> {System.out.println(s);});
在加上lambda的方法引用
List<String> list = Arrays.asList("a", "b", "C", "A", "B");
list.sort(String::compareToIgnoreCase);
list.forEach(System.out::println);
完蛋了. 太方便了. 一旦学会了. 就再也回不去了.
到目前为止,我们只展示了如何利用现有的方法实现和如何创建方法引用。但是你也可以对类的构造函数做类似的事情。
构造函数引用
对于一个现有构造函数,你可以利用它的名称和关键字new来创建它的一个引用:
ClassName::new。它的功能与指向静态方法的引用类似。例如,假设有一个构造函数没有参数。
它适合Supplier的签名() -> Apple。你可以这样做:
此次的Supplier接口也是java8的新特性. 暂时先忽略,一会说.
函数式接口
Java 8的库设计师帮你在java.util.function包中引入了几个新的函数式接口.
内置4大接口
Predicate:断言型接口
java.util.function.Predicate接口定义了一个名叫test的抽象方法,它接受泛型T对象,并返回一个boolean。这恰恰和你先前创建的一样,现在就可以直接使用了。在你需要表示一个涉及类型T的布尔表达式时,就可以使用这个接口。比如,你可以定义一个接受String对象的Lambda表达式,
如下所示。
案例:
比如要从个集合中.打印所有字符串的长度大于2的…
老方法这么写:
List<String> list = Arrays.asList("a11", "b232", "C111", "A1", "B"); for (String s : list) { if(s.length() > 2){ System.out.println(s.length()); } }
Lambda表达式这么写:
List<String> list = Arrays.asList("a11", "b232", "C111", "A1", "B");
list.stream().filter((s)->s.length()>2).forEach(System.out::println);
stream 这个流先不用管. 下一遍文章分析.
主要看这段 (s)->s.length()>2 .
这就是用Predicate接口来实现的.
Consumer 接口(消费者)
consumer接口:对输入的参数进行操作。有输入没输出.
Consumer<String> consumer3 = s -> { String s1 = s.toUpperCase(); System.out.println(s1); };
只有调用了,控制台才有输出
consumer.accept("ss");
其实,主要是理解Consumer,消费者,就可以了~主要是对入参做一些列的操作,在stream里,主要是用于forEach;内部迭代的时候,对传入的参数,做一系列的业务操作,没有返回值;
Supplier (提供者)
看语义,可以看到,这个接口是一个提供者的意思,只有一个get的抽象类,没有默认的方法以及静态的方法,传入一个泛型T的,get方法,返回一个泛型T.
Supplier<Food> supplier = Food::new; Food food = supplier.get(); food.setRet(1); int ret = food.getRet();
可以看到,这个接口,只是为我们提供了一个创建好的对象,这也符号接口的语义的定义,提供者,提供一个对象,
直接理解成一个创建对象的工厂,就可以了.
Function接口以及同类型的特化的接口
先看看Function的定义
@FunctionalInterface public interface Function<T, R> { R apply(T t); default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); } default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } static <T> Function<T, T> identity() { return t -> t; }
看看Function的作用
Funtion接口,定义了一个apply的抽象方法,接收一个泛型T对象,并且返回泛型R对象.
看看实例.
Function<Integer,String> function = (i) ->{ if(i == 1){ return "1月"; }else if(i == 2){ return "2月"; } return "没有"; }; System.out.println(function.apply(1));
下面我们看下Funtion这个接口的“扩展”的原始类型特化的一些函数接口
IntFunction,IntToDoubleFunction,IntToLongFunction,LongFunction,LongToDoubleFunction,LongToIntFunction,DoubleFunction,ToIntFunction,ToDoubleFunction,ToLongFunction
我们知道,我们在做基础数据处理的时候(eg: Integer i=0; Integer dd= i+1;),会对基础类型的包装类,进行拆箱的操作,转成基本类型,再做运算处理,拆箱和装箱,其实是非常消耗性能的,尤其是在大量数据运算的时候;这些特殊的Function函数式接口,根据不同的类型,避免了拆箱和装箱的操作,从而提高程序的运行效率.
看到这里可能还是看的不明白,也不知道他们的具体用处.
来源:https://blog.csdn.net/shuai_ge_feng/article/details/115127607?spm=1001.2101.3001.6650.3&utm_medium=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-3.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-3.pc_relevant_default&utm_relevant_index=6