Java SE 7 新增特性

作者:Grey

二进制表示

Java SE 7中,基本类型(byteshortintlong)也可以用二进制表示。只需要在数字上添加前缀0b0B

public class BinaryTest {
    public static void main(String[] args) {
        // 0b是二进制的开头
        // 所以num表示7
        int num = 0b111;
        System.out.println(num);

        // 二进制0110转换成十进制是6
        num = 0B0110;
        System.out.println(num);
    }
}

下划线

Java SE 7及以后的版本中,任何数量的下划线字符_都可以出现在数字开头和结尾之间的任何地方。这一特性使你能够在数字中分隔数字组,这可以提高代码的可读性。例如,如果你的代码中包含有许多数字,你可以使用下划线字符将数字分成三组,类似于你使用逗号或空格等标点符号作为分隔符的方式。

public class UnderscoresTest {
    public static void main(String[] args) {
        long creditCardNumber = 1234_5678_9012_3456L;
        System.out.println(creditCardNumber);
        long socialSecurityNumber = 999_99_9999L;
        System.out.println(socialSecurityNumber);
        float pi = 3.14_15F;
        System.out.println(pi);
        long hexBytes = 0xFF_EC_DE_5E;
        System.out.println(hexBytes);
        long hexWords = 0xCAFE_BABE;
        System.out.println(hexWords);
        long maxLong = 0x7fff_ffff_ffff_ffffL;
        System.out.println(maxLong);
        byte nybbles = 0b0010_0101;
        System.out.println(nybbles);
        long bytes = 0b11010010_01101001_10010100_10010010;
        System.out.println(bytes);
    }
}

switch中可以使用字符串

public class SwitchTest {
    public static void main(String[] args) {
        System.out.println("Monday");
    }

    public String getTypeOfDayWithSwitchStatement(String dayOfWeekArg) {
        String typeOfDay;
        switch (dayOfWeekArg) {
            case "Monday":
                typeOfDay = "Start of work week";
                break;
            case "Tuesday":
            case "Wednesday":
            case "Thursday":
                typeOfDay = "Midweek";
                break;
            case "Friday":
                typeOfDay = "End of work week";
                break;
            case "Saturday":
            case "Sunday":
                typeOfDay = "Weekend";
                break;
            default:
                throw new IllegalArgumentException("Invalid day of the week: " + dayOfWeekArg);
        }
        return typeOfDay;
    }
}

类型推断

例如,jdk1.7之前:

Map<String, List<String>> myMap = new HashMap<String, List<String>>();

jdk1.7中,可以使用<>,省去了具体类型

Map<String, List<String>> myMap = new HashMap<>();

注意,<>是必需的,如果没有<>,例如:

Map<String, List<String>> myMap = new HashMap();

会报一个unchecked conversion warning的警告信息。

Improved Compiler Warnings and Errors When Using Non-Reifiable Formal Parameters with Varargs Methods

Try-With-Resource

jdk1.7之前的写法

static String readFirstLineFromFileWithFinallyBlock(String path) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(path));
        try {
            return br.readLine();
        } finally {
            if (br != null) br.close();
        }
    }

jdk1.7之后的写法

static String readFirstLineFromFile(String path) throws IOException {
        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
            return br.readLine();
        }
    }

此外,还可以try后面的括号里面还可以接多个表达式,例如:

public static void writeToFileZipFileContents(String zipFileName, String outputFileName)
            throws java.io.IOException {

        java.nio.charset.Charset charset = java.nio.charset.Charset.forName("US-ASCII");
        java.nio.file.Path outputFilePath = java.nio.file.Paths.get(outputFileName);

        // Open zip file and create output file with try-with-resources statement

        try (
                java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName);
                java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
        ) {

            // Enumerate each entry

            for (java.util.Enumeration entries = zf.entries(); entries.hasMoreElements(); ) {

                // Get the entry name and write it to the output file

                String newLine = System.getProperty("line.separator");
                String zipEntryName = ((java.util.zip.ZipEntry) entries.nextElement()).getName() + newLine;
                writer.write(zipEntryName, 0, zipEntryName.length());
            }
        }
    }

注意,这里的类需要实现java.lang.AutoCloseable或者java.io.Closeable接口。

文件操作相关API增强

Java 7 提供了 Path 接口用来表示路径的抽象,然后提供了一系列对于路径的操作方法

private static void pathTest1(final String pathString) {

        Path path = Paths.get(pathString);
        System.out.println("完整路径:" + path.toString());

        Path pathParent = path.getParent();
        System.out.println("父级路径:" + pathParent.toString());

        Path pathRoot = path.getRoot();
        System.out.println("根目录:" + pathRoot.toString());

        int pathNameCount = path.getNameCount();
        System.out.println("目录深度:" + pathNameCount);

        Path pathIndex3 = path.getName(2);
        System.out.println("第三级目录:" + pathIndex3);

        Path subPath = path.subpath(1, 3);
        System.out.println("第1级目录到第三级目录(包左不包右):" + subPath.toString());

// resolveSibling 从当前目录父目录开始拼接目录
        // FIXME 使用你本地存在的路径
        Path pathResolveSibling = path.resolveSibling("src\\main");
        System.out.println("父目录开始拼接参数:" + pathResolveSibling.toString());

// resolve 把当前路径当作父路径,参数作为子目录或者文件
        // FIXME 使用你本地存在的路径
        Path pathResolve = Paths.get("\\java\\snippet").resolve("SimpleSortMethod.java");
        System.out.println("当前目录拼接后的目录:" + pathResolve.toString());

// 参数路径相对于主体路径的相对路径
        // FIXME 使用你本地存在的路径
        Path path1 = Paths.get("\\java\\snippet");
        Path path2 = Paths.get("\\java\\snippet\\SimpleSortMethod.java");
        Path path3 = path1.relativize(path2);
        System.out.println("相对路径:" + path3.toString());
    }

    private static void pathOp() throws IOException {
        // 如果文件不存在,则创建一个文件
        Path path = Paths.get("test.txt");
        Path pathBackup = Paths.get("test_bak.txt");
        Path pathLink = Paths.get("test.txt.link");
        Path pathDir = Paths.get("dir");

// 已存在则删除
        Files.deleteIfExists(path);
        Files.deleteIfExists(pathBackup);
        Files.deleteIfExists(pathLink);
        Files.deleteIfExists(pathDir);

// 创建文件写入内容
        Path file = Files.createFile(path);
        Files.write(path, "ABC".getBytes());
        Files.write(path, System.lineSeparator().getBytes(), StandardOpenOption.APPEND);
        Files.write(path, "EFG".getBytes(), StandardOpenOption.APPEND);
        System.out.println("创建文件:" + file.toString());

// 创建文件链接
        pathLink = Files.createLink(pathLink, path);
        System.out.println("创建文件:" + pathLink.toString());

// 创建目录
        Path directory = Files.createDirectory(pathDir);
        System.out.println("创建目录:" + directory.toString());

// 文件复制
        Files.copy(path, pathBackup);
        System.out.println("复制文件: " + path + " --> " + pathBackup);

// 读取文件
        List<String> lines = Files.readAllLines(pathBackup, Charset.defaultCharset());
        for (String line : lines) {
            System.out.println("文件读取:" + line);
        }
    }

    private static void fileInfo() throws IOException {
        // FIXME 使用你本地存在的文件
        Path path = Paths.get("D:\\git\\algorithm\\README.md");
        BasicFileAttributeView fileAttributeView = Files.getFileAttributeView(path, BasicFileAttributeView.class);
        BasicFileAttributes basicFileAttributes = fileAttributeView.readAttributes();
        FileTime creationTime = basicFileAttributes.creationTime();
        FileTime lastModifiedTime = basicFileAttributes.lastModifiedTime();
        FileTime lastAccessTime = basicFileAttributes.lastAccessTime();
        System.out.println("创建时间:" + creationTime);
        System.out.println("上次修改时间:" + lastModifiedTime);
        System.out.println("上次访问时间:" + lastAccessTime);

        boolean directory = basicFileAttributes.isDirectory();
        boolean regularFile = basicFileAttributes.isRegularFile();
        boolean symbolicLink = basicFileAttributes.isSymbolicLink();
        System.out.println("是否目录:" + directory);
        System.out.println("是否普通文件:" + regularFile);
        System.out.println("是否符号链接:" + symbolicLink);

        long size = basicFileAttributes.size();
        System.out.println("文件大小:" + size);

        // Linux或者Mac使用下面PosixFileAttributeView
        // PosixFileAttributeView linuxFileAttributeView = Files.getFileAttributeView(path, PosixFileAttributeView.class);
//        UserPrincipal owner = linuxFileAttributeView.getOwner();
//        System.out.println("文件归属用户:" + owner.getName());
        // Windows使用DosFileAttributeView
        DosFileAttributeView windowsFileAttributeView = Files.getFileAttributeView(path, DosFileAttributeView.class);
        DosFileAttributes dosFileAttributes = windowsFileAttributeView.readAttributes();
    }

Java 7之前遍历文件目录和文件,可以使用File类的listFiles方法。

public static void listFile() {
        // FIXME 改成你自己本地的可访问路径
        String pathString = "D:\\git\\algorithm\\src\\main\\java\\snippet";
        File file = new File(pathString);
        File[] listFiles = file.listFiles();
        for (File tempFile : listFiles) {
            System.out.println("file list: " + tempFile.getAbsolutePath());
        }
    }

Java 7引入了DirectoryStream文件列表流。它可以进行渐进式的文件遍历,每次读取一定数量,降低遍历时的性能开销,但是DirectoryStream遍历时只会遍历它的直接目录和文件,不会递归的遍历子目录。

public static void listFile2() throws IOException {
        // FIXME 改成你自己本地的可访问路径
        String pathString = "D:\\git\\algorithm\\src\\main\\java\\snippet";
// Path 直接遍历方式,不会遍历子目录
        try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(Paths.get(pathString))) {
            for (Path pathTemp : directoryStream) {
                System.out.println("DirectoryStream: " + pathTemp);
            }
        }

Java 8中对Files类进行了增强,引入了Lambda表达式,增加了walk方法

public static void fileTravel() throws Exception {
// FIXME 修改成你自己本地的路径
        final String pathString = "D:\\git\\algorithm\\src\\main\\java\\snippet";

// 遍历所有目录和子目录
        Stream<Path> pathStream = Files.walk(Paths.get(pathString));
        pathStream.forEach(pathTemp -> {
            System.out.println("Stream: " + pathTemp.toString());
        });

// 遍历所有目录和子目录 - 筛选 java 文件
        pathStream = Files.walk(Paths.get(pathString));
        pathStream
                .filter(pathTemp -> pathTemp.toString().endsWith(".java"))
                .forEach(pathTemp -> {
                    System.out.println("Stream filter java: " + pathTemp.toString());
                });
    }

也就是可以动态的监测指定目录的文件或者内容的变化,被监视的对象要实现Watchable接口,然后通过register方法注册到监视服务WatchService接口的实现,同时指定要监视的事件类型。

以下是代码(监听D:\\test目录的各种改动,测试时请换成你本地存在的文件夹)

// 监听文件/文件夹的变化
    private static void fileWatch() throws Exception {
        WatchService watchService = FileSystems.getDefault().newWatchService();
        // FIXME 换成你本地目录
        Path path = Paths.get("D:\\test");
        path.register(watchService,
                StandardWatchEventKinds.ENTRY_CREATE,
                StandardWatchEventKinds.ENTRY_DELETE,
                StandardWatchEventKinds.ENTRY_MODIFY);

        while (true) {
            WatchKey watchKey = watchService.take();
            // 获取事件类型
            for (WatchEvent<?> pollEvent : watchKey.pollEvents()) {
                // 具体的事件上下文信息
                Path tempPath = (Path) pollEvent.context();
                WatchEvent.Kind<?> kind = pollEvent.kind();
                if (kind.name().equals(StandardWatchEventKinds.ENTRY_CREATE.name())) {
                    System.out.println("创建了一个文件:" + tempPath.toString());
                }
                if (kind.name().equals(StandardWatchEventKinds.ENTRY_DELETE.name())) {
                    System.out.println("删除了一个文件:" + tempPath.toString());
                }
                if (kind.name().equals(StandardWatchEventKinds.ENTRY_MODIFY.name())) {
                    System.out.println("修改了一个文件:" + tempPath.toString());
                }
            }
            // 事件处理完毕后要进行 reset 才能继续监听事件
            watchKey.reset();
            // 取消监视
            // watchKey.cancel();
        }
    }

捕获多个异常类型

如果一段代码有多个异常,原先的代码可能会是这样

catch (IOException ex) {
     logger.log(ex);
     throw ex;
catch (SQLException ex) {
     logger.log(ex);
     throw ex;
}

jdk1.7中,可以简写成这样

catch (IOException|SQLException ex) {
    logger.log(ex);
    throw ex;
}

此外,还可以指定抛出异常类型,而不需要每次都抛出一个大而全的Exception,例如

public void rethrowException(String exceptionName) throws Exception {
        try {
            if (exceptionName.equals("First")) {
                throw new FirstException();
            } else {
                throw new SecondException();
            }
        } catch (Exception e) {
            throw e;
        }
    }

我们可以指定抛出的异常类型

public void rethrowException(String exceptionName)
  throws FirstException, SecondException {
    try {
      // ...
    }
    catch (Exception e) {
      throw e;
    }
  }

参考资料

Java Language Updates

Enhancements in Java SE 7

Java 7 Files,Paths,Path 文件操作介绍