Java5-Java8新特性,这回全了

Java5:

泛型 Generics
引用泛型之后,允许指定集合里元素的类型,免去了强制类型转换,并且能在编译时刻进行类型检查的好处。Parameterized Type作为参数和返回值,Generic是vararg、annotation、enumeration、collection的基石。

   类型安全:抛弃ListMap,使用List<T>Map<K,V>给它们添加元素或者使用Iterator<T>遍历时,编译期就可以给你检查出类型错误
   方法参数和返回值加上了Type,抛弃ListMap,使用List<T>Map<K,V>
   不需要类型转换List<String> list=new ArrayList<String>();String str=list.get(i);
   类型通配符“?”:假设一个打印List<T>中元素的方法printList,我们希望任何类型T的List<T>都可以被打印: 
public void printList(List<?> list,PrintStream out)throws IOException{
for(Iterator<?> i=list.iterator();i.hasNext();){
System.out.println(i.next.toString());
}
}
如果通配符?让我们的参数类型过于广泛,我们可以把List<?>、Iterator<?> 修改为:
List<? Extends Number>、Iterator<? Extends Number>限制一下它。

2、枚举类型 Enumeration
3、自动装箱拆箱
(自动类型包装和解包)autoboxing & unboxing: (int ——Integer)。
4、可变参数
varargs参数类型相同时,把重载函数合并到一起了。
如:public void test(Object … objs){
for(Object obj:objs){
System.out.println(obj);
}
}
5、Annotations
它是java中的metadata
Tiger中预定义的三种标准annotation
a 、Override
指出某个method覆盖了superclass 的method当你要覆盖的方法名拼写错时编译不通过
b、Deprecated
指出某个method或element类型的使用是被阻止的,子类将不能覆盖该方法
c、SupressWarnings
关闭class、method、field、variable 初始化的编译期警告,比如:List没有使用 Generic,则@SuppressWarnings(“unchecked”)去掉编译期警告。
自定义annotation
public @interface Marked{}
meta-annotation
或者说annotation的annotation,四种标准的meta-annotation全部定义在java.lang.annotaion包中:
a, Target:指定所定义的annotation可以用在哪些程序单元上,如果Target没有指定,则表示该annotation可以使用在任意程序单元上

@Target({ElementType.ANNOTATION_TYPE,  
  ElementType.CONSTRUCTOR,  
  ElementType.FIELD,  
  ElementType.LOCAL_VARIABLE,  
  ElementType.METHOD,  
  ElementType.PACKAGE,  
  ElementType.PARAMETER,  
  ElementType.TYPE})  
  public @interface TODO {}  

b, Retention:指出Java编译期如何对待annotation,annotation可以被编译期丢掉,或者保留在编译过的class文件中,在annotation被保留时,它也指定是否会在JVM加载class时读取该annotation

 @Retention(RetentionPolicy.SOURCE)  // Annotation会被编译期丢弃  
public @interface TODO1 {}  
@Retention(RetentionPolicy.CLASS)   // Annotation保留在class文件中,但会被JVM忽略  
public @interface TODO2 {}  
@Retention(RetentionPolicy.RUNTIME) // Annotation保留在class文件中且会被JVM读取  
public @interface TODO3 {}  

c, Documented
指出被定义的annotation被视为所熟悉的程序单元的公开API之一,被@Documented标注的annotation会在javadoc中显示,这在annotation对它标注的元素被客户端使用时有影响时起作用
d, Inherited
该meta-annotation应用于目标为class的annotation类型上,被此annotattion标注的class会自动继承父类的annotation
Annotation的反射
我们发现java.lang.Class有许多与Annotation的反射相关的方法,如getAnnotations、isAnnotationpresent,我们可以利用Annotation反射来做许多事情,比如自定义Annotation来做Model对象验证

@Retention(RetentionPolicy.RUNTIME)  
@Target({ ElementType.FIELD, ElementType.METHOD })  
public @interface RejectEmpty {  
        /** hint title used in error message */  
       String value() default "";  
}  
@Retention(RetentionPolicy.RUNTIME)  
@Target( { ElementType.FIELD, ElementType.METHOD })  
public @interface AcceptInt {  
int min() default Integer.MIN_VALUE;  
int max() default Integer.MAX_VALUE;  
String hint() default "";  
 }  

使用@RejectEmpty和@AcceptInt标注我们的Model的field,然后利用反射来做Model验证
6、foreach语句
(for(int n:numbers))
7、静态导入
(import static )
8、新的格式化方法
(java.util.Formatter)
formatter.format(“Remaining account balance: $%.2f”, balance);
9、新的线程模型和并发库Thread Framework
HashMap的替代者ConcurrentHashMap和ArrayList的替代者CopyOnWriteArrayList,在大并发量读取时采用java.util.concurrent包里的一些类会让大家满意BlockingQueue、Callable、Executor、Semaphore…

Java6

1、引入了一个支持脚本引擎的新框架
2、UI的增强
3、对WebService支持的增强(JAX-WS2.0和JAXB2.0)
4、一系列新的安全相关的增强
5、JDBC4.0
6、Compiler API
7、通用的Annotations支持

Servlet 3.0 新特性概述

Servlet 3.0 作为 Java EE 6 规范体系中一员,随着 Java EE 6 规范一起发布。该版本在前一版本(Servlet 2.5)的基础上提供了若干新特性用于简化 Web 应用的开发和部署。其中有几项特性的引入让开发者感到非常兴奋,同时也获得了 Java 社区的一片赞誉之声:
1. 异步处理支持:有了该特性,Servlet 线程不再需要一直阻塞,直到业务处理完毕才能再输出响应,最后才结束该 Servlet 线程。在接收到请求之后,Servlet 线程可以将耗时的操作委派给另一个线程来完成,自己在不生成响应的情况下返回至容器。针对业务处理较耗时的情况,这将大大减少服务器资源的占用,并且提高并发处理速度。
2. 新增的注解支持:该版本新增了若干注解,用于简化 Servlet、过滤器(Filter)和监听器(Listener)的声明,这使得 web.xml 部署描述文件从该版本开始不再是必选的了。
3. 可插性支持:熟悉 Struts2 的开发者一定会对其通过插件的方式与包括 Spring 在内的各种常用框架的整合特性记忆犹新。将相应的插件封装成 JAR 包并放在类路径下,Struts2 运行时便能自动加载这些插件。现在 Servlet 3.0 提供了类似的特性,开发者可以通过插件的方式很方便的扩充已有 Web 应用的功能,而不需要修改原有的应用。
异步处理支持
Servlet 3.0 之前,一个普通 Servlet 的主要工作流程大致如下:首先,Servlet 接收到请求之后,可能需要对请求携带的数据进行一些预处理;接着,调用业务接口的某些方法,以完成业务处理;最后,根据处理的结果提交响应,Servlet 线程结束。其中第二步的业务处理通常是最耗时的,这主要体现在数据库操作,以及其它的跨网络调用等,在此过程中,Servlet 线程一直处于阻塞状态,直到业务方法执行完毕。在处理业务的过程中,Servlet 资源一直被占用而得不到释放,对于并发较大的应用,这有可能造成性能的瓶颈。对此,在以前通常是采用私有解决方案来提前结束 Servlet 线程,并及时释放资源。
Servlet 3.0 针对这个问题做了开创性的工作,现在通过使用 Servlet 3.0 的异步处理支持,之前的 Servlet 处理流程可以调整为如下的过程:首先,Servlet 接收到请求之后,可能首先需要对请求携带的数据进行一些预处理;接着,Servlet 线程将请求转交给一个异步线程来执行业务处理,线程本身返回至容器,此时 Servlet 还没有生成响应数据,异步线程处理完业务以后,可以直接生成响应数据(异步线程拥有 ServletRequest 和 ServletResponse 对象的引用),或者将请求继续转发给其它 Servlet。如此一来, Servlet 线程不再是一直处于阻塞状态以等待业务逻辑的处理,而是启动异步线程之后可以立即返回。
异步处理特性可以应用于 Servlet 和过滤器两种组件,由于异步处理的工作模式和普通工作模式在实现上有着本质的区别,因此默认情况下,Servlet 和过滤器并没有开启异步处理特性,如果希望使用该特性,则必须按照如下的方式启用:
1. 对于使用传统的部署描述文件 (web.xml) 配置 Servlet 和过滤器的情况,Servlet 3.0 为 和 标签增加了 子标签,该标签的默认取值为 false,要启用异步处理支持,则将其设为 true 即可。以 Servlet 为例,其配置方式如下所示:

<servlet> 
    <servlet-name>DemoServlet</servlet-name> 
    <servlet-class>footmark.servlet.Demo Servlet</servlet-class> 
    <async-supported>true</async-supported> 
</servlet>
  1. 对于使用 Servlet 3.0 提供的 @WebServlet 和 @WebFilter 进行 Servlet 或过滤器配置的情况,这两个注解都提供了 asyncSupported 属性,默认该属性的取值为 false,要启用异步处理支持,只需将该属性设置为 true 即可。以 @WebFilter 为例,其配置方式如下所示:
    @WebFilter(urlPatterns = “/demo”,asyncSupported = true)
    public class DemoFilter implements Filter{…}
    一个简单的模拟异步处理的 Servlet 示例如下:
@WebServlet(urlPatterns = "/demo", asyncSupported = true)
public class AsyncDemoServlet extends HttpServlet {
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
    throws IOException, ServletException {
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter out = resp.getWriter();
        out.println("进入Servlet的时间:" + new Date() + ".");
        out.flush();

        //在子线程中执行业务调用,并由其负责输出响应,主线程退出
        AsyncContext ctx = req.startAsync();
        new Thread(new Executor(ctx)).start();

        out.println("结束Servlet的时间:" + new Date() + ".");
        out.flush();
    }
}

public class Executor implements Runnable {
    private AsyncContext ctx = null;
    public Executor(AsyncContext ctx){
        this.ctx = ctx;
    }

    public void run(){
        try {
            //等待十秒钟,以模拟业务方法的执行
            Thread.sleep(10000);
            PrintWriter out = ctx.getResponse().getWriter();
            out.println("业务处理完毕的时间:" + new Date() + ".");
            out.flush();
            ctx.complete();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

除此之外,Servlet 3.0 还为异步处理提供了一个监听器,使用 AsyncListener 接口表示。它可以监控如下四种事件:
1. 异步线程开始时,调用 AsyncListener 的 onStartAsync(AsyncEvent event) 方法;
2. 异步线程出错时,调用 AsyncListener 的 onError(AsyncEvent event) 方法;
3. 异步线程执行超时,则调用 AsyncListener 的 onTimeout(AsyncEvent event) 方法;
4. 异步执行完毕时,调用 AsyncListener 的 onComplete(AsyncEvent event) 方法;
要注册一个 AsyncListener,只需将准备好的 AsyncListener 对象传递给 AsyncContext 对象的 addListener() 方法即可,如下所示:

AsyncContext ctx = req.startAsync(); 
ctx.addListener(new AsyncListener() { 
    public void onComplete(AsyncEvent asyncEvent) throws IOException { 
        // 做一些清理工作或者其他
    } 
    ... 
});

新增的注解支持
Servlet 3.0 的部署描述文件 web.xml 的顶层标签 有一个 metadata-complete 属性,该属性指定当前的部署描述文件是否是完全的。如果设置为 true,则容器在部署时将只依赖部署描述文件,忽略所有的注解(同时也会跳过 web-fragment.xml 的扫描,亦即禁用可插性支持,具体请看后文关于 可插性支持的讲解);如果不配置该属性,或者将其设置为 false,则表示启用注解支持(和可插性支持)。
@WebServlet
@WebServlet 用于将一个类声明为 Servlet,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为 Servlet。该注解具有下表给出的一些常用属性(以下所有属性均为可选属性,但是 vlaue 或者 urlPatterns 通常是必需的,且二者不能共存,如果同时指定,通常是忽略 value 的取值):
表 1. @WebServlet 主要属性列表
属性名 类型 描述
name String 指定 Servlet 的 name 属性,等价于 。如果没有显式指定,则该 Servlet 的取值即为类的全限定名。
value String[] 该属性等价于 urlPatterns 属性。两个属性不能同时使用。
urlPatterns String[] 指定一组 Servlet 的 URL 匹配模式。等价于 标签。
loadOnStartup int 指定 Servlet 的加载顺序,等价于 标签。
initParams WebInitParam[] 指定一组 Servlet 初始化参数,等价于 标签。
asyncSupported boolean 声明 Servlet 是否支持异步操作模式,等价于 标签。
description String 该 Servlet 的描述信息,等价于 标签。
displayName String 该 Servlet 的显示名,通常配合工具使用,等价于 标签。
下面是一个简单的示例:

@WebServlet(urlPatterns = {"/simple"}, asyncSupported = true, 
loadOnStartup = -1, name = "SimpleServlet", displayName = "ss", 
initParams = {@WebInitParam(name = "username", value = "tom")} 
) 
public class SimpleServlet extends HttpServlet{ … }

如此配置之后,就可以不必在 web.xml 中配置相应的 和 元素了,容器会在部署时根据指定的属性将该类发布为 Servlet。它的等价的 web.xml 配置形式如下:

<servlet>
    <display-name>ss</display-name>
    <servlet-name>SimpleServlet</servlet-name>
    <servlet-class>footmark.servlet.SimpleServlet</servlet-class>
    <load-on-startup>-1</load-on-startup>
    <async-supported>true</async-supported>
    <init-param>
        <param-name>username</param-name>
        <param-value>tom</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>SimpleServlet</servlet-name>
    <url-pattern>/simple</url-pattern>
</servlet-mapping>

@WebInitParam
该注解通常不单独使用,而是配合 @WebServlet 或者 @WebFilter 使用。它的作用是为 Servlet 或者过滤器指定初始化参数,这等价于 web.xml 中 和 的 子标签。@WebInitParam 具有下表给出的一些常用属性:
表 2. @WebInitParam 的常用属性
属性名 类型 是否可选 描述
name String 否 指定参数的名字,等价于 。
value String 否 指定参数的值,等价于 。
description String 是 关于参数的描述,等价于 。
@WebFilter
@WebFilter 用于将一个类声明为过滤器,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为过滤器。该注解具有下表给出的一些常用属性 ( 以下所有属性均为可选属性,但是 value、urlPatterns、servletNames 三者必需至少包含一个,且 value 和 urlPatterns 不能共存,如果同时指定,通常忽略 value 的取值 ):
表 3. @WebFilter 的常用属性
属性名 类型 描述
filterName String 指定过滤器的 name 属性,等价于
value String[] 该属性等价于 urlPatterns 属性。但是两者不应该同时使用。
urlPatterns String[] 指定一组过滤器的 URL 匹配模式。等价于 标签。
servletNames String[] 指定过滤器将应用于哪些 Servlet。取值是 @WebServlet 中的 name 属性的取值,或者是 web.xml 中 的取值。
dispatcherTypes DispatcherType 指定过滤器的转发模式。具体取值包括:
ASYNC、ERROR、FORWARD、INCLUDE、REQUEST。
initParams WebInitParam[] 指定一组过滤器初始化参数,等价于 标签。
asyncSupported boolean 声明过滤器是否支持异步操作模式,等价于 标签。
description String 该过滤器的描述信息,等价于 标签。
displayName String 该过滤器的显示名,通常配合工具使用,等价于 标签。
下面是一个简单的示例:
@WebFilter(servletNames = {“SimpleServlet”},filterName=”SimpleFilter”)
public class LessThanSixFilter implements Filter{…}
如此配置之后,就可以不必在 web.xml 中配置相应的 和 元素了,容器会在部署时根据指定的属性将该类发布为过滤器。它等价的 web.xml 中的配置形式为:

<filter> 
    <filter-name>SimpleFilter</filter-name> 
    <filter-class>xxx</filter-class> 
</filter> 
<filter-mapping> 
    <filter-name>SimpleFilter</filter-name> 
    <servlet-name>SimpleServlet</servlet-name> 
</filter-mapping>

@WebListener
该注解用于将类声明为监听器,被 @WebListener 标注的类必须实现以下至少一个接口:

•   ServletContextListener
•   ServletContextAttributeListener
•   ServletRequestListener
•   ServletRequestAttributeListener
•   HttpSessionListener
•   HttpSessionAttributeListener

该注解使用非常简单,其属性如下:
表 4. @WebListener 的常用属性
属性名 类型 是否可选 描述
value String 是 该监听器的描述信息。
一个简单示例如下:

@WebListener("This is only a demo listener") 
public class SimpleListener implements ServletContextListener{...}

如此,则不需要在 web.xml 中配置 标签了。它等价的 web.xml 中的配置形式如下:

<listener> 
    <listener-class>footmark.servlet.SimpleListener</listener-class> 
</listener>

@MultipartConfig
该注解主要是为了辅助 Servlet 3.0 中 HttpServletRequest 提供的对上传文件的支持。该注解标注在 Servlet 上面,以表示该 Servlet 希望处理的请求的 MIME 类型是 multipart/form-data。另外,它还提供了若干属性用于简化对上传文件的处理。具体如下:
表 5. @MultipartConfig 的常用属性
属性名 类型 是否可选 描述
fileSizeThreshold int 是 当数据量大于该值时,内容将被写入文件。
location String 是 存放生成的文件地址。
maxFileSize long 是 允许上传的文件最大值。默认值为 -1,表示没有限制。
maxRequestSize long 是 针对该 multipart/form-data 请求的最大数量,默认值为 -1,表示没有限制。
可插性支持
如果说 3.0 版本新增的注解支持是为了简化 Servlet/ 过滤器 / 监听器的声明,从而使得 web.xml 变为可选配置, 那么新增的可插性 (pluggability) 支持则将 Servlet 配置的灵活性提升到了新的高度。熟悉 Struts2 的开发者都知道,Struts2 通过插件的形式提供了对包括 Spring 在内的各种开发框架的支持,开发者甚至可以自己为 Struts2 开发插件,而 Servlet 的可插性支持正是基于这样的理念而产生的。使用该特性,现在我们可以在不修改已有 Web 应用的前提下,只需将按照一定格式打成的 JAR 包放到 WEB-INF/lib 目录下,即可实现新功能的扩充,不需要额外的配置。
Servlet 3.0 引入了称之为“Web 模块部署描述符片段”的 web-fragment.xml 部署描述文件,该文件必须存放在 JAR 文件的 META-INF 目录下,该部署描述文件可以包含一切可以在 web.xml 中定义的内容。JAR 包通常放在 WEB-INF/lib 目录下,除此之外,所有该模块使用的资源,包括 class 文件、配置文件等,只需要能够被容器的类加载器链加载的路径上,比如 classes 目录等。
现在,为一个 Web 应用增加一个 Servlet 配置有如下三种方式 ( 过滤器、监听器与 Servlet 三者的配置都是等价的,故在此以 Servlet 配置为例进行讲述,过滤器和监听器具有与之非常类似的特性 ):
• 编写一个类继承自 HttpServlet,将该类放在 classes 目录下的对应包结构中,修改 web.xml,在其中增加一个 Servlet 声明。这是最原始的方式;
• 编写一个类继承自 HttpServlet,并且在该类上使用 @WebServlet 注解将该类声明为 Servlet,将该类放在 classes 目录下的对应包结构中,无需修改 web.xml 文件。
• 编写一个类继承自 HttpServlet,将该类打成 JAR 包,并且在 JAR 包的 META-INF 目录下放置一个 web-fragment.xml 文件,该文件中声明了相应的 Servlet 配置。web-fragment.xml 文件示例如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-fragment 
    xmlns=http://java.sun.com/xml/ns/javaee
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd"
    metadata-complete="true">
    <servlet>
        <servlet-name>fragment</servlet-name>
        <servlet-class>footmark.servlet.FragmentServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>fragment</servlet-name>
        <url-pattern>/fragment</url-pattern>
    </servlet-mapping>
</web-fragment>

从上面的示例可以看出,web-fragment.xml 与 web.xml 除了在头部声明的 XSD 引用不同之外,其主体配置与 web.xml 是完全一致的。
由于一个 Web 应用中可以出现多个 web-fragment.xml 声明文件,加上一个 web.xml 文件,加载顺序问题便成了不得不面对的问题。Servlet 规范的专家组在设计的时候已经考虑到了这个问题,并定义了加载顺序的规则。
web-fragment.xml 包含了两个可选的顶层标签, 和 ,如果希望为当前的文件指定明确的加载顺序,通常需要使用这两个标签, 主要用于标识当前的文件,而 则用于指定先后顺序。一个简单的示例如下:

<web-fragment...>
    <name>FragmentA</name>
    <ordering>
        <after>
            <name>FragmentB</name>
            <name>FragmentC</name>
        </after>
    <before>
        <others/>
    </before>
    </ordering>
    ...
</web-fragment>

如上所示, <name> 标签的取值通常是被其它 web-fragment.xml 文件在定义先后顺序时引用的,在当前文件中一般用不着,它起着标识当前文件的作用。
<ordering> 标签内部,我们可以定义当前 web-fragment.xml 文件与其他文件的相对位置关系,这主要通过 <ordering> 的 <after> 和 <before> 子标签来实现的。在这两个子标签内部可以通过 <name>标签来指定相对应的文件。比如:

<after> 
    <name>FragmentB</name> 
    <name>FragmentC</name> 
</after>

以上片段则表示当前文件必须在 FragmentB 和 FragmentC 之后解析。 的使用于此相同,它所表示的是当前文件必须早于<before>标签里所列出的 web-fragment.xml 文件。
除了将所比较的文件通过 <name> 在 <after> 和 <begin> 中列出之外,Servlet 还提供了一个简化的标签 <others/>。它表示除了当前文件之外的其他所有的 web-fragment.xml 文件。该标签的优先级要低于使用 <name> 明确指定的相对位置关系
ServletContext 的性能增强
除了以上的新特性之外,ServletContext 对象的功能在新版本中也得到了增强。现在,该对象支持在运行时动态部署 Servlet、过滤器、监听器,以及为 Servlet 和过滤器增加 URL 映射等。以 Servlet 为例,过滤器与监听器与之类似。ServletContext 为动态配置 Servlet 增加了如下方法:

•   ServletRegistration.Dynamic addServlet(String servletName,Class<? extends Servlet> servletClass)
•   ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet)
•   ServletRegistration.Dynamic addServlet(String servletName, String className)
•   <T extends Servlet> T createServlet(Class<T> clazz)
•   ServletRegistration getServletRegistration(String servletName)
•   Map<String,? extends ServletRegistration> getServletRegistrations()

其中前三个方法的作用是相同的,只是参数类型不同而已;通过 createServlet() 方法创建的 Servlet,通常需要做一些自定义的配置,然后使用 addServlet() 方法来将其动态注册为一个可以用于服务的 Servlet。两个 getServletRegistration() 方法主要用于动态为 Servlet 增加映射信息,这等价于在 web.xml( 抑或 web-fragment.xml) 中使用 <servlet-mapping>标签为存在的 Servlet 增加映射信息。
以上 ServletContext 新增的方法要么是在 ServletContextListener 的 contexInitialized 方法中调用,要么是在 ServletContainerInitializer 的 onStartup() 方法中调用。
ServletContainerInitializer 也是 Servlet 3.0 新增的一个接口,容器在启动时使用 JAR 服务 API(JAR Service API) 来发现 ServletContainerInitializer 的实现类,并且容器将 WEB-INF/lib 目录下 JAR 包中的类都交给该类的 onStartup() 方法处理,我们通常需要在该实现类上使用 @HandlesTypes 注解来指定希望被处理的类,过滤掉不希望给 onStartup() 处理的类。
HttpServletRequest 对文件上传的支持
此前,对于处理上传文件的操作一直是让开发者头疼的问题,因为 Servlet 本身没有对此提供直接的支持,需要使用第三方框架来实现,而且使用起来也不够简单。如今这都成为了历史,Servlet 3.0 已经提供了这个功能,而且使用也非常简单。为此,HttpServletRequest 提供了两个方法用于从请求中解析出上传的文件:

Part getPart(String name)
•   Collection<Part> getParts()

前者用于获取请求中给定 name 的文件,后者用于获取所有的文件。每一个文件用一个 javax.servlet.http.Part 对象来表示。该接口提供了处理文件的简易方法,比如 write()、delete() 等。至此,结合 HttpServletRequest 和 Part 来保存上传的文件变得非常简单,如下所示:

Part photo = request.getPart("photo"); 
photo.write("/tmp/photo.jpg"); 
// 可以将两行代码简化为 request.getPart("photo").write("/tmp/photo.jpg") 一行。

另外,开发者可以配合前面提到的 @MultipartConfig 注解来对上传操作进行一些自定义的配置,比如限制上传文件的大小,以及保存文件的路径等。其用法非常简单,故不在此赘述了。
需要注意的是,如果请求的 MIME 类型不是 multipart/form-data,则不能使用上面的两个方法,否则将抛异常。
总结
Servlet 3.0 的众多新特性使得 Servlet 开发变得更加简单,尤其是异步处理特性和可插性支持的出现,必将对现有的 MVC 框架产生深远影响。虽然我们通常不会自己去用 Servlet 编写控制层代码,但是也许在下一个版本的 Struts 中,您就能切实感受到这些新特性带来的实质性改变。

Java7新特性

一、自动资源管理
早在7.x版本之前,某些可回收资源比如:I/O链接、DB连接、TCP/UDP连接。开发人员都需要在使用后对其进行手动关闭,如果不关闭或者忘记关闭这些资源,就会长期霸占JVM内部的资源,极大程度上影响了JVM的资源分配。就像内存管理一样,开发人员梦寐以求的就是希望有一天再也无需关注繁琐的资源管理(资源创建、资源就绪、资源回收)。值得庆幸的是7.x为我们带来了一次彻头彻尾改变,我们将再也不必以手动管理我们的资源。
早在Java5.x的时候,Java API为开发人员提供了一个Closeable接口。该接口中包含一个close()方法,允许所有可回收资源的类型对其进行重写实现。7.x版本中几乎所有的资源类型都实现了Closeable接口,并重写了close()方法。也就是说所有可回收的系统资源,我们将再不必每次使用完后调用close()方法进行资源回收,这一切全部交接给自动资源管理器去做即可。
例如Reader资源类型继承Closeable接口实现资源自动管理:
public abstract class Reader implements Readable, Closeable
当然如果你需要在程序中使用自动资源管理,还需要使用API提供的新语法支持,这类语法包含在try语句块内部。看到这里你可能不禁感叹,try也能支持表达式了,是的7.x确实允许try使用表达式的语法方式实现自动资源管理,但仅限于资源类型。

try(BufferedReader reader = new BufferedReader(new FileReader("filepath here"));)  {  
}  
catch(Exception e)  {  
    e.printStackTrace();  
}  

Java7中在try语句中申请资源,实现资源的自动释放(资源类必须实现java.lang.AutoCloseable接口,一般的文件、数据库连接等均已实现该接口,close方法将被自动调用)。

1.   public void read(String filename) throws IOException {  
2.       try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {  
3.           StringBuilder builder = new StringBuilder();  
4.  String line = null;  
5.  while((line=reader.readLine())!=null){  
6.      builder.append(line);  
7.      builder.append(String.format("%n"));  
8.  }  
9.  return builder.toString();  
10.      }   
11.  }  

try子句中可以管理多个资源:

1.   public void copyFile(String fromPath, String toPath) throws IOException {  
2.       try ( InputStream input = new FileInputStream(fromPath);  
3.      OutputStream output = new FileOutputStream(toPath) ) {  
4.           byte[] buffer = new byte[8192];  
5.  int len = -1;  
6.  while( (len=input.read(buffer))!=-1 ) {  
7.      output.write(buffer, 0, len);  
8.  }  
9.       }   
10.  }  

二、“<>”类型推断运算符
Java5.x新增了许多新的功能,在这些新引入的功能中,泛型最为重要。泛型是一种新的语法元素,泛型的出现导致整个Java API都发生了变化(比如:Java集合框架就使用了泛型语法)。
在泛型没有出现之前,我们都是将Object类作为通用的任意数据类型使用。因为在Java语言中,Object类是所有类的超类。但是使用Object类作为任意数据类型并不是安全的,因为在很多时候我们需要将Object类型向下转换,在这些转换过程中偶尔也可能出现不匹配的类型转换错误。泛型的出现则很好的解决了Object类型所存在的安全性问题,且泛型还扩展了代码的重用性。
泛型的核心概念就是参数化类型,所谓参数化类型指的就是开发人员可以在外部指定的数据类型来创建泛型类、泛型接口和泛型方法。

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

通过上述程序示例我们可以看出,笔者定义了一个泛型类型为String的List集合。这样一来List集合的泛型参数将会被定义为String类型。但是你有没有想过,使用里氏替换原则或者实例化泛型类型时,其实现可以简化泛型类型声明吗?答案是肯定的,在Java7.x中,允许使用运算符“<>”来做类型推断。也就是说你只需要在声明时标注泛型类型,实现时无需重复标注。
使用“<>”类型推断运算符简化泛型语法:

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

三、变长参数方法的优化
Java7之前版本中的变长参数方法:

public int sum(int... args) {  
    int result = 0;  
    for (int value : args) {  
        result += value;  
    }  
    return result;  
}  

当参数为不可具体化的类型时,如List,编译器将产生警告,需要使用@SuppressWarnings(“unchecked”)注解声明;Java7中使用@SafeVarargs注解抑制编译器警告。

@SafeVarargs  
public static <T> T useVarargs(T... args) {  
    return args.length > 0 ? args[0] : null;  
}  

三、字面值下划线支持
不知道大家有没有过同笔者一样的烦恼,早在Java7.x版本之前,咱们在定义int或者long类型等变量的字面值时,往往会因为其定义的值过长,从而严重影响后续的可读性。如果你也是这么觉得,那么你可以考虑使用Java7.x为字面值操作提供的可读性优化。那便是允许你直接的字面值中使用符号“”进行切分,这样一来不仅可以提升可读性,还能够清晰的分辨出字面值的长度。当然程序运行时自然会将“”符号进行提取再做运算。
int money = 100_000_000;
四、switch字面值支持
Java一共为开发人员提供了2种多路分支语句,一种是大家常用的if-else,另一种则是switch语句。早在Java7.x版本之前,switch语句表达式值只能定义byte、short、int和char等4种类型,且该语句表达式值只能匹配一个,故不能重复。但是Java7.x的到来允许switch定义另一种全新的表达式值,那就是String类型。

public String generate(String name, String gender) {  
   String title = "";  
   switch (gender) {  
       case "男":  
           title = name + " 先生";  
           break;  
       case "女":  
           title = name + " 女士";  
           break;  
       default:  
           title = name;  
   }  
   return title;  

编译器在编译时先做处理: case只有一种情况,直接转成if; 如果只有一个case和default,则直接转换为if…else…; 有多个case,先将String转换为hashCode,然后对应的进行处理,JavaCode在底层兼容Java7以前版本。
五、声明二进制字面值
Java与C语言、C++语言直接相关。Java语言继承了C语言的语法结构,而OMT(Object Modeling Technique,对象模型)则是直接从C++语言改编而来的。所以早在Java7.x版本之前,开发人员只能够定义十进制、八进制、十六进制等字面值。但是现在你完全可以使用“0b”字符为前缀定义二进制字面值。
Java7前支持十进制(123)、八进制(0123)、十六进制(0X12AB)
Java7增加二进制表示(0B11110001、0b11110001)
int test = 0b010101;
当然这里笔者需要提示你的是,虽然咱们可以直接在程序中定义二进制字面值。但是在程序运算时,仍然会将其转换成十进制展开运算和输出。
六、catch表达式调整
谈到catch语句的时候,不得不提到try语句,因为它们彼此之间存在相互依赖、相互关联的关系。在Java程序中捕获一个异常采用的是try和catch语句,try语句里面所包含的代码块都是需要进行异常监测的,而catch语句里面所包含的代码块,则是告诉程序当异常发生的时候所需要执行的异常处理。
谈到捕获异常,在Java7.x之前有2种方式。第一种是采用定义多个catch代码块,另外一种则是直接使用Exception(可恢复性异常超类)进行捕获。但是现在,如果你觉得不想笼统的将所有异常定义为Exception进行捕获,或者纠结于反复定义catch代码块,那么你完全可以采用Java7.x的catch表达式调整。Java7.x允许你在catch表达式内部使用“|”运算符匹配多个异常类型,当触发异常时,异常类型将自动进行类型匹配操作。
try { } catch (NumberFormatException | RuntimeException e) { }
①Throwable类增加addSuppressed方法和getSuppressed方法,支持原始异常中加入被抑制的异常。
异常抑制:在try和finally中同时抛出异常时,finally中抛出的异常会在异常栈中向上传递,而try中产生的原始异常会消失。
在Java7之前的版本,可以将原始异常保存,在finally中产生异常时抛出原始异常:

public void read(String filename) throws BaseException {  
    FileInputStream input = null;  
    IOException readException = null;  
    try {  
        input = new FileInputStream(filename);  
    } catch (IOException ex) {  
        readException = ex;   //保存原始异常  
    } finally {  
        if (input != null) {  
            try {  
                input.close();  
            } catch (IOException ex) {  
                if (readException == null) {  
                    readException = ex;  
                }  
            }  
        }  
        if (readException != null) {  
            throw new BaseException(readException);  
        }  
    }  
}  

在Java7中的版本,可以使用addSuppressed方法记录被抑制的异常:

public void read(String filename) throws IOException {  
    FileInputStream input = null;  
    IOException readException = null;  
    try {  
        input = new FileInputStream(filename);  
    } catch (IOException ex) {  
        readException = ex;  
    } finally {  
        if (input != null) {  
            try {  
                input.close();  
            } catch (IOException ex) {  
                if (readException != null) {    //此处的区别  
                    readException.addSuppressed(ex);  
                }  
                else {  
                    readException = ex;  
                }  
            }  
        }  
        if (readException != null) {  
            throw readException;  
        }  
    }  
}  

七、文件系统改变
既然本章节咱们已经谈到了Java的文件系统(FileSystem),那么必然同样也会关联到I/O技术。其实所谓I/O(Input/Output)指的就是数据输入/输出的过程,我们称之为流(数据通信通道)这个概念。比如当Java应用程序需要读取目标数据源的数据时,则开启输入流。需要写入时,则开启输出流。数据源允许是本地磁盘、内存或者是网络中的数据。
向目标数据源读取数据:

向目标数据源写入数据:
Java的文件系统主要由java.io及java.nio两个包内的组件构成。早在Java7.x之前,文件的操作一向都比较棘手。当然笔者这里提出的棘手,更多的是指向Java API对文件的管理的不便。比如咱们需要编写一个程序,这个程序的功能仅仅只是拷贝文件后进行粘贴。但就是连这样简单的程序逻辑实现,开发人员动则都需要编写几十行有效代码。

BufferedInputStream reader = new BufferedInputStream(   new FileInputStream(COPYFILEPATH));  
byte[] content = new byte[reader.available()];  
reader.read(content);  

BufferedOutputStream write = new BufferedOutputStream(  new FileOutputStream(PASTEFILEPATH));  
write.write(content);  

通过上述程序示例我们可以看出,仅仅只是编写一个简单的文件复制粘贴逻辑,我们的代码量都大得惊人。如果你也认同上述程序的繁琐,那么你完全有必要体验下Java7.x对文件系统的一次全新改变。
Java7.x推出了全新的NIO.2 API以此改变针对文件管理的不便,使得在java.nio.file包下使用Path、Paths、Files、WatchService、FileSystem等常用类型可以很好的简化开发人员对文件管理的编码工作。
咱们就先从Path接口开始进行讲解吧。Path接口的某些功能其实可以和java.io包下的File类型等价,当然这些功能仅限于只读操作。在实际开发过程中,开发人员可以联用Path接口和Paths类型,从而获取文件的一系列上下文信息。
Path接口常用方法如下:
方法名称 方法返回类型 方法描述

getNameCount()  int 获取当前文件节点数
getFileName()   java.nio.file.Path  获取当前文件名称
getRoot()   java.nio.file.Path  获取当前文件根目录
getParent() java.nio.file.Path  获取当前文件上级关联目录
联用Path接口和Paths类型获取文件信息:
@Test  
public void testFile() {  
    Path path = Paths.get("路径:/文件");  
    System.out.println("文件节点数:" + path.getNameCount());     
    System.out.println("文件名称:" + path.getFileName());     
    System.out.println("文件根目录:" + path.getRoot());     
    System.out.println("文件上级关联目录:" + path.getParent());     
}  

通过上述程序示例我们可以看出,联用Path接口和Paths类型可以很方便的访问到目标文件的上下文信息。当然这些操作全都是只读的,如果开发人员想对文件进行其它非只读操作,比如文件的创建、修改、删除等操作,则可以使用Files类型进行操作。
Files类型常用方法如下:

方法名称    方法返回类型  方法描述
createFile()    java.nio.file.Path  在指定的目标目录创建新文件
delete()    void    删除指定目标路径的文件或文件夹
copy()  java.nio.file.Path  将指定目标路径的文件拷贝到另一个文件中
move()  java.nio.file.Path  将指定目标路径的文件转移到其他路径下,并删除源文件

使用Files类型复制、粘贴文件示例:

Files.copy(Paths.get("路径:/源文件"), Paths.get("路径:/新文件"));  

通过上述程序示例我们可以看出,使用Files类型来管理文件,相对于传统的I/O方式来说更加方便和简单。因为具体的操作实现将全部移交给NIO.2 API,开发人员则无需关注。
Java7.x还为开发人员提供了一套全新的文件系统功能,那就是文件监测。在此或许有很多朋友并不知晓文件监测有何意义及目,那么请大家回想下调试成热发布功能后的Web容器。当项目迭代后并重新部署时,开发人员无需对其进行手动重启,因为Web容器一旦监测到文件发生改变后,便会自动去适应这些“变化”并重新进行内部装载。Web容器的热发布功能同样也是基于文件监测功能,所以不得不承认,文件监测功能的出现对于Java文件系统来说是具有重大意义的。

如果在程序中需要使用Java7.x的文件监测功能,那么我们务必需要了解java.nio.file包下的WatchService接口。WatchService接口不仅作为监测服务,还管理着具体的监控细节。
我们可以通过使用java.nio.file包下的FileSystems类型,并调用FileSystems类型的newWatchService()方法,从而获取到WatchService接口的对象实例。
获取WatchService接口实例:
WatchService watchService = FileSystems.getDefault() .newWatchService();
文件监测是基于事件驱动的,事件触发是作为监测的先决条件。开发人员可以使用java.nio.file包下的StandardWatchEventKinds类型提供的3种字面常量来定义监测事件类型,值得注意的是监测事件需要和WatchService实例一起进行注册。
StandardWatchEventKinds类型提供的监测事件:
1、ENTRY_CREATE:文件或文件夹新建事件;
2、ENTRY_DELETE:文件或文件夹删除事件;
3、ENTRY_MODIFY:文件或文件夹粘贴事件;

使用WatchService类型实现文件监控完整示例:

@Test  
public void testWatch() {  
    Path path = Paths.get("C:/");  
    try {  
        WatchService watchService = FileSystems.getDefault().newWatchService();  
        path.register(watchService, ENTRY_CREATE, ENTRY_DELETE,   ENTRY_MODIFY);  
        while (true) {  
            WatchKey watchKey = watchService.take();  
            for (WatchEvent<?> event : watchKey.pollEvents())  
                System.out.println(event.context().toString() + " 事件类型:"  + event.kind());  
            if (!watchKey.reset())  
                return;  
        }  
    } catch (Exception e) {  
        e.printStackTrace();  
    }  
}  

通过上述程序示例我们可以看出,使用WatchService接口进行文件监控非常简单和方便。首先我们需要定义好目标监控路径,然后调用FileSystems类型的newWatchService()方法创建WatchService对象。接下来我们还需使用Path接口的register()方法注册WatchService实例及监控事件。当这些基础作业层全部准备好后,我们再编写外围实时监测循环。最后迭代WatchKey来获取所有触发监控事件的文件即可。
提示:
如果在项目中使用上述程序示例,笔者建议你最好将这段监控代码进行异步化。因为死循环会占用主线程,后续代码将永远得不到执行机会。

八、探讨Java I/O模型
在基于分布式的编程环境中,系统性能的瓶颈往往由I/O读写性决定。这是不可否认的事实,也是众多开发人员首要考虑的优化问题。如果在Windows环境下我们使用阻塞I/O模型来编写分布式应用,其维护成本的往往超出你的想象。因为客户端的链接数量直接决定了服务器内存开辟的线程数量 * 2(包含:接受线程、输出线程),并且这些线程是无法采取线程池优化的,因为线程的执行之间大于其创建和销毁时间。长时间的大量并发线程挂起,不仅CPU要做实时任务切换,其整体物理资源都将一步步被蚕食,直至最后程序崩溃。在早期的网络编程中,采取阻塞I/O模型来编写分布式应用,唯一能做性能优化只有采取传统的硬件式堆机。在付出高昂的硬件成本开销时,其项目的维护性也令开发人员头痛。而且在实际的开发过程中,大部分开发人员会选择将项目部署在Linux下运行。跟Windows内核结构不同的是,Linux环境下是没有真正意义上的线程概念。其所谓的线程都是采用进程模拟的方式,也就是伪线程。笔者希望大家能够明白,对于并发要求极高的分布式应用,一旦采用阻塞IO模型编写就等于自寻死路。
Java的I/O模型由同步I/O和异步I/O构成。同步I/O模型包含:阻塞I/O和非阻塞I/O,而在Windows环境下只要调用了IOCP的I/O模型,就是真正意义上的异步I/O。
IOCP(Input/Outut Completion Port,输入/输出完成端口)简单来说是一种系统级的高性能异步I/O模型。应用程序中所有的I/O操作将全部委托给操作系统线程去执行,直至最后通知并返回结果。Java7.x对IOCP进行了深度封装,这使得开发人员可以使用IOCP API编写高效的分布式应用。当然IOCP仅限于使用在Windows平台,因而无法在Linux平台上使用它(Linux平台上可以通过Epoll模拟IOCP实现)。
提示:
有过网络编程经验的开发人员都应该明白,在Windows平台下性能最好的I/O模型是IOCP,Linux平台下则是EPOLL。但是EPOLL并不算真正意义上的异步I/O,EPOLL只是在尽可能的模拟IOCP而已。因为按照Unix网络编程的划分,多路复用I/O仍然属于同步I/O模型,也就是说EPOLL其实是属于多路复用I/O。
简单来说异步I/O的特征必须满足如下2点:
1、I/O请求与I/O操作不会阻塞;
2、并非程序自身完成I/O操作,由操作系统线程处理实际的I/O操作,直至最后通知并返回结果;
早在Java4.x的时候,NIO(Java New Input/Output,Java新输入/输出)的出现,使得开发人员可以彻底从阻塞I/O的噩梦中挣脱出来。但编写NIO的成本较大,学习难度也比较高,使得诸多开发人员望而却步(目前比较成熟的NIO Frameworks有:Mina、Netty)。但理解非阻塞I/O的原理还是非常有必要,先来观察下述采用阻塞I/O模式编写的分布式应用示例:

@Test  
public void testServer() {  
    try {  
        ServerSocket server = new ServerSocket(8888);  
        Socket clist = server.accept();  
        BufferedReader reader = new BufferedReader(new InputStreamReader(  clist.getInputStream()));  
        System.out.println(reader.readLine());  
    } catch (Exception e) {  
        e.printStackTrace();  
    }  
}  

I/O的工作内容我们可以根据其性质划分为2部分:I/O请求和I/O操作。上述程序示例我们采用的是阻塞I/O模型,可以很明确的发现当客户端成功握手服务端后,如果服务端并没有收到客户端的I/O请求,服务端会在reader.readLine()方法处阻塞。直到成功接收到I/O请求后,服务端才会开始执行实际的I/O操作。运用阻塞I/O模式进行分布式编程,为了保证服务端与客户端集合的成功会话,我们不得不为每一条客户端连接都开辟独立的线程执行I/O操作。当然在实际的开发过程中,或许已经没有开发人员会做这么荒唐的事情了。
非阻塞I/O和阻塞I/O最大的不同在于,非阻塞I/O并不会在I/O请求时产生阻塞。也就是说如果服务端没有收到I/O请求时,非阻塞I/O会“持续轮循”I/O请求,当有请求进来后就开始执行I/O操作并阻塞请求进程。Java7.x允许开发人员使用IOCP API进行异步I/O编程,这使得开发人员不必再关心I/O是否阻塞。因为应用程序中所有的I/O操作将全部委托给操作系统线程去执行,直至最后通知并返回结果。

九、Swing Framework(JSR 296规范)支持
笔者其实对Swing非常厌恶,如果可以的话笔者希望Oracle能够废除掉Swing这项技术。早在08年的时候笔者由于项目需要,曾饱受Swing的折磨。繁琐的布局、组件加载优化、冗长代码维护等这些令人痛苦和发指的问题,笔者相信使用过Swing的人开发人员都能发出相同的感叹。
早期的Java GUI(图形用户界面)主要由AWT技术主导,但随着用户对胖客户端技术的丰富度要求逐渐提高,AWT主键逐渐被Swing替代。Swing其实继承于AWT,并提供有更加绚丽的视图组件效果。何况相对于重量级的AWT组件来说,Swing显得更加轻量。
笔者刚才说过,Swing虽然相对于AWT来说组件内容更加丰富,但仍然掩盖不了其繁琐的操作实现。如果对组件性能有过高要求,或者需要实现快速开发,笔者更建议你使用SWT或者JFace技术(无需指望使用IDE工具进行可视化编程,因为这纯粹是吃力不讨好)。因为这2种技术可以看成是Swing的过渡,且相对Swing来说性能和丰富度更加优秀。
既然Java7.x对Swing仍不忘眷顾优化,那希望大家还是支持一下吧。从官方声明可以看出,JSR 296规范的目标是简化Swing的开发难度,且提供有更加丰富的组件资源。如果对于从未接触过Swing编程的开发人员,笔者倒是建议你尝试一下,或许你并不反感。

十、JVM内核升级之Class Loader架构调整
类装载器(Class Loader)属于JVM体系结构的重要组成部分,它是将Java类型装载进JVM内部的关键一环。它使得Java类型可以动态的被装载到JVM内部解释并运行。
在JVM内部存在着2种类型的类装载器:非自定义类装载器和自定义类装载器 。非自定义类装载器负责装载Java API中的类型及Java程序中的类型,而自定义类装载器能够使用自定义的方式来装载其类型。不同类型的类装载器所装载的类型将被存放于JVM内部不同的命名空间中。
非自定义类装载器由JVM内部3个核心类装载器构成:
1、BootStrap ClassLoader;
2、ExtClassLoader;
3、AppClassLoader;
BootStrap ClassLoader也称为启动类装载器,它是JVM内部最顶层的类装载器。BootStrap ClassLoader主要负责装载核心Java API中的类型。ExtClassLoader负责装载扩展目录下的类型。AppClassLoader则负责装载ClassPath(Java应用类路径)下指定的所有类型。其中ExtClassLoader和AppClassLoader都属于BootStrap ClassLoader的派生类装载器。
在Object内部封装着一个通过JNI(Java Native Interface,Java本地接口)方式调用的getClass()本地方法,该方法返回一个Class类型。对于开发人员而言,允许直接调用其getClassLoader()方法获取类装载器实例。
使用getClassLoader()方法获取类装载器:
@Test
public void testClassLoader() throws Exception {
ClassLoader loader = System.class.getClassLoader();
System.out.println(null != loader ? loader.getClass().getName() : loader);
System.out.println(CollationData_ar.class.getClassLoader().getClass().getName());
System.out.println(this.getClass().getClassLoader().getClass().getName());
}
通过上述程序示例我们可以看出,System类型是被BootStrap ClassLoader所装载的。但程序的输出结果却是Null,当然这并不代表BootStrap ClassLoader不存在。因为BootStrap ClassLoader并不是采用Java语言编写,而是由C++语言编写并嵌入在JVM内部,所以开发人员无法获取其实例。CollationData_ar类型属于jre\lib\ext目录下的派生类,由ExtClassLoader装载。当前类则由AppClassLoader负责装载。
在此笔者要提示大家,ExtClassLoader和AppClassLoader都是采用Java语言编写。所以ExtClassLoader和AppClassLoader本身也都是Java类型,都会被最顶层的类装载器BootStrap ClassLoader装载,最后才会装载各自管辖范围内的类型。
谈到ClassLoader的架构,我们不得不提及双亲委派模型。在JVM内部,类装载器装载类型所采用的便是双亲委派模型机制。比如AppClassLoader需要将一个类型装载进JVM内部,首先其自身并不会立即装载,而是将目标类型委派给上一级,也就是ExtClassLoader。ExtClassLoader接着再继续委派给BootStrap ClassLoader。在JVM内部最顶层的类装载器就是BootStrap ClassLoader,首先由它负责装载目标类型及其关联或依赖的所有类型。如果BootStrap ClassLoader装载失败,则退回给ExtClassLoader装载。如果ExtClassLoader也无法装载,最后只能退回给AppClassLoader继续装载。最后当AppClassLoader都无法装载的时候,便会抛出ClassNotFoundException异常(开发人员可以在捕获ClassNotFoundException异常的时候重写ClassLoder类型的findClass()方法实现自定义类型装载)。
类装载器架构示例:
类装载器除了需要负责类型的装载,还需要负责验证目标类型的正确性、属性内存分配、解析符号引用等操作。JVM通过装载、连接和初始化一个Java类型,使其可以被运行时的Java应用程序所使用。其中装载就是把二进制形式的Java类型写入进JVM内部。连接则是把已经写入进JVM中的二进制形式的类型合并到JVM的运行时状态中去。然而连接阶段又分成了3个步骤:验证、准备和解析。“验证”步骤确保了Java类型的数据格式,“准备”步骤则负责为目标类型分配所需的内存空间,“解析”步骤负责把常量池中的符号引用转换为直接使用。“验证”和“解析”这2个步骤都是为最后的初始化工作做准备。
类型生命周期示例:
Java7.x在上述ClassLoader架构的基础之上,进行了一些细微调整。在早期开发人员如果想要实现自定义类装载器,恐怕只能实现ClassLoader类型并重写其findClass()方法。但由于findClass()方法是按照串行结构的方式执行,或许是出于对性能和安全的考虑。Java7.x提供了一个拥有并行执行能力的增强实现,这样一来自定义类装载器便可以通过异步方式对类型进行装载。

Java 8

概述
Java 8包含两个主要项目:
1. Lambda
在Lambda项目中,多核处理器下的Java编程将更高效,Lambda表达式可以帮助开发人员提高效率,更好地利用多核处理器。Lambda项目还包括一个新的处理系统,该系统允许要求代码建模的编程模式作为数据。新功能一览:
•Lambda表达式的运用
•扩展目标类型化
•方法和构造函数参考
•默认方法
2. Jigsaw
Jigsaw项目的目标是创建一个实用的方式来在JDK上设计和实施一个模块系统,然后将该系统应用于JDK本身。其关键是令大块的代码更易于管理,并促进应用和大型运算的代码重用。Jigsaw项目还带来了许多新的表单功能,涉及封装、重构、版本和模块集成。
此外,除了这两个项目,Java 8 还增加改进了一些其他语言功能,如升级核心Java库使并行运算的表达更容易;虚拟扩展方法允许对接口增加方法,为默认实现指定参考;增加新的日期/时间API,同时支持传感器,增加代码的部署选项。
新特性
Lambdas

一个函数式接口非常有价值的属性就是他们能够用lambdas来实例化。这里有一些lambdas的例子:
(int x, int y) -> { return x + y; }
    左边是指定类型的逗号分割的输入列表,右边是带有return的代码块
(x, y) -> x + y 左边是推导类型的逗号分割的输入列表,右边是返回值
x -> x * x
    左边是推导类型的单一参数,右边是一个返回值
() -> x
    左边没有输入 (官方名称: "burger arrow"),在右边返回一个值
x -> { System.out.println(x); } 左边是推导类型的单一参数,右边是没返回值的代码块(返回voidString::valueOf
    静态方法引用
Object::toString
    非静态方法引用
x::toString
    继承的函数引用
ArrayList::new
    构造函数引用

方法引用     等价的lambda表达式
String::valueOf x -> String.valueOf(x)
Object::toString    x -> x.toString()
x::toString () -> x.toString()
ArrayList::new  () -> new ArrayList<>()
当然,在Java里方法能被重载。类可以有多个同名但不同参数的方法。这同样对构造方法有效。ArrayList::new能够指向它的3个构造方法中任何一个。决定使用哪个方法是根据在使用的函数式接口。
一个lambda和给定的函数式接口在“外型”匹配的时候兼容。通过“外型”,我指向输入、输出的类型和声明检查异常。
给出两个具体有效的例子:
Comparator<String> c = (a, b) -> Integer.compare(a.length(),b.length());
一个Comparator<String>的compare方法需要输入两个阐述,然后返回一个int。这和lambda右侧的一致,因此这个任务是有效的。
Runnable r = () -> { System.out.println("Running!"); }
一个Runnable的run方法不需要参数也不会返回值。这和lambda右侧一致,所以任务有效。
在抽象方法的签名里的受检查异常(如果存在)也很重要。如果函数式接口在它的签名里声明了异常,lambda只能抛出受检查异常。
捕获和非捕获的Lambda表达式
当Lambda表达式访问一个定义在Lambda表达式体外的非静态变量或者对象时,这个Lambda表达式称为“捕获的”。比如,下面这个lambda表达式捕捉了变量x:
int x = 5; return y -> x + y;
为了保证这个lambda表达式声明是正确的,被它捕获的变量必须是“有效final”的。所以要么它们需要用final修饰符号标记,要么保证它们在赋值后不能被改变。
Lambda表达式是否是捕获的和性能悄然相关。一个非不捕获的lambda通常比捕获的更高效,虽然这一点没有书面的规范说明(据我所知),而且也不能为了程序的正确性指望它做什么,非捕获的lambda只需要计算一次. 然后每次使用到它都会返回一个唯一的实例。而捕获的lambda表达式每次使用时都需要重新计算一次,而且从目前实现来看,它很像实例化一个匿名内部类的实例。
Non-final* 变量捕获 - 如果一个变量被赋予新的数值,它将不能被用于lambda之中。"final"关键字不是必需的,但变量必须是“有效final”的(前面讨论过)。这个代码不会被编译:
int count = 0;
List<String> strings = Arrays.asList("a", "b", "c");
strings.forEach(s -> {
    count++; // error: can't modify the value of count });
例外的透明度 - 如果一个已检测的例外可能从lambda内部抛出,功能性的接口也必须声明已检测例外可以被抛出。这种例外不会散布到其包含的方法。这个代码不会被编译:
void appendAll(Iterable<String> values, Appendable out) throws IOException { 
// doesn't help with the error 
values.forEach(s -> {
out.append(s); 
// error: can't throw IOException here 
// Consumer.accept(T) doesn't allow it 
});
}
•   Function<T, R> -T作为输入,返回的R作为输出
•   Predicate<T> -T作为输入,返回的boolean值作为输出
•   Consumer<T> - T作为输入,执行某种动作但没有返回值
•   Supplier<T> - 没有任何输入,返回T
•   BinaryOperator<T> -两个T作为输入,返回一个T作为输出,对于“reduce”操作很有用
这些最原始的特征同样存在。他们以int,long和double的方式提供。例如:
•   IntConsumer -以int作为输入,执行某种动作,没有返回值
这里存在性能上的一些原因,主要释在输入或输出的时候避免装箱和拆箱操作。

接口改善
现在接口里已经完全可以定义静态方法了.
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 8中接口可以定义默认的方法了.举个例子,一个for-each循环的方法就可以加入到java.lang.Iterable中:

public default void forEach(Consumer

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表达式还能作出什么更方便的东西来:
方法与构造函数引用
前一节中的代码还可以通过静态方法引用来表示:

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方法的签名来选择合适的构造函数。

函数式接口
Java 8 引入的一个核心概念是函数式接口。如果一个接口定义个唯一一个抽象方法,那么这个接口就成为函数式接口。比如,java.lang.Runnable就是一个函数式接口,因为它只定义一个抽象方法:
public abstract void run();
留意到“abstract”修饰词在这里是隐含的,因为这个方法缺少方法体。为了表示一个函数式接口,并非像这段代码一样一定需要“abstract”关键字。
默认方法不是abstract的,所以一个函数式接口里可以定义任意多的默认方法,这取决于你。
同时,引入了一个新的Annotation:@FunctionalInterface。可以把他它放在一个接口前,表示这个接口是一个函数式接口。加上它的接口不会被编译,除非你设法把它变成一个函数式接口。它有点像@Override,都是声明了一种使用意图,避免你把它用错。
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 作用域
在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。

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同样是不允许的。
和本地变量不同的是,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);
        };
    }
}

八、访问接口的默认方法
还记得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函数式接口
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中。
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
十、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 {}

关于Java 8的新特性就写到这了,肯定还有更多的特性等待发掘。JDK 1.8里还有很多很有用的东西,比如Arrays.parallelSort, StampedLock和CompletableFuture等等。

Java 9、10的发展规划

甲骨文对Java 8 的前景很是看好,并已经开始讨论Java 9发展的关键领域。比如加入一个self-tuning JVM,提高本地集成和大规模多核的可扩展性;通过新的元对象协议和资源管理器为云应用添加跨语言支持。
甲骨文也表示,Java9和10将加入大数据、多语言的互操作性、云计算和移动,预期分别于2015年和2017年发布。而关于Java开发工具包(JDK)10以及之后的版本也正在讨论中,比如使Java语言面向对象,形成一个统一的类型系统,所有原语都将转换为对象和方法。

随着使用人数的增加,Java正逐渐成为最常用的编程语言,令每个使用者都满意成了它的目标。甲骨文认为Java在将来会成为开发者们首选的编程语言,因为它可以实现的东西正好符合了开发者们的期望。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值