在了解lambda表达式之前,必须先了解函数式编程、函数式接口和default方法。在Java8出来之前,别的编程语言中已经有了函数式编程这种概念,只不过后来没落了,不过在最近几年又火起来了。在Java8中大力提倡我们使用函数式编程,并且更新了一些类(java.utl.function),让我们根据这些类来编程。
目录
一、有关函数式编程、函数式接口和default方法的介绍
1.什么是函数式编程?
函数式编程是种编程范式,与之相对应的是命令式编程,下面举个例子来对比一下,比如计算(1+1)*2-3的结果:
在命令式编程中可以这样写: int a = (1+1)*2-3;为了体现命令式和函数式的区别,用以下的方式方便理解:
int a = 1 + 1 ;
int b = a * 2 ;
int c = b - 3 ;
函数式编程:
subtract(multiply(add(1,1),2),3);
通过上面这个例子可以看出,函数式编程大量使用的是函数+表达式,不使用语句,因此函数式编程的代码会看起来很精简。
2.什么是函数式接口?
函数式编程是一个很早就提出来的概念,不过函数式接口是Java8中新增的,那什么是函数式接口呢?在一个接口中只有一个抽象方法的接口被称为函数式接口,比如说java.lang.Runnable、java.util.concurrent.Callable。对比看这两个接口可以发现,接口定以前都有一个@FunctionalInterface注解,这是Java8中新增的注解,用来标注一个函数式接口。
自定义一个函数式接口:
/**
* 函数式接口
*/
@FunctionalInterface
public interface MyInterface {
void method01(); //修饰符默认为public abstract,省略不写
}
3.default方法又是什么?
这个方法也是Java8中新增的方法,在jdk8之前,接口中的方法必须都是抽象的,在jdk8中允许接口中定义非抽象方法,在接口中的非抽象方法使用default修饰即可,比如在jdk8中新增一个函数式接口:java.util.function。Java8中打破了接口中的方法必须都是抽象这一规范,有一个好处就是可以提高程序的兼容性,在java.lang.Iterable接口中新增了foreach方法,该方法就是使用default修饰的。
在接口中定义default方法可以变相的让Java支持“多继承”。
下面代码是jdk8中java.lang.Iterable的源码(一共有3个方法, 除了iterator(),另外两个方法都是被default修饰的):
package java.lang;
import java.util.Iterator;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
/**
* Implementing this interface allows an object to be the target of
* the "for-each loop" statement. See
* <strong>
* <a href="{@docRoot}/../technotes/guides/language/foreach.html">For-each Loop</a>
* </strong>
*
* @param <T> the type of elements returned by the iterator
*
* @since 1.5
* @jls 14.14.2 The enhanced for statement
*/
public interface Iterable<T> {
/**
* Returns an iterator over elements of type {@code T}.
*
* @return an Iterator.
*/
Iterator<T> iterator();
/**
* Performs the given action for each element of the {@code Iterable}
* until all elements have been processed or the action throws an
* exception. Unless otherwise specified by the implementing class,
* actions are performed in the order of iteration (if an iteration order
* is specified). Exceptions thrown by the action are relayed to the
* caller.
*
* @implSpec
* <p>The default implementation behaves as if:
* <pre>{@code
* for (T t : this)
* action.accept(t);
* }</pre>
*
* @param action The action to be performed for each element
* @throws NullPointerException if the specified action is null
* @since 1.8
*/
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
/**
* Creates a {@link Spliterator} over the elements described by this
* {@code Iterable}.
*
* @implSpec
* The default implementation creates an
* <em><a href="Spliterator.html#binding">early-binding</a></em>
* spliterator from the iterable's {@code Iterator}. The spliterator
* inherits the <em>fail-fast</em> properties of the iterable's iterator.
*
* @implNote
* The default implementation should usually be overridden. The
* spliterator returned by the default implementation has poor splitting
* capabilities, is unsized, and does not report any spliterator
* characteristics. Implementing classes can nearly always provide a
* better implementation.
*
* @return a {@code Spliterator} over the elements described by this
* {@code Iterable}.
* @since 1.8
*/
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
自定义一个由default方法的接口:
@FunctionalInterface
public interface MyInterface {
double method01(double a, double b); //抽象方法
default String method02() { //default方法
return "hello";
}
default int method03(int a) { //default方法
return a*a;
}
}
二、lambda表达式的介绍
1.lambda表达式是啥?
可以将lambda表达式看作是一个匿名方法,lambda表达式只能用于函数式接口。主要由()->{}这些符号组成,()里放的是参数(列表),{}里放的是代码块(和返回值)。使用lambda表达式可以编写出比匿名内部类更简洁的代码。
使用lambda表达式的例子(用到了上面的例子中函数式接口MyInterface):
public class TestLambda01 {
public static void main(String[] args) {
//不使用lambda表达式,使用匿名内部类创建MyInterface的对象
MyInterface mi1 = new MyInterface() {
@Override
public double method01(double a, double b) {
System.out.println("no lambda:");
return a+b;
}
};
//使用lambda表达式(接口一定要是函数式接口)
MyInterface mi2 = (double a, double b) -> {
System.out.println("lambda:");
return a+b;
};
//使用lambda表达式,省略参数类型(编译器都可以从上下文环境中推断出lambda表达式的参数类型,因此可以省略参数类型)
MyInterface mi3 = (a, b) -> {
System.out.println("lambda:");
return a+b;
};
//使用lambda表达式,省略return关键字(代码块只有一行代码时,{}可以省略)
MyInterface mi4 = (a, b) -> a+b;
}
}
使用lambda表达式创建线程的例子:
public class TestLambda02 {
public static void main(String[] args) {
//不使用lambda表达式创建线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("no lambda");
}
}).start();
//使用lambda表达式
//因为Thread类中接收Runnable类型的对象,所以编译器会识别出lambda表达式是Runnable对象
new Thread(() -> System.out.println("lambda")).start();
}
}
lambda表达式为什么只能用于函数式接口中?
lambda表达式实际上就是重写了接口中的抽象方法,在函数式接口中只有一个抽象方法,此时编译器会认定重写的就是该唯一的抽象方法。倘若一个接口中有多个抽象方法,而lambda表达式是没有匿名的,编译器就无法判断其重写的是哪个抽象方法了。
2. forEach方法
在jdk8中的java.lang.Iterable接口中新增了非抽象的forEach方法,可以使用该方法配合lambda表达式来遍历集合。
// 使用forEach方法+lambda表达式遍历集合
public class TestLambda03 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>(6);
list.add(3);
list.add(8);
list.add(2);
list.add(7);
list.add(5);
list.add(8);
//以前遍历集合的一种方式
for (Integer i:list) {
System.out.println(i);
}
//使用forEach方法+lambda表达式遍历集合
//list.forEach((i) -> System.out.println(i));
//只有一个参数时,可以省略()
list.forEach(i -> System.out.println(i));
}
}
以上使用lambda表达式之后已经够简洁,但是上述的最后一条语句还可以简介,当lambda表达式只有一行代码时,可以使用方法的引用,上述可以写成:
list.forEach(System.out :: println);
3.方法引用
方法引用主要是用来简写lambda表达式,有些lambda表达式里面仅仅是执行一个方法调用,这时使用方法引用可以简写lambda表达式。
四种类型的方法引用:
①静态方法引用,类名::方法名
②某个对象的引用,对象变量名::方法名
③特定类的任意对象的方法引用,类名::方法名
④构造方法,类名::new
前三种比较好理解,就举一个静态方法引用的例子:
public class TestLambda04 {
public static void main(String[] args) {
Integer[] arr = {4,8,2,9,6,5,1}; //对该数组排序
//不使用lambda表达式
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer x, Integer y) {
return Integer.compare(x,y);
}
});
//lambda表达式
Arrays.sort(arr,(x,y) -> Integer.compare(x,y));
//上述只有 Integer.compare(x,y)一条语句,所以可以使用方法的引用,改为:
Arrays.sort(arr,Integer :: compare);
}
}
构造方法引用例子:
import java.util.function.Supplier;
public class Car {
public static Car buy(Supplier<Car> s) {
return s.get(); //通过get方法获取传入的Car类型的对象
}
}
// 构造方法引用
public class Testlambda05 {
public static void main(String[] args) {
Car car = Car.buy(Car::new);
}
}