jdk内置的一些工具类

java中一些工具类

一. java.util.Objects

1. 介绍

该类自jdk1.7引入, 此类包含用于对对象进行操作或在操作前检查某些条件的static实用程序方法.

例如: hashcode计算, 对象比较, 判空, toString, 索引范围检查等等.

相比于我们常见的使用表达式, 静态方法可以更好的配合函数式编程.

2. 方法

image-20220816233552558

Objects类中的方法都相当简单, openjdk17中总计代码行数未破500行(包括注释), 这些方法在空安全和索引检查方面非常实用, 特别是结合Stream使用时.

例如:

// 如果a为空, 会抛出空指针
if(a.equals(b)) {
	// ...  
}
// 可以替换成下面的代码
if(Objects.equals(a, b)) {
    // ...
}

// 可能导致NPE
int hashCode = o.hashCode();
// 空安全的hashCode
int hashCode = Objects.hashCode(o);

// 空安全的toString, 如果o为null, 会变成"null"字符串
String s = Objects.toString(o);
// 空安全的toString, 如果o为null, 会返回传入的字符串, 此处为空字符串.
String s = Objects.toString(o, "");

// 排序比较
String a = "Abc";
String b = "abD";
int compared = Objects.compare(a, b, String.CASE_INSENSITIVE_ORDER);

// 空断言, 为空直接报错
Objects.requireNonNull(obj);
// 可修改为空的报错消息
Objects.requireNonNull(obj, "不能为空!");
// 为空则调用supplier获取错误消息, 常用于错误消息较长需要拼接等情况.
Objects.requireNonNull(obj, () -> "不能为空!");
// 为空则使用给定的默认值, 如果默认也为空则报错
var nonNullObj = Objects.requireNonNullElse(obj, defaultObj);
// 为空则调用supplier获取默认值
var nonNullObj = Objects.requireNonNullElse(obj, () -> { .... return defaultObj; });

// 便于函数式编程的空检查
boolean isNull = Objects.isNull(obj);
boolean nonNull = Objects.nonNull(obj);
list.stream().map(...).filter(Objects::nonNull).collect(....);

// 索引范围检查
Objects.checkIndex(index, length);
Objects.checkFromIndexSize(fromIndex, size, length);
Objects.checkFromToIndex(fromIndex, toIndex, length);

二. java.util.Collections

1. 介绍

集合操作的工具类, Collection, Map, Enumeration 等接口类型的工具类.

2. 方法

image-20240302195105901

内部方法主要有:

  • empty开头的用于创建各种空集合的方法. jdk9以后可以用各种接口类型的空参 of 静态方法创建.

  • checked开头的返回动态类型安全的集合视图的方法.

  • synchronized开头的获取同步集合视图的方法.

  • unmodifiable开头的获取不可变集合视图的方法, jdk10以后可以用List.copyOf.

  • singleton 开头创建单个元素的集合的方法, jdk9以后可以用List.of, Set.of, Map.of 等替换.

其它方法也都是顾名思义.

  • 二分查找: binarySearch
  • 交换: swap
  • 排序: sort
  • 随机打乱: shuffle
  • 旋转(末尾元素移到最前, 前面元素后移): rotate
  • 频率计数(指定元素出现多少次): frequency
  • 无交集判断: disjoint
  • 以一个元素的多次重复创建列表: nCopies
  • 子列表查找: indexOfSubList, lastIndexOfSubList
  • 替换: replaceAll, copy, fill
  • 添加: addAll
  • 反转: reverse
  • 获取反转的Comparator: reverseOrder

使用需要注意的细节:

  1. singleton, empty, unmodifiable, nCopies 方法所创建的集合视图是不可变的.

  2. copy 其实是替换目标列表中的元素为源列表中的元素, 当目标列表的size小于源列表会索引越界.

    var dest = new ArrayList<String>();
    // IndexOutOfBoundsException: Source does not fit in dest
    Collections.copy(dest, List.of("abc"));
    
  3. fill 同样是替换, 将列表中的元素全部替换为指定元素.

    var dest = new ArrayList<String>();
    Collections.fill(dest, "haha");
    System.out.println(dest); // 输出[]
    dest.add("abc");
    Collections.fill(dest, "haha"); // 输出[haha]
    

三. java.nio.file.Files

1. 介绍

java.nio.file.Files类是java1.7引入的一个用于简化一些常用IO操作的工具类.

Files 类中的方法没有使用 File 作为参数, 而是使用 Path 类作为参数. Path 是文件系统路径的抽象.

任何与文件相关的操作, 首先都可以考虑 Files 类中是否已经存在.

2. 方法

image-20240302204231280

2.1. 复制

将输入复制到输出目标, 当输出目标是 Path 时, 可以指定 CopyOption.

CopyOption 一般实现是 StandardCopyOption 枚举类.

  • copy(InputStream, Path, CopyOption...)
  • copy(Path, OutputStream)
  • copy(Path, Path, CopyOption...)

2.2. 创建

create 开头的方法, 可以创建文件夹, 文件, 临时文件夹, 临时文件, 链接.

// 创建文件夹
Files.createDirectory(dirPath);
// 创建文件
Files.createFile(filePath);
// 创建链接
Files.createLink(linkPath);
// 创建符号链接
Files.createSymbolicLink(symbolicPath);
// 在系统的临时文件夹创建临时文件夹
Files.createTempFile(tempFilePrefix, tempFileSufix);
// 在指定文件夹下创建临时文件
Path dirPath = ...
Files.createTempFile(dirPath, tempFilePrefix, tempFileSufix);
// 在系统的临时目录创建临时文件夹
Files.createTempDirectory(tempDirPrefix);
// 在指定文件夹下创建临时文件夹
Files.createTempDirectory(dirPath, tempDirPrefix);

2.3. 删除

delete开头的方法, 可以删除文件和空文件夹, 但是不能删除有文件的文件夹.

如果要递归地删除文件夹和文件, 需要配合walkFileTree:

// 删除
Files.delete(filePath);
// 递归删除
Files.walkFileTree(
    Path.of("./dirToRemove"),
    new SimpleFileVisitor<Path>() {
      @Override
      public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
          throws IOException {
        Files.delete(file);
        return FileVisitResult.CONTINUE;
      }

      @Override
      public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
        Files.delete(dir);
        return FileVisitResult.CONTINUE;
      }
    });

2.4. 判断文件是否存在

exists, notExists, 字面理解即可.

2.4. 查找文件

find 递归地查找文件, 可以指定深度和查找条件, 可以通过FileVisitOption选择关闭跟随符号引用.

需要注意流的关闭.

try (Stream<Path> pathStream =
    Files.find(
        Path.of("./dir"),
        Integer.MAX_VALUE,
        (path, attrs) -> !Files.isDirectory(path) && path.endsWith(".java"))) {
  pathStream.forEach(System.out::println);
}

2.5. 获取, 检查文件的属性或状态

getis 开头的各种方法.

size 可以获取文件的大小.

2.6. 直接将一个字符文件按行读入

lines 方法, 返回 Straem<String>, 比较简单的按行读入的方法.

Stream<String> lines = Files.lines(path);
lines.map(..).forEach(...);

2.7. 返回两个文件第一个不匹配的字节的位置

mismatch.

对于文件 f, mismatch(f,f) 始终返回 -1.

对于文件 fg, mismatch(f,g)mismatch(g,f) 相同.

2.8. 移动(重命名)文件

move.

示例:

// 重命名
Path source = ...
Files.move(source, source.resolveSibling("newname"));
// 移动并替换已存在文件
Path source = ...
Path newdir = ...
Files.move(source, newdir.resolve(source.getFileName()), REPLACE_EXISTING);

2.9. 新建输入和输出

new 开头的各种方法. 可以创建 BufferedReader, BufferedWriter, InputStream, OutputStream, ByteChannel.

newDirectoryStream用于创建可以遍历文件夹的 DirectoryStream.

示例:

// 遍历特定后缀的文件
List<Path> listSourceFiles(Path dir) throws IOException {
    List<Path> result = new ArrayList<>();
    try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.{c,h,cpp,hpp,java}")) {
        for (Path entry: stream) {
            result.add(entry);
        }
    } catch (DirectoryIteratorException ex) {
        // I/O error encountered during the iteration, the cause is an IOException
        throw ex.getCause();
    }
    return result;
}
// 遍历自定义条件的文件
Path dir = ...
try (var stream =
        Files.newDirectoryStream(dir, path -> Files.size(path) > 1000L)) {
    
}

2.10. 探测文件类型

probeContentType. 返回的是MIME字符串, 例如: image/jpg, text/plain 等等, 无法探测到结果时返回null.

依赖平台特定实现.

2.11. 读取文件内容, 属性, 链接

read.

// 读取所有字节
byte[] bytes = Files.readAllBytes(Path.of("./file.txt"));
// 读取所有行
List<String> lines = Files.readAllLines(Path.of("./file.txt"));
// 指定编码
List<String> lines = Files.readAllLines(Path.of("./file.txt"), StandardCharsets.UTF_8);
// 读取文件属性
Path path = ...
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
Map<String,Object> attrMap = Files.readAttributes(path, "*");
// 以字符串读入
String content = Files.readString(path);
// 指定编码
String content = Files.readString(path, StandardCharsets.UTF_8);
// 读取符号链接
Path path = Files.readSymbolicLink(path);

2.12. 设置文件属性, 修改时间, 所有者, 权限等.

set.

Path path = ...
// 设置dos的hidden属性
Files.setAttribute(path, "dos:hidden", true);
// 设置最后修改时间
FileTime now = FileTime.fromMillis(System.currentTimeMillis());
Files.setLastModifiedTime(path, now);
// 设置owner
UserPrincipalLookupService lookupService =
  provider(path).getUserPrincipalLookupService();
UserPrincipal joe = lookupService.lookupPrincipalByName("joe");
Files.setOwner(path, joe);
// 设置Posix的文件权限 (static import PosixFilePermission.*)
Files.setPosixFilePermissions(path, EnumSet.of(OWNER_READ, OWNER_WRITE, GROUP_READ));

2.13. 遍历文件树

walk.

walk 方法与 walkFileTree 不同, walk 直接返回 Stream<Path>, 而 walkFileTree 通过回调设置如何处理文件.

walk 方法返回的Stream<Path> 建议配合 try-with-resource 或类似机制确保流关闭, 如果流不关闭对目录的访问也不会关闭.

// 遍历输出所有文件的文件名
try (Stream<Path> paths = Files.walk(Path.of("./dir"))) {
  paths.filter(Files::isRegularFile)
      .map(Path::getFileName)
      .map(Path::toString)
      .forEach(System.out::println);
}
// 遍历深度两层
try (Stream<Path> paths = Files.walk(Path.of("./dir"), 2)) {
  paths.filter(Files::isRegularFile)
      .map(Path::getFileName)
      .map(Path::toString)
      .forEach(System.out::println);
}

// 递归删除文件和文件夹
Path start = ...
Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
  @Override
  public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
      throws IOException
  {
      Files.delete(file);
      return FileVisitResult.CONTINUE;
  }
  @Override
  public FileVisitResult postVisitDirectory(Path dir, IOException e)
      throws IOException
  {
      if (e == null) {
          Files.delete(dir);
          return FileVisitResult.CONTINUE;
      } else {
          // directory iteration failed
          throw e;
      }
  }
});

// 递归复制文件夹和文件
final Path source = ...
final Path target = ...

Files.walkFileTree(source, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE,
  new SimpleFileVisitor<Path>() {
      @Override
      public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
          throws IOException
      {
          Path targetdir = target.resolve(source.relativize(dir));
          try {
              Files.copy(dir, targetdir);
          } catch (FileAlreadyExistsException e) {
               if (!Files.isDirectory(targetdir))
                   throw e;
          }
          return CONTINUE;
      }
      @Override
      public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
          throws IOException
      {
          Files.copy(file, target.resolve(source.relativize(file)));
          return CONTINUE;
      }
  });

2.14. 写文件

write. 可以通过参数的 OpenOption 设置写文件的选项, 一般实现是StandardOpenOption

Path path = Path.of("./file.txt");
// 写字节
Files.write(path, bytes);
// 写字符串
Files.writeString(path, "hello");
// 写多个字符串
Files.write(path, List.of("hello", " ", "world"));
// Append模式
Files.writeString(path, StandardOpenOption.APPEND);
// 设置编码
Files.writeString(path, "你好!", StandardCharsets.UTF_8);

四. java.util.Comparator

Comparator 类本身较少直接使用, 一般配合集合进行排序时用到, 或者在某些sorted集合中使用.

Comparator 本身提供了一些非常有用的函数式编程的组合方法, 可以非常方便的创建需要的 Comparator.

静态方法 comparing 可以从一个 Function 创建Comparator.

其本质是通过 Function 从要比较的对象上获取数据, 然后再进行比较.

thenComparing 可以组合Comparator, 实现多重排序.

image-20240303123838110

示例:

// 指定按照长度排序 (基础类型使用对应的comparing和thenComparing方法)
List<String> strings = ....	
strings.sort(Comparator.comparingInt(String::length));
// 忽略大小写排序
strings.sort(String.CASE_INSENSITIVE_ORDER);
// 按长度排序并忽略大小写
strings.sort(Comparator.comparingInt(String::length).thenComparing(String.CASE_INSENSITIVE_ORDER));
// 按长度排序并忽略大小写(另一种方式, 但会有装箱拆箱)
strings.sort(Comparator.comparing(String::length, String.CASE_INSENSITIVE_ORDER));
// 反序排序
strings.sort(Comparator.reverseOrder());
// 忽略大小写反序排序
strings.sort(String.CASE_INSENSITIVE_ORDER.reversed());
// 按年龄再按身高排序 (对于基础类型的比较, 有提供相应的方法, 可以减少泛型带来的装箱拆箱)
List<User> users = ...
users.sort(Comparator.comparingInt(User::age).thenComparingInt(User::height));
// 以email地址排序, null优先
List<User> users = ...
users.sort(Comparator.nullsFirst(Comparator.comparing(User::email)));

值得注意的细节:

  • 如果用于比较的字段有可能为空, 必须使用 nullsFirstnullsLastnull 进行处理. 否则可能出现空指针.
  • 如果用于比较的字段是基础类型, 优先使用对应的 comparingthenComparing 方法, 避免拆箱装箱.

五. 其它

java.util.Base64

提供Base64的编解码.

var encoded = Base64.getEncoder().encodeToString("hello".getBytes());
System.out.println(encoded);
System.out.println(new String(Base64.getDecoder().decode(encoded)));

java.uitl.HexFormat

jdk17及以上可用.

提供格式化16进制字符串的方法.

示例:

var bytes = new byte[]{ (byte) 0xAB, (byte) 0xCD, (byte) 0xEF };
System.out.println(HexFormat.of().formatHex(bytes)); // abcdef
System.out.println(HexFormat.of().withUpperCase().formatHex(bytes)); // ABCDEF
System.out.println(HexFormat.ofDelimiter(":").withUpperCase().formatHex(bytes)); // AB:CD:EF

java.util.StringJoiner

将序列以分隔符拼接起来, 可以设置前后缀.

示例:

StringJoiner sj = new StringJoiner(":", "[", "]");  
sj.add("George").add("Sally").add("Fred");  
String desiredString = sj.toString();

Collectors.joining 实际上就是使用了该类实现.

对于集合和数组, String类提供了不含前后缀的拼接方法: String.join.

posted @ 2024-03-03 15:11  无以铭川  阅读(198)  评论(0)    收藏  举报