JDK1.8新特性小总结

JDK1.8新特性

一、接口的默认方法

Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法

interface Formula {
    double calculate(int a);
    default double sqrt(int a) {
        return Math.sqrt(a);
    }
}
Formula formula = new Formula() {
    @Override
    public double calculate(int a) {
        return sqrt(a * 100);
    }
};
formula.calculate(100);     // 100.0
formula.sqrt(16);           // 4.0

二、Lambda 表达式

在老版本的Java中排列字符串

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

在Java 8 中就不用使用这种传统的匿名对象的方式,Java 8提供了更简洁的语法,lambda表达式:

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

对于函数体只有一行代码的,可以去掉大括号{}以及return关键字,参数也可以不写类型:

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

三、函数式接口

可以将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

四、方法与构造函数引用

上面中的代码还可以通过静态方法引用来表示:

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

也可以引用一个对象的方法

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;
    }
}
interface PersonFactory<P extends Person> {
    P create(String firstName, String lastName);
}
PersonFactory<Person> personFactory = Person::new;//相当于用带两个参数的构造方法实现接口方法
Person person = personFactory.create("Peter", "Parker");

五、一些接口配合lambda使用

Predicate接口<br />Predicate 接口只有一个参数,返回boolean类型。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如: 与,或,非):

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

Function 接口<br />Function 接口有一个参数并且返回一个结果,并附带了一些可以和其他函数组合的默认方法(compose, andThen):

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

Supplier 接口
<br />Supplier 接口返回一个任意范型的值,和Function接口不同的是该接口没有任何参数

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

Consumer 接口<br />Consumer 接口表示执行在单个参数上的操作

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

Comparator 接口<br />添加了许多默认的方法

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

Optional 接口<br />Optional 被定义为一个简单的容器,其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是偶尔却可能返回了null,而在Java 8中,不推荐你返回null而是返回Optional。

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"

Stream 接口<br />java.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如 java.util.Collection的子类,List或者Set, Map不支持。Stream的操作可以串行执行或者并行执行。

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

Filter 过滤
过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他Stream操作。

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

Sort 排序
排序是一个中间操作,返回的是排序好后的Stream。如果你不指定一个自定义的Comparator则会使用默认排序,需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据stringCollection是不会被修改的

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

Map 映射
中间操作map会将元素根据指定的Function接口来依次将元素转成另外的对象,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。

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

Match 匹配
Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作,并返回一个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

limit 线段流,使其不超过指定数量

stringCollection.stream()
.sorted()
.limit(2)
.forEach(s-> System.out.println(s));//"aaa1","aaa2"

skip(n)—— 跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit互补

stringCollection.stream()
.sorted()
.skip(2)
.forEach(s-> System.out.println(s));

distinct——筛选,通过硫所生成元素的hashCode()和equals去除重复元素

stringCollection.stream()
.sorted()
.distinct()
.forEach(s-> System.out.println(s));

Count 计数
计数是一个最终操作,返回Stream中元素的个数,返回值类型是long。

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

Reduce 规约
这是一个最终操作,允许通过指定的函数来讲stream中的多个元素规约为一个元素,规越后的结果是通过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"

查找与匹配
allMatch——检查是否匹配所有元素
anyMatch——检查是否至少匹配一个元素
noneMatch——检查是否没有匹配所有元素
findFoirst——返回第一个元素
findAny——返回流中的任意元素
count——返回流中元素的总个数
max——返回流中元素的最大值
min——返回流中元素的最小值

并行Streams
前面提到过Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行

int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
   UUID uuid = UUID.randomUUID();
   values.add(uuid.toString());
}
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));
// 串行耗时: 899 ms
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));
// 并行排序耗时: 472 ms

Map<br />Map类型不支持stream,不过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);
map.containsKey(9);     // false
map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23);    // true
map.computeIfAbsent(3, num -> "bam");
map.get(3);             // val33
map.getOrDefault(42, "not found");  // not found
//Merge做的事情是如果键名不存在则插入,否则则对原键对应的值做合并操作并重新插入到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

六、Optional类

Optional 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用null 表示一个值不存在,现在Optional 可以更好的表达这个概念。并且可以避免空指针异常。<br />常用方法:

  • Optional.of(T t) : 创建一个Optional 实例
  • Optional.empty() : 创建一个空的Optional 实例
  • Optional.ofNullable(T t):若t 不为null,创建Optional 实例,否则创建空实例
  • isPresent() : 判断是否包含值
  • orElse(T t) : 如果调用对象包含值,返回该值,否则返回t
  • orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回s 获取的值
  • map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
  • flatMap(Function mapper):与map 类似,要求返回值必须是Optional

七、新的日期时间API

LocalDate,该类的实例是一个不可变对象,它只提供了简单的日期,并不含当天的时间信息。另外,它也不附带任何与时区相关的信息。
LocalDate例子:

                LocalDate date = LocalDate.of(2019, 1, 20);
        int year = date.getYear(); //2019
        Month month = date.getMonth();//JANUARY
        int dayOfMonth = date.getDayOfMonth();//20
        DayOfWeek dayOfWeek = date.getDayOfWeek();//SUNDAY
        int length = date.lengthOfMonth();//31
        boolean leapYear = date.isLeapYear();//false
        LocalDate now = LocalDate.now();//2019-03-06
        int y = date.get(ChronoField.YEAR);//2019
        int m = date.get(ChronoField.MONTH_OF_YEAR);//1
        int d = date.get(ChronoField.DAY_OF_MONTH);//20

                LocalDate date1 = LocalDate.of(2014, 3, 18); //2014-03-18
            LocalDate date2 = date1.withYear(2011);  //2011-03-18
            LocalDate date3 = date2.withDayOfMonth(25);   //2011-03-25
            LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9);   //2011-09-25
                
                LocalDate date1 = LocalDate.of(2014, 3, 18);
                LocalDate date2 = date1.plusWeeks(1);  //2014-03-25
            LocalDate date3 = date2.minusYears(3);  //2011-03-25
            LocalDate date4 = date3.plus(6, ChronoUnit.MONTHS);  //2011-09-25

LocalTime,该类用于一天中的时间,比如13:45:20

                LocalTime time = LocalTime.of(3,10,20);
        int hour = time.getHour();//3
        int minute = time.getMinute();//10
        int second = time.getSecond();//20

LocalDateTime,是LocalDate和LocalTime的合体。它同时表示了日期和时间,但不带有时区信息,你可以直接创建,也可以通过合并日期和时间对象构造,如下所示。

                // 2014-03-18T13:45:20
        LocalDate date = LocalDate.now();
        LocalTime time = LocalTime.now();
        LocalDateTime dt1 = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45, 20); 
        LocalDateTime dt2 = LocalDateTime.of(date, time);
        LocalDateTime dt3 = date.atTime(13, 45, 20);
        LocalDateTime dt4 = date.atTime(time);
        LocalDateTime dt5 = time.atDate(date);
                LocalDate date1 = dt1.toLocalDate();
                LocalTime time1 = dt1.toLocalTime();

Instant,上面三个有关时间的类都有个一个.toInstant()可转换为Instant()类,可用Date.from(instant)方法转换为Date类。

                Instant instant1 = Instant.ofEpochSecond(3);
        System.out.println(instant1);//1970-01-01T00:00:03Z
        //第一个参数是秒,第二个是纳秒参数,纳秒的存储范围是0至999,999,999
        Instant instant2 = Instant.ofEpochSecond(3,0);
        System.out.println(instant2);//1970-01-01T00:00:03Z
        //2s之后的在加上100万纳秒(1s)
        Instant instant3 = Instant.ofEpochSecond(2,1000000000);
        System.out.println(instant3); //1970-01-01T00:00:03Z
        Instant instant4 = Instant.ofEpochSecond(4,-1000000000);
        System.out.println(instant4); //1970-01-01T00:00:03Z

上面这些类都实现了Temporal接口,所以有一些通用的方法如下:

image.png

Duration类的静态工厂方法between就是需要创建两个Temporal对象,计算之间的秒数。LocalDate不能使用。

    Duration d1 = Duration.between(time1, time2);
    Duration d1 = Duration.between(dateTime1, dateTime2);
    Duration d2 = Duration.between(instant1, instant2);

Period类是以年、月或者日的方式对多个时间单位建模。

        Period period = Period.between(localDate1,localDate2);
        System.out.println(period.getYears()); //获取相隔的年份差 0
        System.out.println(period.getMonths()); //获取相隔的月份差 11
        System.out.println(period.getDays()); //获取相隔的日子差 4
image.png

TemporalAdjuster,有时候需要进行一些更加
复杂的操作,比如,将日期调整到下个周日、下个工作日,或者是本月的最后一天,这时,可以使用重载Temporal中的with方法,向其传递一个提供了更多定制化选择的TemporalAdjuster对象,
更加灵活地处理日期。

                LocalDate date = LocalDate.of(2014,3,18);//2014-03-18
        LocalDate with = date.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));//2014-03-23
        LocalDate with1 = date.with(TemporalAdjusters.lastDayOfMonth());//2014-03-31
image.png

DateTimeFormatter,格式化以及解析日期时间对象

LocalDate date = LocalDate.of(2014, 3, 18);
String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE); //20140318
String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);//2014-03-18

LocalDate date1 = LocalDate.parse("20140318",DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date2 = LocalDate.parse("2014-03-18",DateTimeFormatter.ISO_LOCAL_DATE);

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate date1 = LocalDate.of(2014, 3, 18);
String formattedDate = date1.format(formatter);// 18/3/2014
LocalDate date2 = LocalDate.parse(formattedDate, formatter);

ZoneId,是用来处理时区问题的

        ZoneId romeZone = ZoneId.of("Europe/Rome");
        LocalDate date = LocalDate.of(2014, Month.MARCH, 18); 
    ZonedDateTime zdt1 = date.atStartOfDay(romeZone);
        LocalDateTime dateTime = LocalDateTime.of(2014,Month.MARCH, 18,13,25);
        ZonedDateTime zdt3 = dateTime.atZone(romeZone);
        Instant instant = Instant.now();
    ZonedDateTime zdt3 = instant.atZone(romeZone);

下图为理解LocaleDate、
LocalTime、LocalDateTime以及ZoneId之间的差异。

image.png

ZoneOffset,利用当前时间和伦敦格林尼治子午线时间的差异:

ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");
LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45); 
OffsetDateTime dateTimeInNewYork = OffsetDateTime.of(date, newYorkOffset);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351