λ表达式
什么是λ表达式
λ表达式有三部分组成:参数列表,箭头(->),以及一个表达式或者语句块。
public int add(int x, int y) {
return x + y;
}
转换为λ表达式
(int x, int y) -> x + y;
去除参数类型
(x, y) -> x + y;
无参 以及 只有一个参数
() -> { System.out.println("Hello Lambda!");
c -> { return c.size(); }
λ表达式的类型
λ表达式可以被当做是一个Object(注意措辞)。λ表达式的类型,叫做“目标类型(target type)”。λ表达式的目标类型是“函数接口(functional interface)”,这是Java8新引入的概念。它的定义是:一个接口,如果只有一个显式声明的抽象方法,那么它就是一个函数接口。一般用@FunctionalInterface标注出来(也可以不标)。举例如下:
@FunctionalInterface
public interface Runnable{
void run();
}
public interface Callable{
V call() throws Exception;
}
public interface Comparator{
int compare(T o1, T o2); boolean equals(Object obj);
}
所以 可以定义
Runnable r1 = () -> {System.out.println("Hello Lambda!");};
思考:可否定义 Object o = () -> {System.out.println("Hello Lambda!");};
JDK常用的预定义接口函数
//入参为T,返回R
@FunctionalInterface
public interface Function {
R apply(T t);
}
//入参为T,无返回值
@FunctionalInterface
public interface Consumer {
void accept(T t);
}
//无入参,返回T(通常配合构造方法)
@FunctionalInterface
public interface Supplier {
T get();
}
//入参为T,返回值为boolean
@FunctionalInterface
public interface Predicate {
boolean test(T t);
}
//入参为T,U,返回值为R 类似还有BiConsumer等
@FunctionalInterface
public interface BiFunction {
R accept(T t, U u);
}
方法引用
方法引用让开发者可以直接引用现存的方法、Java类的构造方法或者实例对象。方法引用和Lambda表达式配合使用,使得java类的构造方法看起来紧凑而简洁,没有很多复杂的模板代码。
public static class Car {
public static Car create( final Supplier supplier ) {
return supplier.get();
}
public static void collide( final Car car ) {
System.out.println( "Collided " + car.toString() );
}
public void follow( final Car another ) {
System.out.println( "Following the " + another.toString() );
}
public void repair() {
System.out.println( "Repaired " + this.toString() );
}
}
第一种方法引用的类型是构造器引用,语法是Class::new,或者更一般的形式:Class::new。注意:这个构造器没有参数。
Car car = Car.create(Car::new);
List cars = Arrays.asList(car);
第二种方法引用的类型是静态方法引用,语法是Class::static_method。注意:这个方法接受一个Car类型的参数。
cars.forEach(Car::collide);
第三种方法引用的类型是某个类的成员方法的引用,语法是Class::method,注意,这个方法没有定义入参:
cars.forEach(Car::repair);
第四种方法引用的类型是某个实例对象的成员方法的引用,语法是instance::method。注意:这个方法接受一个Car类型的参数:
final Car police = Car.create(Car::new);
cars.forEach(police::follow);
流
什么是流
Stream 不是数据结构,不保存数据,它是有关算法和计算的,就如同一个高级版本的迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。同时又与迭代器不同,迭代器只能串行操作,Stream可以并行化操作。
流的构成
当我们使用一个流的时候,通常包括三个基本步骤:
获取一个数据源(source)→数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道
,如下图所示。
image
常用的构建流的方式:
集合Collection
Collection.stream()
Collection.parallelStream()
数组
Stream.of(T[] tArray)
多个相同类型对象
Stream.of("chaimm","peter","john");
流的基本使用
常见操作
中间操作
无状态
map (mapToInt, flatMap 等)、filter、peek
有状态
distinct、sorted、limit、skip
终结操作
非短路
forEach、forEachOrdered、toArray、reduce、collect、min、 max、 count
短路操作
anyMatch、allMatch、noneMatch、findFirst、findAny
Stream中的操作可以分为两大类:中间操作与终结操作
中间操作(Intermediate):一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。中间操作又可以分为无状态(Stateless)操作与有状态(Stateful)操作,前者是指元素的处理不受之前元素的影响;后者是指该操作只有拿到所有元素之后才能继续下去。
终结操作(Terminal):一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果。终结操作又可以分为短路与非短路操作,短路是指遇到某些符合条件的元素就可以得到最终结果,比如找到第一个满足条件的元素。而非短路是指必须处理所有元素才能得到最终结果。
map/flatMap
对流中的每个元素执行一个函数,使得元素转换成另一种类型输出。
map 一对一 (入参 Function)
List persons = new ArrayLisy<>();
List result = persons.stream().map(x -> x.getId())
.collect(Collectors.toList());
List result = persons.stream().map(x -> {
Student s = new Student();
s.setName(x.getName());
return s;
}).collect(Collectors.toList());
flatMap 一对多 (入参 Function)
List> listAll = new ArrayList<>();
List result = listAll.stream().flatMap(x -> x.stream())
.collect(Collectors.toList());
List result = listAll.stream().flatMap(x -> x.stream())
.map(x -> {
Student s = new Student();
s.setName(x.getName());
return s;
}).collect(Collectors.toList());
filter
filter 对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。 (入参 Predicate)
Integer[] nums = new Integer[]{1, 2, 3, 4, 5, 6};
Integer[] evens = Stream.of(nums).filter(n -> n%2 == 0)
.toArray(Integer[]::new);
List result = persons.stream().filter(x -> x.getAge() < 20)
.map(x -> {
Student s = new Student();
s.setName(x.getName());
return s;
}).collect(Collectors.toList());
peek
peek 方法我们可以拿到元素,然后做一些其他事情。(入参 Consumer)
List result = persons.stream().map(x -> x.getId())
.peek(x -> System.out.println(x))
.collect(Collectors.toList());
Map map = new HashMap<>();
List result = persons.stream()
.filter(x -> x.getAge() < 20 )
.peek(x -> map.put(x.getId(), x) )
.map(x -> {
Student s = new Student();
s.setName(x.getName());
return s;
}).collect(Collectors.toList());
limit/skip
limit 返回 Stream 的前面 n 个元素,skip 则是扔掉前 n 个元素
List result = persons.stream().map(x -> x.getId())
.limit(10).skip(3).collect(Collectors.toList());
sorted
对元素进行排序 (入参 Comparator)
List personList2 = persons.stream()
.sorted((p1, p2) -> p1.getName().compareTo(p2.getName()))
.collect(Collectors.toList());
forEach
对元素进行遍历消费 (入参 Consumer)
persons.stream().sorted((p1, p2) -> p1.getName().compareTo(p2.getName()))
.forEach(peron -> {
System.out.println(persion.getName());
});
collect
对元素进行收集
List personList2 = persons.stream()
.sorted((p1, p2) -> p1.getName().compareTo(p2.getName()))
.collect(Collectors.toList());
List personList2 = persons.stream()
.sorted((p1, p2) -> p1.getName().compareTo(p2.getName()))
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
Collectors.toMap
转换为MAP
Map personIdNameMap = persons.stream()
.collect(Collectors.toMap(Person::getId, Person::getName);
Map personIdNameMap = persons.stream()
.collect(Collectors.toMap(Person::getId, Person::getName, (v1,v2)->v1);
Collectors.groupingBy
分组
Map> personAgeMap = persons.stream()
.collect(Collectors.groupingBy(Person::getAge));
Map> personAgeMap = persons.stream()
.collect(Collectors.groupingBy(Person::getAge,
Collectors.toMap(Person::getId, Function.identity())));
Collectors.collectingAndThen
收集然后处理
Map personAgeMap = persons.stream()
.collect(Collectors.groupingBy(Person::getAge,
Collectors.collectingAndThen(
Collectors.toList(),
list->list.size()
)));
接口方法
接口default方法
默认方法使得开发者可以在不破坏兼容性的前提下,往现存接口中添加新的方法,即不强制那些实现了该接口的类也同时实现这个新加的方法。
默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写
private interface HelloService {
default String sayHello() {
return "hello";
}
}
private static class HelloImpl implements HelloService {
}
private static class HelloWorldImpl implements HelloService {
@Override
public String sayHello() {
return "hello world";
}
}
思考:为啥要加入default方法?
接口static方法
private interface HelloService {
static boolean testHello(String s) {
return Objects.equals(s,"hello");
}
}
Stream流水线解决方案
Stage(Pipeline)
java8用Stage来记录Stream的中间操作,很多Stream操作会需要一个回调函数(Lambda表达式),因此一个完整的操作是构成的三元组。Stream中使用Stage的概念来描述一个完整的操作,并用某种实例化后的Pipeline来代表Stage,然后将具有先后顺序的各个Stage连到一起,就构成了整个流水线。
image
Sink
有了操作,我们需要将所有操作叠加起来,让流水线起到应有的作用,java用Sink来协调相邻Stage之间的调用关系。每个Stage必须实现opWrapSink方法。Sink接口的主要方法如下
方法名
作用
void begin(long size)
开始遍历元素之前调用该方法,通知Sink做好准备
void end()
所有元素遍历完成之后调用,通知Sink没有更多的元素了
boolean cancellationRequested()
是否可以结束操作,可以让短路操作尽早结束
void accept(T t)
遍历元素时调用,接受一个待处理元素,并对元素进行处理
每个Stage都会将自己的操作封装到一个Sink里,前一个Stage只需调用后一个方法即可,并不需要知道其内部是如何处理的。当然对于有状态的操作,Sink的begin()和end()方法也是必须实现的。比如Stream.sorted()是一个有状态的中间操作,其对应的Sink.begin()方法可能创建一个乘放结果的容器,而accept()方法负责将元素添加到该容器,最后end()负责对容器进行排序。对于短路操作,Sink.cancellationRequested()也是必须实现的,比如Stream.findFirst()是短路操作,只要找到一个元素,cancellationRequested()就应该返回true,以便调用者尽快结束查找。Sink的四个接口方法常常相互协作,共同完成计算任务。实际上Stream API内部实现的的本质,就是如何重载Sink的这四个接口方法。
map方法的主要实现
public final Stream map(Function super P_OUT, ? extends R> mapper) {
Objects.requireNonNull(mapper);
return new StatelessOp(this, StreamShape.REFERENCE,
StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
@Override
Sink opWrapSink(int flags, Sink sink) {
return new Sink.ChainedReference(sink) {
@Override
public void accept(P_OUT u) {
downstream.accept(mapper.apply(u));
}
};
}
};
}
static abstract class ChainedReference implements Sink {
protected final Sink super E_OUT> downstream;
public ChainedReference(Sink super E_OUT> downstream) {
this.downstream = Objects.requireNonNull(downstream);
}
@Override
public void begin(long size) {
downstream.begin(size);
}
@Override
public void end() {
downstream.end();
}
@Override
public boolean cancellationRequested() {
return downstream.cancellationRequested();
}
}
// Stream.sort()方法用到的Sink实现
class RefSortingSink extends AbstractRefSortingSink {
private ArrayList list;// 存放用于排序的元素
RefSortingSink(Sink super T> downstream, Comparator super T> comparator) {
super(downstream, comparator);
}
@Override
public void begin(long size) {
...
// 创建一个存放排序元素的列表
list = (size >= 0) ? new ArrayList((int) size) : new ArrayList();
}
@Override
public void end() {
list.sort(comparator);// 只有元素全部接收之后才能开始排序
downstream.begin(list.size());
if (!cancellationWasRequested) {// 下游Sink不包含短路操作
list.forEach(downstream::accept);// 2. 将处理结果传递给流水线下游的Sink
}
else {// 下游Sink包含短路操作
for (T t : list) {// 每次都调用cancellationRequested()询问是否可以结束处理。
if (downstream.cancellationRequested()) break;
downstream.accept(t);// 2. 将处理结果传递给流水线下游的Sink
}
}
downstream.end();
list = null;
}
@Override
public void accept(T t) {
list.add(t);// 1. 使用当前Sink包装动作处理t,只是简单的将元素添加到中间列表当中
}
}
多个Sink叠加
多个Stage组成的链路如图所示,那么什么时候出发执行结束操作(Terminal Operation),一旦调用某个结束操作,就会触发整个流水线的执行。
final void copyInto(Sink wrappedSink, Spliterator spliterator){
...
if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {
wrappedSink.begin(spliterator.getExactSizeIfKnown());// 通知开始遍历
spliterator.forEachRemaining(wrappedSink);// 迭代
wrappedSink.end();// 通知遍历结束
}
...
}
元空间
永久代的消除
从JDK1.7开始,贮存在永久代的一部分数据已经转移到了Java Heap或者是Native Heap。符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。但永久代仍然存在于JDK7,并没有完全的移除。在JDK1.8版本中永久带被彻底移除。永久代的参数-XX:PermSize和-XX:MaxPermSize也被移除。该参数在JDK1.8使用会有警告
元空间
JDK1.8将类信息存储在元空间中,元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:
-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize,最大空间,默认是没有限制的(取决于内存)。