引言
在 JDK 8 之前接口怎么实例化呢?
- 正经方式是定义一个类去实现接口,然后实现接口中的方法;
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.");
}
}
- 匿名内部类的方式去实现接口,这种方式与第一种相比会少一个 .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++;
}
}
...
}