java8新特性详解

版权声明:本文为博主原创文章,未经博主允许不得转载,转载请注明出处. 博主博客地址是 http://blog.csdn.net/liubenlong007 https://blog.csdn.net/fgyibupi/article/details/62039628

java8已经出了很久,项目中也在使用。其最重要的特性就是Lambda表达式和函数式编程,这让我们的代码可以大大简化,更加优雅。
读者通过这篇文章,可以入门java8,并且可以开始进行java8的程序编写了。讲的还算细致,但是限于篇幅原理讲的不是太深入,原理以后有时间再单独写博客。

Lambda表达式

语法

parameter -> expression body

  • 可选类型声明 - 无需声明参数的类型。编译器可以从该参数的值推断。
  • 可选圆括号参数 - 无需在括号中声明参数。对于多个参数,括号是必需的。
  • 可选大括号 - 表达式主体没有必要使用大括号,如果主体中含有一个单独的语句。
  • 可选return关键字 - 编译器会自动返回值,如果主体有一个表达式返回的值。花括号是必需的,以表明表达式返回一个值。
/**
 * Created by liubenlong on 2016/11/7.
 */
public class LambdaTest {

    /**
     * lambda表达式测试
     * @param args
     */
    public static void main(String args[]){
        LambdaTest tester = new LambdaTest();

        //with type declaration
        MathOperation addition = (int a, int b) -> a + b;

        //with out type declaration
        MathOperation subtraction = (a, b) -> a - b;

        //with return statement along with curly braces
        MathOperation multiplication = (int a, int b) -> { return a * b; };
        //without return statement and without curly braces
        MathOperation division = (int a, int b) -> a / b;

        System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
        System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
        System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
        System.out.println("10 / 5 = " + tester.operate(10, 5, division));

        //with parenthesis
        GreetingService greetService1 = message -> System.out.println("Hello " + message);

        //without parenthesis
        GreetingService greetService2 = (message) -> System.out.println("Hello " + message);

        greetService1.sayMessage("Mahesh");
        greetService2.sayMessage("Suresh");
    }

    interface MathOperation {
        int operation(int a, int b);
    }

    interface GreetingService {
        void sayMessage(String message);
    }

    private int operate(int a, int b, MathOperation mathOperation){
        return mathOperation.operation(a, b);
    }
}

输出结果:

10 + 5 = 15
10 - 5 = 5
10 x 5 = 50
10 / 5 = 2
Hello Mahesh
Hello Suresh

以下是对使用上述例子认为是重要的观点:

  • lambda表达式主要用于定义内联执行的功能的接口,即只有一个单一的方法接口。在上面的例子中,我们使用不同类型的lambda表达式定义MathOperation接口的opearation方法。然后,我们定义GreetingService的sayMessage实现。
  • Lambda表达式消除匿名类的需求,并给出了一个非常简单但功能强大的函数式编程能力。

jdk中lambda表达式的典型用法

Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );

函数接口

定义

为了更友好方便 使用Lambda表达式,JDK设计了函数式接口。

函数式接口就是一个具有一个方法的普通接口

函数式接口可以被隐式转换为lambda表达式。java.lang.Runnablejava.util.concurrent.Callable是函数式接口最典型的两个例子。在实际使用过程中,函数式接口是容易出错的:如有某个人在接口定义中增加了另一个方法,这时,这个接口就不再是函数式的了,并且编译过程也会失败。为了克服函数式接口的这种脆弱性并且能够明确声明接口作为函数式接口的意图,Java 8增加了一种特殊的注解@FunctionalInterface(Java 8中所有类库的已有接口都添加了@FunctionalInterface注解)

JDK中Callable的定义:

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

函数式接口测试

import org.junit.Test;

import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Predicate;

/**
 * Created by liubenlong on 2016/11/8.
 * 函数式接口测试
 */
public class FunctionInterfaceTest {

    @Test
    public void test1(){
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);

        //测试Predicate
        eval(list, abc -> true);
        eval(list, abc -> false);
        eval(list, a -> a % 2 == 0);

        //测试BiFunction
        BiFunction<Integer, Integer , Integer> biFunction  = (a, b) -> {return a * b;};
        System.out.println(biFunction.apply(10, 2));
    }

    public static void eval(List<Integer> list, Predicate<Integer> predicate){
        list.forEach(integer -> {if(predicate.test(integer)) System.out.println(integer);});
    }

}

其中BiFunctionPredicate都是函数式接口。读者可以自行运行,查看结果。

java.util.Function

java.util.Function包下都是函数式接口,我们可以直接拿来使用:

BiConsumer:表示接收两个输入参数和不返回结果的操作。
BiFunction:表示接受两个参数,并产生一个结果的函数。
BinaryOperator:表示在相同类型的两个操作数的操作,生产相同类型的操作数的结果。
BiPredicate:代表两个参数谓词(布尔值函数)。
BooleanSupplier:代表布尔值结果的提供者。
Consumer:表示接受一个输入参数和不返回结果的操作。
DoubleBinaryOperator:代表在两个double值操作数的运算,并产生一个double值结果。
DoubleConsumer:表示接受一个double值参数,不返回结果的操作。
DoubleFunction:表示接受double值参数,并产生一个结果的函数。
DoublePredicate:代表一个double值参数谓词(布尔值函数)。
DoubleSupplier:表示double值结果的提供者。
DoubleToIntFunction:表示接受double值参数,并产生一个int值结果的函数。
DoubleToLongFunction:代表接受一个double值参数,并产生一个long值结果的函数。
DoubleUnaryOperator:表示上产生一个double值结果的单个double值操作数的操作。
Function:表示接受一个参数,并产生一个结果的函数。
IntBinaryOperator:表示对两个int值操作数的运算,并产生一个int值结果。
IntConsumer:表示接受单个int值的参数并没有返回结果的操作。
IntFunction:表示接受一个int值参数,并产生一个结果的函数。
IntPredicate:表示一个整数值参数谓词(布尔值函数)。
IntSupplier:代表整型值的结果的提供者。
IntToDoubleFunction:表示接受一个int值参数,并产生一个double值结果的功能。
IntToLongFunction:表示接受一个int值参数,并产生一个long值结果的函数。
IntUnaryOperator:表示产生一个int值结果的单个int值操作数的运算。
LongBinaryOperator
表示在两个long值操作数的操作,并产生一个long值结果。
LongConsumer
表示接受一个long值参数和不返回结果的操作。
LongFunction
表示接受long值参数,并产生一个结果的函数。
LongPredicate
代表一个long值参数谓词(布尔值函数)。
LongSupplier
表示long值结果的提供者。
LongToDoubleFunction:表示接受double参数,并产生一个double值结果的函数。
LongToIntFunction:表示接受long值参数,并产生一个int值结果的函数。
LongUnaryOperator:表示上产生一个long值结果单一的long值操作数的操作。
ObjDoubleConsumer:表示接受对象值和double值参数,并且没有返回结果的操作。
ObjIntConsumer:表示接受对象值和整型值参数,并返回没有结果的操作。
ObjLongConsumer:表示接受对象的值和long值的说法,并没有返回结果的操作。
Predicate:代表一个参数谓词(布尔值函数)。
Supplier:表示一个提供者的结果。
ToDoubleBiFunction:表示接受两个参数,并产生一个double值结果的功能。
ToDoubleFunction:代表一个产生一个double值结果的功能。
ToIntBiFunction:表示接受两个参数,并产生一个int值结果的函数。
ToIntFunction:代表产生一个int值结果的功能。
ToLongBiFunction:表示接受两个参数,并产生long值结果的功能。
ToLongFunction:代表一个产生long值结果的功能。
UnaryOperator:表示上产生相同类型的操作数的结果的单个操作数的操作。

函数式接口里的默认方法和静态方法

先来看看JDK中Predicate类的声明(为了节省篇幅,我删掉了注释):

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

这是一个典型的函数式接口,里面有多个default方法,和一个static方法。

默认方法与抽象方法不同之处在于抽象方法必须要求实现,但是默认方法则没有这个要求。相反,每个接口都必须提供一个所谓的默认实现,这样所有的接口实现者将会默认继承它(如果有必要的话,可以覆盖这个默认实现)

在JVM中,默认方法的实现是非常高效的,并且通过字节码指令为方法调用提供了支持。默认方法允许继续使用现有的Java接口,而同时能够保障正常的编译过程。这方面好的例子是大量的方法被添加到java.util.Collection接口中去:stream(),parallelStream(),forEach(),removeIf(),……

在声明一个默认方法前,请仔细思考是不是真的有必要使用默认方法,因为默认方法会带给程序歧义,并且在复杂的继承体系中容易产生编译错误。更多详情请参考 官方文档

注意:
1. 静态方法不可以被集成。
2. 默认方法可以被继承
3. 函数式接口可以实现另一个函数式接口
4. 默认方法可以被重写
5. 默认方法重新声明,则变成了普通方法

下面来通过实例说明:
定义一个函数式接口:

@FunctionalInterface
public interface Hello {

    void sayHello();

    /**
     * default方法可以有多个
     */
    default void sayDefault(){
        System.out.println("我是default方法");
    }

    default void sayDefault11(){
        System.out.println("我是default11方法");
    }

    static void sayStatic(){
        System.out.println("我是static方法");
    }
}

Hello1继承了上面的Hello:

@FunctionalInterface
public interface Hello1 extends Hello {

}

测试类:

public class Hello1Impl implements Hello1{

    @Override
    public void sayHello() {
        System.out.println("interface_demo.HelloImpl.sayHello");
    }

    @Test
    public void test(){
        Hello1Impl impl = new Hello1Impl();
        impl.sayHello();
        impl.sayDefault();
        sayDefault();
        sayDefault11();
        Hello.sayStatic();

        //静态方法不可被继承哦
//        Hello1.sayStatic();

    }
}

输出结果:

interface_demo.HelloImpl.sayHello
我是default方法
我是default方法
我是default11方法
我是static方法

定义Hello2继承hello,重写默认方法:

@FunctionalInterface
public interface Hello2 extends Hello {

    @Override
    default void sayDefault() {
        System.out.println("我是重写的Hello的default方法。");
    }
}

测试类:

public class Hello2Impl implements Hello2{

    @Override
    public void sayHello() {
        System.out.println("interface_demo.Hello2Impl.sayHello");
    }

    @Test
    public void test(){
        Hello2Impl impl = new Hello2Impl();
        impl.sayHello();

        sayDefault11();
        sayDefault();
    }
}

结果:

interface_demo.Hello2Impl.sayHello
我是default11方法
我是重写的Hellodefault方法。

Hello3继承hello,但是重写了默认方法:

public interface Hello3 extends Hello {

    /**
     * 重新声明,成了普通抽象方法
     */
    @Override
    void sayDefault();
}

测试类:

public class Hello3Impl implements Hello3{

    @Override
    public void sayHello() {
        System.out.println("interface_demo.Hello3Impl.sayHello");
    }


/**
* 这里必须重写,因为sayDefault已经不是一个默认方法
*/
    @Override
    public void sayDefault() {        System.out.println("interface_demo.Hello3Impl.sayDefault");
    }

    @Test
    public void test(){
        sayDefault();
        sayHello();
        sayDefault11();
    }
}

输出结果:

interface_demo.Hello3Impl.sayDefault
interface_demo.Hello3Impl.sayHello
我是default11方法

方法引用

方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

  • 构造器引用
    语法是Class::new。请注意构造器没有参数。比如HashSet::new.下面详细实例中会有。
  • 静态方法引用
    语法是Class::static_method。这个方法接受一个Class类型的参数
  • 特定类的任意对象的方法引用
    语法是Class::method。这个方法没有参数
  • 特定对象的方法引用
    语法是instance::method。这个方法接受一个instance对应的Class类型的参数

实例:

import org.junit.Test;

import java.util.*;
import java.util.function.Supplier;

/**
 * Created by liubenlong on 2016/11/7.
 * 方法引用测试
 */
public class MethodReferencesTest {

    public void print(){
        System.out.println("sdfs");
    }

    @Test
    public void test1(){
        List<String> names = new ArrayList<String>();
        names.add("Mahesh");
        names.add("Suresh");
        names.add("Ramesh");
        names.add("Naresh");
        names.add("Kalpesh");

        names.forEach(System.out::println);
    }


    @Test
    public void test2(){
        String[] array = {"gjyg", "ads", "jk"};
        Arrays.sort(array, String::compareToIgnoreCase);

        for (String s : array) {
            System.out.println(s);
        }
    }

    class ComparisonProvider{
        public int compareByName(Person a,Person b){
            return a.getName().compareTo(b.getName());
        }

        public int compareByAge(Person a,Person b){
            return a.getAge() - b.getAge();
        }
    }


    @Test
    public void test3(){
        Person [] persons = initPerson();
        for (Person person : persons) person.printPerson();

        System.out.println("*******以下是lambda表达式写法");
        Arrays.sort(persons, (a, b) -> a.getAge() - b.getAge());
        for (Person person : persons) person.printPerson();

        System.out.println("*******以下是引用静态方法,比lambda表达式写法简单");
        Arrays.sort(persons, Person::compareByAge);
        for (Person person : persons) person.printPerson();

        System.out.println("*******以下是引用实例方法");
        ComparisonProvider provider = new ComparisonProvider();
        Arrays.sort(persons, provider::compareByAge);
        for (Person person : persons) person.printPerson();

        System.out.println("*******使用lambda表达式-引用的是构造方法");
        final List<Person> personList = Arrays.asList(persons);
        Set<Person> personSet = transferElements(personList,()-> new HashSet<>());
        personSet.forEach(Person::printPerson);


        System.out.println("*******使用方法引用-引用的是构造方法");
        Set<Person> personSet2 = transferElements(personList, HashSet::new);
        personSet2.forEach(Person::printPerson);
    }
    public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>> DEST transferElements(SOURCE sourceColletions, Supplier<DEST> colltionFactory) {
        DEST result = colltionFactory.get();
        sourceColletions.forEach(o -> result.add(o));
        return result;
    }

    private Person [] initPerson(){
        Person [] persons = new Person[3];
        Person person = new Person();
        person.setName("张三");
        person.setAge(10);
        persons[0] = person;

        person = new Person();
        person.setName("李四");
        person.setAge(50);
        persons[1] = person;

        person = new Person();
        person.setName("王五");
        person.setAge(2);
        persons[2] = person;
        return persons;
    }
}

比较简单,就不列出结果了,读者自行运行该程序。

重复注解

java8以前,注解在同一位置只能声明一次,不能声明多次。在java8中,允许同一个位置声明多次注解。

重复注解机制本身必须用@Repeatable注解。事实上,这并不是语言层面上的改变,更多的是编译器的技巧,底层的原理保持不变。让我们看一个快速入门的例子:

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() );
        }
    }
}

输出结果:

filter1
filter2

正如我们看到的,这里有个使用@Repeatable( Filters.class )注解的注解类Filter,Filters仅仅是Filter注解的数组,但Java编译器并不想让程序员意识到Filters的存在。这样,接口Filterable就拥有了两次Filter(并没有提到Filter)注解。

同时,反射相关的API提供了新的函数getAnnotationsByType()来返回重复注解的类型(请注意Filterable.class.getAnnotation( Filters.class )经编译器处理后将会返回Filters的实例)。

Optional

Optional的引入是为了解决臭名昭著的空指针异常问题.

Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测

 常用的几个方法:

  • ofNullable
    如果不为null, 则返回一个描述指定值的Optional;否则返回空的Optional
  • of
    如果不为null, 则返回一个描述指定值的Optional;否则报异常
  • isPresent
    如果有值存在,则返回TRUE,否则返回false。
  • orElse
    如果有值,则返回当前值;如果没有,则返回other
  • get
    如果有值,则返回当前值;否则返回NoSuchElementException

上面翻译自官方文档

测试代码如下:

import org.junit.Test;

import java.util.Optional;

/**
 * Created by liubenlong on 2016/11/8.
 *
 */
public class OptionalTest {

    @Test
    public void test1(){
        OptionalTest java8Tester = new OptionalTest();

        Integer value1 =  null;
        Integer value2 =  new Integer(10);
        //Optional.ofNullable - allows passed parameter to be null.
        Optional<Integer> a = Optional.ofNullable(value1);
        //Optional.of - throws NullPointerException if passed parameter is null
        Optional<Integer> b = Optional.of(value2);

        System.out.println(java8Tester.sum(a,b));
    }
    public Integer sum(Optional<Integer> a, Optional<Integer> b){
        //Optional.isPresent[判断值是否存在] - checks the value is present or not
        System.out.println("First parameter is present: " + a.isPresent());
        System.out.println("Second parameter is present: " + b.isPresent());

        //Optional.orElse - returns the value if present otherwise returns
        //the default value passed.
        Integer value1 = a.orElse(new Integer(0));

        //Optional.get - gets the value, value should be present
        Integer value2 = b.get();

        return value1 + value2;
    }
}

输出结果:

First parameter is present: false
Second parameter is present: true
10

Stream

Stream 是java8最重要的新特新之一,真正的函数式编程风格引入到Java中,它大大简化了我们的编码量,让程序员写出高效率、干净、简洁的代码。

由于stream过于强大,我们这里只将常用的做一下说明

了解大数据map-reduce的人对这个新特性会比较容易掌握,思想比较像。

几个重要的方法

  • stream 返回顺序流,集合作为其源。
  • parallelStream 返回并行数据流, 集合作为其源。
  • filter 方法用于消除基于标准元素
  • map 方法用于映射每个元素对应的结果
  • forEach 方法遍历该流中的每个元素
  • limit 方法用于减少流的大小
  • sorted 方法用来流排序
  • collect 方法是终端操作,这是通常出现在管道传输操作结束标记流的结束。

示例

  1. 把空指针过滤掉,返回前三个:
@Test
    public void test(){
        List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
        List<String> list = strings.stream().filter(n -> !"".equals(n)).limit(3).collect(Collectors.toList());
        list.forEach(n -> System.out.println(n));
    }

结果:

abc
bc
efg
  1. 计算集合每个元素的平方,并且去重,然后将值为81的去掉,输出排序后的数据
@Test
    public void test2(){
        List<Integer> ints = Arrays.asList(1,5,9,6,5,4,2,5,9);
        ints.stream().map(n->n*n).distinct().filter(n->n!=81).sorted().collect(Collectors.toList()).forEach(n->System.out.println(n));
    }

结果:

1
4
16
25
36
  1. 将上面的实例改为并行流,求和。
@Test
    public void test3(){
        List<Integer> ints = Arrays.asList(1,5,9,6,5,4,2,5,9);
        System.out.println(ints.parallelStream().filter(n->n>2).distinct().count());
        Integer sum = ints.parallelStream().map(n -> n * n).filter(n -> n != 81).reduce(Integer::sum).get();
        System.out.println(sum);
    }

输出结果:

4
132
  1. 统计,这里用到了mapToInt将其转换为可以进行统计的数值型。类似的还有mapToLongmapToDouble
@Test
    public void test5(){
        List<Integer> ints = Arrays.asList(1,5,9,6,5,4,2,5,9);
        IntSummaryStatistics statistics = ints.stream().mapToInt(n -> n).summaryStatistics();
        System.out.println(statistics.getAverage());
        System.out.println(statistics.getCount());
        System.out.println(statistics.getMax());
        System.out.println(statistics.getMin());
    }

结果:

5.111111111111111
9
9
1

Java8 日期/时间(Date/Time)

为什么我们需要新的Java日期/时间API

在开始研究Java 8日期/时间API之前,让我们先来看一下为什么我们需要这样一个新的API。在Java中,现有的与日期和时间相关的类存在诸多问题,其中有:

  • Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。
  • java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
  • 对于时间、时间戳、格式化以及解析,并没有一些明确定义的类。对于格式化和解析的需求,我们有java.text.DateFormat抽象类,但通常情况下,SimpleDateFormat类被用于此类需求。
  • 所有的日期类都是可变的,因此他们都不是线程安全的,这是Java日期类最大的问题之一。
  • 日期类并不提供国际化,没有时区支持,因此Java引入了- java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。

在现有的日期和日历类中定义的方法还存在一些其他的问题,但以上问题已经很清晰地表明:Java需要一个健壮的日期/时间类。这也是为什么- Joda Time在Java日期/时间需求中扮演了高质量替换的重要角色。

Java 8日期/时间API

Java 8日期/时间API是JSR-310的实现,它的实现目标是克服旧的日期时间实现中所有的缺陷,新的日期/时间API的一些设计原则是:

  • 不变性:新的日期/时间API中,所有的类都是不可变的,这对多线程环境有好处。
  • 关注点分离:新的API将人可读的日期时间和机器时间(unix timestamp)明确分离,它为日期(Date)、时间(Time)、日期时间(DateTime)、时间戳(unix timestamp)以及时区定义了不同的类。
  • 清晰:在所有的类中,方法都被明确定义用以完成相同的行为。举个例子,要拿到当前实例我们可以使用now()方法,在所有的类中都定义了format()和parse()方法,而不是像以前那样专门有一个独立的类。为了更好的处理问题,所有的类都使用了工厂模式和策略模式,一旦你使用了其中某个类的方法,与其他类协同工作并不困难。
  • 实用操作:所有新的日期/时间API类都实现了一系列方法用以完成通用的任务,如:加、减、格式化、解析、从日期/时间中提取单独部分,等等。
  • 可扩展性:新的日期/时间API是工作在ISO-8601日历系统上的,但我们也可以将其应用在非IOS的日历上。
    Java日期/时间API包

Java日期/时间API包含以下相应的包。

  • java.time包:这是新的Java日期/时间API的基础包,所有的主要基础类都是这个包的一部分,如:LocalDate, LocalTime, LocalDateTime, Instant, Period, Duration等等。所有这些类都是不可变的和线程安全的,在绝大多数情况下,这些类能够有效地处理一些公共的需求。
  • java.time.chrono包:这个包为非ISO的日历系统定义了一些泛化的API,我们可以扩展AbstractChronology类来创建自己的日历系统。
  • java.time.format包:这个包包含能够格式化和解析日期时间对象的类,在绝大多数情况下,我们不应该直接使用它们,因为java.time包中相应的类已经提供了格式化和解析的方法。
  • java.time.temporal包:这个包包含一些时态对象,我们可以用其找出关于日期/时间对象的某个特定日期或时间,比如说,可以找到某月的第一天或最后一天。你可以非常容易地认出这些方法,因为它们都具有“withXXX”的格式。
  • java.time.zone包:这个包包含支持不同时区以及相关规则的类

Clock

Clock类,它通过指定一个时区,然后就可以获取到当前的时刻,日期与时间。Clock可以替换System.currentTimeMillis()TimeZone.getDefault()

Clock clock = Clock.systemUTC();
System.out.println( clock.instant() );
System.out.println( clock.millis() );

输出:

2017-03-14T08:22:08.019Z
1489479728168

日期时间处理

LocalDateTime类包含了LocalDate和LocalTime的信息,但是不包含ISO-8601日历系统中的时区信息

@Test
    public void testLocalDate(){
        // Get the current date and time
        LocalDateTime currentTime = LocalDateTime.now();
        System.out.println("Current DateTime: " + currentTime);

        LocalDate date1 = currentTime.toLocalDate();
        System.out.println("date1: " + date1);
        Month month = currentTime.getMonth();
        int day = currentTime.getDayOfMonth();
        int seconds = currentTime.getSecond();
        System.out.println("Month: " + month
                +"  day: " + day
                +"  seconds: " + seconds
        );

        //指定时间
        LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
        System.out.println("date2: " + date2);

        //12 december 2014
        LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
        System.out.println("date3: " + date3);

        //22 hour 15 minutes
        LocalTime date4 = LocalTime.of(22, 15);
        System.out.println("date4: " + date4);

        //parse a string
        LocalTime date5 = LocalTime.parse("20:15:30");
        System.out.println("date5: " + date5);
    }

输出:

Current DateTime: 2017-03-14T16:23:08.997
date1: 2017-03-14
Month: MARCH  day: 14  seconds: 8
date2: 2012-03-10T16:23:08.997
date3: 2014-12-12
date4: 22:15
date5: 20:15:30

带时区日期时间处理–ZonedDateTime

@Test
    public void testZonedDateTime(){
        // Get the current date and time
        ZonedDateTime now = ZonedDateTime.now();
        System.out.println("now: " + now);
        ZonedDateTime date1 = ZonedDateTime.parse("2007-12-03T10:15:30+05:30[Asia/Karachi]");
        System.out.println("date1: " + date1);
        ZoneId id = ZoneId.of("Europe/Paris");
        System.out.println("ZoneId: " + id);
        ZoneId currentZone = ZoneId.systemDefault();
        System.out.println("CurrentZone: " + currentZone);
    }

输出:

now: 2017-03-15T09:33:10.108+08:00[Asia/Shanghai]
date1: 2007-12-03T10:15:30+05:00[Asia/Karachi]
ZoneId: Europe/Paris
CurrentZone: Asia/Shanghai

ChronoUnit

可以代替Calendar的日期操作

@Test
    public void testChromoUnits(){
        //Get the current date
        LocalDate today = LocalDate.now();
        System.out.println("Current date: " + today);
        //add 1 week to the current date
        LocalDate nextWeek = today.plus(1, ChronoUnit.WEEKS);
        System.out.println("Next week: " + nextWeek);
        //add 1 month to the current date
        LocalDate nextMonth = today.plus(1, ChronoUnit.MONTHS);
        System.out.println("Next month: " + nextMonth);
        //add 1 year to the current date
        LocalDate nextYear = today.plus(1, ChronoUnit.YEARS);
        System.out.println("Next year: " + nextYear);
        //add 10 years to the current date
        LocalDate nextDecade = today.plus(1, ChronoUnit.DECADES);
        //add 20 years to the current date
        LocalDate nextDecade20 = today.plus(2, ChronoUnit.DECADES);
        System.out.println("Date after 20 year: " + nextDecade20);
    }

输出:

Current date: 2017-03-15
Next week: 2017-03-22
Next month: 2017-04-15
Next year: 2018-03-15
Date after 20 year: 2037-03-15

Period/Duration

使用Java8,两个专门类引入来处理时间差。

  • Period - 处理有关基于时间的日期数量。
  • Duration - 处理有关基于时间的时间量。
@Test
    public void testPeriod(){
        //Get the current date
        LocalDate date1 = LocalDate.now();
        System.out.println("Current date: " + date1);

        //add 1 month to the current date
        LocalDate date2 = date1.plus(3, ChronoUnit.DAYS);
        System.out.println("Next month: " + date2);

        Period period = Period.between(date1, date2);
        System.out.println("Period: " + period);
        System.out.println("Period.getYears: " + period.getYears());
        System.out.println("Period.getMonths: " + period.getMonths());
        System.out.println("Period.getDays: " + period.getDays());
    }
    @Test
    public void testDuration(){
        LocalTime time1 = LocalTime.now();
        Duration twoHours = Duration.ofHours(2);

        System.out.println("twoHours.getSeconds(): " + twoHours.getSeconds());

        LocalTime time2 = time1.plus(twoHours);
        System.out.println("time2: " + time2);

        Duration duration = Duration.between(time1, time2);
        System.out.println("Duration: " + duration);
        System.out.println("Duration.getSeconds: " + duration.getSeconds());
    }

比较简单,读者自行运行查看结果

TemporalAdjuster

TemporalAdjuster 是做日期数学计算。例如,要获得“本月第二个星期六”或“下周二”。

@Test
    public void testAdjusters(){
        //Get the current date
        LocalDate date1 = LocalDate.now();
        System.out.println("Current date: " + date1);

        //get the next tuesday
        LocalDate nextTuesday = date1.with(TemporalAdjusters.next(DayOfWeek.TUESDAY));
        System.out.println("Next Tuesday on : " + nextTuesday);

//        获得当月的第二个周六
        LocalDate firstInMonth = LocalDate.of(date1.getYear(),date1.getMonth(), 1);
        LocalDate secondSaturday = firstInMonth.with(
                TemporalAdjusters.nextOrSame(DayOfWeek.SATURDAY)).with(
                TemporalAdjusters.next(DayOfWeek.SATURDAY));
        System.out.println("Second saturday on : " + secondSaturday);
    }

输出:

Current date: 2017-03-15
Next Tuesday on : 2017-03-21
Second saturday on : 2017-03-11

旧版本Date类的toInstant方法

toInstant()方法被添加到可用于将它们转换到新的日期时间的API原始日期和日历对象。使用ofInstant(Insant,ZoneId)方法得到一个LocalDateTime或ZonedDateTime对象。

@Test
    public void testBackwardCompatability(){
        //Get the current date
        Date currentDate = new Date();
        System.out.println("Current date: " + currentDate);

        //Get the instant of current date in terms of milliseconds
        Instant now = currentDate.toInstant();
        ZoneId currentZone = ZoneId.systemDefault();

        LocalDateTime localDateTime = LocalDateTime.ofInstant(now, currentZone);
        System.out.println("Local date: " + localDateTime);

        ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(now, currentZone);
        System.out.println("Zoned date: " + zonedDateTime);
    }

输出:

Current date: Wed Mar 15 09:51:17 CST 2017
Local date: 2017-03-15T09:51:17.745
Zoned date: 2017-03-15T09:51:17.745+08:00[Asia/Shanghai]

java.util.Date#from方法可以将Instant转化为旧版本的Date对象。这两个方法都是jdk8以后新加的。

DateTimeFormatter

DateTimeFormatter可以替代以前的dateformat,使用起来方便一些。

注意DateTimeFormatter是线程安全的,因为他是final的类

@Test
    public void testDateTimeFormatter(){
        LocalDateTime currentDate = LocalDateTime.now();
        System.out.println("Current date: " + currentDate);

        System.out.println(currentDate.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        System.out.println(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(currentDate));

        System.out.println(currentDate.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG)));
        System.out.println(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG).format(currentDate));

        System.out.println(currentDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        System.out.println(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(currentDate));
    }

输出:

Current date: 2017-03-15T11:22:25.026
2017-03-15T11:22:25.026
2017-03-15T11:22:25.026
2017年3月15日 上午11时22分25秒
2017年3月15日 上午11时22分25秒
2017-03-15 11:22:25
2017-03-15 11:22:25

Base64

在Java 8中,Base64编码已经成为Java类库的标准。它的使用十分简单

Java8,Base64终于得到了在Java整合。 Java8现在有内置编码器和解码器的Base64编码。在Java8中,我们可以使用三种类型的Base64编码。

  • 简单: 输出映射设置字符在A-ZA-Z0-9+/。编码器不添加任何换行输出和解码器拒绝在A-Za-z0-9+/以外的任何字符。
  • URL : 输出映射设置字符在A-Za-z0-9+_。输出URL和文件名安全。
  • MIME : 输出映射到MIME友好的格式。输出表示在每次不超过76个字符行和使用’\r’后跟一个换行符’\n’回车作为行分隔符。无行隔板的存在是为了使编码的结束输出。
@Test
    public void test(){
        try {
            byte[] bytes = "liubenlong?java8".getBytes();
            // Encode using basic encoder
            String base64encodedString = Base64.getEncoder().encodeToString(bytes);
            System.out.println("Base64 Encoded String (Basic) :" + base64encodedString);

            // Decode
            byte[] base64decodedBytes = Base64.getDecoder().decode(base64encodedString);
            System.out.println("Original String: "+new String(base64decodedBytes, "utf-8"));

            base64encodedString = Base64.getUrlEncoder().encodeToString(bytes);
            System.out.println("Base64 Encoded String (URL) :" + base64encodedString);

            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < 10; ++i) {
                stringBuilder.append(UUID.randomUUID().toString());
            }

            byte[] mimeBytes = stringBuilder.toString().getBytes("utf-8");
            String mimeEncodedString = Base64.getMimeEncoder().encodeToString(mimeBytes);
            System.out.println("Base64 Encoded String (MIME) :"+mimeEncodedString);
        }catch(UnsupportedEncodingException e){
            System.out.println("Error :"+e.getMessage());
        }
    }

结果:

Base64 Encoded String (Basic) :bGl1YmVubG9uZz9qYXZhOA==
Original String: liubenlong?java8
Base64 Encoded String (URL) :bGl1YmVubG9uZz9qYXZhOA==
Base64 Encoded String (MIME) :NzU0ZjJiYzgtYmJkYS00YTBlLWJjZmItODZlYTY2OTM1ZWM3YTQ5YWNjNDktZDNkYy00NjA4LTgw
NjctODIzYzgwN2U5N2ViZWZmMmYwMzgtMTEwYS00NGE3LWFlNmMtNGQyMDJlOTA5MTM0OTMxMWRh
ZmYtNmNjMC00MzI5LWI0MGYtMmI3ZWMzMzEzMGMxMzE5NTZhMjYtMDAyZi00ZmFhLWI2ZTEtNDY3
Y2E0ZDBiZjhiYzY3N2Y5NjQtMWRiYy00ZTJlLWFhOGMtYzdkNzlmYTM3MGQzYmJlYTk0ZmUtZWFl
My00MjliLTk4MjEtNmQwNmExNjI3NzNmYmU5Yzc5MWItZjAyNy00NjI1LTliYmEtMjhmNWQyZDk3
NWI2NTlkOGM5ZjAtYzYxNC00YTFmLWI2NmUtZjc0MWE3MzM5ODJiODU0ZmFlMmQtNjFiZi00NWJl
LTgzNjQtNDJiOGY3ZGJiNzNm

并行(parallel)数组

Java 8增加了大量的新方法来对数组进行并行处理。可以说,最重要的是parallelSort()方法,因为它可以在多核机器上极大提高数组排序的速度

实例:并行初始化一个20000的数组,并且填充1000000内的随机数,然后并行排序

import java.time.Duration;
import java.time.LocalTime;
import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;

public class ParallelArrays {
    public static void main( String[] args ) {
        LocalTime begin = LocalTime.now();
        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();
        System.out.println(Duration.between(begin, LocalTime.now()));
    }
}

结果:

256647 515472 636377 442743 107942 904354 253278 17119 131953 86974 
21 30 89 156 173 269 297 405 420 469 
PT0.05S

并发(Concurrency)

CAS原子类增强:LongAddr

LongAddr是高性能的原子计数器,比atomicLong性能还要高。
关于atomicLong请参照java高并发:CAS无锁原理及广泛应用
关于LongAddr的实现原理,简单来说就是解决了伪共享的问题。具体我会单独再写一篇文章讲解这个伪共享以及LongAddr的实现原理。

先忙将对多线程下普通加锁,atomicLong,atomicLong三种原子计数性能进行一下比较:

package LongAddr;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by liubenlong on 2017/1/22.
 * LongAddr: 比AtomicLong性能更高的原子类
 *
 * 本例比较加锁,AtomicLong,LongAddr  三种无锁
 */
public class LongAddrTest {
    static long maxValue = 10000000;
    static CountDownLatch countDownLatch = new CountDownLatch(10);
    public volatile long count = 0;


    public synchronized void incr(){
        if(getValue() < maxValue) count++;
    }

    public long getValue(){
        return count;
    }

    public static void main(String[] args) throws InterruptedException {

        LongAddrTest longAddrTest = new LongAddrTest();

        long begin = System.currentTimeMillis();
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        for(int i = 0 ; i < 10 ; i ++){
            executorService.execute(() -> {
                while(longAddrTest.getValue() < maxValue){
                    longAddrTest.incr();
                }
                countDownLatch.countDown();
            });
        }

        countDownLatch.await();
        executorService.shutdown();

        System.out.println("总共耗时:"+(System.currentTimeMillis() - begin)+"毫秒, count="+longAddrTest.getValue());
    }
}
package LongAddr;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Created by liubenlong on 2017/1/22.
 * LongAddr: 比AtomicLong性能更高的原子类
 *
 * 本例比较加锁,AtomicLong,LongAddr  三种无锁
 */
public class LongAddrTest1 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10);
        AtomicLong atomicLong = new AtomicLong(0);

        long maxValue = 10000000;
        long begin = System.currentTimeMillis();
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        for(int i = 0 ; i < 10 ; i ++){
            executorService.execute(() -> {
                while(atomicLong.get() < maxValue){
                    atomicLong.getAndIncrement();
                }
                countDownLatch.countDown();
            });
        }

        countDownLatch.await();
        executorService.shutdown();

        System.out.println("总共耗时:"+(System.currentTimeMillis() - begin)+"毫秒, count="+atomicLong.get());
    }
}
package LongAddr;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.LongAdder;

/**
 * Created by liubenlong on 2017/1/22.
 * LongAddr: 比AtomicLong性能更高的原子类
 *
 * 本例比较加锁,AtomicLong,LongAddr  三种无锁
 */
public class LongAddrTest2 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10);
        LongAdder longAdder = new LongAdder();

        long maxValue = 10000000;
        long begin = System.currentTimeMillis();
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        for(int i = 0 ; i < 10 ; i ++){
            executorService.execute(() -> {
                while(longAdder.sum() < maxValue){
                    longAdder.increment();
                }
                countDownLatch.countDown();
            });
        }

        countDownLatch.await();
        executorService.shutdown();

        System.out.println("总共耗时:"+(System.currentTimeMillis() - begin)+"毫秒, count="+longAdder.sum());
    }
}

上面三个类执行结果可以看出来,AtomicLong,LongAddr两个比加锁性能提高了很多,LongAddr又比AtomicLong性能高。所以我们以后在遇到线程安全的原子计数器的时候,首先考虑LongAddr。

JVM堆内存分布的优化

多开发者都在其系统中见过“java.lang.OutOfMemoryError: PermGen space”这一问题。这往往是由类加载器相关的内存泄漏以及新类加载器的创建导致的,通常出现于代码热部署以及代理使用较多的情形时。

JDK8 HotSpot JVM 将移除永久区,使用本地内存来存储类元数据信息并称之为:元空间(Metaspace)

在老的JVM中会有PermGen持久区,在java8中去掉了这个区域,取而代之的是将原本要放在持久区的类元数据信息、字节码信息及static final的常量,转移到了计算机本地内存中。这样理论上说就不会出现PermGen space的异常,而且不需要关心元空间的大小,它只受限于就算几内存。
这里写图片描述

但是不要高兴太早,好好想一想,真的不需要关心这部分数据量的大小吗?显然不可以。
因为依旧可能会出现元数据过多,依旧会有类卸载的需求。

java8中metaspace的改变

  • PermGen 空间移除,JVM的参数:PermSize 和 MaxPermSize 会被忽略并给出警告(如果在启用时设置了这两个参数)。
  • 大部分类元数据都在本地内存中分配
  • 新参数(MaxMetaspaceSize)用于限制本地内存分配给类元数据的大小。如果没有指定这个参数,元空间会在运行时根据需要动态调整。
  • Metaspace 垃圾回收: 对于僵死的类及类加载器的垃圾回收将在元数据使用达到“MaxMetaspaceSize”参数的设定值时进行。适时地监控和调整元空间对于减小垃圾回收频率和减少延时是很有必要的。持续的元空间垃圾回收说明,可能存在类、类加载器导致的内存泄漏或是大小设置不合适。

理论上说,现在你完全可以不关注这个元空间,因为JVM会在运行时自动调校为“合适的大小”;元空间提高Full GC的性能,在Full GC期间,Metadata到Metadata pointers之间不需要扫描了,别小看这几纳秒时间;元空间隐患就是如果程序存在内存泄露,像OOMTest那样,不停的扩展metaspace的空间,会导致机器的内存不足,所以还是要有必要的调试和监控。

CompletableFuture

Future是Java 5添加的类,用来描述一个异步计算的结果。你可以使用isDone方法检查计算是否完成,或者使用get阻塞住调用线程,直到计算完成返回结果,你也可以使用cancel方法停止任务的执行。

虽然Future以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式显然和我们的异步编程的初衷相违背,轮询的方式又会耗费无谓的CPU资源,而且也不能及时地得到计算结果,为什么不能用观察者设计模式当计算结果完成及时通知监听者呢?

在Java 8中, 新增加了一个包含50个方法左右的类: CompletableFuture,提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了转换和组合CompletableFuture的方法。

这里只是简单讲解他的使用,可以用于入门。

先定义两个静态的方法供后续使用:

public static Integer square(Integer a){
        try {//模拟耗时任务
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return a * a;
    }

    public static Integer division(Integer a, Integer b){
        return a / b;
    }

简单实例

CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->square(10));

System.out.println("任务已提交完成,等待结果。");

Integer integer = completableFuture.get();
System.out.println(integer);

可以看到结果会先输出“任务已提交完成,等待结果。”。等待几秒钟才输出结果100.

通过get方法,可以获取到线程的执行结果。如果还没有执行完,则阻塞。
默认是在ForkJoinPool.commonPool()线程池中执行任务,该线程池中的线程都是daemon的,所以必须调用get()方法等待返回结果。

流式处理

计算平方,然后转为字符串并且拼接上”!!”:

completableFuture
        .supplyAsync(()->square(20))
        .thenApply(a -> a + "")
        .thenApply(str -> str + "!!")
        .thenAccept(System.out::println)
        .get();

输出结果为:400!!

异常处理

下面这段代码会爆出除数为0异常,通过exceptionally捕获异常进行处理。

completableFuture
        .supplyAsync(() -> division(10, 0))
        .exceptionally((e) -> {//捕获异常,处理完成后返回了0
            e.printStackTrace();
            return 0;
        })
        .thenAccept(System.out::println)
        .get();

输出结果:

java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
    at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
    at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
    at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1592)
    at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1582)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Caused by: java.lang.ArithmeticException: / by zero
    at future.CompletableFutureTest.division(CompletableFutureTest.java:23)
    at future.CompletableFutureTest.lambda$main$4(CompletableFutureTest.java:49)
    at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590)
    ... 5 more
0

多个CompletableFuture组合:thenCombine

//多个CompletableFuture组合使用 方式一
        CompletableFuture.supplyAsync(()->square(10))
                .thenCompose(returnVal -> CompletableFuture.supplyAsync(()->division(200, returnVal)))
        .thenAccept(System.out::println).get();



        //多个CompletableFuture组合使用 方式二
        CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(()->square(10));
        CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(()->division(200, 10));
        completableFuture1.thenCombine(completableFuture2, (i ,j) -> i - j)
                .thenAccept(System.out::println).get();

输出结果:

2
80

参考资料

阅读更多
换一批

没有更多推荐了,返回首页