java 8新特性总结

<接口的默认方法>

 

解决的问题:在java8 之前的版本,在修改已有的接口的时候,需要修改实现该接口的实现类。

 

作用:解决接口的修改与现有的实现不兼容的问题。在不影响原有实现类的结构下修改新的功能方法。

 

java 8抽象类和接口的区别

 

相同点:

 

Ø  都是抽象类型

Ø  都可以有实现方法(在java8之前是不可以的)

Ø  都可以不需要实现类或者继承者去实现所有方法,(以前不行,现在接口中默认方法不需要实现者实现)

 

不同点:

 

Ø  抽象类不可以多重继承,接口可以(无论是多重类型继承还是多重行为继承);

Ø  抽象类和接口所反映出的设计理念不同。其实抽象类表示的是"is-a"关系,接口表示的是"like-a"关系;

Ø  接口中定义的变量默认是publicstatic final 型,且必须给其初值,所以实现类中不能改变其值;抽象类中的变量默认是 friendly 型,其值可以在子类中重新定义,也可以重新赋值。

 

<Lambda表达式>

 

解决问题:为 Java 添加了缺失的函数式编程特点,

 

作用:使程序更简洁便于可读可维护。

 

语法:

(arg1, arg2...) -> { body }
 
 
(type1 arg1, type2 arg2...) -> { body }
 
例子:
(int a, int b) -> {  return a + b; }
 
() -> System.out.println("Hello World");
 
(String s) -> { System.out.println(s); }
 
() -> 42
 
() -> { return 3.1415 };

 

l  lambda表达式的结构:

Ø  一个 Lambda 表达式可以有零个或多个参数

 

Ø  参数的类型既可以明确声明,也可以根据上下文来推断。

Ø  例如:(int a)与(a)效果相同

所有参数需包含在圆括号内,参数之间用逗号相隔。例如:(a, b) 或 (int a, int b) 或 (String a, int b, float c)

 

Ø  空圆括号代表参数集为空。例如:() -> 42

 

Ø  当只有一个参数,且其类型可推导时,圆括号()可省略。例如:a -> return a*a

 

Ø  Lambda 表达式的主体可包含零条或多条语句

 

Ø  如果 Lambda 表达式的主体只有一条语句,花括号{}可省略。匿名函数的返回类型与该主体表达式一致

 

Ø  7.如果 Lambda 表达式的主体包含一条以上语句,则表达式必须包含在花括号{}中(形成代码块)。匿名函数的返回类型与代码块的返回类型一致,若没有返回则为空

 

<Lambda范围>

l  访问本地变量

我们可以访问在lambda表示式之外的本地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在编译期是被隐式的转换为fianl类型的。下面的代码是不能被成功的编译的:

 

    int num = 1;  
    Converter<Integer, String> stringConverter =  
            (from) -> String.valueOf(from + num);  //这里的num会编译报错
    num = 3;  

在lambda表达式内部向变量num写入值同样是不允许的。

l  访问对象字段和静态变量

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

l  访问接口默认方法

Ø  接口默认方法不能被Lambda表达式的内部代码所访问

 

 

 

<功能性接口>

Ø  JDK1.8包括了许多功能性接口。它们中的一些是老版本中被熟知的接口,例如Comparator和Runnable。这些已存在的接口已经通过@FunctionalInterface注解扩展为支持Lambda表达式。

 

Ø  在 Java 中,Marker(标记)类型的接口是一种没有方法或属性声明的接口,简单地说,marker 接口是空接口。相似地,函数式接口是只包含一个抽象方法声明的接口

 

Ø  每个 Lambda 表达式都能隐式地赋值给函数式接口,当不指明函数式接口时,编译器会自动解释这种转化

 

Ø  函数式接口例子:

Consumer<Integer>  c = (int x) -> { System.out.println(x) };
 
BiConsumer<Integer, String> b = (Integer x, String y) -> System.out.println(x + " : " + y);
 
Predicate<String> p = (String s) -> { s == null };

 

 

l  相关接口:

1.        断言接口(Predicates) 是只拥有一个参数的Boolean型功能的接口。这个接口拥有多个默认方法用于构成predicates复杂的逻辑术语

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

 

2.        功能接口(Functions)Functions接受一个参数并产生一个结果,默认方法能够用于将多个函数链接在一起。

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

backToString.apply(“5120”);

3.        供应接口(Supplines) 对于给定的泛型类型产生一个实例。不同于Functions,Suppliers不需要任何参数。

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

 

 

4.        消费接口(Consumers)代表在只有一个输入参数时操作被如何执行

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

5.        比较接口(Comparators)

 
   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  

6.选项接口(Optionals)一种特殊的工具用来解决NullPointerException

    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" 

7. 流接口(Streams)

java.util.Stream代表着一串你可以在其上进行多种操作的元素。流操作既可以是连续的也可以是中断的。中断操作返回操作结果。而连续操作返回流本身,这样你就可以在该行上继续操作。流是创建在数据源上的,例如:java.util.Collection、list集合和set集合。流操作既可以顺序执行也可以并行执行

首先创建一个字符串的数组

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");  

Java8的Collections类已经被扩展了,你可以简单的调用Collection.stream()或者Collection.parallelSteam()来创建流。下面部分将介绍大部分流操作。

Filter

Filter接受一个predicate来过滤流中的所有元素。这个操作是连续的,它可以让我们在结果上继续调用另外一个流操作forEach。ForEach接受一个consumer,它被用来对过滤流中的每个元素执行操作。ForEach是一个中断操作,因此我们不能在ForEach后调用其他流操作。

    stringCollection  
        .stream()  
        .filter((s) -> s.startsWith("a"))  
        .forEach(System.out::println);  
 
    // "aaa2", "aaa1"  
Sorted

Sorted是一个连续操作,它返回流的已排序版本。如果你没有显示的指定Comparator,那么流中元素的排序规则为默认的。

    stringCollection  
        .stream()  
        .sorted()  
        .filter((s) -> s.startsWith("a"))  
        .forEach(System.out::println);  
 
    // "aaa1", "aaa2"  

需要注意的是sorted只创建了流的排序结果,它并没有改变集合中元素的排序位置。stringCollection中元素排序是没有改变的。

    System.out.println(stringCollection);  
    // ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1  
Map

连续性操作map通过指定的Function将流中的每个元素转变为另外的对象。下面的示例将每个字符串转换为大写的字符串。此外,你也可以使用map将每个元素的类型改变为其它类型。转换后流的泛型类型依赖于你传入的Function的泛型类型。

    stringCollection  
        .stream()  
        .map(String::toUpperCase)  
        .sorted((a, b) -> b.compareTo(a))  
        .forEach(System.out::println);  
 
    // "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"  
Match

各种匹配操作可以用来检测是否某种predicate和流中元素相匹配。所有的这些操作是中断的并返回一个boolean结果。

    boolean anyStartsWithA =   
        stringCollection  
            .stream()  
            .anyMatch((s) -> s.startsWith("a"));  
 
    System.out.println(anyStartsWithA);      // true  
 
    boolean allStartsWithA =   
        stringCollection  
            .stream()  
            .allMatch((s) -> s.startsWith("a"));  
 
    System.out.println(allStartsWithA);      // false  
 
    boolean noneStartsWithZ =   
        stringCollection  
            .stream()  
            .noneMatch((s) -> s.startsWith("z"));  
 
    System.out.println(noneStartsWithZ);      // true  
Count

Count是中断型操作,它返回流中的元素数量。

    long startsWithB =   
        stringCollection  
            .stream()  
            .filter((s) -> s.startsWith("b"))  
            .count();  
 
    System.out.println(startsWithB);    // 3  
Reduce

这个中断性操作使用指定的function对流中元素实施消减策略。此操作的返回值是一个包括所有被消减元素的Optional。

    Optional<String> reduced =  
        stringCollection  
            .stream()  
            .sorted()  
            .reduce((s1, s2) -> s1 + "#" + s2);  
 
    reduced.ifPresent(System.out::println);  
    // "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"  

Parallel Streams

在前面部分我们提到流可以是顺序的也可以是并行的。顺序流的操作是在单线程上执行的,而并行流的操作是在多线程上并发执行的。

随后的例子我们展示了并行流可以多么容易的提高性能。

首先,我们创建一个包含唯一元素的大容器:

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

现在我们开始测试排序这些元素需要多长时间。

Sequential 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  

Parallel 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  

你会观察到这两种模式的代码基本上是一致的,但是并行排序所花费的时间大约是顺序排序的一半。

Map

我们已经提到maps不支持流。然而现在maps包括了许多新的非常有用的方法用于执行通用任务。

    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使得我们不用写是否为null值的检测语句;forEach使用consumer来对map中的每个元素进行操作。下面的例子向我们展示使用功能性函数在map里执行代码:

    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  

接下来,我们将学习如何删除给定键所对应的元素。删除操作还需要满足给定的值需要和map中的值想等:

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

其他一些帮助性方法:

    map.getOrDefault(42, "not found");  // not found  

合并map中的实体是十分容易的:

    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  

如果map不存在指定的键,那么它将把该键值对key/value加入map中。反而,如果存在,它将调用function来进行合并操作。

l  Date API 新的date和time 的api,与joda-Time库是兼容的。

Ø  Clock

提供了访问当前日期和时间的方法。Clock是时区敏感的并且它可以被用来替代System.currentTimeMillis进行获取当前毫秒数。同时,时间轴上的时间点是可以用类Instant来表示的。Instants可以被用来创建遗留的java.util.Date对象。

    Clock clock = Clock.systemDefaultZone();  
    long millis = clock.millis();  
 
    Instant instant = clock.instant();  
    Date legacyDate = Date.from(instant);   // legacy java.util.Date  

 

Ø  TimeZones

被用来表示ZoneId。它们可以通过静态工厂方法访问。TImeZones定义了时差,它在instants和本地日期时间转换上十分重要。

        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]  

 

Ø  LocalTime

本地时间代表了一个和时区无关的时间,e.g. 10pm or 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  

 

Ø  LocalDate

 LocalDate代表了一个可区分日期,e.g.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  

     解析字符串的时间格式:

DateTimeFormatter germanFormatter =  
        DateTimeFormatter  
            .ofLocalizedDate(FormatStyle.MEDIUM)  
            .withLocale(Locale.GERMAN);  
    LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);  
    System.out.println(xmas);   // 2014-12-24  

 

Ø  LocalDateTime

LocalDateTime代表日期和时间。它将我们前部分看到的时间和日期组合进一个实例。LocalDateTime是不可变的并且它的工作原理和LocalTime和LocalDate十分相似。

使用示例:

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  

在一些额外的时区信息帮助下,它可以被转换为instant。Instants可以被容易的转换为遗留的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  

格式date-time的过程和格式date和time基本上是一样的。在使用系统自带的定义格式时,我们也可以定义我们自己的格式:

    DateTimeFormatter formatter =  
        DateTimeFormatter  
            .ofPattern("MMM dd, yyyy - HH:mm");  
 
    LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);  
    String string = formatter.format(parsed);  
    System.out.println(string);     // Nov 03, 2014 - 07:13  

 

 

<方法和构造函数引用>

 

解决问题:简化静态方法的引用

 

使用方式:通过关键字::来传递方法和构造函数的引用

 

例子:

静态方法:

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

 

对象方法:

 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"  

<Annotations>

支持多重注解,在注解声明时使用@Repeatable。

例子:

我们首先定义一个包装注解

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

 

使用1:容器注解

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

使用2:重复注解

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

特别注意:

在原有注解使用范围基础之上,如下:

public enum ElementType {

   /** Class, interface (including annotation type), or enum declaration */

   TYPE,

 

   /** Field declaration (includes enum constants) */

   FIELD,

 

   /** Method declaration */

   METHOD,

 

   /** Parameter declaration */

   PARAMETER,

 

   /** Constructor declaration */

   CONSTRUCTOR,

 

   /** Local variable declaration */

   LOCAL_VARIABLE,

 

   /** Annotation type declaration */

   ANNOTATION_TYPE,

 

   /** Package declaration */

    PACKAGE

Java8注解的使用范围新增两种类型:

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

其他特性:Arrays.parallelSort,StampedLock,CompletableFuture

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值