Java8的十大新特性你了解多少呢?
JDK1.8继JDK1.5之后号称革命性改革
那下面我们来谈谈Java8的新特性:Lambda表达式、接口的默认方法与静态方法、方法引用、重复注解、扩展注解的支持、Optional、Stream、Date/Time API(JSR 310)、JavaScript引擎Nashorn、Base64等等。
1、Lambda表达式 Lambda表达式说是Java8最大的卖点,他将函数式编程引入了Java、允许把函数作为一个方法的参数、或者把代码看成数据
一个Lambda表达式可以由用逗号分隔的参数列表、–>符号与函数体三部分表示。例如:
Arrays.asList( "p", "k", "u","f", "o", "r","k").forEach( e -> System.out.println( e ) );为了使现有函数更好的支持Lambda表达式,Java 8引入了函数式接口的概念。函数式接口就是只有一个方法的普通接口。java.lang.Runnable与java.util.concurrent.Callable是函数式接口最典型的例子。为此,Java 8增加了一种特殊的注解@FunctionalInterface:
@FunctionalInterface public interface Functional { void method(); }2、接口的默认方法与静态方法
我们可以在接口中定义默认方法,使用default关键字,并提供默认的实现。所有实现这个接口的类都会接受默认方法的实现,除非子类提供的自己的实现。例如:
public interface DefaultFunctionInterface { default String defaultFunction() { return "default function"; } }我们还可以在接口中定义静态方法,使用static关键字,也可以提供实现。例如:
public interface StaticFunctionInterface { static String staticFunction() { return "static function"; } }上面两部份,lambda及接口的默认方法与静态方法的实现例子: Test.java
package tomcat3; import java.time.Instant; import java.util.Arrays; import java.util.List; import java.util.function.Function; /** * Created by User on 2017/8/30. */ public class Test { public static void main(String args[])throws Exception { // lambda 表达式和功能接口 Add a = (x,y)-> x *y; System.out.println(a.add(100,200)); System.out.println("------------------1"); //lambda表达式和集合 List<String> list = Arrays.asList("aaa","bbb","ccc"); list.forEach(e -> {System.out.println(e);}); System.out.println("-------------------"); //接口中可以包含静态方法 Add.interfaceStaticMethod(); System.out.println("-------------------"); //接口中可以包含默认方法,默认方法实现类并不是必须实现 Add add = (x,y)->x-y; add.defaultMethod(); System.out.println("-------------------"); //java.util.function 包提供的函数式接口 String s = "hello"; Function function = x->x+"world"; System.out.println(function.apply(s)); System.out.println("--------------------"); } }Add.java 接口
package tomcat3; /** * Created by User on 2017/8/30. */ public abstract interface Add { public int add(int x, int y) ; public static void interfaceStaticMethod(){ System.out.println("interface static method"); } public default void defaultMethod(){ System.out.println("default method"); } }这样也可以方便我们理解接口的定义、static静态修饰 知识点1:一个类,首先他会在内存里面有一个类对象,然后由类对象生成类的对象 知识点2:为什么接口Interface里面的值必须是常量呢? 因为类可以被实例化,实例化的类的对象里面的变量就会被赋初始值。比如String 是 null int是0,double是0.0。但是接口呢?接口不能被实例化,所以接口里面如果是变量的话不会被赋初始值这样就会出问题。所以接口里面的值必须是常量final而且一定是static不管写不写都是(这句话引用自李老师的讲课内容) 知识点3:那为什么它要是静态的呢?因为static是什么?是所有对象可以访问,而且可以直接通过类名访问。接口有对象么?显然没有,必须通过类名来访问所以是要静态的。
3、方法引用 通常与Lambda表达式联合使用,可以直接引用已有Java类或对象的方法。一般有四种不同的方法引用:
构造器引用。语法是Class::new,或者更一般的Class< T >::new,要求构造器方法是没有参数;
静态方法引用。语法是Class::static_method,要求接受一个Class类型的参数;
特定类的任意对象方法引用。它的语法是Class::method。要求方法是没有参数的;
特定对象的方法引用,它的语法是instance::method。要求方法接受一个参数,与3不同的地方在于,3是在列表元素上分别调用方法,而4是在某个对象上调用方法,将列表元素作为参数传入;
public static class Car { public static Car create( final Supplier< Car > 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。注意:这个构造器没有参数。 final Car car = Car.create( Car::new ); final List< Car > 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 ); 运行上述例子,可以在控制台看到如下输出(Car实例可能不同):
Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d4、重复注解 自从Java 5中引入注解以来,这个特性开始变得非常流行,并在各个框架和项目中被广泛使用。不过,注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。Java 8打破了这个限制,引入了重复注解的概念,允许在同一个地方多次使用同一个注解。 在Java 8中使用@Repeatable注解定义重复注解,实际上,这并不是语言层面的改进,而是编译器做的一个trick,底层的技术仍然相同。可以利用下面的代码说明:
package com.javacodegeeks.java8.repeatable.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; public class RepeatingAnnotations { @Target( ElementType.TYPE ) @Retention( RetentionPolicy.RUNTIME ) public @interface Filters { Filter[] value(); } @Target( ElementType.TYPE ) @Retention( RetentionPolicy.RUNTIME ) @Repeatable( Filters.class ) public @interface Filter { String value(); }; @Filter( "filter1" ) @Filter( "filter2" ) public interface Filterable { } public static void main(String[] args) { for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) { System.out.println( filter.value() ); } } }5、类型推断
package com.javacodegeeks.java8.type.inference; public class Value< T > { public static< T > T defaultValue() { return null; } public T getOrDefault( T value, T defaultValue ) { return ( value != null ) ? value : defaultValue; } } 下列代码是Value<String>类型的应用: package com.javacodegeeks.java8.type.inference; public class TypeInference { public static void main(String[] args) { final Value< String > value = new Value<>(); value.getOrDefault( "22", Value.defaultValue() ); } }6、Optional java8 增加了很多新的工具类(date/time类),并扩展了现存的工具类,以支持的并发编程、函数式编程等 Java最常见的bug就是空指针异常。Google Guava引入了Optionals类来解决NullPointerException,从而避免源码被各种null检查污染,以便开发者写出更加整洁的代码。Java 8也将Optional加入了官方库。
Optional< String > fullName = Optional.ofNullable( null ); System.out.println( "Full Name is set? " + fullName.isPresent() ); System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) ); System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );如果Optional实例持有一个非空值,则isPresent()方法返回true,否则返回false;orElseGet()方法,Optional实例持有null,则可以接受一个lambda表达式生成的默认值;map()方法可以将现有的Opetional实例的值转换成新的值;orElse()方法与orElseGet()方法类似,但是在持有null的时候返回传入的默认值。 上述代码的输出结果如下: Full Name is set? false Full Name: [none] Hey Stranger!
再看下另一个简单的例子: Optional< String > firstName = Optional.of( “Tom” ); System.out.println( "First Name is set? " + firstName.isPresent() ); System.out.println( "First Name: " + firstName.orElseGet( () -> “[none]” ) ); System.out.println( firstName.map( s -> "Hey " + s + “!” ).orElse( “Hey Stranger!” ) ); System.out.println(); 这个例子的输出是: First Name is set? true First Name: Tom Hey Tom!
7、streams 新增的Stream API(java.util.stream)将生成环境的函数式编程引入了Java库中。这是目前为止最大的一次对Java库的完善,以便开发者能够写出更加有效、更加简洁和紧凑的代码。 Steam API极大得简化了集合操作(后面我们会看到不止是集合)
流的操作可以归结为几种:
7.1、遍历操作(map): 使用map操作可以遍历集合中的每个对象,并对其进行操作,map之后,用.collect(Collectors.toList())会得到操作后的集合。
7.1、遍历转换为大写:
List<String> output = wordList.stream(). map(String::toUpperCase). collect(Collectors.toList());7.3、平方数:
List<Integer> nums = Arrays.asList(1, 2, 3, 4); List<Integer> squareNums = nums.stream(). map(n -> n * n). collect(Collectors.toList());7.4、过滤操作(filter): 使用filter可以对象Stream中进行过滤,通过测试的元素将会留下来生成一个新的Stream,得到其中不为空的String
List<String> filterLists = new ArrayList<>(); filterLists.add(""); filterLists.add("a"); filterLists.add("b"); List afterFilterLists = filterLists.stream() .filter(s -> !s.isEmpty()) .collect(Collectors.toList());7.5、循环操作(forEach): 如果只是想对流中的每个对象进行一些自定义的操作,可以使用forEach:
List<String> forEachLists = new ArrayList<>(); forEachLists.add("a"); forEachLists.add("b"); forEachLists.add("c"); forEachLists.stream().forEach(s-> System.out.println(s));7.6、返回特定的结果集合(limit/skip): limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素:
List<String> forEachLists = new ArrayList<>(); forEachLists.add("a"); forEachLists.add("b"); forEachLists.add("c"); forEachLists.add("d"); forEachLists.add("e"); forEachLists.add("f"); List<String> limitLists = forEachLists.stream().skip(2).limit(3).collect(Collectors.toList());注意skip与limit是有顺序关系的,比如使用skip(2)会跳过集合的前两个,返回的为c、d、e、f,然后调用limit(3)会返回前3个,所以最后返回的c,d,e
7.7、排序(sort/min/max/distinct): sort可以对集合中的所有元素进行排序。max,min可以寻找出流中最大或者最小的元素,而distinct可以寻找出不重复的元素:
7.8、对一个集合进行排序:
List<Integer> sortLists = new ArrayList<>(); sortLists.add(1); sortLists.add(4); sortLists.add(6); sortLists.add(3); sortLists.add(2); List<Integer> afterSortLists = sortLists.stream().sorted((In1,In2)-> In1-In2).collect(Collectors.toList());7.9、得到其中长度最大的元素:
List<String> maxLists = new ArrayList<>(); maxLists.add("a"); maxLists.add("b"); maxLists.add("c"); maxLists.add("d"); maxLists.add("e"); maxLists.add("f"); maxLists.add("hahaha"); int maxLength = maxLists.stream().mapToInt(s->s.length()).max().getAsInt(); System.out.println("字符串长度最长的长度为"+maxLength);7.10、对一个集合进行查重:
List<String> distinctList = new ArrayList<>(); distinctList.add("a"); distinctList.add("a"); distinctList.add("c"); distinctList.add("d"); List<String> afterDistinctList = distinctList.stream().distinct().collect(Collectors.toList());其中的distinct()方法能找出stream中元素equal(),即相同的元素,并将相同的去除,上述返回即为a,c,d。
7.11、匹配(Match方法): 有的时候,我们只需要判断集合中是否全部满足条件,或者判断集合中是否有满足条件的元素,这时候就可以使用match方法: allMatch:Stream 中全部元素符合传入的 predicate,返回 true anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true
7.12、判断集合中没有有为‘c’的元素:
List<String> matchList = new ArrayList<>(); matchList.add("a"); matchList.add("a"); matchList.add("c"); matchList.add("d"); boolean isExits = matchList.stream().anyMatch(s -> s.equals("c"));7.13、判断集合中是否全不为空:
List<String> matchList = new ArrayList<>(); matchList.add("a"); matchList.add(""); matchList.add("a"); matchList.add("c"); matchList.add("d"); boolean isNotEmpty = matchList.stream().noneMatch(s -> s.isEmpty());则返回的为false
8、 Date/Time API(JSR 310) Java 8引入了新的Date-Time API(JSR 310)来改进时间、日期的处理。时间和日期的管理一直是最令Java开发者痛苦的问题。java.util.Date和后来的java.util.Calendar一直没有解决这个问题(甚至令开发者更加迷茫)。 因为上面这些原因,诞生了第三方库Joda-Time,可以替代Java的时间管理API。Java 8中新的时间和日期管理API深受Joda-Time影响,并吸收了很多Joda-Time的精华。新的java.time包包含了所有关于日期、时间、时区、Instant(跟日期类似但是精确到纳秒)、duration(持续时间)和时钟操作的类。新设计的API认真考虑了这些类的不变性(从java.util.Calendar吸取的教训),如果某个实例需要修改,则返回一个新的对象。 我们接下来看看java.time包中的关键类和各自的使用例子。首先,Clock类使用时区来返回当前的纳秒时间和日期。Clock可以替代
System.currentTimeMillis()和TimeZone.getDefault()。 // Get the system clock as UTC offset final Clock clock = Clock.systemUTC(); System.out.println( clock.instant() ); System.out.println( clock.millis() );第二,关注下LocalDate和LocalTime类。LocalDate仅仅包含ISO-8601日历系统中的日期部分;LocalTime则仅仅包含该日历系统中的时间部分。这两个类的对象都可以使用Clock对象构建得到。
// Get the local date and local time final LocalDate date = LocalDate.now(); final LocalDate dateFromClock = LocalDate.now( clock ); System.out.println( date ); System.out.println( dateFromClock ); // Get the local date and local time final LocalTime time = LocalTime.now(); final LocalTime timeFromClock = LocalTime.now( clock ); System.out.println( time ); System.out.println( timeFromClock );上述例子的输出结果如下: 2014-04-12 2014-04-12 11:25:54.568 15:25:54.568
如果你需要特定时区的data/time信息,则可以使用ZoneDateTime,它保存有ISO-8601日期系统的日期和时间,而且有时区信息。下面是一些使用不同时区的例子:
// Get the zoned date/time final ZonedDateTime zonedDatetime = ZonedDateTime.now(); final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock ); final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) ); System.out.println( zonedDatetime ); System.out.println( zonedDatetimeFromClock ); System.out.println( zonedDatetimeFromZone );这个例子的输出结果是: 2014-04-12T11:47:01.017-04:00[America/New_York] 2014-04-12T15:47:01.017Z 2014-04-12T08:47:01.017-07:00[America/Los_Angeles] 最后看下Duration类,它持有的时间精确到秒和纳秒。这使得我们可以很容易得计算两个日期之间的不同,例子代码如下:
// Get duration between two dates final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 ); final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 ); final Duration duration = Duration.between( from, to ); System.out.println( "Duration in days: " + duration.toDays() ); System.out.println( "Duration in hours: " + duration.toHours() );这个例子用于计算2014年4月16日和2015年4月16日之间的天数和小时数,输出结果如下: Duration in days: 365 Duration in hours: 8783 对于Java 8的新日期时间的总体印象还是比较积极的,一部分是因为Joda-Time的积极影响,另一部分是因为官方终于听取了开发人员的需求。如果希望了解更多细节,可以参考官方文档。
9、Nashorn JavaScript引擎 Java 8提供了新的Nashorn JavaScript引擎,使得我们可以在JVM上开发和运行js应用。Nashorn JavaScript引擎是javax.script.ScriptEngine的另一个实现版本,这类Script引擎遵循相同的规则,允许Java和javascript交互使用,例子代码如下:
ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName( "JavaScript" ); System.out.println( engine.getClass().getName() ); System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );这个代码的输出结果如下: jdk.nashorn.api.scripting.NashornScriptEngine Result: 2
10、Base64 对Base64编码的支持已经被加入到Java 8官方库中,这样不需要使用第三方库就可以进行Base64编码,例子代码如下:
package com.javacodegeeks.java8.base64; import java.nio.charset.StandardCharsets; import java.util.Base64; public class Base64s { public static void main(String[] args) { final String text = "Base64 for 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 ); } }这个例子的输出结果如下: QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ== Base64 finally in Java 8! 新的Base64API也支持URL和MINE的编码解码。 (Base64.getUrlEncoder() / Base64.getUrlDecoder(), Base64.getMimeEncoder() / Base64.getMimeDecoder())。
11、并行数组 Java8版本新增了很多新的方法,用于支持并行数组处理。最重要的方法是parallelSort(),可以显著加快多核机器上的数组排序。下面的例子论证了parallexXxx系列的方法:
package com.javacodegeeks.java8.parallel.arrays; import java.util.Arrays; import java.util.concurrent.ThreadLocalRandom; public class ParallelArrays { public static void main( String[] args ) { 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(); } }上述这些代码使用parallelSetAll()方法生成20000个随机数,然后使用parallelSort()方法进行排序。这个程序会输出乱序数组和排序数组的前10个元素。上述例子的代码输出的结果是: Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378 Sorted: 39 220 263 268 325 607 655 678 723 793
12、并发性 基于新增的lambda表达式和steam特性,为Java 8中为java.util.concurrent.ConcurrentHashMap类添加了新的方法来支持聚焦操作;另外,也为java.util.concurrentForkJoinPool类添加了新的方法来支持通用线程池操作(更多内容可以参考我们的并发编程课程)。 Java 8还添加了新的java.util.concurrent.locks.StampedLock类,用于支持基于容量的锁——该锁有三个模型用于支持读写操作(可以把这个锁当做是java.util.concurrent.locks.ReadWriteLock的替代者)。 在java.util.concurrent.atomic包中也新增了不少工具类,列举如下: DoubleAccumulator DoubleAdder LongAccumulator LongAdder
13、 JVM的新特性 使用Metaspace(JEP 122)代替持久代(PermGen space)。在JVM参数方面,使用-XX:MetaSpaceSize和-XX:MaxMetaspaceSize代替原来的-XX:PermSize和-XX:MaxPermSize。
虽然Java8 是14年出的,现在回头写写发现,只是看解决不了根本问题,看看让你动手写的时候,你都傻了,一定要动手亲自试验。
Java8 使用技巧一: 根据某个对象的值去重 //function1 @Override public List getTrackRecordUsers(Integer userType) { List userTrackRecords = userTrackRecordRepository.findByUserType(userType); Set set = new HashSet<>(); userTrackRecords = userTrackRecords.stream().filter(userTrackRecord -> set.add(userTrackRecord.getCreateUsername())).collect(Collectors.toList()); return userTrackRecords; }
//function2 @Override public List getTrackRecordUsers(Integer userType) { List userTrackRecords = userTrackRecordRepository.findByUserType(userType); userTrackRecords = userTrackRecords.stream().collect(Collectors.collectingAndThen(Collectors.toCollection( // 利用 TreeSet 的排序去重构造函数来达到去重元素的目的 // 根据createUserName去重 () -> new TreeSet<>(Comparator.comparing(UserTrackRecord::getCreateUsername))), ArrayList::new)); return userTrackRecords; }