【Java8新特性 串行/并行流-Optional容器类-时间格式化线程安全等】

在这里插入图片描述

一、并行流与顺序流

1.概念

并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。

java8中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API可以声明性地通过 paralle()sequential()在并行流与顺序流之间进行切换,并行流的底层其实就是Fork/Join框架的一个实现

2.Fork/Join框架

Fork/Join框架:就是在必要的情況下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行Join汇总
在这里插入图片描述
ForkJoin框架采用的是“工作窃取模式”,传统线程在处理任务时,假设有一个大任务被分解成了20个小任务,并由四个线程A,B,C,D处理,理论上来讲一个线程处理5个任务,每个线程的任务都放在一个队列中,当B,C,D的任务都处理完了,而A因为某些原因阻塞在了第二个小任务上,那么B,C,D都需要等待A处理完成,此时A处理完第二个任务后还有三个任务需要处理,可想而知,这样CPU的利用率很低。而ForkJoin采取的模式是,当B,C,D都处理完了,而A还阻塞在第二个任务时,B会从A的任务队列的末尾偷取一个任务过来自己处理,C和D也会从A的任务队列的末尾偷一个任务,这样就相当于B,C,D额外帮A分担了一些任务,提高了CPU的利用率

3. Fork/Join框架代码示例:

首先要编写一个ForkJoin的计算类继承RecursiveTask 并重写 T compute() 方法

/**
 * forkjoin框架使用示例: 利用ForkJoin框架求一个区间段的和
 */
public class ForkJoinTest extends RecursiveTask<Long> {
    private static final long serialVersionUID = -4665674866263069371L;
    //计算的起始值
    private Long start;
    //计算的终止值
    private Long end;
    //做任务拆分时的临界值
    private static final Long THRESHOLD = 100L;

    public ForkJoinTest(Long start, Long end) {
        this.start = start;
        this.end = end;
    }

    /**
     * 计算代码,当计算区间的长度大于临界值时,继续拆分,当小于临界值时,进行计算
     *
     * @return
     */
    @Override
    protected Long compute() {
        Long length = this.end - this.start;
        if (length > THRESHOLD) {
            long middle = (start + end) / 2;
            ForkJoinTest left = new ForkJoinTest(start, middle);
            left.fork();//拆分子任务并加入到线程队列
            ForkJoinTest right = new ForkJoinTest(middle + 1, this.end);
            right.fork();
            return left.join() + right.join();
        } else {
            long sum = 0;
            for (long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        }
    }
}

测试类:

import org.junit.Test;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;

public class TestForkJoin {

    @Test
    public void test1(){
        long start = System.currentTimeMillis();
        //1.ForkJoin框架也需要一个ForkJoin池来启动
        ForkJoinPool pool = new ForkJoinPool();
        //2.创建一个ForkJoinTask,RecursiveTask也是继承自ForkJoinTask,所以我们new自己写的那个计算类
        ForkJoinTask<Long> task = new ForkJoinTest(0L, 1000000L);
        //3.执行计算
        long sum = pool.invoke(task);
        System.out.println(sum);

        long end = System.currentTimeMillis();

        System.out.println("耗费的时间为: " + (end - start)); //5463
    }
    /**
     * 测试用for循环计算
     */
    @Test
    public void test2(){
        long start = System.currentTimeMillis();
        long sum = 0L;
        for (long i = 0L; i <= 1000000L; i++) {
            sum += i;
        }
        System.out.println(sum);
        long end = System.currentTimeMillis();
        System.out.println("耗费的时间为: " + (end - start)); //7610
    }
    /**
     * 测试用并行流计算
     */
    @Test
    public void test3(){
        long start = System.currentTimeMillis();
        Long sum = LongStream.rangeClosed(0L, 1000000L).parallel().sum();
        System.out.println(sum);
        long end = System.currentTimeMillis();
        System.out.println("耗费的时间为: " + (end - start)); //2813
    }
}

这里我就不粘贴打印结果了,因为我上面拆分的数据太小,体现不出并行流的优势,forkjoin拆分合并任务也是需要时间的,对于计算量比较小的任务,拆分合并所花费的时间可能会大于计算时间,这时候用forkjoin拆分任务就会有点得不偿失了,如果你的电脑配置好,你可以测试更大的数据,运行结果更明显。

二、Optional类

1. 什么是Optional对象

Java 8中所谓的Optional对象,即一个容器对象,该对象可以包含一个null或非null值。如果该值不为null,则调用isPresent()方法将返回true,且调用get()方法会返回该值

2. Optional类常用方法

在这里插入图片描述
1.of(T value)方法

就是返回一个包含非空值的Optional对象

源码:

 public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
    }

注意:如果我们给of(null)传参数为null,依然会抛出空指针异常,但是我们不用像以前一样,一步步的Debug去慢慢找抛出异常的代码;现在我们可以直接定位:就是Optional这一行of()参数为null造成的

2.ofNullable(T value)方法
返回一个可以包含空值的Optional对象
源码:

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

3.ifPresent(Consumer<? super T> consumer)方法
当值不为null时,执行consumer
源码:

  public void ifPresent(Consumer<? super T> consumer) {
        if (value != null)
            consumer.accept(value);
    }

4.empty()方法
返回一个Optional实例,里面存放的value是null,源码如下:

 public static<T> Optional<T> empty() {
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }

5.orElse(T other)方法
当值为null时返回传入的值,否则返回原值;
源码:

 public T orElse(T other) {
        return value != null ? value : other;
    }

6.orElseGet(Supplier<? extends T> other)方法

功能与orElse(T other)类似,不过该方法可选值的获取不是通过参数直接获取,而是通过调用传入的Lambda表达式获取
源码:

  public T orElseGet(Supplier<? extends T> other) {
        return value != null ? value : other.get();
    }

7.orElseThrow(Supplier<? extends X> exceptionSupplier)方法
当遇到值为null时,根据传入的Lambda表达式跑出指定异常
源码:

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

8.filter(Predicate<? super T> predicate)方法

过滤符合条件的Optional对象,这里的条件用Lambda表达式来定义,
如果入参predicate对象为null将抛NullPointerException异常,
如果Optional对象的值为null,将直接返回该Optional对象,
如果Optional对象的值符合限定条件(Lambda表达式来定义),返回该值,否则返回空的Optional对象

源码如下:

 public Optional<T> filter(Predicate<? super T> predicate) {
        Objects.requireNonNull(predicate);
        if (!isPresent())
            return this;
        else
            return predicate.test(value) ? this : empty();
    }

使用示例:

package optional;

import java.util.Optional;

public class Snippet
{
    public static void main(String[] args)
    {
        Optional<String> test = Optional.ofNullable("abcD");
        
        //过滤值的长度小于3的Optional对象
        Optional<String> less3 = test.filter((value) -> value.length() < 3);
        //打印结果
        System.out.println(less3.orElse("不符合条件,不打印值!"));
    }
}

9.map(Function<? super T, ? extends U> mapper)方法

前面的filter方法主要用于过滤,一般不会修改Optional里面的值,map方法则一般用于修改该值,并返回修改后的Optional对象
如果入参mapper对象为null将抛NullPointerException异常,
如果Optional对象的值为null,将直接返回该Optional对象,
最后,执行传入的lambda表达式,并返回经lambda表达式操作后的Optional对象

源码:

 public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }

栗子:

package optional;

import java.util.Optional;

public class Snippet
{
    public static void main(String[] args)
    {
        Optional<String> test = Optional.ofNullable("abcD");
        
        //将值修改为大写
        Optional<String> less3 = test.map((value) -> value.toUpperCase());
        //打印结果 ABCD
        System.out.println(less3.orElse("值为null,不打印!"));
    }
}

10.flatMap(Function<? super T, Optional> mapper)方法

flatMap方法与map方法基本一致,唯一的区别是,
如果使用flatMap方法,需要自己在Lambda表达式里将返回值转换成Optional对象,
而使用map方法则不需要这个步骤,因为map方法的源码里已经调用了Optional.ofNullable方法;

源码:

 public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Objects.requireNonNull(mapper.apply(value));
        }
    }

栗子:

public class Snippet
{
    public static void main(String[] args)
    {
        Optional<String> test = Optional.ofNullable("abcD");
        
        //使用flatMap,将值修改为大写
        Optional<String> less3 = test.flatMap((value) -> Optional.ofNullable(value.toUpperCase()));
        //使用map,将值修改为大写
        //Optional<String> less3 = test.map((value) -> value.toUpperCase());
        
        //打印结果 ABCD
        System.out.println(less3.orElse("值为null,不打印!"));
    }
}

最后举个栗子,我们结合实际来了解Optional

栗子: 每个男人心中都有自己的女神,苍老师也罢,波多老师也罢(可能这两位也是搞IT的大佬吧),也有男的没有女神,那我们想看一个男的的女神叫什么名字(可能会抛出空指针异常,这就是我们要解决的)
在这里插入图片描述
创建一个女神类:

//女神
public class Godness {
    String name;

    public Godness() {
    }

    public Godness(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Godness{" +
                "name='" + name + '\'' +
                '}';
    }
}

创建一个男人类:

//男人
public class Man {
    private Godness godness;

    public Man(Godness godness) {
        this.godness = godness;
    }

    public Man() {
    }

    public Godness getGodness() {
        return godness;
    }

    public void setGodness(Godness godness) {
        this.godness = godness;
    }
}

测试:

 //获取一个男人心中的女神
    @Test
    public void test1(){
        Man man = new Man();//空指针异常,因为当前这个男的没女神
        System.out.println(getGodname(man));
    }
    public String getGodname(Man man){
        return man.getGodness().name;
    }

运行上述代码抛出异常,因为我们创建了一个男人对象,但是这个男人没有女神(没有女神参数),所以在我们调用getGodname(Man man)中的man.getGodness().name,前面为null,会null.name空指针异常

所以我们要防止女神为null,我们可以把女神封装成Optional类型,所以改写男人类(Man):

public class newMan {
    private Optional<Godness> godness = Optional.empty();

    public newMan() {
    }
    public newMan(Optional<Godness> godness) {
        this.godness = godness;
    }

    public Optional<Godness> getGodness() {
        return godness;
    }

    public void setGodness(Optional<Godness> godness) {
        this.godness = godness;
    }

    @Override
    public String toString() {
        return "newMan{" +
                "godness=" + godness +
                '}';
    }
}

测试:

 @Test
    public void test2(){
        Optional<Godness> o = Optional.ofNullable(new Godness("白豆腐"));
        Optional<newMan> op = Optional.ofNullable(new newMan());
        System.out.println(getGodnewName(op));

    }
    public String getGodnewName(Optional<newMan> newMan){
        return newMan.orElse(new newMan())//如果传进来的男人为null,那我们就创建一个男的
                .getGodness()//获取这个男人的女神
                .orElse(new Godness("李诗诗"))//如果没有女神,为了防止null.name空指针异常,我们创建一个女神
                .getName();//获取女神名字
    }

注释在代码写的很清楚,所以我们可以看到Optional这个类很方便,解决了我们的空指针异常问题,即使出现空指针异常,我们也可以很快定位到代码

三、接口中的默认方法和静态方法

在Java8之前的版本中,接口中只能声明常量和抽象方法,接口的实现类中必须实现接口中所有的抽象方法。而在Java8中,接口中可以声明默认方法和静态方法

1.默认方法

Java 8中允许接口中包含具有具体实现的方法,该方法称为“默认方法”,默认方法使用 default 关键字修饰

例如,我们可以定义一个接口MyFun,其中,包含有一个默认方法getName,如下所示:

public interface MyFun {
    default String getName(){
        return "默认方法";
    }
}

类优先”的原则

若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时,遵循如下的原则
1.选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略

例如,现在有一个接口为MyFunction,和一个类MyClass,如下所示:

  • MyFunction接口
public interface MyFunction{
    default String getName(){
        return "MyFunction";
    }
}
  • MyClass类
public class MyClass{
    public String getName(){
        return "MyClass";
    }
}

此时,创建SubClass类继承MyClass类,并实现MyFunction接口,如下所示:

public class SubClass extends MyClass implements MyFunction{

}

接下来,我们创建一个SubClassTest类,对SubClass类进行测试,如下所示:

public class SubClassTest{
    @Test
    public void testDefaultFunction(){
        SubClass subClass = new SubClass();
        System.out.println(subClass.getName());
    }
}

运行上述程序,会输出字符串:MyClass

2.接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法), 那么必须覆盖该方法来解决冲突

例如,现在有两个接口,分别为MyFunction和MyInterface,各自都有一个默认方法getName(),如下所示:

  • MyFunction接口
public interface MyFunction{
    
    
    default String getName(){
    
    
        return "function";
    }
}
  • MyInterface接口
public interface MyInterface{
    default String getName(){
        return "interface";
    }
}

实现类MyClass同时实现了MyFunction接口和MyInterface接口,由于MyFunction接口和MyInterface接口中都存在getName()默认方法,所以,MyClass必须覆盖getName()方法来解决冲突,如下所示:

public class MyClass{
    @Override
    public String getName(){
        return MyInterface.super.getName();
    }
}

此时,MyClass类中的getName方法返回的是:interface

如果MyClass中的getName()方法覆盖的是MyFunction接口的getName()方法,如下所示:

public class MyClass{
    @Override
    public String getName(){
        return MyFunction.super.getName();
    }
}

此时,MyClass类中的getName方法返回的是:function

2.静态方法

在Java8中,接口中允许添加静态方法,使用方式接口名.方法名。例如MyFunction接口中定义了静态方法send()

public interface MyFunction{
    default String getName(){
        return "binghe";
    }
    static void send(){
        System.out.println("Send Message...");
    }
}

我们可以直接使用如下方式调用MyFunction接口的send静态方法

MyFunction.send();
3.新时间日期API

Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理

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

  • 非线程安全 java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一
  • 设计很差 Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计
  • 时区处理麻烦日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题

Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:
Local(本地): 简化了日期时间的处理,没有时区的问题
Zoned(时区):通过制定的时区处理日期时间
新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作

本地化日期时间 API
LocalDate/LocalTime 和 LocalDateTime 类可以在处理时区不是必须的情况。代码如下:

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.time.Month;

public class Java8Tester {
   public static void main(String args[]){
      Java8Tester java8tester = new Java8Tester();
      java8tester.testLocalDateTime();
   }
	
   public void testLocalDateTime(){
	
      // 获取当前的日期时间
      LocalDateTime currentTime = LocalDateTime.now();
      System.out.println("当前时间: " + currentTime);
		
      LocalDate date1 = currentTime.toLocalDate();
      System.out.println("date1: " + date1);
		
      Month month = currentTime.getMonth();
      int day = currentTime.getDayOfMonth();
      int seconds = currentTime.getSecond();
		
      System.out.println("月: " + month +", 日: " + day +", 秒: " + seconds);
		
      LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
      System.out.println("date2: " + date2);
		
      // 12 december 2014
      LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
      System.out.println("date3: " + date3);
		
      // 22 小时 15 分钟
      LocalTime date4 = LocalTime.of(22, 15);
      System.out.println("date4: " + date4);
		
      // 解析字符串
      LocalTime date5 = LocalTime.parse("20:15:30");
      System.out.println("date5: " + date5);
   }
}

结果:

当前时间: 2020-11-14T21:21:46.969
date1: 2020-11-14
月: NOVEMBER, 日: 14, 秒: 46
date2: 2012-11-10T21:21:46.969
date3: 2014-12-12
date4: 22:15
date5: 20:15:30

使用时区的日期时间API
如果我们需要考虑到时区,就可以使用时区的日期时间API:

import java.time.ZonedDateTime;
import java.time.ZoneId;

public class Java8Tester {
   public static void main(String args[]){
      Java8Tester java8tester = new Java8Tester();
      java8tester.testZonedDateTime();
   }
	
   public void testZonedDateTime(){
	
      // 获取当前时间日期
      ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
      System.out.println("date1: " + date1);
		
      ZoneId id = ZoneId.of("Europe/Paris");
      System.out.println("ZoneId: " + id);
		
      ZoneId currentZone = ZoneId.systemDefault();
      System.out.println("当期时区: " + currentZone);
   }
}

结果:

$ javac Java8Tester.java 
$ java Java8Tester
date1: 2015-12-03T10:15:30+08:00[Asia/Shanghai]
ZoneId: Europe/Paris
当期时区: Asia/Shanghai

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_44682003/article/details/109667201