JDK1.8整理笔记

一、引言

jdk1.8已经出来好久了,目前13也已经出来了,但是很多公司(我们公司也一样)不太愿意升级到高版本的jdk,主要是有老的项目要维护,还有升级的话配套的框架也要升级,要考虑的细节事情太多。

二、JDK1.8新特性

本文主要介绍了JDK1.8版本中的一些新特性,是个人去学习过程中的记录,仅供参考!如有改进的希望各位建议!感谢!

在jdk1.8中对hashMap等map集合的数据结构优化。hashMap数据结构的优化

原来的hashMap采用的数据结构是哈希表(数组+链表),hashMap默认大小是16,一个0-15索引的数组,如何往里面存储元素,首先调用元素的hashcode 方法,计算出哈希码值,经过哈希算法算成数组的索引值,如果对应的索引处没有元素,直接存放,如果有对象在,那么比较它们的equals方法比较内容 ,如果内容一样,后一个value会将前一个value的值覆盖,如果不一样,在1.7的时候,后加的放在前面,形成一个链表,形成了碰撞,在某些情况下如果链表 无限下去,那么效率极低,碰撞是避免不了的 
加载因子:0.75,数组扩容,达到总容量的75%,就进行扩容,但是无法避免碰撞的情况发生 在1.8之后,在数组+链表+红黑树来实现hashmap,当碰撞的元素个数大于8时 & 总容量大于64,会有红黑树的引入 
除了添加之后,效率都比链表高,1.8之后链表新进元素加到末尾 
ConcurrentHashMap (锁分段机制),concurrentLevel,jdk1.8采用CAS算法(无锁算法,不再使用锁分段),数组+链表中也引入了红黑树的使用

1.default关键字:

在java里面,我们通常都是认为接口里面是只能有抽象方法,不能有任何方法的实现的,那么在jdk1.8里面打破了这个规定,引入了新的关键字default,通过使用default修饰方法,可以让我们在接口里面定义具体的方法实现,如下。

public interface NewCharacter {
    public void test1();
    public default void test2(){
        System.out.println("JDK新特性引入default关键字 ");
    }
}

此时定义这样一个方法有什么作用呢?为何不去该接口的实现类中去实现这个方法呢?
其实这么定义一个方法的主要意义是定义一个默认方法,也就是说这个接口的实现类实现了这个接口之后,不用管这个default修饰的方法,也可以直接调用,如下。

public class NewCharacterImpl implements NewCharacter{

    @Override
    public void test1() {      
    }
    public static void main(String[] args) {
        NewCharacter nca = new NewCharacterImpl();
        nca.test2();
    }
}

所以说这个default方法是所有的实现类都不需要去实现的就可以直接调用,那么比如说jdk的集合List里面增加了一个sort方法,那么如果定义为一个抽象方法,其所有的实现类如arrayList,LinkedList等都需要对其添加实现,那么现在用default定义一个默认的方法之后,其实现类可以直接使用这个方法了,这样不管是开发还是维护项目,都会大大简化代码量。

2.lambda 表达式

lambda表达式本质上是一段匿名内部类,也可以是一段可以传递的代码

Lambda表达式是jdk1.8里面的一个重要的更新,这意味着java也开始承认了函数式编程,并且尝试引入其中。

首先,什么是函数式编程,引用廖雪峰先生的教程里面的解释就是说:函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
  简单的来说就是,函数也是一等公民了,在java里面一等公民有变量,对象,那么函数式编程语言里面函数也可以跟变量,对象一样使用了,也就是说函数既可以作为参数,也可以作为返回值了,看一下下面这个例子。

//这是常规的Collections的排序的写法,需要对接口方法重写
        public void test1(){
        List<String> list =Arrays.asList("aaa","fsa","ser","eere");
        Collections.sort(list, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o2.compareTo(o1);
            }
        });
        for (String string : list) {
            System.out.println(string);
        }
    }
//这是带参数类型的Lambda的写法
        public void testLamda1(){
        List<String> list =Arrays.asList("aaa","fsa","ser","eere");
        Collections.sort(list, (Comparator<? super String>) (String a,String b)->{
            return b.compareTo(a);
        }
        );
        for (String string : list) {
            System.out.println(string);
        }
    }
//这是不带参数的lambda的写法
        public void testLamda2(){
        List<String> list =Arrays.asList("aaa","fsa","ser","eere");
        Collections.sort(list, (a,b)->b.compareTo(a)
        );
        for (String string : list) {
            System.out.println(string);
        }

可以看到不带参数的写法一句话就搞定了排序的问题,所以引入lambda表达式的一个最直观的作用就是大大的简化了代码的开发,像其他一些编程语言Scala,Python等都是支持函数式的写法的。

引入这样一个场景设计,在商城浏览商品信息时,经常会有条件的进行筛选浏览,例如要选颜色为红色的、价格小于8k的….
// 筛选颜色为红色
public  List<Product> filterProductByColor(List<Product> list){
    List<Product> prods = new ArrayList<>();
    for (Product product : list){
        if ("红色".equals(product.getColor())){
            prods.add(product);
        }
    }
    return prods;
 }

// 筛选价格小于8千的
public  List<Product> filterProductByPrice(List<Product> list){
    List<Product> prods = new ArrayList<>();
    for (Product product : list){
        if (product.getPrice() < 8000){
            prods.add(product);
        }
    }
    return prods;
 }

我们发现实际上这些过滤方法的核心就只有if语句中的条件判断,其他均为模版代码,每次变更一下需求,都需要新增一个方法,然后复制黏贴,假设这个过滤方法有几百行,那么这样的做法难免笨拙了一点。如何进行优化呢?
优 化 一 : 使 用 设 计 模 式 \color{red}{优化一:使用设计模式} 使
定义一个MyPredicate接口

public interface MyPredicate <T> {
    boolean test(T t);
}

如果想要筛选颜色为红色的商品,定义一个颜色过滤类

public class ColorPredicate implements MyPredicate <Product> {

     private static final String RED = "红色";

     @Override
     public boolean test(Product product) {
         return RED.equals(product.getColor());
     }

定义过滤方法,将过滤接口当做参数传入,这样这个过滤方法就不用修改,在实际调用的时候将具体的实现类传入即可。

public List<Product> filterProductByPredicate(List<Product> list,MyPredicate<Product> mp){
        List<Product> prods = new ArrayList<>();
        for (Product prod : list){
            if (mp.test(prod)){
                prods.add(prod);
            }
        }
        return prods;
    }

例如,如果想要筛选价格小于8000的商品,那么新建一个价格过滤类既可

public class PricePredicate implements MyPredicate<Product> {
    @Override
    public boolean test(Product product) {
        return product.getPrice() < 8000;
    }
}

这样实现的话可能有人会说,每次变更需求都需要新建一个实现类,感觉还是有点繁琐呀,那么再来优化一下
优 化 二 : 使 用 匿 名 内 部 类 \color{red}{优化二:使用匿名内部类} 使
定义过滤方法:

public List<Product> filterProductByPredicate(List<Product> list,MyPredicate<Product> mp){
        List<Product> prods = new ArrayList<>();
        for (Product prod : list){
            if (mp.test(prod)){
                prods.add(prod);
            }
        }
        return prods;
    }

调用过滤方法的时候:

// 按价格过滤
public void test2(){
    filterProductByPredicate(proList, new MyPredicate<Product>() {
        @Override
        public boolean test(Product product) {
            return product.getPrice() < 8000;
        }
    });
}

 // 按颜色过滤
 public void test3(){
     filterProductByPredicate(proList, new MyPredicate<Product>() {
         @Override
         public boolean test(Product product) {
             return "红色".equals(product.getColor());
         }
     });
 }

使用匿名内部类,就不需要每次都新建一个实现类,直接在方法内部实现。看到匿名内部类,不禁想起了Lambda表达式。
优 化 三 : 使 用 l a m b d a 表 达 式 \color{red}{优化三:使用lambda表达式} 使lambda
定义过滤方法:

public List<Product> filterProductByPredicate(List<Product> list,MyPredicate<Product> mp){
        List<Product> prods = new ArrayList<>();
        for (Product prod : list){
            if (mp.test(prod)){
                prods.add(prod);
            }
        }
        return prods;
    }

使用lambda表达式进行过滤

@Test
public void test4(){
      List<Product> products = filterProductByPredicate(proList, (p) -> p.getPrice() < 8000);
      for (Product pro : products){
          System.out.println(pro);
      }
  }

在jdk1.8中还有更加简便的操作 Stream API
优 化 四 : 使 用 S t r e a m A P I \color{red}{优化四:使用Stream API} 使StreamAPI
甚至不用定义过滤方法,直接在集合上进行操作

// 使用jdk1.8中的Stream API进行集合的操作
@Test
public void test(){
    // 根据价格过滤
    proList.stream()
           .fliter((p) -> p.getPrice() <8000)
           .limit(2)
           .forEach(System.out::println);

    // 根据颜色过滤
    proList.stream()
           .fliter((p) -> "红色".equals(p.getColor()))
           .forEach(System.out::println);

    // 遍历输出商品名称
    proList.stream()
           .map(Product::getName)
           .forEach(System.out::println);
}

Lmabda表达式的语法总结: () -> ();

前置 语法
无参数无返回值 () -> System.out.println(“Hello WOrld”)
有一个参数无返回值 (x) -> System.out.println(x)
有且只有一个参数无返回值 x -> System.out.println(x)
有多个参数,有返回值,有多条lambda体语句 (x,y) -> {System.out.println(“xxx”);return xxxx;};
有多个参数,有返回值,只有一条lambda体语句 (x,y) -> xxxx

注 : 当 一 个 接 口 中 存 在 多 个 抽 象 方 法 时 , 如 果 使 用 l a m b d a 表 达 式 , 并 不 能 智 能 匹 配 对 应 的 抽 象 方 法 , 因 此 引 入 了 函 数 式 接 口 的 概 念 当 然 , 不 是 所 有 的 接 口 都 可 以 通 过 这 种 方 法 来 调 用 , 只 有 函 数 式 接 口 才 行 , j d k 1.8 里 面 定 义 了 好 多 个 函 数 式 接 口 , 我 们 也 可 以 自 己 定 义 一 个 来 调 用 \color{red}{注:当一个接口中存在多个抽象方法时,如果使用lambda表达式,并不能智能匹配对应的抽象方法,因此引入了函数式接口的概念 当然,不是所有的接口都可以通过这种方法来调用,只有函数式接口才行,jdk1.8里面定义了好多个函数式接口,我们也可以自己定义一个来调用} 使lambdajdk1.8

3.函数式接口:

下面说一下什么是函数式接口。
定 义 : “ 函 数 式 接 口 ” 是 指 仅 仅 只 包 含 一 个 抽 象 方 法 的 接 口 , 每 一 个 该 类 型 的 l a m b d a 表 达 式 都 会 被 匹 配 到 这 个 抽 象 方 法 。 j d k 1.8 提 供 了 一 个 @ F u n c t i o n a l I n t e r f a c e 注 解 来 定 义 函 数 式 接 口 , 如 果 我 们 定 义 的 接 口 不 符 合 函 数 式 的 规 范 便 会 报 错 。 \color{green}{定义:“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。jdk1.8提供了一个@FunctionalInterface注解来定义函数式接口,如果我们定义的接口不符合函数式的规范便会报错。} lambdajdk1.8@FunctionalInterface便

@FunctionalInterface
public interface MyLamda {
    
    public void test1(String y);

//这里如果继续加一个抽象方法便会报错
//    public void test1();
    
//default方法可以任意定义
    default String test2(){
        return "123";
    }  
    default String test3(){
        return "123";
    }
//static方法也可以定义
    static void test4(){
        System.out.println("234");
    }
}

看一下这个接口的调用,符合lambda表达式的调用方法。

MyLamda m = y -> System.out.println("ss"+y);

常见的四大函数式接口

  • Consumer 《T》:消费型接口,有参无返回值
@Test
    public void test(){
        changeStr("hello",(str) -> System.out.println(str));
    }

    /**
     *  Consumer 消费型接口
     * @param str
     * @param con
     */
    public void changeStr(String str, Consumer<String> con){
        con.accept(str);
    }
  • Supplier 《T》:供给型接口,无参有返回值
  @Test
    public void test2(){
        String value = getValue(() -> "hello");
        System.out.println(value);
    }

    /**
     *  Supplier 供给型接口
     * @param sup
     * @return
     */
    public String getValue(Supplier<String> sup){
        return sup.get();
    }
  • Function 《T,R》::函数式接口,有参有返回值
 @Test
    public void test3(){
        Long result = changeNum(100L, (x) -> x + 200L);
        System.out.println(result);
    }

    /**
     *  Function 函数式接口
     * @param num
     * @param fun
     * @return
     */
    public Long changeNum(Long num, Function<Long, Long> fun){
        return fun.apply(num);
    }
  • Predicate《T》: 断言型接口,有参有返回值,返回值是boolean类型
public void test4(){
        boolean result = changeBoolean("hello", (str) -> str.length() > 5);
        System.out.println(result);
    }

    /**
     *  Predicate 断言型接口
     * @param str
     * @param pre
     * @return
     */
    public boolean changeBoolean(String str, Predicate<String> pre){
        return pre.test(str);
    }

在四大核心函数式接口基础上,还提供了诸如BiFunction、BinaryOperation、toIntFunction等扩展的函数式接口,都是在这四种函数式接口上扩展而来的,不做赘述。

总结:函数式接口的提出是为了让我们更加方便的使用lambda表达式,不需要自己再手动创建一个函数式接口,直接拿来用就好了

4. 方法与构造函数引用:

jdk1.8提供了另外一种调用方式::,当 你 需 要使用 方 法 引用时 , 目 标引用 放 在 分隔符::前 ,方法 的 名 称放在 后 面 ,即ClassName :: methodName 。例如 ,Apple::getWeight就是引用了Apple类中定义的方法getWeight。请记住,不需要括号,因为你没有实际调用这个方法。方法引用就是Lambda表达式(Apple a) -> a.getWeight()的快捷写法,如下示例。

//先定义一个函数式接口
@FunctionalInterface
public interface TestConverT<T, F> {
    F convert(T t);
}
测试如下,可以以::形式调用。
public void test(){
    TestConverT<String, Integer> t = Integer::valueOf;
    Integer i = t.convert("111");
    System.out.println(i);
}

此外,对于构造方法也可以这么调用。

//实体类User和它的构造方法
public class User {    
    private String name;
    
    private String sex;

    public User(String name, String sex) {
        super();
        this.name = name;
        this.sex = sex;
    }
}
//User工厂
public interface UserFactory {
    User get(String name, String sex);
}
//测试类
    UserFactory uf = User::new;
    User u = uf.get("ww", "man");

这里的User::new就是调用了User的构造方法,Java编译器会自动根据UserFactory.get方法的签名来选择合适的构造函数。

(a) 方法引用
三种表现形式:

  1. 对象::实例方法名
  2. 类::静态方法名
  3. 类::实例方法名 (lambda参数列表中第一个参数是实例方法的调用 者,第二个参数是实例方法的参数时可用)
public void test() {
        /**
        *注意:
        *   1.lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致!
        *   2.若lambda参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用ClassName::method
        *
        */
        Consumer<Integer> con = (x) -> System.out.println(x);
        con.accept(100);

        // 方法引用-对象::实例方法
        Consumer<Integer> con2 = System.out::println;
        con2.accept(200);

        // 方法引用-类名::静态方法名
        BiFunction<Integer, Integer, Integer> biFun = (x, y) -> Integer.compare(x, y);
        BiFunction<Integer, Integer, Integer> biFun2 = Integer::compare;
        Integer result = biFun2.apply(100, 200);

        // 方法引用-类名::实例方法名
        BiFunction<String, String, Boolean> fun1 = (str1, str2) -> str1.equals(str2);
        BiFunction<String, String, Boolean> fun2 = String::equals;
        Boolean result2 = fun2.apply("hello", "world");
        System.out.println(result2);
    }

(b)构造器引用
格式:ClassName::new

public void test2() {

        // 构造方法引用  类名::new
        Supplier<Employee> sup = () -> new Employee();
        System.out.println(sup.get());
        Supplier<Employee> sup2 = Employee::new;
        System.out.println(sup2.get());

        // 构造方法引用 类名::new (带一个参数)
        Function<Integer, Employee> fun = (x) -> new Employee(x);
        Function<Integer, Employee> fun2 = Employee::new;
        System.out.println(fun2.apply(100));
 }

©数组引用

格式:Type[]::new

public void test(){
        // 数组引用
        Function<Integer, String[]> fun = (x) -> new String[x];
        Function<Integer, String[]> fun2 = String[]::new;
        String[] strArray = fun2.apply(10);
        Arrays.stream(strArray).forEach(System.out::println);
}

5、局部变量限制

Lambda表达式也允许使用自由变量(不是参数,而是在外层作用域中定义的变量),就像匿名类一样。 它们被称作捕获Lambda。 Lambda可以没有限制地捕获(也就是在其主体中引用)实例变量和静态变量。但局部变量必须显式声明为final,或事实上是final。
  为什么局部变量有这些限制?
  (1)实例变量和局部变量背后的实现有一个关键不同。实例变量都存储在堆中,而局部变量则保存在栈上。如果Lambda可以直接访问局部变量,而且Lambda是在一个线程中使用的,则使用Lambda的线程,可能会在分配该变量的线程将这个变量收回之后,去访问该变量。因此, Java在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变量。如果局部变量仅仅赋值一次那就没有什么区别了——因此就有了这个限制。
  (2)这一限制不鼓励你使用改变外部变量的典型命令式编程模式。

final int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);
stringConverter.convert(2);

6、Date Api更新

1.8之前JDK自带的日期处理类非常不方便,我们处理的时候经常是使用的第三方工具包,比如commons-lang包等。不过1.8出现之后这个改观了很多,比如日期时间的创建、比较、调整、格式化、时间间隔等。这些类都在java.time包下。比原来实用了很多。

6.1 LocalDate/LocalTime/LocalDateTime

LocalDate为日期处理类、LocalTime为时间处理类、LocalDateTime为日期时间处理类,方法都类似,具体可以看API文档或源码,选取几个代表性的方法做下介绍。

now相关的方法可以获取当前日期或时间,of方法可以创建对应的日期或时间,parse方法可以解析日期或时间,get方法可以获取日期或时间信息,with方法可以设置日期或时间信息,plus或minus方法可以增减日期或时间信息;

6.2TemporalAdjusters

这个类在日期调整时非常有用,比如得到当月的第一天、最后一天,当年的第一天、最后一天,下一周或前一周的某天等。

6.3DateTimeFormatter

以前日期格式化一般用SimpleDateFormat类,但是不怎么好用,现在1.8引入了DateTimeFormatter类,默认定义了很多常量格式(ISO打头的),在使用的时候一般配合LocalDate/LocalTime/LocalDateTime使用,比如想把当前日期格式化成yyyy-MM-dd hh:mm:ss的形式:

LocalDateTime dt = LocalDateTime.now();  
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");         
System.out.println(dtf.format(dt));

7、流

定义:流是Java API的新成员,它允许我们以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现)。就现在来说,我们可以把它们看成遍历数据集的高级迭代器。此外,流还可以透明地并行处理,也就是说我们不用写多线程代码了。
并 行 流 和 串 行 流 \color{red}{并行流和串行流}
在jdk1.8新的stream包中针对集合的操作也提供了并行操作流和串行操作流。并行流就是把内容切割成多个数据块,并且使用多个线程分别处理每个数据块的内容。Stream api中声明可以通过parallel()与sequential()方法在并行流和串行流之间进行切换。
jdk1.8并行流使用的是fork/join框架进行并行操作

Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。

Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。

流的操作类型分为两种:

Intermediate:一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
Terminal:一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。
  在对于一个 Stream 进行多次转换操作 (Intermediate 操作),每次都对 Stream 的每个元素进行转换,而且是执行多次,这样时间复杂度就是 N(转换次数)个 for 循环里把所有操作都做掉的总和吗?其实不是这样的,转换操作都是 lazy 的,多个转换操作只会在 Terminal 操作的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在 Terminal 操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。

构造流的几种方式

// 1. Individual values
Stream stream = Stream.of("a", "b", "c");
// 2. Arrays
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 3. Collections
List<String> list = Arrays.asList(strArray);
stream = list.stream();

8.Stream API:

Stream操作的三个步骤

  • 创建stream
  • 中间操作(过滤、map)
  • 终止操作

stream的创建:

   // 1,校验通过Collection 系列集合提供的stream()或者paralleStream()
    List<String> list = new ArrayList<>();
    Strean<String> stream1 = list.stream();

    // 2.通过Arrays的静态方法stream()获取数组流
    String[] str = new String[10];
    Stream<String> stream2 = Arrays.stream(str);

    // 3.通过Stream类中的静态方法of
    Stream<String> stream3 = Stream.of("aa","bb","cc");

    // 4.创建无限流
    // 迭代
    Stream<Integer> stream4 = Stream.iterate(0,(x) -> x+2);

    //生成
    Stream.generate(() ->Math.random());

Stream的中间操作:

/**
   * 筛选 过滤  去重
   */
  emps.stream()
          .filter(e -> e.getAge() > 10)
          .limit(4)
          .skip(4)
          // 需要流中的元素重写hashCode和equals方法
          .distinct()
          .forEach(System.out::println);


  /**
   *  生成新的流 通过map映射
   */
  emps.stream()
          .map((e) -> e.getAge())
          .forEach(System.out::println);


  /**
   *  自然排序  定制排序
   */
  emps.stream()
          .sorted((e1 ,e2) -> {
              if (e1.getAge().equals(e2.getAge())){
                  return e1.getName().compareTo(e2.getName());
              } else{
                  return e1.getAge().compareTo(e2.getAge());
              }
          })
          .forEach(System.out::println);

Stream的终止操作:

 /**
         *      查找和匹配
         *          allMatch-检查是否匹配所有元素
         *          anyMatch-检查是否至少匹配一个元素
         *          noneMatch-检查是否没有匹配所有元素
         *          findFirst-返回第一个元素
         *          findAny-返回当前流中的任意元素
         *          count-返回流中元素的总个数
         *          max-返回流中最大值
         *          min-返回流中最小值
         */

        /**
         *  检查是否匹配元素
         */
        boolean b1 = emps.stream()
                .allMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
        System.out.println(b1);

        boolean b2 = emps.stream()
                .anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
        System.out.println(b2);

        boolean b3 = emps.stream()
                .noneMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
        System.out.println(b3);

        Optional<Employee> opt = emps.stream()
                .findFirst();
        System.out.println(opt.get());

        // 并行流
        Optional<Employee> opt2 = emps.parallelStream()
                .findAny();
        System.out.println(opt2.get());

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

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

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

还有功能比较强大的两个终止操作 reduce和collect
reduce操作: reduce:(T identity,BinaryOperator)/reduce(BinaryOperator)-可以将流中元素反复结合起来,得到一个值

         /**
         *  reduce :规约操作
         */
        List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        Integer count2 = list.stream()
                .reduce(0, (x, y) -> x + y);
        System.out.println(count2);

        Optional<Double> sum = emps.stream()
                .map(Employee::getSalary)
                .reduce(Double::sum);
        System.out.println(sum);

collect操作:Collect-将流转换为其他形式,接收一个Collection接口的实现,用于给Stream中元素做汇总的方法

        /**
         *  collect:收集操作
         */

        List<Integer> ageList = emps.stream()
                .map(Employee::getAge)
                .collect(Collectors.toList());
        ageList.stream().forEach(System.out::println);

9.ForkJoin框架

Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总。
关 键 字 : 递 归 分 合 、 分 而 治 之 。 \color{red}{关键字:递归分合、分而治之。}
采用 “工作窃取”模式(work-stealing):
当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线
程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中
相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的
处理方式上.在一般的线程池中,如果一个线程正在执行的任务由于某些原因
无法继续运行,那么该线程会处于等待状态.而在fork/join框架实现中,如果
某个子问题由于等待另外一个子问题的完成而无法继续运行.那么处理该子
问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了线程
的等待时间,提高了性能.。

/**
 * 要想使用Fark—Join,类必须继承
 * RecursiveAction(无返回值)
 * Or
 * RecursiveTask(有返回值)
*
*/
public class ForkJoin extends RecursiveTask<Long> {

    /**
     * 要想使用Fark—Join,类必须继承RecursiveAction(无返回值) 或者
     * RecursiveTask(有返回值)
     *
     * @author Wuyouxin
     */
    private static final long serialVersionUID = 23423422L;

    private long start;
    private long end;

    public ForkJoin() {
    }

    public ForkJoin(long start, long end) {
        this.start = start;
        this.end = end;
    }

    // 定义阙值
    private static final long THRESHOLD = 10000L;

    @Override
    protected Long compute() {
        if (end - start <= THRESHOLD) {
            long sum = 0;
            for (long i = start; i < end; i++) {
                sum += i;
            }
            return sum;
        } else {
            long middle = (end - start) / 2;
            ForkJoin left = new ForkJoin(start, middle);
            //拆分子任务,压入线程队列
            left.fork();
            ForkJoin right = new ForkJoin(middle + 1, end);
            right.fork();

            //合并并返回
            return left.join() + right.join();
        }
    }

    /**
     * 实现数的累加
     */
    @Test
    public void test1() {
        //开始时间
        Instant start = Instant.now();

        //这里需要一个线程池的支持
        ForkJoinPool pool = new ForkJoinPool();

        ForkJoinTask<Long> task = new ForkJoin(0L, 10000000000L);
        // 没有返回值     pool.execute();
        // 有返回值
        long sum = pool.invoke(task);

        //结束时间
        Instant end = Instant.now();
        System.out.println(Duration.between(start, end).getSeconds());
    }

    /**
     * java8 并行流 parallel()
     */
    @Test
    public void test2() {
        //开始时间
        Instant start = Instant.now();

        // 并行流计算    累加求和
        LongStream.rangeClosed(0, 10000000000L).parallel()
                .reduce(0, Long :: sum);

        //结束时间
        Instant end = Instant.now();
        System.out.println(Duration.between(start, end).getSeconds());
    }

    @Test
    public void test3(){
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        list.stream().forEach(System.out::print);

        list.parallelStream()
            .forEach(System.out::print);
    }

展示多线程的效果:

@Test
    public void test(){
        // 并行流 多个线程执行
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
        numbers.parallelStream()
                .forEach(System.out::print);

        //
        System.out.println("=========================");
        numbers.stream()
                     .sequential()
                     .forEach(System.out::print);
    }

10.Optional容器

使用Optional容器可以快速的定位NPE,并且在一定程度上可以减少对参数非空检验的代码量。
/**
     *      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
     *
     *      总结:Optional.of(null)  会直接报NPE
     */

Optional<Employee> op = Optional.of(new Employee("zhansan", 11, 12.32, Employee.Status.BUSY));
        System.out.println(op.get());

        // NPE
        Optional<Employee> op2 = Optional.of(null);
        System.out.println(op2);
@Test
    public void test2(){
        Optional<Object> op = Optional.empty();
        System.out.println(op);

        // No value present
        System.out.println(op.get());
    }
@Test
    public void test3(){
        Optional<Employee> op = Optional.ofNullable(new Employee("lisi", 33, 131.42, Employee.Status.FREE));
        System.out.println(op.get());

        Optional<Object> op2 = Optional.ofNullable(null);
        System.out.println(op2);
       // System.out.println(op2.get());
    }
    @Test
    public void test5(){
        Optional<Employee> op1 = Optional.ofNullable(new Employee("张三", 11, 11.33, Employee.Status.VOCATION));
        System.out.println(op1.orElse(new Employee()));
        System.out.println(op1.orElse(null));
    }

    @Test
    public void test6(){
        Optional<Employee> op1 = Optional.of(new Employee("田七", 11, 12.31, Employee.Status.BUSY));
        op1 = Optional.empty();
        Employee employee = op1.orElseGet(() -> new Employee());
        System.out.println(employee);
    }

    @Test
    public void test7(){
        Optional<Employee> op1 = Optional.of(new Employee("田七", 11, 12.31, Employee.Status.BUSY));
        System.out.println(op1.map( (e) -> e.getSalary()).get());
    }

接 口 中 可 以 定 义 默 认 实 现 方 法 和 静 态 方 法 \color{blue}{接口中可以定义默认实现方法和静态方法}
在接口中可以使用default和static关键字来修饰接口中定义的普通方法

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

    static String getName2(){
        return "zhangsan";
    }
}

在JDK1.8中很多接口会新增方法,为了保证1.8向下兼容,1.7版本中的接口实现类不用每个都重新实现新添加的接口方法,引入了default默认实现,static的用法是直接用接口名去调方法即可。当一个类继承父类又实现接口时,若后两者方法名相同,则优先继承父类中的同名方法,即“类优先”,如果实现两个同名方法的接口,则要求实现类必须手动声明默认实现哪个接口中的方法。

11.新的日期API LocalDate | LocalTime | LocalDateTime

新 的 日 期 A P I 都 是 不 可 变 的 , 更 使 用 于 多 线 程 的 使 用 环 境 中 \color{red}{新的日期API都是不可变的,更使用于多线程的使用环境中} API使线使

    @Test
    public void test(){
        // 从默认时区的系统时钟获取当前的日期时间。不用考虑时区差
        LocalDateTime date = LocalDateTime.now();
        //2018-07-15T14:22:39.759
        System.out.println(date);

        System.out.println(date.getYear());
        System.out.println(date.getMonthValue());
        System.out.println(date.getDayOfMonth());
        System.out.println(date.getHour());
        System.out.println(date.getMinute());
        System.out.println(date.getSecond());
        System.out.println(date.getNano());

        // 手动创建一个LocalDateTime实例
        LocalDateTime date2 = LocalDateTime.of(2017, 12, 17, 9, 31, 31, 31);
        System.out.println(date2);
        // 进行加操作,得到新的日期实例
        LocalDateTime date3 = date2.plusDays(12);
        System.out.println(date3);
        // 进行减操作,得到新的日期实例
        LocalDateTime date4 = date3.minusYears(2);
        System.out.println(date4);
    }

    @Test
    public void test2(){
        // 时间戳  1970年1月1日00:00:00 到某一个时间点的毫秒值
        // 默认获取UTC时区
        Instant ins = Instant.now();
        System.out.println(ins);

        System.out.println(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli());
        System.out.println(System.currentTimeMillis());

        System.out.println(Instant.now().toEpochMilli());
        System.out.println(Instant.now().atOffset(ZoneOffset.ofHours(8)).toInstant().toEpochMilli());
    }
    @Test
    public void test3(){
        // Duration:计算两个时间之间的间隔
        // Period:计算两个日期之间的间隔

        Instant ins1 = Instant.now();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Instant ins2 = Instant.now();
        Duration dura = Duration.between(ins1, ins2);
        System.out.println(dura);
        System.out.println(dura.toMillis());

        System.out.println("======================");
        LocalTime localTime = LocalTime.now();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LocalTime localTime2 = LocalTime.now();
        Duration du2 = Duration.between(localTime, localTime2);
        System.out.println(du2);
        System.out.println(du2.toMillis());
    }
@Test
    public void test4(){
        LocalDate localDate =LocalDate.now();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        LocalDate localDate2 = LocalDate.of(2016,12,12);
        Period pe = Period.between(localDate, localDate2);
        System.out.println(pe);
    }
    @Test
    public void test5(){
        // temperalAdjust 时间校验器
        // 例如获取下周日  下一个工作日
        LocalDateTime ldt1 = LocalDateTime.now();
        System.out.println(ldt1);

        // 获取一年中的第一天
        LocalDateTime ldt2 = ldt1.withDayOfYear(1);
        System.out.println(ldt2);
        // 获取一个月中的第一天
        LocalDateTime ldt3 = ldt1.withDayOfMonth(1);
        System.out.println(ldt3);

        LocalDateTime ldt4 = ldt1.with(TemporalAdjusters.next(DayOfWeek.FRIDAY));
        System.out.println(ldt4);

        // 获取下一个工作日
        LocalDateTime ldt5 = ldt1.with((t) -> {
            LocalDateTime ldt6 = (LocalDateTime)t;
            DayOfWeek dayOfWeek = ldt6.getDayOfWeek();
            if (DayOfWeek.FRIDAY.equals(dayOfWeek)){
                return ldt6.plusDays(3);
            }
            else if (DayOfWeek.SATURDAY.equals(dayOfWeek)){
                return ldt6.plusDays(2);
            }
            else {
                return ldt6.plusDays(1);
            }
        });
        System.out.println(ldt5);
    }
    @Test
    public void test6(){
        // DateTimeFormatter: 格式化时间/日期
        // 自定义格式
        LocalDateTime ldt = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
        String strDate1 = ldt.format(formatter);
        String strDate = formatter.format(ldt);
        System.out.println(strDate);
        System.out.println(strDate1);

        // 使用api提供的格式
        DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE;
        LocalDateTime ldt2 = LocalDateTime.now();
        String strDate3 = dtf.format(ldt2);
        System.out.println(strDate3);

        // 解析字符串to时间
        DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime time = LocalDateTime.now();
        String localTime = df.format(time);
        LocalDateTime ldt4 = LocalDateTime.parse("2017-09-28 17:07:05",df);
        System.out.println("LocalDateTime转成String类型的时间:"+localTime);
        System.out.println("String类型的时间转成LocalDateTime:"+ldt4);
    }
    // ZoneTime  ZoneDate       ZoneDateTime
    @Test
    public void test7(){
        LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
        System.out.println(now);

        LocalDateTime now2 = LocalDateTime.now();
        ZonedDateTime zdt = now2.atZone(ZoneId.of("Asia/Shanghai"));
        System.out.println(zdt);

        Set<String> set = ZoneId.getAvailableZoneIds();
        set.stream().forEach(System.out::println);
    }

补充:

表示日期的LocalDate 
表示时间的LocalTime 
表示日期时间的LocalDateTime

新的日期API的几个优点:

  • 之前使用的java.util.Date月份从0开始,我们一般会+1使用,很不方便,java.time.LocalDate月份和星期都改成了enum

  • java.util.Date和SimpleDateFormat都不是线程安全的,而LocalDate和LocalTime和最基本的String一样,是不变类型,不但线程安全,而且不能修改。

  • java.util.Date是一个“万能接口”,它包含日期、时间,还有毫秒数,更加明确需求取舍

  • 新接口更好用的原因是考虑到了日期时间的操作,经常发生往前推或往后推几天的情况。用java.util.Date配合Calendar要写好多代码,而且一般的开发人员还不一定能写对。

  • LocalDate

public static void localDateTest() {

        //获取当前日期,只含年月日 固定格式 yyyy-MM-dd    2018-05-04
        LocalDate today = LocalDate.now();

        // 根据年月日取日期,5月就是5,
        LocalDate oldDate = LocalDate.of(2018, 5, 1);

        // 根据字符串取:默认格式yyyy-MM-dd,02不能写成2
        LocalDate yesteday = LocalDate.parse("2018-05-03");

        // 如果不是闰年 传入29号也会报错
        LocalDate.parse("2018-02-29");
    }
  • LocalDate常用转化
    /**
     * 日期转换常用,第一天或者最后一天...
     */
    public static void localDateTransferTest(){
        //2018-05-04
        LocalDate today = LocalDate.now();
        // 取本月第1天: 2018-05-01
        LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth());
        // 取本月第2天:2018-05-02
        LocalDate secondDayOfThisMonth = today.withDayOfMonth(2);
        // 取本月最后一天,再也不用计算是28,29,30还是31: 2018-05-31
        LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth());
        // 取下一天:2018-06-01
        LocalDate firstDayOf2015 = lastDayOfThisMonth.plusDays(1);
        // 取2018年10月第一个周三 so easy?:  2018-10-03
        LocalDate thirdMondayOf2018 = LocalDate.parse("2018-10-01").with(TemporalAdjusters.firstInMonth(DayOfWeek.WEDNESDAY));
    }
  • LocalTime
 public static void localTimeTest(){
        //16:25:46.448(纳秒值)
        LocalTime todayTimeWithMillisTime = LocalTime.now();
        //16:28:48 不带纳秒值
        LocalTime todayTimeWithNoMillisTime = LocalTime.now().withNano(0);
        LocalTime time1 = LocalTime.parse("23:59:59");
    }
  • LocalDateTime
public static void localDateTimeTest(){
        //转化为时间戳  毫秒值
        long time1 = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
        long time2 = System.currentTimeMillis();

        //时间戳转化为localdatetime
        DateTimeFormatter df= DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss.SSS");

        System.out.println(df.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(time1),ZoneId.of("Asia/Shanghai"))));
    }

12.总结

这是个人学习记录,可能存在问题,望大家多多提出建议!

你可能感兴趣的:(Java)