1 Star 1 Fork 0

kemp/java8-tutorial

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

Java 8 学习手册

java8越来越普及,里面提供了很多新的API,大大提高了我们日常的编码效率,本文对java8的一些新特性做了简单归纳,并附有代码示例

代码示例:https://github.com/aalansehaiyang/java8-tutorial

目录

接口使用default方法

Java 8 允许我们在接口类中使用非结构化方法比如default修饰词,来实现接口。

这一特性在 virtual extension methods有详细介绍。

示例:

interface Formula {
    double calculate(int a);

    default double sqrt(int a) {
        return Math.sqrt(a);
    }
}

Formula 接口类中除了定义抽象方法 calculate,还定义default方法sqrt。实现类必须实现抽象方法calculate。default修饰的方法sqrt在类外同样可以使用。

Formula formula = new Formula() {
    @Override
    public double calculate(int a) {
        return sqrt(a * 100);
    }
};

formula.calculate(100);     // 100.0
formula.sqrt(16);           // 4.0

公式采用匿名实现类。代码非常冗余,需要6行代码来实现一个简单的计算 sqrt(a * 100)。下一节中,用Java 8 能更加巧妙的实现一个方法。

Lambda表达式

一个简单例子,对字符串集合排序

List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return b.compareTo(a);
    }
});

静态方法 Collections.sort 接收list集合和一个comparator比较器从而实现排序功能。通常你需要创建一个匿名比较器,并传递给排序方法。

为了替换匿名类实例,Java 8 引入一种非常简洁的语法, lambda 表达式:

Collections.sort(names, (String a, String b) -> {
    return b.compareTo(a);
});

当然你也可以采用更短更易读的写法,如上。

Collections.sort(names, (String a, String b) -> b.compareTo(a));

可以进一步精简,只剩一行代码,省略{}return方法,如上。

names.sort((a, b) -> b.compareTo(a));

List类现在提供sort方法。同时java编译器能自动识别参数类型,所以编码时你可以忽略它们。接下来让我们深入学习lambda表达式如何广泛使用。

Functional接口

lambda表达式是如何识别Java的系统类型?每个lambda对应一个由接口指定的类型。因此每一个_functional 接口_ 的定义必须包含一个抽象方法声明。每个lambda表达式的类型需要与抽象方法匹配。由于默认方法不是抽象的,你需要将默认方法添加到函数接口。

我们可以任意定义一个接口作为lambda表达式,其内部需要包含一个抽象方法。 为了确保接口满足规范,你需要添加 @FunctionalInterface 注解。一旦你试图添加第二个抽象方法,编译器会自动检测并抛出一个编译错误。

示例:

@FunctionalInterface
interface Converter<F, T> {
    T convert(F from);
}
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted);    // 123

代码:com.winterbe.java8.samples.lambda.Lambda2

记住,如果省略@FunctionalInterface,代码也是有效的。

Method and Constructor 引用

上面的代码示例可以采用静态方法引用进一步简化:

Converter<String, Integer> converter = Integer::valueOf;
Integer converted = converter.convert("123");
System.out.println(converted);   // 123

Java 8允许你通过方法或构造器的引用,如 :: 。上面示例演示了引用一个静态方法。另外我们也可以用类实例对象的方法。

class Something {
    String startsWith(String s) {
        return String.valueOf(s.charAt(0));
    }
}
Something something = new Something();
Converter<String, String> converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted);    // "J"

让我们了解下 :: 字键字如何用在构造器中。首先定义一个类如下结构:

class Person {
    String firstName;
    String lastName;

    Person() {}

    Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

接下来,我们定义一个person工厂接口,用于创建新的persons:

interface PersonFactory<P extends Person> {
    P create(String firstName, String lastName);
}

与传统的实现方式不同,我们通过调用构造器方法来实现:

PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");

我们通过 Person::new来触发Person的构造器函数。Java编译器能自动选择合适的构造器函数来匹配PersonFactory.create

代码:com.winterbe.java8.samples.lambda.Lambda2

Lambda 范围

lambda表达式访问外部变量同匿名对象相似。你可以访问final修饰的本地局部变量。

局部变量

我们可以读取final修饰的本地变量

final int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);

stringConverter.convert(2);     // 3

与匿名对象不同,变量num不必强制一定用final修饰。下面写法也是有效的:

int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);

stringConverter.convert(2);     // 3

num在代码编译时必须是隐式的final类型。下面的写法编译会报错:

int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);
num = 3;

访问全局变量或静态变量

与局部变量相反,我们可以在lambda表达式中读或写全局变量或静态全局变量。

代码:com.winterbe.java8.samples.lambda.Lambda4
class Lambda4 {
    static int outerStaticNum;
    int outerNum;
	
	// 方法
    void testScopes() {
        Converter<Integer, String> stringConverter1 = (from) -> {
            outerNum = 23;
            return String.valueOf(from);
        };

        Converter<Integer, String> stringConverter2 = (from) -> {
            outerStaticNum = 72;
            return String.valueOf(from);
        };
    }
}

default方法

还记得第一节的示例公式吗?Formula接口类中定义一个默认方法 sqrt ,它可以被每一个formula实例对象包括匿名对象访问。但这并适用于lambda表达式。

lambda表达式语句不能直接访问default方法。下面写法编译不过:

Formula formula = (a) -> sqrt(a * 100);

方法引用

方法引用和 lambda 表达式拥有相同的特性(例如,它们都需要一个目标类型,并需要被转化为函数式接口的实例),不过我们并不需要为方法引用提供方法体,我们可以直接通过方法名称引用已有方法。

方法引用分三种情况:

  • 1)类+动态方法
  • 2)类+静态方法
  • 3)类实例对象+动态方法
  • 4)类实例对象+静态方法(无效,不正确写法)
    public static void main(String[] args) {

        // 1)类+动态方法
        BiConsumer<LinkedHashSet, Object> biConsumer1 = LinkedHashSet::add;
        LinkedHashSet s1 = new LinkedHashSet();
        biConsumer1.accept(s1, "aaa");
        System.out.println(s1);

        // 2)类+静态方法
        BiConsumer<String, Long> biConsumer2 = Utils::concatStatic;
        biConsumer2.accept("first_param", 6L);

        // 3)类实例对象+动态方法
        BiConsumer<String, Long> biConsumer3 = new Utils()::concat;
        biConsumer3.accept("first_param", 7L);

        // 4)类实例对象+静态方法
        // Error:(35, 48) java: 方法引用无效 ,静态限制范围方法引用
        // BiConsumer<String, Long> biConsumer4 = new Utils()::concatStatic;
        // biConsumer4.accept("first_param", 8L);

    }

接收 int 参数的数组构造方法

IntFunction<int[]> arrayMaker = int[]::new;
int[] array = arrayMaker.apply(10) // 创建数组 int[10]

内置 Functional Interfaces

JDK 1.8 API提供了许多内置的功能接口。其中一些来自Java旧版本,如ComparatorRunnable。通过@FunctionalInterface注解扩展那些已存在的接口以实现lambda支持。

同时Java 8 API还提供一些新的functional interfaces满足多场景需求。一些新特性出自Google Guava 三方库。

判断 Predicates

Predicates内部定义了一个boolean类型判断方法,带有一个入参。这个接口还包含很多default方法满足各种复杂的逻辑表达式,如(与、或、非)

代码:com.winterbe.java8.samples.lambda.Lambda3

Predicate<String> predicate = (s) -> s.length() > 0;

predicate.test("foo");              // true
predicate.negate().test("foo");     // false

Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;

Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();

函数 Functions

函数接收一个入参并返回一个结果。default方法被用于将多个功能函数链接在一起,(compose 之前执行、andThen 之后执行)

代码:com.winterbe.java8.samples.lambda.Lambda3

Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);

backToString.apply("123");     // "123"

生产 Suppliers

Suppliers 生产指定类型的结果。不同于Functions,Suppliers 不接受任何参数。

代码:com.winterbe.java8.samples.lambda.Lambda3

Supplier<Person> personSupplier = Person::new;
personSupplier.get();   // new Person

案例:java8引入了一个对log方法的重载版本,这个版本的log方法接受一个Supplier作为参数。这个替代版本的log方法的函数签名如下:

public void log(Level level, Supplier<String> msgSupplier)
你可以通过下面的方式对它进行调用:

logger.log(Level.FINER, () -> "Problem: " + generateDiagnostic());
如果日志器的级别设置恰当, log 方法会在内部才执行作为参数传递进来的Lambda表达式(注意:常规写法,先执行所有的入参方法,得到实参,再执行方法)。惰性求值,可以有效避免一些不必要的性能开销。

这里介绍的 Log 方法的内部实现如下:
public void log(Level level, Supplier<String> msgSupplier){
    if(logger.isLoggable(level)){
        log(level, msgSupplier.get());
    }
}

https://my.oschina.net/bairrfhoinn/blog/142985

消费 Consumers

Consumers 表示对单个输入参数加工处理,并提供 andThen 'default'方法进行后续处理。

代码:com.winterbe.java8.samples.lambda.Lambda3

Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));

比较 Comparators

Comparators 在Java老版本就已经存在。Java 8增加了各种各样的default方法

代码:com.winterbe.java8.samples.lambda.Lambda3

Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);

Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");

comparator.compare(p1, p2);             // > 0
comparator.reversed().compare(p1, p2);  // < 0

Comparator与reversed组合使用,支持多字段的排序,默认由小到大,或由大到小

代码:com.winterbe.java8.samples.functional.Comparator1

List<Score> list = new ArrayList<>();
list.add(new Score("xiaohong", 90L, 91L));
list.add(new Score("xiaoming", 85L, 90L));
list.add(new Score("wanggang", 90L, 96L));
list.add(new Score("xiaoma", 85L, 70L));

// 先按语文由小到大,如果相等,按数学由小到大
Collections.sort(list, Comparator.comparing(Score::getYuwen).thenComparing(Score::getShuxue));
System.out.println("先按语文由小到大,如果相等,按数学由小到大");
list.forEach(System.out::println);

// 先按语文由大到小,如果相等,按数学由大到小
Comparator c1 = Comparator.comparing(Score::getYuwen).reversed();
Comparator c2 = Comparator.comparing(Score::getShuxue).reversed();
Collections.sort(list, c1.thenComparing(c2));
System.out.println("先按语文由大到小,如果相等,按数学由大到小");
list.forEach(System.out::println);

其它的函数接口

象BinaryOperator、等

代码位置:com.winterbe.java8.samples.functional

可选择 Optionals

Optionals 不是一个函数式接口,主要是为了防止NullPointerException。下一节会重点介绍,现在让我们先了解下Optionals是如何工作的。

Optional是一个用于存放 null 或非null值的简易容器。试想,一个带返回值的方法有时会返回空。相反,Java 8 返回的是Optional ,而非 null

代码:com.winterbe.java8.samples.stream.Optional1

Optional<String> optional = Optional.of("bam");

optional.isPresent();           // true
optional.get();                 // "bam"
optional.orElse("fallback");    // "bam"

optional.ifPresent((s) -> System.out.println(s.charAt(0)));     // "b"

流 Streams

java.util.Stream 可以对元素列表进行一次或多次操作。Stream操作可以是中间值也可以是最终结果。 最后的操作返回的是某种类型结果,而中间操作返回的是stream本身。因此你可以在一行代码链接多个方法调用。Streams被创建于java.util.Collection ,比如 list or set (map 并不支持)。Stream可以顺序执行,也可以并行执行。

Streams 非常强大, 因此我单独写了一篇文章介绍 Java 8 Streams Tutorial。代码库 Sequency

  • 中间操作:filter、map、mapToInt、mapToLong、mapToDouble、flatMap、sorted、distinct、limit、skip、of、iterate
  • 终止操作:forEach、count、collect、reduce、toArray、anyMatch、allMatch、noneMatch、findAny、findFirst、max、min
  • 原始类型特化流:IntStream、LongStream、DoubleStream

过滤 Filter

Filter通过predicate判断函数来过滤所有的元素。这个操作是中间操作,需要通过终止操作才会触发执行。

代码:com.winterbe.java8.samples.stream.Stream_filter
stringCollection
    .stream()
    .filter((s) -> s.startsWith("a"))
    .forEach(System.out::println);

// "aaa2", "aaa1"

映射 Map

map是一种中间过程操作,借助函数表达式将元素转换成另一种形式。下面的例子将每个字符串转换成大写的字符串。但你也可以使用map将每个对象转换为另一种类型。最终输出的结果类型依赖于你传入的函数表达式。

stringCollection
    .stream()
    .map(String::toUpperCase)
    .sorted((a, b) -> b.compareTo(a))  //由大到小
    .forEach(System.out::println);

// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"

映射 flatMap

如果涉及到一对多映射,需要将映射结果放入Stream中。使用flatMap方法的效果是,转换后的多个结果并不是分别映射成一个流,而是映射成流的内容。

代码:com.winterbe.java8.samples.stream.Stream_flatMap

List<String> lists = Arrays.asList("Hello", "World");
lists.stream().flatMap(word-> Stream.of(word.split("")))
        .distinct()
        .forEach(System.out::println);

排序 Sorted

Sorted是一个中间态操作,它返回流的有序视图。 除非你传递自定义的Comparator,否则元素按默认的由小到大排序。

代码:com.winterbe.java8.samples.stream.Stream_sorted

//默认排序
stringCollection.stream().sorted().forEach(System.out::println);
System.out.println(stringCollection);
特别注意:`sorted`只是创建流的排序视图,并没有改变原始集合的顺序。所以说`stringCollection`的顺序并没有改变。
//自定义排序,按字符串,由大到小
stringCollection
        .stream()
        .map(String::toUpperCase)
        .sorted((a, b) -> b.compareTo(a))
        .forEach(System.out::println);

归约 Reduce

终止型操作,通过给定的函数表达式来处理流中的前后两个元素,或者中间结果与下一个元素。Lambda 反复结合每一个元素,直到流被归约成一个值。例如求和或查找最大元素。

代码:com.winterbe.java8.samples.stream.Stream_reduce

// 将流数据列表拆分多批,sum初始为0,每批都执行 (sum, p) -> sum = sum + p.age,得到局部的sum总和。并行计算思想
// 最后通过 (sum1, sum2) -> sum1 + sum2 ,计算最终的总和
// (sum1, sum2) -> sum1 + sum2,主要适用于并行,parallelStream(),单线程是无效的。

private static void test3(List<Person> persons) {
    Integer ageSum = persons.parallelStream().reduce(0, (sum, p) -> sum += p.age, (sum1, sum2) -> sum1 + sum2);
    System.out.println(ageSum);
}

更多reduce用法可参考:https://blog.csdn.net/io_field/article/details/54971679

计数 Count

Count是一个终止型操作,返回一个long类型的元素列表总数。

代码:com.winterbe.java8.samples.stream.Stream_count
long startsWithB =
    stringCollection
        .stream()
        .filter((s) -> s.startsWith("b"))
        .count();
        
System.out.println(startsWithB);    // 3

匹配 Match

各种匹配操作用于判断是否满足stream条件。所有的操作都完成后,返回一个boolean类型结果。

代码:com.winterbe.java8.samples.stream.Stream_match
List<String> stringCollection = new ArrayList<>();
stringCollection.add("ddd2");
stringCollection.add("aaa2");
stringCollection.add("bbb1");
stringCollection.add("aaa1");
stringCollection.add("bbb3");
stringCollection.add("ccc");
stringCollection.add("bbb2");
stringCollection.add("ddd1");

// 只需要一个条件满足
boolean anyStartsWithA = stringCollection.stream().anyMatch((s) -> s.startsWith("a"));
System.out.println("anyMatch:" + anyStartsWithA); // true

// 所有条件都要满足
boolean allStartsWithA = stringCollection.stream().allMatch((s) -> s.startsWith("a"));
System.out.println("allMatch:" + allStartsWithA); // false

// 所有的条件都要不满足
boolean noneStartsWithZ = stringCollection.stream().noneMatch((s) -> s.startsWith("z"));
System.out.println("noneMatch:" + noneStartsWithZ); // true

// 返回任意一个元素
Optional<String> anyE = stringCollection.stream().findAny();
System.out.println("findAny:" + anyE.get());

//返回第一个元素
Optional<String> firstE = stringCollection.stream().findFirst();
System.out.println("findFirst:" + firstE.get());

跳过 skip

返回一个扔掉前n个元素的流

代码:com.winterbe.java8.samples.stream.Stream_skip
// 扔掉前三个元素
stringCollection
    .stream()
    .skip(3)
    .forEach(System.out::println);

输出 limit

只取前N个结果

代码:com.winterbe.java8.samples.stream.Stream_limit
// 取前三个元素
stringCollection
    .stream()
    .limit(3)
    .forEach(System.out::println);

输出 collect

接受各种做法作为参数,将流中的元素累积成一个汇总结果

常见例子:

  • 对一个交易列表按货币分组,获得该货币的所有交易额总和(返回一个Map<Currency,Integer>)
  • 将交易列表分成两组,贵的和不贵的(返回一个Map<Boolean,List<Transaction>>)
  • 创建多级分组,比如按城市对交易分组,然后进一步按照贵的或不贵分组

Collectors常见方法:

  • Collectors.toList,得到List列表
  • Collectors.toSet,得到Set集合
  • Collectors.joining ,通过连接符拼接字符串
  • Collectors.groupingBy(Function<? super T,? extends K>) ,按K值分组,返回Map<K,List>
  • Collectors.groupingBy(Function<? super T,? extends K>, Collector<? super T,A,D>),二级分组,得到两级Map
  • Collectors.partitioningBy(Predicate<? super T> predicate) ,分区是分组的特殊情况,返回一个布尔值,意味着得到的分组Map的key只能是Boolean,于是它最多可以分为两组
  • Collectors.maxBy,求最大值,需要传一个自定义的Comparator
  • Collectors.reducing,广义的归约汇总。
  • Collectors.toMap ,得到Map集合。注意:如果key重复会抛异常,要特殊处理
代码:com.winterbe.java8.samples.stream.Stream_collect

// 将字符串换成大写,并用逗号连接起来
List<String> citys = Arrays.asList("USA", "Japan", "France");
String cityS = citys.stream().map(x -> x.toUpperCase()).collect(Collectors.joining(", "));
        
// 按性别分组
Map<String, List<Student>> maps = studentList.stream().collect(Collectors.groupingBy(Student::getSex));

// 先按性别分组,然后再按年龄段分组
Map<String, Map<String, List<Student>>> maps = studentList.stream()
   .collect(Collectors.groupingBy(Student::getSex,
      Collectors.groupingBy(s -> {
          if (s.getAge() < 20) {
              return "低age";
          } else {
              return "高age";
          }
      })));

// 按年龄25分成两个组
Map<Boolean, List<Student>> maps = studentList.stream().collect(Collectors.partitioningBy(s -> {
    if (s.getAge() < 25) {
        return true;
    } else {
        return false;
    }
}));

// 找出年龄最大的人
Optional<Student> optional1 = studentList.stream().collect(Collectors.maxBy(Comparator.comparing(Student::getAge)));
optional1.ifPresent(System.out::println);

// 年龄总和
// reducing的参数,第一个:初始值。第二个:转换函数。第三个:累积函数
int sum = studentList.stream().collect(Collectors.reducing(0, Student::getAge, Integer::sum));

// 转换成一个Map<sex,name>,注意:如果key重复会抛异常
// 如果key重复,用后一个的value值覆盖前一个
private static void test8(List<Student> studentList) {
    Map<String, String> sexNameMap = studentList.stream().collect(Collectors.toMap(p -> {
        return p.getSex();
    }, p2 -> {
        return p2.getName();
    }, (oldValue, newValue) -> newValue));
    System.out.println(sexNameMap);
}

并行 Streams

如下所述,流可以是串行执行,也可以并行执行。对于流的串行执行是单个线程完成。而并行流处理则是在多个线程上同时执行。

下面这个例子将会演示如何通过并行流处理来显著提升性能。

首先我们创建一个大容量的List元素集合:

代码:com.winterbe.java8.samples.stream.Stream_reduce

int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
    UUID uuid = UUID.randomUUID();
    values.add(uuid.toString());
}

现在我们测量对此集合的流排序所花费的时间。

串行 Sort

long t0 = System.nanoTime();

long count = values.stream().sorted().count();
System.out.println(count);

long t1 = System.nanoTime();

long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("sequential sort took: %d ms", millis));

// sequential sort took: 899 ms

代码:com.winterbe.java8.samples.stream.Streams3

并行 Sort

long t0 = System.nanoTime();

long count = values.parallelStream().sorted().count();
System.out.println(count);

long t1 = System.nanoTime();

long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("parallel sort took: %d ms", millis));

// parallel sort took: 472 ms

代码:com.winterbe.java8.samples.stream.Streams3

正如你所看到的那样,两块代码片段几乎是相同的。但并行排序大约能快50%。所以你要做的是将stream()换成parallelStream()

集合 Maps

如前所述,Maps不直接支持streams。Map接口并不提供 stream()相关方法。然而你能借助于map.keySet().stream(), map.values().stream()map.entrySet().stream()创建基于keys,values或entries的流。

此外maps还提供一些新的有用的方法来支持常规任务。

代码:com.winterbe.java8.samples.misc.Maps1

Map<Integer, String> map = new HashMap<>();

for (int i = 0; i < 10; i++) {
    map.putIfAbsent(i, "val" + i);
}

map.forEach((id, val) -> System.out.println(val));

putIfAbsent如果为空,执行put,否则返回key对应的value值,这样可以避免一次空判断代码冗余。forEach内部通过BiConsumer来操作。

下面的例子讲述了,map如何利用functions函数来计算:

map.computeIfPresent(3, (num, val) -> val + num);
map.get(3);             // val33

map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9);     // false

map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23);    // true

map.computeIfAbsent(3, num -> "bam");
map.get(3);             // val33(缺失才执行)

下面,我们学习如何通过给定key来删除entries,前提它当前存在kv映射:


// 3对应的value等于“val3”,才执行删除动作
map.remove(3, "val3");
map.get(3);             // val33

map.remove(3, "val33");
map.get(3);             // null

其它有用的方法:

// 返回key关联的值,否则返回后面的默认值
map.getOrDefault(42, "not found");  // not found

map中的entries合并也是非常容易的:

map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
map.get(9);             // val9

map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
map.get(9);             // val9concat

如果key-value键值对不存在,则合并到map中。否则执行function函数来改变其value值。

日期 API

Java 8提供了全新的日期和时间API,位于 java.time包下面。全新的日期API与Joda-Time 库相当,但它也不太一样。下面的例子涵盖了此新API的最重要部分。

时钟

时钟提供了对当前日期和时间的访问。时钟知晓时区,可以用来代替System.currentTimeMillis()来检索自Unix EPOCH以来的当前时间(以毫秒为单位)。在时间轴上的某一时刻用Instant表示。Instant可以创建遗留的java.util.Date 对象。

Clock provides access to the current date and time. Clocks are aware of a timezone and may be used instead of System.currentTimeMillis() to retrieve the current time in milliseconds since Unix EPOCH. Such an instantaneous point on the time-line is also represented by the class Instant. Instants can be used to create legacy java.util.Date objects.

Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();

Instant instant = clock.instant();
Date legacyDate = Date.from(instant);   // legacy java.util.Date

代码:com.winterbe.java8.samples.time.LocalTime1

时区

时区是通过 ZoneId来表示,它提供了很多静态方法。时区定义了在瞬间和本地日期和时间之间转换的重要偏移。

System.out.println(ZoneId.getAvailableZoneIds());
// prints all available timezone ids

ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
System.out.println(zone1.getRules());
System.out.println(zone2.getRules());

// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]

代码:com.winterbe.java8.samples.time.LocalTime1

本地时间

LocalTime表示没有时区的时间,如晚上10点 或者 17:30:15。下文例子创建了两个带时区的本地时间。我们比较这两个时间,并计算两者之间的小时或分钟差值。

LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);

System.out.println(now1.isBefore(now2));  // false

long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);

System.out.println(hoursBetween);       // -3
System.out.println(minutesBetween);     // -239

LocalTime 提供了很多工厂方法用于创建各种新实例,包括解析时间字符串。

LocalTime late = LocalTime.of(23, 59, 59);
System.out.println(late);       // 23:59:59

DateTimeFormatter germanFormatter =
    DateTimeFormatter
        .ofLocalizedTime(FormatStyle.SHORT)
        .withLocale(Locale.GERMAN);

LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);
System.out.println(leetTime);   // 13:37

代码:com.winterbe.java8.samples.time.LocalTime1

本地日期

LocalDate表示不同的日期,如 2014-03-11。它是不可变的并且与LocalTime完全相似。示例演示了加或减天,月,年来计算新日期。请记住,每次操作都会返回一个新实例。

LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
LocalDate yesterday = tomorrow.minusDays(2);

LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek);    // FRIDAY

从字符串解析LocalDate就像解析LocalTime一样简单:

DateTimeFormatter germanFormatter =
    DateTimeFormatter
        .ofLocalizedDate(FormatStyle.MEDIUM)
        .withLocale(Locale.GERMAN);

LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas);   // 2014-12-24

代码:com.winterbe.java8.samples.time.LocalDate1

本地日期时间

LocalDateTime表示日期时间。它包含了上面的日期和时间组成一个实例。LocalDateTime是不可变的,它的工作方式类似于LocalTime 和 LocalDate。我们可以利用日期时间检索某些字段的方法:

代码:com.winterbe.java8.samples.time.LocalDateTime1

LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);

DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek);      // WEDNESDAY

Month month = sylvester.getMonth();
System.out.println(month);          // DECEMBER

long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay);    // 1439

通过时区的附加信息,它可以转换为时刻。轻松地将实例转换为java.util.Date类型的旧日期。

Instant instant = sylvester
        .atZone(ZoneId.systemDefault())
        .toInstant();

Date legacyDate = Date.from(instant);
System.out.println(legacyDate);     // Wed Dec 31 23:59:59 CET 2014

格式化日期-时间就象格式化日期或者时间一样。我们可以使用自定义模式创建格式化程序,而不是使用预定义的格式。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM dd, yyyy - HH:mm");
        LocalDateTime parsed = LocalDateTime.parse("07 25, 2017 - 14:00", formatter);
        System.out.println(parsed); // 2017-07-25T14:00

java.text.NumberFormat 不同, DateTimeFormatter 是不可改变的, 线程安全.

更多语法详情, 参考这里.

注解

Java 8中的注解是可重复的。让我们通过一个例子说明。

首先,我们定义一个包装器注解,它包含实际注解的数组:

代码:com.winterbe.java8.samples.misc.Annotations1

@interface Hints {
    Hint[] value();
}

@Repeatable(Hints.class)
@interface Hint {
    String value();
}

Java 8允许我们通过声明注解 @Repeatable来使用相同类型的多个注释。

形式 1: 使用容器注解 (旧方式)

@Hints({@Hint("hint1"), @Hint("hint2")})
class Person {}

形式 2: 使用可重复注解 (新方式)

@Hint("hint1")
@Hint("hint2")
class Person {}

形式2,java编译器隐式的设置@Hints 注解。这对于经由反射读取注解信息非常重要。

Hint hint = Person.class.getAnnotation(Hint.class);
System.out.println(hint);                   // null

Hints hints1 = Person.class.getAnnotation(Hints.class);
System.out.println(hints1.value().length);  // 2

Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class);
System.out.println(hints2.length);          // 2

虽然我们从未在 Person 类上定义@Hints注解,但它仍然可以通过 getAnnotation(Hints.class)读取。然而,更方便的是 getAnnotationsByType能访问所有带@Hint的注解。

此外,Java 8 注解扩展了两个新的target:

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}

写在最后

收到一些热衷于技术的同学反馈,考虑github实时通知不是特别好,以后时间里,一些技术经验总结、心得体会,会不定期同步一份到微信公众号上,欢迎关注,多多交流,共同进步

image

The MIT License (MIT) Copyright (c) 2017 Benjamin Winterberg Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

暂无描述 展开 收起
README
MIT
取消

发行版

暂无发行版

贡献者

全部

近期动态

不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Java
1
https://gitee.com/rocling/java8-tutorial.git
git@gitee.com:rocling/java8-tutorial.git
rocling
java8-tutorial
java8-tutorial
master

搜索帮助