品茗Java8新特性

in Java with 0 comment

看了下openjdk官网 http://openjdk.java.net/projects/jdk/14/ jdk14今年就要发布稳定版本了,连java8都没有系统的学习过。那就来学习下java8吧,这是一个长期维护的版本。这里主要来学习java8的一些新特性。对应demo可以下载链接:

https://github.com/wenqy/java-study

接口默认方法

使用default关键字,为接口声明添加非抽象的方法实现接口默认方法

public interface DefaultInterfaceMethod {
    double calc(int a);
    /**
     * 接口使用 <b>default</b>关键字,添加非抽象方法实现(扩展方法)
     * @param a
     * @return
     * @author wenqy
     * @date 2020年1月16日 上午9:55:20
     */
    default double sqrt(int a) {
        return Math.sqrt(a);
    }
}

然后实现匿名内部类,并调用默认方法

DefaultInterfaceMethod interfaceMethod = new DefaultInterfaceMethod() {
    @Override
    public double calc(int a) {
        return sqrt(a * 100);
    }
};
System.out.println(“calc:” + interfaceMethod.calc(100)); // calc:100.0
System.out.println(“sqrt:” + interfaceMethod.sqrt(16)); // sqrt:4.0

Lambda表达式

来看下排序的例子,静态工具方法Collections.sort接受一个list,和一个Comparator接口作为输入参数,java8之前需要一个匿名对象,java8可以用Lambda表达式变得简洁

    /**
     * Java8之前方法(匿名对象)实现排序
     * @param names
     * @author wenqy
     * @date 2020年1月16日 上午10:07:45
     */
    private static void sortByBefore8Method(List<String> names) {
        Collections.sort(names, new Comparator<String>() {
            @Override
            public int compare(String a, String b) {
                return b.compareTo(a);
            }
        });
    }
    /**
     * 用Lambda表达式排序,Java编译器能够自动识别参数类型
     * @param names
     * @author wenqy
     * @date 2020年1月16日 上午10:09:40
     */
    private static void sortByLambda(List<String> names) {
//      可以这样写
//      Collections.sort(names, (String a, String b) -> {
//          return b.compareTo(a);
//      });
//      也可以这样写
//      Collections.sort(names, (String a, String b) -> b.compareTo(a));
//      还可以这样写
        Collections.sort(names, (a, b) -> b.compareTo(a));
    }

Java编译器能够自动识别参数的类型,所以可以省略掉类型不写。

函数式接口

每一个lambda都能够通过一个特定的接口,与一个给定的类型进行匹配。一个所谓的函数式接口必须要有且仅有一个抽象方法声明。每个与之对应的lambda表达式必须要与抽象方法的声明相匹配。由于默认方法不是抽象的,因此你可以在你的函数式接口里任意添加默认方法。

任意只包含一个抽象方法的接口,我们都可以用来做成lambda表达式。为了让你定义的接口满足要求,你应当在接口前加上@FunctionalInterface 标注。编译器会注意到这个标注,如果你的接口中定义了第二个抽象方法的话,编译器会抛出异常。

/**
 * 
 * 每一个lambda都能够通过一个特定的接口,与一个给定的类型进行匹配。
 * 一个所谓的函数式接口必须要有且仅有一个抽象方法声明。
 * 每个与之对应的lambda表达式必须要与抽象方法的声明相匹配。
 * 
 * @FunctionalInterface 标注。
 *  编译器会注意到这个标注,如果接口中定义了第二个抽象方法的话,编译器会抛出异常
 * 
 * @version V5.0
 * @author wenqy
 * @date   2020年1月16日
 */
@FunctionalInterface
public interface Converter<F, T> {
    /**
     * 类型转换  F->T
     * @param from
     * @return
     * @author wenqy
     * @date 2020年1月16日 上午10:22:32
     */
    T convert(F from);
//  T mapping(F from);
}

方法和构造函数引用

函数式接口可以通过方法或构造函数引用使代码变得更简洁。Java 8 允许你通过::关键字获取方法或者构造函数的的引用。

static class Something {
    String startsWith(String s) {
        return String.valueOf(s.charAt(0));
    }
}
private static <F, T> void convert(Converter<F,T> converter, F from) {
    T converted = converter.convert(from);
    System.out.println(converted);
}
public static void main(String[] args) {
    Converter<String, Integer> converter1 = Integer::valueOf; // 静态方法引用
    convert(converter1,“123”); // “1”
    Something something = new Something();
    Converter<String, String> converter2 = something::startsWith; // 对象方法引用
    convert(converter2,“Java”); // “J”
    PersonFactory<Person> personFactory = Person::new;  // 构造函数引用(构造参数需一致)
    Person person = personFactory.create(“Peter”, “Parker”);
    System.out.println(person); // Person [firstName=Peter, lastName=Parker]
}

Lambda访问范围

对于lambda表达式外部的变量,其访问权限的粒度与匿名对象的方式非常类似。你能够访问局部对应的外部区域的局部final变量,以及成员变量和静态变量。默认方法无法在lambda表达式内部被访问。

我们可以访问lambda表达式外部的final局部变量,但是与匿名对象不同的是,变量num并不需要一定是final,然而,num在编译的时候被隐式地当做final变量来处理。

 static class Lambda {
        static int outerStaticNum;
        int outerNum;
        /**
         * 访问成员变量
         * 
         * @author wenqy
         * @date 2020年1月16日 上午11:15:31
         */
        void accessOuterNum() {
            Converter<Integer, String> converter = (from) -> {
                outerNum = 23;
                return String.valueOf(outerNum + from);
            };
            System.out.println(“accessOuterNum:” + converter.convert(2)); // 25
//          outerNum = 3;
        }
        /**
         * 访问静态变量
         * 
         * @author wenqy
         * @date 2020年1月16日 上午11:18:41
         */
        void accessOuterStaticNum() {
            Converter<Integer, String> converter = (from) -> {
                outerStaticNum = 72;
                return String.valueOf(outerStaticNum + from);
            };
            System.out.println(“accessOuterStaticNum:” + converter.convert(2)); // 74
//          outerStaticNum = 3;
        }
        /**
         * 访问局部变量
         *      可以访问lambda表达式外部的final局部变量
         *      但是与匿名对象不同的是,变量num并不需要一定是final
         * @author wenqy
         * @date 2020年1月16日 上午11:06:09
         */
        void accessLocalVariable() {
//          final int num = 1;
            int num = 1;
            Converter<Integer, String> stringConverter =
                    (from) -> String.valueOf(from + num);
            System.out.println(“accessLocalVariable:” + stringConverter.convert(2));     // 3
//          num = 3; // 编译的时候被隐式地当做final变量来处理,无法改变值
        }
    }
    public static void main(String[] args) {
        Lambda lambda = new Lambda();
        lambda.accessLocalVariable();
        lambda.accessOuterNum();
        lambda.accessOuterStaticNum();
//      默认方法无法在lambda表达式内部被访问,无法通过编译
//      DefaultInterfaceMethod formula = (a) -> sqrt( a * 100);
    }

内置函数式接口

Java 8 API 还提供了很多新的函数式接口,来降低程序员的工作负担。让我们来看下几个内置的函数式接口

Predicate是一个布尔类型的函数,该函数只有一个输入参数。Predicate接口包含了多种默认方法,用于处理复杂的逻辑动词(and, or,negate)

Function接口接收一个参数,并返回单一的结果。默认方法可以将多个函数串在一起(compse, andThen)

Supplier接口产生一个给定类型的结果。与Function不同的是,Supplier没有输入参数

Consumer代表了在一个输入参数上需要进行的操作

Comparator接口在早期的Java版本中非常著名。Java 8 为这个接口添加了不同的默认方法

Optional不是一个函数式接口,而是一个精巧的工具接口,用来防止NullPointerException产生。他是一个简单的值容器,这个值可以是null,也可以是non-null。考虑到一个方法可能会返回一个non-null的值,也可能返回一个空值。为了不直接返回null,我们在Java 8中就返回一个Optional

/**
     * 布尔类型函数
     * 
     * @author wenqy
     * @date 2020年1月16日 上午11:39:26
     */
    private static void testPredicates() {
        System.out.println(“——>testPredicates——>”);
        Predicate<String> predicate = (s) -> s.length() > 0;
        System.out.println(predicate.test(“foo”)); // true
        System.out.println(predicate.negate().test(“foo”)); // false
        Predicate<Boolean> nonNull = Objects::nonNull;
        Predicate<Boolean> isNull = Objects::isNull;
        System.out.println(nonNull.test(null)); // false
        System.out.println(isNull.test(null)); // true
        Predicate<String> isEmpty = String::isEmpty;
        Predicate<String> isNotEmpty = isEmpty.negate();
        System.out.println(isEmpty.test(“”)); // true
        System.out.println(isNotEmpty.test(“”)); // false
    }
    /**
     * 内置 Function 函数,将多个函数串联
     * 
     * @author wenqy
     * @date 2020年1月16日 上午11:41:35
     */
    private static void testFunctions() {
        System.out.println(“——>testFunctions——>”);
        Function<String, Integer> toInteger = Integer::valueOf;
        Function<String, String> backToString = toInteger.andThen(String::valueOf);
        System.out.println(backToString.apply(“123”));     // “123”
    }
    /**
     * 内置 Supplier 函数
     *  产生一个给定类型的结果
     * @author wenqy
     * @date 2020年1月16日 上午11:52:51
     */
    private static void testSuppliers() {
        System.out.println(“——>testSuppliers——>”);
        Supplier<Person> personSupplier = Person::new;
        Person person = personSupplier.get();   // new Person
        person.setFirstName(“wen”);
        person.setLastName(“qy”);
        System.out.println(person);
    }
    /**
     * 内置 Consumer 函数
     *  输入参数上需要进行操作
     * @author wenqy
     * @date 2020年1月16日 下午1:48:33
     */
    private static void testConsumers() {
        System.out.println(“——>testConsumers——>”);
        Consumer<Person> greeter = (p) -> System.out.println(“Hello, “ + p.getFirstName());
        greeter.accept(new Person(“Luke”, “Skywalker”));
    }
    /**
     * 
     * 内置 Comparator 函数
     *      用于比较
     * @author wenqy
     * @date 2020年1月16日 下午1:54:29
     */
    private static void testComparators() {
        System.out.println(“——>testComparators——>”);
        Comparator<Person> comparator = (p1, p2) -> p1.getFirstName().compareTo(p2.getFirstName());
        Person p1 = new Person(“John”, “Doe”);
        Person p2 = new Person(“Alice”, “Wonderland”);
        System.out.println(comparator.compare(p1, p2)); // > 0
        System.out.println(comparator.reversed().compare(p1, p2)); // < 0
    }
    /**
     * Optional
     *  值容器,用来防止NullPointerException产生
     * @author wenqy
     * @date 2020年1月16日 下午1:58:44
     */
    private static void testOptionals() {
        System.out.println(“——>testOptionals——>”);
        Optional<String> optional = Optional.of(“bam”);
        System.out.println(optional.isPresent()); // true
        System.out.println(optional.get()); // “bam”
        System.out.println(optional.orElse(“fallback”)); // “bam”
        optional.ifPresent((s) -> System.out.println(s.charAt(0)));     // “b”
    }

流Stream

java.util.Stream表示了某一种元素的序列,在这些元素上可以进行各种操作。Stream操作可以是中间操作,也可以是完结操作。完结操作会返回一个某种类型的值,而中间操作会返回流对象本身,并且你可以通过多次调用同一个流操作方法来将操作结果串起来(就像StringBuffer的append方法一样)。Stream是在一个源的基础上创建出来的,例如java.util.Collection中的list或者set(map不能作为Stream的源)。Stream操作往往可以通过顺序或者并行两种方式来执行。Stream并不会改变原有的序列。

List<String> stringCollection = new ArrayList<>();
public StreamsMain() {
    init();
}
private void init() {
    if (stringCollection.isEmpty()) {
        stringCollection.add(“ddd2”);
        stringCollection.add(“aaa2”);
        stringCollection.add(“bbb1”);
        stringCollection.add(“aaa1”);
        stringCollection.add(“bbb3”);
        stringCollection.add(“ccc”);
        stringCollection.add(“bbb2”);
        stringCollection.add(“ddd1”);
    }
}
/**
 * 
 * Filter接受一个predicate接口类型的变量,并将所有流对象中的元素进行过滤。
 * 该操作是一个中间操作,因此它允许我们在返回结果的基础上再进行其他的流操作(forEach)。
 * ForEach接受一个function接口类型的变量,用来执行对每一个元素的操作。
 * ForEach是一个中止操作。它不返回流,所以我们不能再调用其他的流操作。
 * @author wenqy
 * @date 2020年1月16日 下午2:34:58
 */
private void testFilter() {
    System.out.println(“—–>testFilter—–>”);
    stringCollection
        .stream()
        .filter((s) -> s.startsWith(“a”)) // 过滤以“a”开头的集合
        .forEach(System.out::println);
}
/**
 * Sorted是一个中间操作,能够返回一个排过序的流对象的视图。
 * 流对象中的元素会默认按照自然顺序进行排序,除非你自己指定一个Comparator接口来改变排序规则
 * 
 * @author wenqy
 * @date 2020年1月16日 下午2:38:28
 */
private void testSorted() {
    System.out.println(“—–>testSorted—–>”);
    stringCollection
        .stream()
        .sorted() // 自然排序
        .filter((s) -> s.startsWith(“a”)) // 过滤以“a”开头的集合
        .forEach(System.out::println);
    // sorted不会改变原来集合中元素的顺序。原来string集合中的元素顺序是没有改变的。
    System.out.println(stringCollection);
}
/**
 * map是一个对于流对象的中间操作,通过给定的方法,它能够把流对象中的每一个元素对应到另外一个对象上。
 * 下面的例子就演示了如何把每个string都转换成大写的string. 
 * 不但如此,你还可以把每一种对象映射成为其他类型。
 * 对于带泛型结果的流对象,具体的类型还要由传递给map的泛型方法来决定。
 * 
 * @author wenqy
 * @date 2020年1月16日 下午2:43:38
 */
private void testMap() {
    System.out.println(“—–>testMap—–>”);
    stringCollection
        .stream()
        .map(String::toUpperCase) // 每个元素转大写
        .sorted((a, b) -> b.compareTo(a))
        .forEach(System.out::println);
    // sorted不会改变原来集合中元素的顺序。原来string集合中的元素顺序是没有改变的。
    System.out.println(stringCollection);
}
/**
 * 匹配操作有多种不同的类型,都是用来判断某一种规则是否与流对象相互吻合的。
 * 所有的匹配操作都是终结操作,只返回一个boolean类型的结果。
 * 
 * @author wenqy
 * @date 2020年1月16日 下午2:47:48
 */
private void testMatch() {
    System.out.println(“—–>testMatch—–>”);
    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是一个终结操作,它的作用是返回一个数值,用来标识当前流对象中包含的元素数量。
 * 
 * @author wenqy
 * @date 2020年1月16日 下午2:51:40
 */
private void testCount() {
    System.out.println(“—–>testCount—–>”);
    long startsWithB =
        stringCollection
            .stream()
            .filter((s) -> s.startsWith(“b”))
            .count();
    System.out.println(startsWithB);    // 3
}
/**
 * 该操作是一个终结操作,它能够通过某一个方法,对元素进行削减操作。
 * 该操作的结果会放在一个Optional变量里返回。
 * 
 * @author wenqy
 * @date 2020年1月16日 下午2:54:00
 */
private void testReduce() {
    System.out.println(“—–>testReduce—–>”);
    Optional<String> reduced =
        stringCollection
            .stream()
            .sorted()
            .reduce((s1, s2) -> s1 + “#” + s2);
    reduced.ifPresent(System.out::println); // “aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2”
}

并发流Stream

数量量大时,可以使用并行流进行操作来提高运行效率。

//  int max = 1000000; // 大数据量并发才有优势
    int max = 400000;
    List<String> values = new ArrayList<>(max);
    public ParallelStreamsMain() {
        init();
    }
    private void init() {
        for (int i = 0; i < max; i++) {
            UUID uuid = UUID.randomUUID();
            values.add(uuid.toString());
        }
    }
    /**
     * 顺序排序
     * 
     * @author wenqy
     * @date 2020年1月16日 下午3:20:22
     */
    private void sequenceSorted() {
        System.out.println(“——>sequenceSorted—–>”);
        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));
    }
    /**
     * 并行排序
     * 
     * @author wenqy
     * @date 2020年1月16日 下午3:21:56
     */
    private void parallelSorted() {
        System.out.println(“——>parallelSorted—–>”);
        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));
    }

Map

map是不支持流操作的。而更新后的map现在则支持多种实用的新方法

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));
map.computeIfPresent(3, (num, val) -> val + num);
map.get(3);             // val33
map.computeIfPresent(9, (num, val) -> null); // 返回null时,remove(key)
map.containsKey(9);     // false
map.computeIfAbsent(23, num -> “val” + num);
map.containsKey(23);    // true
map.computeIfAbsent(3, num -> “bam”); // 旧值存在时返回旧值,不进行替换
map.get(3);             // val33
map.remove(3, “val3”);  // 校验value删除
map.get(3);             // val33
map.remove(3, “val33”);
map.get(3);             // null
map.getOrDefault(42, “not found”);  // not found 不存在则返回默认值
map.merge(9, “val9”, (value, newValue) -> value.concat(newValue)); // 先前key 9 已经删除了,不存在key,则返回 value
System.out.println(map.get(9));             // val9
map.merge(9, “concat”, (value, newValue) -> value.concat(newValue)); // 存在key,才进行合并
System.out.println(map.get(9));             // val9concat

时间日期API

Java 8 包含了全新的时间日期API,这些功能都放在了java.time包下。新的时间日期API是基于Joda-Time库开发的,但是也不尽相同。

/**
 * Clock提供了对当前时间和日期的访问功能。Clock是对当前时区敏感的
 * 
 * @author wenqy
 * @date 2020年1月16日 下午4:36:49
 */
private static void testClock() {
    System.out.println(“—–>testClock—–>”);
    Clock clock = Clock.systemDefaultZone();
    long millis = clock.millis();
    System.out.println(millis); // 获取当前毫秒时间
    Instant instant = clock.instant();
    Date date = Date.from(instant);   // java.util.Date
    System.out.println(date); // 获取日期 Thu Jan 16 16:38:01 CST 2020
}
/**
 * 时区类可以用一个ZoneId来表示。
 * 时区类还定义了一个偏移量,用来在当前时刻或某时间与目标时区时间之间进行转换。
 * 
 * @author wenqy
 * @date 2020年1月16日 下午4:40:38
 */
private static void testTimezones() {
    System.out.println(“—–>testTimezones—–>”);
    System.out.println(ZoneId.getAvailableZoneIds());
    // prints all available timezone ids
    ZoneId zone1 = ZoneId.of(“Europe/Berlin”);
    ZoneId zone2 = ZoneId.of(“Brazil/East”);
    ZoneId zone3 = ZoneId.of(“Asia/Shanghai”);
    System.out.println(zone1.getRules());
    System.out.println(zone2.getRules());
    System.out.println(zone3.getRules());
}
/**
 * 本地时间类表示一个没有指定时区的时间
 * 
 * @author wenqy
 * @date 2020年1月16日 下午4:49:11
 */
private static void testLocalTime() {
    System.out.println(“—–>testLocalTime—–>”);
    LocalTime now1 = LocalTime.now(ZoneId.of(“Europe/Berlin”));
    LocalTime now2 = LocalTime.now(ZoneId.of(“Asia/Shanghai”));
    // 比较两个时间
    System.out.println(now1.isBefore(now2));  // true
    long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
    long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);
    // 柏林时区在东一区,上海时区在东八区   柏林比北京时间慢7小时
    System.out.println(hoursBetween);       // 7
    System.out.println(minutesBetween);     // 420
    // 时间字符串解析操作
    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
}
/**
 * 本地日期
 *  每一次操作都会返回一个新的时间对象
 * 
 * @author wenqy
 * @date 2020年1月16日 下午5:05:52
 */
private static void testLocalDate() {
    System.out.println(“—–>testLocalDate—–>”);
    LocalDate today = LocalDate.now(); // 今天
    LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS); // 明天=今天+1
    LocalDate yesterday = tomorrow.minusDays(2); // 昨天=明天-2
    System.out.println(yesterday);
    LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
    DayOfWeek dayOfWeek = independenceDay.getDayOfWeek(); // 获取周
    System.out.println(dayOfWeek); // FRIDAY
}
/**
 * 日期-时间
 *  final对象
 * @author wenqy
 * @date 2020年1月16日 下午5:12:37
 */
private static void testLocalDateTime() {
    System.out.println(“—–>testLocalDateTime—–>”);
    LocalDateTime sylvester = LocalDateTime.of(2019, Month.DECEMBER, 31, 23, 59, 59);
    DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
    System.out.println(dayOfWeek);      // TUESDAY
    Month month = sylvester.getMonth();
    System.out.println(month);          // DECEMBER
    long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
    System.out.println(minuteOfDay);    // 1439
    // 加上时区,转日期Date
    Instant instant = sylvester
            .atZone(ZoneId.systemDefault())
            .toInstant();
    Date legacyDate = Date.from(instant);
    System.out.println(legacyDate);     // Tue Dec 31 23:59:59 CST 2019
    // 自定义格式对象
    DateTimeFormatter formatter =
        DateTimeFormatter
            .ofPattern(“yyyy-MM-dd HH:mm:ss”);  // 线程安全,不可变
    LocalDateTime parsed = LocalDateTime.parse(“2020-01-16 17:13:00”, formatter);
    String string = formatter.format(parsed);
    System.out.println(string);     // 2020-01-16 17:13:00
}

可重复注解

Java 8中的注解是可重复的

/**
 * @Repeatable,Java 8 允许我们对同一类型使用多重注解
 * 
 * @version V5.0
 * @author wenqy
 * @date   2020年1月16日
 */
@Repeatable(Hints.class)
@Retention(RetentionPolicy.RUNTIME) // 需要指定runtime,否则测试会失败
public @interface Hint {
    String value();
}

在Person类添加注解,尝试获取类注解

/**
 * @Hint 可重复注解(新方法)
 * 使用注解容器(老方法): @Hints({@Hint(“hint1”), @Hint(“hint2”)})
 * @version V5.0
 * @author wenqy
 * @date   2020年1月16日
 */
@Hint(“hint1”)
@Hint(“hint2”)
//@Hints({@Hint(“hint1”), @Hint(“hint2”)})
public class Person {
    // …
}
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

String

java8添加了对字符数据流的流式操作

System.out.println(String.join(“:”, “foobar”, “foo”, “bar”)); // foobar:foo:bar
        // 指定分割符,将字符串拼接
        System.out.println(
                “foobar:foo:bar”
                .chars() // 字符数据流
                .distinct()
                .mapToObj(c -> String.valueOf((char)c))
                .sorted()
                .collect(Collectors.joining())
                ); // :abfor
        System.out.println(
                Pattern.compile(“:”)
                .splitAsStream(“foobar:foo:bar”)
                .filter(s -> s.contains(“bar”))
                .sorted()
                .collect(Collectors.joining(“:”))
            ); // bar:foobar
        System.out.println(
                Stream.of(“bob@gmail.com”, “alice@hotmail.com”)
                    .filter(Pattern.compile(“.*@gmail\\.com”).asPredicate())
                    .count()
            ); // 1

数值处理

Java8添加了对无符号数的额外支持。新增了一些方法来处理数值溢出

System.out.println(Integer.MAX_VALUE);      // 2147483647
        System.out.println(Integer.MAX_VALUE + 1);  // -2147483648
        long maxUnsignedInt = (1l << 32) – 1; // 
        String string = String.valueOf(maxUnsignedInt);
        int unsignedInt = Integer.parseUnsignedInt(string, 10); // 无符号转换
        String string2 = Integer.toUnsignedString(unsignedInt, 10);
        System.out.println(string2);
        try {
            Integer.parseInt(string, 10);
        } catch (NumberFormatException e) {
            System.err.println(“could not parse signed int of “ + maxUnsignedInt);
        }
        try {
            Math.addExact(Integer.MAX_VALUE, 1);
        }
        catch (ArithmeticException e) {
            System.err.println(e.getMessage());
            // => integer overflow
        }
        try {
            Math.toIntExact(Long.MAX_VALUE);
        }
        catch (ArithmeticException e) {
            System.err.println(e.getMessage());
            // => integer overflow
        }

文件处理

java8新增了对文件处理的方法

/**
 * 文件查找
 * @throws IOException
 * @author wenqy
 * @date 2020年1月18日 下午3:02:05
 */
private static void findFiles() throws IOException {
    Path start = Paths.get(“”);
    int maxDepth = 5;
    try (Stream<Path> stream = Files.find(start, maxDepth, (path, attr) ->
            String.valueOf(path).endsWith(“.js”))) {
        String joined = stream
            .sorted()
            .map(String::valueOf)
            .collect(Collectors.joining(“; “));
        System.out.println(“Found: “ + joined);
    }
}
/**
 * 获取文件列表
 * @throws IOException
 * @author wenqy
 * @date 2020年1月18日 下午3:02:26
 */
private static void listFiles() throws IOException {
    try (Stream<Path> stream = Files.list(Paths.get(“”))) {
        String joined = stream
            .map(String::valueOf)
            .filter(path -> !path.startsWith(“.”))
            .sorted()
            .collect(Collectors.joining(“; “));
        System.out.println(“List: “ + joined);
        // 列出了当前工作目录的所有文件,之后将每个路径都映射为它的字符串表示。之后结果被过滤、排序,最后连接为一个字符串
    }
}
/**
 * 文件读写处理
 * @throws IOException
 * @author wenqy
 * @date 2020年1月18日 下午3:02:52
 */
private static void handleFiles() throws IOException {
    List<String> lines = Files.readAllLines(Paths.get(“res/nashorn1.js”)); // 整个文件都会读进内存,不高效。文件越大,所用的堆区也就越大
    lines.add(“print(‘foobar’);”);
    Files.write(Paths.get(“res/nashorn1-modified.js”), lines);
    try (Stream<String> stream = Files.lines(Paths.get(“res/nashorn1.js”))) { // 行读取,高效点
        stream
            .filter(line -> line.contains(“print”))
            .map(String::trim)
            .forEach(System.out::println);
    }
    try (BufferedReader reader = Files.newBufferedReader(Paths.get(“res/nashorn1.js”))) { // BufferedReader 更精细
        System.out.println(reader.readLine());
    }
    Path pathOut = Paths.get(“res/output.js”);
    try (BufferedWriter writer = Files.newBufferedWriter(pathOut)) { // BufferedWriter 写入文件
        writer.write(“print(‘Hello World’);”);
    }
    try (BufferedReader reader = Files.newBufferedReader(Paths.get(“res/nashorn1.js”))) {
        long countPrints = reader
            .lines() // 流式处理
            .filter(line -> line.contains(“print”))
            .count();
        System.out.println(countPrints);
    }
}

避免 Null 检查

java8之前,如果是多层内嵌对象,需要多次判空,引入Optional 类型提高安全性

public static void main(String[] args) {
    Outer outer = new Outer();
    if (outer != null && outer.nested != null && outer.nested.inner != null) {
        System.out.println(outer.nested.inner.foo);
    }
    Optional.of(new Outer())
        .map(Outer::getNested)
        .map(Nested::getInner)
        .map(Inner::getFoo)
        .ifPresent(System.out::println);
    Outer obj = new Outer();
    resolve(() -> obj.getNested().getInner().getFoo())
        .ifPresent(System.out::println);
    // 这两个解决方案可能没有传统 null 检查那么高的性能
}
private static <T> Optional<T> resolve(Supplier<T> resolver) {
    try {
        T result = resolver.get();
        return Optional.ofNullable(result);
    }
    catch (NullPointerException e) {
        return Optional.empty();
    }
}

初识java8新特性,java8语法糖还是挺香的,java8之前方法调用一堆的回调,让人看的眼花缭乱,不整洁。但让人想吐槽的是调试,加难了调试力度,可能是新手错觉。。。

参考

https://github.com/winterbe/java8-tutorial java8教程

https://wizardforcel.gitbooks.io/modern-java/content/ 中文译站

https://github.com/wenqy/java-study

https://github.com/wenqy/java-study