程序员Java学习笔记

Java8:4个例子说明lamdba表达式

2017-07-17  本文已影响481人  蓝汝丶琪

关于lamdba表达式,网上文章很多,我也看了很多篇文章,不过在实际编程中,却怎么也运用不到实际中,因为没有系统地整理过这些知识块。所以,就利用一个下午,去专门整理一下,把自己的理解写出来。若有错误,或者补充,欢迎评论。
本篇先以运用为开头,原理在后文。先用4个例子来运用一下lamdba表达式。文章顺序如下:

4个例子:

Predicate<T>接口:

/**
     * 找到列表里面的是给我丶鼓励字符串就输出
     * @param list
     * @param pre Predicate接口是函数式接口,test(T t)方法返回值是boolean
     * @param <T>
     */
 public static <T > void testPredicate(List<T> list, Predicate<T> pre){
            for(T s:list){
                if (pre.test(s)){
                    System.out.println("这就是你要找的"+s);
                }}}

   public static void main(String[] ages){
            List<String> list=new ArrayList<>();
            list.add("无敌小脾气");
            list.add("无敌小任性");
            list.add("开心小程序");
            list.add("给我丶鼓励");

            //用lamdba重写test()方法, s.equals(xxxx) 就是重写test()方法的内容
            testPredicate(list,(s)->s.equals("给我丶鼓励"));
            //使用Predicate的默认方法   从jdk1.8 api 可以看到接口里具有默认方法,.isEqual()就是一个默认方法
            testPredicate(list,Predicate.isEqual("开心小程序"));
}

输出

这就是你要找的给我丶鼓励
这就是你要找的开心小程序

Consumer<T>接口

- 关注的点
   - 也是java.util.function 里面的函数式接口。
   - 该接口里面的accept(T t)方法返回值是void。
 /**
     * 将list列表的数据输出
     * @param list
     * @param con  Consumer是一个函数式接口,accept方法返回值是void
     * @param <T>
     */
    public static <T> void testConsumer(List<T> list, Consumer<T> con){
        for(T s:list){
            con.accept(s);
        }
    }

 public static void main(String[] ages){
            System.out.println("ok");
            List<String> list=new ArrayList<>();
            list.add("无敌小脾气");
            list.add("无敌小任性");
            list.add("开心小程序");
            list.add("给我丶鼓励");


            //用lamdba重写accept()方法
            testConsumer(list,s-> System.out.println(s));
            //用方法引用
            testConsumer(list,System.out::println);
}

Function<T,R>接口

 /**
     * 每个列表元素最后一个字凑成一个字符串输出
     *
     */
public static <T,R> void testFunction(List<T> list, Function<T,R> fun){
        StringBuffer stringBuffer=new StringBuffer();
        for(T s:list){
            //将每个list对象的最后一个字符提取出来放到stringBuffer变量里。
            stringBuffer.append(fun.apply(s));
        }
        System.out.println(stringBuffer);
    }

 public static void main(String[] ages){
            System.out.println("ok");
            List<String> list=new ArrayList<>();
            list.add("无敌小脾气");
            list.add("无敌小任性");
            list.add("开心小程序");
            list.add("给我丶鼓励");
            //用lamdba重写apply()方法
            testFunction(list,s->s.charAt(s.length()-1));
}

自定义接口

//自定义函数式接口
@FunctionalInterface
interface mylamdba<T>{
    //抽象方法
    abstract <T> void mylamdba(T s);
    //默认方法
    default String getName(Object obj){
        return obj.toString();
}}


public static <T> void testMy(List<T> list,mylamdba<T> my){
        for (T s:list) {
           //调用自定义接口的抽象方法。将用lambda方法重写
            my.mylamdba(s);
        }
    }

 public static void main(String[] ages){
            System.out.println("ok");
            List<String> list=new ArrayList<>();
            list.add("无敌小脾气");
            list.add("无敌小任性");
            list.add("开心小程序");
            list.add("给我丶鼓励");
            //自己写的一个函数式接口
            testMy(list,System.out::println);
}

总结

lambda的类型检查,推断过程。

上下文:比如接受它传递的方法的参数,或接受他的值的局部变量。

以第一个例子为例.png

lamdba的局部变量

public class Hello {
  Runnable r1 = () -> { System.out.println(this); }
  Runnable r2 = () -> { System.out.println(toString()); }
  public String toString() {  return "Hello, world"; }
  public static void main(String... args) {
    new Hello().r1.run();
    new Hello().r2.run();
  }
}
输出:
Hello$1@5b89a773 和 Hello$2@537a7706

因为toString()可以当做是方法内存块的变量,该变量指向一个堆地址。所以在lamdba里面,输出的是该变量指向的地址。

int sum = 0;
list.forEach(e -> { sum += e.size(); }); // 对值封闭,编译出错
List<Integer> aList = new List<>();
list.forEach(e -> { aList.add(e); }); // 对变量开放,编译通过

Lambda表达式 vs 匿名类

既然lambda表达式即将正式取代Java代码中的匿名内部类,那么有必要对二者做一个比较分析。

方法引用的种类

方法引用有很多种,它们的语法如下:

对于具体对象上的实例方法引用,我们则需要在对象名和方法名之间加入分隔符:

Set<String> knownNames = ...
Predicate<String> isKnown = knownNames::contains;

这里的隐式 lambda 表达式(也就是实例方法引用)会从 knownNames 中捕获 String 对象,而它的方法体则会通过Set.contains 使用该 String 对象。


有了实例方法引用,在不同函数式接口之间进行类型转换就变的很方便:

Callable<Path> c = ...
Privileged<Path> a = c::call;

引用任意对象的实例方法则需要在实例方法名称和其所属类型名称间加上分隔符:

Function<String, String> upperfier=String::toUpperCase;

这里的隐式 lambda 表达式(即 String::toUpperCase 实例方法引用)有一个 String 参数,这个参数会被 toUpperCase 方法使用。

数组的构造方法引用的语法则比较特殊,为了便于理解,你可以假想存在一个接收 int 参数的数组构造方法。参考下面的代码:

IntFunction<int[]> arrayMaker = int[]::new;
int[] array = arrayMaker.apply(10) // 创建数组 int[10]

例子

我们通过一个实际例子(按照姓对名字列表进行排序)来演示这一点:

List<Person> people = ...
Collections.sort(people, new Comparator<Person>() {
public int compare(Person x, Person y) {
return x.getLastName().compareTo(y.getLastName());
   }
})

冗余代码实在太多了!有了lambda表达式,我们可以去掉冗余的匿名类:

Collections.sort(
people, (Person x, Person y) -> x.getLastName().compareTo(y.getLastName()));

在类型推导和静态导入的帮助下,我们可以进一步简化上面的代码:

Collections.sort(people, comparing(p -> p.getLastName()));

我们注意到这里的 lambda 表达式实际上是 getLastName的代理(forwarder),于是我们可以用方法引用代替它:

Collections.sort(people, comparing(Person::getLastName));

最后,使用 Collections.sort 这样的辅助方法并不是一个好主意:它不但使代码变的冗余,也无法为实现 List
接口的数据结构提供特定(specialized)的高效实现,而且由于 Collections.sort方法不属于 List接口,用户在阅读 List接口的文档时不会察觉在另外的 Collections 类中还有一个针对 List 接口的排序(sort())方法。
默认方法可以有效的解决这个问题,我们为 List增加默认方法 sort(),然后就可以这样调用:

people.sort(comparing(Person::getLastName));

此外,如果我们为 Comparator接口增加一个默认方法 reversed()(产生一个逆序比较器),我们就可以非常容易的在前面代码的基础上实现降序排序。

people.sort(comparing(Person::getLastName).reversed());
上一篇 下一篇

猜你喜欢

热点阅读