文章目录
Lambda表达式
看下以下四种写法:
package lambda;
import java.util.Arrays;
/**
* @ClassName lambdaDemo
* @Deacription TODO
* @Author linsen
* @Date 2020/11/16 19:21
* @Version 1.0
**/
public class lambdaDemo {
public static void main(String[] args) {
for (String e : Arrays.asList("a", "b", "c")) {
System.out.println(e);
}
Arrays.asList("a", "b", "c").forEach(e -> System.out.println(e));
Arrays.asList("a", "b", "c").forEach((String e) -> System.out.println(e));
Arrays.asList("a", "b", "c").forEach((String e) -> {
System.out.println(e);
});
}
}
这几种写法都是遍历新生成的包含a,b,c三个元素的集合。第一种是java8之前的写法,而后三种都是java8的写法,称为Lambda表达式。
Lambda表达式是Java 8中最大和最令人期待的语言改变。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理。在java8之前,一直使用匿名内部类代替Lambda表达式。
Lambda中可以引用类成员和局部变量,这些变量会被隐式的转化为final。
package lambda;
import java.util.Arrays;
/**
* @ClassName lambdaDemo1
* @Deacription TODO
* @Author linsen
* @Date 2020/11/16 19:31
* @Version 1.0
**/
public class lambdaDemo1 {
public static void main(String[] args) {
String s = ",";
Arrays.asList( "a", "b", "d" ).forEach(
( String e ) -> System.out.print( e + s ) );
}
}
Lambda表达式有返回值,返回值的类型也由编译器推理得出。如果Lambda表达式中的语句块只有一行,则可以不用使用return语句,下列两个代码片段效果相同:
Arrays.asList( "a", "b", "d" ).sort((e1,e2)->e1.compareTo(e2));
Arrays.asList( "a", "b", "d" ).sort((e1,e2)->{
int num = e1.compareTo(e2);
return num;
});
函数式接口
Lambda的设计者们为了让现有的功能与Lambda表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念。函数接口指的是只有一个函数的接口,这样的接口可以隐式转换为Lambda表达式。
在实践中,函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface。
@FunctionalInterface
public interface Functional {
void method();
}
不过有一点需要注意,默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的。
@FunctionalInterface
public interface FunctionalDefaultMethods {
void method();
default void defaultMethod() {
}
}
四大内置函数接口
Consumer(消费型接口)接收一个参数,无返回值:
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
}
Supplier(提供型接口) 无参,有返回值
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
Function(函数接口)接收一个参数,有返回值
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
};
Predicat(断言型接口)接收一个参数,返回boolean:
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
}
接口的默认方法和静态方法
在java8之前,接口中定义的方法都需要被它的实现类去实现,而在java8中,出现了默认方法和静态方法。
默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写,也就是说可以实现也可以不实现:
private interface Defaulable {
default String notRequired() {
return "Default implementation";
}
}
private static class DefaultableImpl implements Defaulable {
}
private static class OverridableImpl implements Defaulable {
@Override
public String notRequired() {
return "Overridden implementation";
}
}
这两个实现类,一个覆盖了默认方法,一个继承了默认方法,都是没问题的。
java8的接口中也可以定义静态方法。
public interface Demoface {
static void say(){
System.out.println("Demoface");
}
}
可以直接调用接口中的静态方法。
public class MainDemo {
public static void main(String[] args) {
Demoface.say();
}
}
方法引用
方法引用使得开发者可以直接引用现存的方法、Java类的构造方法或者实例对象。方法引用和Lambda表达式配合使用,使得java类的构造方法看起来紧凑而简洁,没有很多复杂的模板代码。
引用方法有下面几种方式:
(1)对象引用::实例方法名
(2)类名::静态方法名
(3)类名::实例方法名
(4)类名::new
对象引用::实例方法名:
public class Car {
private long price;
public Car() {
}
public Car(long price) {
this.price = price;
}
public long getPrice() {
return price;
}
public void setPrice(long price) {
this.price = price;
}
}
public class CarCpmapre {
public int priceCompare(Car a, Car b) {
return (int) (a.getPrice() - b.getPrice());
}
public static void main(String[] args) {
Car[] cars = new Car[]{
new Car(100),
new Car(300),
new Car(200),
};
CarCpmapre carCpmapre = new CarCpmapre();
Arrays.sort(cars,carCpmapre::priceCompare);
for (Car car : cars) {
System.out.println(car.getPrice());
}
}
}
类名::静态方法名:
举几个例子:
String::valueOf,等价于 Lambda:s -> String.valueOf(s);
Math::pow 等价于lambda表达式 (x, y) -> Math.pow(x, y);
可以直接使用类的静态方法。
类名::实例方法名:
若Lambda表达式的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,就可以使用这种方法:
public class MainDemo {
public static void main(String[] args) {
BiPredicate<String,String> biPredicate = String::contains;
System.out.println(biPredicate.test("1213","121"));
}
}
类名::new:
在引用构造器的时候,构造器参数列表要与接口中抽象方法的参数列表一致。
public class CarFactory {
private Supplier<Car> supplier;
public CarFactory(Supplier<Car> supplier) {
this.supplier = supplier;
}
public Car getCar(){
return supplier.get();
}
public static void main(String[] args) {
CarFactory carFactory = new CarFactory(Car::new);
carFactory.getCar();
}
}
这里的Car类,还是上文中的Car类。
重复注解
在 Java 8 之前我们不能在一个类型重复使用同一个注解
比如:
@PropertySource("classpath:config.properties")
@PropertySource("file:application.properties")
public class MainApp {}
在java7中是报错的。在java7中,我们只有通过嵌套的方式来解决达到这个效果:
@Retention(RetentionPolicy.RUNTIME)
public @interface PropertySources {
PropertySource[] value();
}
使用时:
@PropertySources({
@PropertySource("classpath:config.properties"),
@PropertySource("file:application.properties")
})
public class MainApp {
}
但是在java8中,引入了重复注解的概念。
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(PropertySources.class) //这行建立了 @PropertySource 与 @PropertySources 的关系
public @interface PropertySource {
String value();
}
只需要声明@Repeatable(PropertySources.class) ,就可以使用重复注解。这里这个申明,其实和上面的java7中的嵌套注解的写法意义是相同的,一个是显式嵌套,一个是隐式嵌套。使用代码:
@PropertySource("classpath:config.properties")
@PropertySource("file:application.properties")
public class MainApp {}
所以java7,java8中都可以使用一个代码去获取注解值:
PropertySources annotation = MainApp.class.getAnnotation(PropertySources.class);
for (PropertySource propertySource: annotation.value()){
System.out.println(propertySource.value());
}
不过在java8中,有更推荐的获取重复注解的方式:
PropertySource[] propertySources = MainApp.class.getAnnotationsByType(PropertySource.class);
for (PropertySource propertySource: propertySources ){
System.out.println(propertySource.value()); //获得所有 @PropertySource 的内容
}
更好的类型推断
Java 8编译器在类型推断方面有很大的提升,在很多场景下编译器可以推导出某个参数的数据类型,从而使得代码更为简洁。
拓宽注解的应用场景
Java 8拓宽了注解的应用场景。现在,注解几乎可以使用在任何元素上:局部变量、接口类型、超类和接口实现类,甚至可以用在函数的异常定义上。
Optional
NullPointerException相信每个JAVA程序员都不陌生,是JAVA应用程序中最常见的异常。JDK8的类中引入了Optional类,来解决这个问题。
Optional是一个容器对象,它可能包含空值,也可能包含非空值。当属性value被设置时,isPesent()方法将返回true,并且get()方法将返回这个值。
我们可以用Optional.ofNullable()或者Optional.of()的方式生成一个Optional对象,前者可以传入null值,后者只能传入非空对象。
Optional<String> optionalS1 = Optional.ofNullable(null);
Optional<String> optionalS2 = Optional.ofNullable("null");
Optional<String> optionalS3 = Optional.of("null");
我们可以调用isPresent()和get()方法来判断Optional中的对象是否为null和在非null的情况下获得该对象。
String s = "123";
Optional<String> optionalS = Optional.of(s);
if (optionalS.isPresent()){
System.out.println(optionalS.get());
}
我们可以在Optional为空的时候使用orElseGet传入一个提供方法,返回一个新的值去取代null。
String s = null;
Optional<String> optionalS = Optional.ofNullable(s);
System.out.println(optionalS.orElseGet(() -> "123"));
也可以直接用orElse传入自己需要的对象。
String s = null;
Optional<String> optionalS = Optional.ofNullable(s);
System.out.println(optionalS.orElse("123"));
用ifPresent去消费。
String s = "123";
Optional<String> optionalS = Optional.ofNullable(s);
optionalS.ifPresent(System.out::println);
调用map方法,传入一个函数式接口,可以得到经过函数处理的Optional。
//返回1232
String s = "123";
Optional<String> optionalS = Optional.ofNullable(s);
System.out.println(optionalS.map(s1->s1+"2").get());
用flatmap方法,和上面一个效果。
public class optionalDemo {
public static void main(String[] args) {
String s = "123";
Optional<String> optionalS = Optional.ofNullable(s);
System.out.println(optionalS.flatMap(optionalDemo::apply).get());
}
private static Optional<String> apply(String s1) {
return Optional.of(s1 + 2);
}
}
可以调用filter传入断言去过滤。
String s = "123";
Optional<String> optionalS = Optional.ofNullable(s);
System.out.println(optionalS.filter(s1 -> s1.equals("1234")));
Stream
新增的Stream API(java.util.stream)将生成环境的函数式编程引入了Java库中。这是目前为止最大的一次对Java库的完善,以便开发者能够写出更加有效、更加简洁和紧凑的代码。
下面来看它的使用实例,先给出一个实体类:
public class Good {
private String name; // 商品名称
private long price; // 价格
private long sales; // 销量
private List<String> categories; // 类别
//省略构造器,getset,toString等
}
现在需要将商品中价格前十名且价格大于200的商品设为半价,如果在java7中,这可能很麻烦,但是使用Stream来操作,我们只需要以下方法即可:
void pross(List<Good> goods){
goods.stream().filter(good -> good.getPrice()>200)
.sorted((g1,g2)-> (int) (g2.getPrice()-g1.getPrice()))
.limit(10).forEach(good -> good.setPrice(good.getPrice()/2));
}
goods集合提供了元素序列的数据源,通过 stream() 方法获得 Stream,filter / sorted / limit 进行数据处理,“连接起来” 构成 “流水线”,forEach 最终执行。需要说明,filter / sorted / limit 的返回值均为 Stream,但它们并不立即执行,而是构成了 “流水线”,直到 forEach:最终执行,并且关闭 Stream。
像filter / sorted / limit这种构成流水线的,我们称为中间操作,而像forEach,则称为终止操作。
下面展示一下Stream的一些常用操作,还是上面的商品集合goods。
生成Stream。
goods.stream().forEach(System.out::println);
过滤操作。
goods.stream().filter(good -> good.getPrice()>200).forEach(System.out::println);
anyMatch / allMatch / noneMatch都是终止操作,传入断言条件,分别判断集合中是否含有符合条件的元素,是否全是符合条件的元素,是否都不符合条件,返回值为boolen类型。
boolean flag = goods.stream().anyMatch(good -> "name".equals(good.getName()));
findAny、findFirst,都是 “终止操作”,分别获取 Stream 元素序列的任意元素和第一个元素。
Optional<Good> optionalGood = goods.stream().filter(c -> "name".equals(c.getName())).findAny();
Optional<Good> optionalGood1 = goods.stream().filter(c -> "name".equals(c.getName())).findFirst();
map 是中间操作,将 Stream 序列的元素映射为其他的元素。
goods.stream().map(good -> good.getName()).forEach(System.out::println);
map 直接将 Stream 序列的元素映射到新的元素,假如 map 映射获得的是 Stream,flatMap 能够将各个 Stream 的元素合并到一个 Stream 中。其实可以理解成和map一样,只不过方法实现里要返回Stream。
goods.stream().flatMap(good -> Stream.of(good.getName())).forEach(System.out::println);
distinct 是 “中间操作”,即去重,去重的依据即为 Stream 序列元素类型的 equals 和 hashCode 方法。
goods.stream().map(Good::getName).distinct().forEach(System.out::println);
sorted用于排序,底下两行是一个效果。
goods.stream().sorted((g1,g2)-> (int) (g2.getPrice()-g1.getPrice())).forEach(System.out::println);
goods.stream().sorted(Comparator.comparing(Good::getPrice).reversed()).forEach(System.out::println);
limit / skip 是 “中间操作”,接收 long 类型的参数,实现 Stream 序列元素的截取和跳过。
//取第三个开始后的五个
goods.stream().skip(2).limit(5).forEach(System.out::println);
count / min / max是终止操作,分别返回元素数量,最小元素和最大元素。
goods.stream().count();
goods.stream().max(Comparator.comparing(Good::getPrice).reversed());
goods.stream().min(Comparator.comparing(Good::getPrice).reversed());
reduce,归约,是终止操作,用于将 Stream 中的元素归约到一个具体的值。
看下底下二个实例,是得到商品的价格总额。
goods.stream().reduce((g1,g2)->{
Good good = new Good();
good.setPrice(g1.getPrice()+g2.getPrice());
return good;
}).get();
//传入apply方法参与聚合,结果为空时返回第一个参数,不为空时第一个参数也参与运算
goods.stream().reduce(new Good(),(g1,g2)->{
Good good = new Good();
good.setPrice(g1.getPrice()+g2.getPrice());
return good;
}).getPrice();
着重第三种reduce的方法。
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
后面两个参数其实就是两个apply函数,值得一提的是,最后一个参数只有在并行Stream中才会有用,在单线程下是无用的。
他的执行流程可以理解为,在每个线程中将第一个参数在第二个参数的方法中计算出结果,最后再用第三个方法去把这些线程的结果合并计算。
给一个示例:
int s = Arrays.asList(2,3,4,5,6).stream().parallel().reduce(3,(a,b)->a+b,(a,b)->{
System.out.println("id"+Thread.currentThread().getId()+"\t"+a+"+"+b);
return a+b;
});
打印结果是:
结合上面的说法,思考一下,流程应该很明确了。
collect,是终止操作,collect 即 收集数据。collect以Collector<? super T, A, R> 作为参数,通常,我们使用 Collectors 提供的辅助函数获得收集器实例。看以下方法。
返回一个List示例。
goods.stream().map(Good::getName).collect(Collectors.toList());
返回一个Set示例。
goods.stream().map(Good::getName).collect(Collectors.toSet());
返回一个Map示例,按价格分组。
Map m = goods.stream().collect(Collectors.groupingBy(Good::getPrice));
与groupingBy类似,返回一个Map,但是按照是否符合条件按true,false分组。
Map m1 = goods.stream().collect(Collectors.partitioningBy(c -> c.getSales() >= 200));
reducing,Collectors.reducing 参数与 Stream.reduce 一致,作为 collect 参数,能够与 reduce 获得相同的结果。
通过 “并行 Stream” 即可获得 Stream API 的并行能力。
goods.stream().parallel();
goods.parallelStream();
并行 Stream 具有并行能力,其默认线程数量即为通过 Runtime.getRuntime().availableProcessors() 获得的线程数,并行未必能够提高性能,通常适用于 Stream 元素数量大、或单个元素处理非常耗时的场景。
Date/Time API
java8推出了更好的时间相关的API。
LocalDate:
看下localDate的创建。
//创建一个当前日期的LocalDate
LocalDate localDate = LocalDate.now();
System.out.println(localDate);
//指定年月日创建LocalDate
localDate=LocalDate.of(2020,11,11);
System.out.println(localDate);
//指定某年的第多少天创建
localDate = LocalDate.ofYearDay(2020, 256);
System.out.println(localDate);
得到LocalDate的信息。
System.out.println(localDate.getYear());
System.out.println(localDate.getDayOfMonth());
System.out.println(localDate.getDayOfYear());
System.out.println(localDate.getMonthValue());
使用 TemporalField 读取 LocalDate 的值,ChronoField 是个枚举,实现了TemporalField接口,我们可以使用ChronoField方式读取date。
LocalDate date = LocalDate.of(2019, 10, 27);
// 获得年份 2019
System.out.println(date.get(ChronoField.YEAR));
// 获得月份 10
System.out.println(date.get(ChronoField.MONTH_OF_YEAR));
// 获得这个月中的第几天 27
System.out.println(date.get(ChronoField.DAY_OF_MONTH));
// 获得这个星期的第几天 7
System.out.println(date.get(ChronoField.DAY_OF_WEEK));
将String解析为LocalDate。
String dateStr = "2019-10-27";
LocalDate parse = LocalDate.parse(dateStr);
System.out.println(parse);//2019-10-27
// 指定格式解析
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
LocalDate date = LocalDate.parse("2019/10/27", dateTimeFormatter);
System.out.println(date);//2019-10-27
使用 Period 操纵 date,获得连个日期之间的差值。
LocalDate date1 = LocalDate.of(2019, 10, 27);
LocalDate date2 = LocalDate.of(2019, 12, 25);
Period between = Period.between(date1, date2);
System.out.println(between.getDays());// 28
System.out.println(between.getMonths());//1
通过withAttribute修改不会改变原来的date,会在原来date的基础上形成新的LocalDate副本。
LocalDate date1 = LocalDate.of(2019, 10, 27);
LocalDate date2 = date1.withMonth(9);//2019-09-27
System.out.println(date2);
LocalDate date3 = date2.withYear(2018);//2018-09-27
System.out.println(date3);
// 2019-10-27
System.out.println(date1);
TemporalAdjuster 时间矫正器修改时间也是不会改变原来的date,会新生成LocalDate 副本,相比于withAttribute,其API更加丰富,提供大量的静态工厂方法。
LocalDate date1 = LocalDate.of(2019, 10, 27);
LocalDate date2 = date1.with(TemporalAdjusters.firstDayOfMonth());// 2019-10-01
LocalDate date3 = date1.with(TemporalAdjusters.firstDayOfYear());
// 2019-01-01
System.out.println(date3);
LocalDate date4 = date1.with(TemporalAdjusters.lastDayOfYear());
// 2019-12-31
System.out.println(date4);
LocalTime:
LocalTime的用法和LocalDate很相似。
//创建时间
LocalTime now = LocalTime.now();
System.out.println(now);//22:49:03.360
LocalTime of = LocalTime.of(22, 47);
System.out.println(of);//22:47
//修改时间
LocalTime time = LocalTime.of(22, 50);
LocalTime time1 = time.withHour(2);//02:50
System.out.println(time1);
LocalTime time2 = time.withMinute(10);//22:10
System.out.println(time2);
//获得时间秒差
LocalTime time1 = LocalTime.of(22, 50);
LocalTime time2 = LocalTime.of(23, 10);
Duration duration = Duration.between(time1,time2);
System.out.println(duration.getSeconds());
LocalDateTime:
LocalDate 和 LocalTime 能相互合并成 LocalDateTime ,LocalDateTime 也可以转为 LocalDate 或者 LocalTime。其他用法和之前也很相似。
LocalDate localDate = LocalDate.of(2019, 10, 27);
LocalTime localTime = LocalTime.of(23, 20, 00);
// 合并为 LocalDateTime
LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
//指定格式输出
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd hh:mm:ss");
String ldt = dateTimeFormatter.format(localDateTime);
System.out.println(ldt);//2019-10-27T23:20
// 转为LocalDate
LocalDate localDate1 = localDateTime.toLocalDate();
System.out.println(localDate);//2019-10-27
// 转为 LocalTime
LocalTime localTime1 = localDateTime.toLocalTime();
System.out.println(localTime);// 23:20
Instant :
计算机存储的当前时间,本质上只是一个不断递增的整数。Java提供的System.currentTimeMillis()返回的就是以毫秒表示的当前时间戳。
这个当前时间戳在java.time中以Instant类型表示,我们用Instant.now()获取当前时间戳,效果和System.currentTimeMillis()类似。
Instant now = Instant.now();
System.out.println(now.getEpochSecond()); // 秒
System.out.println(now.toEpochMilli()); // 毫秒
ZoneId:
java8中 java.time.ZoneId代替了老版本java.util.TimeZone 。
默认是当前时区和UTC /格林威治的固定偏差值。
ZoneId shanghai = ZoneId.of("Asia/Shanghai");
LocalDate date = LocalDate.of(2019, 10, 27);
// 设置时区
ZonedDateTime zonedDateTime = date.atStartOfDay(shanghai);
// 获得偏移
ZoneOffset offset = zonedDateTime.getOffset();
System.out.println(offset);//+08:00
时区时间计算。
// 芝加哥时间
ZoneId zoneId = ZoneId.of("America/Chicago");
Instant instant = Instant.now();
ZonedDateTime zonedDateTime = instant.atZone(zoneId);
LocalDateTime localDateTime = zonedDateTime.toLocalDateTime();
System.out.println(localDateTime);
//上海时间
ZoneId zoneId1 = ZoneId.of("Asia/Shanghai");
LocalDateTime localDateTime1 = LocalDateTime.ofInstant(instant,zoneId1);
System.out.println(localDateTime1);
Base64
对Base64编码的支持已经被加入到Java 8官方库中,这样不需要使用第三方库就可以进行Base64编码,例子代码如下:
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class Base64s {
public static void main(String[] args) {
final String text = "Base64 finally in Java 8!";
final String encoded = Base64
.getEncoder()
.encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
System.out.println( encoded );
final String decoded = new String(
Base64.getDecoder().decode( encoded ),
StandardCharsets.UTF_8 );
System.out.println( decoded );
}
}
并行数组
Java8除Stream并行流外,对于普通数组,Java8提供了也简单的并行功能。
例如数组排序,一般使用Arrays.sort()方法串行排序,Java8新增方法Arrays.parallelSort()并行排序。
先用parallelSetAll并行随机插入数组元素,之后并行排序:
Long[] longs = new Long[1000];
Arrays.parallelSetAll(longs, index ->
ThreadLocalRandom.current().nextLong(1000000));
Arrays.parallelSort(longs);
Arrays.stream(longs).limit(10).forEach(System.out::println);
数组也可以用Stream来操作,
Long[] longs = new Long[1000];
Arrays.parallelSetAll(longs, index ->
ThreadLocalRandom.current().nextLong(10000));
Arrays.parallelSort(longs);
Map<Boolean, List<Long>> hashMap = Arrays.stream(longs).parallel().collect(Collectors.partitioningBy(l->{
return l>2000;
}));
hashMap.get(true).stream().limit(10).forEach(System.out::println);
并发性
以下是官方文档的原话翻译。
在新增Stream机制与lambda的基础之上,在java.util.concurrent.ConcurrentHashMap中加入了一些新方法来支持聚集操作。同时也在java.util.concurrent.ForkJoinPool类中加入了一些新方法来支持共有资源池(common pool)(请查看我们关于Java 并发的免费课程)。
新增的java.util.concurrent.locks.StampedLock类提供一直基于容量的锁,这种锁有三个模型来控制读写操作(它被认为是不太有名的java.util.concurrent.locks.ReadWriteLock类的替代者)。
在java.util.concurrent.atomic包中还增加了下面这些类:
DoubleAccumulator
DoubleAdder
LongAccumulator
LongAdder
JVM的新特性
使用Metaspace(元空间)代替持久代。
在JVM参数方面,使用-XX:MetaSpaceSize和-XX:MaxMetaspaceSize代替原来的-XX:PermSize和-XX:MaxPermSize。