Lambda表达式本质上是匿名方法,其底层还是通过invokedynamic指令来生成匿名类来实现。它提供了更为简单的语法和写作方式,允许你通过表达式来代替函数式接口。在一些人看来,Lambda就是可以让你的代码变得更简洁,完全可以不使用——这种看法当然没问题,但重要的是lambda为Java带来了闭包。得益于Lamdba对集合的支持,通过Lambda在多核处理器条件下对集合遍历时的性能提高极大,另外我们可以以数据流的方式处理集合——这是非常有吸引力的。
编译器负责推导lambda表达式的类型。它利用lambda表达式所在上下文所期待的类型进行推导,这个被期待的类型被称为目标类型。lambda表达式只能出现在目标类型为函数式接口的上下文中。
当然,lambda表达式对目标类型也是有要求的。编译器会检查lambda表达式的类型和目标类型的方法签名(method signature)是否一致。当且仅当下面所有条件均满足时,lambda表达式才可以被赋给目标类型T:
由于目标类型(函数式接口)已经“知道”lambda表达式的形式参数(Formal parameter)类型,所以我们没有必要把已知类型再重复一遍。也就是说,lambda表达式的参数类型可以从目标类型中得出:
可以看到,在lambda中的参数列表,有些时候可以不用写参数的类型,跟java7中 new ArrayList<>()
不需要指定泛型类型,这样的<>棱形操作符一样,根据上下文做出类型推断。
即lambda表达式并不是第一个拥有上下文相关类型的Java表达式:泛型方法调用和“菱形”构造器调用也通过目标类型来进行类型推导
Lambda表达式所用的接口,必须是函数式接口;接口中只有一个抽象方法的接口,称为函数式接口;可以使用@FunctionalInterface
注解修饰,对该接口做检查;如果接口里,有多个抽象方法,使用该注解,会有语法错误。
Lambda表达式的目标类型是函数性接口@FunctionalInterface
,每一个Lambda都能通过一个特定的函数式接口与一个给定的类型进行匹配。因此一个Lambda表达式能被应用在与其目标类型匹配的任何地方,lambda表达式必须和函数式接口的抽象函数描述一样的参数类型,它的返回类型也必须和抽象函数的返回类型兼容,并且他能抛出的异常也仅限于在函数的描述范围中。
然而,有时候我们仅仅是调用了一个已存在的方法。如下:
Arrays.sort(stringsArray,(s1,s2)->s1.compareToIgnoreCase(s2));
在Java8中,我们可以直接通过方法引用来简写lambda表达式中已经存在的方法。
Arrays.sort(stringsArray, String::compareToIgnoreCase);
这种特性就叫做方法引用(Method Reference)。
实际上就是
创建了一个类实现了函数式接口,并重写了接口中的抽象方法,在重写的方法中调用了被引用的方法。
创建该实现类对象。
1.指向静态方法的方法引用,例如Integer的parseInt方法 ,可以写成Integer::parseInt
类::静态方法名
2.引用某个类型的任意对象的实例方法,例如String的length方法,写成String::length;
类::实例方法名
3.指向现有对象的实例方法的方法引用
对象::实例方法名
4.构造器的引用:对于一个现有构造函数,你可以利用它的名称和关键字new来创建它的一个引用
类::new
域即作用域,Lambda表达式中的参数列表中的参数在该Lambda表达式范围内(域)有效。在作用Lambda表达式内,可以访问外部的变量:局部变量、类变量和静态变量,但操作受限程度不一。
Lambda不能访问函数接口的默认方法
在Lambda表达式外部的局部变量会被JVM隐式的编译成final类型,因此只能访问外而不能修改。
在Lambda表达式内部,对静态变量和成员变量可读可写。
@FunctionalInterface
public interface FunctionalInterfaceTest<T> {
T run(T t);
default void defaultFun(){
System.out.println("default...........");
}
}
public class LambdaTest {
private static int staticInt = 1;
public int memberInt = 1;
public static void main(String[] args) {
int localInt = 1;
FunctionalInterfaceTest<String> f = s -> s;
f = s -> {
System.out.println(localInt);
return s;
};
//报错Variable used in lambda expression should be final or effectively final
//在lambda表达式中使用的局部变量应该是final或有效的final,试图++,所以不是有效final。
//f = s -> {
// localInt++;
// return s;
//};
f = s -> {
staticInt++;
return s;
};
f = s -> {
new LambdaTest().memberInt++;
return s;
};
f = LambdaTest::fun;
//Cannot resolve method 'defaultFun' in 'LambdaTest'
// f = s -> {
// defaultFun();
// return s;
// };
}
private static String fun(String s){
return s;
}
}
java.util.function包中内置许多函数式接口,内置了4个核心接口。
Predicate用来逻辑判断,Function用在有输入有输出的地方,Supplier用在无输入,有输出的地方,而Consumer用在有输入,无输出的地方。你可以通过其名称的含义来获知其使用场景。
函数型接口,接收一个参数,返回单一的结果,默认的方法(andThen)可将多个函数串在一起,形成复合Funtion(有输入,有输出)结果
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
public class FunctionInterfaceTest {
public static void main(String[] args) {
Function<Integer,Integer> f = integer -> {
return integer * 2;
};
//8
System.out.println(f.andThen(f).apply(2));
}
}
消费型接口,代表了在单一的输入参数上需要进行的操作。和Function不同的是,Consumer没有返回值(消费者,有输入,无输出)
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
public class FunctionInterfaceTest {
public static void main(String[] args) {
Consumer<String> c = str -> {
System.out.println(str);
};
//str
//str
c.andThen(c).accept("str");
}
}
供给型接口,返回一个给定类型的结果,与Function不同的是,Supplier不需要接受参数(供应者,有输出无输入)
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
public class FunctionInterfaceTest {
public static void main(String[] args) {
Supplier<Integer> s = () -> 99;
//99
System.out.println(s.get());
}
}
断言型接口,输入一个参数,并返回一个Boolean值,其中内置许多用于逻辑判断的默认方法
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef) ? Objects::isNull : object -> targetRef.equals(object);
}
}
public class FunctionInterfaceTest {
public static void main(String[] args) {
Predicate<Integer> p = integer -> integer > 99;
//true
System.out.println(p.and(integer -> integer < 101).test(100));
}
}
Stream表示数据流,它没有数据结构,本身也不存储元素,其操作也不会改变源Stream,而是生成新Stream.作为一种操作数据的接口,它提供了过滤、排序、映射、规约等多种操作方法,这些方法按照返回类型被分为两类:凡是返回Stream类型的方法,称之为中间方法(中间操作),其余的都是完结方法(完结操作)。完结方法返回一个某种类型的值,而中间方法则返回新的Stream。中间方法的调用通常是链式的,该过程会形成一个管道,当完结方法被调用时会导致立即从管道中消费值,这里我们要记住:Stream的操作尽可能以“延迟”的方式运行,也就是我们常说的“懒操作”,这样有助于减少资源占用,提高性能。对于所有的中间操作(除sorted外)都是运行在延迟模式下。
Stream不但提供了强大的数据操作能力,更重要的是Stream既支持串行也支持并行,并行使得Stream在多核处理器上有着更好的性能。
private static Stream<String> init() {
List<String> list = new ArrayList<>(10);
String[] arr = new String[]{"a","b","c"};
Stream<String> stream = list.stream();
stream = Stream.of(arr);
stream = Arrays.stream(arr);
return stream;
}
Stream.generate(new Supplier<Integer>() {
Integer value = 0;
@Override
public Integer get() {
return value++;
}
});
Stream<Object> empty = Stream.empty();
可以分成两种类型的操作(方法),一种返回类型为接口本身的Stream,我们称这些方法为中间操作,返回其他具体类型的,我们称为终端操作。
结合Predicate接口,Filter对流对象中的所有元素进行过滤,该操作是一个中间操作,这意味着你可以在操作返回结果的基础上进行其他操作。
结合Comparator接口,该操作返回一个排序过后的流的视图,原始流的顺序不会改变。通过Comparator来指定排序规则,默认是按照自然顺序排序。
结合Function接口,该操作能将流对象中的每个元素映射为另一种元素(或者同种元素之间的映射),实现元素类型的转换。
Stream<String> init = init();
init.filter(s -> s.startsWith("a")).sorted((str1,str2) -> str1.compareTo(str2)).map(s -> s+"_map").forEach(System.out::println);
用来判断某个predicate是否和流对象相匹配,最终返回Boolean类型结果,例如:
//流对象中只要有一个元素匹配就返回true
boolean anyStartWithA = lists.stream().anyMatch((s -> s.startsWith("a")));
//流对象中每个元素都匹配就返回true
boolean allStartWithA = lists.stream().allMatch((s -> s.startsWith("a")));
在对经过变换之后,我们将变换的Stream的元素收集,比如将这些元素存至集合中,此时便可以使用Stream提供的collect方法,例如:
List<String> list = lists.stream().filter((p) -> p.startsWith("a")).sorted().collect(Collectors.toList());
类似sql的count,用来统计流中元素的总数,例如:
long count = lists.stream().filter((s -> s.startsWith("a"))).count();
reduce方法允许我们用自己的方式去计算元素或者将一个Stream中的元素以某种规律关联,例如:
init = init();
Optional<String> a = init.filter(s -> s.startsWith("a")).reduce((str1, str2) -> str1 + str2);
System.out.println(a.get());
到目前我们已经将常用的中间操作和完结操作介绍完了。当然所有的的示例都是基于串行Stream。接下来介绍并行Stream(parallel Stream)。并行Stream基于Fork-join并行分解框架实现,将大数据集合切分为多个小数据结合交给不同的线程去处理,这样在多核处理情况下,性能会得到很大的提高。这和MapReduce的设计理念一致:大任务化小,小任务再分配到不同的机器执行。只不过这里的小任务是交给不同的处理器。
通过parallelStream()创建并行Stream。并行Stream在某些场景能够提高性能。
你也会发现在某些情况,串行流的性能反而更好,至于具体的使用,需要你根据实际场景先测试后再决定。
通过创建一个无穷大的Stream来说明,无限Stream通常配合limit使用,限制这个流生成的元素的个数。:
private static void lazyTest(){
Stream<Integer> stream = Stream.generate(new Supplier<Integer>() {
Integer value = 0;
@Override
public Integer get() {
return value++;
}
});
stream.map(p -> p+1000).limit(1000).forEach(System.out::println);
}
我们发现开始时对这个无穷大的Stream做任何中间操作(如:filter,map等,但sorted不行)都是可以的,也就是对Stream进行中间操作并生存一个新的Stream的过程并非立刻生效的(不然此例中的map操作会永远的运行下去,被阻塞住),当遇到完结方法时stream才开始计算。通过limit()方法,把这个无穷的Stream转为有穷的Stream。