JDK1.8 新的特性

一 Lambda 使用

        Lambda表达式应用场景:任何有函数式接口的地方,只有一个抽象方法(Object类中的方法除外)的接口是函数式接口。就像Runnable接口中,只有一个run方法。

  • 1、Runnable
    	//在JDK1.8之前的写法
        new Thread(new Runnable() {
            public void run() {
                System.out.println("The runable  is using!");
            }
       }).start();
    
    	//在JDK1.8之后可以使用lambda表达式
    	new Thread(() -> System.out.println("It's a lambda function")).start();
    
  •  2、排序

对集合进行排序

 

        List<String> list = Arrays.asList("java","javascript","phpa","python");
        //在JDK1.8之前的写法
        Collections.sort(list, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.length()-o2.length();
            }
        });

        //在JDK1.8之后可以使用lambda表达式
        Collections.sort(list,(a,b)->a.length()-b.length());
  • 3.遍历

遍历集合中的元素 

        List<String> languages = Arrays.asList("Java","Python","C++");
        //在JDK1.8之前的写法
        for(String language:languages) {
            System.out.println(language);
        }
        //在JDK1.8之后可以使用lambda表达式
        languages.forEach(x -> System.out.println(x));

二 默认方法

  • 1.接口中的默认方法和静态方法

jdk1.8接口中允许包含具有具体实现的方法,该方法成为“默认方法”。默认方法使用defaut关键字修饰。

接口示例:
public interface IHello {
    void sayHi();
    //接口中的static方法
    static void sayHello(){
        System.out.println("static method: say hello");
    }
    //接口中的default方法
    default void sayByebye(){
        System.out.println("default mehtod: say byebye");
    }
}
实现类示例:

public class HelloImpl implements IHello {
    @Override
    public void sayHi() {
        System.out.println("normal method: say hi");
    }
}
  • 默认方法的原则

 默认具有“类优先”的原则,若一个接口中定义了一个默认的方法,而另一个父类或接口中又定义了一个同名的方法时,遵循如下的原则

  • 1.选择父类中的方法---如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略
示例:
public interface MyFunction{
  	default String getName(){
    	return "MyFunction";
 	}
}

public class MyClass{
  	public String getName(){
    	return "MyClass";
 	}
}

//创建SubClass类继承MyClass类,并实现MyFunction接口
public class SubClass extends MyClass implements MyFunction{
}
创建示例:

public class SubClassTest{
  	@Test
  	public void testDefaultFunction(){
    	SubClass subClass = new SubClass();
    	System.out.println(subClass.getName());  //MyClass
 	}
}
测试示例:
public class SubClassTest{
  	@Test
  	public void testDefaultFunction(){
    	SubClass subClass = new SubClass();
    	System.out.println(subClass.getName());  //MyClass
 	}
}
  • 2.接口冲突,如果一个父类接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法,那么必须覆盖来解决冲突。
public interface MyFunction{
  	default String getName(){
    	return "function";
 	}
}

public interface MyInterface{
  	default String getName(){
    	return "interface";
 	}
}

实现类MyClass同时实现MyFunction接口和MyInterface接口,里面同样的方法冲突,所以,MyClass必须覆盖getName的方法来解决冲突

//示例1
public class MyClass{
	//方法返回的是:interface
  	@Override
  	public String getName(){
    	return MyInterface.super.getName();
 	}
}

//示例2
public class MyClass{
	//方法返回的是:function
  	@Override
  	public String getName(){
    	return MyFunction.super.getName();
 	}
}

三、Stream*

       Stream是Java8中处理集合的关键抽象概念,它可以 指定你希望对集合进行的操作,可以执行非常复杂的查找,过滤和映射数据等操作。简而言之,Stream API提供了一种高效且易于使用的处理数据的方式。

  • Stream的特性:

1.不是数据结构,不会存储元素;
2.不支持索引访问;
3.惰性求值/延迟执行,流在中间处理过程中,只是对操作进行了记录,并不会立即执行,需要等到执行终止操作的时候才会进行实际的计算;
4.支持并行;
5.很容易生成数组或集合(List,Set);
6.支持过滤,查找,转换,汇总,聚合等操作;
7.不会修改原来的数据源,它会将操作后的数据保存到另外一个对象中(保留意见:毕竟peek方法可以修改流中元素)

  • Stream的创建*

 

获取Stream(常用)
  在JDK1.8中, 集合接口有两个方法来生成流:
   1)stream() ,为集合创建串行流;
   2)parallelStream(),为集合创建并行流。stream()方法更常用一些。
   所有的Collection集合都可以通过stream默认方法获取流。Collection接口中加入了default方法stream用来获取流,所以其所有实现类均可获取流

	//List集合获取流
	List<String> list = new ArrayList<>();
	Stream<String> stream1 = list.stream();
	Stream<String> stream2 = list.parallelStream();
	//Set集合获取流
	Set<String> set = new HashSet<>();
	Stream<String> stream3 = set.stream();
	Stream<String> stream4 = set.parallelStream();

Map接口不是Collection的子接口,且其K-V数据结构不符合元素的单一特性,所以获取对应的流需要分key,value或者entry等情况

	Map<String, String> map = new HashMap<>();
	Stream<String> keyStream = map.keySet().stream();
	Stream<String> valueStream = map.values().stream();
	Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();

通过Arrays中静态方法stream()获取数组流

	//获取数组流的重载方法
	public static <T> Stream<T> stream(T[] array)
	public static Stream stream(T[] array)
	public static IntStream stream(int[] array)
	public static LongStream stream(long[] array)
	public static DoubleStream stream(double[] array)
	//示例
	Integer[] nums = new Integer[]{1,2,3,4,5,6,7,8,9};
	Stream<Integer> numStream = Arrays.stream(nums);

通过Stream类静态方法of()获取数组流,可以使用静态方法Stream.of()通过显示值创建一个流,它可以接收任意数量的参数。

Stream类中,提供了两个of()方法,一个只需要传入一个泛型参数,一个需要传入一个可变泛型参数

示例:

	String[] array = { "张三", "李四", "王五", "赵六" };
	Stream<String> stream = Stream.of(array);
	//of方法的参数其实是一个可变参数,所以支持数组
	Stream<String> stream1 = Stream.of(1, 2, 3, 4, 5);

Stream的中间操作

 中间操作在整体上可以分为:筛选与切片、映射、排序

 1.filter

功能:用于接收Lambda表达式,从六种排除某些元素。

Stream<Person> stream = list.stream().filter((e) -> {
  	System.out.println("Stream API 中间操作");
  	//过滤出年龄大于30的数据
  	return e.getAge() > 30;
});

2.limit

	//功能:截断流,使其元素不超过给定数量。方法定义:
	Stream<T> limit(long maxSize);
	//过滤之后取2个值
	list.stream().filter((e) -> e.getAge() >30 )
		.limit(2).forEach(System.out ::println);

3.skip

	//跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流
	//方法定义:
	Stream<T> skip(long n);
	//跳过前2个值
	list.stream().skip(2).forEach(System.out :: println);

4.distinct--需要重写hashCode() 和equals()方法才可以使用

	//筛选,通过流所生成元素的 hashCode() 和 equals() 去 除重复元素。
	//方法定义:
	Stream<T> distinct();
	//示例
	list.stream().distinct().forEach(System.out :: println);

排序*

	// 自然排序
	Arrays.asList(1,2,3,4,5,6)
		.stream()
		.sorted((a,b)->b-a)
		.forEach(System.out::println);
结果:
6
5
4
3
2
1

定制排序:

	//定制排序
	List<Employee> persons1 = list.stream().sorted((e1, e2) -> {
	  	if (e1.getAge() == e2.getAge()) {
	    	return 0;
	 	} else if (e1.getAge() > e2.getAge()) {
	    	return 1;
	 	} else {
	    	return -1;
	 	}
	}).collect(Collectors.toList());

查找与匹配*

allMatch:表示检查是否匹配所有元素

只有所有的元素都匹配条件时, allMatch()方法才会返回true

	boolean match = employees.stream()
		.allMatch((e) -> Employee.Stauts.SLEEPING.equals(e.getStauts()));
	System.out.println(match);

anyMatch:表示检查是否至少匹配一个元素。只要有任意一个元素符合条件,anyMatch()方法就会返回true

	boolean match = employees.stream()
		.anyMatch((e) -> Employee.Stauts.SLEEPING.equals(e.getStauts()));
	System.out.println(match);

noneMatch:表示检查是否没有匹配所有元素

	boolean match = employees.stream()
		.noneMatch((e) -> Employee.Stauts.SLEEPING.equals(e.getStauts()));
	System.out.println(match);

findFirst:表示返回第一个元素

	Optional<Employee> op = employees.stream()
		.sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
		.findFirst();
	System.out.println(op.get());

findAny :返回当前流中任意元素

	Optional<Employee> op = employees.stream()
		.filter((e) -> Employee.Stauts.WORKING.equals(e.getStauts()))
		.findFirst();
	System.out.println(op.get());

count:返回流中元素总数

	long count = employees.stream().count();
	System.out.println(count);

max:返回流中最大值

示例:

Optional<Employee> op = employees.stream()
		.max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(op.get());

min:返回流中最小值

	Optional<Double> op = employees.stream()
		.map(Employee::getSalary)
		.min(Double::compare);
	System.out.println(op.get());

forEach:表示内部迭代

	employees.stream().forEach(System.out::println);

规约

reduce方法的功能,简单来说就是根据一定的规则将stream中的元素进行计算后返回一个唯一的值。

	Integer reduce = Arrays.asList(1, 2, 3, 4, 5)
		.stream()
		.reduce((a, b) -> a + b)
		.get();
	System.out.print(reduce);   //15

收集

Collect接口中方法的实现决定了如何对流执行收集操作,(List,Set,Map)Collectors 实用类提供了很多静态方法,可以方便创建常见收集器实例。

 

 

	//将过滤后的元素存储到List
	List<Integer> collect = Arrays.asList(1, 2, 3, 4, 5)
		.stream()
		.filter(x -> x % 2 == 0)
		.collect(Collectors.toList());
	System.out.print(collect.toString()); //[2, 4]

新日期类

在旧版的Java中,日期时间API存在问题,其中:

非线程安全:java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一

时区处理麻烦:日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但这些类也有线程安全等问题。

LocalDate、 LocalTime、 LocalDateTime 类的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。 ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法。

具体的示例

	// 获取当前系统时间
	LocalDateTime localDateTime1 = LocalDateTime.now();
	// 2019-10-27T13:49:09.483
	System.out.println(localDateTime1);

	// 指定日期时间
	LocalDateTime localDateTime2 = LocalDateTime.of(2019, 10, 27, 13, 45,10);
	System.out.println(localDateTime2);
	// 2019-10-27T13:45:10
	LocalDateTime localDateTime3 = localDateTime1
	    // 加三年
	   .plusYears(3)
	    // 减三个月
	   .minusMonths(3);
	// 2022-07-27T13:49:09.483
	System.out.println(localDateTime3);
	// 2019
	System.out.println(localDateTime1.getYear());  
	// 10  
	System.out.println(localDateTime1.getMonthValue());
	// 27 
	System.out.println(localDateTime1.getDayOfMonth()); 
	// 13
	System.out.println(localDateTime1.getHour());
	// 52    
	System.out.println(localDateTime1.getMinute());  
	// 6
	System.out.println(localDateTime1.getSecond());   
	LocalDateTime localDateTime4 = LocalDateTime.now();
	// 2019-10-27T14:19:56.884
	System.out.println(localDateTime4);   
	LocalDateTime localDateTime5 = localDateTime4.withDayOfMonth(10);
	// 2019-10-10T14:19:56.884
	System.out.println(localDateTime5);   

 Instant:用于“时间戳”的运算,它是以Unix元年开始所经历的描述进行运算的

	// 默认获取UTC时区
	Instant instant1 = Instant.now();
	// 2019-10-27T05:59:58.221Z  
	System.out.println(instant1);
	
	// 偏移量运算
	OffsetDateTime offsetDateTime = instant1.atOffset(ZoneOffset.ofHours(8));
	// 2019-10-27T13:59:58.221+08:00
	System.out.println(offsetDateTime);
	
	// 获取时间戳
	// 1572156145000
	System.out.println(instant1.toEpochMilli());
	
	// 以Unix元年为起点,进行偏移量运算
	Instant instant2 = Instant.ofEpochSecond(60);
	// 1970-01-01T00:01:00Z
	System.out.println(instant2);
Duration和Period

Duration是用于计算两个“时间”间隔,Period是用于计算两个“日期”间隔

	Instant instant_1 = Instant.now();
	try {
	  	Thread.sleep(1000);
	} catch (InterruptedException e) {
	 	e.printStackTrace();
	}
	Instant instant_2 = Instant.now();
	Duration duration = Duration.between(instant_1, instant_2);
	System.out.println(duration.toMillis());
	// 1000
	LocalTime localTime_1 = LocalTime.now();
	try {
	  	Thread.sleep(1000);
	} catch (InterruptedException e) {
	  	e.printStackTrace();
	}
	LocalTime localTime_2 = LocalTime.now();
	// 1000
	System.out.println(Duration.between(localTime_1, localTime_2).toMillis());

	LocalDate localDate_1 = LocalDate.of(2018,9, 9);
	LocalDate localDate_2 = LocalDate.now();
	Period period = Period.between(localDate_1, localDate_2);
	// 1
	System.out.println(period.getYears());
	// 1    
	System.out.println(period.getMonths());   
	// 18
	System.out.println(period.getDays());    

时区

Java8 中加入了对时区的支持,带时区的时间分别为:ZonedDate、 ZonedTime、ZonedDateTime。其中每个时区都对应着 ID,地区ID都为 “{区域}/{城市}”的格式,例如 : Asia/Shanghai 等。

// 通过时区构建LocalDateTime
LocalDateTime localDateTime1 =
LocalDateTime.now(ZoneId.of("America/El_Salvador"));
// 2019-10-27T00:46:21.268
System.out.println(localDateTime1);

// 以时区格式显示时间
LocalDateTime localDateTime2 = LocalDateTime.now();
ZonedDateTime zonedDateTime1 =
localDateTime2.atZone(ZoneId.of("Africa/Nairobi"));
// 2019-10-27T14:46:21.273+03:00[Africa/Nairobi]
System.out.println(zonedDateTime1);

Optional*

在开发过程中,NullPointerException可谓是随时随处可见,为了避免空指针异常,常常需要进行一些防御式的检查,所以在代码中常常可见if (null != obj)这样的判断。

  在JDK1.8中,Java提供了一个Optional类,Optional类能让我们省掉繁琐的非空的判断。Optional 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
  Optional类中的方法:

  • of

 

    //创建一个值为one的String类型的Optional
    Optional<String> ofOptional = Optional.of("one");
    //如果我们用of方法创建Optional对象时,所传入的值为null,则抛出NullPointerException
    Optional<String> nullOptional = Optional.of(null);
异常信息:
Exception in thread "main" java.lang.NullPointerException
	at java.util.Objects.requireNonNull(Objects.java:203)
	at java.util.Optional.<init>(Optional.java:96)
	at java.util.Optional.of(Optional.java:108)
	at com.test.TestDemo.main(TestDemo.java:25)
  • ofNullable

 

    //为指定的值创建Optional对象,不管所传入的值为null不为null,创建的时候都不会报错
    Optional<String> nullOptional = Optional.ofNullable(null);
    Optional<String> nullOptional2 = Optional.ofNullable("two");

empty

    //创建一个空的String类型的Optional对象
    Optional<String> emptyOptional = Optional.empty();
isPresent
可以使用这个isPresent()方法检查一个Optional对象中是否有值,只有值非空才返回true

	//在Java8之前,我们一般使用如下方式来检查空值
	if(name != null){
	  System.out.println(name.length);
	}
	//在Java8中,我们就可以使用如下方式来检查空值
	Optional<String> opt = Optional.of("asd");
	opt.ifPresent(name -> System.out.println(name.length()));
orElse和orElseGet*

 orElse()方法用来返回Optional对象中的默认值,它被传入一个“默认参数‘。如果对象中存在一个值,则返回它,否则返回传入的“默认参数”。

    Optional<String> stringOptional = Optional.of("four");
    //four
    System.out.println(stringOptional.orElse("five"));

    Optional<String> emptyOptional = Optional.empty();
    //six
    System.out.println(emptyOptional.orElse("six"));

如果创建的Optional中有值存在,则返回此值,否则返回一个由Supplier接口生成的值。orElseGet与orElse()方法类似,但是这个函数不接收一个“默认参数”,而是一个函数接口

    Optional<String> stringOptional = Optional.of("seven");
    //seven
    System.out.println(stringOptional.orElseGet(() -> "eight"));

    Optional<String> emptyOptional = Optional.empty();
    //nine
    System.out.println(emptyOptional.orElseGet(() -> "nine"));
orElseThrow:

  如果创建的Optional中有值存在,则返回此值,否则抛出一个由指定的Supplier接口生成的异常:

public class TestDemo {

    public static void main(String[] args) throws InterruptedException {

    	Optional<String> stringOptional = Optional.of("张三");
    	System.out.println(stringOptional.orElseThrow(CustomException::new));

    	Optional<String> emptyOptional = Optional.empty();
    	System.out.println(emptyOptional.orElseThrow(CustomException::new));

    }
}

class CustomException extends RuntimeException {
	private static final long serialVersionUID = -4399699891687593264L;

	public CustomException() {
	   super("自定义异常");
	}

	public CustomException(String message) {
	    super(message);
	}
}

结果:
张三
Exception in thread "main" com.test.CustomException: 自定义异常
	at java.util.Optional.orElseThrow(Optional.java:290)
	at com.test.TestDemo.main(TestDemo.java:26)
map:如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()。
flagMap:如果创建的Optional中的值存在,就对该值执行提供的Function函数调用,返回一个Optional类型的值,否则就返回一个空的Optional对象。
flatMap与map(Funtion)方法类似,区别在于flatMap中的mapper返回值必须是Optional,map方法的mapping函数返回值可以是任何类型T。调用结束时,flatMap不会对结果用Optional封装。
    //map方法中的lambda表达式返回值可以是任意类型,在map函数返回之前会包装为Optional。 
    //但flatMap方法中的lambda表达式返回值必须是Optionl实例
    Optional<String> stringOptional = Optional.of("zhangsan");
    //lisi
    System.out.println(stringOptional.flatMap(e -> Optional.of("lisi")).orElse("失败"));

    stringOptional = Optional.empty();
    //失败
    System.out.println(stringOptional.flatMap(e -> Optional.empty()).orElse("失败"));
使用Optional处理空指针

 

1、对于Integer的判空
  可以使用Optional.ofNullable来构造一个Optional,然后使用orElse(0)把null 替换为默认值再进行+1操作。
2、对于 String 和字面量的比较
  可以把字面量放在前面,比如"OK".equals(s),这样即使s是null也不会出现空指针异常;而对于两个可能为null的字符串变量的equals比较,可以使用Objects.equal,它会做判空处理。
3、对于 ConcurrentHashMap,既然其Key和Value都不支持null,修复方式就是不要把null存进去
  HashMap的Key和Value可以存入null,而ConcurrentHashMap看似是HashMap的线程安全版本,却不支持null值的Key和Value,这是容易产生误区的一个地方。
4、对于类似fooService.getBarService().bar().equals(“OK”)的级联调用
  需要判空的地方有很多,包括fooService、getBarService()方法的返回值,以及bar方法返回的字符串。如果使用if-else来判空的话可能需要好几行代码,但使用Optional的话一行代码就够了。
5、对于返回的List
  由于不能确认其是否为null,所以在调用size方法获得列表大小之前,同样可以使用Optional.ofNullable包装一下返回值,然后通过orElse(Collections.emptyList())实现在List为null的时候获得一个空的List,最后再调用size方法。

	private List<String> rightMethod(FooService fooService, Integer i, String s, String t) {
	    log.info("result {} {} {} {}", Optional.ofNullable(i).orElse(0) + 1, "OK".equals(s), Objects.equals(s, t), new HashMap<String, String>().put(null, null));
	    Optional.ofNullable(fooService)
	            .map(FooService::getBarService)
	            .filter(barService -> "OK".equals(barService.bar()))
	            .ifPresent(result -> log.info("OK"));
	    return new ArrayList<>();
	}
	
	@GetMapping("right")
	public int right(@RequestParam(value = "test", defaultValue = "1111") String test) {
	    return Optional.ofNullable(rightMethod(test.charAt(0) == '1' ? null : new FooService(),
	            test.charAt(1) == '1' ? null : 1,
	            test.charAt(2) == '1' ? null : "OK",
	            test.charAt(3) == '1' ? null : "OK"))
	            .orElse(Collections.emptyList()).size();
	}

使用判空方式或Optional方式来避免出现空指针异常,不一定是解决问题的最好方式,空指针没出现可能隐藏了更深的Bug

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值