Go To My HomePage

Java9新特性

一、JDK目录结构

目录名称 说明
bin 包含所有命令和运行时动态链接库
conf 包含用户可编辑的配置文件,例如以前位于jre\lib目录中的.properties.policy文件
include 包含编译本地代码时使用的C/C++头文件
jmods 包含JMOD格式的平台模块, 创建自定义运行时映像时需要它
legal 包含法律声明
lib 其它平台上的动态链接本地库, 其子目录和文件不应由开发人员直接编辑或使用

二、模块化系统

​ Java运行环境的膨胀和臃肿的主要原因是JVM需要加载rt.jar【runtime环境】。不管其中的类是否被classloader加载,第一步整个jar都会被JVM加载到内存当中去,一般需要30~60MB内存(而模块化可以根据模块的需要加载程序运行需要的class)

​ 每一个公共类都可以被类路径之下任何其它的公共类所访问到,这样就会导致无意中使用了并不想被公开访问的 API。模块化限制了类的访问

使用模块化的前提条件是java文件夹下必须有module-info.java

exports&requires

//========================================= 工程java9.modular ========================================
package com.kun.modular.export.bean;

public class Person {
  // filed and method
}

/*
 * module-info配置
 */
module java9.modular {
  // exports [包名...]    暴露当前模块的包,所有其它模块均可访问
  exports com.kun.modular.export.bean;
  
  // exports [包名...] to [包名...]   暴露当前模块的包,只有指定的其它包可访问
  // exports com.kun.modular.export.bean to com.kun.test.modular;
}

//============================================ 工程java9 ===========================================
package com.kun.test.modular;

public class ModularTest {

  public static void main(String[] args) throws Exception {
    Person person = new Person();
  }
}

/*
 * module-info配置
 */
module java9 {
  // 引入模块
  requires java9.modular;
}

opens&requires

//========================================= 工程java9.modular ========================================
package com.kun.modular.export.bean;

public class Person {
  // filed and method
}

/*
 * module-info配置
 */
module java9.modular {
  // 指定包可以通过java反射访问
  opens com.kun.modular.export.bean;
}

//============================================ 工程java9 ===========================================
package com.kun.test;

public class ModularTest {

  public static void main(String[] args) throws Exception {
    Class<?> aClass = Class.forName("com.kun.modular.bean.Person");
    Constructor<?> constructor = aClass.getConstructor();
    Object o = constructor.newInstance();
  }
}

/*
 * module-info配置
 */
module java9 {
  // 通过反射也需要显示引入模块
  requires java9.modular;
}

provides [class-qualified] with [class-qualified-impl] & use

//========================================= 工程java9.modular ========================================
package com.kun.modular.export.provides;

public interface PersonService {
  Person getPerson();
}

public class PersonServiceImpl  implements PersonService{
  @Override
  public Person getPerson() {
    return new Person();
  }
}

/*
 * module-info配置
 */
module java9.modular {
  // 指定服务类和其实现类
  provides com.kun.modular.export.provides.PersonService with com.kun.modular.export.provides.PersonServiceImpl;

}

//============================================ 工程java9 ===========================================
package com.kun.test;

public class ModularTest {

  public static void main(String[] args) throws Exception {
    // 加载服务
    PersonService personService = ServiceLoader.load(PersonService.class).findFirst().get();
    personService.getPerson();
  }
}
/*
 * module-info配置
 */
module java9 {
    requires java9.modular;
    // 指定使用什么服务,上方的requires也必须使用
    uses com.kun.modular.export.provides.PersonService;
}

requires

requires static [包名]表示依赖的模块是编译时是必需的,但运行时是可选的,比如一些检查注解,运行时并不需要

requires transitive [包名]表示假如模块A依赖模块 B,外界的模块不仅想使用 A 模块还想使用B模块,那么可以使用该指令获取到B 的访问权限,这种对外暴露传递依赖的模块也称为隐式读取(implied read)

三、接口的私有方法

public interface NewInterface {
  // JDK 7及之前版本只能创建public abstract 方法 和 public static final常量
  void methodJDK7();
  public static final String CONSTANT_DATA = "常量";

  // JDK8允许创建default方法访问修饰符是public, 还允许创建静态方法修饰符是public
  default void methodJDK8() {
    System.out.println("JDK8允许创建default方法");
  }
  static void methodStaticJDK8() {
    System.out.println("JDK8允允许创建静态方法");
  }
  
  // JDK9允许创建private方法,供default方法调用[多个default抽取公共方法]
  private void methodJDK9() {
    System.out.println("JDK9允许创建private方法,供default方法调用[多个default抽取公共方法]");
  }
  default void methodDefault(){
    methodJDK9();
  }
}

四、钻石操作符的使用升级

public class DiamondOperatorTest {

  public static void main(String[] args) {
    // JDK8也可以使用但是new HashMap<>不能进行泛型推到必须使用new HashMap<Integer, String>
    Map<Integer, String> map = new HashMap<>(){
      {
        put(1, "A");
        put(2, "B");
        put(3, "C");
      }
    };
  }
}

五、try语句改进

//======================================== JDK 7 处理方式 ========================================
InputStream inputStream = null;
try {
  inputStream = new FileInputStream("index.html");
} catch (FileNotFoundException e) {
  System.out.println("异常处理");
} finally {
  if (inputStream != null) {
    try {
      inputStream.close();
    } catch (IOException e) {
      System.out.println("异常处理");
    }
  }
}

//======================================== JDK 8 处理方式 ========================================
// 流会自动关闭但是 InputStream inputStream = new FileInputStream("index.html") 必须写在括号内
try(InputStream inputStream = new FileInputStream("index.html")) {
  // do something
} catch (FileNotFoundException e) {
  System.out.println("异常处理");
} catch (IOException e) {
  System.out.println("异常处理");
}

//======================================== JDK 9 处理方式 ========================================
// 流会自动关闭,但是文件找不到这种异常还是会向上抛出
InputStream inputStream = new FileInputStream("index.html");
try(inputStream) {
  // do something
} catch (IOException e) {
  System.out.println("异常处理");
}

六、String存储结构变更

背景:JDK8的字符串存储在char类型的数组里面,在大多数情况下字符只需要一个字节就能表示出来了[英文字母和数字],所以使用char[一个char占两个字节]存储势必会浪费空间

JDK9改用byte[]表示

  • 当String中只包含一个字节能表示的字符时,使coder=LATIN1=0,采用一个byte存储一个字符的策略
  • 当String中包含非一个字节能表示的字符时,使coder=UTF-16=1,采用两个byte存储一个字符的策略
  • 计算长度时【byte[].length « coder】位

String底层存储改变引起的连锁反应:StringBufferStringBuilder底层也使用byte[]存储,当调用append时会判断需要连接的俩字符串是否是同coder,是同coder直接连接。不是同coder则需要变为coder=UTF-16格式

七、创建只读集合

//========================================= JDK 8 创建只读容器 =========================================
List<String> stringList = new ArrayList<>();
stringList.add("1");
stringList.add("2");
stringList.add("3");
stringList = Collections.unmodifiableList(stringList);
stringList.add("2");

//========================================= JDK 9 创建只读容器 =========================================
List<String> stringList = List.of("1", "2", "3", "4", "5");

Set<String> stringSet = Set.of("1", "2", "3", "4", "5");

Map<Integer, String> map = Map.of(1, "v1", 2, "v2", 3, "v3");
Map<Integer, String> mapEntry = Map.ofEntries(
  Map.entry(1, "v1"),
  Map.entry(2, "v2"),
  Map.entry(3, "v3"));

八、增强的Stream API

List<Integer> list = Arrays.asList(45, 43, 76, 87, 42, 77, 90, 73, 67, 88);
// 在流中得到尽可能多的符合条件的数据,即从左往右查找
list.stream().takeWhile(x -> x < 80).forEach(System.out::println);

// takeWhile的补集
list.stream().dropWhile(x -> x < 80).forEach(System.out::println);

/*
 * public static<T> Stream<T> of(T... values)  可以元素全部为null,count时null不计入
 * public static<T> Stream<T> of(T t)          元素不能为null会抛出异常
 * public static<T> Stream<T> ofNullable(T t)  元素可以为null,count时null计入
 */
Stream.of("1", "2", null); //count为2
Stream.ofNullable(null);   //count为1

// iterator()重载的使用,创建一个1~10的Integer类型的Stream
Stream.iterate(1, i -> i + 1).limit(10)
// 比使用limit更加灵活
Stream.iterate(1, i -> i <= 10,i -> i + 1).forEach(System.out::println);

// Optional类中stream()的使用
Optional<List<String>> optional = Optional.of(Arrays.asList("1", "2", "3", "4"));
// optional.stream() 得到的是Stream<List<String>>
optional.stream().flatMap(x -> x.stream()).forEach(System.out::println);

创建Stream的方法:

  • 通过集合的stream()
  • 通过数组工具类Arrays
  • Stream中静态方法of()
  • iterator()

九、InputStream增强

InputStream添加了transferTo放法,可以用来将数据直接传输到OutputStream,这是在处理原始数据流时非常常见的一种用法

public class InputStreamTest {

  public static void main(String[] args) throws IOException {
    InputStream inputStream = new FileInputStream("src/1.txt");
    OutputStream outputStream = new FileOutputStream("1_bak.txt");
    inputStream.transferTo(outputStream);
  }
}