Java 8 新特性一览

本文描述 Java 8 的其余新特性,包括提升的类型推断,Java 类型注解,方法参数反射,针对存在键冲突的 HashMap 的性能改进,Date-Time 程序包,HotSpot 删除了 PermGen 。

提升的类型推断

类型推断这个概念可能比较陌生,但我们编程时或多或少都享受过它带来的便利。

在 Java 7 中,可以使用赋值语句的目标类型来进行类型推断。

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

这是在使用泛型类实例时进行的类型推断,只要编译器可以从上下文中推断类型参数,就可以用一组空的类型参数(<>)替换调用泛型类的构造函数所需的类型参数。这对尖括号非正式地称为钻石符。

理解类型推断需要明白一个目标类型和上下文的概念,目标类型是编译器在表达式出现的地方(上下文)所期待的数据类型。例如此处的目标类型便是 ArrayList<String> ,参数化类型是 String,上下文在此处应该是赋值语句。

在 JavaSE 8 中,可以在更多上下文中使用目标类型进行 类型推断。最突出的例子是使用 方法调用的目标类型 来推断其参数的数据类型。

List<String> stringList = new ArrayList<>();
stringList.add("A");
stringList.addAll(Arrays.asList());

此处 addAll 期待一个集合实例,在不考虑泛型的情况下,Arrays.asList 返回一个 List 实例,它作为集合的子类,这是正常的。

在考虑泛型的情况下,此处的目标类型是 Collection<? extends String> ,Arrays.asList 返回的是一个 List 实例。在这里 Java 8 编译器能够根据目标类型推断出 T 是 String。

Java 7 中,则无法从方法调用的参数目标类型中推断出类型,上述代码将在 Java 7 中得到一个错误:

error: no suitable method found for addAll(List<Object>) ...
method List.addAll(Collection<? extends String>) is not applicable (actual argument List<Object> cannot be converted to Collection<? extends String> by method invocation conversion)

因此,在这种Java编译器无法推断类型的情况下,必须用 类型见证器 显式地指定类型变量的值。例如,以下在Java SE 7中将正常工作:

List<String> stringList = new ArrayList<>();
stringList.add("A");
stringList.addAll(Arrays.<String>asList());

Java 类型注解

Java 8 之前注解只能出现在元素声明的地方,而在 Java 8 注解可以出现在任何类型使用的地方,这类注解称为类型注解。像这样:

    public static <@Schedule T> void start(T time) throws @Schedule Exception{
        Job job =  new @Schedule Job();
        float i = ((@Schedule float) 123);
        class TestImpl implements @Schedule Serializable{
            
        }
    }
    
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Schedules.class)
public @interface Schedule {
    String time() default "";
}    

表示注解能够在哪里使用的元注解是 Target, 其接受一个 ElementType 类型的 value 元素数组。
Java 8 为其新增了两种类型以支持类型注解,需要使用类型注解时,为期望注解的 Target 元注解添加 TYPE_USE 值即可:

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE

TYPE_PARAMETER 表示可在声明参数类型使用。TYPE_USE 则表示可在类型出现的任何地方使用注解。如果仔细观察其它类型,会发现它们的注释均以 declaration 结尾,这也表明了在 Java 8 之前注解只能应用于声明。

类型注解的作用,官方文档这样描述道:

创建类型注解是为了支持对 Java 程序的改进分析,从而确保更强的类型检查。JavaSE 8 发行版不提供类型检查框架,但它允许您编写(或下载)一个类型检查框架,该框架实现为与Java编译器一起使用的一个或多个可插入模块.

这有一个官方推荐的 检查框架, 这个框架的简介非常吸引人,它这样描述道:“您是否厌倦了空指针异常、意外的副作用、SQL注入、并发错误、错误的相等性测试以及其他在测试期间或字段中出现的运行时错误?”。如果感兴趣的话,可以深入研究。

方法参数反射

java.lang.reflect.Executable.getParameters 方法能够从任何方法或者构造函数中获取形式参数的名称,但 .class 默认情况下并不存储形式参数名称,为了启用它,需要在使用 javac 编译时,使用 -parameters 配置选项。

抽象类 Executable 有两个子类,分别是 MethodConstructor 类。

public class Job {

    public void testParametersName(String name, List<Integer> ages){

    }
}

以上代码使用 javac Job.java 编译后,再使用 javap -v -s Job.class 反汇编后,可得到以下代码(部分):

  public void testParametersName(java.lang.String, java.util.List<java.lang.Integer>);
    descriptor: (Ljava/lang/String;Ljava/util/List;)V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=3, args_size=3
         0: return
      LineNumberTable:
        line 14: 0
    Signature: #13                          // (Ljava/lang/String;Ljava/util/List<Ljava/lang/Integer;>;)V

如果使用携带 -parameters 选项编译,再使用同样的反汇编命令,针对以上代码,我们可以得到如下:

  public void testParametersName(java.lang.String, java.util.List<java.lang.Integer>);
    descriptor: (Ljava/lang/String;Ljava/util/List;)V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=3, args_size=3
         0: return
      LineNumberTable:
        line 14: 0
    MethodParameters:
      Name                           Flags
      name
      ages
    Signature: #17                          // (Ljava/lang/String;Ljava/util/List<Ljava/lang/Integer;>;)V

对此,可以证实,方法参数名真实地存储于 .class 文件中。

针对存在键冲突的 HashMap 的性能改进

这指的是提高java.util.HashMap在高哈希冲突条件下,使用平衡树而不是链表来存储映射项,在LinkedHashMap类中实现相同的改进。

如果直接说红黑树,便是老生常谈的一个话题。这里简单描述下其主要思想:“一旦 hash bucket 中的项数增长超过某个阈值,该 bucket 将从使用条目的链表切换到平衡树。在高哈希冲突的情况下,这将提高从O(n)到O(logn)的最坏情况性能”。

那么,Hashtable, WeakHashMap以及 IdentityHashMap 有没有实现这个改进呢?

使用 Hashtable 的遗留代码可能依赖其迭代顺序,所以,不会做此改进。WeakHashMap 又由于必须考虑其弱密钥的复杂性,这将导致性能下降,这也是不允许的。IdentityHashMap 使用系统识别码来生成 hashcode,因此冲突通常很少发生,所以它并不需要实现此技术。

Date-Time 程序包

一组新的日期-时间程序包,提供新的日期-时间模型,这在 java.time 包中。常用的类应该是 LocalDateTimeLocalDate 以及 LocalTime ,使用起来非常方便。

HotSpot 删除了 PermGen

PermGen 被称为永久代,也叫方法区。

Java 类在 Java Hotspot VM 中具有内部表示形式,被称为类元数据。在 Java Hotspot VM 的早期版本中,类元数据是在永久代中生成分配的。在 JDK 8 中,永久代已删除,并且类元数据已分配在本机内存中。默认情况下,可用于类元数据的本地内存数量是无限的。使用该选项 MaxMetaspaceSize 对用于类元数据的本机内存量设置上限。

Java Hotspot VM 显式管理用于元数据的空间。从操作系统请求空间,然后将其分成多个块。类加载器从其块分配元数据空间(块绑定到特定的类加载器)。当为类加载器卸载类时,其块将被回收以重新使用或返回给OS。

需要明白的是,尽管永久代也常被称作方法区,但永久代的移除和方法区并没有关系。方法区是在 JVM 规范中定义的,不同的虚拟机选择了不同的实现方式,这可能是最容易被误解的地方了。

写在最后

这是 Java 8 系列最后的一篇文章,此篇文章描述的内容都是浅尝辄止。Java 8 增强的特性还有很多,但日常接触的大多是这些了。

如果你觉得我的文章还不错,并对后续文章感兴趣的话,或者说我们有什么能够交流分享的,可以通过扫描下方二维码来关注我的公众号!我与风来

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值