JDK 8 新特性 lambda

引言

在 JDK 8 之前接口怎么实例化呢?

  1. 正经方式是定义一个类去实现接口,然后实现接口中的方法;
public class Test implements Runnable{
    public static void main(String[] args) {
        new Thread(new Test()).start();
    }
    @Override
    public void run() {
        System.out.println("thread name " + Thread.currentThread().getName() + " is running.");
    }
}
  1. 匿名内部类的方式去实现接口,这种方式与第一种相比会少一个 .java 的源文件,但编译后也会产生字节码文件,文件名格式是类名 $ 加上从 1 开始的编号;
public class Test {
    public static void main(String[] args) {
        new Thread(new Runnable(){
            @Override
            public void run() {
                System.out.println("thread name " + Thread.currentThread().getName() + " is running.");
            }
        }).start();
    }
}

在这里插入图片描述
3. lambda 方式实现接口,这种方式编译后不会产生 .class 文件,且你不用指明该类要去实现什么方法,从代码量上可以感受到它使代码更优雅的特性。

public class Test {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("thread name " + Thread.currentThread().getName() + " is running."));
    }
}

概念

lambda 是 JDK 8 之后的新特性,可以取代大部分需要用到匿名内部类的场景,常用在集合的遍历操作中。
它可以对函数式接口做一个简单的实现,但不是所有的接口实现都能使用 lambda 去编写。它要求被实现的接口只能有一个抽象方法(函数式接口)。
JDK 提供的内置函数式接口之一:

// 该注解通常和 lambda 一同出现,它表明该接口的实现可以使用简洁的 lambda 实现
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
    // default 方法是 jdk 8 之后有的特性
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> {
            accept(t); 
            after.accept(t);
       };
    }
}

语法

正常版

(Object a,Object b,Object c,...) -> {...}
小括号里面就是被重写的方法需要的各参数值
花括号里面就是实现类要编写的方法实现逻辑

lambda 表达式其实就是函数式编程思想的落地,所以 lambda 就是编写一个 Java 中的方法,既然是方法,那么它就有参数列表(小括号里面的内容),也有方法体(花括号中的内容)。

简化版

  • 参数列表中的所有参数的类型可以一起省略;
(a, b, c,...) -> {...}
@FunctionalInterface
public interface Running {
    void speed(String day, Integer speed);
}
public class Test{
    public static void main(String[] args) {
        Running running = (day, speed) -> {
            System.out.println(day + "---" + speed);
        };
        running.speed("20200123", 200);
    }
}
  • 当参数列表就一个参数时,可以省略 () ;
a -> {...}
@FunctionalInterface
public interface Running {
    void speed(String day);
}
public class Test{
    public static void main(String[] args) {
        Running running = day -> {
            System.out.println(day);
        };
        running.speed("20200123");
    }
    
    public void method(String day) {
        System.out.println(day);
    }
}
  • 当方法体只有一条语句时,可以省略 {},如果这个方法有返回值,那么该条语句前面会默认加上 return 关键字。
public class Test{
    public static void main(String[] args) {
        Running running = day -> System.out.println(day);
        running.speed("20200123");
    }
}
  • 如果你重写的那个匿名内部类的方法,有一个方法已经帮你实现了,此时我们就没必要再去实现。语法上分两种引用写法,分别是静态引用和实例引用两种:
public class Test{
    public static void main(String[] args) {
        //Running running = day -> System.out.println(day);
        //静态引用
        Running running = System.out::println;
        //实例引用
        Running running = new Test()::method;
        running.speed("20200123");
    }
    
    public void method(String day) {
        System.out.println(day);
    }
}

常用示例

1. 在集合中的应用举例(ArrayList )

forEach(Consumer<? super E> action) 迭代

集合遍历的业务代码

public class App {
    static List<String> list = new ArrayList<>();
    public static void main(String[] args) {
        list.add("上海");
        list.add("北京");
        list.add("广州");
        list.add("深圳");
        // jdk 8 之前匿名内部类写法
        list.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });
        // 普通 lambda 写法
        list.forEach(s -> {
            System.out.println(s);
        });
        // 简便写法,如果实现类重写的方法是其他类已经实现的类方法
        list.forEach(System.out::println);
        // 简便写法,如果实现类重写的方法是其他实例已有的方法
        list.forEach(new App()::method);
    }
    // 实例方法
    public void method(String ele) {
        System.out.println(ele);
    }
}
// ArrayList 复写 Iterable 接口的 forEach 方法
@Override
public void forEach(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    final int expectedModCount = modCount;
    @SuppressWarnings("unchecked")
    final E[] elementData = (E[]) this.elementData;
    final int size = this.size;
    // fori 循环依次获取每个元素传递给 action 实现的 accept 方法
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        action.accept(elementData[i]);
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

代码解读:
forEach 方法需要传递 Consumer 对象,所以需要声明一个实现了 Consumer 接口的类型,你可以单独写个类去做实现,一般都使用匿名内部类的方式做的实现,但在 JDK 8 之后可以使用 lambda 表达式对函数式接口做个简单的实现。
下面是 foreach 中最主要的两行代码:

action.accept(elementData[i]);
// elementData[i] 会传递给 s,而 s 就是方法的参数,所以方法体能拿到这个变量的引用
list.forEach(s -> System.out.println(s));
removeIf(Predicate<? super E> filter) 删除元素

业务代码

// 匿名内部类写法
list.removeIf(new Predicate<String>() {
    @Override
    public boolean test(String s) {
        return s.equals("上海");
    }
});
// lambda 写法
list.removeIf(s -> s.equals("上海"));
@Override
public boolean removeIf(Predicate<? super E> filter) {
    ...
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        @SuppressWarnings("unchecked")
        final E element = (E) elementData[i];
        if (filter.test(element)) {
            removeSet.set(i);
            removeCount++;
        }
    }
    ...
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值