1 函数式接口
用@FunctionalInterface修饰的接口叫做函数式接口 ,或者,函数式接口就是一个只具有一个抽象方法的普通接口,@FunctionalInterface可以起到校验的作用,如果加了**@FunctionalInterface**注解的接口有两个或者两个以上的抽象方法,编译时报错。
在JDK7中其实就已经有一些函数式接口了,比如Runnable、Callable、FileFilter等等。
在JDK8中也增加了很多函数式接口,比如java.util.function包。 比如这四个常用的接口:
接口 | 描述 |
---|---|
Supplier | 无参数,一个返回结果 |
Function | 接收一个输入参数,一个返回结果 |
Consumer | 接收一个输入参数,无返回结果 |
Predicate | 接收一个输入参数,返回一个Boolean结果 |
2 Lambda表达式
一种将函数作为参数传递的方式。
Lambda表达式完成了实现函数式接口并且实现接口里的方法这一功能。
lambda表达式的写法:
()->{return 0;} 无输入参数,一个返回结果
()->{System.out.print(0);} 无输入参数,无返回结果
()->{return true;} 无输入参数,Boolean返回结果
(int i)->{return 0;} 一个输入参数,一个返回结果
(int i)->{System.out.print(0);} 一个输入参数,无返回结果
(int i)->{return true;} 一个输入参数,Boolean返回结果
等等还有多个输入参数的情况,每一种lambda表达式的写法都对应特定的函数式接口,在java.util.function包中有大量的函数式接口提供使用,让程序员编写lambda表达式更加方便,如果遇到特殊的情况还需要自定义函数式接口进行lambda表达式的编写。
3 接口的默认方法和静态方法
默认方法:
在接口中用default修饰的方法, 接口中的默认方法一定要有默认实现(方法体),接口实现者可以继承它,也可以覆盖它。
静态方法:
在接口中用static修饰的方法。
接口中增加了默认方法和静态方法之后,给接口增加一个方法就不用了修改了所有实现该接口的类了。
public interface test{
default void testDefault(){
System.out.println("default");
};
static void testStatic(){
System.out.println("static");
};
}
4 方法引用
简化lambda表达式的书写,方法引用方法的参数列表必须与函数式接口的抽象方法的参数列表保持一致,返回值不作要求。
4.1 引用方法
实例对象::实例方法名
System.out代表的就是PrintStream类型的一个实例,println是这个实例的一个方法。
Consumer<String> consumer = s -> System.out.println(s);
Consumer<String> consumer = System.out::println;
consumer.accept("test");
类名::静态方法名
Math是一个类而abs为该类的静态方法。Function中的唯一抽象方法apply方法参数列表与abs方法的参数列表相 同,都是接收一个Long类型参数。
Function<Long, Long> f = x -> Math.abs(x);
Function<Long, Long> f = Math::abs;
Long result = f.apply(-3L);
类名::实例方法名
若Lambda表达式的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,就 可以使用这种方法,String是一个类而equals为该类的定义的实例方法。BiPredicate中的唯一抽象方法test方法参数列表与equals方法 的参数列表相同,都是接收两个String类型参数。
BiPredicate<String, String> b = (x,y) -> x.equals(y);
BiPredicate<String, String> b = String::equals;
b.test("a", "b");
4.2 引用构造器
在引用构造器的时候,构造器参数列表要与接口中抽象方法的参数列表一致,格式为 类名::new。
Function接口的apply方法接收一个参数,并且有返回值。在这里接收的参数是Integer类型,与StringBuffer类的 一个构造方法StringBuffer(int capacity)对应,而返回值就是StringBuffer类型。下面这段代码的功能就是创建一个 Function实例,并把它apply方法实现为创建一个指定初始大小的StringBuffer对象。
Function<Integer, StringBuffer> fun = n -> new StringBuffer(n);
Function<Integer, StringBuffer> fun = StringBuffer::new;
StringBuffer buffer = fun.apply(10);
4.3 引用数组
引用数组和引用构造器很像,格式为 类型[]::new,其中类型可以为基本类型也可以是类。
Function<Integer, int[]> fun = n -> new int[n];
Function<Integer, int[]> fun = int[]::new;
int[] arr = fun.apply(10);
Function<Integer, Integer[]> fun2 = Integer[]::new;
Integer[] arr2 = fun2.apply(10);
5 Optional
专门用来对null值处理的类,让代码看起来更干净。
Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们 就不用显式进行空值检测。
创建Optional对象的几个方法:
- Optional.of(T value), 返回一个Optional对象,value不能为空,否则会出空指针异常
- Optional.ofNullable(T value), 返回一个Optional对象,value可以为空
- Optional.empty(),代表空
其他API: - optional.isPresent(),是否存在值(不为空)
- optional.ifPresent(Consumer<? super T> consumer), 如果存在值则执行consumer
- optional.get(),获取value
- optional.orElse(T other),如果没值则返回other
- optional.orElseGet(Supplier<? extends T> other),如果没值则执行other并返回
- optional.orElseThrow(Supplier<? extends X> exceptionSupplier),如果没值则执行exceptionSupplier, 并抛出异常。
高级API: - optional.map(Function<? super T, ? extends U> mapper),映射,映射规则由function指定,返回映射值 的Optional,所以可以继续使用Optional的API。
- optional.flatMap(Function<? super T, Optional< U > > mapper),同map类似,区别在于map中获取的返 回值自动被Optional包装,flatMap中返回值保持不变,但入参必须是Optional类型。
- optional.filter(Predicate<? super T> predicate),过滤,按predicate指定的规则进行过滤,不符合规则则 返回empty,也可以继续使用Optional的API。
public class Order {
String name;
public String getOrderName(Order order ) {
// if (order == null) {
// return null;
// }
// return order.name;
return Optional.ofNullable(order).map(order1 -> order1.name).orElse(null);
}
}
6 Stream
Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的 聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。
Stream API 借助于同样新出现 的 Lambda 表达式,极大的提高编程效率和程序可读性。
同时它提供串行和并行两种模式进行汇聚操作,并发模 式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。
6.1 案例分析
学生类
public class Student {
private Integer id; // ID
private Grade grade; // 年级
private Integer score; // 分数
public Student(Integer id, Grade grade, Integer score) {
this.id = id;
this.grade = grade;
this.score = score;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Grade getGrade() {
return grade;
}
public void setGrade(Grade grade) {
this.grade = grade;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}
//年级类
public enum Grade {
FIRST, SECOND, THTREE
}
如果要找一年级的所有学生,然后返回按学生分数值降序排序好的学生ID的集合:
java7做法:
public static void main(String[] args) {
final Collection<Student> students = Arrays.asList(
new Student(1, Grade.FIRST, 60),
new Student(2, Grade.SECOND, 80),
new Student(3, Grade.FIRST, 100)
);
//筛选一年级学生
List<Student> gradeOneStudents = Lists.newArrayList();
for (Student student: students) {
if (Grade.FIRST.equals(student.getGrade())) {
gradeOneStudents.add(student);
}
}
//按照分数进行排序
Collections.sort(gradeOneStudents, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o2.getScore().compareTo(o1.getScore());
}
});
//将排好序的集合遍历取出学生id
List<Integer> studentIds = new ArrayList<>();
for(Student t: gradeOneStudents){
studentIds.add(t.getId());
}
}
java8做法:
public static void main(String[] args) {
final Collection< Student > students= Arrays.asList(
new Student(1, Grade.FIRST, 60),
new Student(2, Grade.SECOND, 80),
new Student(3, Grade.FIRST, 100)
); new Student(3, Grade.FIRST, 100) );
List<Integer> studentIds = students.stream()
.filter(student -> student.getGrade().equals(Grade.FIRST))
.sorted(Comparator.comparingInt(Student::getScore))
.map(Student::getId)
.collect(Collectors.toList());
}
6.2 什么是Stream
Stream不是集合元素,不是数据结构它并不保存数据,它是有关算法和计算得,像是一个过滤器,每个方法就像一层过滤网。
6.3 Stream构成
6.3.1 数据源
生成Stream source的方式:
从Collection 和数组生成
Collection.stream()
Collection.parallelStream()
Arrays.stream(T array)
Stream.of(T t)
从 BufferedReader
java.io.BufferedReader.lines()
静态工厂
java.util.stream.IntStream.range()
java.nio.file.Files.walk()
自己构建
java.util.Spliterator
其它
Random.ints()
BitSet.stream()
Pattern.splitAsStream(java.lang.CharSequence)
JarFile.stream()
常用的Stream source的生成方式:
// 1. Individual values
Stream stream = Stream.of("a", "b", "c");
// 2. Arrays
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 3. Collections
List<String> list = Arrays.asList(strArray);
stream = list.stream();
需要注意的是,对于基本数值型,目前有三种对应的包装类型 Stream: IntStream、LongStream、DoubleStream。当然我们也可以用 Stream、Stream >、Stream,但是 boxing (装箱)和 unboxing (拆箱)会很耗时,所以特别为这三种基本数值型提供了对应的 Stream。
6.3.2 数据转换
Stream<String> stream = Stream.<String>of(new String[]{"1", "2", "3"});
List<String> list1 = stream.collect(Collectors.toList());
List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
String str = stream.collect(Collectors.joining());
System.out.println(str);
6.3.3 执行操作
6.3.3.1 中间操作(Intermediate Operation)
一个流可以后面跟随零个或多个 intermediate 操作。其目的主 要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类 操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
Intermediate Operation又可以分为两种类型:
无状态操作(Stateless Operation):操作是无状态的,不需要知道集合中其他元素的状态,每个元 素之间是相互独立的,比如map()、filter()等操作。
有状态操作(Stateful Operation):有状态操作,操作是需要知道集合中其他元素的状态才能进行 的,比如sort()、distinct()。
6.3.3.2 最终操作((Terminal Operation)
一个流只能有一个 terminal 操作,当这个操作执行后,流就被使 用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始 流的遍历,并且会生成一个结果。
Terminal Operation从逻辑上可以分为两种:
短路操作(short-circuiting):短路操作是指不需要处理完所有元素即可结束整个过程
非短路操作(non-short-circuiting):非短路操作是需要处理完所有元素之后才能结束整个过程
6.4 流的典型用法
6.4.1 map/flatMap
map的作用就是把 input Stream 的每一个元素,映射成 output Stream 的另外一个元素.
Stream<String> stream = Stream.<String>of(new String[]{"a", "b", "c"});
stream.map(String::toUpperCase).forEach(System.out::println);
这段代码把所有的字母转换为大写。map 生成的是个 1:1 映射,每个输入元素,都按照规则转换成为另外一个元 素。还有一些场景,是一对多映射关系的,这时需要 flatMap。
Stream<List<Integer>> inputStream = Stream.of(
Arrays.asList(1),
Arrays.asList(2, 3),
Arrays.asList(4, 5, 6)
);
Stream<Integer> mapStream = inputStream.map(List::size);
Stream<Integer> flatMapStream = inputStream.flatMap(Collection::stream);
6.4.2 filter
filter 对输入 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。
Integer[] nums = new Integer[]{1,2,3,4,5,6};
Arrays.stream(nums).filter(n -> n<3).forEach(System.out::println);
6.4.3 foreach
forEach 是 terminal 操作,因此它执行后,Stream 的元素就被“消费”掉了,你无法对一个 Stream 进行两次 terminal 运算。
Integer[] nums = new Integer[]{1,2,3,4,5,6};
Stream stream = Arrays.stream(nums);
stream.forEach(System.out::print);
,具有相似功能的 intermediate 操作 peek 可以进行两次操作:
Integer[] nums = new Integer[]{1,2,3,4,5,6};
Stream stream = Arrays.stream(nums);
stream.peek(System.out::print)
.peek(System.out::print)
.collect(Collectors.toList());
6.4.4 reduce
这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则 (BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数 值的 sum、min、max、average 都是特殊的 reduce。
Integer[] nums = new Integer[]{1,2,3,4,5,6};
//有初始值,直接返回结果
Integer sum = Arrays.stream(nums).reduce(0, (integer, integer2) -> integer+integer2);
//无初始值,返回的时Optional
Integer sum1 = Arrays.stream(nums).reduce(Integer::sum).get();
System.out.println(sum);
6.4.5 limit/skip
limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素。
Integer[] nums = new Integer[]{1,2,3,4,5,6};
Arrays.stream(nums).limit(3).forEach(System.out::print);
System.out.println();
Arrays.stream(nums).skip(2).forEach(System.out::print);
6.4.6 sorted
对 Stream 的排序通过 sorted 进行,它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、 limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间。
Integer[] nums = new Integer[]{1,2,3,4,5,6};
Arrays.stream(nums).sorted((i1, i2) -> i2.compareTo(i1)).limit(3)
.forEach(System.out::print);
System.out.println();
Arrays.stream(nums).sorted((i1, i2) -> i2.compareTo(i1)).skip(2).
forEach(System.out::print);
6.4.7 max/min/distinct
Integer[] nums = new Integer[]{1, 2, 2, 3, 4, 5, 5, 6};
System.out.println(Arrays.stream(nums).min(Comparator.naturalOrder()).get());
System.out.println(Arrays.stream(nums).max(Comparator.naturalOrder()).get());
Arrays.stream(nums).distinct().forEach(System.out::print);
6.4.8 match
Stream 有三个 match 方法,从语义上说:
allMatch:Stream 中全部元素符合传入的 predicate,返回 true
anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true
noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true
它们都不是要遍历全部元素才能返回结果。例如 allMatch 只要一个元素不满足条件,就 skip 剩下的所有元素, 返回 false。
Integer[] nums = new Integer[]{1, 2, 2, 3, 4, 5, 5, 6};
System.out.println(Arrays.stream(nums).allMatch(integer -> integer < 7));
System.out.println(Arrays.stream(nums).anyMatch(integer -> integer < 2));
System.out.println(Arrays.stream(nums).noneMatch(integer -> integer < 0));
6.4.9 使用Collectors进行reduction操作
java.util.stream.Collectors 类的主要作用就是辅助进行各类有用的 reduction 操作,例如转变输出为 Collection, 把 Stream 元素进行归组。
对Student进行按年级进行分组:
final Collection<Student> students = Arrays.asList(
new Student(1, Grade.FIRST, 60),
new Student(2, Grade.SECOND, 80),
new Student(3, Grade.FIRST, 100)
);
students.stream().collect(Collectors.groupingBy(Student::getGrade)).
forEach(((grade, students1) -> {
System.out.println(grade);
students1.forEach(student ->
System.out.println(student.getId()+","+student.getGrade()+","+student.getScore()));
}));
6.4.10 parallelStream
parallelStream其实就是一个并行执行的流.它通过默认的ForkJoinPool,可以提高你的多线程任务的速度。
Arrays.stream(nums).parallel().forEach(System.out::print);
System.out.println(Arrays.stream(nums).parallel().reduce(Integer::sum).get());
System.out.println(); Arrays.stream(nums).forEach(System.out::print);
System.out.println(Arrays.stream(nums).reduce(Integer::sum).get());
parallelStream底层是使用的ForkJoin。而ForkJoin里面的线程是通过ForkJoinPool来运行的,Java 8为 ForkJoinPool添加了一个通用线程池,这个线程池用来处理那些没有被显式提交到任何线程池的任务。它是 ForkJoinPool类型上的一个静态元素。它拥有的默认线程数量等于运行计算机上的处理器数量,所以这里就出现 了这个java进程里所有使用parallelStream的地方实际上是公用的同一个ForkJoinPool。parallelStream提供了更 简单的并发执行的实现,但并不意味着更高的性能,它是使用要根据具体的应用场景。如果cpu资源紧张 parallelStream不会带来性能提升;如果存在频繁的线程切换反而会降低性能。
7 注解新特性
7.1 重复注解
假设,现在有一个服务我们需要定时运行,就像Linux中的cron一样,假设我们需要它在每周三的12点运行一 次,那我们可能会定义一个注解,有两个代表时间的属性。
public @interface Schedule {
int dayOfWeek() default 1; // 周几
int hour() default 0; // 几点
}
所以我们可以给对应的服务方法上使用该注解,代表运行的时间:
public class ScheduleService {
//每周三12点运行
@Schedule(dayOfWeek = 3, hour = 12)
public void start() {
//执行服务
}
}
那么如果我们需要这个服务在每周四的13点也需要运行一下,
如果是JDK8之前,那么…尴尬了!你不能像下面的 代码,会编译错误
public class ScheduleService {
@Schedule(dayOfWeek = 3, hour = 12)
@Schedule(dayOfWeek = 4, hour = 13)
public void start() {
//执行服务
}
}
那么如果是JDK8,你可以改一下注解的代码,在自定义注解上加上@Repeatable元注解,并且指定重复注解的存 储注解(其实就是需要需要数组来存储重复注解),这样就可以解决上面的编译报错问题。
@Repeatable(value = Schedule.Schedules.class)
public @interface Schedule {
int dayOfWeek() default 1;
int hour() default 0;
@interface Schedules {
Schedule[] value();
}
}
同时,反射相关的API提供了新的函数getAnnotationsByType()来返回重复注解的类型。 添加main方法:
public static void main(String[] args) {
try {
Method method = ScheduleService.class.getMethod("start");
for (Annotation annotation : method.getAnnotations()) {
System.out.println(annotation);
}
for (Schedule s : method.getAnnotationsByType(Schedule.class)) {
System.out.println(s.dayOfWeek() + "|" + s.hour());
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
7.2 扩展注解
JDK8之前的注解只能加在:
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,//类,接口,枚举
/** Field declaration (includes enum constants) */
FIELD,//类型变量
/** Method declaration */
METHOD,//方法
/** Formal parameter declaration */
PARAMETER,//方法参数
/** Constructor declaration */
CONSTRUCTOR,//构造方法
/** Local variable declaration */
LOCAL_VARIABLE,//局部变量
/** Annotation type declaration */
ANNOTATION_TYPE,//注解类型
/** Package declaration */
PACKAGE,//包
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,//类型变量的声明语句中
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE //能写在使用类型的任何语句中
}
8 更好的类型推断机制
#1.7
List<String> list= new ArrayList<String>();
#1.8
List<String> list= new ArrayList<>();
9 参数名保留在字节码中
先来想一个问题:JDK8之前,怎么获取一个方法的参数名列表?
之前,怎么获取一个方法的参数名列表?
在JDK7中一个Method对象有下列方法:
Method.getParameterAnnotations() 获取方法参数上的注解
Method.getParameterTypes() 获取方法的参数类型列表
但是没有能够获取到方法的参数名字列表!
在JDK8中增加了两个方法
Method.getParameters() 获取参数名字列表
Method.getParameterCount() 获取参数名字个数
用法:
在编译时加上–parameters参数就可以使用以下方法获取参数名了,
public class ParameterNames {
public void test(String p1, String p2) { }
public static void main(String[] args) throws Exception {
Method method = ParameterNames.class.getMethod("test", String.class, String.class);
for (Parameter parameter : method.getParameters()) {
System.out.println(parameter.getName());
}
System.out.println(method.getParameterCount());
}
}
使用maven编译加–parameters参数的方法:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<compilerArgument>-parameters</compilerArgument>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
10 锁的优化
StampedLock
是对读写锁ReentrantReadWriteLock的增强,该类提供了一些功能,优化了读锁、写锁的访问,同时使读写锁之 间可以互相转换,更细粒度控制并发。
ReentrantLock
ReentrantLock类,实现了Lock接口,是一种可重入的独占锁,它具有与使用 synchronized 相同的一些基本行为 和语义,但功能更强大。ReentrantLock内部通过内部类实现了AQS框架(AbstractQueuedSynchronizer)的API来 实现独占锁的功能。
ReentrantReadWriteLock
ReentrantReadWriteLock和ReentrantLock不同,ReentrantReadWriteLock实现的是ReadWriteLock接口。
读写锁的概念:
加读锁时其他线程可以进行读操作但不可进行写操作,加写锁时其他线程读写操作都不可进 行。
但是,读写锁如果使用不当,很容易产生“饥饿 ”问题 ,在ReentrantReadWriteLock中,当读锁被使用时,如 果有线程尝试获取写锁,该写线程会阻塞。,在读线程非常多,写线程很少的情况下,很容易导致写线程“饥 饿”,虽然使用“公平”策略可以一定程度上缓解这个问题,但是“公平”策略是以牺牲系统吞吐量为代价的。
11 并行数组
Java 8增加了大量的新方法来对数组进行并行处理。可以说,重要的是parallelSort()方法,因为它可以在多核 机器上极大提高数组排序的速度。
下面的代码演示了先并行随机生成20000个0-1000000的数字,然后打印前10个数字,然后使用并行排序,再次 打印前10个数字。
long[] arrayOfLong = new long [ 20000 ];
Arrays.parallelSetAll( arrayOfLong,
index -> ThreadLocalRandom.current().nextInt( 1000000 )
);
Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
i -> System.out.print( i + " " ) );
System.out.println();
Arrays.parallelSort( arrayOfLong );
Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
i -> System.out.print( i + " " ) );
System.out.println();
12 CompletableFuture
当我们Javer说异步调用时,我们自然会想到Future,比如:
public class FutureDemo {
/**
* 异步进行一个计算
* @param args
*/
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
Future<Integer> result = executor.submit(new Callable<Integer>() {
public Integer call() throws Exception {
int sum=0;
System.out.println("正在进行计算...");
for (int i=0; i<100; i++) {
sum = sum + i;
}
Thread.sleep(TimeUnit.SECONDS.toSeconds(3));
System.out.println("算完了");
return sum;
}
});
System.out.println("做其他的事...");
try {
System.out.println("result:" + result.get());
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("事情都做完了...");
executor.shutdown();
}
}
那么现在如果想实现异步计算完成之后,立马能拿到这个结果继续异步做其他事情呢?这个问题就是一个线程依 赖另外一个线程,这个时候Future就不方便,我们来看一下CompletableFuture的实现:
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10);
CompletableFuture result = CompletableFuture.supplyAsync(() -> {
int sum=0;
System.out.println("正在计算...");
for (int i=0; i<100; i++) {
sum = sum + i;
}
try {
Thread.sleep(TimeUnit.SECONDS.toSeconds(3));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"计算完了");
return sum;
}, executor).thenApplyAsync(sum -> {
System.out.println(Thread.currentThread().getName()+"打印"+sum);
return sum;
}, executor);
System.out.println("做其他的事...");
try {
System.out.println("result:" + result.get());
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("事情都做完了...");
executor.shutdown();
}
13 java虚拟机(jvm)新特性
PermGen空间被移除了,取而代之的是Metaspace(JEP 122)。
JVM选项-XX:PermSize与-XX:MaxPermSize分 别被-XX:MetaSpaceSize与-XX:MaxMetaspaceSize所代替。