java 8 的新特性

380 阅读21分钟

java 8 的新特性

lambada表达式

Lambda 表达式,也可称为闭包,它是 Java 8 发布的最重要新特性。Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中) 。使用 Lambda 表达式可以使代码变的更加简洁紧凑。

一个简单的例子

本例是新建一个线程,不使用lambda表达式的写法为 :

     public void testThread(){
         new Thread(new Runnable() {
             @Override
             public void run() {
                 System.out.println("hello, I am a thread!");
             }
         }).start();
     }

使用lambda表达式的写法为:

 public void testThread()
     new Thread(()-> System.out.println("hello, I am a thread!")).start();
 }

lambda语法

类型格式备注
多参数(s1,s2) -> {表达式...}参数类型可以省略,两边的括号不能省略。如果表达式只有一行,那么表达式两边的花括号可以省略。
一个参数s -> {表达式...}参数类型可以省略,两边的可以括号省略。如果表达式只有一行,那么表达式两边的花括号可以省略。
没有参数(如上例)() -> {表达式...}括号不能省略。如果表达式只有一行,那么表达式两边的花括号可以省略。

如需进一步的了解,可以参看博客:www.cnblogs.com/kangkaii/p/…

JDK 提供的常用函数式接口

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。 以下是部分Java 8 中新增的函数式接口,它们都在java.util.function 中:

NameTypeDescription
PredicatePredicate< T >接收T对象并返回boolean
FunctionFunction< T, R >接收T对象,返回R对象
SupplierSupplier< T >提供T对象(例如工厂),不接收值
ConsumerConsumer< T >接收T对象,不返回值
UnaryOperatorUnaryOperator接收T对象,返回T对象
BinaryOperatorBinaryOperator接收两个T对象,返回T对象

通过一个例子看看它们的应用:

 public class Mytest {
         
     //对于数组的操作的函数, 函数本身并没有“功能”,仅仅应当于一种“规范”
     public static Integer arrayOperate(Integer[] ints, Function<Integer[], Integer>  func) {
         return func.apply(ints);
     }
     
     public static void main(String[] args){
         Integer[] v = {1, 2, 3, 4, 5};
         
         //求和
         int sum = Mytest.arrayOperate(v, (s) -> {
             int total = 0;
             for(int i=0; i<s.length; i++){
                 total += s[i];
             }
             return total;
         });
         System.out.println(sum);
         
         //计算奇数的个数
         int count = Mytest.arrayOperate(v, (s) -> {
             int num = 0;
             for(int i=0; i<s.length; i++){
                 if (s[i]%2 == 1) num++;
             }
             return num;
         });
         System.out.println(count);
     }
 }

如需更多的了解,可以参考博客:www.runoob.com/java/java8-…

方法引用

在Java 8中,会使用Lambda表达式创建匿名方法,但是有时候,我们的Lambda表达式可能仅仅调用一个已存在的方法,而不做任何其它事,对于这种情况,通过一个方法名字来引用这个已存在的方法会更加清晰,Java 8的方法引用允许我们这样做。

方法引用是一个更加紧凑、易读的Lambda表达式,注意方法引用是一个Lambda表达式,其中方法引用的操作符是双冒号" :: "。

先看一个简单的例子:

 public static void main(String[] args) {
     List<String> slist = Arrays.asList("Tom", "Jack", "Monica", "Alpha");
 ​
     //slist.sort((s1,s2) -> s1.compareTo(s2));
     slist.sort(String::compareTo);
     
     for(String s: slist)
     {
         System.out.println(s);
     }
 }

在本例中,String::compareTo 等同于 (s1,s2) -> s1.compareTo(s2)

方法引用的标准形式是:类名::方法名。(注意:只需要写方法名,不需要写括号)有以下四种形式的方法引用:

类型示例
静态方法引用ClassName::methodName
实例上的实例方法引用instanceReference::methodName
超类上的实例方法引用super::methodName
类型上的实例方法引用ClassName::methodName
构造方法引用Class::new
数组构造方法引用TypeName[]::new

这里不便一一枚举详解,可以参考博客:www.cnblogs.com/xiaoxi/p/70…

【主要摘自】 www.cnblogs.com/andywithu/p…

Stream API

集合是Java中用途十分广泛的一个集合,正是因为它对于数据的处理的优势,几乎任何一个Java应用程序都会设计对Java集合的制造和处理。然而,传统的Java集合仅仅是对Java集合内部的数据进行简单的添加、删除等操作,而且处理的数据类型有限。当涉及到复杂的业务逻辑处理时,需要借助大量的迭代才能完成需求,操作繁琐,工作量大。

Java 8 中的 Stream 是对集合(Collection) 对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。所以说,Java 8 中首次出现的 java.util.stream 是一个函数式语言+多核时代综合影响的产物。

一个简单的例子

现在,通过一个简单的例子来看一下对集合进行操作时,Stream API 和传统方法的区别。

先定义一个Student类:

 public class Student {
     private int number;//学号
     private String name;//姓名
     private boolean sex;//性别 : true--女, false--男 
     private float height;//身高
     
     public Student(int number, String name, boolean sex, float height) {
         this.number = number;
         this.name = name;
         this.sex = sex;
         this.height = height;
     }
     
     ...... //getter and setter
 ​
     @Override
     public String toString() {
         return "Student [number=" + number + ", name=" + name + ", sex=" + sex + ", height=" + height + "]";
     }
 }

新建一个Student的列表:

         List<Student> list = new ArrayList<>();
         Random rand = new Random();
         list.add(new Student(1, "A", true, 150+rand.nextInt(40)));
         list.add(new Student(2, "B", false, 150+rand.nextInt(40)));
         list.add(new Student(3, "C", true, 150+rand.nextInt(40)));
         list.add(new Student(4, "D", false, 150+rand.nextInt(40)));
         list.add(new Student(5, "E", true, 150+rand.nextInt(40)));
         list.add(new Student(6, "F", false, 150+rand.nextInt(40)));
         list.add(new Student(7, "G", true, 150+rand.nextInt(40)));
         list.add(new Student(8, "H", false, 150+rand.nextInt(40)));
         list.add(new Student(9, "L", true, 150+rand.nextInt(40)));
         list.add(new Student(10, "M", false, 150+rand.nextInt(40)));

对Student列表进行以下的操作:

  1. 找出身高在170以上的女生;
  2. 将满足上述条件的列表按学生身高进行排序;
  3. 将列表中的学生信息打印出来。

传统的方式:

         //1.找出高于170的女生
         List<Student> list01 = new ArrayList<>();
         for(Student stu : list)
         {
             if(true == stu.getSex() && stu.getHeight() > 170){
                 list01.add(stu);
             }
         }
         //2.按身高进行排序
         list01.sort(new Comparator<Student>(){
 ​
             @Override
             public int compare(Student o1, Student o2) {
                 return (int) (o1.getHeight() - o1.getHeight());
             }
         });
         //3.打印出来
         for(Student stu : list01){
             System.out.println(stu);
         }

Stream API:

    list.stream()
             .filter(s -> true==s.getSex() && s.getHeight()>170)  //1.找出高于170的女生
             .sorted((o1,o2) -> (int)(o1.getHeight()-o2.getHeight())) //2.按身高进行排序
             .forEach(s -> System.out.println(s)); //3.打印出来

这对Stream API 只是做了简单的介绍,更多详情请看博客:

blog.csdn.net/qq_33429968…

blog.csdn.net/IO_Field/ar…

www.cnblogs.com/Dorae/p/777…

blog.csdn.net/piglite/art…

当然,本也节参考过这些博客。

默认方法

在 java 8 之前,接口与其实现类之间的耦合度太高(tightly coupled),当需要为一个接口添加方法时,所有的实现类都必须随之修改。默认方法解决了这个问题,它可以为接口添加新的方法,而不会破坏已有的接口的实现。这在 lambda 表达式作为 java 8 语言的重要特性而出现之际,为升级旧接口且保持向后兼容(backward compatibility)提供了途径。

请看下面这个简单的例子:

 interface InterfaceA {
     void go();//抽象方法
     
     default void foo() { //默认方法
         System.out.println("I am foo, a default mothod ,in interface");
     }
 }
  
 class ClassA implements InterfaceA { //默认方法可以不用覆写(但也可以覆写)
 ​
     @Override
     public void go() {
         // TODO Auto-generated method stub  
     }
     
 //  @Override
 //  public void foo() {
 //      System.out.println("I am foo, a default mothod ,in instance class");
 //  }
 }
  
 public class Test {
     public static void main(String[] args) {
         new ClassA().foo(); //接口的默认方法,可以由实现类直接调用
     }
 }

如本例所示,在接口中添加一个新的方法,其实现类可以自由的选择要不要覆写,而 Java 8 之前是需要强制覆写。

关于默认方法的更多内容,可以参考博客:blog.csdn.net/u010003835/… ,应阐明, 本小节也有参考。

重复注解

在Java 8 之前,同一个注解是不能在一个位置上重复使用的, 这样不能通过编译。请看例子:

 //自定义一个注解
 @Target(ElementType.TYPE) 
 @Retention(RetentionPolicy.RUNTIME) 
 public @interface MyAnnotation {
     String value();
 }
 @MyAnnotation("hello")
 @MyAnnotation("world")//重复了,会报错
 class Test {
     public static void main(String[] args) {     
     }
 }

如果一定要在这个类上多次使用注解,按照以前的做法,我们需要定义一个注解容器

 //自定义一个注解容器
 @Target(ElementType.TYPE)
 @Retention(RetentionPolicy.RUNTIME)
 public @interface MyAnnotationContainer {
     MyAnnotation[] value();
 }
 @MyAnnotationContainer({@MyAnnotation("hello"), @MyAnnotation("world")})
 class Test {
 ​
     public static void main(String[] args) {  
     }
 }

这样实在不怎么优雅!

在 Java 8 中提供了一个元注解@Repeatable,可以对用户隐藏掉注解容器,需要在@MyAnnotation注解的定义上加上这个注解,并指定相应的注解容器,如 :

 @Target(ElementType.TYPE)
 @Retention(RetentionPolicy.RUNTIME) 
 @Repeatable(MyAnnotationContainer.class) //指定注解容器
 public @interface MyAnnotation {
     String value();
 }
 @MyAnnotation("hello")
 @MyAnnotation("world") //可以重复使用了
 class Test {
 ​
     public static void main(String[] args) {  
     }
 }

【参考】 blog.csdn.net/TimHeath/ar…

注解增强

Java 8 扩展了注解可以使用的范围,现在我们几乎可以在所有的地方:局部变量、泛型、超类和接口实现...... 具体表现为:ElementType新增了两个枚举值:TYPE_PARAMETERTYPE_USE。对应的含义:

TYPE_PARAMETER

表示该注解能使用在自定义类型参数(参数的自定义类型可以是javaBean或者枚举等)的声明语句中。请看示例:

 @Target(ElementType.TYPE_PARAMETER)
 @Retention(RetentionPolicy.RUNTIME)
 public @interface TypeParameterAnnotation {
     
 }
 public class TypeParameterClass<@TypeParameterAnnotation T> {
     public <@TypeParameterAnnotation U> T foo(T t) {
         return null;
     }    
 }

TYPE_USE

表示该注解能使用在使用类型的任意语句中。请看示例:

 @Target(ElementType.TYPE_USE)
 @Retention(RetentionPolicy.RUNTIME)
 public @interface TypeUseAnnotation {    
 ​
 }
 public class Test {
 ​
     public static @TypeUseAnnotation class TypeUseClass<@TypeUseAnnotation T> extends @TypeUseAnnotation Object {
         public void foo(@TypeUseAnnotation T t) throws @TypeUseAnnotation Exception {
             
         }
     }
     @SuppressWarnings({ "rawtypes", "unused", "resource" })
     public static void main(String[] args) throws Exception {
         TypeUseClass<@TypeUseAnnotation String> typeUseClass = new @TypeUseAnnotation TypeUseClass<>();
         typeUseClass.foo("");
         List<@TypeUseAnnotation Comparable> list1 = new ArrayList<>();
         List<? extends Comparable> list2 = new ArrayList<@TypeUseAnnotation Comparable>();
         @TypeUseAnnotation String text = (@TypeUseAnnotation String)new Object();
         java.util. @TypeUseAnnotation Scanner console = new java.util.@TypeUseAnnotation Scanner(System.in);
     }
 }

方法参数反射

在 Java 8 之前,代码编译为 .class 文件后,方法参数的类型是固定的,但参数名称却丢失了,这和动态语言严重依赖参数名称形成了鲜明对比。现在,Java 8开始在class文件中保留参数名,给反射带来了极大的便利。请看示例:

 //定义一个类,有两个方法
 public class Person {
     
     public void say(String str, int num)
     {   
     }
     
     public void work(Double salary)
     {   
     }
 }
     public static void main(String[] args) {
         
         for (Method m : Person.class.getDeclaredMethods()) {
             
             String returnValue = m.getReturnType().getName();//获取返回值
             String methodName = m.getName(); //获取方法名
             
             Map<String, String> paraMap = new LinkedHashMap<>();
             for (Parameter p : m.getParameters()) {
                 String paraType = p.getType().getName(); //获取参数类型 
                 String paraName = p.getName();  // 重点: 获取参数名
                 paraMap.put(paraName, paraType);
             }
             
             System.out.println("------------------------------");
             System.out.println("returnValue: " + returnValue);
             System.out.println("methodName: " + methodName);
             paraMap.forEach((k, v)->System.out.println("   parameter: " + k +  "--" + v ));
         }
     }

打印的结果:

 ------------------------------
 returnValue: void
 methodName: say
    parameter: str--java.lang.String
    parameter: num--int
 ------------------------------
 returnValue: void
 methodName: work
    parameter: salary--java.lang.Double

遗憾的是,保留参数名这一选项由**编译开关javac -parameters**打开,默认是关闭的。具体集成开发环境打开方法请自行查阅。

【摘自】 blog.csdn.net/qq_32331073…

Optional类

Optional 的简介

Optional 类的引入很好的解决空指针异常。在 java 8 之前,调用一个方法得到了返回值后,不能直接将其去调用别的方法,因为其可能为 null,所以要先进行判断。

Java 8 引入了一个新的 Optional 类。Optional 类的 Javadoc 描述如下:

这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。 具体的,可以参考一篇很好的博客:

www.cnblogs.com/yw0219/p/73…

一个简单的例子

这是一个来自博客:www.importnew.com/26066.html 的一个很漂亮的例子,与大家分享一下:

在 Java 8 之前,若要判断调用一个方法得到了返回值是否为 null ,我们必须依赖 if 语句:

 public static String getChampionName(Competition comp) throws IllegalArgumentException {
     if (comp != null) {
         CompResult result = comp.getResult();
         if (result != null) {
             User champion = result.getChampion();
             if (champion != null) {
                 return champion.getName();
             }
         }
     }
     throw new IllegalArgumentException("The value of param comp isn't available.");
 }

由于种种原因(比如:比赛还没有产生冠军、方法的非正常调用、某个方法的实现里埋藏的“大礼包”等等),我们并不能开心的一路comp.getResult().getChampion().getName()到底。而其他语言比如 kotlin ,就提供了在语法层面的操作符加持:comp?.getResult()?.getChampion()?.getName()。所以讲到底在Java里我们怎么办?

让我们看看经过Optional加持过后,这些代码会变成什么样子:

 public static String getChampionName(Competition comp) throws IllegalArgumentException {
     return Optional.ofNullable(comp)
             .map(c->c.getResult())
             .map(r->r.getChampion())
             .map(u->u.getName())
             .orElseThrow(()->new IllegalArgumentException("The value of param comp isn't available."));
 }

这样的语法好不好看见仁见智,但是Optional给了我们一种优雅的 Java 风格的方法来解决 null 的安全问题。

【参考】

www.cnblogs.com/yw0219/p/73…

www.importnew.com/26066.html

Date Time API

为什么需要新的 Date Time API

在Java 8之前中,日期和时间相关的类诸多问题:

  • Java中日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义;
  • java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计;
  • 对于时间、时间戳、格式化以及解析,并没有一些明确定义的类。对于格式化和解析的需求,我们有java.text.DateFormat抽象类,但通常情况下,SimpleDateFormat类被用于此类需求;
  • 所有的日期类都是可变的,因此他们都不是线程安全的,这是Java日期类最大的问题之一;
  • 日期类并不提供国际化,没有时区支持,虽然引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。

在现有的日期和日历类中定义的方法还存在一些其他的问题,但以上问题已经很清晰地表明:Java需要一个健壮的日期/时间类。

新的 Date Time API 的优点

Java 8 Date Time API 的目标是克服旧的日期/时间API实现中所有的缺陷,新的日期/时间API的一些设计原则如下:

  • 不变性:新的日期/时间API中,所有的类都是不可变的,这种设计有利于并发编程;
  • 关注点分离:新的API将人可读的日期时间和机器时间(unix timestamp)明确分离,它为日期(Date)、时间(Time)、日期时间(DateTime)、时间戳(unix timestamp)以及时区定义了不同的类;
  • 清晰:在所有的类中,方法都被明确定义用以完成相同的行为。举个例子,要拿到当前实例我们可以使用now()方法,在所有的类中都定义了 format() 和 parse() 方法,而不是像以前那样专门有一个独立的类。为了更好的处理问题,所有的类都使用了工厂模式和策略模式,一旦你使用了其中某个类的方法,与其他类协同工作并不困难;
  • 实用操作:所有新的日期/时间API类都实现了一系列方法用以完成通用的任务,如:加、减、格式化、解析、从日期/时间中提取单独部分等操作;
  • 可扩展性:新的日期/时间API是工作在ISO-8601日历系统上的,但我们也可以将其应用在非IOS的日历上。

新的 Date Time API 的包

  • java.time:这是新的Java日期/时间API的基础包,所有的主要基础类都是这个包的一部分,如:LocalDate, LocalTime, LocalDateTime, Instant, Period, Duration等等。所有这些类都是不可变的和线程安全的;
  • java.time.chrono:这个包为非ISO的日历系统定义了一些泛化的API,我们可以扩展AbstractChronology类来创建自己的日历系统;
  • java.time.format:这个包包含能够格式化和解析日期时间对象的类,在绝大多数情况下,我们不应该直接使用它们,因为java.time包中相应的类已经提供了格式化和解析的方法;
  • java.time.temporal:这个包包含一些时态对象,我们可以用其找出关于日期/时间对象的某个特定日期或时间,比如说,可以找到某月的第一天或最后一天。你可以非常容易地认出这些方法,因为它们都具有“withXXX”的格式;
  • java.time.zone:这个包包含支持不同时区以及相关规则的类。

结语

很遗憾,在这里没有给出示例。新的 Date Time API 的类型过多,由于篇幅的原因,无法一一列举。这里推介一篇非常好的blog:

www.cnblogs.com/chenpi/p/59…

在此声明,本节的内容也是出自这篇blog。

java.util 和 java.lang 的增强

并行数组排序(java.util.Arrays.parallelSort(T[] a))

将数组拆分成多个子数组,多线程进行排序,然后归并,只有当数组长度大于 8192 时才进行并行排序。

运行以下测试代码(运行的示例,不一定要读懂,目的是说明parallelSort使用场合):

测试环境(硬件)

处理器:Intel Core i5-6200U

核心数:4 核

RAM: 8.00 GB

 public class MyTest {
     
     final int UPPER_LIMIT = 0xffffff;   //数组容量的上限: 16,777,215
     final int ROUNDS = 20;  //重复计算的次数(多次计算取平均值)
     final int INCREMENT = 2; //数组容量的增量,取INCREMENT的幂
     final int INIT_SIZE = 1024; //数组的初始容量  
  
     @Test
     public void test() {
         
         ArrayList<Integer> list = new ArrayList<Integer>();
         
         // 测试数组大小从INIT_SIZE开始,每次增加INCREMENT倍,直到超过UPPER_LIMIT.
         for (int capacity = INIT_SIZE; capacity <= UPPER_LIMIT; capacity *= INCREMENT) {
             
             int priorCapacity = (capacity/INCREMENT)<INIT_SIZE ? 0 : capacity/INCREMENT;
             for (int i = priorCapacity; i < capacity; i++) list.add(i);
             
             // sumTimeOfParallelSort: parallelSort经过ROUNDS次排序所耗费的总时间
             double sumTimeOfParallelSort = 0;
             // sumTimeOfSort: sort经过ROUNDS次排序所耗费的总时间
             double sumTimeOfSort = 0;
  
             for (int i = 0; i < ROUNDS; i++) {
                 // 每次排序都先打乱顺序
                 Collections.shuffle(list);
  
                 Integer[] arr1 = list.toArray(new Integer[capacity]);
                 Integer[] arr2 = arr1.clone();
  
                 sumTimeOfParallelSort += counter(arr1, true);
  
                 sumTimeOfSort += counter(arr2, false);
  
             }
  
             output(capacity, sumTimeOfParallelSort / ROUNDS, sumTimeOfSort
                     / ROUNDS);
         }
     }
  
     /**
      * 用于计算排序花费的时间
      * 
      * @param arr
      *            要排序的数组
      * @param useParallelSort
      *            true:使用parallelSort;false:使用sort
      * @return 返回花费的时间
      */
     private double counter(Integer[] arr, boolean useParallelSort) {
         long begin, end;
         begin = System.nanoTime();
         if (useParallelSort) {
             Arrays.parallelSort(arr);
         } else {
             Arrays.sort(arr);
         }
         end = System.nanoTime();
         return BigDecimal.valueOf(end - begin, 9).doubleValue();
     }
  
     /**
      * 
      * @param capacity
      *            当前数组容量
      * @param avgTimeOfParallelSort
      *            parallelSort花费的平均时间
      * @param avgTimeOfSort
      *            sort花费的平均时间
      */
     private void output(int capacity, double avgTimeOfParallelSort,
             double avgTimeOfSort) {
         System.out
                 .println("==================================================");
         System.out.println("Capacity:" + capacity);
         System.out.println("ParallelSort:" + avgTimeOfParallelSort);
         System.out.println("Sort:" + avgTimeOfSort);
         System.out.println("ParallelSort - Sort:" + (avgTimeOfParallelSort-avgTimeOfSort));
         System.out.println("Winner is:"
                 + (avgTimeOfParallelSort < avgTimeOfSort ? "ParallelSort"
                         : "Sort"));
         System.out
                 .println("==================================================");
     }
 }
 ​

结果统计表:

Array capacityParallelSort time(s)Sort time(s)Sort - ParallelSort (s)Winner
10244.3118880000000005E-44.578128E-42.662399999999994E-5ParallelSort
20485.576312499999999E-44.7123139999999993E-4-8.639984999999999E-5Sort
40960.00109738529999999999.039348000000003E-4-1.934504999999996E-4Sort
81920.00230749579999999950.0021431013-1.6439449999999974E-4Sort
163840.0029898841500000000.00406382425000000050.0010739400999999998ParallelSort
327680.00544265980.0089331939500000010.003490534150000001ParallelSort
655360.010759879650.018544296150.0077844165ParallelSort
1310720.0242996168499999940.043718920100000010.019419303250000016ParallelSort
2621440.0584451944499999950.11325677230.05481157785000001ParallelSort
5242880.168126782249999970.290162679349999950.12203589709999998ParallelSort
10485760.36928763395000010.64776132559999990.2784736916499998ParallelSort
20971520.84756510184999991.363443268250.5158781664000001ParallelSort
41943041.68775323165000063.06297410624999961.375220874599999ParallelSort

由此可见,只有当数组的容量大于8192之后,并列排序才有优势,而且这种优势随着数组容量的增加而上升。

【摘自】 blog.csdn.net/lc0817/arti…

支持标准的Base64编解码

Java 8 之前,要实现Base64编码,要么借助sun.misc.BASE64Encoder,或者apache commons-codec,再或者Guava、JAXB的DatatypeConverter。现在,官方已经把Base64进行了重新优化,放到 java.util.Base64 包里,比之前的都要简单优雅,且性能卓越,无需引用第三方包。

java.util.Base64工具类提供了一套静态方法获取下面三种Base64编解码器:

Basic编码

Basic编码是标准的Base64编码,用于处理常规的需求:输出的内容不添加换行符,而且输出的内容由字母加数字组成。示例如下:

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

URL编码

由于URL对反斜线“/”有特殊的意义,因此URL编码需要替换掉它,使用下划线替换。示例如下:

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

输出为:

 Using Basic Alphabet: c3ViamVjdHM/YWJjZA==
 Using URL Alphabet: c3ViamVjdHM_YWJjZA==

MIME编码

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);

输出为:

 YzY2ZGMzZmEtMjg0NS00YTVmLThjNzMtNGYyYjBkNjM0YzI3M2ZhZmYyYjgtMThlNC00ZTBjLTk3
 MGQtMWNhYzkzNjVkNjViZmRkNmI0OGUtMWFkNC00OTA4LTg5YWMtZTU3ZWM4NTU1YTNiYzg1YWRh
 ZTItYTA1OC00YWY3LWFmOWUtYjdmZjVmNmFmMDU2YjY1MWE0N2UtN2ZmMy00Njc5LTlhNTQtMjIy
 ZjhlMzVkODE2MGYyNmRiNDQtM2RmZS00OGU1LWJmZGYtMDMwYTgxMmQzN2U1NzRiNjlhNzAtYmRm
 Ny00ODRiLWE3MWMtYzcxOTQyNTg5YTIxMmQ5ZDFlMmUtODAzZi00NmY5LWFiNWUtNTgxZjg5MTQy
 MDNiYTYwOWUyNTEtZTMxNS00ZGY2LTkxZTctZDdiYzlkZjhjNGIxYjIzN2U1NDQtYzQ0Yy00Y2I5
 LTg3NzAtYzhmYjUwOTg4YWY1

【摘自】 blog.csdn.net/chszs/artic…

支持无符号运算

Java 8 为整型包装类增加支持无符号运算的方法。

IntegerLong 新增如下静态方法

IntegerLong
long toUnsignedLong(int x)
String toUnsignedString(int x)String UnsignedString(long i)
String toUnsignedString(int x, int radix)String UnsignedString(long i, int radix)
int parseUnsignedInt(String s)long parseUnsignedLong(String s)
int parseUnsignedInt(String s, int radix)long parseUnsignedLong(String s, int radix)
int compareUnsigned(int x, int y)int compareUnsigned(long x, long y)
int divideUnsigned(int dividend, int divisor)long divideUnsigned(long dividend, long divisor)
int remainderUnsigned(int dividend, int divisor)long remainderUnsigned(long dividend, long divisor)

java8 还为 ByteShort 新增如下的静态方法

ByteShort
int toUnsignedInt(byte x)int toUnsignedInt(short x)
long toUnsignedLong(byte x)long toUnsignedLong(short x)

java.util.concurrent 包的增强

ConcurrentHashMap 的增强

ConcurrentHashMap 在 java 8 中进行了巨大改动。它摒弃了Segment(锁段) 的概念,而是使用CAS来实现并发保护。它沿用了与它同时期的 HashMap 版本的思想,底层依然由 “数组” + 链表 + 红黑树 的方式思想,但是为了做到并发,又增加了很多辅助的类,例如TreeBin,Traverser等对象内部类。在这里不作重点讨论,可以参考其他博客:www.importnew.com/22007.html

不仅如此,为了回应新增的 Stream API 和 Lambda表达式 ,ConcurrentHashMap 新增三十多种方法。包括forEach系列(forEach,forEachKey, forEachValue, forEachEntry)、search系列(search, searchKeys, searchValues, searchEntries)、reduce系列(reduce, reduceToDouble, reduceToLong)以及mappingCount 、newKeySet等方法。请看示例:

forEach系列

     public static void main(String[] args) {
         final ConcurrentHashMap<String, Integer> count = new ConcurrentHashMap<>();
      
         count.put("a", 97);
         count.put("b", 98);
         count.put("c", 99);
 ​
         count.forEach((k, v)->System.out.println(k + ": " + v));
         count.forEachKey(0, s -> System.out.println(s));
         count.forEachValue(0, s -> System.out.println(s));
         count.forEachEntry(0, s -> System.out.println(s));
     }

search系列

 public static void main(String[] args) {
         final ConcurrentHashMap<String, Integer> count = new ConcurrentHashMap<>();
      
         count.put("a", 1);
         count.put("ab", 2);
         count.put("abc", 3);
         count.put("abcd", 4);   
         
         Object obj = count.search(0, (k, v) -> v%2==0 ? k:null);
         Boolean boolK = count.searchKeys(0, k -> 3==k.length() ? true:null);
         Boolean boolV = count.searchValues(0, v -> v>4 ? true:null);
         Object entry = count.searchEntries(0, e -> e.getKey().length()==e.getValue() ? e:null);
         
         System.out.println(obj);
         System.out.println(boolK);
         System.out.println(boolV);
         System.out.println(entry);
 }   

atomic 包下新增了类以支持可伸缩可更新的变量

LongAccumulator

在初始化这个类的时候,需要提供一个累加函数,该累加函数应该是无副作用的(side-effect-free), 因为当与其他线程的竞争而失败时,函数操作将会重复进行。 在累加函数中,将当前的值作为第一个参数,将“更新值”作为第二个参数。线程内部或跨线程的累积的顺序是不能保证的,也不能依赖于此类,因此该类只适用于累积的顺序无关的函数。

在多线程的情况下,面对一个用于汇总统计(collecting statistics),而非细粒度(fine-grained)同步控制的公共值时,这个类会比 AtomicLong 的表现更好。在高竞争的情况下,这个类的性能(throughput)会明显的高于AtomicLong,但是以更高的内存消耗为代价。

示例:

     public static void main(String[] args) {
        //初始化LongAccumulator时,需要 “累加函数” 和 “初始值”
        LongAccumulator la = new LongAccumulator((current, update) -> current/2 + update/2, 1024); 
         la.accumulate(512);
         la.accumulate(256);
         la.accumulate(128);
         la.accumulate(64);
         la.accumulate(32);
         la.accumulate(16);
        // la.reset();
         la.accumulate(8);
         la.accumulate(4);
         la.accumulate(2);
         System.out.println(la.get());
     }

LongAdder

LongAdder其实是LongAccumulator的一个特例,调用LongAdder相当使用下面的方式调用LongAccumulator。

     LongAccumulator accumulator = new LongAccumulator((current, update)->current+update, 0L);

相比于LongAccumulator,LongAdder的初始值默认为0,并默认累加规则为相加。示例:

     public static void main(String[] args) {        
         LongAdder la = new LongAdder();
         la.add(100);
         la.add(100);
         la.add(100);
         //la.decrement();
         //la.reset();
         System.out.println(la.longValue());
     }

DoubleAccumulator & DoubleAdder

DoubleAccumulator 、 DoubleAdder 的用法和 LongAccumulator、LongAdder 的用法大同小异,这里就不详细列举了。

【参考】 docs.oracle.com/javase/8/do…

ForkJoinPool 类新增了方法以支持 common pool

新增的 commonPool() 是一个静态的方法,适合于大多数的应用。common pool 可用于任何没有其他特殊的池的ForkJoinTask。正常情况下,使用 common pool 能减少资源的消耗(在不使用 common pool 的情况下,ForkJoinPool 的线程在不使用时会被缓慢地回收,并在随后的使用时恢复)。

示例:

public class SumTask extends RecursiveTask<Integer> {
	static final int THRESHOLD = 50;
	private Integer[] numbers;
	private int from;
	private int to;

	public SumTask(Integer[] numbers, int from, int to) {
		this.numbers = numbers;
		this.from = from;
		this.to = to;
	}

	@Override
	protected Integer compute() {
		// 当需要计算的数字小于THRESHOLD时,直接计算结果
		if (to - from < THRESHOLD) {
			int total = 0;
			for (int i = from; i <= to; i++) {
				total += numbers[i];
			}
			return total;
			// 否则,把任务一分为二,递归计算
		} else {
			int middle = (from + to) / 2;
			SumTask taskLeft = new SumTask(numbers, from, middle);
			SumTask taskRight = new SumTask(numbers, middle + 1, to);
			taskLeft.fork();
			taskRight.fork();
			return taskLeft.join() + taskRight.join();
		}
	}
}
 public class Test {
 ​
     public static void main(String args[]) throws InterruptedException, ExecutionException{
         
         final int TOTAL = 1000;
         
         ForkJoinPool commonpool = ForkJoinPool.commonPool();//重点:返回公共池实例
         
         Integer[] numbers = new Integer[TOTAL];
         for(int i=0; i<TOTAL; i++){
             numbers[i] = Integer.valueOf(i+1);
         }
         
         SumTask task = new SumTask(numbers, 0, numbers.length-1);
         int result = commonpool.submit(task).get();
         System.out.println("result: " + result);
     }
 }

【参考】 docs.oracle.com/javase/8/do…

新增了 java.util.concurrent.locks.StampedLock 类

StampedLock的简介

为控制读/写访问提供了一个基于性能的锁(capability-based lock),且有三种模式可供选择。StampedLock的状态由版本和模式构成。锁获取方法(Lock acquisition methods )返回一个标志(stamp),这个标志代表并控制对锁状态的访问。而“try”版本的锁获取方法也许会返回特殊值——零,代表对锁状态获取的失败。锁的释放和转换方法需要标志(stamp)作为参数,如果没有得到相应的锁状态就会失败。三种模式分别是:

  • Writing(写模式)

    writeLock()方法可能会因独占的的访问(exclusive access)而阻塞,并最终返回一个标志,这个标志能由方法unlockWrite(long)释放掉相应的锁。也提供了不定时和定时的tryWriteLock方法。当锁由写模式(write mode)持有时,所有的读操作将无法获得该锁,所有的乐观读的验证( optimistic read validations)将会失败。

  • Reading(读模式)

    readLock()方法可能会因独占的的访问(exclusive access)而阻塞,并最终返回一个标志,这个标志能由方法unlockRead(long)释放掉相应的锁。也提供了不定时和定时的tryReadLock方法。

  • Optimistic Reading(乐观读模式)

    仅当锁此时没有由写模式持有时,tryOptimisticRead()方法返回一个非零的标志。自从获取一个给定的标志(tryOptimisticRead()返回的非零标志)起,如果锁没有被写模式获得,则validate(long)方法的返回值为真(true)。这个模式可以看作一个非常弱的读锁版本 ,写操作能随时破坏它。对于那些短小且只读的代码段(short read-only code segments),乐观模式的使用常常能减少竞争和提高效率。

    然而,乐观模式本质上是脆弱的,乐观读部分应该仅仅读取字段(fields),并将它们保存在局部变量中,以便在验证之后使用。在乐观模式下,字段的读取极其可能不一致, 仅当你对数据表示非常熟悉,足以检查一致性,且(或)重复地调用validate()方法,否则不应该使用。例如,当第一次读取对象或者数组引用,并之后访问它的其中一个字段、元素或方法时,通常需要这些步骤。

一个简单的例子

封装一个线程安全的文件读写通道。基于StampedLock,读操作使用乐观读模式

 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
 import java.util.concurrent.locks.StampedLock;
 ​
 /**
  * 线程安全的文件读写通道,基于lock,读操作使用乐观锁
  * 
  * @author Tucson
  */
 public class TextRWChannel {
 ​
     private final StampedLock lock = new StampedLock();
     private final int BUF_LIMIT = 1024;
 ​
     private Path path;
     private String charset = "utf-8";
 ​
     /**
      * 构造函数
      * 
      * @param path
      *            文件路径
      * @throws IOException
      */
     public TextRWChannel(Path path) throws IOException {
         this.path = path;
     }
 ​
     /**
      * 构造函数
      * 
      * @param path
      *            文件路径
      * @param charset
      *            编码方式
      * @throws IOException
      */
     public TextRWChannel(Path path, String charset) throws IOException {
         this.path = path;
         this.charset = charset;
     }
 ​
     /**
      * 写方法
      * 
      * @param text
      *            要进行写操作的文本
      * @param position
      *            写的开始位置
      * @throws IOException
      */
     public void write(String text, long position) throws IOException {
         try (FileChannel writeChannel = FileChannel.open(path, StandardOpenOption.WRITE)) {
             
             long writeStamp = lock.writeLock(); // 写锁
             try {
                 writeText(writeChannel, text, charset, position);
             } finally {
                 lock.unlockWrite(writeStamp);// 释放写锁
             }
         }
     }
 ​
     /**
      * 文件读操作,使用乐观锁
      * 
      * @param postion
      *            读的开始位置
      * @return 读取的文本
      * @throws IOException
      */
     public String read(long position) throws IOException  {
         try (FileChannel readChannel = FileChannel.open(path, StandardOpenOption.READ)) {
 ​
             long stamp = lock.tryOptimisticRead(); // 1、先使用乐观读锁
             String text = this.readText(readChannel, charset, position);// 2、直接读文件(在没有加锁的情况下)
             if (!lock.validate(stamp)) { // 3、判断读文件期间有没有别的线程进行写操作
                 long readStamp = lock.readLock();// 4、若有,加“读锁”
                 try {
                     //System.out.println("******** the file has been changed ********");
                     text = this.readText(readChannel, charset, position);// 5、重读
                 } finally {
                     lock.unlockRead(readStamp);// 6、释放读锁
                 }
             }
             return text;
         }
     }
 ​
     private String readText(FileChannel channel, String charset, long position) throws IOException {
 ​
         ByteBuffer buffer = ByteBuffer.allocate(BUF_LIMIT);
         StringBuilder sBuilder = new StringBuilder();
         channel.position(position);
         while (-1 != channel.read(buffer)) {
             buffer.flip();
             String str = new String(buffer.array(), buffer.position(), buffer.limit(), charset);
             sBuilder.append(str);
             buffer.clear();
         }
         return sBuilder.toString();
     }
 ​
     private void writeText(FileChannel channel, String text, String charset, long position) throws IOException {
 ​
         ByteBuffer buffer = ByteBuffer.allocate(BUF_LIMIT);
         byte[] bytes = text.getBytes(charset);
         channel.position(position);
         int count = bytes.length / BUF_LIMIT;
         for (int i = 0; i < count; i++) {
             buffer.put(bytes, BUF_LIMIT * i, BUF_LIMIT * (i + 1));
             buffer.flip();
             channel.write(buffer);
             buffer.clear();
         } 
 ​
         buffer.put(bytes, count*BUF_LIMIT, bytes.length);
         buffer.flip();
         channel.write(buffer);
         buffer.clear();
     }
 }

测试代码:

 public class Test {
 ​
     static TextRWChannel channel = null;
 ​
     public static void main(String[] args) throws IOException {
 ​
         Path path = Paths.get("E:\test.txt");
         String text = "使用 Lambda 表达式可以使代码变的更加简洁紧凑。";
         channel = new TextRWChannel(path, "GBK");
     
         for (int i = 0; i < 100; i++) {
             new Thread(() -> {
                 try {
                     channel.read(0);
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
             }).start();
 ​
             if (i % 5 == 0) {
                 new Thread(() -> {
                     try {
                         channel.write(text, 0);
                     } catch (IOException e) {
                         e.printStackTrace();
                     }
                 }).start();
             }
         }
     }
 }

【参考】 docs.oracle.com/javase/8/do…