Java8学习(3)- Lambda 表达式
来源:互联网 发布:linux 网络性能测试 编辑:程序博客网 时间:2024/05/21 17:22
猪脚:以下内容参考《Java 8 in Action》
本次学习内容:
- Lambda 基本模式
- 环绕执行模式
- 函数式接口,类型推断
- 方法引用
- Lambda 复合
代码: https://github.com/Ryan-Miao/someTest/blob/master/src/main/java/com/test/java8/c3/AppleSort.java
上一篇: Java8学习(2)- 通过行为参数化传递代码--lambda代替策略模式
1. 结构
初始化一个比较器:
Comparator<Apple> byWeight = new Comparator<Apple>() { public int copare(Apple a1, Apple a2){ return a1.getWeight().compareTo(a2.getWeight() ); }}
使用Lambda表达式:
Comparator<Apple> byWeight = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight() );
- 参数列表--compare方法的的两个参数
- 箭头 --- 把参数列表与lambda主体分割开
- Lambda主体 --- 表达式的值就是Lambda的返回值
1.1 Java8中有效的Lambda表达式
接收一个字符串,并返回字符串长度int
(String a) -> s.length()
接收一个Apple类参数,返回一个boolean值
(Apple a) -> a.getWeight() > 150
接收两个参数,没有返回值(void),多行语句需要用大括号包围
(int x, int y) -> { System.out.println("Result:"); System.out.println(x + y);}
不接收参数,返回一个值
()-> 42
接收两个参数,返回一个值
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight() );
1.2 Lambda的基本语法
(parameters) -> expression或(parameters) -> {statements}
2. 函数式接口
在上次的学习中的通过行为参数化传递代码, Predicate(T)
就是一个函数式接口:
public interface Predicate<T> { boolean test(T t);}
函数式接口就是只定义一个抽象方法的接口。
Java API中很多符合这个条件。比如:
public interface Comparable<T> { public int compareTo(T o);}public interface Runnable { public abstract void run();}@FunctionalInterfacepublic interface Callable<V> { V call() throws Exception;}
2.1 函数式接口可以做什么
Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把表达式作为函数式接口的实例(函数式接口一个具体实现的实例)。就像内部类一样,但看起来比内部类简洁。
Runnable r1 = () -> System.out.println("1");Runnable r2 = new Runnable(){ public void run(){ System.out.println("2"); }};public static void process(Runnable r) { r.run();}process(r1);process(r2);process(() -> System.out.println(3));
@FunctionalInterface
是一个标注,用来告诉编译器这是一个函数式接口,如果不满足函数式接口的条件,编译器就会报错。当然,这不是必须的。好处是编译器帮助检查问题。
3. 一步步修改为Lambda表达式
Lambda式提供了传递方法的能力。这种能力首先可以用来处理样板代码。比如JDBC连接,比如file读写。这些操作会有try-catcha-finally,但我们更关心的是中间的部分。那么,是不是可以将中间的部分提取出来,当做参数传递进来?
3.1 第1步: 行为参数化
下面是读一行:
public String read(){ try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) { return br.readLine(); } catch (IOException e) { e.printStackTrace(); } return null;}
行为参数化就是把一个过程行为转换成参数。在这里就是将br.readLine()
提取成参数。
3.2 第2步:使用函数式接口来传递行为
定义一个接口来执行上述的行为:
public interface BufferedReaderProcessor{ String process(BufferedReader b) throws IOException;}
然后把这个接口当作参数:
public String read(BufferedReaderProcessor p) throws IOException{ try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))){ return p.process(br); }}
3.3 第3步: 传递Lambda
@Testpublic void readFile() throws IOException { String oneLine = read(BufferedReader::readLine); String twoLine = read((BufferedReader b) -> b.readLine() + b.readLine());}
如此,我们就把中间的逻辑抽出来了。把行为抽象成一个接口调用,然后通过Lambda来实现接口的行为。传递参数。完毕。
4. Java API中内置的一些函数式接口
Java API中内置了一些很有用的Function接口。
4.1 Predicate
java.util.function.Predicate<T>
定义了一个抽象方法,返回一个boolean
。
使用demo如下:
private <T> List<T> filter(List<T> list, Predicate<T> p){ List<T> results = new ArrayList<>(); for (T t : list) { if (p.test(t)){ results.add(t); } } return results;}@Testpublic void testPredicate(){ List<String> list = Arrays.asList("aa","bbb","ccc"); List<String> noEmpty = filter(list, (String s) -> !s.isEmpty());}
4.2 Consuer
java.util.function.Consumer<T>
定义了一个抽象方法,接收一个参数。
private <T> void forEach(List<T> list, Consumer<T> c){ for (T t : list) { c.accept(t); }}@Testpublic void testConsumer() { List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5); forEach(integers, System.out::println);}
4.3 Function
java.util.function.Function<T,R>
定义了一个抽象方法,接收一个参数T
,返回一个对象R
private <T,R> List<R> map(List<T> list, Function<T,R> f){ List<R> result = new ArrayList<>(); for (T t : list) { result.add(f.apply(t)); } return result;}@Testpublic void testFunction(){ List<String> strings = Arrays.asList("a", "bb", "ccc"); List<Integer> lengths = map(strings, String::length);}
4.4 基本类型函数接口
前面三个泛型函数式接口Predicate<T>
、Consumer<T>
、Function<T,R>
,这些接口是专门为引用类型设计的。那么基本类型怎么办?我们知道可以自动装箱嘛。但装箱是有损耗的。装箱(boxing)的本质是把原始类型包裹起来,并保存在堆里。因此装箱后的值需要更多的内存,并需要额外的内存搜索来获取包裹的原始值。
Java8为函数式接口带来了专门的版本。
@Testpublic void testIntPredicate() { //无装箱 IntPredicate intPredicate = (int t) -> t%2 == 0; boolean isEven = intPredicate.test(100); Assert.assertTrue(isEven); //装箱 Predicate<Integer> integerPredicate = (Integer i) -> i%2 == 0; boolean isEven2 = integerPredicate.test(100); Assert.assertTrue(isEven2);}
类似的还有:
Java 8中的常用函数式接口
5. Lambda原理
- 编译器可以推断出方法的参数类型,由此可以省略一些样板代码。
- void和其他返回值做了兼容性处理
6. Lambda的局部变量
在Lambda中可以使用局部变量,但要求必须是final的。因为Lambda可能在另一个线程中运行,而局部变量是在栈上的,Lambda作为额外的线程会拷贝一份变量副本。这样可能会出现同步问题,因为主线程的局部变量或许已经被回收了。基于此,必须要求final的。
而实例变量则没问题,因为实例变量存储于堆中,堆是共享的。
7. 方法引用
Lambda表达式可以用方法引用来表示。比如
(String s) -> s.length()==String::length
这是因为可以通过Lambda表达式的参数以及方法来确定一个方法。在这里,每个方法都叫做方法签名
。方法签名由方法名+参数列表唯一确定。其实就是重载的判断方式。
当Lambda的主体只是一个简单的方法调用的时候,我们可以直接使用一个方法引用来代替。方法引用可以知道要接受的参数类型,以及方法体的逻辑。
方法引用结构:类名::方法名
什么可以使用方法引用?
- 静态方法。
- 指向任意类型实例方法的方法引用。
- 指向现有对象的实例方法。
8. 构造函数引用
构造函数可以通过类名::new
的方式引用。
9. Lambda实战
目标: 用不同的排序策略给apple排序。
过程: 把一个原始粗暴的解决方案变得更加简单。
资料: 行为参数化
, 匿名类
,Lambda
, 方法引用
.
最终: inventory.sort(comparing(Apple::getWeight) );
9.1 原始方案
/** * Created by ryan on 7/20/17. */public class AppleSort { private List<Apple> inventory; @Before public void setUp() { inventory = new ArrayList<>(); inventory.add(new Apple("red", 1)); inventory.add(new Apple("red", 3)); inventory.add(new Apple("red", 2)); inventory.add(new Apple("red", 21)); } @Test public void sort_old() { Collections.sort(inventory, new Comparator<Apple>() { @Override public int compare(Apple o1, Apple o2) { return o1.getWeight() - o2.getWeight(); } }); printApples(); } private void printApples() { inventory.forEach(System.out::println); }}
排序首先要注意的一点就是排序的标准。那么要搞清楚为什么这样写?
Comparator定义的其实就是一个方法,此处就是将排序的原则抽取出来。特别符合Lambda的思想!这里先不说Lambda,先说这个方法的作用:定义什么时候发生交换。
跟踪源码可以发现这样一段代码:
//java.util.Arrays#mergeSort(java.lang.Object[], java.lang.Object[], int, int, int, java.util.Comparator)if (length < INSERTIONSORT_THRESHOLD) { for (int i=low; i<high; i++) for (int j=i; j>low && c.compare(dest[j-1], dest[j])>0; j--) swap(dest, j, j-1); return;}
假设比较的两个数为o1
和o2
,并且o1
在o2
前一位(left>right)。如下:
....o1,o2...
compare(o1,o2)
的结果大于0则,o1
和o2
交换。那么,显然,如果
compare(o1,o2) = o1-o2
则说明,前一个值比后一个值大的时候,发生交换。也即大的往后冒泡。就是升序了。
所以:
o1-o2
升序o2-o1
降序
9.2 使用List内置sort
好消息是Java8提供了sort方法给list:java.util.List#sort
:
则原始方案转换为:
@Testpublic void sort1(){ inventory.sort(new Comparator<Apple>() { @Override public int compare(Apple o1, Apple o2) { return o1.getWeight() - o2.getWeight(); } }); printApples();}
9.3 Lambda表达式代替匿名内部类
从之前的学习可以得到,几乎所有的匿名内部类都可以用Lambda表达式替代!
inventory.sort((o1, o2) -> o1.getWeight() - o2.getWeight());
9.4 进一步优化Lambda
Comparator
提供了一个生成Comparator的方法:
public static <T, U extends Comparable<? super U>> Comparator<T> comparing( Function<? super T, ? extends U> keyExtractor){ Objects.requireNonNull(keyExtractor); return (Comparator<T> & Serializable) (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));}
其中,Function<T,R>
已经在前面学习过了,就是一个接受一个参数并返回另一个参数的函数式接口。在本例中,apple.getWeight()
符合接受一个参数apple
返回一个int
。那么,就可以使用这个方法:
inventory.sort(Comparator.comparing((Apple a)->a.getWeight()));
进一步,将Lambda改为方法引用:
inventory.sort(Comparator.comparing(Apple::getWeight));
这里有个问题,记得之前讲的基本类型的自动装箱吗。Apple::getWeight
的返回值是int
。而comparing
的返回值是一个对象。那么,必然要经过自动装箱的过程。所以,应该使用基本类型的函数式接口:
inventory.sort(Comparator.comparingInt(Apple::getWeight));
至此,基本已经改造完毕了。最多就是静态引入comparingInt
方法:
inventory.sort(comparingInt(Apple::getWeight));
目标达到。相比原始方法,不要太简洁!
话说,这种是不是只能默认升序?因此没有任何一个单词可以看出排序规则。
是的,想要降序?
inventory.sort(comparingInt(Apple::getWeight).reversed());
10 复合Lambda
上节看到逆序的方法就是后面追加一个逆序的方法。现在需求变更了。需要先按照颜色排序,然后再按照重量从大到小排序。
10.1 比较器链
这里,一共涉及了3个过程。往常的做法是连续写在一个方法里,或者3个方法连续调用。Lambda提供了类似语句陈述一般的写法。
inventory.sort(comparing(Apple::getColor) .reversed() .thenComparingInt(Apple::getWeight));
10.2 谓词复合
前面的Prediacate
接口包含4个方法:negate
,and
,or
,isEqual
,对应逻辑运算里的取反
,且
,或
,==
。这样,通过复合就可以写出语义声明式的代码:
想要红苹果:
Predicate<Apple> red = apple -> "red".equalsIgnoreCase(apple.getColor());
想要不是红的苹果:
Predicate<Apple> nonRed = red.negate();
想要大的红苹果:
Predicate<Apple> redAndHeavy = red.and(apple -> apple.getWeight() > 150);
想要大的红苹果或者绿色的:
Predicate<Apple> redAndHeavyOrGreen = redAndHeavy.or(apple -> "green".equalsIgnoreCase(apple.getColor()));或者:redAndHeavyOrGreen = ((Predicate<Apple>) apple -> "red".equalsIgnoreCase(apple.getColor())) .and(apple -> apple.getWeight() > 150) .or(apple -> "green".equalsIgnoreCase(apple.getColor()));
10.3 函数复合
f(x) = (x+1) * 2;求 f(2)
普通写法:
Assert.assertEquals(6, f(2));private int f(int x){ return (x + 1) * 2;}
函数式可以这样写:
Function<Integer, Integer> f = x -> x +1;Function<Integer, Integer> g = x -> x * 2;Function<Integer, Integer> h = f.andThen(g);int r = h.apply(2);Assert.assertEquals(6, r);
看起来似乎更麻烦了,但这只是一个举例。事实上,Function提供了连续处理逻辑的能力,可以不断的处理上一次计算的返回值。
比如,封装一个写信的类:
public class Letter { public static String addHeader(String text){ return "From Ryan Miao: " + text; } public static String addFooter(String text) { return text + " Kind regards"; } public static String checkSpelling(String text){ return text.replace("<", "<"); }}@Testpublic void testFunction3(){ Function<String, String> transformationPipeline = ((Function<String, String>)Letter::addHeader) .andThen(Letter::checkSpelling) .andThen(Letter::addFooter); String letter = transformationPipeline.apply("Hello world!"); Assert.assertEquals("From Ryan Miao: Hello world! Kind regards", letter);}
11 小结
- Lambda表达式可以理解为一种匿名函数:它没有名称,但有参数列表、函数主题、返回值类型,可能还有一个可以抛出的异常列表。
- Lambda表达式让你可以更简洁的传递代码。
- 函数式接口就是仅仅声明了一个抽象方法的接口。
- 只有在接受函数式接口的地方才可以使用Lambda表达式。
- Lambda表达式允许你直接内联,为函数式接口的抽象方法提供实现,并且将整个表达式作为函数式接口的一个实例。
- Java 8自带了一些常用函数式接口,放在
java.util.function
里。包括Prediacate<T>
,Function<T,R>
,Supplier<T>
,Consumer<T>
,BinaryOperator<T>
。 - 为了避免装箱操作,对Predicate和Function等通用函数式接口的原始类特殊化:IntPredicate,InToLong等。
- 环绕执行模式(方法的中间代码)可以配合Lambda提高灵活性和可重用性。
- Lambda表达式所需要代表的类型成为目标类型。
- Comparator,Predicate,Function等函数接口都有几个可以用来结合Lambda表达式的默认方法。
- Java8学习(3)- Lambda 表达式
- Java8学习(3)- Lambda 表达式
- java8 Lambda 表达式 学习
- java8学习 -- lambda表达式
- java8学习-Lambda表达式
- Java8 Lambda表达式学习
- java8 Lambda表达式学习
- 学习Java8--Lambda表达式
- java8基础学习-lambda表达式
- 学习java8的lambda表达式
- Java8中Lambda表达式学习
- JAVA8学习之Lambda表达式
- java8 Lambda表达式的学习与测试
- Java8学习笔记之Lambda表达式
- Java8 实战学习 — Lambda 表达式
- Java8学习笔记 — 【Lambda表达式】
- 深入学习java8一(Lambda表达式简介)
- java8之Lambda表达式 3:数据流
- mysql 查询优化01
- 旋转数组的最小数字
- 内核中的
- 深入理解Java之线程池
- 找个更换头像的地方都没有
- Java8学习(3)- Lambda 表达式
- NYOJ 1112 求次数(map容器)
- Play framework使用java代码自定义标签--FastTags
- 《javascript从入门到精通》第三篇 javascript高级应用
- 如何才能用C语言代码帅气地获取现在是今年的第几天呢?
- 挂载nfs磁盘
- [leetcode] 103.Binary Tree Zigzag Level Order Traversal
- qtcretor通过gdb和gdbserver远程调试arm程序
- 哎妈呀