好用但不为人知的API

1.好用的集合工厂方法

  • Collection.of()
    这些集合的of方法让我们代码变得简单,当我们只想展示我们想要展示的列表或者东西不是很多的场景中使用,这些api都是不可变的,我们知道集合工具类已经帮我们提供了转换成不可变的集合的方法unmodifiableXXX,为什么我们还需要List.of?因为它总是会copy原来的list到新的list,它不是很有效率,
1
2
3
4
5
6
7
8
@Test
public void testCollectionOf() {
// 返回不可变的集合,且不能包含null,所以我们遍历使用的时候不用判断空值
List<String> names = List.of("zs", "ls", "ww");
// names.add("zl"); error
Set<String> of = Set.of("zl", "zq");
Map<String, Integer> users = Map.of("zs", 23, "ls", 24);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void testList() {
ArrayList<String> nameList = new ArrayList<>() {{
add("zs");
add("ls");
add("ww");
}};
// 转成不可修改list
List<String> unmodifiableList = Collections.unmodifiableList(nameList);
// 不可修改
// unmodifiableList.add("zl");
List<String> names = List.of("zs", "ls", "ww");
// 不可修改
// names.add("zl");
}
  • Stream.toList()
    帮我们返回一个不可变的list,但它绝对不是collect(Collectors.toList())的替换,它有比collect(Collectors.toList())更好的优化,默认返回一个不可变的list,而前者返回的是一个arraylist
1
2
3
4
5
6
7
8
9
@Test
@SneakyThrows
public void testList() {
List<String> names = List.of("zs", "ls", "ww");
List<String> collectList = names.stream().collect(Collectors.toList());
collectList.add("zl");
List<String> toList = names.stream().toList();
// toList.add("zl");
}
1
2
3
default List<T> toList() {
return (List<T>) Collections.unmodifiableList(new ArrayList<>(Arrays.asList(this.toArray())));
}
  • List.copyOf()
    返回一个不可变的集合副本,并且不能接受集合中的空值
    1
    2
    3
    4
    5
    6
    7
    @Test
    @SneakyThrows
    public void testCopy(){
    List<String> names = Arrays.asList("zs", "ls", "ww");
    List<String> names_bak = List.copyOf(names);
    // names_bak.add("aaa");
    }

2.HTTPclient

有时候我们需要做一个小项目,为了尽量减小项目的体量和避免外部依赖的侵入性我们可以用过内置的api进行接口调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void testHttpClient() throws Exception {
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(3))
.followRedirects(HttpClient.Redirect.ALWAYS)
.build();
HttpRequest request = HttpRequest.newBuilder()
.GET().uri(URI.create("http://www.baidu.com"))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
String body = response.body();
log.info("response: {}", body);
} else {
log.warn("request error, status code {}", response.statusCode());
}
}

3.null in java

java的null变量是可以隐藏在任何类型背后的,而NullPointException是我们最讨厌的报错,所以我们需要在程序中花费大量的时间去进行null的判断,当方法返回null的时候我们都需要花费大量的精力去判断这是有意的缺失还是故障状态,所以我们应该善用jdk帮我们提供的api。

  • 判断变量是否是null或者不是null

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Test
    public void testNull() {
    Object obj = null;
    if (Objects.isNull(obj)){
    log.warn("variable obj is null");
    }
    var str = "hello";
    if (Objects.nonNull(str)){
    log.info("variable str not null");
    }
    Objects.requireNonNull(obj); // throw NullPointerException
    }
  • 当我们自己的方法有可能返回null值的时候我们应该使用Optional进行封装,这样在语义上就能立刻知道它有可能存在null的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void testOptional() {
String obj = null;
// 创建
Optional<String> aaa = Optional.of("aaa");
Optional<String> bbb = Optional.ofNullable(obj);
// 判断是否有值
if (aaa.isPresent()) {
log.info("aaa not null, value is {}", aaa.get());
}
// 获取,如果是null返回default
String result = Optional.ofNullable(obj).orElse("bbb");
log.info("default value is {}", result);
// 获取,如果是null抛出指定异常
result = bbb.orElseThrow(IllegalArgumentException::new);
}

4.String类新方法

Java11之前我们想要对String进行一些判空操作的时候往往会引入Commons-lang3包,因为自带的String方法实在太少了,而Java11之后String有新增一些比较实用的方法

  • isBlank():如果字符串为空或仅包含空格代码点,则此方法返回true。
  • lines():此方法返回从字符串中提取的行流,并用\ n,\ r等行终止符分隔。
  • strip(),stripLeading(),stripTrailing() :这些方法用于从字符串中去除空格。 顾名思义, strip()将删除前导和尾随空格。 但是, stripLeading()将仅删除前导空格,而stripTrailing()将仅删除尾随空格
  • repeat(int n) :此方法返回一个新字符串,该字符串的值是该字符串的重复n次的串联
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Test
    public void testStr() {
    var blank = " ";
    assert blank.isBlank();
    var str = " hello world ";
    assert Objects.equals(str.strip(), "hello world");
    assert Objects.equals(str.stripLeading(), "hello world ");
    assert Objects.equals(str.stripTrailing(), " hello world");
    assert Objects.equals(str.repeat(2), " hello world hello world ");
    }

4.java基于值类型 (Value Types)

在Java11中明确定义了基于值的类型,值类型是一种数据类型,旨在按值传入和传出方法,并按值存储在数据结构中。
基于值的类是Container类,是基于值的类型的包装器,其中基于值的类的实例遵循以下原则(来自Java文档):

  • 该类是final的(尽管可能包含对可变对象的引用),并且只定义final字段,并且不存在继承或者实现
  • 具有equals,hashCode和toString的实现,这些实现仅根据实例的状态而不是根据其标识或任何其他对象或变量的状态进行计算
  • 没有可访问的构造函数(或者明确该构造以后会删除,所以String不满足该条件),而是通过工厂方法实例化的,这些方法不承诺返回实例的身份
  • 不使用身份敏感的操作,例如实例之间的引用相等(==),而是基于equals被视为相等,因为上边不保证生成相同或者不同的实例
  • 在相等时可以自由替换,这意味着在任何计算或方法调用中互换等于equals()的任意两个实例x和y都不会在行为上产生任何可见的变化。
  • 通过工厂方法获取的实例不保证每次都是不同的实例(integer就存在缓存-128-127之间只生成一份)

显而易见Java语言直接支持的唯一值类型是八种原始类型和Optional

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Deprecated(since="9", forRemoval = true)
public Integer(int value) {
this.value = value;
}

public static Integer valueOf(String s) throws NumberFormatException {
return Integer.valueOf(parseInt(s, 10));
}

@IntrinsicCandidate
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

5.AutoCloseable

我们都知道Java的Object对象有一个finalize方法,所以我们所有的对象都会存在该方法并且我们可以重写它(继承带来的弊端)。该方法会对象不存在引用的时候进行垃圾回收时执行一次,通常我们会实现该方法在该方法中释放IO或者链接或者资源,但是它不是一个线程安全的操作,而且我们永远也不知道它什么时候会被调用(假设在没有引用之前它先晋升到了老年代,那么资源就挂在了老年代,很恐怖的事情),它也会给垃圾回收器带来负担,因为处理它会增加时间,也不能保证调用它的时候会不会出现线程安全问题。比如我们现在有一个对象它有一个ArrayList字段,我们想要在对象垃圾回收的时候释放list,我们都知道释放list需要调用list的clear方法,其实我们都知道ArrayList不是一个线程安全的集合,如果真的这样做很可能会出现并发修改的异常,而且因为是重写了finalize方法所以我们不可能对方法加锁,所以jdk官方也认识到了这一点在jdk9版本将该方法设置为过时的方法了,我们可以通过实现AutoCloseable接口通过try with语法去操作资源并保证资源释放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Data
@Slf4j
public class DataSourceConnection implements AutoCloseable{

private String url;
private String connection;

public DataSourceConnection(String url) {
this.url = url;
this.connection = url + "conn";
}

@Override
public void close() throws Exception {
// TODO:
log.info("关闭链接,释放资源====>");
}
}
1
2
3
4
5
6
7
@Test
public void testClose() throws Exception {
try (DataSourceConnection connection = new DataSourceConnection("rul")) {
String conn = connection.getConnection();
// TODO:
}
}

6.break label

其实这个也不算是api了,它只能算是一个大家都没注意的语法特性,Java没有像c中的goto语句,想要实现任意的循环跳转就要使用label
我们都知道break和continue分别是结束当且循环和结束这一轮循环,如果是多层for循环嵌套的话就不能直接退出到指定的循环层级中,所以我们这里借助lable实现该功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
if (j == 3) {
break; // continue;
}
System.out.print("" + i + j + " ");
}
System.out.println();
}
System.out.println("This is end");
//00 01 02
//10 11 12
//20 21 22
//30 31 32
//40 41 42
//This is end

//00 01 02 04
//10 11 12 14
//20 21 22 24
//30 31 32 34
//40 41 42 44
//This is end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
for (int i = 0; i < 5; i++) {
Label_inner: // 其实默认break和continue就是在这层,所以写不写都一样
for (int j = 0; j < 5; j++) {
if (j == 3) {
//break Label_inner;
continue Label_inner;
}
System.out.print("" + i + j + " ");
}
System.out.println();
}
System.out.println("This is end");

//00 01 02
//10 11 12
//20 21 22
//30 31 32
//40 41 42
//This is end

//00 01 02 04
//10 11 12 14
//20 21 22 24
//30 31 32 34
//40 41 42 44
//This is endinner
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Label_outer:
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
if (j == 3) {
break Label_outer;
//continue Label_outer;
}
System.out.print("" + i + j + " ");
}
System.out.println();
}
System.out.println("This is end");

// 00 01 02 This is end
// 00 01 02 10 11 12 20 21 22 30 31 32 40 41 42 This is end

7.Predicate断言型函数接口

Predicate我们会经常使用,因为Java8只从有了stream流之后我们经常使用filter方法进行集合的过滤筛选,有很多内置api帮我们提供了方法引用符合我们使用场景的时候只需引用就行,但是因为是方法引用所以它不支持非(操作)或者组合操作,这个时候就可以使用Predicate内置的方法帮我们进行增强了,如下

1
2
3
4
5
6
7
@Test
public void testPredicate() {
List<List<?>> list1 = List.of(List.of("zz"), Collections.emptyList());
// 我们想要过滤list内部不为空的集合
List<List<?>> list2 = list1.stream().filter(Predicate.not(List::isEmpty)).toList();
System.out.println(list2);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void testPredicate() {
List<String> names = List.of("zs", "ls", "ww", "lxy", "ywj", "suaf");
// 过滤名字长度小于等于3,且不以W开头的
List<String> names2 = names.stream().filter(s -> {
if (!s.isBlank() && s.length() <= 3 && !s.startsWith("w")) {
return true;
} else {
return false;
}
}).toList();
Predicate<String> isNotStartsWith = s -> !s.startsWith("w");
Predicate<String> shorterThan4 = s -> s.length() <= 3;
List<String> names3 = names.stream().filter(Predicate.not(String::isBlank).and(isNotStartsWith).and(shorterThan4)).toList();
assert names3.equals(names2);
}

8.Comparator比较器

Comparator比较器我们会经常使用,因为Java8自从有了stream流之后我们经常使用sort方法进行集合的排序,有很多内置api帮我们提供了默认的比较方法,在符合我们使用场景的时候只需使用方法引用就行,但是因为是方法引用所以它不支持倒序或者多值比较什么的,如下:

1
2
3
4
5
6
7
8
@Test
public void testComparable() {
List<String> names = Arrays.asList("zhangsan", "lisi", "wangwu", "zhaoliu", "zhouqi");
// 我不认为这是一个很好的方法,也许有一个不接受任何参数的重载方法更好
// 如果指定的比较器是 null ,则此列表中的所有元素都必须实现接口Comparable并且应使用元素的自然顺序,string正好实现了Comparable
names.sort(null);
names.forEach(System.out::println);
}
1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testComparator() {
List<String> names = Arrays.asList("zhangsan", "lisi", "wangwu", "zhaoliu", "zhouqi");
// 按照字符串长度排序
// 书写法则:if o1 > o2 then (o1, o2) > 0
// 书写法则:if o1 < o2 then (o1, o2) < 0
// 书写法则:if o1 = o2 then (o1, o2) = 0
// Comparator<String> comparator = ((o1, o2) -> o1.length() - o2.length();
Comparator<String> comparator = Comparator.comparingInt(String::length);
names.sort(comparator);
names.forEach(System.out::println);
}
1
2
3
4
5
6
7
8
9
10
11
@Test
public void testComparator() {
List<Integer> numbers = List.of(123, 21, 32, 125, 1, 321);
// 升序
List<Integer> list = numbers.stream().sorted(Integer::compare).toList();
System.out.println("list = " + list);
// 降序
Comparator<Integer> comparator = Integer::compare;
List<Integer> list2 = numbers.stream().sorted(comparator.reversed()).toList();
System.out.println("list2 = " + list2);
}
1
2
3
4
5
6
7
8
@Test
public void testComparator() {
List<String> names = List.of("zs", "ls", "ww", "lxy", "ywj", "suaf");
// 按照名字字符长短排序
Comparator<String> comparing = Comparator.comparing(String::length);
List<String> list1 = names.stream().sorted(comparing).toList();
List<String> list2 = names.stream().sorted(comparing.reversed()).toList();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Data
@AllArgsConstructor
class User {
private String userName;
private Integer age;
}
@Test
public void testComparator23() {
ArrayList<User> users = new ArrayList<>() {{
add(new User("zs", 23));
add(new User("ls", 24));
add(new User("ww", 25));
add(new User("zl", 26));
add(new User("zqq", 26));
add(null);
}};
Comparator<User> comparatorByAge = Comparator.comparing(User::getAge);
Comparator<User> comparatorByName = Comparator.comparing(User::getUserName);
// 先按照age排序。再按照name排序
List<User> list1 = users.stream().sorted(Comparator.nullsLast(comparatorByAge.thenComparing(comparatorByName))).toList();
List<User> list2 = users.stream().sorted(Comparator.nullsFirst(comparatorByAge.thenComparing(comparatorByName).reversed())).toList();
System.out.println("list1 = " + list1);
System.out.println("list2 = " + list2);
}

9.Java shebang

我们一直羡慕python作为脚本语言的时候能直接执行,而一个Java代码你需要先使用Javac编译成class文件,然后再使用Java命令运行,这是一个简单Java脚本最繁琐的地方,好在Java推出了shebang行

1
2
3
4
5
6
# !/path/you java path     --source 17
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
1
./HelloWorld

10.string相关

字符串jdk也一直在优化,曾经不同版本的字符串可能会有点性能差异,但是现在经过优化之后好了很多但是我们还是应该注意使用的时候按照规范使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void testStringJoin() throws Exception {
List<String> string = List.of("a", "b", "c");
String join = "";
for (String s : string) {
// 永远不要在循环内进行这样的字符串拼接,会隐士的创建很多StringBuilder对象
join += s;
}
System.out.println("join = " + join);
StringBuilder sb = new StringBuilder();
for (String s : string) {
sb.append(s);
}
System.out.println("sb.toString() = " + sb);
}
1
2
3
4
5
6
7
8
@Test
public void testNumber2Str() throws Exception {
int num = 1;
Integer num2 = 2;
String num_str = 1 + ""; // 永远不要这样,虽然java8之前这样是最快的
String num_str2 = num2.toString();
String num_str1 = Integer.toString(num);
}

好用但不为人知的API
https://vegetablest.github.io/2022/08/12/好用但不为人知的API/
作者
af su
发布于
2022年8月12日
许可协议