java8新特性

java8是java的一个重要的版本,引入了一些新的特性,在实践中,很多老项目都把java8当做一个升级的台阶。对这些新特性的学习,可以很好的使用java8带来的便利,而不是雨我无瓜的版本升级。

近期听了身边前辈的分享,从一个特别的角度进行了分析,在这里进行总结学习,本文主要涉及Lambda表达式、方法引用、Stream、Optional类、日期类五部分。

其实每一个部分的使用都很有讲究,但是具体的实现,可以查看文档性质的网站,这里换个角度进行梳理。

1. lambda表达式

Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中),使用 Lambda 表达式可以使代码变的更加简洁紧凑。

主要针对的场景是只有一个抽象方法待实现的时候,可以使用lambda表达式,也可以使用匿名内部类。以下简单探究了实现导致的不同,具体实现机制有待学习。

首先看一下匿名内部类的实现代码:

public class LambdaTest {
    public static void main(String[] args) {
        MyFunc<String> stringMyFunc = new MyFunc<String>() {
            @Override
            public void print(String x) {
                System.out.println(x);
            }
        };

        stringMyFunc.print("dudu");
    }
    
    interface MyFunc<T>{
        void print(T x);
    }
}

这里使用匿名内部类的方法实例化了一个对象,那么这个LambdaTest编译之后生成几个class文件呢?

在这里插入图片描述

如上图所示,这里生成了一个LambdaTest的class文件,一个内部接口MyFunc的class文件,以及一个接口的实现类class文件。因为是匿名内部类,实现类的名字给了1。

下面是Lambda的实现:

public class LambdaTest {
    public static void main(String[] args) {
        MyFunc<String> stringMyFunc = x -> System.out.println(x);

        stringMyFunc.print("dudu");
    }

    interface MyFunc<T>{
        void print(T x);
    }
}

编译之后生成文件:
在这里插入图片描述

和匿名内部类的实现方式相比,没有是接口实现类生成。

通过javap -p 反汇编器查看实现的方法有哪些:

在这里插入图片描述

lambda情况下,编译器生成了一个lambda$m1$0方法,并且将Lambda表达式的内容搬到了里面。

通过javap -verbose className查看字节码与常量池:

在这里插入图片描述

查看Lambda的#2,这个是jdk7引入的引得操作码,主要是为了支持动态语言的方法调用。

lambda情况下,会多一个BootstrapMethods,指向invokestatic java/lang/invoke/LambdaMetafactory.metafactory...

lambda表达式对应一个incokedynamic 指令,通过指令在常量池的符号引用,可以得到BootstrapMethods 属性表对应的引导方法。在运行时,JVM会通过调用这个引导方法生成一个含有MethodHandle(CallSite的target属性)对象的CallSite作为一个Lambda的回调点。Lambda的表达式信息在JVM中通过字节码生成技术转换成一个内部类,这个内部类被绑定到MethodHandle对象中。每次执行lambda的时候,都会找到表达式对应的回调点CallSite执行。一个CallSite可以被多次执行(在多次调用的时候)。只会有一个invokedynamic指令,在comparator调用comparator.comparecomparator.reversed方法时,都会通过CallSite找到其内部的MethodHandle,并通过MethodHandle调用Lambda的内部表示形式LambdaForm

使用lambda,虽然在运行时需要转化Lambda Form,并且生成CallSite,但是随着调用点被频繁调用,通过JIT编译优化等,性能会有明显提升。

写到这里,发现具体细节还有待学习,涉及JVM的一些东西,朋友也只是简单讲述,未涉及更深,关于lambda的实现机制,之后再深究一下。

2. 方法引用

什么是方法引用

通过方法的名字来指向一个方法, 使用一对冒号 :: ,直接访问类或者实例的已经存在的方法或者构造方法。

方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。

适用场景:

用Lambda表达式来实现匿名方法时,有时候用Lambda表达式仅仅是调用已有方法,没有其他任何多余的动作,在这种情况下,可以通过方法名来调用它,而方法引用可以帮助我们实现这一要求,它使得Lambda在调用那些已经拥有方法名的方法的代码更简洁、更容易理解。

类型语法对应lambda
静态方法引用类名::静态方法名(args)->类名.静态方法名(args)
实例方法引用实例名::成员方法名(args)->实例名.成员方法名(args)
特定对象方法引用类名::成员方法名(特定对象实例,args)->类名.成员方法名(args)
构建方法引用类名::new(args)->new 类名(args)

以上情景也可以总结为以下三种形式:

  1. 方法引用所引用的方法的参数列表与返回值类型,和函数式接口中的抽象方法的参数列表和返回值类型保持一致。(对应一般使用场景)
  2. lambda的参数列表的第一个参数,是实例方法的调用者,第二个参数(或者无参)是实例方法的参数。(对应特定对象的方法引用)
  3. 构造器引用,类名::new。

3. stream

什么是 Stream?

Stream(流)是一个来自数据源的元素队列并支持聚合操作

  • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
  • 数据源 流的来源,可以是集合,数组,I/O channel, 产生器generator 等。
  • 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

和以前的Collection操作不同, Stream操作还有两个基础的特征:

①Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。

②内部迭代:以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

整个流程可以简单的概括为:

源集合–> 转化为流 --> 在管道中进行一系列中间操作 -->终止操作(调用器)–>新集合。

List<String> sortedList = initList.stream().sorted().collect(Collectors.toList());

中间操作:

forEach 用于迭代,limit获取指定数量的流,

list.stream().limit(10).forEach(System.out::println);

map用于映射每个元素到对应的结果,

squaresList = list.stream().map( i -> i*i).distinct().collect(Collectors.toList())

filter通过设置条件过滤元素,

int count = strings.stream().filter(str -> str.isEmpty()).count();

sorted用于对流进行排序,

List<String> sortedList = initList.stream().sorted().collect(Collectors.toList());

parallelStream并行流

long count = strings.parallelStream().filter(string -> string.isEmpty()).count();

Collectors类提供了很多归约操作,将流转化成集合和聚合元素可用于返回列表或字符串,

String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));

一些统计结果的收集器

IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
stats.getMax();
stats.getMin();
stats.getSum();
stats.getAverage();

Java8集合新增方法

Iterable#forEach(Consumer<?super T> action) 遍历集合

//遍历的三种写法
for(int i = 0; i<list.size(); i++){
	System.out.println(list.get(i));
}

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

list.forEach(s->System.out.println(s));

Collection#removeIf(Predicate<? super E> filter) 移除符合条件的集合元素

// 删除的两种写法
Iterator<String> iterator = list.iterator();
while(iterator.hasNext){
	String str = iterator.next();
	if(Objects.equals("dudu",str)){
		iterator.remove();
	}
}

list.removeIf(s->Objects.equals("dudu",s));

List#void replaceAll(UnaryOperator operator) 替换所有集合元素的值

List<String> newList = new ArrayList<>(list.size());
for(String s: list){
	newList.add(s+"0");
}

list.replaceAll(s->s+"0");

List#sort(Comparator<? super E> c) 集合元素排序

Collection.sort(list);

list.sort(Comparator.naturalOrder());

4.Optional

这样一个类主要为了解决NPE空指针异常问题。

在不使用Optional的时候,为了保证不出现NPE,需要对每一次获取的值进行判空:

if (user != null) {
    Manager manager = user.getManager();
    if (manager != null) {
        Group group = manager.getGroup();
        if (group != null) {
            String groupId = group.getId();
            if (groupId != null) {
                groupId = groupId.toUpperCase();
            }
        }
    }
}

将对象放入Optional容器

类型方法名说明
static Optionalof(T value)返回具有Optional的当前非空的Optional。如果value传入为null,则抛出NPE。
static OptionalofNullable(T value)返回一个可以为空的指定值的Optional。

查看一下ofNullable(T value)的源码:

public static <T> Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value);
}

其中,empty()会执行new Optional<>()
of(value) 会调用到new Optional<>(value)

Optional是为了简化流程的,如果一个对象的简单判断,可以直接使用Objects.nonNull()等方法

if(Objects.nonNull(str)){
	//do something
}

//回到一开始的例子,使用optional加上方法引用
Optional.ofNullable(user)		//Optional<User>
    	.map(User::getManager)	//Optional<Manager>
    	.map(Manager::getGroup)	//Optional<Group>
    	.map(Group::getId)		//Optional<String>
    	.ifPresent(String::toUpperCase);
    	

代替三目运算符:

User newUser = Objects.isNull(user)? new User() :user;

User newUser1 = Optional.ofNullable(user).orElse(new User());

代替判空抛异常:

if(Objects.isNull(user)){
	throw new Exception();
}

User newUser2 = Optional.ofNullable(user).orElseThrow(Exception::new);

其中orElseThrow源码:

public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    if (value != null) {
        return value;
    } else {
        throw exceptionSupplier.get();
    }
}

5. 新增时间包java.time

传统日期类的缺点:

  1. java.util.Date 是可变的,线程不安全的;
  2. 不支持时区,国际化处理困难;
  3. 如果需要进行日期运算,Date需要配合日历类Calendar使用;

新的时间API

  1. java.time 包下所有时间的实现类均不可变,是线程安全的;
  2. 设计合理,细化了时间类型:Clock、Instant、Year、Month…;
  3. 支持时区,以LocalZoned作为前缀,区分是否需要考虑时区的影响;
  4. LocalDateTime支持从毫秒级到年月级的各类方法(加减、比较、获取等)。

在阿里的开发规范里规定了新旧版本的对应关系:

Instant代替Date,LocalDateTime代替calendar,DateTimeFormatter代替SimpleDateFormat。
在这里插入图片描述

5.1LocalDate

LocalDate是一个不可变的日期时间对象,线程安全的,表示日期,通常被视为年月日,也可以访问其他日期字段(日期、星期等),不存储或者表示时间和时区。

获取当前日期:

LocalDate localDate = LocalDate.now();
System.out.println(localDate);	//2020-11-29

获取指定日期 & 日期的比较:

LocalDate localDate1 = LocalDate.of(2020,11,29);
System.out.println(localDate1.equals(localDate));	//true

此外还有一些方法:

// 获得所在月的第几天
localDate.getDayOfMonth()
// 获得星期几 (英文全称)
localDate.getDayOfWeek()
// 获得所在年的第几天
    
localDate.getDayOfYear()
// 获取所在月份 英文全称
localDate.getMonth()
// 获取所在月份 数值
localDate.getMonthValue()
// 获取所在月份有多少天
localDate.lengthOfMonth()
    
// 获取当前日期所在年有多少天
localDate.lengthOfYear()
// 是否是闰年
localDate.isLeapYear()

// 一些日期运算
localDate.minusDays(long days)
localDate.minusWeeks(long weeks)
localDate.minusMonths(long months)
localDate.minusYears(long years)
localDate.plusDays(long days)
localDate.plusWeeks(long weeks)
localDate.plusMonths(long months)
localDate.plusYears(long years)

5.2LocalTime

LocalTime是一个不可变的日期时间对象,线程安全的,表示时间,通常被视为小时 秒,时间表示为纳秒精度,不存储或者表示时间和时区。

// now()
LocalTime localTime = LocalTime.now();
System.out.println(localTime);	//00:24:32.963

// of()
localTime = LocalTime.of(00,24);
System.out.println(localTime);
LocalTime localTime = LocalTime.of(00,24,59);
System.out.println(localTime);


// 获取细粒度时间
System.out.println(localTime.getHour());
System.out.println(localTime.getMinute());
System.out.println(localTime.getSecond());

// 将参数替换对应属性 重新计算
System.out.println(localTime.withHour(1));
System.out.println(localTime.withMinute(2));
System.out.println(localTime.withSecond(3));

// 运算
LocalTime localTime = LocalTime.of(12,35,59);
System.out.println(localTime.minusHours(1));
System.out.println(localTime.minusMinutes(1));
System.out.println(localTime.minusSeconds(10));
System.out.println(localTime.plusHours(1));
System.out.println(localTime.plusMinutes(1));
System.out.println(localTime.plusSeconds(10));

LocalDate提供了atTime方法,用来表示日期 + 时间(支持LocalTime的构造方法)

LocalTime提供了atDate方法,用来表示日期 + 时间(支持LocalDate的构造方法)

5.3LocalDateTime

它有两个成员变量,上述的两个对象,LocalDate和LocalDateTime,故其包含二者的属性。

5.2最后提到的 atTime和atDate方法的返回值都是LocalDateTime类 ,其toString方法重写了:

@Override
public String toString() {
    return date.toString() + 'T' + time.toString();
}

5.4日期格式转化DateTimeFormatter

LocalDate和LocalTime都提供了格式转换的方法,format和parse

LocalDate localDate = LocalDate.of(2020,11,29);
LocalTime localTime = LocalTime.now();

DateTimeFormatter f1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println(localDate.atTime(localTime).format(f1));

若LocalDate未调用atTime方法,则表示LocalDate只表示日期,则此时不能转换带有时间格式的表示式

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println(LocalTime.now().atDate(LocalDate.now()).format(formatter));

若LocalTime未调用atDate方法,则表示LocalTime只表示时间,则此时不能转换带有日期格式的表示式

字符串转日期格式:

System.out.println(LocalDate.parse("2019-10-15"));
System.out.println(LocalTime.parse("22:35:40"));

5.5Instant

时间戳类,调用now()获得当前时间是没有问题的,在重写toString()的时候默认时区是UTC时区,在输出的时候,需要调整时区。

Instant instant = Instant.now();
System.out.println(instant);	// 2020-11-28T17:00:51.128Z

// 调整时区
Instant LocalInstant = Instant.now().plusMillis(TimeUnit.HOURS.toMillis(8));
System.out.println(LocalInstant);	// 2020-11-29T01:00:51.182Z

5.6Duration

时间差类,代表一段持续到时间

Duration timeDiff = Duration.between(LocalDateTime.now(),LocalDateTime.now().plusHours(6));
System.out.println(timeDiff.toHours());	//6
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值