目录
- java中一些工具类
java中一些工具类
一. java.util.Objects
类
1. 介绍
该类自jdk1.7引入, 此类包含用于对对象进行操作或在操作前检查某些条件的static实用程序方法.
例如: hashcode计算
, 对象比较
, 判空
, toString
, 索引范围检查
等等.
相比于我们常见的使用表达式, 静态方法可以更好的配合函数式编程.
2. 方法
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. 方法
内部方法主要有:
-
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
使用需要注意的细节:
-
singleton
,empty
,unmodifiable
,nCopies
方法所创建的集合视图是不可变的. -
copy
其实是替换目标列表中的元素为源列表中的元素, 当目标列表的size
小于源列表会索引越界.var dest = new ArrayList<String>(); // IndexOutOfBoundsException: Source does not fit in dest Collections.copy(dest, List.of("abc"));
-
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. 方法
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. 获取, 检查文件的属性或状态
get
和 is
开头的各种方法.
size
可以获取文件的大小.
2.6. 直接将一个字符文件按行读入
lines
方法, 返回 Straem<String>
, 比较简单的按行读入的方法.
Stream<String> lines = Files.lines(path);
lines.map(..).forEach(...);
2.7. 返回两个文件第一个不匹配的字节的位置
mismatch
.
对于文件 f
, mismatch(f,f)
始终返回 -1
.
对于文件 f
和g
, 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
, 实现多重排序.
示例:
// 指定按照长度排序 (基础类型使用对应的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)));
值得注意的细节:
- 如果用于比较的字段有可能为空, 必须使用
nullsFirst
或nullsLast
对null
进行处理. 否则可能出现空指针. - 如果用于比较的字段是基础类型, 优先使用对应的
comparing
和thenComparing
方法, 避免拆箱装箱.
五. 其它
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
.