JDK1.5,1.6,1.7,1.8,1.9的新特性


JDK1.5新特性(2004年10月发布

1.泛型
2.foreach
3.自动拆箱装箱
4.枚举
5.静态导入(Static import)
6.元数据(Metadata)
7.线程池
8.Java Generics 

JDK1.6新特性(2006年4月发布)

1.Desktop类和SystemTray类
2.使用JAXB2来实现对象与XML之间的映射
3.StAX
4.使用Compiler API
5.轻量级Http Server API
6.插入式注解处理API(Pluggable Annotation Processing API)
7.用Console开发控制台程序
8.对脚本语言的支持
9.Common Annotations

JDK1.7新特性(2011年7月28号发布)

1 对集合类的语言支持; 
2 自动资源管理; 
3 改进的通用实例创建类型推断; 
4 数字字面量下划线支持; 
5 switch中使用string; 
6 二进制字面量; 
7 简化可变参数方法调用。 

JDK1.8新特性(2014年3月发布)

1 接口的默认方法
Lambda 表达式
函数式接口
方法与构造函数引用
Lambda 作用域
访问局部变量
访问对象字段与静态变量
访问接口的默认方法
Date API

10 Annotation 注解

JDK1.9新特性(2017年9月21号发布)

Java 平台级模块系统

2 Linking

3 JShell : 交互式 Java REPL

4 改进的 Javadoc

5 集合工厂方法

6 改进的 Stream API

7 私有接口方法

8 HTTP/2

9 多版本兼容 JAR


下面是各个版本特性的详细解答:

jdk1.5新特性


1.泛型
2.foreach
3.自动拆箱装箱
4.枚举
5.静态导入(Static import)
6.元数据(Metadata)
7.线程池
8.Java Generics 

让我们详细讨论每个新特性,并看一些例子。 

1、泛型(Generics) 

  泛型是JDK1.5中一个最“酷”的特征。通过引入泛型,我们将获得编译时类型的安全和运行时更小地抛出 ClassCastExceptions的可能。在JDK1.5中,你可以声明一个集合将接收/返回的对象的类型。在JDK1.4中,创建雇员名字的清单 (List)需要一个集合对象,像下面的语句: 
  List listOfEmployeeName = new ArrayList(); 
  在JDK1.5中,你将使用下面语句 
  List<String> listOfEmployeeName = new ArrayList<String>(); 
  最“酷”的是,如果你试图插入非string类型的值,你将在编译时发现并且修正这类问题。没有泛型,你会发现这样一个bug,当你的客户调用后会告诉你,你所编写的程序抛出ClassCastException异常而崩溃。 
  另外,当你从集合中得到一个元素时你无需进行强制转换。故原先为: 
  String employeeName = ((String) listOfEmployee.get(i)); 
  而下面的语句将比上面的更加简单: 
  String employeeName = listOfEmployee.get(i); 

  不清楚对象的类型而强制转换对象是不合理的,并且更重要的是,它将在运行时失败。假使用户无意间传入一个包含string buffers类型而非string类型的集合,那结果会怎样呢。在Listing A中,客户被要求传入一个编译器无法强制的strings类型集合。Listing B中显示了同样的方法使用泛型是如何实现的。 
  Listing A 
  staticbooleancheckName(Collection employeeNameList, String name) { 
  for (Iteratori = employeeNamList.iterator(); i.hasNext(); ) { 
  String s = (String) i.next(); 
  if(s.equals(name)){ 
  return true; 
  //print employee name here ...... 
  } 
  } 
  return false; 
  } 
  Listing B 
  staticbooleancheckName(Collection<String> employeeNameList, String name) { 
  for (Iteratori = employeeNamList.iterator(); i.hasNext(); ) { 
  if(i.next().equals(name)){ 
  return true; 
  //print employee name here ...... 
  } 
  } 
  return false; 
  } 

  现在,通过方法签名可以清楚知道输入集合必须只能包含strings。如果客户试图传入一个包含string buffers的集合,程序将不会编译。同时注意,该方法不包含任何强制转换。它只需要短短一行,一旦你习惯泛型后,它也更加清晰。 

2、在JDK当前版本下的For循环语法如下: 


  void printAll(Collection c) { 

  for (Iteratori = c.iterator(); i.hasNext(); ) { 

  Employee emp = (Employee)i.next(); 

  System.out.println(emp.getName()); 

  } 

  } 

  现在,用增强的For语句实现相同方法: 

  voidprintAll(Collection c) { 

  for (Object o : c) 

  System.out.println((TimerTask)o).getName()); 

  } 

  在这类For循环中,你应该将":"看成"in",所以,在该例中可以看成"for Object o in c"。你可以发现这种For循环更具可读性。 

3、自动置入/自动取出(Autoboxing/unboxing) 


  Java有基本数据类型,在这些基本数据类型周围又有包装类。通常,编程人员需要将一种类型转换成另一种。看看Listing C.中的代码片断。 

  Listing C 

  public class Employee { 

  private static final Integer CHILD = new Integer(0); 

  public static void main(String args[]) { 

  //code for adding n to an Integer 

  int n=10; 

  Integer age= new Integer(30); 

  Integer ageAfterTenYear= new Integer(age.intValue +10); 

  } 

  } 

  请注意,用于计算ageAfterTenYear的内循环代码看上去是多么杂乱。现在,在Listing D.中看看相同的程序使用autoboxing重写后的样子。 

  Listing D 

  public class Employee { 

  public static void main(String args[]) { 

  int n=10; 

  Integer age= new Integer(30); 

  Integer ageAfterTenYear= age +10; 

  } 

  } 

  有一件事值得注意的:在先前,如果你取出(unbox)Null值,它将变为0。在次代码中,编译器将自动地转换Integer为int然后加上10,接着将其转换回Integer.。 

4、类型安全的枚举(Typesafeenums) 


  类型安全枚举提供下列特性: 

  他们提供编译时类型安全。 

  他们都是对象,因此你不需要将他们放入集合中。 

  他们作为一种类的实现,因此你可以添加一些方法。 

  他们为枚举类型提供了合适的命名空间。 

  他们打印的值具有情报性(informative)― 如果你打印一个整数枚举(intenum),你只是看见一个数字,它可能并不具有情报性。 

  例一: 

  enum Season { winter, spring, summer, fall } 

  例二: 

  public enum Coin { 

  penny(1), nickel(5), dime(10), quarter(25); 

  Coin(int value) { this.value = value; } 

  private final int value; 

  public int value() { return value; } 

  } 

5、静态导入(Static import) 


  静态导入使代码更易读。通常,你要使用定义在另一个类中的常量(constants),像这样: 

  importorg.yyy.pkg.Increment; 

  class Employee { 

  public Double calculateSalary(Double salary{ 

  return salary + Increment.INCREMENT * salary; 

  } 

  } 

  当时使用静态导入,我们无需为常量名前缀类名就能使用这些常量,像这样: 

  import static org.yyy.pkg.Increment; 

  class Employee { 

  public Double calculateSalary(Double salary{ 

  return salary + INCREMENT * salary; 

  } 

  } 

  注意,我们可以调用INCREMENT这一常量而不要使用类名Increment.。 

6、元数据(Metadata) 


  元数据特征志于使开发者们借助厂商提供的工具可以进行更简易的开发。看一看Listing E.中的代码。 

  Listing E 

  importorg.yyy.hr; 

  public interface EmployeeI extends Java.rmi.Remote { 

  public String getName() 

  throwsJava.rmi.RemoteException; 

  public String getLocation () 

  throwsJava.rmi.RemoteException; 

  } 

  public class EmployeeImpl implements EmployeeI { 

  public String getName(){ 

  } 

  public String getLocation (){ 

  } 

  } 

  通过元数据的支持,你可以改写Listing E中的代码为: 

  importorg.yyy.hr; 

  public class Employee { 

  @Remote public String getName() { 

  ... 

  } 

  @Remote public public String getLocation() { 

  ... 

  } 

  } 

7、线程池

Java5中,对Java线程的类库做了大量的扩展,其中线程池就是Java5的新特征之一,除了线程池之外,还有很多多线程相关的内容,为多线程的编程带来了极大便利。为了编写高效稳定可靠的多线程程序,线程部分的新增内容显得尤为重要。

有关Java5线程新特征的内容全部在java.util.concurrent下面,里面包含数目众多的接口和类,熟悉这部分API特征是一项艰难的学习过程。目前有关这方面的资料和书籍都少之又少,大所属介绍线程方面书籍还停留在java5之前的知识层面上。

当然新特征对做多线程程序没有必须的关系,在java5之前通用可以写出很优秀的多线程程序。只是代价不一样而已。

线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。

在Java5之前,要实现一个线程池是相当有难度的,现在Java5为我们做好了一切,我们只需要按照提供的API来使用,即可享受线程池带来的极大便利。

Java5的线程池分好多种:固定尺寸的线程池、可变尺寸连接池、。

在使用线程池之前,必须知道如何去创建一个线程池,在Java5中,需要了解的是java.util.concurrent.Executors类的API,这个类提供大量创建连接池的静态方法,是必须掌握的。

7.1、固定大小的线程池


import java.util.concurrent.Executors; 
import java.util.concurrent.ExecutorService; 

/** 
* Java线程:线程池- 

* @author Administrator 2009-11-4 23:30:44 
*/ 
public class Test { 
        public static void main(String[] args) { 
                //创建一个可重用固定线程数的线程池 
                ExecutorService pool = Executors.newFixedThreadPool(2); 
                //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口 
                Thread t1 = new MyThread(); 
                Thread t2 = new MyThread(); 
                Thread t3 = new MyThread(); 
                Thread t4 = new MyThread(); 
                Thread t5 = new MyThread(); 
                //将线程放入池中进行执行 
                pool.execute(t1); 
                pool.execute(t2); 
                pool.execute(t3); 
                pool.execute(t4); 
                pool.execute(t5); 
                //关闭线程池 
                pool.shutdown(); 
        } 


class MyThread extends Thread{ 
        @Override 
        public void run() { 
                System.out.println(Thread.currentThread().getName()+"正在执行。。。"); 
        } 
}

pool-1-thread-1正在执行。。。 
pool-1-thread-1正在执行。。。 
pool-1-thread-1正在执行。。。 
pool-1-thread-1正在执行。。。 
pool-1-thread-2正在执行。。。 

Process finished with exit code 0

7.2、单任务线程池


在上例的基础上改一行创建pool对象的代码为:
                //创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。 
                ExecutorService pool = Executors.newSingleThreadExecutor(); 

输出结果为:
pool-1-thread-1正在执行。。。 
pool-1-thread-1正在执行。。。 
pool-1-thread-1正在执行。。。 
pool-1-thread-1正在执行。。。 
pool-1-thread-1正在执行。。。 

Process finished with exit code 0

对于以上两种连接池,大小都是固定的,当要加入的池的线程(或者任务)超过池最大尺寸时候,则入此线程池需要排队等待。
一旦池中有线程完毕,则排队等待的某个线程会入池执行。

7.3、可变尺寸的线程池


与上面的类似,只是改动下pool的创建方式:
                //创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。 
                ExecutorService pool = Executors.newCachedThreadPool(); 

pool-1-thread-5正在执行。。。 
pool-1-thread-1正在执行。。。 
pool-1-thread-4正在执行。。。 
pool-1-thread-3正在执行。。。 
pool-1-thread-2正在执行。。。 

Process finished with exit code 0

7.4、延迟连接池


import java.util.concurrent.Executors; 
import java.util.concurrent.ScheduledExecutorService; 
import java.util.concurrent.TimeUnit; 

/** 
* Java线程:线程池- 

* @author Administrator 2009-11-4 23:30:44 
*/ 
public class Test { 
        public static void main(String[] args) { 
                //创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。 
                ScheduledExecutorService pool = Executors.newScheduledThreadPool(2); 
                //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口 
                Thread t1 = new MyThread(); 
                Thread t2 = new MyThread(); 
                Thread t3 = new MyThread(); 
                Thread t4 = new MyThread(); 
                Thread t5 = new MyThread(); 
                //将线程放入池中进行执行 
                pool.execute(t1); 
                pool.execute(t2); 
                pool.execute(t3); 
                //使用延迟执行风格的方法 
                pool.schedule(t4, 10, TimeUnit.MILLISECONDS); 
                pool.schedule(t5, 10, TimeUnit.MILLISECONDS); 
                //关闭线程池 
                pool.shutdown(); 
        } 


class MyThread extends Thread { 
        @Override 
        public void run() { 
                System.out.println(Thread.currentThread().getName() + "正在执行。。。"); 
        } 
}

pool-1-thread-1正在执行。。。 
pool-1-thread-2正在执行。。。 
pool-1-thread-1正在执行。。。 
pool-1-thread-1正在执行。。。 
pool-1-thread-2正在执行。。。 

Process finished with exit code 0

7.5、单任务延迟连接池


在四代码基础上,做改动
                //创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。 
                ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor();

pool-1-thread-1正在执行。。。 
pool-1-thread-1正在执行。。。 
pool-1-thread-1正在执行。。。 
pool-1-thread-1正在执行。。。 
pool-1-thread-1正在执行。。。 

Process finished with exit code 0 

7.6、自定义线程池


import java.util.concurrent.ArrayBlockingQueue; 
import java.util.concurrent.BlockingQueue; 
import java.util.concurrent.ThreadPoolExecutor; 
import java.util.concurrent.TimeUnit; 

/** 
* Java线程:线程池-自定义线程池 

* @author Administrator 2009-11-4 23:30:44 
*/ 
public class Test { 
        public static void main(String[] args) { 
                //创建等待队列 
                BlockingQueue<Runnable> bqueue = new ArrayBlockingQueue<Runnable>(20); 
                //创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。 
                ThreadPoolExecutor pool = new ThreadPoolExecutor(2,3,2,TimeUnit.MILLISECONDS,bqueue); 
                //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口 
                Thread t1 = new MyThread(); 
                Thread t2 = new MyThread(); 
                Thread t3 = new MyThread(); 
                Thread t4 = new MyThread(); 
                Thread t5 = new MyThread(); 
                Thread t6 = new MyThread(); 
                Thread t7 = new MyThread(); 
                //将线程放入池中进行执行 
                pool.execute(t1); 
                pool.execute(t2); 
                pool.execute(t3); 
                pool.execute(t4); 
                pool.execute(t5); 
                pool.execute(t6); 
                pool.execute(t7); 
                //关闭线程池 
                pool.shutdown(); 
        } 


class MyThread extends Thread { 
        @Override 
        public void run() { 
                System.out.println(Thread.currentThread().getName() + "正在执行。。。"); 
                try { 
                        Thread.sleep(100L); 
                } catch (InterruptedException e) { 
                        e.printStackTrace(); 
                } 
        } 
}

pool-1-thread-1正在执行。。。 
pool-1-thread-2正在执行。。。 
pool-1-thread-2正在执行。。。 
pool-1-thread-1正在执行。。。 
pool-1-thread-2正在执行。。。 
pool-1-thread-1正在执行。。。 
pool-1-thread-2正在执行。。。 

Process finished with exit code 0

创建自定义线程池的构造方法很多,本例中参数的含义如下:
ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue)
用给定的初始参数和默认的线程工厂及处理程序创建新的 ThreadPoolExecutor。使用 Executors 工厂方法之一比使用此通用构造方法方便得多。
参数:
corePoolSize - 池中所保存的线程数,包括空闲线程。
maximumPoolSize - 池中允许的最大线程数。
keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
unit - keepAliveTime 参数的时间单位。
workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务。
抛出:
IllegalArgumentException - 如果 corePoolSize 或 keepAliveTime 小于零,或者 maximumPoolSize 小于或等于零,或者 corePoolSize 大于 maximumPoolSize。
NullPointerException - 如果 workQueue 为 null

自定义连接池稍微麻烦些,不过通过创建的ThreadPoolExecutor线程池对象,可以获取到当前线程池的尺寸、正在执行任务的线程数、工作队列等等。


8.Java Generics 


8.1

在JDK1.5之前的版本中,对于一个Collection类库中的容器类实例,可将任意类型对象加入其中(都被当作Object实例看待);从容器中取出的对象也只是一个Object实例,需要将其强制转型为期待的类型,这种强制转型的运行时正确性由程序员自行保证。 

例如以下代码片断: 

List intList = new ArrayList(); //创建一个List,准备存放一些Integer实例 
intList.add(new Integer(0)); 
intList.add(“1”); //不小心加入了一个字符串;但在编译和运行时都不报错,只有仔细的代码走 
                       //才能揪出 
Integer i0 = (Integer)intList.get(0); 
Integer i1 = (Integer)intList.get(1); //编译通过,直到运行时才抛ClassCastException 

而在JDK1.5中,可以创建一个明确只能存放某种特定类型对象的容器类实例,例如如下代码: 
List<Integer> intList = new ArrayList<Integer>(); //intList为存放Integer实例的List 
intList.add(new Integer(0)); 
Integer i0 = intList.get(0); //无需(Integer)强制转型;List<Integer>的get()返回的就是Integer类 
                                    //型对象 
intList.add(“1”); //编译不通过,因为List<Integer>的add()方法只接受Integer类型的参数 

       “List<Integer> intList = new ArrayList<Integer>();”就是最简单且最常用的Generic应用;显然,运用Generic后的代码更具可读性和健壮性。 

8.2         Generic类 

JDK1.5中Collection类库的大部分类都被改进为Generic类。以下是从JDK1.5源码中 

截取的关于List和Iterator接口定义的代码片断: 

public interface List<E> { 
       void add(E x); 
       Iterator<E> iterator; 


public interface Iterator<E> { 
       E next(); 
       boolean hasNext(); 


以List为例,“public interface List<E>”中的E是List的类型参数,用户在使用List 

时可为类型参数指定一个确定类型值(如List<Integer>)。类型值为Java编译器所用,确保用户代码类型安全。例如,编 译器知道List<Integer>的add()方法只接受Integer类型的参数,因此如果你在代码中将一个字符串传入add()将导致 编译错误;编译器知道Iterator<Integer>的next()方法返回一个Integer的实例,你在代码中也就无需对返回结果进 行(Integer)强制转型。代码检验通过(语法正确且不会导致运行时类型安全问题)后,编译器对现有代码有一个转换工作。简单的说,就是去除代码中的 类型值信息,在必要处添加转型代码。例如如下代码: 

public String demo() { 

       List<String> strList = new ArrayList<String>(); 

       strList.add(“Hello!”); 

       return strList.get(0); 



编译器在检验通过后,将其转换为: 

public String demo() { 

       List strList = new ArrayList(); //去除类型值<String> 

       strList.add(“Hello!”); 

       return (String)strList.get(0);  //添加转型动作代码(String) 




       可见,类型值信息只为Java编译器在编译时所用,确保代码无类型安全问题;验证通过之后,即被去除。对于JVM而言,只有如JDK1.5之前版本一样的 List,并无List<Integer>和List<String>之分。这也就是Java Generics实现中关键技术Erasure的基本思想。以下代码在控制台输出的就是“true”。 

List<String> strList = new ArrayList<String>(); 

List<Integer> intList = new ArrayList<Integer>(); 

System.out.println(strList.getClass() == intList.getClass()); 


       可以将Generic理解为:为提高Java代码类型安全性(在编译时确保,而非等到运行时才暴露),Java代码与Java编译器之间新增的一种约定规 范。Java编译器在编译结果*.class文件中供JVM读取的部分里没有保留Generic的任何信息;JVM看不到Generic的存在。 

       对于Generic类(设为GenericClass)的类型参数(设为T): 

1)        由于对于JVM而言,只有一个GenericClass类,所以GenericClass类的静态字段和静态方法的定义中不能使用T。T只能出现在 GenericClass的非静态字段或非静态方法中。也即T是与GenericClass的实例相关的信息; 

2)        T只在编译时被编译器理解,因此也就不能与运行时被JVM理解并执行其代表的操作的操作符(如instanceof 和new)联用。 


class GenericClass<T> { 

    T t1; 

    public void method1(T t){ 

       t1 = new T(); //编译错误,T不能与new联用 

       if (t1 instanceof T) {}; //编译错误,T不能与instanceof联用 

    }; 

    static T t2; //编译错误,静态字段不能使用T 

    public static void method2(T t){};//编译错误,静态方法不能使用T 



       Generic类可以有多个类型参数,且类型参数命名一般为大写单字符。例如Collection类库中的Map声明为: 

public interface Map<K,V> { 

       ……; 



8.3        Generic类和原(Raw)类 

对每一个Generic类,用户在使用时可以不指定类型参数。例如,对于List<E>,用户 

可以以“List<String> list;”方式使用,也可以以“List list;”方式使用。“List<String>”被称为参数化的Generic类(类型参数被赋值),而“List”称为原类。原类 List的使用方式和效果与JDK1.5之前版本List的一样;使用原类也就失去了Generic带来的可读性和健壮性的增强。 

       允许原类使用方式的存在显然是为了代码的向前兼容:即JDK1.5之前的代码在JDK1.5下仍然编译通过且正常运行。 

       当你在JDK1.5中使用原类并向原类实例中添加对象时,编译器会产生警告,因为它无法保证待添加对象类型的正确性。编译通过是为了保证代码向前兼容,产生警告是提醒潜在的风险。 

public void test () { 

    List list = new ArrayList(); 

    list.add("tt");//JDK1.5编译器对此行产生警告 



8.4 Generic类和子类 

List<String> ls = new ArrayList<String>(); 

List<Object> lo = ls; //编译错误:Type mismatch: cannot convert from List<Dummy> to 
                                                           //List<Object> 

以上第二行代码导致的编译错误“Type mismatch: cannot convert from List<Dummy> to 

List<Object>”是不是有点出人意料?直观上看,就像String是Object的子类,因此‘Object o = “String”’合法一样,存放String的List是存放Object的List的子类,因此第二行应该是合法的。反过来分析,如果第二行是合法 的,那么如下会导致运行时异常的代码也是合法的: 

lo.add(new Object); //会导致在ls中添加了非String对象 

String s = ls.get(0); //ls.get(0)返回的实际上只是一个Object实例,会导致ClassCastException 

       编译器显然不允许此种情形发生,因此不允许“List<Object> lo = ls”编译通过。 

       因此,直观上的“存放String的List是存放Object的List的子类”是错误的。更一般的说,设Foo是Bar的子类,G是Generic类型声明,G<Foo>不是G<Bar>的子类。 

8.5 参数化的Generic类和数组 

我们知道,如果T是S的子类,则T[]也是S[]的子类。因此,如下代码编译通过,只 

在运行时于第三行代码处抛ArrayStoreException。 

String[] words = new String[10]; 

Object[] objects = words; 

Objects[0] = new Object(); //编译通过,但运行时会抛ArrayStoreException


再分析如下代码: 

List<String>[] wordLists = new ArrayList<String>[10]; 

ArrayList<Integer> integerList = new ArrayList<Integer>(); 

integerList.add(123); 

Object[] objects = wordLists; 

objects[0] = integerList;//运行时不出错,因为运行时ArrayList<String>和ArrayList<Integer>都 
                                 //为ArrayList 

String s = wordlists[0].get(0); //编译通过,运行时抛ClassCastException 

       就出现了“正确使用了Generic,但在运行时仍然出现ClassCastException”的情形。显然Java编译器不允许此种情形的发生。事实 上,以上代码的第一行“List<String>[] wordLists = new ArrayList<String>[10];”就是编译不通过的,也就不存在接下来的代码。 

更一般地说,不能创建参数化的Generic类的数组。 

8.6 类型参数通配符? 

由“Generic类和子类”节知,Collection<Object>不是存放其它类型对象的Collection(例 

如Collection<String>)的基类(抽象),那么如何表示任一种参数化的Collection的呢?使用 Collection<?>。?即代表任一类型参数值。例如,我们可以很容易写出下面的通用函数printCollection(): 

public static void printCollection(Collection<?> c) { 

    //如此遍历Collection的简洁方式也是JDK1.5新引入的 

       for (Object o : c) { 

              System.out.println(o); 

    } 



       这样,既可以将Collection<String>的实例,也可以将Collection<Integer>的实例作为参数调用printCollection()方法。 

       然而,要注意一点,你不能往Collection<?>容器实例中加入任何非null元素,例如如下代码的第三行编译不通过: 

public static void testAdd(Collection<?> c) { 

       c.add(null); //编译通过 

       c.add(“test”); //编译错误 



       很好理解:c中要存放的对象的具体类型不确定,编译器无法验证待添加对象类型的正确性,因此不能加入对象实例;而null可以看作是任一个类的实例,因而允许加入。 

       另外,尽管c中要存放的对象的类型不确定,但我们知道任何类都是Object子类,因此从c中取出的对象都统一作为Object实例。 

       更进一步,如果BaseClass代表某个可被继承的类的类名,那么Collection<? extends BaseClass>代表类型参数值为BaseClass或BaseClass某个子类的任一参数化Collection。对于 Collection<? extends BaseClass>的实例c,因为c中要存放的对象具体类型不确定,不能往其加入非null对象,但从c中取出的对象都统一作为 BaseClass实例。事实上,你可以把Collection<?>看作Collection<? extends Object>的简洁形式。 

       另一种情形:如果SubClass代表任一个类的类名,那么Collection<? super SubClass>代表类型参数值为SubClass或SubClass某个祖先类的任一参数化Collection。对于 Collection<? super SubClass>的实例c,你可以将SubClass实例加入其中,但从中取出的对象都是Object实例。 

8.7 Generic方法 

我们可以定义Generic类,同样可以定义Generic方法,即将方法的一个或多个参数的类型参数化,如代码: 


public static <T> void fromArrayToCollection(T[] a, Collection<T> c) { 

       for (T o : a) { 

           c.add(o); //合法。注意与Collection<?>的区别 

    } 




我们可以以如下方式调用fromArrayToCollection(): 

Object[] oa = new Object[100]; 

Collection<Object> co = new ArrayList<Object>(); 

fromArrayToCollection(oa, co); //此时,T即为Object 


String[] sa = new String[100]; 

Collection<String> cs = new ArrayList<String>(); 

fromArrayToCollection(sa, cs); //此时,T即为String 

fromArrayToCollection(sa, co); //此时,T即为Object 

Integer[] ia = new Integer[100]; 

Float[] fa = new Float[100]; 

Number[] na = new Number[100]; 

Collection<Number> cn = new ArrayList<Number>(); 

fromArrayToCollection(ia, cn); //此时,T即为Number 

fromArrayToCollection(fa, cn); //此时,T即为Number 

fromArrayToCollection(na, cn); //此时,T即为Number 

fromArrayToCollection(na, co); //此时,T即为Object 

fromArrayToCollection(na, cs); //编译错误 

       通过以上代码可以看出,我们在调用fromArrayToCollection()时,无需明确指定T为何种类型(与Generic类的使用方式不同), 而是像调用一般method一样,直接提供参数值,编译器会根据提供的参数值自动为T赋类型值或提示编译错误(参数值不当)。 
考虑如下函数sum() 
public static long sum(Collection<? extends Number> numbers) { 
    long sum = 0; 
    for (Number n : numbers) { 
       sum += n.longValue(); 
    } 
    return sum; 


我们也可以将其以Generic方法实现: 

public static <T extends Number> long sum(Collection<T> numbers) { 

    long sum = 0; 

    for (Number n : numbers) { 

       sum += n.longValue(); 

    } 

    return sum; 


       那么对于一个方法,当要求参数类型可变时,是采用Generic方法,还是采用类型参数通配符方式呢?一般而言,如果参数类型间或参数类型与返回值类型间存在某种依赖关系,则采取Generic方法,否则采取类型参数通配符方式。 

       这一原则在Collection类库的源代码中得到了很好的体现,例如Collection接口的containsAll()、addAll()和toArray()方法: 


interface Collection<E> { 

       public boolean containsAll(Collecion<?> c); //参数间类型以及参数与返回 
                                                                    //值间类型无依赖 
       <T> T[] toArray(T[] a); //参数a与返回值都是相同类的数组,有依赖 


当然,根据需要,二者也可以结合使用,例如Collections中的copy()方法: 

class Collections { 

       public static <T> void copy(List<T> dest, List<? extends T> src) { 

           ……. 

       } 




jdk1.6新特性

1.Desktop类和SystemTray类
2.使用JAXB2来实现对象与XML之间的映射
3.StAX
4.使用Compiler API
5.轻量级Http Server API
6.插入式注解处理API(Pluggable Annotation Processing API)
7.用Console开发控制台程序
8.对脚本语言的支持
9.Common Annotations

1.Desktop类和SystemTray类

在JDK1.6中,AWT新增加了两个类:Desktop和SystemTray.
前者可以用来打开系统默认浏览器浏览指定的URL,打开系统默认邮件客户端给指定的邮箱发邮件,用默认应用程序打开或编辑文件(比如,用记事本打开以txt为后缀名的文件),用系统默认的打印机打印文档;后者可以用来在系统托盘区创建一个托盘程序.

2.使用JAXB2来实现对象与XML之间的映射

JAXB是Java Architecture for XML Binding的缩写,可以将一个Java对象转变成为XML格式,反之亦然.
我们把对象与关系数据库之间的映射称为ORM,其实也可以把对象与XML之间的映射称为OXM(Object XML Mapping).原来JAXB是Java EE的一部分,在JDK1.6中,SUN将其放到了Java SE中,这也是SUN的一贯做法.JDK1.6中自带的这个JAXB版本是2.0,比起1.0(JSR 31)来,JAXB2(JSR 222)用JDK5的新特性Annotation来标识要作绑定的类和属性等,这就极大简化了开发的工作量.实际上,在Java EE 5.0中,EJB和Web Services也通过Annotation来简化开发工作.另外,JAXB2在底层是用StAX(JSR 173)来处理XML文档.除了JAXB之外,我们还可以通过XMLBeans和Castor等来实现同样的功能.

3.StAX

StAX(JSR 173)是JDK1.6.0中除了DOM和SAX之外的又一种处理XML文档的API.
StAX 的来历:在JAXP1.3(JSR 206)有两种处理XML文档的方法:DOM(Document Object Model)和SAX(Simple API for XML).
JDK1.6.0中的JAXB2(JSR 222)和JAX-WS 2.0(JSR 224)都会用到StAXSun决定把StAX加入到JAXP家族当中来,并将JAXP的版本升级到1.4(JAXP1.4是JAXP1.3的维护版 本).JDK1.6里面JAXP的版本就是1.4.
StAX是The Streaming API for XML的缩写,一种利用拉模式解析(pull-parsing)XML文档的API.StAX通过提供一种基于事件迭代器(Iterator)的API让 程序员去控制xml文档解析过程,程序遍历这个事件迭代器去处理每一个解析事件,解析事件可以看做是程序拉出来的,也就是程序促使解析器产生一个解析事件 然后处理该事件,之后又促使解析器产生下一个解析事件,如此循环直到碰到文档结束符;
SAX也是基于事件处理xml文档,但却是用推模式解析,解析器解析完整个xml文档后,才产生解析事件,然后推给程序去处理这些事件;DOM采 用的方式是将整个xml文档映射到一颗内存树,这样就可以很容易地得到父节点和子结点以及兄弟节点的数据,但如果文档很大,将会严重影响性能.

4.使用Compiler API

现在我 们可以用JDK1.6 的Compiler API(JSR 199)去动态编译Java源文件,Compiler API结合反射功能就可以实现动态的产生Java代码并编译执行这些代码,有点动态语言的特征.
这个特性对于某些需要用到动态编译的应用程序相当有用,比如JSP Web Server,当我们手动修改JSP后,是不希望需要重启Web Server才可以看到效果的,这时候我们就可以用Compiler API来实现动态编译JSP文件,当然,现在的JSP Web Server也是支持JSP热部署的,现在的JSP Web Server通过在运行期间通过Runtime.exec或ProcessBuilder来调用javac来编译代码,这种方式需要我们产生另一个进程去 做编译工作,不够优雅容易使代码依赖与特定的操作系统;Compiler API通过一套易用的标准的API提供了更加丰富的方式去做动态编译,是跨平台的.

5.轻量级Http Server API

JDK1.6 提供了一个简单的Http Server API,据此我们可以构建自己的嵌入式Http Server,它支持Http和Https协议,提供了HTTP1.1的部分实现,没有被实现的那部分可以通过扩展已有的Http Server API来实现,程序员自己实现HttpHandler接口,HttpServer会调用HttpHandler实现类的回调方法来处理客户端请求,在这 里,我们把一个Http请求和它的响应称为一个交换,包装成HttpExchange类,HttpServer负责将HttpExchange传给 HttpHandler实现类的回调方法.

6.插入式注解处理API(Pluggable Annotation Processing API)

插入式注解处理API(JSR 269)提供一套标准API来处理Annotations(JSR 175)
实际上JSR 269不仅仅用来处理Annotation,我觉得更强大的功能是它建立了Java 语言本身的一个模型,它把method,package,constructor,type,variable, enum,annotation等Java语言元素映射为Types和Elements(两者有什么区别?),从而将Java语言的语义映射成为对象,我 们可以在javax.lang.model包下面可以看到这些类. 我们可以利用JSR 269提供的API来构建一个功能丰富的元编程(metaprogramming)环境.
JSR 269用Annotation Processor在编译期间而不是运行期间处理Annotation,Annotation Processor相当于编译器的一个插件,称为插入式注解处理.如果Annotation Processor处理Annotation时(执行process方法)产生了新的Java代码,编译器会再调用一次Annotation Processor,如果第二次处理还有新代码产生,就会接着调用Annotation Processor,直到没有新代码产生为止.每执行一次process()方法被称为一个"round",这样整个Annotation processing过程可以看作是一个round的序列.
JSR 269主要被设计成为针对Tools或者容器的API. 举个例子,我们想建立一套基于Annotation的单元测试框架(如TestNG),在测试类里面用Annotation来标识测试期间需要执行的测试方法.

7.用Console开发控制台程序

JDK1.6中提供了java.io.Console 类专用来访问基于字符的控制台设备.你的程序如果要与Windows下的cmd或者Linux下的Terminal交互,就可以用Console类代劳. 但我们不总是能得到可用的Console,一个JVM是否有可用的Console依赖于底层平台和JVM如何被调用.如果JVM是在交互式命令行(比如 Windows的cmd)中启动的,并且输入输出没有重定向到另外的地方,那么就可以得到一个可用的Console实例.

8.对脚本语言的支持

如: ruby,groovy,javascript

9.Common Annotations

Common annotations原本是Java EE 5.0(JSR 244)规范的一部分,现在SUN把它的一部分放到了Java SE 6.0中.
随着Annotation元数据功能(JSR 175)加入到Java SE 5.0里面,很多Java 技术(比如EJB,Web Services)都会用Annotation部分代替XML文件来配置运行参数(或者说是支持声明式编程,如EJB的声明式事务),如果这些技术为通用 目的都单独定义了自己的Annotations,显然有点重复建设,,为其他相关的Java技术定义一套公共的Annotation是有价值的,可以避免 重复建设的同时,也保证Java SE和Java EE 各种技术的一致性.
下面列举出Common Annotations 1.0里面的10个Annotations Common Annotations Annotation Retention Target Description Generated Source ANNOTATION_TYPE,CONSTRUCTOR,FIELD,LOCAL_VARIABLE,METHOD,PACKAGE,PARAMETER,TYPE 用于标注生成的源代码Resource Runtime TYPE,METHOD,FIELD用于标注所依赖的资源,容器据此注入外部资源依赖,有基于字段的注入和基于setter方法的注入两种方式 Resources Runtime TYPE同时标注多个外部依赖,容器会把所有这些外部依赖注入PostConstruct Runtime METHOD标注当容器注入所有依赖之后运行的方法,用来进行依赖注入后的初始化工作,只有一个方法可以标注为PostConstruct PreDestroy Runtime METHOD当对象实例将要被从容器当中删掉之前,要执行的回调方法要标注为PreDestroy RunAs Runtime TYPE用于标注用什么安全角色来执行被标注类的方法,这个安全角色和Container的Security角色一致的.RolesAllowed Runtime TYPE,METHOD用于标注允许执行被标注类或方法的安全角色,这个安全角色和Container的Security角色一致的PermitAll Runtime TYPE,METHOD允许所有角色执行被标注的类或方法DenyAll Runtime TYPE,METHOD不允许任何角色执行被标注的类或方法,表明该类或方法不能在Java EE容器里面运行DeclareRoles Runtime TYPE用来定义可以被应用程序检验的安全角色,通常用isUserInRole来检验安全角色.



jdk1.7新特性

1 对集合类的语言支持; 
2 自动资源管理; 
3 改进的通用实例创建类型推断; 
4 数字字面量下划线支持; 
5 switch中使用string; 
6 二进制字面量; 
7 简化可变参数方法调用。 


   下面我们来仔细看一下这7大新功能: 

     1 对集合类的语言支持 

      Java将包含对创建集合类的第一类语言支持。这意味着集合类的创建可以像Ruby和Perl那样了。 
      原本需要这样: 
         List<String> list = new ArrayList<String>(); 
         list.add("item"); 
         String item = list.get(0); 
   
         Set<String> set = new HashSet<String>(); 
         set.add("item"); 
         Map<String, Integer> map = new HashMap<String, Integer>(); 
         map.put("key", 1); 
         int value = map.get("key"); 

      现在你可以这样: 
         List<String> list = ["item"]; 
         String item = list[0]; 
         
         Set<String> set = {"item"}; 
         
         Map<String, Integer> map = {"key" : 1}; 
         int value = map["key"]; 

      这些集合是不可变的。 

   

     2 自动资源管理 

      Java中某些资源是需要手动关闭的,如InputStream,Writes,Sockets,Sql classes等。这个新的语言特性允许try语句本身申请更多的资源, 
   这些资源作用于try代码块,并自动关闭。 
      这个: 
         BufferedReader br = new BufferedReader(new FileReader(path)); 
         try { 
         return br.readLine(); 
               } finally { 
                   br.close(); 
         } 

      变成了这个: 
          try (BufferedReader br = new BufferedReader(new FileReader(path)) { 
             return br.readLine(); 
          } 
    
      你可以定义关闭多个资源: 
         try ( 
             InputStream in = new FileInputStream(src); 
             OutputStream out = new FileOutputStream(dest)) 
         { 
         // code 
         } 
      为了支持这个行为,所有可关闭的类将被修改为可以实现一个Closable(可关闭的)接口。 
   

     3 增强的对通用实例创建(diamond)的类型推断 

      类型推断是一个特殊的烦恼,下面的代码: 
         Map<String, List<String>> anagrams = new HashMap<String, List<String>>(); 

      通过类型推断后变成: 
         Map<String, List<String>> anagrams = new HashMap<>(); 
      这个<>被叫做diamond(钻石)运算符,这个运算符从引用的声明中推断类型。 

   

     4 数字字面量下划线支持 

      很长的数字可读性不好,在Java 7中可以使用下划线分隔长int以及long了,如: 
         int one_million = 1_000_000; 
   运算时先去除下划线,如:1_1 * 10 = 110,120 – 1_0 = 110 
   

     5 switch中使用string 

      以前你在switch中只能使用number或enum。现在你可以使用string了: 
         String s = ... 
         switch(s) { 
         case "quux": 
              processQuux(s); 
     // fall-through 
         case "foo": 
  case "bar": 
              processFooOrBar(s); 
          break; 
         case "baz": 
       processBaz(s); 
              // fall-through 
  default: 
                processDefault(s); 
                break; 
  } 

  

     6 二进制字面量 

      由于继承C语言,Java代码在传统上迫使程序员只能使用十进制,八进制或十六进制来表示数(numbers)。 
      由于很少的域是以bit导向的,这种限制可能导致错误。你现在可以使用0b前缀创建二进制字面量: 
         int binary = 0b1001_1001; 
   现在,你可以使用二进制字面量这种表示方式,并且使用非常简短的代码,可将二进制字符转换为数据类型,如在byte或short。 
   byte aByte = (byte)0b001;    
   short aShort = (short)0b010;    

   

      7 简化的可变参数调用 

      当程序员试图使用一个不可具体化的可变参数并调用一个*varargs* (可变)方法时,编辑器会生成一个“非安全操作”的警告。 
   JDK 7将警告从call转移到了方法声明(methord declaration)的过程中。这样API设计者就可以使用vararg,因为警告的数量大大减少了。


JDK1.8新特性

1 接口的默认方法
2 Lambda 表达式
3 函数式接口
4 方法与构造函数引用
5 Lambda 作用域
6 访问局部变量
7 访问对象字段与静态变量
8 访问接口的默认方法
9 Date API
10 Annotation 注解



本教程将Java8的新特新逐一列出,并将使用简单的代码示例来指导你如何使用默认接口方法,lambda表达式,方法引用以及多重Annotation,之后你将会学到最新的API上的改进,比如流,函数式接口,Map以及全新的日期API

“Java is still not dead—and people are starting to figure that out.”

本教程将用带注释的简单代码来描述新特性,你将看不到大片吓人的文字。

1 接口的默认方法


Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,示例如下:

代码如下:

interface Formula {
    double calculate(int a);

    default double sqrt(int a) {
        return Math.sqrt(a);
    }
}



Formula接口在拥有calculate方法之外同时还定义了sqrt方法,实现了Formula接口的子类只需要实现一个calculate方法,默认方法sqrt将在子类上可以直接使用。
代码如下:

Formula formula = new Formula() {
    @Override
    public double calculate(int a) {
        return sqrt(a * 100);
    }
};

formula.calculate(100);     // 100.0
formula.sqrt(16);           // 4.0


文中的formula被实现为一个匿名类的实例,该代码非常容易理解,6行代码实现了计算 sqrt(a * 100)。在下一节中,我们将会看到实现单方法接口的更简单的做法。

译者注: 在Java中只有单继承,如果要让一个类赋予新的特性,通常是使用接口来实现,在C++中支持多继承,允许一个子类同时具有多个父类的接口与功能,在其他语言中,让一个类同时具有其他的可复用代码的方法叫做mixin。新的Java 8 的这个特新在编译器实现的角度上来说更加接近Scala的trait。 在C#中也有名为扩展方法的概念,允许给已存在的类型扩展方法,和Java 8的这个在语义上有差别。

2 Lambda 表达式


首先看看在老版本的Java中是如何排列字符串的:

代码如下:

List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return b.compareTo(a);
    }
});


只需要给静态方法 Collections.sort 传入一个List对象以及一个比较器来按指定顺序排列。通常做法都是创建一个匿名的比较器对象然后将其传递给sort方法。

在Java 8 中你就没必要使用这种传统的匿名对象的方式了,Java 8提供了更简洁的语法,lambda表达式:

代码如下:

Collections.sort(names, (String a, String b) -> {
    return b.compareTo(a);
});

看到了吧,代码变得更段且更具有可读性,但是实际上还可以写得更短:
代码如下:

Collections.sort(names, (String a, String b) -> b.compareTo(a));

对于函数体只有一行代码的,你可以去掉大括号{}以及return关键字,但是你还可以写得更短点:
代码如下:

Collections.sort(names, (a, b) -> b.compareTo(a));

Java编译器可以自动推导出参数类型,所以你可以不用再写一次类型。接下来我们看看lambda表达式还能作出什么更方便的东西来:

3 函数式接口


Lambda表达式是如何在java的类型系统中表示的呢?每一个lambda表达式都对应一个类型,通常是接口类型。而“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。因为 默认方法 不算抽象方法,所以你也可以给你的函数式接口添加默认方法。

我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface 注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。

示例如下:

代码如下:

@FunctionalInterface
interface Converter<F, T> {
    T convert(F from);
}
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted);    // 123

需要注意如果@FunctionalInterface如果没有指定,上面的代码也是对的。

译者注 将lambda表达式映射到一个单方法的接口上,这种做法在Java 8之前就有别的语言实现,比如Rhino JavaScript解释器,如果一个函数参数接收一个单方法的接口而你传递的是一个function,Rhino 解释器会自动做一个单接口的实例到function的适配器,典型的应用场景有 org.w3c.dom.events.EventTarget 的addEventListener 第二个参数 EventListener。

4 方法与构造函数引用


前一节中的代码还可以通过静态方法引用来表示:

代码如下:

Converter<String, Integer> converter = Integer::valueOf;
Integer converted = converter.convert("123");
System.out.println(converted);   // 123

Java 8 允许你使用 :: 关键字来传递方法或者构造函数引用,上面的代码展示了如何引用一个静态方法,我们也可以引用一个对象的方法:
代码如下:

 converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted);    // "J"

接下来看看构造函数是如何使用::关键字来引用的,首先我们定义一个包含多个构造函数的简单类:
代码如下:

class Person {
    String firstName;
    String lastName;

    Person() {}

    Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}


接下来我们指定一个用来创建Person对象的对象工厂接口:
代码如下:

interface PersonFactory<P extends Person> {
    P create(String firstName, String lastName);
}

这里我们使用构造函数引用来将他们关联起来,而不是实现一个完整的工厂:
代码如下:

PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");

我们只需要使用 Person::new 来获取Person类构造函数的引用,Java编译器会自动根据PersonFactory.create方法的签名来选择合适的构造函数。

5 Lambda 作用域


在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。

6 访问局部变量

我们可以直接在lambda表达式中访问外层的局部变量:

代码如下:

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

stringConverter.convert(2);     // 3


但是和匿名对象不同的是,这里的变量num可以不用声明为final,该代码同样正确:
代码如下:

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

stringConverter.convert(2);     // 3


不过这里的num必须不可被后面的代码修改(即隐性的具有final的语义),例如下面的就无法编译:
代码如下:

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

在lambda表达式中试图修改num同样是不允许的。

7 访问对象字段与静态变量

和本地变量不同的是,lambda内部对于实例的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的:

代码如下:
class Lambda4 {
    static int outerStaticNum;
    int outerNum;

    void testScopes() {
        Converter<Integer, String> stringConverter1 = (from) -> {
            outerNum = 23;
            return String.valueOf(from);
        };

        Converter<Integer, String> stringConverter2 = (from) -> {
            outerStaticNum = 72;
            return String.valueOf(from);
        };
    }
}



8 访问接口的默认方法


还记得第一节中的formula例子么,接口Formula定义了一个默认方法sqrt可以直接被formula的实例包括匿名对象访问到,但是在lambda表达式中这个是不行的。
Lambda表达式中是无法访问到默认方法的,以下代码将无法编译:

代码如下:

Formula formula = (a) -> sqrt( a * 100);
Built-in Functional Interfaces

JDK 1.8 API包含了很多内建的函数式接口,在老Java中常用到的比如Comparator或者Runnable接口,这些接口都增加了@FunctionalInterface注解以便能用在lambda上。
Java 8 API同样还提供了很多全新的函数式接口来让工作更加方便,有一些接口是来自Google Guava库里的,即便你对这些很熟悉了,还是有必要看看这些是如何扩展到lambda上使用的。

Predicate接口

Predicate 接口只有一个参数,返回boolean类型。该接口包含多种默认方法来将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 接口

Function 接口有一个参数并且返回一个结果,并附带了一些可以和其他函数组合的默认方法(compose, andThen):


代码如下:

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

backToString.apply("123");     // "123"



Supplier 接口

Supplier 接口返回一个任意范型的值,和Function接口不同的是该接口没有任何参数

代码如下:

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


Consumer 接口

Consumer 接口表示执行在单个参数上的操作。

代码如下:

Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));


Comparator 接口

Comparator 是老Java中的经典接口, Java 8在此之上添加了多种默认方法:

代码如下:

Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);

Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");

comparator.compare(p1, p2);             // > 0
comparator.reversed().compare(p1, p2);  // < 0



Optional 接口

Optional 不是函数是接口,这是个用来防止NullPointerException异常的辅助类型,这是下一届中将要用到的重要概念,现在先简单的看看这个接口能干什么:

Optional 被定义为一个简单的容器,其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是偶尔却可能返回了null,而在Java 8中,不推荐你返回null而是返回Optional。


代码如下:

Optional<String> optional = Optional.of("bam");

optional.isPresent();           // true
optional.get();                 // "bam"
optional.orElse("fallback");    // "bam"

optional.ifPresent((s) -> System.out.println(s.charAt(0)));     // "b"



Stream 接口

java.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如 java.util.Collection的子类,List或者Set, Map不支持。Stream的操作可以串行执行或者并行执行。

首先看看Stream是怎么用,首先创建实例代码的用到的数据List:


代码如下:

List<String> stringCollection = new ArrayList<>();
stringCollection.add("ddd2");
stringCollection.add("aaa2");
stringCollection.add("bbb1");
stringCollection.add("aaa1");
stringCollection.add("bbb3");
stringCollection.add("ccc");
stringCollection.add("bbb2");
stringCollection.add("ddd1");

Java 8扩展了集合类,可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个Stream。下面几节将详细解释常用的Stream操作:

Filter 过滤

过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他Stream操作。


代码如下:

stringCollection
    .stream()
    .filter((s) -> s.startsWith("a"))
    .forEach(System.out::println);

// "aaa2", "aaa1"



Sort 排序

排序是一个中间操作,返回的是排序好后的Stream。如果你不指定一个自定义的Comparator则会使用默认排序。


代码如下:

stringCollection
    .stream()
    .sorted()
    .filter((s) -> s.startsWith("a"))
    .forEach(System.out::println);

// "aaa1", "aaa2"


需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据stringCollection是不会被修改的:

代码如下:

System.out.println(stringCollection);
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1


Map 映射

中间操作map会将元素根据指定的Function接口来依次将元素转成另外的对象,下面的示例展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。

代码如下:

stringCollection
    .stream()
    .map(String::toUpperCase)
    .sorted((a, b) -> b.compareTo(a))
    .forEach(System.out::println);

// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"



Match 匹配

Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作,并返回一个boolean类型的值。


代码如下:

boolean anyStartsWithA = 
    stringCollection
        .stream()
        .anyMatch((s) -> s.startsWith("a"));

System.out.println(anyStartsWithA);      // true

boolean allStartsWithA = 
    stringCollection
        .stream()
        .allMatch((s) -> s.startsWith("a"));

System.out.println(allStartsWithA);      // false

boolean noneStartsWithZ = 
    stringCollection
        .stream()
        .noneMatch((s) -> s.startsWith("z"));

System.out.println(noneStartsWithZ);      // true

Count 计数

计数是一个最终操作,返回Stream中元素的个数,返回值类型是long。


代码如下:

long startsWithB = 
    stringCollection
        .stream()
        .filter((s) -> s.startsWith("b"))
        .count();

System.out.println(startsWithB);    // 3



Reduce 规约

这是一个最终操作,允许通过指定的函数来讲stream中的多个元素规约为一个元素,规越后的结果是通过Optional接口表示的:


代码如下:

Optional<String> reduced =
    stringCollection
        .stream()
        .sorted()
        .reduce((s1, s2) -> s1 + "#" + s2);

reduced.ifPresent(System.out::println);
// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"



并行Streams

前面提到过Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。

下面的例子展示了是如何通过并行Stream来提升性能:

首先我们创建一个没有重复元素的大表:


代码如下:

int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
    UUID uuid = UUID.randomUUID();
    values.add(uuid.toString());
}

然后我们计算一下排序这个Stream要耗时多久,
串行排序:

代码如下:

long t0 = System.nanoTime();

long count = values.stream().sorted().count();
System.out.println(count);

long t1 = System.nanoTime();

long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("sequential sort took: %d ms", millis));

// 串行耗时: 899 ms
并行排序:


代码如下:

long t0 = System.nanoTime();

long count = values.parallelStream().sorted().count();
System.out.println(count);

long t1 = System.nanoTime();

long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("parallel sort took: %d ms", millis));

// 并行排序耗时: 472 ms
上面两个代码几乎是一样的,但是并行版的快了50%之多,唯一需要做的改动就是将stream()改为parallelStream()。

Map

前面提到过,Map类型不支持stream,不过Map提供了一些新的有用的方法来处理一些日常任务。


代码如下:

Map<Integer, String> map = new HashMap<>();

for (int i = 0; i < 10; i++) {
    map.putIfAbsent(i, "val" + i);
}

map.forEach((id, val) -> System.out.println(val));
以上代码很容易理解, putIfAbsent 不需要我们做额外的存在性检查,而forEach则接收一个Consumer接口来对map里的每一个键值对进行操作。

下面的例子展示了map上的其他有用的函数:


代码如下:

map.computeIfPresent(3, (num, val) -> val + num);
map.get(3);             // val33

map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9);     // false

map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23);    // true

map.computeIfAbsent(3, num -> "bam");
map.get(3);             // val33


接下来展示如何在Map里删除一个键值全都匹配的项:

代码如下:

map.remove(3, "val3");
map.get(3);             // val33

map.remove(3, "val33");
map.get(3);             // null


另外一个有用的方法:

代码如下:

map.getOrDefault(42, "not found");  // not found

对Map的元素做合并也变得很容易了:

代码如下:

map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
map.get(9);             // val9

map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
map.get(9);             // val9concat


Merge做的事情是如果键名不存在则插入,否则则对原键对应的值做合并操作并重新插入到map中。

9 Date API


Java 8 在包java.time下包含了一组全新的时间日期API。新的日期API和开源的Joda-Time库差不多,但又不完全一样,下面的例子展示了这组新API里最重要的一些部分:

Clock 时钟

Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数。某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建老的java.util.Date对象。


代码如下:

Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();

Instant instant = clock.instant();
Date legacyDate = Date.from(instant);   // legacy java.util.Date



Timezones 时区

在新API中时区使用ZoneId来表示。时区可以很方便的使用静态方法of来获取到。 时区定义了到UTS时间的时间差,在Instant时间点对象到本地日期对象之间转换的时候是极其重要的。


代码如下:

System.out.println(ZoneId.getAvailableZoneIds());
// prints all available timezone ids

ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
System.out.println(zone1.getRules());
System.out.println(zone2.getRules());

// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]



LocalTime 本地时间

LocalTime 定义了一个没有时区信息的时间,例如 晚上10点,或者 17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差:


代码如下:

LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);

System.out.println(now1.isBefore(now2));  // false

long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);

System.out.println(hoursBetween);       // -3
System.out.println(minutesBetween);     // -239


LocalTime 提供了多种工厂方法来简化对象的创建,包括解析时间字符串。

代码如下:

LocalTime late = LocalTime.of(23, 59, 59);
System.out.println(late);       // 23:59:59

DateTimeFormatter germanFormatter =
    DateTimeFormatter
        .ofLocalizedTime(FormatStyle.SHORT)
        .withLocale(Locale.GERMAN);

LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);
System.out.println(leetTime);   // 13:37


LocalDate 本地日期

LocalDate 表示了一个确切的日期,比如 2014-03-11。该对象值是不可变的,用起来和LocalTime基本一致。下面的例子展示了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。


代码如下:

LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
LocalDate yesterday = tomorrow.minusDays(2);

LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();


System.out.println(dayOfWeek);    // FRIDAY
从字符串解析一个LocalDate类型和解析LocalTime一样简单:

代码如下:

DateTimeFormatter germanFormatter =
    DateTimeFormatter
        .ofLocalizedDate(FormatStyle.MEDIUM)
        .withLocale(Locale.GERMAN);

LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas);   // 2014-12-24



LocalDateTime 本地日期时间

LocalDateTime 同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime和LocalTime还有LocalDate一样,都是不可变的。LocalDateTime提供了一些能访问具体字段的方法。


代码如下:

LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);

DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek);      // WEDNESDAY

Month month = sylvester.getMonth();
System.out.println(month);          // DECEMBER

long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay);    // 1439


只要附加上时区信息,就可以将其转换为一个时间点Instant对象,Instant时间点对象可以很容易的转换为老式的java.util.Date。

代码如下:

Instant instant = sylvester
        .atZone(ZoneId.systemDefault())
        .toInstant();

Date legacyDate = Date.from(instant);
System.out.println(legacyDate);     // Wed Dec 31 23:59:59 CET 2014


格式化LocalDateTime和格式化时间和日期一样的,除了使用预定义好的格式外,我们也可以自己定义格式:

代码如下:

DateTimeFormatter formatter =
    DateTimeFormatter
        .ofPattern("MMM dd, yyyy - HH:mm");

LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);
String string = formatter.format(parsed);
System.out.println(string);     // Nov 03, 2014 - 07:13


和java.text.NumberFormat不一样的是新版的DateTimeFormatter是不可变的,所以它是线程安全的。
关于时间日期格式的详细信息:http://download.java.net/jdk8/docs/api/java/time/format/DateTimeFormatter.html

10 Annotation 注解


在Java 8中支持多重注解了,先看个例子来理解一下是什么意思。
首先定义一个包装类Hints注解用来放置一组具体的Hint注解:


代码如下:

@interface Hints {
    Hint[] value();
}

@Repeatable(Hints.class)
@interface Hint {
    String value();
}


Java 8允许我们把同一个类型的注解使用多次,只需要给该注解标注一下@Repeatable即可。

例 1: 使用包装类当容器来存多个注解(老方法)


代码如下:

@Hints({@Hint("hint1"), @Hint("hint2")})
class Person {}

例 2:使用多重注解(新方法)

代码如下:

@Hint("hint1")
@Hint("hint2")
class Person {}

第二个例子里java编译器会隐性的帮你定义好@Hints注解,了解这一点有助于你用反射来获取这些信息:

代码如下:

Hint hint = Person.class.getAnnotation(Hint.class);
System.out.println(hint);                   // null

Hints hints1 = Person.class.getAnnotation(Hints.class);
System.out.println(hints1.value().length);  // 2

Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class);
System.out.println(hints2.length);          // 2


即便我们没有在Person类上定义@Hints注解,我们还是可以通过 getAnnotation(Hints.class) 来获取 @Hints注解,更加方便的方法是使用 getAnnotationsByType 可以直接获取到所有的@Hint注解。
另外Java 8的注解还增加到两种新的target上了:


代码如下:

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}

代码如下:

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}

JDK1.9新特性

转载自:https://yq.aliyun.com/articles/173987

Java 8 发布三年多之后,即将快到2017年7月下一个版本发布的日期了。 你可能已经听说过 Java 9 的模块系统,但是这个新版本还有许多其它的更新。 这里有九个令人兴奋的新功能将与 Java 9 一起发布。

1. Java 平台级模块系统

Java 9 的定义功能是一套全新的模块系统。当代码库越来越大,创建复杂,盘根错节的“意大利面条式代码”的几率呈指数级的增长。这时候就得面对两个基础的问题: 很难真正地对代码进行封装, 而系统并没有对不同部分(也就是 JAR 文件)之间的依赖关系有个明确的概念。每一个公共类都可以被类路径之下任何其它的公共类所访问到, 这样就会导致无意中使用了并不想被公开访问的 API。此外,类路径本身也存在问题: 你怎么知晓所有需要的 JAR 都已经有了, 或者是不是会有重复的项呢? 模块系统把这俩个问题都给解决了。

模块化的 JAR 文件都包含一个额外的模块描述器。在这个模块描述器中, 对其它模块的依赖是通过 “ requires” 来表示的。另外, “ exports” 语句控制着哪些包是可以被其它模块访问到的。所有不被导出的包默认都封装在模块的里面。如下是一个模块描述器的示例,存在于 “module-info.java” 文件中:

module blog {

我们可以如下展示模块:

32130002475b31336ae7

请注意,两个模块都包含封装的包,因为它们没有被导出(使用橙色盾牌可视化)。 没有人会偶然地使用来自这些包中的类。Java 平台本身也使用自己的模块系统进行了模块化。通过封装 JDK 的内部类,平台更安全,持续改进也更容易。

当启动一个模块化应用时, JVM 会验证是否所有的模块都能使用,这基于 `requires` 语句——比脆弱的类路径迈进了一大步。模块允许你更好地强制结构化封装你的应用并明确依赖。你可以在这个 课程 中学习更多关于 Java 9 中模块工作的信息 。

如果你想学习Java可以来这个群,首先是二二零,中间是一四二,最后是九零六,里面可以学习和交流,也有资料可以下载。

2. Linking

当你使用具有显式依赖关系的模块和模块化的 JDK 时,新的可能性出现了。你的应用程序模块现在将声明其对其他应用程序模块的依赖以及对其所使用的 JDK 模块的依赖。为什么不使用这些信息创建一个最小的运行时环境,其中只包含运行应用程序所需的那些模块呢? 这可以通过 Java 9 中的新的 jlink 工具实现。你可以创建针对应用程序进行优化的最小运行时映像而不需要使用完全加载 JDK 安装版本。

3. JShell: 交互式 Java REPL

许多语言已经具有交互式编程环境,Java 现在加入了这个俱乐部。您可以从控制台启动 jshell ,并直接启动输入和执行 Java 代码。 jshell 的即时反馈使它成为探索 API 和尝试语言特性的好工具。

322d0003e3f6c42e3137

测试一个 Java 正则表达式是一个很好的说明 jshell 如何使您的生活更轻松的例子。 交互式 shell 还可以提供良好的教学环境以及提高生产力,您可以 在此 了解更多信息。在教人们如何编写 Java 的过程中,不再需要解释 “public static void main(String [] args)” 这句废话。

4. 改进的 Javadoc

有时一些小事情可以带来很大的不同。你是否就像我一样在一直使用 Google 来查找正确的 Javadoc 页面呢? 这不再需要了。Javadoc 现在支持在 API 文档中的进行搜索。另外,Javadoc 的输出现在符合兼容 HTML5 标准。此外,你会注意到,每个 Javadoc 页面都包含有关 JDK 模块类或接口来源的信息。

321000048ba2cfe25db6

5. 集合工厂方法

通常,您希望在代码中创建一个集合(例如,List 或 Set ),并直接用一些元素填充它。 实例化集合,几个 “add” 调用,使得代码重复。 Java 9,添加了几种集合工厂方法:

Set<Integer> ints = Set.of(1, 2, 3);

除了更短和更好阅读之外,这些方法也可以避免您选择特定的集合实现。 事实上,从工厂方法返回已放入数个元素的集合实现是高度优化的。这是可能的,因为它们是不可变的:在创建后,继续添加元素到这些集合会导致 “UnsupportedOperationException” 。

6. 改进的 Stream API

长期以来,Stream API 都是 Java 标准库最好的改进之一。通过这套 API 可以在集合上建立用于转换的申明管道。在 Java 9 中它会变得更好。Stream 接口中添加了 4 个新的方法: dropWhile, takeWhile, ofNullable 。还有个 iterate 方法的新重载方法,可以让你提供一个 Predicate (判断条件)来指定什么时候结束迭代:

IntStream.iterate(1, i -> i < 100, i -> i + 1).forEach(System.out::println);

第二个参数是一个 Lambda,它会在当前 IntStream 中的元素到达 100 的时候返回 true。因此这个简单的示例是向控制台打印 1 到 99。

除了对 Stream 本身的扩展,Optional 和 Stream 之间的结合也得到了改进。现在可以通过 Optional 的新方法 `stram` 将一个 Optional 对象转换为一个(可能是空的) Stream 对象:

Stream<Integer> s = Optional.of(1).stream();

在组合复杂的 Stream 管道时,将 Optional 转换为 Stream 非常有用。

7. 私有接口方法

Java 8 为我们带来了接口的默认方法。 接口现在也可以包含行为,而不仅仅是方法签名。 但是,如果在接口上有几个默认方法,代码几乎相同,会发生什么情况? 通常,您将重构这些方法,调用一个可复用的私有方法。 但默认方法不能是私有的。 将复用代码创建为一个默认方法不是一个解决方案,因为该辅助方法会成为公共API的一部分。 使用 Java 9,您可以向接口添加私有辅助方法来解决此问题:

public interface MyInterface { void normalInterfaceMethod(); default void interfaceMethodWithDefault() { init(); } default void anotherDefaultMethod() { init(); } // This method is not part of the public API exposed by MyInterface

如果您使用默认方法开发 API ,那么私有接口方法可能有助于构建其实现。

8. HTTP/2

Java 9 中有新的方式来处理 HTTP 调用。这个迟到的特性用于代替老旧的 `HttpURLConnection` API,并提供对 WebSocket 和 HTTP/2 的支持。注意:新的 HttpClient API 在 Java 9 中以所谓的孵化器模块 交付。也就是说,这套 API 不能保证 100% 完成。不过你可以在 Java 9 中开始使用这套 API:

HttpClient client = HttpClient.newHttpClient();

除了这个简单的请求/响应模型之外,HttpClient 还提供了新的 API 来处理 HTTP/2 的特性,比如流和服务端推送。


9. 多版本兼容 JAR

我们最后要来着重介绍的这个特性对于库的维护者而言是个特别好的消息。当一个新版本的 Java 出现的时候,你的库用户要花费数年时间才会切换到这个新的版本。这就意味着库得去向后兼容你想要支持的最老的 Java 版本 (许多情况下就是 Java 6 或者 7)。这实际上意味着未来的很长一段时间,你都不能在库中运用 Java 9 所提供的新特性。幸运的是,多版本兼容 JAR 功能能让你创建仅在特定版本的 Java 环境中运行库程序时选择使用的 class 版本:

1
2
3
4
5
6
7
8
9
multirelease.jar
├── META-INF
│   └── versions
│       └── 9
│           └── multirelease
│               └── Helper.class
├── multirelease
    ├── Helper.class
    └── Main.class

在上述场景中, multirelease.jar 可以在 Java 9 中使用, 不过 Helper 这个类使用的不是顶层的 multirelease.Helper 这个 class, 而是处在“META-INF/versions/9”下面的这个。这是特别为 Java 9 准备的 class 版本,可以运用 Java 9 所提供的特性和库。同时,在早期的 Java 诸版本中使用这个 JAR 也是能运行的,因为较老版本的 Java 只会看到顶层的这个 Helper 类。





代码如下:

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}

JDK1.9新特性

转载自:https://yq.aliyun.com/articles/173987

Java 8 发布三年多之后,即将快到2017年7月下一个版本发布的日期了。 你可能已经听说过 Java 9 的模块系统,但是这个新版本还有许多其它的更新。 这里有九个令人兴奋的新功能将与 Java 9 一起发布。

1. Java 平台级模块系统

Java 9 的定义功能是一套全新的模块系统。当代码库越来越大,创建复杂,盘根错节的“意大利面条式代码”的几率呈指数级的增长。这时候就得面对两个基础的问题: 很难真正地对代码进行封装, 而系统并没有对不同部分(也就是 JAR 文件)之间的依赖关系有个明确的概念。每一个公共类都可以被类路径之下任何其它的公共类所访问到, 这样就会导致无意中使用了并不想被公开访问的 API。此外,类路径本身也存在问题: 你怎么知晓所有需要的 JAR 都已经有了, 或者是不是会有重复的项呢? 模块系统把这俩个问题都给解决了。

模块化的 JAR 文件都包含一个额外的模块描述器。在这个模块描述器中, 对其它模块的依赖是通过 “ requires” 来表示的。另外, “ exports” 语句控制着哪些包是可以被其它模块访问到的。所有不被导出的包默认都封装在模块的里面。如下是一个模块描述器的示例,存在于 “module-info.java” 文件中:

module blog {

我们可以如下展示模块:

32130002475b31336ae7

请注意,两个模块都包含封装的包,因为它们没有被导出(使用橙色盾牌可视化)。 没有人会偶然地使用来自这些包中的类。Java 平台本身也使用自己的模块系统进行了模块化。通过封装 JDK 的内部类,平台更安全,持续改进也更容易。

当启动一个模块化应用时, JVM 会验证是否所有的模块都能使用,这基于 `requires` 语句——比脆弱的类路径迈进了一大步。模块允许你更好地强制结构化封装你的应用并明确依赖。你可以在这个 课程 中学习更多关于 Java 9 中模块工作的信息 。

如果你想学习Java可以来这个群,首先是二二零,中间是一四二,最后是九零六,里面可以学习和交流,也有资料可以下载。

2. Linking

当你使用具有显式依赖关系的模块和模块化的 JDK 时,新的可能性出现了。你的应用程序模块现在将声明其对其他应用程序模块的依赖以及对其所使用的 JDK 模块的依赖。为什么不使用这些信息创建一个最小的运行时环境,其中只包含运行应用程序所需的那些模块呢? 这可以通过 Java 9 中的新的 jlink 工具实现。你可以创建针对应用程序进行优化的最小运行时映像而不需要使用完全加载 JDK 安装版本。

3. JShell: 交互式 Java REPL

许多语言已经具有交互式编程环境,Java 现在加入了这个俱乐部。您可以从控制台启动 jshell ,并直接启动输入和执行 Java 代码。 jshell 的即时反馈使它成为探索 API 和尝试语言特性的好工具。

322d0003e3f6c42e3137

测试一个 Java 正则表达式是一个很好的说明 jshell 如何使您的生活更轻松的例子。 交互式 shell 还可以提供良好的教学环境以及提高生产力,您可以 在此 了解更多信息。在教人们如何编写 Java 的过程中,不再需要解释 “public static void main(String [] args)” 这句废话。

4. 改进的 Javadoc

有时一些小事情可以带来很大的不同。你是否就像我一样在一直使用 Google 来查找正确的 Javadoc 页面呢? 这不再需要了。Javadoc 现在支持在 API 文档中的进行搜索。另外,Javadoc 的输出现在符合兼容 HTML5 标准。此外,你会注意到,每个 Javadoc 页面都包含有关 JDK 模块类或接口来源的信息。

321000048ba2cfe25db6

5. 集合工厂方法

通常,您希望在代码中创建一个集合(例如,List 或 Set ),并直接用一些元素填充它。 实例化集合,几个 “add” 调用,使得代码重复。 Java 9,添加了几种集合工厂方法:

Set<Integer> ints = Set.of(1, 2, 3);

除了更短和更好阅读之外,这些方法也可以避免您选择特定的集合实现。 事实上,从工厂方法返回已放入数个元素的集合实现是高度优化的。这是可能的,因为它们是不可变的:在创建后,继续添加元素到这些集合会导致 “UnsupportedOperationException” 。

6. 改进的 Stream API

长期以来,Stream API 都是 Java 标准库最好的改进之一。通过这套 API 可以在集合上建立用于转换的申明管道。在 Java 9 中它会变得更好。Stream 接口中添加了 4 个新的方法: dropWhile, takeWhile, ofNullable 。还有个 iterate 方法的新重载方法,可以让你提供一个 Predicate (判断条件)来指定什么时候结束迭代:

IntStream.iterate(1, i -> i < 100, i -> i + 1).forEach(System.out::println);

第二个参数是一个 Lambda,它会在当前 IntStream 中的元素到达 100 的时候返回 true。因此这个简单的示例是向控制台打印 1 到 99。

除了对 Stream 本身的扩展,Optional 和 Stream 之间的结合也得到了改进。现在可以通过 Optional 的新方法 `stram` 将一个 Optional 对象转换为一个(可能是空的) Stream 对象:

Stream<Integer> s = Optional.of(1).stream();

在组合复杂的 Stream 管道时,将 Optional 转换为 Stream 非常有用。

7. 私有接口方法

Java 8 为我们带来了接口的默认方法。 接口现在也可以包含行为,而不仅仅是方法签名。 但是,如果在接口上有几个默认方法,代码几乎相同,会发生什么情况? 通常,您将重构这些方法,调用一个可复用的私有方法。 但默认方法不能是私有的。 将复用代码创建为一个默认方法不是一个解决方案,因为该辅助方法会成为公共API的一部分。 使用 Java 9,您可以向接口添加私有辅助方法来解决此问题:

public interface MyInterface { void normalInterfaceMethod(); default void interfaceMethodWithDefault() { init(); } default void anotherDefaultMethod() { init(); } // This method is not part of the public API exposed by MyInterface

如果您使用默认方法开发 API ,那么私有接口方法可能有助于构建其实现。

8. HTTP/2

Java 9 中有新的方式来处理 HTTP 调用。这个迟到的特性用于代替老旧的 `HttpURLConnection` API,并提供对 WebSocket 和 HTTP/2 的支持。注意:新的 HttpClient API 在 Java 9 中以所谓的孵化器模块 交付。也就是说,这套 API 不能保证 100% 完成。不过你可以在 Java 9 中开始使用这套 API:

HttpClient client = HttpClient.newHttpClient();

除了这个简单的请求/响应模型之外,HttpClient 还提供了新的 API 来处理 HTTP/2 的特性,比如流和服务端推送。


9. 多版本兼容 JAR

我们最后要来着重介绍的这个特性对于库的维护者而言是个特别好的消息。当一个新版本的 Java 出现的时候,你的库用户要花费数年时间才会切换到这个新的版本。这就意味着库得去向后兼容你想要支持的最老的 Java 版本 (许多情况下就是 Java 6 或者 7)。这实际上意味着未来的很长一段时间,你都不能在库中运用 Java 9 所提供的新特性。幸运的是,多版本兼容 JAR 功能能让你创建仅在特定版本的 Java 环境中运行库程序时选择使用的 class 版本:

1
2
3
4
5
6
7
8
9
multirelease.jar
├── META-INF
│   └── versions
│       └── 9
│           └── multirelease
│               └── Helper.class
├── multirelease
    ├── Helper.class
    └── Main.class

在上述场景中, multirelease.jar 可以在 Java 9 中使用, 不过 Helper 这个类使用的不是顶层的 multirelease.Helper 这个 class, 而是处在“META-INF/versions/9”下面的这个。这是特别为 Java 9 准备的 class 版本,可以运用 Java 9 所提供的特性和库。同时,在早期的 Java 诸版本中使用这个 JAR 也是能运行的,因为较老版本的 Java 只会看到顶层的这个 Helper 类。


猜你喜欢

转载自blog.csdn.net/qq_33204709/article/details/78948650