java8新特性整理

java9快来了,必须得梳理一下java8了。

官方文档:http://docs.oracle.com/javase/specs/jls/se8/html/index.html

一、接口的默认方法和静态方法

接口里也可以写方法体了,实现该接口的类不再强制实现该方法,只需要在方法签名增加default签名并实现方法体,如:

接口:

1public interface Compute { 2 default Integer add(Integer x,Integer y) { 3 return x+y; 4 } 5 Integer minus(Integer x,Integer y); 6}

实现:

1public class ComputeImpl implements Compute { 2 3 @Override 4 public Integer minus(Integer x, Integer y) { 5 return x-y; 6 } 7 8 public static void main(String[] args) { 9 ComputeImpl c=new ComputeImpl(); 10 System.out.println(c.add(2, 1)); 11 System.out.println(c.minus(22, 1)); 12 } 13}

接口可以定义静态方法,通过接口调用。实现类不需实现,也无法在实现类中直接调用。

1public interface Compute { 2 default Integer add(Integer x,Integer y) { 3 return x+y; 4 } 5 Integer minus(Integer x,Integer y); 6 7 static void test() { 8 System.out.println("static method test()"); 9 } 10}

调用时:

Compute.test();

注意:缺省方法或静态方法引入,让java拥有了C++类似的多重继承的能力,所以同样会存在继承导致的歧义。

二、内部类访问外部变量

java7及以前,在内部类中访问外部变量,需要外部变量定义为final。java8中final关键字不是必须的了,但是需要确保不会修改该变量,否则仍旧会编译错误。

对于外部来的静态成员和字段,可以任意访问。

lambda中同内部类相同。

三、Lambda 表达式

3.1) 什么是lambda

http://www.importnew.com/8118.html

① 格式

lambda是一个语法糖,编写代码时可以提高编码效率,实际编译后的结果是一样的。形如:

1(Class1 p1,Class2 p2...pn) -> { 2 statement1; 3 statement2; 4 //... 5 statementn; 6}

它相当于jdk7及以前的内部类:

1new Interface1(){ 2 @Override 3 public Class1 abstractMethod(Class1 p1,Class2 p2...pn){ 4 statement1; 5 statement2; 6 //... 7 statementn; 8 } 9}

② lambda格式说明:

(Class1 p1,Class2 p2...pn) 为lambda头,或称为参数列表,即接口中方法的参数列表。

-> 为 lambda运算符。

{statementn...;} 为 lambda体,即接口中方法实现的代码块。

③ lambda代码简化规则

由于jdk在编译时会有代码检查,java8 针对lambda增强了代码检查时的推测能力,他可以根据上下文对变量类型、代码块关键字进行一些推断,因此提供了lambda简化的能力。

  • 参数列表中的变量类型可以省略,jdk会根据接口中的参数进行匹配。

  • 如果参数只有一个,参数列表的括号可以省略。

  • 如果方法体中的语句只有一行,那么大括号和分号可以省略。需要注意,如果方法体有返回值,return关键字也一起省略。

④ lambda和内部类的区别

除了精简了代码之外,lambda中如果引用this,那么this指针是包装类,即lambda被调用的类;而内部类中,则是指内部类。

3.2) 函数式接口

要用lambda形式使用接口,需要接口为一种特殊的接口类型,即函数式接口。所谓函数接口,是只有一个抽象方法的接口。

所谓函数式接口中的抽象方法,并不是抽象类中的抽象方法,它不需要abstract关键修饰,是指jdk7及以前的没有方法体的方法。

函数式接口中除了唯一的未实现方法之外,可以有其他有函数体的default方法。

只要符合以前上条件,就是一个合法的函数式接口。java8中提供了一个注解@FunctionalInterface,将它标注在接口定义上面,可以在编译阶段校验,如果接口中定义了第二个抽象方法会编译失败。该注解不是必需的。

1@FunctionalInterface 2public interface Compute { 3 default Integer add(Integer x,Integer y) { 4 return x+y; 5 } 6 Integer minus(Integer x,Integer y); 7}

3.3) 测试lambda

① 无参无返回值

接口定义:

1public interface ICompute { 2 public void print(); 3}

定义使用该接口的方法:

1private void hello(ICompute c){ 2 c.print(); 3}

测试用例1——传统内部类方式

1@Test 2 public void testAnonymousInnerClass(){ 3 int i=12; 4 hello(new ICompute() { 5 6 @Override 7 public void print() { 8 System.out.println(i);//jdk7会报错,要求i为final;java8则不报错,假定你不会修改,如果修改就会报错 9 System.out.println("Hello anonymous inner class."); 10 } 11 }); 12 }

测试用例2——lambda

hello(()-> System.out.println("Hello lambda"));

② 有参无返回值

接口定义:

1public interface FunctionalInterfaceVoidArg { 2 public void sayHi(String str); 3}

定义使用该接口的方法:

1private void sayHi(String str,FunctionalInterfaceVoidArg object) { 2 object.sayHi(str); 3}

测试用例:

1@Test 2public void testLambdaVoidHasArg(){ 3 sayHi("peter",x-> System.out.println("Hello "+x)); 4}

② 有返回值

接口定义:

1public interface FunctionalInterfaceReturn { 2 public String generateTime(); 3}

定义使用该接口的方法:

1private String getTime(FunctionalInterfaceReturn fi){ 2 return fi.generateTime(); 3}

测试用例:

1@Test 2public void testLambdaReturn(){ 3 String str=getTime(()->{ 4 return String.valueOf(System.currentTimeMillis()); 5 }); 6 System.out.println(str); 7 8 //省略大括号和分号的简化lambda 9 str=getTime(()->new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); 10 System.out.println(str); 11}

3.4) java8内置的特定函数式接口

① Predicate

接收一个参数,返回一个boolen值,用于条件判断。

1//使用lambda 2Predicate<String> pre=(x)-> x.startsWith("t_"); 3boolean isTable=pre.startsWith("t_users"); 4//使用方法引用 5Predicate<Boolean> notNull=Objects::nonNull; 6Predicate<Boolean> isNull=Objects::isNull; 7 8Predicate<Sting> isEmpty=String::isEmpty; 9Predicate<String> notEmpty=isEmpty.negate();

② Comparator

这是一个早已存在的接口,在java8中实现了一些默认方法。

3.5) java8内置的通用函数式接口

① Supplier

1package java.util.function; 2@FunctionalInterface 3public interface Supplier<T>{ 4 T get(); 5}

适用于无入参,有返回值的场景。如生产者。

典型的应用是List.forEach(Supplier<T> s);

Arrays.asList("aa","bb","cc").forEach((x)->System.out.println(x));

② Consumer

1package java.util.function; 2 3@FunctionalInterface 4public interface Consumer<T> { 5 void accept(T t); 6}

适用于有一个入参,无返回值的场景。如消费者。

③ Function

1package java.util.function; 2@FunctionalInterface 3public interface Function<T, R> { 4 R apply(T t); 5}

适用于一个类型的入参,返回一个类型的场景。

④ UnaryOperator

1package java.util.function; 2@FunctionalInterface 3public interface UnaryOperator<T> extends Function<T, T> { 4 static <T> UnaryOperator<T> identity() { 5 return t -> t; 6 } 7}

事实上,他就是Function<T,R>的一种特殊情况,即Function<T,T>

⑤ Predicate

1package java.util.function; 2@FunctionalInterface 3public interface Predicate<T>{ 4 boolean test(T t); 5}

它类似于Function<T, Boolean>,但并未继承Function,用于条件判断,类似于guava中的Predicate。

⑥ 其他类型

java8在包java.util.function 中提供了很多类型,如果不能满足可以查看javadocs,查找合适的类型,或自定义。

四、方法引用

在需要函数参数的方法中,我们可以把另一个同类型的方法直接传入,这称为方法引用的绑定。类似于C语言中的函数指针。

lambda表达式可以替代方法引用;或者说方法引用是lambda的一种特例,方法引用不可以控制传递参数。

4.1) 构造器引用

1private Person construntorRef(Supplier<Person> sup){ 2 Person p=sup.get(); 3 return p; 4} 5 6@Test 7public void testConstructorRef(){ 8 Person p=construntorRef(Person::new); 9 System.out.println(p); 10}

需要有无参的构造器。

4.2) 静态方法引用

1 private static void print(String s){ 2 System.out.println(s); 3 } 4 5 @Test 6 public void testStaticRef(){ 7 Arrays.asList("aa","bb","cc").forEach(TestMethodReference::print); 8 }

so easy,只要静态方法的参数列表和FI需要的参数一致就可以。

4.3) 成员方法引用

1@Test 2 public void testMemberMethodRef(){ 3 Arrays.asList("aa","bb","cc").forEach(System.out::println); 4 }

so easy,只要成员方法的参数列表和FI需要的参数一致就可以。

4.4) 类的任意对象的实例方法引用(很怪异)

1@Test 2public void testClassMemberMethodRef(){ 3 String[] strs={"zzaa","xxbb","yycc"}; 4 Arrays.sort(strs,String::compareToIgnoreCase);//OK 5 System.out.println(Arrays.asList(strs)); 6 File[] files = new File("C:").listFiles(File::isHidden); // OK 7}

👻,前方高能,请关掉耳机的音乐,认真思考,小心行事。

传统的java开发中,是不允许使用类名去调用成员方法的,这是一个基本原则,那么这里的这种写法就有点不太容易理解了。还是用实例说明:

用到的内部类:

1import lombok.Data; 2 3@Data 4public static class Person{ 5 private String name; 6 private Integer age; 7 8 public int mycompare(Person p1){ 9 return p1.getAge()-this.getAge(); 10 } 11 public void print(){ 12 System.out.println(this); 13 } 14 15 public void println(Person p){ 16 System.out.println(p); 17 } 18 19 public int compareByAge(Person a,Person b){ 20 return a.getAge().compareTo(b.getAge()); 21 } 22} 23 24public static class APerson{ 25 public void print(){ 26 System.out.println(this); 27 } 28 29 public void println(Person p){ 30 System.out.println(p); 31 } 32}

测试代码:

1@Test 2 public void testClassMemberMethodRef2() { 3 // R apply(T t);//要求一个参数 4 Function<String, String> upperfier1 = String::toUpperCase; 5 UnaryOperator<String> upperfier2 = (x) -> x.toUpperCase();//这里没有参数,即0个 6 7 /* 8 * 小结:如果方法引用表达式 "String::toUpperCase" 可以用lambda表达式中参数的指定成员方法(这个成员方法的参数比FI要求的参数少一个改类型的参数)改写, 9 * 那么就可以使用 "类的实例方法"来表示方法引用。 10 * 11 * 或者说:如果lambda表达式的lambda体中使用的方法是参数匹配的方法,那么方法引用表达式就用"类引用对象的实例方法"。 12 * 13 * lambda的参数是方法的主体。 14 */ 15 16 17 class Print { 18 public void println(String s) { 19 System.out.println(s); 20 } 21 } 22 // void accept(T t); 23 24 Consumer<String> sysout1 = new Print()::println; 25 Consumer<String> sysout2 = (x) -> new Print().println(x); 26 27 /* 28 * 小结:如果方法引用表达式 "new Print()::println" 可以用lambda表达式中参数的具体对象的参数匹配的成员方法改写, 29 * 那么就用 "对象的实例方法"来表示方法引用。 30 * 31 * 或者说:如果lambda表达式的lambda体中使用的方法来操作lambda的参数,那么方法引用表达式就用"对象的实例方法"。 32 * 33 * lambda的参数是方法的参数。 34 */ 35 36 //有一个更让人易混淆的例子,可以用上面的规则来验证,Arrays.sort(T t,Comparator<? extends t> c) 37 38 class Person { 39 public int com1(Person p) { 40 return 1; 41 } 42 43 public int com2(Person p1, Person p2) { 44 return 1; 45 } 46 } 47 48 // int compare(T o1, T o2);//需要两个参数 49 50 Person【】 ps = { new Person(), new Person() }; 51 Arrays.sort(ps, Person::com1); 52 Arrays.sort(ps, (x,y)->x.com1(y)); 53 54 Arrays.sort(ps, new Person()::com2); 55 Arrays.sort(ps, (x,y)->new Person().com2(x, y)); 56 57 //按照以上规则验证应该能说明清楚。 58 59 /* 60 * 但是一个接口为什么有两种写法?缺省的lambda会匹配FI方法,即"int compare(T o1, T o2);" 61 * 从上面的lambda表达式来分析,默认的使用lambda应该是: 62 */ 63 64 Comparator<Person> comparator1 = new Person()::com2; 65 66 /* 67 * 下面的方式又是怎么回事呢? 68 */ 69 Comparator<Person> comparator2 = Person::com1; 70 System.out.println(comparator2); 71 72 /* 73 * 任一个两个参数的FI接口方法(int compare(T o1, T o2)),都可以用引用减少一个参数的方法(int o1<T>.compare(T o2))来代替,而引用对象本身作为另一个隐含参数,那么方法引用的对象用类名,表示类的任意对象。 74 75 还是有点乱?我们来换一个角度来看一下: 76 首先,我们需要的是int compare(T o1, T o2)是两个参数; 77 其次,先不考虑::前缀是类还是对象,你给了我一个compare(T o2),少一个参数?怎么办? 78 lambda机制为了解决这个问题,它使用::前面的类名new一个对象,当做需要的缺少的那个参数,这就是类的实例方法。 79 */ 80 }

小结一下:

1首先明确此处需要的方法参数列表,此处标记参数个数为N,那么: 2 31. 如果传入的方法是一个类型的静态方法,而且参数匹配,使用“类的静态方法引用”;这应该不难理解。 4 52. 如果传入的方法是一个实例的成员方法,而且参数匹配,使用“实例的成员方法”;这也应该不难理解。 6 73. 如果传入的方法是一个类型T的实例的成员方法,而且参数为N-1个,缺少了一个T类型的参数,那么就使用“T类型的实例方法”。

烧脑分析类的实例方法省略了哪个参数

前面的例子,FI的两个参数是同一个类型,如果类型不同呢?省略了哪个参数呢?

是按照位置省略了第一个,亦或者是省略了最后一个?

还是按照类型自动去对应,而不关心第几个呢?

这个时候,我们能想到的办法可能是去看源码,但是一般看代码没有个把礼拜甚至更长,毛都看不出来。我们还是用一个例子来分析一下吧。

定义一个FI接口:

1package com.pollyduan.fi; 2 3public interface TestInterface { 4 //随便什么名字,lambda并不关心,因为FI只有一个接口,并且根据参数来匹配 5 public void anyStringAsName(TestBean1 bean1,TestBean2 bean2); 6}

编写两个用于参数的类:

TestBean1.java

1package com.pollyduan.fi; 2 3public class TestBean1 { 4 public void expect1(TestBean1 bean1){ 5 6 } 7 public void expect2(TestBean2 bean2){ 8 9 } 10 public void test1(TestInterface i){ 11 12 } 13}

TestBean2.java

1package com.pollyduan.fi; 2 3public class TestBean2 { 4 public void expect1(TestBean1 bean1){ 5 6 } 7 public void expect2(TestBean2 bean2){ 8 9 } 10 public void test1(TestInterface i){ 11 12 } 13}

二者区别不大。

编写测试类:

1package com.pollyduan.fi; 2 3public class TestFIMain { 4 public static void main(String[] args) { 5 TestBean1 bean1=new TestBean1(); 6 bean1.test1(TestBean1::expect1);//① 7 bean1.test1(TestBean1::expect2);//② ok 8 bean1.test1(TestBean2::expect1);//③ 9 bean1.test1(TestBean2::expect2);//④ 10 11 TestBean2 bean2=new TestBean2(); 12 bean2.test1(TestBean1::expect1);//⑤ 13 bean2.test1(TestBean1::expect2);//⑥ ok 14 bean2.test1(TestBean2::expect1);//⑦ 15 bean2.test1(TestBean2::expect2);//⑧ 16 } 17}

测试方法中,除了标记OK的行正确,其他都报错。

分析:

首先我们要明确FI需要的参数列表是:(TestBean1,TestBean2)

  1. 我们先看①行,我们传入的"::"前导的类是TestBean1,而expect1方法匹配的是TestBean1类型的入参bean1,也就是说省略了TestBean2类型的参数bean2,FI中的最后一个参数。即便我们使用类TestBean1去new一个对象,也找不到TestBean2,因此这个错误。

  2. 我们先看②行,我们传入的"::"前导的类是TestBean1,而expect2方法匹配的是TestBean2类型的入参bean2,也就是说省略了TestBean1类型的参数bean1,那么lambda就可以使用"::"前导的TestBean1构建一个对象,作为第一个参数,从而匹配FI的接口方法。ok。

  3. 我们先看③行,我们传入的"::"前导的类是TestBean2,而expect1方法匹配的是TestBean1类型的入参bean1,也就是说省略了TestBean2类型的参数bean2,FI的最后一个参数。按照第二步的分析,我们用"::"前导的类TestBean2去new一个对象,应该可以凑足两个参数。实际测试会发现这不灵。这就证明了只能省略第一个参数,而且,用"::"前导的类也必须是第一个参数的类型。

  4. 同第一步类似,第④行代码,找不到TestBean1的参数,有错误可以理解。

  5. 至于⑤~⑧,只是替换了外层的test1的主体,没有任何区别。这证明了,lambda的匹配与外层是什么鬼没有任何关系,它只关心外层需要的FI的参数列表。

  6. 请不要看下一步,在这里停下来冷静的思考一下,如果我们把TestInterface中FI方法的参数位置换一下,即public void anyStringAsName(TestBean2 cat,TestBean1 dog);,结果应该是哪两行正确呢?认真思考一下,实在想不明白跑一下测试用例,也许对理解更有帮助。

  7. 如果想明白了用这个思路验证一下:参照参数列表(TestBean2,TestBean1),可以确定只可以省略第一个参数即TestBean2,那么"::"前导类必须是TestBean2,用于自动创建对象;而未省略的参数是TestBean1,那么方法名为expect1,结果为xxx(TestBean2::expect1),即③和⑦,你答对了吗?

五、容器功能增强

5.1) Collection

List<String> list = Arrays.asList(new String[] { "xxx", "aaa", "zzz", "eee", "yyy", "ccc" });

stream()与parallelStream()

打开流
1Stream<String> stream1 = list.stream(); 2Stream<String> stream2 = list.stream().parallel();

Stream类有几个工厂方法可以创建Stream:

1Stream<Object> stream3 = Stream.builder().add(list).build(); 2 3Stream<Object> stream4 = Stream.empty(); 4 5Stream<String> stream5 = Stream.of("111"); 6 7Stream<String> stream6 = Stream.of("111","222","333"); 8 9ArrayBlockingQueue<String> queue=new ArrayBlockingQueue<>(20); 10queue.addAll(list); 11Stream<String> stream7 = Stream.generate(()->{ 12try { 13 return queue.take(); 14} catch (InterruptedException e) { 15 e.printStackTrace(); 16} 17return null; 18}); 19stream7.forEach(System.out::println); 20 21Stream<String> stream8 = Stream.concat(list.stream(), list.stream());

事实上我们很少直接操作Stream对象,而是作为中间结果,最终要关闭流返回数据。

关闭流

查看Stream实现,其中返回非Stream对象的方法,即为关闭流返回数据的方法。

1Object[] toArray(); 2 3<A> A[] toArray(IntFunction<A[]> generator); 4 5T reduce(T identity, BinaryOperator<T> accumulator); 6 7Optional<T> reduce(BinaryOperator<T> accumulator); 8 9<U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner); 10 11<R> R collect(Supplier<R> supplier,BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner); 12 13<R, A> R collect(Collector<? super T, A, R> collector); 14 15Optional<T> min(Comparator<? super T> comparator); 16 17Optional<T> max(Comparator<? super T> comparator); 18 19long count(); 20 21boolean anyMatch(Predicate<? super T> predicate); 22 23boolean allMatch(Predicate<? super T> predicate); 24 25boolean noneMatch(Predicate<? super T> predicate); 26 27Optional<T> findFirst(); 28 29Optional<T> findAny();

Stream对象打开后,关闭前,是我们要对数据做处理的主要途径。

collect()

将流转换成List。

1@Test 2public void testStreamToList() { 3 List<String> list1 = list.stream().collect(Collectors.toList()); 4}
sort
1@Test 2public void testStreamSort() { 3 System.out.println(list); 4 // list.sort((x, y) -> x.compareTo(y)); 5 List<String> list2 = list.stream().parallel() 6 .sorted(Comparator.comparing(String::toLowerCase)) 7 .collect(Collectors.toList()); 8 System.out.println(list2); 9}
filter
1@Test 2public void testStreamFilter() { 3 System.out.println(list); 4 List<String> list2 = list.stream() 5 .filter(x->!x.equals("zzz")) 6 .collect(Collectors.toList()); 7 System.out.println(list2); 8}
map

转换大小写

1List<String> list...; 2list.stream().map(String::toUpperCase).forEach(System.out::println);

提取局部信息:

1Field[] fields = entityClass.getDeclaredFields(); 2String fieldNames = Arrays.asList(fields).stream().map(x -> x.getName()).collect(Collectors.joining(","));

提取局部信息到到Map

Map<String, Integer> peoples = persons.stream().collect(Collectors.toMap(Person::getName, Person::getAge));

forEach()

1list.forEach(x->System.out.println(x)); 2 3list.forEach(System.out::println);

reduce()

一个参数:

1@Test 2public void testStreamReduce1() { 3 System.out.println(list); 4 BinaryOperator<String> accumulator=BinaryOperator.maxBy((x,y)->x.compareTo(y)); 5 Optional<String> max = list.stream() 6 .reduce(accumulator); 7 System.out.println(max); 8}

两个参数:

1@Test 2public void testStreamReduce2() { 3 System.out.println(list); 4 Optional<String> max = list.stream() 5 .reduce((x,y)->x+y); 6 System.out.println(max); 7}

三个参数:

1@Test 2public void testStreamReduce3() { 3 System.out.println(list); 4 StringBuilder joining = list.stream() 5 .reduce(new StringBuilder(), 6 (x, e) -> x.append(e).append(","), 7 (u, t) -> u.append(t) 8 ); 9 System.out.println(joining); 10}

参数1:返回的类型,此处传入一个初始对象; 参数2:一个Funcion(x,e),x为处理的数据中间结果,e为当前元素。 参数3:同2。

这个例子有点复杂,打点日志分析一下:

1@Test 2public void testStreamReduce3A() { 3 System.out.println(list); 4 StringBuilder joining = list.stream() 5 .reduce(new StringBuilder(), 6 (x, e) -> { 7 System.out.println("2:t="+x+"; u="+e); 8 x.append(",").append(e); 9 return x; 10 }, 11 (x, e) -> { 12 System.out.println("3:t="+x+"; u="+e); 13 //x.append(",").append(e); 14 return x; 15 } 16 ); 17 System.out.println(joining); 18}

你会发现第三个FI并没有执行。stream加上.parallel(),并行处理,就看到第三个FI执行了。但是结果有点意外,乱了。

list中有6个元素,2打印6次正常;而3只打印5次。而且每运行一次,3的打印结果都是变化的。

12:t=; u=ccc 22:t=; u=eee 32:t=; u=aaa 42:t=; u=xxx 52:t=,ccc,eee,aaa; u=zzz 62:t=,ccc; u=yyy 73:t=,ccc,eee,aaa,xxx,zzz,yyy; u=,ccc,eee,aaa,xxx,zzz,yyy 83:t=,ccc,eee,aaa,xxx,zzz,yyy; u=,ccc,eee,aaa,xxx,zzz,yyy 93:t=,ccc,eee,aaa,xxx,zzz; u=,ccc,eee,aaa,xxx,zzz,yyy 103:t=,ccc,eee,aaa,xxx,zzz,yyy; u=,ccc,eee,aaa,xxx,zzz,yyy 113:t=,ccc,eee,aaa,xxx,zzz,yyy; u=,ccc,eee,aaa,xxx,zzz,yyy

count()

1@Test 2public void testStreamCount() { 3 long count = list.stream().count(); 4 System.out.println(count); 5}

distinct()

1@Test 2public void testStreamDistinct() { 3 List<String> list2 = list.stream().distinct().collect(Collectors.toList()); 4 System.out.println(list2); 5}

allMatch/anyMatch/noneMatch

1@Test 2public void testStreamAllMatch() { 3 boolean b = list.stream().allMatch(x->x.matches("a*")); 4 System.out.println("AllMath:"+b); 5 b = list.stream().anyMatch(x->x.matches("a*")); 6 System.out.println("anyMatch:"+b); 7 b = list.stream().noneMatch(x->x.matches("a*")); 8 System.out.println("noneMatch:"+b); 9}

5.2)Map

Map不支持stream(),不过java8也为它增加了附加检查的方法,如: putIfAbsent() computeIfPresent() getOrDefault() merge()

forEach

1Map<String,String> map=...; 2map.forEach((key,value)->System.out.pritnln("key="+key+";value="+value))

5.3) Optional

六、Date Time API

java8 的 Date Time API来自jsr310(https://jcp.org/en/jsr/detail?id=310),这个specification的主导者中jota-time的作者Stephen Colebourne就是四分之一。

除了改为线程安全,基本上很大程度上延续了java自己的设计风格。而joda-time一些很方便的特性,如Property、toString(fmt) 都没有引入。

LocalDateTime 与Calendar相比改变有限 LocalDate 抄过来的 LocalTime 抄过来的 Instance 抄过来的 DateTimeFormatter 节操,还不如SimpleDateFormat,你怎么不去屎? int LocalDateTime.getDayOfMonth()DayOfWeek dateTime.getDayOfWeek()什么玩意儿?

1@Test 2 public void testClock(){ 3 Clock clock = null; 4 5 clock=Clock.system(ZoneId.of("Europe/Paris")); 6 System.out.println(clock.millis()); 7 8 clock=Clock.systemDefaultZone(); 9 long millis = clock.millis(); 10 System.out.println(millis); 11 12 System.out.println(System.currentTimeMillis()); 13 14 Instant instant = clock.instant(); 15 Date legacyDate = Date.from(instant); 16 System.out.println(legacyDate.getTime()); 17 } 18 19 @Test 20 public void testTimezone(){ 21 System.out.println(ZoneId.getAvailableZoneIds()); 22 ZoneId zone1 = ZoneId.of("Europe/Berlin"); 23 System.out.println(zone1.getRules()); 24 25 ZoneId zone2=ZoneId.of("Asia/Shanghai"); 26 System.out.println(zone2.getRules()); 27 } 28 29 @Test 30 public void testLocalDate(){ 31 //LocalDate是不可变的 32 LocalDate today = LocalDate.now(); 33 System.out.println(today); 34 35 LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS); 36 37 DayOfWeek dayOfWeek = tomorrow.getDayOfWeek(); 38 System.out.println(dayOfWeek); 39 System.out.println(dayOfWeek.getValue()); 40 } 41 42 @Test 43 public void testLocalTime(){ 44 LocalTime now1 = LocalTime.now(); 45 System.out.println(now1); 46 LocalTime now2=now1.plusMinutes(10); 47 System.out.println(now2); 48 LocalTime now3=now1.plus(2, ChronoUnit.HOURS); 49 System.out.println(now3); 50 } 51 52 @Test 53 public void testLocalDateTime(){ 54 LocalDateTime dateTime=LocalDateTime.now(); 55 System.out.println(dateTime); 56 dateTime=LocalDateTime.of(2017, 2, 22, 13, 23); 57 System.out.println(dateTime); 58 } 59 60 @Test 61 public void testFormater(){ 62 //DateTimeFormatter是线程安全的,SimpleDateFormat不是 63 LocalDateTime dateTime=LocalDateTime.now(); 64 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); 65 System.out.println(formatter.format(dateTime)); 66 }

欣赏一下joda-time:

1System.out.println(DateTime.now().toString("yyyy-MM-dd HH:mm:ss")); 2//2017-02-21 18:26:49 3 4//计算两个时间时间隔了多少天 5DateTime dateTime=new DateTime(2008,12,8,0,0); 6Duration duration=new Duration(dateTime,DateTime.now()); 7System.out.println(duration.getStandardDays()); 8 9//Interval可以获得两个时间每个单位的时间差 10DateTime dt1 = new DateTime(2008, 12, 8, 8, 31); 11DateTime dt2 = new DateTime(2017, 2, 21, 17, 21); 12Interval interval = new Interval(dt1.getMillis(), dt2.getMillis()); 13Period p = interval.toPeriod(); 14System.out.println(p.toString()); 15System.out.println("years:" + p.getYears()+";months:"+p.getMonths() 16 +";days:"+p.getDays()+";hours:"+p.getHours() 17 +";minutes:"+p.getMinutes()+";seconds:"+p.getSeconds() 18 +";mills:"+p.getMillis()); 19 20//链式代码 21DateTime dateTime=new DateTime(2017,2,21,0,0); 22System.out.println(dateTime.dayOfMonth() 23 .setCopy(28)//穿越到2017-02-28 24 .minusYears(9)//穿越到9年前 25 .dayOfMonth() 26 .withMaximumValue()//穿越到那年那月的最后一天,那天是29日 27 .dayOfWeek() 28 //.get()//29日那天是星期五 29 .setCopy(1)//(不管29日是星期几)穿越到29日那天所在的星期一 30 //Hello,我来过 2008-02-25T00:00:00.000+08:00

七、重复注解

java8之前一个注解在一个位置只能标注一次,java8允许标注多次。

注解定义:

1@Target( ElementType.TYPE ) 2@Retention( RetentionPolicy.RUNTIME ) 3public @interface Filters { 4 Filter[] value(); 5} 6 7@Target( ElementType.TYPE ) 8@Retention( RetentionPolicy.RUNTIME ) 9@Repeatable( Filters.class ) 10public @interface Filter { 11 String value(); 12}

在java8 中标注:

1@Filter( "filter1" ) 2@Filter( "filter2" ) 3public void sayHi(){}

编译器隐式地增加了一个Filters注解。查询注解可以看到:

@com.pollyduan.annotation.Filters(value=[@com.pollyduan.annotation.Filter(value=filter1), @com.pollyduan.annotation.Filter(value=filter2)])

在这之前实现同一目标需要这么做:

1@Filters({@Filter("filter1"),@Filter("filter2")}) 2public void sayHi(){}

实际上这是一致的,也就是说java8的重复注解是一个语法糖,编译器会自动包装成之前的格式。

八、Base64

Base64在java8中转正了,不需要再使用sun.misc.BASE64Encodersun.misc.BASE64Decoder了, 而是java.util.Base64,这是一个工厂类,它用来创建两个内部实现类:

1Encoder encoder = Base64.getEncoder(); 2String str="abcd"; 3String enc=encoder.encodeToString(str.getBytes()); 4System.out.println(enc); 5 6System.out.println(new String(Base64.getDecoder().decode(enc)));

九、Nashorn - a new javascript engine

十、异常捕获的改变

新的try...cache可以自动关闭在try表达式中打开的对象,而无需开发者手动关闭。

多个流对象打开语句,用分号分隔,不是逗号。

1try(ObjectInputStream in=new ObjectInputStream(new FileInputStream("p1.obj"))){ 2 System.out.println(Person.class.hashCode()); 3 Person person=(Person)in.readObject(); 4 System.out.println(person.staticString); 5 } catch (Exception e) { 6 e.printStackTrace(); 7 } 8}

不再需要:

1finally{ 2 in.close(); 3}

十一、nashorn

Java8 更新了JavaScript引擎,从jdk6引入的Mozilla Rhino引擎改为使用nashorn

测试一下,首先编写一个js文件my.js。

1function myfun(str){ 2 return str+new Date(); 3}

java调用js:

1package com.pollyduan.nashron; 2 3import java.io.FileReader; 4 5import javax.script.ScriptEngine; 6import javax.script.ScriptEngineManager; 7 8public class NashornTest { 9 10 public static void main(String[] args) throws Exception { 11 ScriptEngine nashorn = new ScriptEngineManager().getEngineByName("js"); 12 nashorn.eval(new FileReader("src/main/resources/my.js")); 13 Object ret = nashorn.eval("myfun(\"Hello,It\'s \")"); 14 System.out.println(ret); 15 } 16}

在java6、7自动使用rhino引擎,在java8则自动使用nashorn引擎。

java8中,建议显示指定使用nashorn:

ScriptEngine nashorn = new ScriptEngineManager().getEngineByName("nashorn");  

如果项目曾经使用过rhino,在移植到java8时,需要引入依赖并指定engineName为rhino

1<dependency> 2 <groupId>cat.inspiracio</groupId> 3 <artifactId>rhino-js-engine</artifactId> 4 <version>1.7.7.1</version> 5</dependency>

同时java修改:

ScriptEngine nashorn = new ScriptEngineManager().getEngineByName("rhino");

小结

java8,节操洒了一地。很多的改变违背了面向对象的初衷,多重继承、函数指针都有了,你要变成c--语言吗?

有些改变是积极的,不过效果差强人意。像date time api。

诸如Predicate、Optional、stream,从guava借鉴而来,也是不错的。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

Java日期时间API系列31

  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )