1. 概述
作为 Java 开发者,你可能在某些场景下见过 Void
类型,但又不确定它的用途。
在这篇简短的文章中,我们将了解这个“奇怪”的类,探讨它在什么场景下有用,如何使用,以及在某些情况下如何避免使用它。
2. 什么是 Void
类型
从 JDK 1.1 开始,Java 提供了 Void
类型。✅
它的作用是将 void
返回类型封装为一个类,并提供一个 Class<Void>
类型的常量。
需要注意的是:
Void
类不能被实例化(构造函数是私有的)- 唯一能赋值给
Void
变量的就是null
看起来它似乎没什么用,但在特定场景下还是有其用途的。
3. 使用场景
Void
类型在以下两个场景中比较有用:
3.1. 反射(Reflection)
在反射中,我们可以通过 Void.TYPE
来判断一个方法是否返回 void
。
比如我们有如下 Calculator
类:
public class Calculator {
private int result = 0;
public int add(int number) {
return result += number;
}
public int sub(int number) {
return result -= number;
}
public void clear() {
result = 0;
}
public void print() {
System.out.println(result);
}
}
如果我们想通过反射找出所有返回类型为 void
的方法,可以这样做:
@Test
void givenCalculator_whenGettingVoidMethodsByReflection_thenOnlyClearAndPrint() {
Method[] calculatorMethods = Calculator.class.getDeclaredMethods();
List<Method> calculatorVoidMethods = Arrays.stream(calculatorMethods)
.filter(method -> method.getReturnType().equals(Void.TYPE))
.collect(Collectors.toList());
assertThat(calculatorVoidMethods)
.allMatch(method -> Arrays.asList("clear", "print").contains(method.getName()));
}
测试结果会只包含 clear()
和 print()
方法,说明我们成功筛选出了返回类型为 void
的方法。
3.2. 泛型(Generics)
另一个使用场景是泛型编程。比如我们有如下工具类:
public class Defer {
public static <V> V defer(Callable<V> callable) throws Exception {
return callable.call();
}
}
如果我们想传入一个不返回值的 Callable
,可以使用 Callable<Void>
:
@Test
void givenVoidCallable_whenDiffer_thenReturnNull() throws Exception {
Callable<Void> callable = new Callable<Void>() {
@Override
public Void call() {
System.out.println("Hello!");
return null;
}
};
assertThat(Defer.defer(callable)).isNull();
}
✅ 使用 Void
可以明确表示我们不期望返回值,避免使用 Callable<Integer>
或原始类型 Callable
等模糊表达。
同样的逻辑也适用于 Function
:
public static <T, R> R defer(Function<T, R> function, T arg) {
return function.apply(arg);
}
我们可以这样使用:
@Test
void givenVoidFunction_whenDiffer_thenReturnNull() {
Function<String, Void> function = s -> {
System.out.println("Hello " + s + "!");
return null;
};
assertThat(Defer.defer(function, "World")).isNull();
}
4. 如何避免使用 Void
虽然 Void
在某些泛型场景下有用,但 在大多数情况下我们应尽量避免使用它,因为它带来的 null
返回值容易引发困惑。
替代方案一:使用 Runnable
比如上面的 defer(Callable<Void>)
方法,我们可以提供一个接受 Runnable
的重载:
public static void defer(Runnable runnable) {
runnable.run();
}
这样调用者就无需再写 return null
:
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Hello!");
}
};
Defer.defer(runnable);
替代方案二:自定义函数式接口
如果原类无法修改,可以封装一个适配器类:
public class MyOwnDefer {
public static void defer(Runnable runnable) throws Exception {
Defer.defer(new Callable<Void>() {
@Override
public Void call() {
runnable.run();
return null;
}
});
}
}
这样,Void
的使用就被封装在内部,外部 API 更加清晰。
替代方案三:使用 Consumer
对于不返回值的 Function
,可以用 Consumer
替代:
public static <T> void defer(Consumer<T> consumer, T arg) {
consumer.accept(arg);
}
如果函数没有参数,还可以自定义一个 Action
接口:
public interface Action {
void execute();
}
然后提供一个重载:
public static void defer(Action action) {
action.execute();
}
调用示例:
Action action = () -> System.out.println("Hello!");
Defer.defer(action);
5. 总结
在这篇文章中,我们了解了 Java 中的 Void
类型:
- 它是一个特殊的类,用于表示
void
返回类型 - 主要用于反射和泛型编程中
- 但在实际开发中应尽量避免使用它,优先使用
Runnable
、Consumer
等更清晰的替代方案
如果你在项目中看到 Void
类型的使用,不妨思考一下是否有更优雅的替代方式。这样不仅代码更清晰,也更容易维护。
完整示例代码可以参考 GitHub 仓库。