【java 新特性】java8新特性

在这里插入图片描述

核心内容

lambda

函数编程

在Java世界里面,面向对象还是主流思想,对于习惯了面向对象编程的开发者来说,抽象的概念并不陌生。面向对象编程是对数据进行抽象,而函数式编程是对行为进行抽象。现实世界中,数据和行为并存,程序也是如此,因此这两种编程方式我们都得学。

这种新的抽象方式还有其他好处。很多人不总是在编写性能优先的代码,对于这些人来说,函数式编程带来的好处尤为明显。程序员能编写出更容易阅读的代码——这种代码更多地表达了业务逻辑,而不是从机制上如何实现。易读的代码也易于维护、更可靠、更不容易出错。

在写回调函数和事件处理器时,程序员不必再纠缠于匿名内部类的冗繁和可读性,函数式编程让事件处理系统变得更加简单。能将函数方便地传递也让编写惰性代码变得容易,只有在真正需要的时候,才初始化变量的值。

面向对象编程是对数据进行抽象;函数式编程是对行为进行抽象。

核心思想: 使用不可变值和函数,函数对一个值进行处理,映射成另一个值。

对核心类库的改进主要包括集合类的API和新引入的流Stream。流使程序员可以站在更高的抽象层次上对集合进行操作。
Lambda 表达式在 Java 8 中引入,并且被吹捧为 Java 8 最大的特性。

Lambda 表达式是函数式编程的的一个重要特性,标志者 Java 向函数式编程迈出了重要的第一步。

Java Lambda 表达式语法

Java Lambda 表达式的语法结构如下

parameter -> expression body

实际代码可能如下

有参数且只有一条语句时

                (a,b) -> a + b

只有一个参数时

                a  -> a

没有参数时

                ()  -> System.out.println("简单教程")

有多条语句时

                (a,b) -> {
                    int c = a + b;
                    System.out.println("简单教程")
                }

针对这个 Java Lambda 表达式语法,有几个重要的特征需要说明

  • 可选的参数类型声明 : 无需声明参数的类型。编译器可以从参数的值推断出相同的值。
  • 可选的参数周围的小括号 () : 如果只有一个参数,可以忽略参数周围的小括号。但如果有多个参数,则必须添加小括号。
  • 可选的大括号 {} : 如果 Lambda 表达式只包含一条语句,那么可以省略大括号。但如果有多条语句,则必须添加大括号。
  • 可选的 return 关键字 : 如果 Lambda 表达式只有一条语句,那么编译器会自动 return 该语句最后的结果。但如果显式使用了 return 语句,则必须添加大括号 {} ,哪怕只有一条语句。
Java Lambda 表达式的原理

后面我们会讲到,Java 8 中的 Lambda 表达式其实是一个特殊的只有一个方法的类的实例。

这些类是 Java 8 内部已经定义好的,而且实现了 java.lang.FunctionalInterface 这个接口。

这个 java.lang.FunctionalInterface 接口是一种信息性注解类型,用于标识一个接口类型声明为函数接口( functional interface )。

从某些方面说,Java 8 的 Lambda 表达式是使用匿名内部类的语法创建了 java.util.function 包下相应签名的接口的或者其它自定义的只有一个方法的接口实例。

但是,实际上,Java 8 中的 Lambda 不仅仅是使用匿名内部类,还使用了 Java 8 接口的默认方法和一些其它的功能。这方面,有空我会写一篇文章。

范例

范例一: Java Lambda 表达式
Lambda 比较常见的使用场景就是 new Runnable 匿名内部类的使用

LambdaTester.java

public class LambdaTester
{
    public static void main(String[] args)
    {
        Runnable r = () -> System.out.println("你好,简单教程,你好,简单编程");
        Thread th = new Thread(r);
        th.start(); 
     }
}

运行结果如下

你好,简单教程,你好,简单编程

范例二
LambdaTester.java

public class LambdaTester {
    
           public static void main(String args[])
           {
              LambdaTester tester = new LambdaTester();
    
              // 有声明参数类型
              MathOperation addition = (int a, int b) -> a + b;
    
              // 没有声明参数类型
              MathOperation subtraction = (a, b) -> a - b;
    
              // 使用 return 语句显式返回值需要添加大括号
              MathOperation multiplication = (int a, int b) -> { return a * b; };
    
              // 如果只有一条语句,那么可以省略大括号,Java 会返回表达式的值
              MathOperation division = (int a, int b) -> a / b;
    
              System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
              System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
              System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
              System.out.println("10 / 5 = " + tester.operate(10, 5, division));
           }
    
           interface MathOperation {
              int operation(int a, int b);
           }
    
           private int operate(int a, int b, MathOperation mathOperation) {
              return mathOperation.operation(a, b);
           }
        }

运行结果如下

            [yufei@www.twle.cn helloworld]$ javac LambdaTester.java && java LambdaTester
10 + 5 = 15
10 - 5 = 5
10 x 5 = 50
10 / 5 = 2
Java Lambda 表达式的缺点

不知道大家有没有从上面的表达式中看到一些端倪,好像,好像 Java 8 中的 Lambda 不能凭空出现。

Java Lambda 表达式最大的缺点,就是不能像其它语言的 Lambda 表达式一样凭空出现。

Java 中的 Lambda 表达式需要有一个函数接口声明作为模板。这个模板定义了 Lambda 表达式的参数类型和返回值类型。

例如下面的代码,我们先要声明一个函数接口类型,然后才能定义一个参数和返回值都一样的表达式

LambdaTester.java

 public class LambdaTester {
        
               // 先声明一个函数接口
               interface GreetingService {
                  void sayMessage(String message);
               }
        
               public static void main(String args[])
               {
                  LambdaTester tester = new LambdaTester();
        
                  // 有小括号
                  GreetingService greetService1 = message ->
                  System.out.println("你好," + message);
        
                  // 省略小括号
                  GreetingService greetService2 = (message) ->
                  System.out.println("你好," + message);
        
                  greetService1.sayMessage("简单教程");
                  greetService2.sayMessage("简单编程");
               }
            }

运行结果如下

你好,简单教程
你好,简单编程

Java 8 Lambda 表达式作用域 ( scope )

因为 Java 8 的 lambda 表达式其实是函数接口的内联实现,也就是匿名内部类,因此,可以引用任何外部的变量或者常量。

但是,lambda 对这些外部的变量是有要求的: 它们必须使用 final 修饰符修饰。

如果一个变量允许被第二次赋值,则 Lambda 表达式会抛出编译错误。

注意: 其实这条规则并不是非常严格执行的,普通变量也是可以的,只要,只要不进行第二次赋值就可以。

注意: 刚刚测试了下,其实只要不是当前作用域声明的变量,可以随意第二次赋值,也不会报错

范例一
Java 8 lambda 表达式使用外部 final 变量

LambdaTester.java

                public class LambdaTester
                {
            
                   final static String salutation = "你好,";
            
                   public static void main(String args[])
                   {
                      GreetingService greetService1 = message -> 
                      System.out.println(salutation + message);
                      greetService1.sayMessage("简单教程");
                   }
            
                   interface GreetingService {
                      void sayMessage(String message);
                   }
                }
                

运行结果如下

你好,简单教程

范例二
lambda 引用的普通的变量也是可以的,只要这个变量没有第二次被赋值,不管是任何地方。

LambdaTester.java

                public class LambdaTester
                {
            
                   static String salutation = "你好,";
            
                   public static void main(String args[])
                   {
                      GreetingService greetService1 = message -> 
                      System.out.println(salutation + message);
                      greetService1.sayMessage("简单教程");
                   }
            
                   interface GreetingService {
                      void sayMessage(String message);
                   }
                }

运行结果如下

                你好,简单教程

范例三
如果 lambda 表达式引用的是当前作用域下的普通的变量,而该变量又在某个地方第二次被赋值,则会抛出一个编译错误

LambdaTester.java
                public class LambdaTester
                {
                   public static void main(String args[])
                   {
                      String salutation = "你好,";
            
                      GreetingService greetService1 = message -> 
                      System.out.println(salutation + message);
                      greetService1.sayMessage("简单教程");
                      salutation = "Hello,";
                   }
            
                   interface GreetingService {
                      void sayMessage(String message);
                   }
                }

运行结果如下

                LambdaTester.java:8: 错误: 从lambda 表达式引用的本地变量必须是最终变量或实际上的最终变量
                      System.out.println(salutation + message);
                                         ^
                1 个错误

范例四
如果 lambda 表达式引用的变量并不是当前作用域下声明的,也可以随意赋值,并不会报错

LambdaTester.java
                public class LambdaTester
                {
                   static String salutation = "你好,";
                   public static void main(String args[])
                   {
                      salutation = "Hello,";
                      GreetingService greetService1 = message -> 
                      System.out.println(salutation + message);
                      greetService1.sayMessage("简单教程");
                      salutation = "你好,";
                   }
            
                   interface GreetingService {
                      void sayMessage(String message);
                   }
                }

运行结果如下

Hello,简单教程
总结

Java lambda 表达式可以随意引用外部变量,但如果外部变量是在当前作用域声明的,则一定不可以进行第二次赋值,哪怕是在 lambda 语句之后。

方法引用

Java 8 中新增加了 方法引用 这个概念。 但,什么是方法引用呢 ?

我们先来看一个例子,下面这个范例演示了如何遍历字符串列表并进行一些操作

LambdaTester.java

                import java.util.Arrays;
                import java.util.List;
                import java.util.function.Function;
            
                public class LambdaTester {
            
                    public static void main(String[] args)
                    {
                        LambdaTester tester = new LambdaTester();
                        tester.run();
                    }
            
                    public void run()
                    {
                        List<String> list = Arrays.asList("Ram","Shyam","Kabir");
            
                        // 输出
                        for(String st: list){
                            System.out.println(st);
                        }
            
                        // 转换为大写
                        for(String st: list){
                            upperAndPrint(st);
                        }
                    }
            
                    public static void upperAndPrint(String s)
                    {
                        System.out.println(s.toUpperCase());
                    }
                }
                

运行结果如下

                Ram
                Shyam
                Kabir
                RAM
                SHYAM
                KABIR

有简洁代码倾向的我们,看到那重复的 for(String st: list) 就会想如何能够直接把代码改的更简洁一些。

第一个想到的,肯定是使用 Java 8 新增的 lambda 表达式和 forEach 改造下

LambdaTester.java

                import java.util.Arrays;
                import java.util.List;
                import java.util.function.Function;
            
                public class LambdaTester {
            
                    public static void main(String[] args)
                    {
                        LambdaTester tester = new LambdaTester();
                        tester.run();
                    }
            
                    public void run()
                    {
                        List<String> list = Arrays.asList("Ram","Shyam","Kabir");
            
                        // 输出
                        list.forEach(item -> System.out.println(item));
            
                        // 转换为大写
                        list.forEach(item -> upperAndPrint(item));
                    }
            
                    public static void upperAndPrint(String s)
                    {
                        System.out.println(s.toUpperCase());
                    }
                }

哇,使用 Lambda 表达式真的是简洁了不少,但是,还能更简单一点吗?

比如 item -> System.out.println(item) 这个表达式,其实就是调用 System.out.println() 方法,并把 forEach 迭代列表生成的唯一参数 item 传给它而已

比如 item -> upperAndPrint(item) 这个表达式,也是一样啊,就是把 forEach 生成的唯一参数 item 传给给 upperAndPrint() 方法而已。

既然这样,我们为什么不能直接传递方法名给 forEach 呢?

对吧,所以我们改改,改成如下这种方式

                import java.util.Arrays;
                import java.util.List;
                import java.util.function.Function;
            
                public class LambdaTester {
            
                    public static void main(String[] args)
                    {
                        LambdaTester tester = new LambdaTester();
                        tester.run();
                    }
            
                    public void run()
                    {
                        List<String> list = Arrays.asList("Ram","Shyam","Kabir");
            
                        // 输出
                        list.forEach(System.out.println);
            
                        // 转换为大写
                        list.forEach(upperAndPrint);
                    }
            
                    public static void upperAndPrint(String s)
                    {
                        System.out.println(s.toUpperCase());
                    }
                }

运行结果如下

                LambdaTester.java:18: 错误: 找不到符号
                        list.forEach(System.out.println);
                                               ^
                  符号:   变量 println
                  位置: 类型为PrintStream的变量 out
                LambdaTester.java:21: 错误: 找不到符号
                        list.forEach(upperAndPrint);
                                     ^
                  符号:   变量 upperAndPrint
                  位置:LambdaTester
                2 个错误

这在所有的 Java 版本中都会报错,但是自 Java 8 开始,还真是可以直接传递方法名的,只是我们使用的方式不对而已。

加入我们改成下面这样,就会正确了

                import java.util.Arrays;
                import java.util.List;
                import java.util.function.Function;
            
                public class LambdaTester {
            
                    public static void main(String[] args)
                    {
                        LambdaTester tester = new LambdaTester();
                        tester.run();
                    }
            
                    public void run()
                    {
                        List<String> list = Arrays.asList("Ram","Shyam","Kabir");
            
                        // 输出
                        list.forEach(System.out::println);
            
                        // 转换为大写
                        list.forEach(LambdaTester::upperAndPrint);
                    }
            
                    public static void upperAndPrint(String s)
                    {
                        System.out.println(s.toUpperCase());
                    }
                }

运行结果如下

                Ram
                Shyam
                Kabir
                RAM
                SHYAM
                KABIR

哈哈哈,正确了。

在这个正确的方法中,有两个重点:

方法名和类名之间不是使用点号 ( . ) 而是使用两个冒号 :: 。
提供的方法名必须包含类名,如果没有引入该类名,则需要使用全限定类名,也就是需要添加包名作为前缀。
这种使用方法名做参数的做法,在 Java 8 中称之为 「 方法引用 」

Java 8 方法引用

方法引用 是 Java 8 新增加的功能。方法引用有点类似于 C / C++ 中的 函数指针 ,通过方法名称指向方法。

Java 8 中的方法引用通过 :: 符号引用方法,而且支持一下类型的方法引用

  1. 静态方法
  2. 实例方法
  3. 使用 new 运算符的构造函数。例如 TreeSet::new

范例
我们重写一下上面的范例,演示下如何引用静态方法和实例方法

                import java.util.Arrays;
                import java.util.List;
                import java.util.function.Function;
            
                public class LambdaTester {
            
                    public static void main(String[] args)
                    {
                        LambdaTester tester = new LambdaTester();
                        tester.run();
                    }
            
                    public void run()
                    {
                        List<String> list = Arrays.asList("Ram","Shyam","Kabir");
            
                        // 输出
                        list.forEach(System.out::println);
            
                        // 转换为大写
                        list.forEach(LambdaTester::upperAndPrint);
            
                        // 转换为小写并输出
                        list.forEach(this::lowerAndPrint);
                    }
            
                    public void lowerAndPrint(String s)
                    {
                        System.out.println(s.toLowerCase());
                    }
            
                    public static void upperAndPrint(String s)
                    {
                        System.out.println(s.toUpperCase());
                    }
                }

运行结果如下

                Ram
                Shyam
                Kabir
                RAM
                SHYAM
                KABIR
                ram
                shyam
                kabir

Stream API

流 ( Stream ) 是 Java 8 新增加的一个重磅级的功能。流是一个抽象层。有了流,我们就可以使用类似于 SQL 语句的声明方式来处理数据。

比如,下面的 SQL 语句

SELECT max(grade), student_id, student_name FROM Students;

上面这条 SQL 会自动返回最高学习绩点的学生的信息,而全程,开发人员却不用直接面对任何计算和比较。

在流 ( Stream ) 出现之前,对于Java 中的集合框架的使用。开发人员不得不一次次的写一个循环,一次次的重复检查。当然了,这也什么,毕竟大家都是这样过来的。

更大的问题在于开发效率。面对当前的多核 CPU 计算机,面对并发编程。我们开发者常常会写出非常容易出错的并发执行的代码。

为了解决这些问题,Java 8 引入了流 ( Stream ) 这个概念,允许开发人员以声明的方式处理数据的同时,还能利用多核构架,而无需编写任何特殊的代码。

流是什么 ?

Java 中的 流 ( Stream ) 表示来自 源 ( source ) 的一系列对象,它支持统计、求和、求平均值等聚合操作。

流具有以下特征:

  • 元素序列 : 流以顺序方式提供特定类型的一组元素。流只会按需获取/计算元素。但它从不存储元素。
  • 源 ( Source ) :流可以将集合,数组或 I/O 资源作为输入源。
  • 聚合操作 : 流支持聚合操作,如 filter、map、limit、reduce、find、match 等
  • 管道 ( pipelining ) :大多数流操作都返回流本身,以便可以对其结果进行流水线操作。这些操作称为 中间 操作,它们的功能是获取输入,处理它们并将输出返回到目标。collect() 方法是一个终端操作,通常在流水线操作结束时出现,以标记流的结尾。
  • 原子性迭代 ( Automatic iterations ) : 与需要显式迭代的集合相比,流操作在内部对所提供的源元素进行迭代。

流的创建

Java 8 在推出流的同时,对集合框架也进行了一些比较大变更。主要是在 Collection 接口上提供了两种生成 Stream 的方法:

stream() 方法,该方法以集合作为源,返回集合中的所有元素以在集合中出现的顺序组成的流。
parallelStream() 方法,该方法以集合作为源,返回一个支持并发操作的流。
流支持的聚合操作.

Stream中常用方法

Stream中常用方法如下:

  • filter()
  • findAny() findFirst()
  • sort
  • forEach
  • map(), reduce()
  • flatMap() - 将多个Stream连接成一个Stream
  • distinct, limit
  • count
  • min, max, summaryStatistics
    在这里插入图片描述
filter() 方法

filter() 方法根据一个谓词来过滤元素。这个谓词是一个方法,以流中的每一个元素作为参数,如果返回 true 则会被过滤掉。

例如下面的代码,使用 filter() 方法过滤那些空字符串。

                List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
            
                int count = strings.stream().filter(string -> string.isEmpty()).count();
findAny() findFirst()
sorted() 方法

sorted() 方法用于给流中的元素进行排序。

下面的范例演示了如何按照排序顺序打印 10 个随机数

                Random random = new Random();
                random.ints().limit(10).sorted().forEach(System.out::println);
forEach() 方法

Java 8 为 Stream 提供了一种新方法 forEach(),用于迭代流的每个元素。

下面的代码片段演示了如何使用 forEach 打印 10 个随机数。

                Random random = new Random();
                random.ints().limit(10).forEach(System.out::println);

上面这个代码片段中,Random 对象的 ints() 方法会返回一个整数流。而 limit() 方法则限制了流中的元素个数。从某些方面说,可以理解为当源产生了 10 个随机数之后就关闭源。

map(), reduce() 方法

map() 方法会迭代流中的元素,并为每个元素应用一个方法,然后返回应用后的流。

例如下面的代码,使用 map() 方法把求出每个元素的平方,然后过滤掉重复的元素,最后在转换为列表集合

                List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
            
                //获取每个元素的平方
                List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());

map( i -> i*i) 操作求取流中每个元素的平方,并返回一个新的流。distinct() 方法则用于过滤流中的重复元素。
map将集合类(例如列表)元素进行转换的。还有一个 reduce() 函数可以将所有值合并成一个

List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
double bill = costBeforeTax.stream().map((cost) -> cost + .12*cost).reduce((sum, cost) -> sum + cost).get();
System.out.println("Total : " + bill);
flatMap() - 将多个Stream连接成一个Stream

将多个Stream连接成一个Stream

List<Integer> result= Stream.of(Arrays.asList(1,3),Arrays.asList(5,6)).flatMap(a->a.stream()).collect(Collectors.toList());

结果: [1, 3, 5, 6]

distinct, limit

去重

List<LikeDO> likeDOs=new ArrayList<LikeDO>();
List<Long> likeTidList = likeDOs.stream().map(LikeDO::getTid)
                .distinct().collect(Collectors.toList());

limit() 方法用于减少( 限制 ) 流中的元素数量。

例如下面的代码段演示了如何使用 limit() 方法只输出 10 个随机数

                Random random = new Random();
                random.ints().limit(10).forEach(System.out::println);
count

计总数

int countOfAdult=persons.stream()
                       .filter(p -> p.getAge() > 18)
                       .map(person -> new Adult(person))
                       .count();
min, max

最小值,最大值

List<Person> lists = new ArrayList<Person>();
lists.add(new Person(1L, "p1"));
lists.add(new Person(2L, "p2"));
lists.add(new Person(3L, "p3"));
lists.add(new Person(4L, "p4"));
Person a = lists.stream().max(Comparator.comparing(t -> t.getId())).get();
System.out.println(a.getId());

如果比较器涉及多个条件,比较复杂,可以定制

 Person a = lists.stream().min(new Comparator<Person>() {

      @Override
      public int compare(Person o1, Person o2) {
           if (o1.getId() > o2.getId()) return -1;
           if (o1.getId() < o2.getId()) return 1;
           return 0;
       }
 }).get();
并发处理

parallelStream() 是需要并发处理的流的替代方案。stream() 方法产生的流只能是串行处理,可以理解为只在一个线程中,按照流中元素的顺序一个接一个的处理。

而并发处理,就是传说中的 map-reduce 方法,可以充分利用多核优势。

需要注意的是,parallelStream() 会打乱流的顺序,也就是返回的序列顺序不一定是输入的序列顺序。

例如下面的代码用于打印序列中的空字符串的数量

                List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
            
                //get count of empty string
                int count = strings.parallelStream().filter(string -> string.isEmpty()).count();

因为 stream() 返回是串行流,而 parallelStream() 返回的是并行流。因此在串行和并行之间切换是非常简单的。

收集器 ( Collectors )

收集器 ( Collectors )用于将已经处理的流中的元素组合到一起。

Collectors 类提供了大量方法用于指示如何收集元素。

比如 Collectors.toList() 方法可以将流中的元素收集起来,并转换为列表

                List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
                List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
            
                System.out.println("Filtered List: " + filtered);

比如 Collectors.joining() 方法可以将流中的元素收集起来,并使用指定的字符串拼接符拼接成一个字符串。

                List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
                String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
            
                System.out.println("Merged String: " + mergedString);

Java 8 流的新类 java.util.stream.Collectors 实现了 java.util.stream.Collector 接口,同时又提供了大量的方法对流 ( stream ) 的元素执行 map and reduce 操作,或者统计操作。

Collectors 常用的方法

本章节,我们就来看看那些常用的方法,顺便写几个示例练练手。

Collectors.averagingDouble()

Collectors.averagingDouble() 方法将流中的所有元素视为 double 类型并计算他们的平均值。该方法返回的是同一个 Collectors 实例,因此可以进行链式操作。

Collectors.averagingDouble() 接受一个参数,这个参数是一个 lambda 表达式,用于对所有的元素执行一个 map 操作。

Java 所有集合的 stream().collect() 可以接受一个收集器实例作为其参数并返回该收集器的计算结果

例如下面的代码,collect() 方法会把所有的元素收集起来然后传递给 Collectors.averagingDouble(d->d*2) 收集器,对每个元素执行 *2 操作后计算平均值

AveragingDoubleExample.java

                package cn.twle.util.stream;
                import java.util.Arrays;
                import java.util.List;
                import java.util.stream.Collectors;
                public class AveragingDoubleExample {
                    public static void main(String[] args) {
                        List<Integer> list = Arrays.asList(1,2,3,4);
                        Double result = list.stream().collect(Collectors.averagingDouble(d->d*2));
                        System.out.println(result);
                    }
                }
                

输出结果为 5.0

Collectors.averagingInt()

Collectors.averagingInt() 方法和 Collectors.averagingDouble() 一样,不同的是它把流中的所有元素看成是 int 类型,并返回一个浮点类型的平均值

AveragingIntExample.java

                package cn.twle.util.stream;
                import java.util.Arrays;
                import java.util.List;
                import java.util.stream.Collectors;
                public class AveragingIntExample {
                    public static void main(String[] args) {
                        List<Integer> list = Arrays.asList(1,2,3,4);
                        Double result = list.stream().collect(Collectors.averagingInt(v->v*2));
                        System.out.println(result);
                    }
                }

输出结果为 5.0

Collectors.averagingLong()

Collectors.averagingLong() 方法也和 Collectors.averagingDouble() 类似,不同的是它把流中的所有元素看成是 long 类型,并返回一个 double 类型的平均值

AveragingLongExample.java
package cn.twle…util.stream;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class AveragingLongExample {
public static void main(String[] args) {
List list = Arrays.asList(1,2,3,4);
Double result = list.stream().collect(Collectors.averagingLong(v->v*2));
System.out.println(result);
}
}
输出结果为 5.0

Collectors.collectingAndThen()

Collectors.collectingAndThen() 函数应该最像 map and reduce 了,它可接受两个参数,第一个参数用于 reduce 操作,而第二参数用于 map 操作。

也就是,先把流中的所有元素传递给第二个参数,然后把生成的集合传递给第一个参数来处理。

例如下面的代码,先把 [1,2,3,4] 这个集合传递给 s-> ss lambda 表达式,计算得出结果为 [1,4,9,16] ,然后再把 [1,4,9,16] 传递给 v->v2 表达式,计算得出 [2,8,18,32] ,然后传递给 Collectors.averagingLong() 计算得到结果为 25.0

CollectingAndThenExample.java

                package cn.twle..util.stream;
            
                import java.util.Arrays;
                import java.util.List;
                import java.util.stream.Collectors;
                public class CollectingAndThenExample {
                    public static void main(String[] args) {
                        List<Integer> list = Arrays.asList(1,2,3,4);
                        Double result = list.stream().collect(Collectors.collectingAndThen(Collectors.averagingLong(v->v*2),
                                s-> s*s));
                        System.out.println(result);
                    }
                }
Collectors.counting()

Collectors.counting() 用于统计流中元素的个数。

CountingExample.java

                package cn.twle.util.stream;
                import java.util.Arrays;
                import java.util.List;
                import java.util.stream.Collectors;
                public class CountingExample {
                    public static void main(String[] args) {
                        List<Integer> list = Arrays.asList(1,2,3,4);
                       long result=  list.stream().collect(Collectors.counting());
                       System.out.println(result);
                    }
                }

输出结果为 4

Collectors.joining()

Collectors.joining() 方法用某个指定的拼接字符串把所有元素拼接成一个字符串,并添加可选的前缀和后缀

JoiningExample.java

                package cn.twle.util.stream;
                import java.util.Arrays;
                import java.util.List;
                import java.util.stream.Collectors;
                public class JoiningExample {
                    public static void main(String[] args) {
                       List<String> list = Arrays.asList("A","B","C","D");
                       String result=  list.stream().collect(Collectors.joining(",","(",")"));
                       System.out.println(result);
                    }
                }

输出结果为 (A,B,C,D)
Collectors.joining() 方法以遭遇元素的顺序拼接元素。我们可以传递可选的拼接字符串、前缀和后缀

joinning() 方法定义
假设我们的流中有四个元素 [“A”,“B”,“C”,“D”],那么我们就可以按照以下方式来收集它们

joining()
joinning() 无参数方法会返回一个 Collectors 实例,并且以空字符串 ( “” ) 来拼接收集到的所有元素

JoiningExample.java
                package cn.twle.util.stream;
                import java.util.Arrays;
                import java.util.List;
                import java.util.stream.Collectors;
                public class JoiningExample {
                    public static void main(String[] args) {
                       List<String> list = Arrays.asList("A","B","C","D");
                       String result=  list.stream().collect(Collectors.joining());
                       System.out.println(result);
                    }
                }

输出结果为 ABCD

joining(CharSequence delimiter)
joining(CharSequence delimiter) 接受一个参数字符串序列作为拼接符,并返回一个 Collectors 实例。假如我们传递的拼接符为 “-” 。那么输出结果为 A-B-C-D

JoiningExample.java

                package cn.twle.util.stream;
                import java.util.Arrays;
                import java.util.List;
                import java.util.stream.Collectors;
                public class JoiningExample {
                    public static void main(String[] args) {
                       List<String> list = Arrays.asList("A","B","C","D");
                       String result = list.stream().collect(Collectors.joining("-"));
                       System.out.println(result);
                    }
                }

运行结果为 A-B-C-D

joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix) 方法接受一个字符串序列作为拼接符,并在拼接完成后添加传递的前缀和后缀。假如我们传递的分隔符为 “-”,前缀为 “[” , 后缀为 “]” 。那么输出结果为 [A-B-C-D]

JoiningExample.java

                package cn.twle.util.stream;
                import java.util.Arrays;
                import java.util.List;
                import java.util.stream.Collectors;
                public class JoiningExample {
                    public static void main(String[] args) {
                       List<String> list = Arrays.asList("A","B","C","D","[","]");
                       String result=  list.stream().collect(Collectors.joining("-"));
                       System.out.println(result);
                    }
                }

运行结果为 [A-B-C-D]

范例
范例 1 : 如果流中的数据是字符串
下面的代码演示了如何使用 joinning() 的三种重载方法来拼接字符串

JoiningExampleWithListOfString.java

                package cn.twle.util.stream;
                import java.util.Arrays;
                import java.util.List;
                import java.util.stream.Collectors;
                public class JoiningExampleWithListOfString {
                    public static void main(String[] args) {
                        List<String> list = Arrays.asList("Ram","Shyam","Shiv","Mahesh");
                        String result=  list.stream().collect(Collectors.joining());
                        System.out.println(result);
                        result=  list.stream().collect(Collectors.joining(","));
                        System.out.println(result);        
                        result=  list.stream().collect(Collectors.joining("-","[","]"));
                        System.out.println(result);        
                    }       
                }

运行结果为

                RamShyamShivMahesh
                Ram,Shyam,Shiv,Mahesh
                [Ram-Shyam-Shiv-Mahesh]

范例 2: 如果流中的数据是对象
如果流中的数据是对象,下面的代码演示了如何拼接它们。

首先,我们创建一个 Person 类

Person.java

                package cn.twle.util.stream;
                import java.util.ArrayList;
                import java.util.List;
                public class Person {
                    private String name;
                    private int age;
                    public Person(String name, int age) {
                        this.name = name;
                        this.age = age;
                    }
                    public String getName() {
                        return name;
                    }
                    public int getAge() {
                        return age;
                    }
                    public static List<Person> getList() {
                        List<Person> list = new ArrayList<>();
                        list.add(new Person("Ram", 23));
                        list.add(new Person("Shyam", 20));
                        list.add(new Person("Shiv", 25));
                        list.add(new Person("Mahesh", 30));
                        return list;
                    }
                }

然后创建一个 Person 对象流

JoiningExampleWithListOfObject.java

                package cn.twle.util.stream;
                import java.util.List;
                import java.util.stream.Collectors;
                public class JoiningExampleWithListOfObject {
                    public static void main(String[] args) {
                        List<Person> list = Person.getList();
                        System.out.println("--Join person name--");
                        String result=  list.stream().map(p -> p.getName()).collect(Collectors.joining());
                        System.out.println(result);
                        result=  list.stream().map(p -> p.getName()).collect(Collectors.joining("|"));
                        System.out.println(result);
                        result=  list.stream().map(p -> p.getName()).collect(Collectors.joining("-","[","]"));
                        System.out.println(result);
            
                        System.out.println("--Join person age--");
                        result=  list.stream().map(p -> String.valueOf(p.getAge())).collect(Collectors.joining());
                        System.out.println(result);
                        result=  list.stream().map(p -> String.valueOf(p.getAge())).collect(Collectors.joining("|"));
                        System.out.println(result);
                        result=  list.stream().map(p -> String.valueOf(p.getAge())).collect(Collectors.joining("-","[","]"));
                        System.out.println(result);       
            
                        System.out.println("--Join person name-age--");
                        result=  list.stream().map(p -> p.getName()+"-" + p.getAge()).collect(Collectors.joining("|"));
                        System.out.println(result);
                        result=  list.stream().map(p -> p.getName()+"-" + p.getAge()).collect(Collectors.joining("|","[","]"));
                        System.out.println(result);        
                    }       
                }

运行结果为

                --Join person name--
                RamShyamShivMahesh
                Ram|Shyam|Shiv|Mahesh
                [Ram-Shyam-Shiv-Mahesh]
                --Join person age--
                23202530
                23|20|25|30
                [23-20-25-30]
                --Join person name-age--
                Ram-23|Shyam-20|Shiv-25|Mahesh-30
                [Ram-23|Shyam-20|Shiv-25|Mahesh-30]
Collectors.maxBy() 和 Collectors.minBy()

Collectors.maxBy() 和 Collectors.minBy() 两个方法分别用于计算流中所有元素的最大值和最小值。

两个方法都可以接受一个比较器作为参数,用于如何计算最大值或最小值

MaxByMinByExample.java

                package cn.twle.util.stream;
                import java.util.Arrays;
                import java.util.Comparator;
                import java.util.List;
                import java.util.stream.Collectors;
                public class MaxByMinByExample {
                    public static void main(String[] args) {
                       List<Integer> list = Arrays.asList(30,10,20,35);
                       //Get Max       
                       list.stream().collect(Collectors.maxBy(new MaxByMinByExample().new IntegerComp()))
                               .ifPresent(i->System.out.println(i));
                       //Get Min
                       list.stream().collect(Collectors.minBy(new MaxByMinByExample().new IntegerComp()))
                               .ifPresent(i->System.out.println(i));
                    }
                    class IntegerComp implements Comparator<Integer> {
                        @Override
                        public int compare(Integer i1, Integer i2) {
                          if(i1 >=i2 ){
                              return 1;
                          }else{
                              return -1;
                          }
                        }
                    }
                }

输出结果如下

                35
                10
                
Collectors.summingInt()

Collectors.summingInt() 方法将流中的所有元素视为 int 类型,并计算所有元素的总和 ( sum )

SummingIntExample.java

                package cn.twle.util.stream;
                import java.util.Arrays;
                import java.util.List;
                import java.util.stream.Collectors;
                public class SummingIntExample {
                    public static void main(String[] args) {
                       List<Integer> list = Arrays.asList(30,10,20,35);
                       int result = list.stream().collect(Collectors.summingInt(i->i));
                       System.out.println(result);
                    }
                }

输出结果为 95

Collectors.summingLong()

Collectors.summingLong() 将流中的所有元素视为 long 类型,并计算所有元素的总和

SummingLongExample.java

                package cn.twle.util.stream;
                import java.util.ArrayList;
                import java.util.List;
                import java.util.stream.Collectors;
                public class SummingLongExample {
                    public static void main(String[] args) {
                       List<Long> list = new ArrayList<>();
                       list.add((long)340);
                       list.add((long)240);
                       list.add((long)360);
                       long result = list.stream().collect(Collectors.summingLong(l->l));
                       System.out.println(result);
                    }
                }
Collectors.summingDouble()

Collectors.summingDouble() 将流中的所有元素视为 double 类型,并计算所有元素的总和

SummingDoubleExample.java

                package cn.util.stream;
                import java.util.Arrays;
                import java.util.List;
                import java.util.stream.Collectors;
                public class SummingDoubleExample {
                    public static void main(String[] args) {
                       List<Double> list = Arrays.asList(340.5,234.56,672.76);
                       Double result = list.stream().collect(Collectors.summingDouble(d->d));
                       System.out.println(result);
                    }
                }

输出结果为 1247.82

也许你也注意到了,这三个函数的结果的类型,就是它们如何看待元素的类型。

Collectors.toList()

Collectors.toList() 将流中的所有元素导出到一个列表 ( List ) 中

ToListExample.java

                package cn.twle.util.stream;
                import java.util.List;
                import java.util.stream.Collectors;
                import java.util.stream.Stream;
                public class ToListExample {
                    public static void main(String[] args) {
                       List<String> list = Stream.of("AA","BB","CC").collect(Collectors.toList());
                       list.forEach(s->System.out.println(s));
                    }
                }

输出结果如下

                AA
                BB
                CC
Collectors.toSet()

Collectors.toSet() 把流中的所有元素导出到一个集合 ( Set ) 中,并排除重复的元素 ( Set 的特性 )

ToSetExample.java

                package cn.twle.util.stream;
                import java.util.Set;
                import java.util.stream.Collectors;
                import java.util.stream.Stream;
                public class ToSetExample {
                    public static void main(String[] args) {
                       Set<String> set = Stream.of("AA","AA","BB").collect(Collectors.toSet());
                       set.forEach(s->System.out.println(s));
                    }
                }

输出结果为

                AA
                BB
Collectors.toMap()

Collectors.toMap() 将流中的所有元素导出到一个哈希表 ( Map ) 中。该方法接受两个参数,第一个参数用于生成键 ( key ) ,第二个参数用于生成值 ( value )。两个参数都是 Lambda 表达式。

ToMapExample.java

                package cn.twle.util.stream;
                import java.util.Map;
                import java.util.stream.Collectors;
                import java.util.stream.Stream;
                public class ToMapExample {
                    public static void main(String[] args) {
                       Map<String,String> map = Stream.of("AA","BB","CC").collect(Collectors.toMap(k->k, v->v+v));
                       map.forEach((k,v)->System.out.println("key:"+k +"  value:"+v));
                    }

输出结果为

                key:CC  value:CCCC
                key:BB  value:BBBB
                key:AA  value:AAAA
Collectors.mapping()

Collectors.mapping() 一般用于多重 map and reduce 中。 Java 文档中描述的原型如下

                mapping(Function<? super T,? extends U> mapper, Collector<? super U,A,R> downstream)

第一个参数用于 map ,第二个参数用于 reduce

MappingDemo.java

                package cn.twle.util.stream;
                import java.util.ArrayList;
                import java.util.List;
                import java.util.Map;
                import java.util.stream.Collectors;
                public class MappingDemo {
                    public static void main(String[] args) {
                        List<Person> list = Person.getList();
                        Map<Integer, String> nameByAge
                           = list.stream().collect(Collectors.groupingBy(Person::getAge, 
                                   Collectors.mapping(Person::getName, Collectors.joining(","))));
                        nameByAge.forEach((k,v)->System.out.println("Age:"+k +"  Persons: "+v));
                    }   
                }
                class Person {
                    private String name;
                    private int age;
                    public Person(String name, int age) {
                        this.name = name;
                        this.age = age;
                    }
                    public String getName() {
                        return name;
                    }
                    public int getAge() {
                        return age;
                    }
                    public static List<Person> getList() {
                        List<Person> list = new ArrayList<>();
                        list.add(new Person("Ram", 30));
                        list.add(new Person("Shyam", 20));
                        list.add(new Person("Shiv", 20));
                        list.add(new Person("Mahesh", 30));
                        return list;
                    }
                }

输出结果如下

                Age:20  Persons: Shyam,Shiv
                Age:30  Persons: Ram,Mahesh
统计 ( Statistics )

Java 8 同时新增了大量的统计收集器来来获取流中的元素的一些统计信息。

前提是我们先要在流上调用 summaryStatistics() 方法返回统计信息概要,然后在调用相应的方法来获取具体的统计信息。

例如下面的代码,先调用 summaryStatistics() 方法返回统计概要,然后调用 getMax() 方法获取最大值

                List numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
            
                IntSummaryStatistics stats = integers.stream().mapToInt((x) -> x).summaryStatistics();
            
                System.out.println("Highest number in List : " + stats.getMax());

例如下面的代码,先调用 summaryStatistics() 方法返回统计概要,然后调用 getMin() 和 getSum() 方法获取最小值和所有数字之和

                List numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
            
                IntSummaryStatistics stats = integers.stream().mapToInt((x) -> x).summaryStatistics();
            
                System.out.println("Lowest number in List : " + stats.getMin());
                System.out.println("Sum of all numbers : " + stats.getSum());
                

例如下面的代码,先调用 summaryStatistics() 方法返回统计概要,然后调用 getAverage() 方法获取平均值

                List numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
            
                IntSummaryStatistics stats = integers.stream().mapToInt((x) -> x).summaryStatistics();
            
                System.out.println("Average of all numbers : " + stats.getAverage());
FunctionalInterface

理解注解 @FunctionInterface

/**
 * An informative annotation type used to indicate that an interface
 * type declaration is intended to be a <i>functional interface</i> as
 * defined by the Java Language Specification.
 *
 * Conceptually, a functional interface has exactly one abstract
 * method.  Since {@linkplain java.lang.reflect.Method#isDefault()
 * default methods} have an implementation, they are not abstract.  If
 * an interface declares an abstract method overriding one of the
 * public methods of {@code java.lang.Object}, that also does
 * <em>not</em> count toward the interface's abstract method count
 * since any implementation of the interface will have an
 * implementation from {@code java.lang.Object} or elsewhere.
 *
 * <p>Note that instances of functional interfaces can be created with
 * lambda expressions, method references, or constructor references.
 *
 * <p>If a type is annotated with this annotation type, compilers are
 * required to generate an error message unless:
 *
 * <ul>
 * <li> The type is an interface type and not an annotation type, enum, or class.
 * <li> The annotated type satisfies the requirements of a functional interface.
 * </ul>
 *
 * <p>However, the compiler will treat any interface meeting the
 * definition of a functional interface as a functional interface
 * regardless of whether or not a {@code FunctionalInterface}
 * annotation is present on the interface declaration.
 *
 * @jls 4.3.2. The Class Object
 * @jls 9.8 Functional Interfaces
 * @jls 9.4.3 Interface Method Body
 * @since 1.8
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface{}
  • interface做注解的注解类型,被定义成java语言规范
  • 一个被它注解的接口只能有一个抽象方法,有两种例外
  • 第一是接口允许有实现的方法,这种实现的方法是用default关键字来标记的(java反射中java.lang.reflect.Method#isDefault()方法用来判断是否是default方法)
  • 第二如果声明的方法和java.lang.Object中的某个方法一样,它可以不当做未实现的方法,不违背这个原则: 一个被它注解的接口只能有一个抽象方法, 比如: java public interface Comparator { int compare(T o1, T o2); boolean equals(Object obj); }
  • 如果一个类型被这个注解修饰,那么编译器会要求这个类型必须满足如下条件:
  1. 这个类型必须是一个interface,而不是其他的注解类型、枚举enum或者类class
  2. 这个类型必须满足function interface的所有要求,如你个包含两个抽象方法的接口增加这个注解,会有编译错误。
  • 编译器会自动把满足function interface要求的接口自动识别为function interface,所以你才不需要对上面示例中的 ITest接口增加@FunctionInterface注解。
自定义函数接口
@FunctionalInterface
public interface IMyInterface {
    void study();
}

package com.isea.java;
public class TestIMyInterface {
    public static void main(String[] args) {
        IMyInterface iMyInterface = () -> System.out.println("I like study");
        iMyInterface.study();
    }
}
内置四大函数接口

消费型接口: Consumer< T> void accept(T t)有参数,无返回值的抽象方法;

比如: map.forEach(BiConsumer<A, T>)
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));

供给型接口: Supplier < T> T get()无参有返回值的抽象方法;
以stream().collect(Collector<? super T, A, R> collector)为例:

比如:

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

再如:

// 调用方法
<R, A> R collect(Collector<? super T, A, R> collector)

// Collectors.toSet
public static <T>
 Collector<T, ?, Set<T>> toSet() {
     return new CollectorImpl<>((Supplier<Set<T>>) HashSet::new, Set::add,
                               (left, right) -> { left.addAll(right); return left; },
                               CH_UNORDERED_ID);
}

// CollectorImpl
private final Supplier<A> supplier;
private final BiConsumer<A, T> accumulator;
private final BinaryOperator<A> combiner;
private final Function<A, R> finisher;
private final Set<Characteristics> characteristics;

CollectorImpl(Supplier<A> supplier,
              BiConsumer<A, T> accumulator,
              BinaryOperator<A> combiner,
              Function<A,R> finisher,
              Set<Characteristics> characteristics) {
    this.supplier = supplier;
    this.accumulator = accumulator;
    this.combiner = combiner;
    this.finisher = finisher;
    this.characteristics = characteristics;
}

CollectorImpl(Supplier<A> supplier,
              BiConsumer<A, T> accumulator,
              BinaryOperator<A> combiner,
              Set<Characteristics> characteristics) {
    this(supplier, accumulator, combiner, castingIdentity(), characteristics);
}

// collect()方法实现
public final <R, A> R collect(Collector<? super P_OUT, A, R> collector) {
    A container;
    if (isParallel()
            && (collector.characteristics().contains(Collector.Characteristics.CONCURRENT))
            && (!isOrdered() || collector.characteristics().contains(Collector.Characteristics.UNORDERED))) {
        container = collector.supplier().get();
        BiConsumer<A, ? super P_OUT> accumulator = collector.accumulator();
        forEach(u -> accumulator.accept(container, u));
    }
    else {
        container = evaluate(ReduceOps.makeRef(collector));
    }
    return collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)
           ? (R) container
           : collector.finisher().apply(container);
}

断定型接口: Predicate boolean test(T t):有参,但是返回值类型是固定的boolean
比如: steam().filter()中参数就是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<T,R> R apply(T t)有参有返回值的抽象方法;
比如: steam().map()中参数就是Function<? super T, ? extends R>;reduce()中参数BinaryOperator (ps: BinaryOperator extends BiFunction<T,T,T>)

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

backToString.apply("123");     // "123"
一些例子

输出 年龄>25的女程序员中名字排名前3位的姓名

javaProgrammers.stream()
          .filter((p) -> (p.getAge() > 25))
          .filter((p) -> ("female".equals(p.getGender())))
          .sorted((p, p2) -> (p.getFirstName().compareTo(p2.getFirstName())))
          .limit(3)
          //.forEach(e -> e.setSalary(e.getSalary() / 100 * 5 + e.getSalary()))//涨工资
          .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));

工资最高的 Java programmer

Person person = javaProgrammers
          .stream()
          .max((p, p2) -> (p.getSalary() - p2.getSalary()))
          .get()

将 Java programmers 的 first name 存放到 TreeSet

TreeSet<String> javaDevLastName = javaProgrammers
          .stream()
          .map(Person::getLastName)
          .collect(toCollection(TreeSet::new))

计算付给 Java programmers 的所有money

int totalSalary = javaProgrammers
          .parallelStream()
          .mapToInt(p -> p.getSalary())
          .sum();

Comparator多属性排序: 先按名字不分大小写排,再按GID倒序排,最后按年龄正序排

public static void main(String[] args) {
 List<Person> personList = getTestList();
 personList.sort(Comparator.comparing(Person::getName, String.CASE_INSENSITIVE_ORDER)
   .thenComparing(Person::getGid, (a, b) -> b.compareTo(a))
   .thenComparingInt(Person::getAge));
 personList.stream().forEach(System.out::println);
}

public static List<Person> getTestList() {
 return Lists.newArrayList(new Person("dai", "301", 10), new Person("dai", "303", 10),
   new Person("dai", "303", 8), new Person("dai", "303", 6), new Person("dai", "303", 11),
   new Person("dai", "302", 9), new Person("zhang", "302", 9), new Person("zhang", "301", 9),
   new Person("Li", "301", 8));
}

// 输出结果

// Person [name=dai, gid=303, age=6]
// Person [name=dai, gid=303, age=8]
// Person [name=dai, gid=303, age=10]
// Person [name=dai, gid=303, age=11]
// Person [name=dai, gid=302, age=9]
// Person [name=dai, gid=301, age=10]
// Person [name=Li, gid=301, age=8]
// Person [name=zhang, gid=302, age=9]
// Person [name=zhang, gid=301, age=9]

处理字符串
两个新的方法可在字符串类上使用: join和chars。第一个方法使用指定的分隔符,将任何数量的字符串连接为一个字符串。

String.join(":", "foobar", "foo", "bar");
// => foobar:foo:bar

第二个方法chars从字符串所有字符创建数据流,所以你可以在这些字符上使用流式操作。

"foobar:foo:bar"
    .chars()
    .distinct()
    .mapToObj(c -> String.valueOf((char)c))
    .sorted()
    .collect(Collectors.joining());
// => :abfor

不仅仅是字符串,正则表达式模式串也能受益于数据流。我们可以分割任何模式串,并创建数据流来处理它们,而不是将字符串分割为单个字符的数据流,像下面这样:

Pattern.compile(":")
    .splitAsStream("foobar:foo:bar")
    .filter(s -> s.contains("bar"))
    .sorted()
    .collect(Collectors.joining(":"));
// => bar:foobar

此外,正则模式串可以转换为谓词。这些谓词可以像下面那样用于过滤字符串流:

Pattern pattern = Pattern.compile(".*@gmail\\.com");
Stream.of("bob@gmail.com", "alice@hotmail.com")
    .filter(pattern.asPredicate())
    .count();
// => 1

上面的模式串接受任何以@gmail.com结尾的字符串,并且之后用作Java8的Predicate来过滤电子邮件地址流。

Local Cache实现

public class TestLocalCache {

 private static ConcurrentHashMap<Integer, Long> cache = new ConcurrentHashMap<>();

 static long fibonacci(int i) {
  if (i == 0)
   return i;

  if (i == 1)
   return 1;

  return cache.computeIfAbsent(i, (key) -> {
   System.out.println("Slow calculation of " + key);

   return fibonacci(i - 2) + fibonacci(i - 1);
  });
 }
 
 public static void main(String[] args) {
  // warm up
  for (int i = 0; i < 101; i++)
         System.out.println(
             "f(" + i + ") = " + fibonacci(i));
  
  // read -> cal
  long current = System.currentTimeMillis();
  System.out.println(fibonacci(100));
  System.out.println(System.currentTimeMillis()-current);
 }
}

集合–》取元素的一个属性–》去重—》组装成List–》返回

List<LikeDO> likeDOs=new ArrayList<LikeDO>();
List<Long> likeTidList = likeDOs.stream().map(LikeDO::getTid)
                .distinct().collect(Collectors.toList());

集合–》按表达式过滤–》遍历、每个元系处理–》放入预先定义的集合中

  Map<String, StkProduct> newStockName2Product = Maps.newConcurrentMap();
        stockProducts.stream().filter(stkProduct -> stkProduct.enabled).forEach(stkProduct -> {
            String newName = BCConvert.bj2qj(StringUtils.replace(stkProduct.name, " ", ""));
            newStockName2Product.put(newName, stkProduct);
        });
 Set<String> qjStockNames;
 qjStockNames.stream().filter(name -> !acAutomaton.getKey2link().containsKey(name)).forEach(name -> {
            String value = "";
            StkProduct stkProduct = stockNameQj2Product.get(name);
            if (stkProduct != null) {
                value = stkProduct.name;
            }
            acAutomaton.getKey2link().put(name, value);
        });

集合–》map

List<ImageModel> imageModelList = null;
Map<Long, String> imagesMap = null;
imagesMap = imageModelList.stream().collect(Collectors.toMap(ImageModel::getAid, o -> IMAGE_ADDRESS_PREFIX + o.getUrl()));
              
             

Map<String, String> kvMap = postDetailCacheList.stream().collect(Collectors.toMap((detailCache) ->
                getBbsSimplePostKey(detailCache.getTid()), JSON::toJSONString));


Map<Long, Long> pidToTid;
List<String> pidKeyList = pidToTid.entrySet().stream().map((o) -> getKeyBbsReplyPid(o.getValue(), o.getKey())).collect(Collectors.toList());

DO模型—》Model模型

List<AdDO> adDOList;
adDOList.stream().map(adDo -> convertAdModel(adDo))
                .collect(Collectors.toList());
phones 是一个List<String>,将相同的元素分组、归类
List<String> phones=new ArrayList<String>();
        phones.add("a");
        phones.add("b");
        phones.add("a");
        phones.add("a");
        phones.add("c");
        phones.add("b");
        Map<String, List<String>> phoneClassify = phones.stream().collect(Collectors.groupingBy(item -> item));
        System.out.println(phoneClassify);

返回结果:

{a=[a, a, a], b=[b, b], c=[c]}

optional

在不考虑竖起来的情况下,抛一个硬币,落地时,显示正面的情况只有两种:是正面和不是正面。很多时候,这是一个 「 谓词 」,也就是返回布尔类型 ( bool )。但有时候,我们需要返回另一种类型:存在 和 空。

  • 存在 就是硬币落地时显示为正面
  • 就是硬币落地式显示的不是正面。
    从另一方面说,结果就是 有值 和 空 。

一个类,如果可以同时表示 有值 ,我们称这种类为 可选类 ( Optional )

从某些方面说,Optional 类型就是 「那里有一个值,它等于 x,或者那里没有那个值」

JAVA 8 java.util.Optional 类

Java 8 在 java.util 包中添加了一个新的类 Optional 。

Optional 类是一个容器,用于表示可能包含也可能不包含非 null 值。如果存在值,isPresent() 方法将返回 true,get() 将返回该值。

Optional 类提供了许多方法用于处理 「 可用 」 或 「 不可用 」 ,而不是简单的检查空值情况。

java.util.Optional 类的声明如下

                public final class Optional<T> extends Object

注意:该类是一个最终类,不能被继承和扩展。

创建 Optional 类的实例

Optional 类提供了三个静态方法用于创建 Optional 类的实例,这三个方法的返回值都是 Optional

方法说明
empty()创建一个空(empty)的Optional类的实例
of(Tvalue)创建一个包含了指定T类型的value值的Optional实例
ofNullable(Tvalue)如果value非null,则创建一个包含了指定T类型的value值的Optional实例,否则创建一个空的Optional实例
of

为非null的值创建一个Optional。

of方法通过工厂方法创建Optional类。需要注意的是,创建对象时传入的参数不能为null。如果传入参数为null,则抛出NullPointerException 。

//调用工厂方法创建Optional实例
Optional<String> name = Optional.of("Sanaulla");
//传入参数为null,抛出NullPointerException.
Optional<String> someNull = Optional.of(null);
ofNullable

为指定的值创建一个Optional,如果指定的值为null,则返回一个空的Optional。

ofNullable与of方法相似,唯一的区别是可以接受参数为null的情况。示例如下:

//下面创建了一个不包含任何值的Optional实例
//例如,值为'null'
Optional empty = Optional.ofNullable(null);

Optional 类提供的方法

方法说明
boolean equals(Objectobj)判断某个其它的对象是否「等于」此Optional
Optional<T&gts;filter(Predicate<?superT>predicate)如果存在值,并且值与给定谓词匹配,则返回描述值的Optional,否则返回空Optional
Optional<U&gst;flatMap(Function<?superT,Optional>mapper)如果值存在,则将map应用到该值上并返回应用后的结果,如果值不存在,则返回一个空的Optional
T get()如果此Optional中存在值,则返回该值,否则抛出NoSuchElementException异常
inthashCode()如果值存在,则返回当前值的哈希值,如果不存在值,则返回0
voidifPresent(Consumer<?superT>consumer)如果值存在,则使用该值作为参数调用方法consumer。如果值不存在,则什么事情都不做
booleanisPresent()如果值存在则返回true,否则返回false
Optionalmap(Function<?superT,?extendsU>mapper)如果存在值,则将传递的map函数应用于该值,如果结果为非null,则返回描述结果的Optionals
TorElse(Tother)如果值存在则返回值,否则返回other
TorElseGet(Supplier<?extendsT>other)如果值存在则返回值,否则调用other并返回该调用的结果
TorElseThrow(Supplier<?extendsX>>exceptionSupplier)如果值存在,则返回包含的值,否则抛出由开发者提供的异常
StringtoString()返回此Optional的非空字符串表示形式,一般用于调试
isPresent

非常容易理解:如果值存在返回true,否则返回false。

类似下面的代码:

//isPresent方法用来检查Optional实例中是否包含值
if (name.isPresent()) {
  //在Optional实例内调用get()返回已存在的值
  System.out.println(name.get());//输出Sanaulla
}
get

如果Optional有值则将其返回,否则抛出NoSuchElementException。

上面的示例中,get方法用来得到Optional实例中的值。下面我们看一个抛出NoSuchElementException的例子:

//执行下面的代码会输出: No value present 
try {
  //在空的Optional实例上调用get(),抛出NoSuchElementException
  System.out.println(empty.get());
} catch (NoSuchElementException ex) {
  System.out.println(ex.getMessage());
}
ifPresent

如果Optional实例有值则为其调用consumer,否则不做处理

要理解ifPresent方法,首先需要了解Consumer类。简答地说,Consumer类包含一个抽象方法。该抽象方法对传入的值进行处理,但没有返回值。Java8支持不用接口直接通过lambda表达式传入参数。

如果Optional实例有值,调用ifPresent()可以接受接口段或lambda表达式。类似下面的代码:

//ifPresent方法接受lambda表达式作为参数。
//lambda表达式对Optional的值调用consumer进行处理。
name.ifPresent((value) -> {
  System.out.println("The length of the value is: " + value.length());
});
orElse

如果有值则将其返回,否则返回指定的其它值。

如果Optional实例有值则将其返回,否则返回orElse方法传入的参数。示例如下:

//如果值不为null,orElse方法返回Optional实例的值。
//如果为null,返回传入的消息。
//输出: There is no value present!
System.out.println(empty.orElse("There is no value present!"));
//输出: Sanaulla
System.out.println(name.orElse("There is some value!"));
orElseGet

orElseGet与orElse方法类似,区别在于得到的默认值。orElse方法将传入的字符串作为默认值,orElseGet方法可以接受Supplier接口的实现用来生成默认值。示例如下:

//orElseGet与orElse方法类似,区别在于orElse传入的是默认值,
//orElseGet可以接受一个lambda表达式生成默认值。
//输出: Default Value
System.out.println(empty.orElseGet(() -> "Default Value"));
//输出: Sanaulla
System.out.println(name.orElseGet(() -> "Default Value"));
orElseThrow

如果有值则将其返回,否则抛出supplier接口创建的异常。

在orElseGet方法中,我们传入一个Supplier接口。然而,在orElseThrow中我们可以传入一个lambda表达式或方法,如果值不存在来抛出异常。示例如下:

try {
  //orElseThrow与orElse方法类似。与返回默认值不同,
  //orElseThrow会抛出lambda表达式或方法生成的异常 

  empty.orElseThrow(ValueAbsentException::new);
} catch (Throwable ex) {
  //输出: No value present in the Optional instance
  System.out.println(ex.getMessage());
}

ValueAbsentException定义如下:

class ValueAbsentException extends Throwable {

  public ValueAbsentException() {
    super();
  }

  public ValueAbsentException(String msg) {
    super(msg);
  }

  @Override
  public String getMessage() {
    return "No value present in the Optional instance";
  }
}
map

map方法文档说明如下:

如果有值,则对其执行调用mapping函数得到返回值。如果返回值不为null,则创建包含mapping返回值的Optional作为map方法返回值,否则返回空Optional。

map方法用来对Optional实例的值执行一系列操作。通过一组实现了Function接口的lambda表达式传入操作。如果你不熟悉Function接口,可以参考我的这篇博客。map方法示例如下:

//map方法执行传入的lambda表达式参数对Optional实例的值进行修改。
//为lambda表达式的返回值创建新的Optional实例作为map方法的返回值。
Optional<String> upperName = name.map((value) -> value.toUpperCase());
System.out.println(upperName.orElse("No value found"));
flatMap

如果有值,为其执行mapping函数返回Optional类型返回值,否则返回空Optional。flatMap与map(Funtion)方法类似,区别在于flatMap中的mapper返回值必须是Optional。调用结束时,flatMap不会对结果用Optional封装。

flatMap方法与map方法类似,区别在于mapping函数的返回值不同。map方法的mapping函数返回值可以是任何类型T,而flatMap方法的mapping函数必须是Optional。

参照map函数,使用flatMap重写的示例如下:

//flatMap与map(Function)非常类似,区别在于传入方法的lambda表达式的返回类型。
//map方法中的lambda表达式返回值可以是任意类型,在map函数返回之前会包装为Optional。 
//但flatMap方法中的lambda表达式返回值必须是Optionl实例。 
upperName = name.flatMap((value) -> Optional.of(value.toUpperCase()));
System.out.println(upperName.orElse("No value found"));//输出SANAULLA
filter

filter个方法通过传入限定条件对Optional实例的值进行过滤。文档描述如下:

如果有值并且满足断言条件返回包含该值的Optional,否则返回空Optional。

读到这里,可能你已经知道如何为filter方法传入一段代码。是的,这里可以传入一个lambda表达式。对于filter函数我们应该传入实现了Predicate接口的lambda表达式。如果你不熟悉Predicate接口,可以参考这篇文章。

现在我来看看filter的各种用法,下面的示例介绍了满足限定条件和不满足两种情况:

//filter方法检查给定的Option值是否满足某些条件。
//如果满足则返回同一个Option实例,否则返回空Optional。
Optional<String> longName = name.filter((value) -> value.length() > 6);
System.out.println(longName.orElse("The name is less than 6 characters"));//输出Sanaulla

//另一个例子是Optional值不满足filter指定的条件。
Optional<String> anotherName = Optional.of("Sana");
Optional<String> shortName = anotherName.filter((value) -> value.length() > 6);
//输出: name长度不足6字符
System.out.println(shortName.orElse("The name is less than 6 characters"));

范例

我们写一个范例演示下 Optional 类的使用

OptionalTester.java

                import java.util.Optional;
            
                public class OptionalTester
                {
            
                   public static void main(String args[]) {
                      OptionalTester tester = new OptionalTester();
                      Integer value1 = null;
                      Integer value2 = Integer.valueOf(10);
            
                      //Optional.ofNullable - allows passed parameter to be null.
                      Optional<Integer> a = Optional.ofNullable(value1);
            
                      //Optional.of - throws NullPointerException if passed parameter is null
                      Optional<Integer> b = Optional.of(value2);
                      System.out.println(tester.sum(a,b));
                   }
            
                   public Integer sum(Optional<Integer> a, Optional<Integer> b)
                   {
                      //Optional.isPresent - checks the value is present or not
            
                      System.out.println("First parameter is present: " + a.isPresent());
                      System.out.println("Second parameter is present: " + b.isPresent());
            
                      //Optional.orElse - returns the value if present otherwise returns
                      //the default value passed.
                      Integer value1 = a.orElse(Integer.valueOf(0));
            
                      //Optional.get - gets the value, value should be present
                      Integer value2 = b.get();
                      return value1 + value2;
                   }
                }

运行以上范例,输出结果如下

                [yufei@www.twle.cn helloworld]$ javac OptionalTester.java && java OptionalTester
                First parameter is present: false
                Second parameter is present: true
                10

一个综合例子

public class OptionalDemo {

  public static void main(String[] args) {
    //创建Optional实例,也可以通过方法返回值得到。
    Optional<String> name = Optional.of("Sanaulla");

    //创建没有值的Optional实例,例如值为'null'
    Optional empty = Optional.ofNullable(null);

    //isPresent方法用来检查Optional实例是否有值。
    if (name.isPresent()) {
      //调用get()返回Optional值。
      System.out.println(name.get());
    }

    try {
      //在Optional实例上调用get()抛出NoSuchElementException。
      System.out.println(empty.get());
    } catch (NoSuchElementException ex) {
      System.out.println(ex.getMessage());
    }

    //ifPresent方法接受lambda表达式参数。
    //如果Optional值不为空,lambda表达式会处理并在其上执行操作。
    name.ifPresent((value) -> {
      System.out.println("The length of the value is: " + value.length());
    });

    //如果有值orElse方法会返回Optional实例,否则返回传入的错误信息。
    System.out.println(empty.orElse("There is no value present!"));
    System.out.println(name.orElse("There is some value!"));

    //orElseGet与orElse类似,区别在于传入的默认值。
    //orElseGet接受lambda表达式生成默认值。
    System.out.println(empty.orElseGet(() -> "Default Value"));
    System.out.println(name.orElseGet(() -> "Default Value"));

    try {
      //orElseThrow与orElse方法类似,区别在于返回值。
      //orElseThrow抛出由传入的lambda表达式/方法生成异常。
      empty.orElseThrow(ValueAbsentException::new);
    } catch (Throwable ex) {
      System.out.println(ex.getMessage());
    }

    //map方法通过传入的lambda表达式修改Optonal实例默认值。 
    //lambda表达式返回值会包装为Optional实例。
    Optional<String> upperName = name.map((value) -> value.toUpperCase());
    System.out.println(upperName.orElse("No value found"));

    //flatMap与map(Funtion)非常相似,区别在于lambda表达式的返回值。
    //map方法的lambda表达式返回值可以是任何类型,但是返回值会包装成Optional实例。
    //但是flatMap方法的lambda返回值总是Optional类型。
    upperName = name.flatMap((value) -> Optional.of(value.toUpperCase()));
    System.out.println(upperName.orElse("No value found"));

    //filter方法检查Optiona值是否满足给定条件。
    //如果满足返回Optional实例值,否则返回空Optional。
    Optional<String> longName = name.filter((value) -> value.length() > 6);
    System.out.println(longName.orElse("The name is less than 6 characters"));

    //另一个示例,Optional值不满足给定条件。
    Optional<String> anotherName = Optional.of("Sana");
    Optional<String> shortName = anotherName.filter((value) -> value.length() > 6);
    System.out.println(shortName.orElse("The name is less than 6 characters"));

  }
}

上述代码输出如下:

Sanaulla
No value present
The length of the value is: 8
There is no value present!
Sanaulla
Default Value
Sanaulla
No value present in the Optional instance
SANAULLA
SANAULLA
Sanaulla
The name is less than 6 characters

在 Java 8 中提高 Null 的安全性
假设我们有一个像这样的类层次结构:

class Outer {
    Nested nested;
    Nested getNested() {
        return nested;
    }
}
class Nested {
    Inner inner;
    Inner getInner() {
        return inner;
    }
}
class Inner {
    String foo;
    String getFoo() {
        return foo;
    }
}

解决这种结构的深层嵌套路径是有点麻烦的。我们必须编写一堆 null 检查来确保不会导致一个 NullPointerException:

Outer outer = new Outer();
if (outer != null && outer.nested != null && outer.nested.inner != null) {
    System.out.println(outer.nested.inner.foo);
}

我们可以通过利用 Java 8 的 Optional 类型来摆脱所有这些 null 检查。map 方法接收一个 Function 类型的 lambda 表达式,并自动将每个 function 的结果包装成一个 Optional 对象。这使我们能够在一行中进行多个 map 操作。Null 检查是在底层自动处理的。

Optional.of(new Outer())
    .map(Outer::getNested)
    .map(Nested::getInner)
    .map(Inner::getFoo)
    .ifPresent(System.out::println);

还有一种实现相同作用的方式就是通过利用一个 supplier 函数来解决嵌套路径的问题:

Outer obj = new Outer();
resolve(() -> obj.getNested().getInner().getFoo());
    .ifPresent(System.out::println);

调用 obj.getNested().getInner().getFoo()) 可能会抛出一个 NullPointerException 异常。在这种情况下,该异常将会被捕获,而该方法会返回 Optional.empty()。

public static <T> Optional<T> resolve(Supplier<T> resolver) {
    try {
        T result = resolver.get();
        return Optional.ofNullable(result);
    }
    catch (NullPointerException e) {
        return Optional.empty();
    }
}

请记住,这两个解决方案可能没有传统 null 检查那么高的性能。不过在大多数情况下不会有太大问题。

默认方法

总所周知,在 Java 7 和之前的版本中,接口 interface 是不能包含具体的方法实现的。

比如,下面的代码,是会报错的。

public class InterfaceDefaultMethodTester
{

    public static void main(String[] args)
    {
    }

    interface Greeter {
        public void greeter(String name)
        {
            Syste.out.println("你好," + name );
        }
    }
}

运行结果如下

                InterfaceDefaultMethodTester.java:10: 错误: 接口抽象方法不能带有主体
                        {
                        ^
                1 个错误

如果一个接口有多个实现,那么每个实现都要重复的一遍一遍的实现接口中的所有方法,岂不是很痛苦。

在 Java 7 及以前的版本,对于一个接口有多个实现的时候,我们通常的做法就是让所有的实现继承另一个基础类,然后在这个基础类中实现这个方法。

这就是,为什么 Java 中的 I/O 那么多类的原因,一个庞大的家族体系,每次看到我都头疼。

但是 Java 8 中,我想应该是 Java 8 核心开发者们也厌倦了这种不断的重复实现接口方法和庞大的类家族体系。竟然在 Java 8 中为接口提供了一个新的功能,允许某个接口方法有个默认实现。

Java 8 接口的默认方法

Java 8 为 接口 ( interface ) 中引入了 「 默认方法 」( default method ) 实现这个新的概念。

但是,引入的初衷竟然是不是为了解救一个接口多个实现的痛苦,而是为了向后兼容,以便旧接口也可以使用 Java 8 的 lambda 表达式 功能。

例如,Java 8 新引入的 forEach 这个功能,其实,List 或 Collection 接口没有声明和实现 forEach 方法。因为,添加此类方法将简单地破坏集合框架实现。

P.S 另一个重要原因,我想,是因为 Java 集合太过庞大,每个都改过去你们应该会手软才对。

既然不能每个类都改过去,那怎么办呢?

当然是从它们都实现了的共同的祖先处想办法啦。

跳来跳去,最后选中了接口 interface 。

P.S Java 是基于接口的编程,这个,大家应该没意见吧。除了主入口类,如果一个类没有实现个把接口,都会被认定为格格不入的。

那我们知道,Java 中的接口是不能有具体实现的。哦,不是,是在现行体系下,Java 中没有哪个语法允许接口的方法有具体的实现。

但是,Java 8 又非常需要这个功能, 那要怎么办呢?

好吧,拍一下脑袋,灵光一闪,我们可以新增加一个关键字,比如 default ,用于标识这个方法是可以有具体的实现。

有了 default 关键字,我们就可以在所有集合都实现的接口 Collection 中添加一个 forEach 方法啦。

这是一个非常重要的新功能,它的出现,Java 8 及以后的版本,添加新功能的速度明显加快了很多

Java 8 接口默认方法语法

                public interface Greeter {
            
                   default void greet() {
                      System.out.println("你好,我时简单教程!");
                   }
                }

从语法中可以看到,一个接口默认方法和普通的接口方法声明有两个不同点:

  • 接口默认方法可以有具体实现
  • 接口默认方法需要使用 default 关键字修饰。

Java 8 接口默认方法特征

一个接口可以有任意数量的默认方法,也可以没有默认方法
如果一个类实现的两个接口都有一个同名的默认方法,那么该类必须自己实现同样的方法,然后在实现内部可以调用相应接口的方法
范例
我们写一些范例来演示下 Java 8 中的接口默认方法的特征

接口默认方法
我们写一个范例简单演示下接口默认方法的使用

                public class InterfaceDefaultMethodTester
                {
            
                    public static void main(String[] args)
                    {
                        Greeter gt = new Greeter(){};
                        gt.greeter("简单教程");
                    }
            
                    interface Greeter {
                        default public void greeter(String name)
                        {
                            System.out.println("你好," + name );
                        }
                    }
                }

运行结果如下

                你好,简单教程

一个接口可以有多个默认方法

                public class InterfaceDefaultMethodTester
                {
            
                    public static void main(String[] args)
                    {
                        Greeter gt = new Greeter(){};
                        gt.greeter("简单教程");
                        gt.greeterEn("简单教程");
                    }
            
                    interface Greeter {
                        default public void greeter(String name)
                        {
                            System.out.println("你好," + name );
                        }
            
                        default public void greeterEn(String name)
                        {
                            System.out.println("Hello," + name );
                        }
                    }
                }

运行结果如下

                你好,简单教程
                Hello,简单教程

一个类实现了多个具有同名的默认方法接口
如果一个类实现了两个或两个以上的接口,而这些接口有两个或两个以上实现了相同的方法名的默认方法,结果会怎么样呢? 比如下面这个范例

                public class InterfaceDefaultMethodTester
                {
            
                    public static void main(String[] args)
                    {
                        InterfaceDefaultMethodTester tester = new InterfaceDefaultMethodTester();
                        tester.run();
                    }
            
                    public void run()
                    {
                        Greeter gt = new Hello();
                        gt.greeter("简单教程");
                    }
            
                    class Hello implements Greeter,GreeterEn {}
            
                    interface Greeter {
                        default public void greeter(String name)
                        {
                            System.out.println("你好," + name );
                        }
                    }
            
                    interface GreeterEn {
            
                        default public void greeter(String name)
                        {
                            System.out.println("Hello," + name );
                        }
                    }
                }

运行结果如下

                InterfaceDefaultMethodTester.java:16: 错误: 类 InterfaceDefaultMethodTester.Hello从类型 Greeter 和 GreeterEn 中继承了greeter(String) 的不相关默认值
                    class Hello implements Greeter,GreeterEn {}
                    ^
                1 个错误

多重继承的冲突
由于同一个方法可以从不同接口引入,自然而然的会有冲突的现象,默认方法判断冲突的规则如下:

  1. 一个声明在类里面的方法优先于任何默认方法(classes always win)

  2. 否则,则会优先选取路径最短的。
    修复这个错误,最简单的方法就是类自己实现一个相同的方法

                public class InterfaceDefaultMethodTester
                {
            
                    public static void main(String[] args)
                    {
                        InterfaceDefaultMethodTester tester = new InterfaceDefaultMethodTester();
                        tester.run();
                    }
            
                    public void run()
                    {
                        Greeter gt = new Hello();
                        gt.greeter("简单教程");
                    }
            
                    class Hello implements Greeter,GreeterEn {
                        public void greeter(String name)
                        {
                            System.out.println("你好," + name );
                        }
                    }
            
                    interface Greeter {
                        default public void greeter(String name)
                        {
                            System.out.println("你好," + name );
                        }
                    }
            
                    interface GreeterEn {
            
                        default public void greeter(String name)
                        {
                            System.out.println("Hello," + name );
                        }
                    }
                }

运行结果如下

                你好,简单教程

当然了,还可以调用相关接口的默认方法

                public class InterfaceDefaultMethodTester
                {
            
                    public static void main(String[] args)
                    {
                        InterfaceDefaultMethodTester tester = new InterfaceDefaultMethodTester();
                        tester.run();
                    }
            
                    public void run()
                    {
                        Greeter gt = new Hello();
                        gt.greeter("简单教程");
                    }
            
                    class Hello implements Greeter,GreeterEn {
                        public void greeter(String name)
                        {
                            Greeter.super.greeter(name);
                        }
                    }
            
                    interface Greeter {
                        default public void greeter(String name)
                        {
                            System.out.println("你好," + name );
                        }
                    }
            
                    interface GreeterEn {
            
                        default public void greeter(String name)
                        {
                            System.out.println("Hello," + name );
                        }
                    }
                }

运行结果如下

                你好,简单教程

类型注解

什么是类型注解
注解大家都知道,从java5开始加入这一特性,发展到现在已然是遍地开花,在很多框架中得到了广泛的使用,用来简化程序中的配置。那充满争议的类型注解究竟是什么? 复杂还是便捷?

  1. 在java 8之前,注解只能是在声明的地方所使用,比如类,方法,属性;
  2. java 8里面,注解可以应用在任何地方,比如:
    创建类实例
new @Interned MyObject();

类型映射

myString = (@NonNull String) str;

implements 语句中

class UnmodifiableList<T> implements @Readonly List<@Readonly T> {}

throw exception声明

void monitorTemperature() throws @Critical TemperatureException {}

需要注意的是,类型注解只是语法而不是语义,并不会影响java的编译时间,加载时间,以及运行时间,也就是说,编译成class文件的时候并不包含类型注解。

类型注解的作用

先看看下面代码

Collections.emptyList().add("One");
int i=Integer.parseInt("hello");
System.console().readLine();

上面的代码编译是通过的,但运行是会分别报UnsupportedOperationException;NumberFormatException;NullPointerException异常,这些都是runtime error;

类型注解被用来支持在Java的程序中做强类型检查。配合插件式的check framework,可以在编译的时候检测出runtime error,以提高代码质量。这就是类型注解的作用了。

check framework是第三方工具,配合Java的类型注解效果就是1+1>2。它可以嵌入到javac编译器里面,可以配合ant和maven使用, 地址是http://types.cs.washington.edu/checker-framework/。check framework可以找到类型注解出现的地方并检查,举个简单的例子:

import checkers.nullness.quals.*;
public class GetStarted {
    void sample() {
        @NonNull Object ref = new Object();
    }
}

使用javac编译上面的类

javac -processor checkers.nullness.NullnessChecker GetStarted.java

编译是通过,但如果修改成

@NonNull Object ref = null;

再次编译,则出现

GetStarted.java:5: incompatible types.
found   : @Nullable <nulltype>
required: @NonNull Object
        @NonNull Object ref = null;
                              ^
1 error

类型注解向下兼容的解决方案

如果你不想使用类型注解检测出来错误,则不需要processor,直接javac GetStarted.java是可以编译通过的,这是在java 8 with Type Annotation Support版本里面可以,但java 5,6,7版本都不行,因为javac编译器不知道@NonNull是什么东西,但check framework 有个向下兼容的解决方案,就是将类型注解nonnull用/**/注释起来,比如上面例子修改为

import checkers.nullness.quals.*;
public class GetStarted {
    void sample() {
        /*@NonNull*/ Object ref = null;
    }
}

这样javac编译器就会忽略掉注释块,但用check framework里面的javac编译器同样能够检测出nonnull错误。通过类型注解+check framework我们可以看到,现在runtime error可以在编译时候就能找到。

关于JSR 308

JSR 308想要解决在Java 1.5注解中出现的两个问题:

  • 在句法上对注解的限制: 只能把注解写在声明的地方
  • 类型系统在语义上的限制: 类型系统还做不到预防所有的bug

JSR 308 通过如下方法解决上述两个问题:

  • 对Java语言的句法进行扩充,允许注解出现在更多的位置上。包括: 方法接收器(method receivers,译注: 例public int size() @Readonly { … }),泛型参数,数组,类型转换,类型测试,对象创建,类型参数绑定,类继承和throws子句。其实就是类型注解,现在是java 8的一个特性
  • 通过引入可插拔的类型系统(pluggable type systems)能够创建功能更强大的注解处理器。类型检查器对带有类型限定注解的源码进行分析,一旦发现不匹配等错误之处就会产生警告信息。其实就是check framework
    对JSR308,有人反对,觉得更复杂更静态了,比如

@NotEmpty List<@NonNull String> strings = new ArrayList<@NonNull String>()>
换成动态语言为

var strings = ["one", "two"];

有人赞成,说到底,代码才是“最根本”的文档。代码中包含的注解清楚表明了代码编写者的意图。当没有及时更新或者有遗漏的时候,恰恰是注解中包含的意图信息,最容易在其他文档中被丢失。而且将运行时的错误转到编译阶段,不但可以加速开发进程,还可以节省测试时检查bug的时间。

总结

并不是人人都喜欢这个特性,特别是动态语言比较流行的今天,所幸,java 8并不强求大家使用这个特性,反对的人可以不使用这一特性,而对代码质量有些要求比较高的人或公司可以采用JSR 308,毕竟代码才是“最基本”的文档,这句话我是赞同的。虽然代码会增多,但可以使你的代码更具有表达意义。对这个特性有何看法,大家各抒己见。。。。

重复注解

什么是重复注解

允许在同一申明类型(类,属性,或方法)的多次使用同一个注解

JDK8之前
java 8之前也有重复使用注解的解决方案,但可读性不是很好,比如下面的代码:

public @interface Authority {
     String role();
}

public @interface Authorities {
    Authority[] value();
}

public class RepeatAnnotationUseOldVersion {

    @Authorities({@Authority(role="Admin"),@Authority(role="Manager")})
    public void doSomeThing(){
    }
}

由另一个注解来存储重复注解,在使用时候,用存储注解Authorities来扩展重复注解。

Jdk8重复注解

我们再来看看java 8里面的做法:

@Repeatable(Authorities.class)
public @interface Authority {
     String role();
}

public @interface Authorities {
    Authority[] value();
}

public class RepeatAnnotationUseNewVersion {
    @Authority(role="Admin")
    @Authority(role="Manager")
    public void doSomeThing(){ }
}

不同的地方是,创建重复注解Authority时,加上@Repeatable,指向存储注解Authorities,在使用时候,直接可以重复使用Authority注解。从上面例子看出,java 8里面做法更适合常规的思维,可读性强一点

总结

JEP120没有太多内容,是一个小特性,仅仅是为了提高代码可读性。这次java 8对注解做了2个方面的改进(JEP 104,JEP120),相信注解会比以前使用得更加频繁了。

LocalDateTime

Java8之前的Date有哪些槽点

Tiago Fernandez做过一次投票,选举最烂的JAVA API,排第一的EJB2.X,第二的就是日期API。

槽点一
最开始的时候,Date既要承载日期信息,又要做日期之间的转换,还要做不同日期格式的显示,职责较繁杂(不懂单一职责,你妈妈知道吗? 纯属恶搞~哈哈)

后来从JDK 1.1 开始,这三项职责分开了:

  • 使用Calendar类实现日期和时间字段之间转换;
  • 使用DateFormat类来格式化和分析日期字符串;
  • 而Date只用来承载日期和时间信息。
    原有Date中的相应方法已废弃。不过,无论是Date,还是Calendar,都用着太不方便了,这是API没有设计好的地方。

槽点二
坑爹的year和month

Date date = new Date(2012,1,1);
System.out.println(date);

输出

Thu Feb 01 00:00:00 CST 3912

观察输出结果,year是2012+1900,而month,月份参数我不是给了1吗? 怎么输出二月(Feb)了?

应该曾有人告诉你,如果你要设置日期,应该使用 java.util.Calendar,像这样…

Calendar calendar = Calendar.getInstance();
calendar.set(2013, 8, 2);

这样写又不对了,calendar的month也是从0开始的,表达8月份应该用7这个数字,要么就干脆用枚举

calendar.set(2013, Calendar.AUGUST, 2);

注意上面的代码,Calendar年份的传值不需要减去1900(当然月份的定义和Date还是一样),这种不一致真是让人抓狂!

有些人可能知道,Calendar相关的API是IBM捐出去的,所以才导致不一致。

槽点三
java.util.Date与java.util.Calendar中的所有属性都是可变的

下面的代码,计算两个日期之间的天数….

public static void main(String[] args) {
    Calendar birth = Calendar.getInstance();
    birth.set(1975, Calendar.MAY, 26);
    Calendar now = Calendar.getInstance();
    System.out.println(daysBetween(birth, now));
    System.out.println(daysBetween(birth, now)); // 显示 0? 
 }  

public static long daysBetween(Calendar begin, Calendar end) {
    long daysBetween = 0;
    while(begin.before(end)) {
        begin.add(Calendar.DAY_OF_MONTH, 1);
        daysBetween++;
    }
    return daysBetween;
}

daysBetween有点问题,如果连续计算两个Date实例的话,第二次会取得0,因为Calendar状态是可变的,考虑到重复计算的场合,最好复制一个新的Calendar

public static long daysBetween(Calendar begin, Calendar end) {
    Calendar calendar = (Calendar) begin.clone(); // 复制
    long daysBetween = 0;
    while(calendar.before(end)) {
        calendar.add(Calendar.DAY_OF_MONTH, 1);
        daysBetween++;
    }
    return daysBetween;
}

槽点四
SimpleDateTimeFormat是非线程安全的。

Java8时间和日期

类概览

Java 8仍然延用了ISO的日历体系,并且与它的前辈们不同,java.time包中的类是不可变且线程安全的。新的时间及日期API位于java.time包中,下面是里面的一些关键的类:

  • Instant——它代表的是时间戳
  • LocalDate——不包含具体时间的日期,比如2014-01-14。它可以用来存储生日,周年纪念日,入职日期等。
  • LocalTime——它代表的是不含日期的时间
  • LocalDateTime——它包含了日期及时间,不过还是没有偏移信息或者说时区。
  • ZonedDateTime——这是一个包含时区的完整的日期时间,偏移量是以UTC/格林威治时间为基准的。
    新的库还增加了ZoneOffset及Zoned,可以为时区提供更好的支持。有了新的DateTimeFormatter之后日期的解析及格式化也变得焕然一新了。

方法概览
该包的API提供了大量相关的方法,这些方法一般有一致的方法前缀:

of: 静态工厂方法。
parse: 静态工厂方法,关注于解析。
get: 获取某些东西的值。
is: 检查某些东西的是否是true。
with: 不可变的setter等价物。
plus: 加一些量到某个对象。
minus: 从某个对象减去一些量。
to: 转换到另一个类型。
at: 把这个对象与另一个对象组合起来,例如: date.atTime(time)。

https://mp.weixin.qq.com/s/mOKbrEkY4xmPilCSwjMwaA#:~:text=%E7%84%95%E7%84%B6%E4%B8%80%E6%96%B0%E4%BA%86%E3%80%82-,%E6%96%B9%E6%B3%95%E6%A6%82%E8%A7%88,-%E8%AF%A5%E5%8C%85%E7%9A%84

本地日期时间 API

Java 8 为处理本地的日期时间提供了三个类 LocalDate 、LocalTime 和 LocalDateTime。分别用于处理 本地日期 、 本地时间 和 本地日期时间 。

当使用这三个类时,开发者并不需要关心时区是什么。因为它默认使用的是操作系统的时区。

比如,可以使用 LocalDateTime.now() 方法返回当前的日期时间。

Java8Tester.java

                import java.time.LocalDateTime;
            
                public class Java8Tester {
            
                   public static void main(String args[]) {
                      Java8Tester tester = new Java8Tester();
                      tester.run();
                   }
            
                   public void run() {
                      LocalDateTime currentTime = LocalDateTime.now();
                      System.out.println("当前日期时间: " + currentTime);
                   }
                }

运行结果如下

                当前日期时间: 2018-10-08T21:47:12.488370

比如,我们可以调用 LocalDateTime 对象的 toLocalDate() 方法和 toLocalTime() 分别返回当前的日期和当前的时间,也就是 LocalDate 和 LocalTime 两个类的实例

                import java.time.LocalDate;
                import java.time.LocalTime;
                import java.time.LocalDateTime;
            
                public class Java8Tester {
            
                   public static void main(String args[]) {
                      Java8Tester tester = new Java8Tester();
                      tester.run();
                   }
            
                   public void run() {
                      LocalDateTime currentTime = LocalDateTime.now();
                      System.out.println("当前日期时间: " + currentTime);
            
                      LocalDate date1 = currentTime.toLocalDate();
                      System.out.println("当前日期: " + date1);
            
                      LocalTime time1 = currentTime.toLocalTime();
                      System.out.println("当前时间: " + time1);
                   }
                }

运行结果如下

                当前日期时间: 2018-10-08T21:50:23.181839
                当前日期: 2018-10-08
                当前时间: 21:50:23.181839

比如我们可以调用 LocalDateTime 对象的 getMonth() 方法返回当前的月份,调用 getDayOfMonth() 返回当前的日期,调用 getSecond() 返回当前时间的秒数

                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 tester = new Java8Tester();
                      tester.run();
                   }
            
                   public void run() {
                      LocalDateTime currentTime = LocalDateTime.now();
                      System.out.println("当前日期时间: " + currentTime);
            
                      Month month = currentTime.getMonth();
                      System.out.println("当前月份: " + month);
            
                      int day = currentTime.getDayOfMonth();
                      System.out.println("当前月中的第几天: " + day);
            
                      int seconds = currentTime.getSecond();
                      System.out.println("当前秒数: " + seconds);
                   }
                }

运行结果如下

                当前日期时间: 2018-10-08T21:54:40.534997
                当前月份: OCTOBER
                当前月中的第几天: 8
                当前秒数: 40

比如我们可以调用 LocalDateTime 对象的 withDayOfMonth() 修改日并返回一个新的实例,调用 withYear() 修改年,调用其它 with* 方法修改其它属性。

这些 with 方法都是返回新的实例,而原来的实例并不会改变。

                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 tester = new Java8Tester();
                      tester.run();
                   }
            
                   public void run() {
                      LocalDateTime currentTime = LocalDateTime.now();
                      System.out.println("当前日期时间: " + currentTime);
            
                      LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
                      System.out.println("新的日期时间: " + date2);
            
                      System.out.println("原来的日期时间: " + currentTime);
                   }
                }

运行结果如下

                [yufei@www.twle.cn helloworld]$ javac Java8Tester.java && java Java8Tester
                当前日期时间: 2018-10-08T21:58:14.011932
                新的日期时间: 2012-10-10T21:58:14.011932
                原来的日期时间: 2018-10-08T21:58:14.011932

可以发现原先的实例并没有被修改。

同时,新的日期时间 API 还大量引入了 of() 方法,比如我们可以调用 LocalDate.of() 方法创建一个日期实例,调用 LocalTime.of() 方法创建一个时间实例。

                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 tester = new Java8Tester();
                      tester.run();
                   }
            
                   public void run() {
            
                      // 2018-10-01
                      LocalDate date = LocalDate.of(2018, Month.OCTOBER, 01);
                      System.out.println("日期是: " + date);
            
                      // 22:15
                      LocalTime time = LocalTime.of(22, 15);
                      System.out.println("时间是: " + time);
                   }
                }

运行结果如下

                [yufei@www.twle.cn helloworld]$ javac Java8Tester.java && java Java8Tester
                日期是: 2018-10-01
                时间是: 22:15

我们还可以调用 LocalDateTime.parse() 、LocalDate.parse() 和 LocalTime.parse() 方法解析字符串格式的日期时间、日期和时间。

                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 tester = new Java8Tester();
                      tester.run();
                   }
            
                   public void run() {
            
                      LocalDateTime datetime = LocalDateTime.parse("2012-10-10T21:58:00");
                      System.out.println("日期时间是:" + datetime);
            
                      LocalDate date = LocalDate.parse("2012-10-10");
                      System.out.println("日期是: " + date);
            
                      LocalTime time = LocalTime.parse("21:58:01");
                      System.out.println("时间是: " + time);
                   }
                }

运行结果如下

                [yufei@www.twle.cn helloworld]$ javac Java8Tester.java && java Java8Tester
                日期时间是:2012-10-10T21:58
                日期是: 2012-10-10
                时间是: 21:58
                
时区日期格式

我同时也发现,这三个类没有任何时区相关的信息,但也不能说它们没处理时区,而只能说它们有选择的隐藏了时区的处理。它们内部会使用操作系统当前的时区。

以此同时,Java 在 java.time 包中也提供了几个类用于处理需要关注时区的日期时间 API。它们是 java.time.ZonedDateTime 和 java.time.ZoneId。前者用于处理需要时区的日期时间,后者用于处理时区。

ZonedDateTime 和 LocalDateTime 类似,几乎有着相同的 API。从某些方面说,ZonedLocalTime 如果不传递时区信息,那么它会默认使用操作系统的时区,这样,结果其实和 LocalDateTime 是类似的。

比如,我们可以使用 ZonedDateTime 的 now() 方法返回当前时区 ( 操作系统时区 ) 的日期时间,调用 parse() 方法可以将一个包含了时区信息的字符串格式的日期时间转化为一个 ZonedDateTime 实例。

Java8Tester.java

                import java.time.ZonedDateTime;
            
                public class Java8Tester {
            
                   public static void main(String args[]) {
                      Java8Tester tester = new Java8Tester();
                      tester.run();
                   }
            
                   public void run() {
            
                      ZonedDateTime now = ZonedDateTime.now();
                      System.out.println("当前日期时间是:" + now);
            
                      ZonedDateTime datetime = ZonedDateTime.parse("2012-10-10T21:58:00+08:00");
                      System.out.println("日期时间是:" + datetime);
                   }
                }

运行结果如下

                [yufei@www.twle.cn helloworld]$ javac Java8Tester.java && java Java8Tester
                当前日期时间是:2018-10-08T22:21:56.806597+08:00[Asia/Shanghai]
                日期时间是:2012-10-10T21:58+08:00

我们还可以调用 ZonedDateTime 对象的 toLocalDate() 和 toLocalTime() 方法将获取该实例的转换为本地的日期和时间

                import java.time.LocalTime;
                import java.time.LocalDate;
                import java.time.ZonedDateTime;
            
                public class Java8Tester {
            
                   public static void main(String args[]) {
                      Java8Tester tester = new Java8Tester();
                      tester.run();
                   }
            
                   public void run() {
            
                      ZonedDateTime now = ZonedDateTime.now();
                      System.out.println("当前日期时间是:" + now);
            
                      LocalDate date = now.toLocalDate();
                      System.out.println("本地日期是:" + now);
            
                      LocalTime time = now.toLocalTime();
                      System.out.println("本地时间是:" + time);
            
                   }
                }

运行结果如下

                [yufei@www.twle.cn helloworld]$ javac Java8Tester.java && java Java8Tester
                当前日期时间是:2018-10-08T22:28:10.389487+08:00[Asia/Shanghai]
                本地日期是:2018-10-08T22:28:10.389487+08:00[Asia/Shanghai]
                本地时间是:22:28:10.389487

P.S 这个返回值好诡异,为啥 toLocalDate() 还返回了时间?

处理时区

时区相关的信息,我们可以使用 ZoneId 类来处理。

比如可以调用 ZoneId 类的静态方法 systemDefault() 返回当前的时区。

                import java.time.ZonedDateTime;
                import java.time.ZoneId;
            
                public class Java8Tester {
            
                   public static void main(String args[]) {
                      Java8Tester tester = new Java8Tester();
                      tester.run();
                   }
            
                   public void run() {
            
                      ZoneId currentZone = ZoneId.systemDefault();
                      System.out.println("当前时区是: " + currentZone);
                   }
                }

运行结果如下

                当前时区是: Asia/Shanghai

我们还可以调用 ZonedDateTime 实例的 getZone() 方法获取实例所在的时区

                import java.time.ZonedDateTime;
                import java.time.ZoneId;
            
                public class Java8Tester {
            
                   public static void main(String args[]) {
                      Java8Tester tester = new Java8Tester();
                      tester.run();
                   }
            
                   public void run() {
            
                      ZonedDateTime now = ZonedDateTime.now();
                      System.out.println("当前时区是: " + now.getZone());
                   }
                }

运行结果如下

                当前时区是: Asia/Shanghai
格式化

Java 8 似乎也对 java.text.SimpleDateFormat 也不太满意,竟然重新创建了一个 java.time.format 包,该包下包含了几个类和枚举用于格式化日期时间。

java.time.format 包

java.time.format 包提供了以下几个类用于格式化日期时间

说明
DateTimeFormatter用于打印和解析日期时间对象的格式化程序
DateTimeFormatterBuilder创建日期时间格式化样式的构建器
DecimalStyle日期和时间格式中使用的本地化十进制样式

java.time.format 包还提供了以下几个枚举,包含了常见的几种日期时间格式。

枚举说明
FormatStyle包含了本地化日期,时间或日期时间格式器的样式的枚举
ResolverStyle包含了解决日期和时间的不同方法的枚举
SignStyle包含了如何处理正/负号的方法的枚举
TextStyle包含了文本格式和解析的样式的枚举
DateTimeFormatter 类

DateTimeFormatter 类格式化日期时间的最重要的类,该类是一个最终类,只能实例化,不能被扩展和继承。

DateTimeFormatter 类的定义如下

    public final class DateTimeFormatter extends Object

DateTimeFormatter 类用于打印和解析日期时间对象的格式化器。此类提供打印和解析的主要应用程序入口点,并提供 DateTimeFormatter 的常见模式

  • 使用预定义的常量,比如 ISO_LOCAL_DATE
  • 使用模式字母,例如 uuuu-MMM-dd
  • 使用本地化样式,例如 long 或 medium

所有的日期时间类,包括本地日期时间和包含时区的日期时间类,都提供了两个重要的方法

  • 一个用于格式化,format(DateTimeFormatter formatter)
  • 另一个用于解析,parse(CharSequence text, DateTimeFormatter formatter)
    下面,我们写几个示例来演示下这两个方法,并演示下如和使用 DateTimeFormatter 类

Java8Tester

                import java.time.ZonedDateTime;
                import java.time.format.DateTimeFormatter;
            
                public class Java8Tester {
            
                   public static void main(String args[]) {
                      Java8Tester tester = new Java8Tester();
                      tester.run();
                   }
            
                   public void run() {
            
                      ZonedDateTime now = ZonedDateTime.now();
                      System.out.println("当前时间是: " + now);
            
                      System.out.println("另一种表示形式:" + now.format(DateTimeFormatter.RFC_1123_DATE_TIME));
                   }
                }

运行结果如下

                [yufei@www.twle.cn helloworld]$ javac Java8Tester.java && java Java8Tester
                当前时间是: 2018-10-08T23:02:03.133357+08:00[Asia/Shanghai]
                另一种表示形式:Mon, 8 Oct 2018 23:02:03 +0800

我们还可以调用 DateTimeFormatter.ofPattern() 方法创建自己的日期时间格式,例如

                import java.time.ZonedDateTime;
                import java.time.format.DateTimeFormatter;
            
                public class Java8Tester {
            
                   public static void main(String args[]) {
                      Java8Tester tester = new Java8Tester();
                      tester.run();
                   }
            
                   public void run() {
            
                      ZonedDateTime now = ZonedDateTime.now();
                      System.out.println("当前时间是: " + now);
            
                      DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd H:m:s");
                      System.out.println("另一种表示形式:" + now.format(formatter));
                   }
                }

运行结果如下

                [yufei@www.twle.cn helloworld]$ javac Java8Tester.java && java Java8Tester
                当前时间是: 2018-10-08T23:04:49.925018+08:00[Asia/Shanghai]
                另一种表示形式:2018/10/08 23:4:49

当然了,我们可以调用 LocalDateTime 类的静态方法 parse() 将我们刚刚自定义的日期时间格式给解析回来

                import java.time.LocalDateTime;
                import java.time.ZonedDateTime;
                import java.time.format.DateTimeFormatter;
            
                public class Java8Tester {
            
                   public static void main(String args[]) {
                      Java8Tester tester = new Java8Tester();
                      tester.run();
                   }
            
                   public void run() {
            
                      ZonedDateTime now = ZonedDateTime.now();
                      System.out.println("当前时间是: " + now);
            
                      DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd H:m:s");
                      String text = now.format(formatter);
                      System.out.println("另一种表示形式:" + text );
            
                      LocalDateTime parsed = LocalDateTime.parse(text, formatter);
                      System.out.println("解析后:" + parsed );
                   }
                }

运行结果如下

            [yufei@www.twle.cn helloworld]$ javac Java8Tester.java && java Java8Tester
            当前时间是: 2018-10-08T23:10:00.979253+08:00[Asia/Shanghai]
            另一种表示形式:2018/10/08 23:10:0
            解析后:2018-10-08T23:10
其它语言时间

日期与时间处理API,在各种语言中,可能都只是个不起眼的API,如果你没有较复杂的时间处理需求,可能只是利用日期与时间处理API取得系统时间,简单做些显示罢了,然而如果认真看待日期与时间,其复杂程度可能会远超过你的想象,天文、地理、历史、政治、文化等因素,都会影响到你对时间的处理。所以在处理时间上,最好选用JSR310(如果你用java8的话就实现310了),或者Joda-Time。

不止是java面临时间处理的尴尬,其他语言同样也遇到过类似的问题,比如

Arrow: Python 中更好的日期与时间处理库
Moment.js: JavaScript 中的日期库
Noda-Time: .NET 阵营的 Joda-Time 的复制

总结

看完了这些例子后,我相信你已经对Java 8这套新的时间日期API有了一定的了解了。现在我们来回顾下关于这个新的API的一些关键的要素。

它提供了javax.time.ZoneId用来处理时区。
它提供了LocalDate与LocalTime类 Java 8中新的时间与日期API中的所有类都是不可变且线程安全的,这与之前的Date与Calendar API中的恰好相反,那里面像java.util.Date以及SimpleDateFormat这些关键的类都不是线程安全的。
新的时间与日期API中很重要的一点是它定义清楚了基本的时间与日期的概念,比方说,瞬时时间,持续时间,日期,时间,时区以及时间段。它们都是基于ISO日历体系的。
每个Java开发人员都应该至少了解这套新的API中的这五个类:

  • Instant 它代表的是时间戳,比如2014-01-14T02:20:13.592Z,这可以从java.time.Clock类中获取,像这样: Instant current = Clock.system(ZoneId.of(“Asia/Tokyo”)).instant();
  • LocalDate 它表示的是不带时间的日期,比如2014-01-14。它可以用来存储生日,周年纪念日,入职日期等。
  • LocalTime – 它表示的是不带日期的时间 LocalDateTime – 它包含了时间与日期,不过没有带时区的偏移量 ZonedDateTime – 这是一个带时区的完整时间,它根据UTC/格林威治时间来进行时区调整
    这个库的主包是java.time,里面包含了代表日期,时间,瞬时以及持续时间的类。它有两个子package,一个是java.time.foramt,这个是什么用途就很明显了,还有一个是java.time.temporal,它能从更低层面对各个字段进行访问。
  • 时区指的是地球上共享同一标准时间的地区。每个时区都有一个唯一标识符,同时还有一个地区/城市(Asia/Tokyo)的格式以及从格林威治时间开始的一个偏移时间。比如说,东京的偏移时间就是+09:00。OffsetDateTime类实际上包含了LocalDateTime与ZoneOffset。它用来表示一个包含格林威治时间偏移量(+/-小时: 分,比如+06:00或者 -08: 00)的完整的日期(年月日)及时间(时分秒,纳秒)。
  • DateTimeFormatter类用于在Java中进行日期的格式化与解析。与SimpleDateFormat不同,它是不可变且线程安全的,如果需要的话,可以赋值给一个静态变量。DateTimeFormatter类提供了许多预定义的格式器,你也可以自定义自己想要的格式。当然了,根据约定,它还有一个parse()方法是用于将字符串转换成日期的,如果转换期间出现任何错误,它会抛出DateTimeParseException异常。类似的,DateFormatter类也有一个用于格式化日期的format()方法,它出错的话则会抛出DateTimeException异常。

再说一句,“MMM d yyyy”与“MMm dd yyyy”这两个日期格式也略有不同,前者能识别出”Jan 2 2014″与”Jan 14 2014″这两个串,而后者如果传进来的是”Jan 2 2014″则会报错,因为它期望月份处传进来的是两个字符。为了解决这个问题,在天为个位数的情况下,你得在前面补0,比如”Jan 2 2014″应该改为”Jan 02 2014″。

类型判断

简单理解泛型

泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。通俗点将就是“类型的变量”。这种类型变量可以用在类、接口和方法的创建中。

理解Java泛型最简单的方法是把它看成一种便捷语法,能节省你某些Java类型转换(casting)上的操作:

List<Apple> box = new ArrayList<Apple>();
box.add(new Apple());
Apple apple =box.get(0);

上面的代码自身已表达的很清楚: box是一个装有Apple对象的List。get方法返回一个Apple对象实例,这个过程不需要进行类型转换。没有泛型,上面的代码需要写成这样:

Apple apple = (Apple)box.get(0);

泛型的尴尬

泛型的最大优点是提供了程序的类型安全同时可以向后兼容,但也有尴尬的地方,就是每次定义时都要写明泛型的类型,这样显示指定不仅感觉有些冗长,最主要是很多程序员不熟悉泛型,因此很多时候不能够给出正确的类型参数,现在通过编译器自动推断泛型的参数类型,能够减少这样的情况,并提高代码可读性。

java7的泛型类型推断改进

在以前的版本中使用泛型类型,需要在声明并赋值的时候,两侧都加上泛型类型。例如:

Map<String, String> myMap = new HashMap<String, String>();

你可能觉得:老子在声明变量的的时候已经指明了参数类型,为毛还要在初始化对象时再指定? 幸好,在Java SE 7中,这种方式得以改进,现在你可以使用如下语句进行声明并赋值:

Map<String, String> myMap = new HashMap<>(); //注意后面的"<>"

在这条语句中,编译器会根据变量声明时的泛型类型自动推断出实例化HashMap时的泛型类型。再次提醒一定要注意new HashMap后面的“<>”,只有加上这个“<>”才表示是自动类型推断,否则就是非泛型类型的HashMap,并且在使用编译器编译源代码时会给出一个警告提示。

但是: Java SE 7在创建泛型实例时的类型推断是有限制的: 只有构造器的参数化类型在上下文中被显著的声明了,才可以使用类型推断,否则不行。例如: 下面的例子在java 7无法正确编译(但现在在java8里面可以编译,因为根据方法参数来自动推断泛型的类型):

List<String> list = new ArrayList<>();
list.add("A");// 由于addAll期望获得Collection<? extends String>类型的参数,因此下面的语句无法通过
list.addAll(new ArrayList<>());

Java8的泛型类型推断改进

java8里面泛型的目标类型推断主要2个:

  1. 支持通过方法上下文推断泛型目标类型
  2. 支持在方法调用链路当中,泛型类型推断传递到最后一个方法

让我们看看官网的例子

class List<E> {
   static <Z> List<Z> nil() { ... };
   static <Z> List<Z> cons(Z head, List<Z> tail) { ... };
   E head() { ... }
}

根据JEP101的特性,我们在调用上面方法的时候可以这样写

//通过方法赋值的目标参数来自动推断泛型的类型
List<String> l = List.nil();
//而不是显示的指定类型
//List<String> l = List.<String>nil();
//通过前面方法参数类型推断泛型的类型
List.cons(42, List.nil());
//而不是显示的指定类型
//List.cons(42, List.<Integer>nil());

总结

以上是JEP101的特性内容了,Java作为静态语言的代表者,可以说类型系统相当丰富。导致类型间互相转换的问题困扰着每个java程序员,通过编译器自动推断类型的东西可以稍微缓解一下类型转换太复杂的问题。虽然说是小进步,但对于我们天天写代码的程序员,肯定能带来巨大的作用,至少心情更愉悦了。

其他内容

JRE简化

JRE精简好处

  • 更小的Java环境需要更少的计算资源。
  • 一个较小的运行时环境可以更好的优化性能和启动时间。
  • 消除未使用的代码从安全的角度总是好的。
  • 这些打包的应用程序可以下载速度更快。

概念

紧凑的JRE分3种,分别是compact1、compact2、compact3,他们的关系是compact1<compact2<compact3,他们包含的API如下图所示
在这里插入图片描述

使用javac根据profile编译应用程序

javac –bootclasspath, or javac –profile

如果不符合compact的api,则报错。

$ javac -profile compact2 Test.java
Test.java:7: error: ThreadMXBean is not available in profile 'compact2'
 ThreadMXBean bean = ManagementFactory.getThreadMXBean();
 ^
Test.java:7: error: ManagementFactory is not available in profile 'compact2'
 ThreadMXBean bean = ManagementFactory.getThreadMXBean();
                     ^
2 errors

使用工具开发的效果
图片

JPEDS工具使用

java8新增一个工具,用来分析应用程序所依赖的profile,有三个参数比较常用 -p,-v,-r

import java.util.Set;
import java.util.HashSet;

public class Deps {
  public static void main(String[] args) {
    System.out.println(Math.random());
    Set<String> set = new HashSet<>();
  }
}
************** PROFILE ********************
jdeps -P Deps.class 
Deps.class -> /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/lib/rt.jar
   <unnamed> (Deps.class)
      -> java.io                                            compact1
      -> java.lang                                          compact1
      -> java.util                                          compact1

************** VERBOSE ********************
jdeps -v Deps.class 
Deps.class -> /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/lib/rt.jar
   Deps (Deps.class)
      -> java.io.PrintStream                                
      -> java.lang.Math                                     
      -> java.lang.Object                                   
      -> java.lang.String                                   
      -> java.lang.System                                   
      -> java.util.HashSet  

************** RECURSIVE ********************
jdeps -R Deps.class 
Deps.class -> /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/lib/rt.jar
   <unnamed> (Deps.class)
      -> java.io                                            
      -> java.lang                                          
      -> java.util                                          
/Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/lib/jce.jar -> /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/lib/rt.jar
   javax.crypto (jce.jar)
      -> java.io                                            
      -> java.lang                                          
      -> java.lang.reflect                                  
      -> java.net                                           
      -> java.nio                                           
      -> java.security                                      
      -> java.security.cert                                 
      -> java.security.spec                                 
      -> java.util                                          
      -> java.util.concurrent                               
      -> java.util.jar                                      
      -> java.util.regex                                    
      -> java.util.zip                                      
      -> javax.security.auth                                
      -> sun.security.jca                                   JDK internal API (rt.jar)
      -> sun.security.util                                  JDK internal API (rt.jar)
      -> sun.security.validator                             JDK internal API (rt.jar)
   javax.crypto.interfaces (jce.jar)
      -> java.lang                                          
      -> java.math                                          
      -> java.security                                      
   javax.crypto.spec (jce.jar)
      -> java.lang                                          
      -> java.math                                          
      -> java.security.spec                                 
      -> java.util                                          
/Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/lib/rt.jar -> /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/lib/jce.jar
   java.security (rt.jar)
      -> javax.crypto                                       JDK internal API (jce.jar)
   sun.security.util (rt.jar)
      -> javax.crypto                                       JDK internal API (jce.jar)
      -> javax.crypto.interfaces                            JDK internal API (jce.jar)
      -> javax.crypto.spec                                  JDK internal API (jce.jar)

在linux上构建profile

$ hg clone http://hg.openjdk.java.net/jdk8/jdk8/
$ cd jdk8
$ make images profiles : 
# Finished profiles (build time 00:00:27)
----- Build times -------
Start 2013-03-17 14:47:35
End 2013-03-17 14:58:26
00:00:25 corba
00:00:15 demos
00:01:50 hotspot
00:00:24 images
00:00:21 jaxp
00:00:31 jaxws
00:05:37 jdk
00:00:43 langtools
00:00:18 nashorn
00:00:27 profiles
00:10:51 TOTAL
-------------------------
Finished building Java(TM) for target 'images profiles'
$ cd images
$ ls -d *image
j2re-compact1-image j2re-compact2-image j2re-compact3-image j2re-image j2sdk-image

编译后compact大致的占用空间
图片

总结

如今,物联网正风行一时。我们看到大量不同的设备在市场上出现,每一种的更新速度都越来越快。java需要一个占用资源少的JRE运行环境,紧凑的JRE特性的出现,希望能带来以后的物联网的发展,甚至还是会有大量的java应用程序出现在物联网上面。目前oracle也发布了针对raspberry pi的JRE了。

另外该特性也是为java9的模块化项目做准备,模块化特性是javaer所期待的特性。他是解决业务系统复杂度的一个利器,当然OSGI也是相当的出色。但osgi对于新学者来说未免太复杂了。

Permgen删除

PermGen space简单介绍

PermGen space的全称是Permanent Generation space,是指内存的永久保存区域,说说为什么会内存益出: 这一部分用于存放Class和Meta的信息,Class在被 Load的时候被放入PermGen space区域,它和和存放Instance的Heap区域不同,所以如果你的APP会LOAD很多CLASS的话,就很可能出现PermGen space错误。这种错误常见在web服务器对JSP进行pre compile的时候。

JVM 种类有很多,比如 Oralce-Sun Hotspot, Oralce JRockit, IBM J9, Taobao JVM(淘宝好样的!)等等。当然武林盟主是Hotspot了,这个毫无争议。需要注意的是,PermGen space是Oracle-Sun Hotspot才有,JRockit以及J9是没有这个区域。

元空间(MetaSpace)一种新的内存空间诞生

JDK8 HotSpot JVM 将移除永久区,使用本地内存来存储类元数据信息并称之为: 元空间(Metaspace);这与Oracle JRockit 和IBM JVM’s很相似,如下图所示。

图片

这意味着不会再有java.lang.OutOfMemoryError: PermGen问题,也不再需要你进行调优及监控内存空间的使用……但请等等,这么说还为时过早。在默认情况下,这些改变是透明的,接下来我们的展示将使你知道仍然要关注类元数据内存的占用。请一定要牢记,这个新特性也不能神奇地消除类和类加载器导致的内存泄漏。

java8中metaspace总结如下:

  • PermGen 空间的状况
    这部分内存空间将全部移除。
    JVM的参数: PermSize 和 MaxPermSize 会被忽略并给出警告(如果在启用时设置了这两个参数)。

  • Metaspace 内存分配模型
    大部分类元数据都在本地内存中分配。
    用于描述类元数据的“klasses”已经被移除。

  • Metaspace 容量
    默认情况下,类元数据只受可用的本地内存限制(容量取决于是32位或是64位操作系统的可用虚拟内存大小)。
    新参数(MaxMetaspaceSize)用于限制本地内存分配给类元数据的大小。如果没有指定这个参数,元空间会在运行时根据需要动态调整。

  • Metaspace 垃圾回收
    对于僵死的类及类加载器的垃圾回收将在元数据使用达到“MaxMetaspaceSize”参数的设定值时进行。
    适时地监控和调整元空间对于减小垃圾回收频率和减少延时是很有必要的。持续的元空间垃圾回收说明,可能存在类、类加载器导致的内存泄漏或是大小设置不合适。

  • Java 堆内存的影响
    一些杂项数据已经移到Java堆空间中。升级到JDK8之后,会发现Java堆 空间有所增长。

  • Metaspace 监控
    元空间的使用情况可以从HotSpot1.8的详细GC日志输出中得到。

Jstat 和 JVisualVM两个工具,在使用b75版本进行测试时,已经更新了,但是还是能看到老的PermGen空间的出现。
前面已经从理论上充分说明,下面让我们通过“泄漏”程序进行新内存空间的观察……

PermGen vs. Metaspace 运行时比较

为了更好地理解Metaspace内存空间的运行时行为,

将进行以下几种场景的测试:

  • 使用JDK1.7运行Java程序,监控并耗尽默认设定的85MB大小的PermGen内存空间。
  • 使用JDK1.8运行Java程序,监控新Metaspace内存空间的动态增长和垃圾回收过程。
  • 使用JDK1.8运行Java程序,模拟耗尽通过“MaxMetaspaceSize”参数设定的128MB大小的Metaspace内存空间。

首先建立了一个模拟PermGen OOM的代码

public class ClassA {
 public void method(String name) {
  // do nothing
 }
}

上面是一个简单的ClassA,把他编译成class字节码放到D: /classes下面,测试代码中用URLClassLoader来加载此类型上面类编译成class

/**
 * 模拟PermGen OOM
 * @author benhail
 */
public class OOMTest {
    public static void main(String[] args) {
        try {
            //准备url
            URL url = new File("D:/classes").toURI().toURL();
            URL[] urls = {url};
            //获取有关类型加载的JMX接口
            ClassLoadingMXBean loadingBean = ManagementFactory.getClassLoadingMXBean();
            //用于缓存类加载器
            List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();
            while (true) {
                //加载类型并缓存类加载器实例
                ClassLoader classLoader = new URLClassLoader(urls);
                classLoaders.add(classLoader);
                classLoader.loadClass("ClassA");
                //显示数量信息(共加载过的类型数目,当前还有效的类型数目,已经被卸载的类型数目)
                System.out.println("total: " + loadingBean.getTotalLoadedClassCount());
                System.out.println("active: " + loadingBean.getLoadedClassCount());
                System.out.println("unloaded: " + loadingBean.getUnloadedClassCount());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

虚拟机器参数设置如下: -verbose -verbose:gc

  • 设置-verbose参数是为了获取类型加载和卸载的信息
  • 设置-verbose:gc是为了获取垃圾收集的相关信息
JDK 1.7 @64-bit – PermGen 耗尽测试

Java1.7的PermGen默认空间为85 MB(或者可以通过-XX:MaxPermSize=XXXm指定)

图片

可以从上面的JVisualVM的截图看出: 当加载超过6万个类之后,PermGen被耗尽。我们也能通过程序和GC的输出观察耗尽的过程。

程序输出(摘取了部分)

......
[Loaded ClassA from file:/D:/classes/]
total: 64887
active: 64887
unloaded: 0
[GC 245041K->213978K(536768K), 0.0597188 secs]
[Full GC 213978K->211425K(644992K), 0.6456638 secs]
[GC 211425K->211425K(656448K), 0.0086696 secs]
[Full GC 211425K->211411K(731008K), 0.6924754 secs]
[GC 211411K->211411K(726528K), 0.0088992 secs]
...............
java.lang.OutOfMemoryError: PermGen space
JDK 1.8 @64-bit – Metaspace大小动态调整测试

Java的Metaspace空间: 不受限制 (默认)

图片

从上面的截图可以看到,JVM Metaspace进行了动态扩展,本地内存的使用由20MB增长到646MB,以满足程序中不断增长的类数据内存占用需求。我们也能观察到JVM的垃圾回收事件—试图销毁僵死的类或类加载器对象。但是,由于我们程序的泄漏,JVM别无选择只能动态扩展Metaspace内存空间。程序加载超过10万个类,而没有出现OOM事件。

JDK 1.8 @64-bit – Metaspace 受限测试

Java的Metaspace空间: 128MB(-XX:MaxMetaspaceSize=128m)

图片

可以从上面的JVisualVM的截图看出: 当加载超过2万个类之后,Metaspace被耗尽;与JDK1.7运行时非常相似。我们也能通过程序和GC的输出观察耗尽的过程。另一个有趣的现象是,保留的原生内存占用量是设定的最大大小两倍之多。这可能表明,如果可能的话,可微调元空间容量大小策略,来避免本地内存的浪费。

从Java程序的输出中看到如下异常。

[Loaded ClassA from file:/D:/classes/]
total: 21393
active: 21393
unloaded: 0
[GC (Metadata GC Threshold) 64306K->57010K(111616K), 0.0145502 secs]
[Full GC (Metadata GC Threshold) 57010K->56810K(122368K), 0.1068084 secs]
java.lang.OutOfMemoryError: Metaspace

在设置了MaxMetaspaceSize的情况下,该空间的内存仍然会耗尽,进而引发“java.lang.OutOfMemoryError: Metadata space”错误。因为类加载器的泄漏仍然存在,而通常Java又不希望无限制地消耗本机内存,因此设置一个类似于MaxPermSize的限制看起来也是合理的。

总结
  • 之前不管是不是需要,JVM都会吃掉那块空间……如果设置得太小,JVM会死掉;如果设置得太大,这块内存就被JVM浪费了。理论上说,现在你完全可以不关注这个,因为JVM会在运行时自动调校为“合适的大小”;
  • 提高Full GC的性能,在Full GC期间,Metadata到Metadata pointers之间不需要扫描了,别小看这几纳秒时间;
  • 隐患就是如果程序存在内存泄露,像OOMTest那样,不停的扩展metaspace的空间,会导致机器的内存不足,所以还是要有必要的调试和监控。

StampedLock

synchronized

在java5之前,实现同步主要是使用synchronized。它是Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

有四种不同的同步块:

  • 实例方法
  • 静态方法
  • 实例方法中的同步块
  • 静态方法中的同步块

大家对此应该不陌生,所以不多讲了,以下是代码示例

synchronized(this)
// do operation
}

小结: 在多线程并发编程中Synchronized一直是元老级角色,很多人都会称呼它为重量级锁,但是随着Java SE1.6对Synchronized进行了各种优化之后,性能上也有所提升。

Lock


rwlock.writeLock().lock();
try {
 // do operation
} finally {
 rwlock.writeLock().unlock();
}

它是Java 5在java.util.concurrent.locks新增的一个API。

Lock是一个接口,核心方法是lock(),unlock(),tryLock(),实现类有ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock;

ReentrantReadWriteLock, ReentrantLock 和synchronized锁都有相同的内存语义。

与synchronized不同的是,Lock完全用Java写成,在java这个层面是无关JVM实现的。Lock提供更灵活的锁机制,很多synchronized 没有提供的许多特性,比如锁投票,定时锁等候和中断锁等候,但因为lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中

下面是Lock的一个代码示例

class Point {
   private double x, y;
   private final StampedLock sl = new StampedLock();
   void move(double deltaX, double deltaY) { // an exclusively locked method
     long stamp = sl.writeLock();
     try {
       x += deltaX;
       y += deltaY;
     } finally {
       sl.unlockWrite(stamp);
     }
   }
   //下面看看乐观读锁案例
   double distanceFromOrigin() { // A read-only method
     long stamp = sl.tryOptimisticRead(); //获得一个乐观读锁
     double currentX = x, currentY = y; //将两个字段读入本地局部变量
     if (!sl.validate(stamp)) { //检查发出乐观读锁后同时是否有其他写锁发生? 
        stamp = sl.readLock(); //如果没有,我们再次获得一个读悲观锁
        try {
          currentX = x; // 将两个字段读入本地局部变量
          currentY = y; // 将两个字段读入本地局部变量
        } finally {
           sl.unlockRead(stamp);
        }
     }
     return Math.sqrt(currentX * currentX + currentY * currentY);
   }
 //下面是悲观读锁案例
   void moveIfAtOrigin(double newX, double newY) { // upgrade
     // Could instead start with optimistic, not read mode
     long stamp = sl.readLock();
     try {
       while (x == 0.0 && y == 0.0) { //循环,检查当前状态是否符合
         long ws = sl.tryConvertToWriteLock(stamp); //将读锁转为写锁
         if (ws != 0L) { //这是确认转为写锁是否成功
           stamp = ws; //如果成功 替换票据
           x = newX; //进行状态改变
           y = newY; //进行状态改变
           break;
         }
         else { //如果不能成功转换为写锁
           sl.unlockRead(stamp); //我们显式释放读锁
           stamp = sl.writeLock(); //显式直接进行写锁 然后再通过循环再试
         }
       }
     } finally {
       sl.unlock(stamp); //释放读锁或写锁
     }
   }
 }

小结: 比synchronized更灵活、更具可伸缩性的锁定机制,但不管怎么说还是synchronized代码要更容易书写些

StampedLock

它是java8在java.util.concurrent.locks新增的一个API。

ReentrantReadWriteLock 在沒有任何读写锁时,才可以取得写入锁,这可用于实现了悲观读取(Pessimistic Reading),即如果执行中进行读取时,经常可能有另一执行要写入的需求,为了保持同步,ReentrantReadWriteLock 的读取锁定就可派上用场。

然而,如果读取执行情况很多,写入很少的情况下,使用 ReentrantReadWriteLock 可能会使写入线程遭遇饥饿(Starvation)问题,也就是写入线程吃吃无法竞争到锁定而一直处于等待状态。

StampedLock控制锁有三种模式(写,读,乐观读),一个StampedLock状态是由版本和模式两个部分组成,锁获取方法返回一个数字作为票据stamp,它用相应的锁状态表示并控制访问,数字0表示没有写锁被授权访问。在读锁上分为悲观锁和乐观锁。

所谓的乐观读模式,也就是若读的操作很多,写的操作很少的情况下,你可以乐观地认为,写入与读取同时发生几率很少,因此不悲观地使用完全的读取锁定,程序可以查看读取资料之后,是否遭到写入执行的变更,再采取后续的措施(重新读取变更信息,或者抛出异常) ,这一个小小改进,可大幅度提高程序的吞吐量!!

下面是java doc提供的StampedLock一个例子

class Point {
   private double x, y;
   private final StampedLock sl = new StampedLock();
   void move(double deltaX, double deltaY) { // an exclusively locked method
     long stamp = sl.writeLock();
     try {
       x += deltaX;
       y += deltaY;
     } finally {
       sl.unlockWrite(stamp);
     }
   }
  //下面看看乐观读锁案例
   double distanceFromOrigin() { // A read-only method
     long stamp = sl.tryOptimisticRead(); //获得一个乐观读锁
     double currentX = x, currentY = y; //将两个字段读入本地局部变量
     if (!sl.validate(stamp)) { //检查发出乐观读锁后同时是否有其他写锁发生? 
        stamp = sl.readLock(); //如果没有,我们再次获得一个读悲观锁
        try {
          currentX = x; // 将两个字段读入本地局部变量
          currentY = y; // 将两个字段读入本地局部变量
        } finally {
           sl.unlockRead(stamp);
        }
     }
     return Math.sqrt(currentX * currentX + currentY * currentY);
   }
 //下面是悲观读锁案例
   void moveIfAtOrigin(double newX, double newY) { // upgrade
     // Could instead start with optimistic, not read mode
     long stamp = sl.readLock();
     try {
       while (x == 0.0 && y == 0.0) { //循环,检查当前状态是否符合
         long ws = sl.tryConvertToWriteLock(stamp); //将读锁转为写锁
         if (ws != 0L) { //这是确认转为写锁是否成功
           stamp = ws; //如果成功 替换票据
           x = newX; //进行状态改变
           y = newY; //进行状态改变
           break;
         }
         else { //如果不能成功转换为写锁
           sl.unlockRead(stamp); //我们显式释放读锁
           stamp = sl.writeLock(); //显式直接进行写锁 然后再通过循环再试
         }
       }
     } finally {
       sl.unlock(stamp); //释放读锁或写锁
     }
   }
 }

小结:

StampedLock要比ReentrantReadWriteLock更加廉价,也就是消耗比较小。

StampedLock与ReadWriteLock性能对比

是和ReadWritLock相比,在一个线程情况下,是读速度其4倍左右,写是1倍。

下图是六个线程情况下,读性能是其几十倍,写性能也是近10倍左右:

图片

总结

  • synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定;
  • ReentrantLock、ReentrantReadWriteLock,、StampedLock都是对象层面的锁定,要保证锁定一定会被释放,就必须将unLock()放到finally{}中;
  • StampedLock 对吞吐量有巨大的改进,特别是在读线程越来越多的场景下;
  • StampedLock有一个复杂的API,对于加锁操作,很容易误用其他方法;
  • 当只有少量竞争者的时候,synchronized是一个很好的通用的锁实现;
  • 当线程增长能够预估,ReentrantLock是一个很好的通用的锁实现;

StampedLock 可以说是Lock的一个很好的补充,吞吐量以及性能上的提升足以打动很多人了,但并不是说要替代之前Lock的东西,毕竟他还是有些应用场景的,起码API比StampedLock容易入手。

JavaFX

JavaFX历史

跟java在服务器端和web端成绩相比,桌面一直是java的软肋,于是Sun公司在2008年推出JavaFX,弥补桌面软件的缺陷,请看下图JavaFX一路走过来的改进。

图片

从上图看出,一开始推出时候,开发者需使用一种名为JavaFX Script的静态的、声明式的编程语言来开发JavaFX应用程序。因为JavaFX Script将会被编译为Java bytecode,程序员可以使用Java代码代替。

JavaFX 2.0之后的版本摒弃了JavaFX Script语言,而作为一个Java API来使用。因此使用JavaFX平台实现的应用程序将直接通过标准Java代码来实现。

JavaFX 2.0 包含非常丰富的 UI 控件、图形和多媒体特性用于简化可视化应用的开发,WebView可直接在应用中嵌入网页;另外 2.0 版本允许使用 FXML 进行 UI 定义,这是一个脚本化基于 XML 的标识语言。

从JDK 7u6开始,JavaFx就与JDK捆绑在一起了,JavaFX团队称,下一个版本将是8.0,目前所有的工作都已经围绕8.0库进行。这是因为JavaFX将捆绑在Java 8中,因此该团队决定跳过几个版本号,迎头赶上Java 8。

JavaFx8的新特性

全新现代主题: Modena
新的Modena主题来替换原来的Caspian主题。不过在Application的start()方法中,可以通过setUserAgentStylesheet(STYLESHEET_CASPIAN)来继续使用Caspian主题。

参考http://fxexperience.com/2013/03/modena-theme-update/

JavaFX 3D
在JavaFX8中提供了3D图像处理API,包括Shape3D (Box, Cylinder, MeshView, Sphere子类),SubScene, Material, PickResult, LightBase (AmbientLight 和PointLight子类),SceneAntialiasing等。Camera类也得到了更新。从JavaDoc中可以找到更多信息。

富文本
强化了富文本的支持

TreeTableView
日期控件DatePicker
增加日期控件

用于 CSS 结构的公共 API
CSS 样式设置是 JavaFX 的一项主要特性
CSS 已专门在私有 API 中实现(com.sun.javafx.css 软件包)
多种工具(例如 Scene Builder)需要 CSS 公共 API
开发人员将能够定义自定义 CSS 样式
WebView 增强功能
Nashorn JavaScript 引擎 https://blogs.oracle.com/nashorn/entry/open_for_business
WebSocket http://javafx-jira.kenai.com/browse/RT-14947
Web Workers http://javafx-jira.kenai.com/browse/RT-9782
JavaFX Scene Builder 2.0
可视化工具,加速JavaFX图形界面的开发,下载地址

JavaFX Scene Builder如同NetBeans一般,通过拖拽的方式配置界面,待完成界面之後,保存为FXML格式文件,此文件以XML描述物件配置,再交由JavaFX程式处理,因此可減少直接以JavaFX编写界面的困難度。

JavaFX Scene Builder 2.0新增JavaFX Theme预览功能,菜单「Preview」→「JavaFX Theme」选择不同的主題,包括:

Modena (FX8).
Modena Touch (FX8).
Modena High Contrast – Black on White (FX8).
Modena High Contrast – White on Black (FX8).
Modena High Contrast – Yellow on Black (FX8).
Caspian (FX2).
Caspian Embedded (FX2).
Caspian Embedded QVGA (FX2).
JavaFX 8开发2048游戏
2048虽然不像前段时间那么火了,但个人还是非常喜欢玩2048,空闲时间都忍不住来一发,感谢 Gabriele Cirulli 发明了这了不起 (并且会上瘾)的2048游戏,因为是用MIT协议开源出来,各种语言版本的2048游戏横空出世,下图是用JavaFX 8来开发的一款2048。

所用到的技术

Lambda expressions
Stream API
JavaFX 8
JavaFX CSS basics
JavaFX animationsfx2048相关类的说明
Game2048,游戏主类
GameManager,包含游戏界面布局(Board)以及Grid的操作(GridOperator)
Board,包含labels ,分数,grid ,Tile
Tile,游戏中的数字块
GridOperator,Grid操作类
Location,Direction 位置帮助类
RecordManager,SessionManager,纪录游戏分数,会话类
总结
比起AWT和SWING,JavaFX的优势很明显,各大主流IDE已经支持JavaFX的开发了,最佳的工具莫过于NetBeans,且随着lambda带来的好处,JavaFX的事件处理简洁了不少,以前需要写匿名函数类。另外JavaFX开源以来,JavaFX的生态环境也越来越活跃了,包括各种教程,嵌入式尝试,还有一些开源项目,比如: ControlsFX,JRebirth,DataFX Flow,mvvmFX,TestFX 等等。还有JavaFX是可以运行在Android和ios上面,这个很赞!

好了,总结到这里也差不多了,在RIA平台上面,有HTML5、Flex和微软的Sliverlight,JavaFX能否表现优秀,在于大家的各位,只要我们多用JavaFX,那么JavaFX也会越来越优秀,任何语言都是这样, THE END .

更多

处理数值

Java8添加了对无符号数的额外支持。Java中的数值总是有符号的,例如,让我们来观察Integer:

int可表示最多2 ** 32个数。Java中的数值默认为有符号的,所以最后一个二进制数字表示符号(0为正数,1为负数)。所以从十进制的0开始,最大的有符号正整数为2 ** 31 - 1。

你可以通过Integer.MAX_VALUE来访问它:

System.out.println(Integer.MAX_VALUE);      // 2147483647
System.out.println(Integer.MAX_VALUE + 1);  // -2147483648

Java8添加了解析无符号整数的支持,让我们看看它如何工作:

long maxUnsignedInt = (1l << 32) - 1;
String string = String.valueOf(maxUnsignedInt);
int unsignedInt = Integer.parseUnsignedInt(string, 10);
String string2 = Integer.toUnsignedString(unsignedInt, 10);

就像你看到的那样,现在可以将最大的无符号数2 ** 32 - 1解析为整数。而且你也可以将这个数值转换回无符号数的字符串表示。

这在之前不可能使用parseInt完成,就像这个例子展示的那样:

try {
    Integer.parseInt(string, 10);
}
catch (NumberFormatException e) {
    System.err.println("could not parse signed int of " + maxUnsignedInt);
}

这个数值不可解析为有符号整数,因为它超出了最大范围2 ** 31 - 1。算术运算

Math工具类新增了一些方法来处理数值溢出。这是什么意思呢? 我们已经看到了所有数值类型都有最大值。所以当算术运算的结果不能被它的大小装下时,会发生什么呢?

System.out.println(Integer.MAX_VALUE);      // 2147483647
System.out.println(Integer.MAX_VALUE + 1);  // -2147483648

就像你看到的那样,发生了整数溢出,这通常是我们不愿意看到的。

Java8添加了严格数学运算的支持来解决这个问题。Math扩展了一些方法,它们全部以exact结尾,例如addExact。当运算结果不能被数值类型装下时,这些方法通过抛出ArithmeticException异常来合理地处理溢出。

try {
    Math.addExact(Integer.MAX_VALUE, 1);
}
catch (ArithmeticException e) {
    System.err.println(e.getMessage());
    // => integer overflow
}

当尝试通过toIntExact将长整数转换为整数时,可能会抛出同样的异常:

try {
    Math.toIntExact(Long.MAX_VALUE);
}
catch (ArithmeticException e) {
    System.err.println(e.getMessage());
    // => integer overflow
}

处理文件

Files工具类首次在Java7中引入,作为NIO的一部分。JDK8 API添加了一些额外的方法,它们可以将文件用于函数式数据流。让我们深入探索一些代码示例。列出文件

Files.list方法将指定目录的所有路径转换为数据流,便于我们在文件系统的内容上使用类似filter和sorted的流操作。

try (Stream<Path> stream = Files.list(Paths.get(""))) {
    String joined = stream
        .map(String::valueOf)
        .filter(path -> !path.startsWith("."))
        .sorted()
        .collect(Collectors.joining("; "));
    System.out.println("List: " + joined);
}

上面的例子列出了当前工作目录的所有文件,之后将每个路径都映射为它的字符串表示。之后结果被过滤、排序,最后连接为一个字符串。如果你还不熟悉函数式数据流,你应该阅读我的Java8数据流教程。

你可能已经注意到,数据流的创建包装在try-with语句中。数据流实现了AutoCloseable,并且这里我们需要显式关闭数据流,因为它基于IO操作。

返回的数据流是DirectoryStream的封装。如果需要及时处理文件资源,就应该使用try-with结构来确保在流式操作完成后,数据流的close方法被调用。

查找文件
下面的例子演示了如何查找在目录及其子目录下的文件:

Path start = Paths.get("");
int maxDepth = 5;
try (Stream<Path> stream = Files.find(start, maxDepth, (path, attr) ->
        String.valueOf(path).endsWith(".js"))) {
    String joined = stream
        .sorted()
        .map(String::valueOf)
        .collect(Collectors.joining("; "));
    System.out.println("Found: " + joined);
}

find方法接受三个参数: 目录路径start是起始点,maxDepth定义了最大搜索深度。第三个参数是一个匹配谓词,定义了搜索的逻辑。上面的例子中,我们搜索了所有JavaScirpt文件(以.js结尾的文件名)。

我们可以使用Files.walk方法来完成相同的行为。这个方法会遍历每个文件,而不需要传递搜索谓词。

Path start = Paths.get("");
int maxDepth = 5;
try (Stream<Path> stream = Files.walk(start, maxDepth)) {
    String joined = stream
        .map(String::valueOf)
        .filter(path -> path.endsWith(".js"))
        .sorted()
        .collect(Collectors.joining("; "));
    System.out.println("walk(): " + joined);
}

这个例子中,我们使用了流式操作filter来完成和上个例子相同的行为。

读写文件

将文本文件读到内存,以及向文本文件写入字符串在Java 8 中是简单的任务。不需要再去摆弄读写器了。Files.readAllLines从指定的文件把所有行读进字符串列表中。你可以简单地修改这个列表,并且将它通过Files.write写到另一个文件中:

List<String> lines = Files.readAllLines(Paths.get("res/nashorn1.js"));
lines.add("print('foobar');");
Files.write(Paths.get("res/nashorn1-modified.js"), lines);

要注意这些方法对内存并不十分高效,因为整个文件都会读进内存。文件越大,所用的堆区也就越大。

你可以使用Files.lines方法来作为内存高效的替代。这个方法读取每一行,并使用函数式数据流来对其流式处理,而不是一次性把所有行都读进内存。

try (Stream<String> stream = Files.lines(Paths.get("res/nashorn1.js"))) {
    stream
        .filter(line -> line.contains("print"))
        .map(String::trim)
        .forEach(System.out::println);
}

如果你需要更多的精细控制,你需要构造一个新的BufferedReader来代替:

Path path = Paths.get("res/nashorn1.js");
try (BufferedReader reader = Files.newBufferedReader(path)) {
    System.out.println(reader.readLine());
}

或者,你需要写入文件时,简单地构造一个BufferedWriter来代替:

Path path = Paths.get("res/output.js");
try (BufferedWriter writer = Files.newBufferedWriter(path)) {
    writer.write("print('Hello World');");
}

BufferedReader也可以访问函数式数据流。lines方法在它所有行上面构建数据流:

Path path = Paths.get("res/nashorn1.js");
try (BufferedReader reader = Files.newBufferedReader(path)) {
    long countPrints = reader
        .lines()
        .filter(line -> line.contains("print"))
        .count();
    System.out.println(countPrints);
}

目前为止你可以看到Java8提供了三个简单的方法来读取文本文件的每一行,使文件处理更加便捷。

不幸的是你需要显式使用try-with语句来关闭文件流,这会使示例代码有些凌乱。我期待函数式数据流可以在调用类似count和collect时可以自动关闭,因为你不能在相同数据流上调用终止操作两次。

java.util.Random

在Java8中java.util.Random类的一个非常明显的变化就是新增了返回随机数流(random Stream of numbers)的一些方法。

下面的代码是创建一个无穷尽的double类型的数字流,这些数字在0(包括0)和1(不包含1)之间。

Random random = new Random();
DoubleStream doubleStream = random.doubles();

下面的代码是创建一个无穷尽的int类型的数字流,这些数字在0(包括0)和100(不包括100)之间。

Random random = new Random();
IntStream intStream = random.ints(0, 100);

那么这些无穷尽的数字流用来做什么呢? 接下来,我通过一些案例来分析。记住,这些无穷大的数字流只能通过某种方式被截断(limited)。

示例1: 创建10个随机的整数流并打印出来:

intStream.limit(10).forEach(System.out::println);

示例2: 创建100个随机整数:

List<Integer> randomBetween0And99 = intStream
                                       .limit(100)
                                       .boxed()
                                       .collect(Collectors.toList());

对于高斯伪随机数(gaussian pseudo-random values)来说,random.doubles()方法所创建的流不能等价于高斯伪随机数,然而,如果用java8所提供的功能是非常容易实现的。

Random random = new Random();
DoubleStream gaussianStream = Stream.generate(random::nextGaussian).mapToDouble(e -> e);

这里,我使用了Stream.generate api,并传入Supplier 类的对象作为参数,这个对象是通过调用Random类中的方法 nextGaussian()创建另一个高斯伪随机数。

接下来,我们来对double类型的伪随机数流和double类型的高斯伪随机数流做一个更加有意思的事情,那就是获得两个流的随机数的分配情况。预期的结果是: double类型的伪随机数是均匀的分配的,而double类型的高斯伪随机数应该是正态分布的。

通过下面的代码,我生成了一百万个伪随机数,这是通过java8提供的api实现的:

Random random = new Random();
DoubleStream doubleStream = random.doubles(-1.0, 1.0);
LinkedHashMap<Range, Integer> rangeCountMap = doubleStream.limit(1000000)
    .boxed()
    .map(Ranges::of)
    .collect(Ranges::emptyRangeCountMap, (m, e) -> m.put(e, m.get(e) + 1), Ranges::mergeRangeCountMaps);

rangeCountMap.forEach((k, v) -> System.out.println(k.from() + "\t" + v));

代码的运行结果如下:

-1      49730
-0.9    49931
-0.8    50057
-0.7    50060
-0.6    49963
-0.5    50159
-0.4    49921
-0.3    49962
-0.2    50231
-0.1    49658
0       50177
0.1     49861
0.2     49947
0.3     50157
0.4     50414
0.5     50006
0.6     50038
0.7     49962
0.8     50071
0.9     49695

为了类比,我们再生成一百万个高斯伪随机数:

Random random = new Random();
DoubleStream gaussianStream = Stream.generate(random::nextGaussian).mapToDouble(e -> e);
LinkedHashMap<Range, Integer> gaussianRangeCountMap =
    gaussianStream
            .filter(e -> (e >= -1.0 && e < 1.0))
            .limit(1000000)
            .boxed()
            .map(Ranges::of)
            .collect(Ranges::emptyRangeCountMap, (m, e) -> m.put(e, m.get(e) + 1), Ranges::mergeRangeCountMaps);

gaussianRangeCountMap.forEach((k, v) -> System.out.println(k.from() + "\t" + v));

上面代码输出的结果恰恰与我们预期结果相吻合,即: double类型的伪随机数是均匀的分配的,而double类型的高斯伪随机数应该是正态分布的。

附: 完整代码可点击这里获取 https://gist.github.com/bijukunjummen/8129250

译文链接: http://www.importnew.com/9672.html

java.util.Base64

Java8中java.util.Base64性能比较高,推荐使用。请参考:

  • 性能对比: https://wizardforcel.gitbooks.io/java8-new-features/content/11.html
  • 源代码: http://git.oschina.net/benhail/javase8-sample
    该类提供了一套静态方法获取下面三种BASE64编解码器:

1)Basic编码: 是标准的BASE64编码,用于处理常规的需求

// 编码
String asB64 = Base64.getEncoder().encodeToString("some string".getBytes("utf-8"));
System.out.println(asB64); // 输出为: c29tZSBzdHJpbmc=
// 解码
byte[] asBytes = Base64.getDecoder().decode("c29tZSBzdHJpbmc=");
System.out.println(new String(asBytes, "utf-8")); // 输出为: some string

2)URL编码: 使用下划线替换URL里面的反斜线“/”

String urlEncoded = Base64.getUrlEncoder().encodeToString("subjects?abcd".getBytes("utf-8"));
System.out.println("Using URL Alphabet: " + urlEncoded);

// 输出为:

Using URL Alphabet: c3ViamVjdHM_YWJjZA==

3)MIME编码: 使用基本的字母数字产生BASE64输出,而且对MIME格式友好: 每一行输出不超过76个字符,而且每行以“\r\n”符结束。

StringBuilder sb = new StringBuilder();
for (int t = 0; t < 10; ++t) {
  sb.append(UUID.randomUUID().toString());
}
byte[] toEncode = sb.toString().getBytes("utf-8");
String mimeEncoded = Base64.getMimeEncoder().encodeToString(toEncode);
System.out.println(mimeEncoded);

来源:pdai.tech/md/java/java8/java8.html

新特性来源

JEP

JSP

java 17 已经发布 还学习java8新特性还有意义吗

  1. 特性是继承的8添加的特性17还存在
  2. 8是lct 长支持版本
  3. 主流架构,中间件的主流版本还在使用8
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值