Java中Void引用类型的用途是什么?

172

Java中有一个名为Void的类型,注意是大写的V,它是一个引用类型。我只看到过一种情况使用它,那就是将其作为Callable的参数。

final Callable<Void> callable = new Callable<Void>() {
            public Void call() {
                foobar();
                return null;
            }
        };

Java中的Void引用类型还有其他用途吗? 它是否可以被赋予除null之外的任何值? 如果是,你有例子吗?


https://dzone.com/articles/why-does-void-class-exist-jdk - Kanagavelu Sugumar
在Java8中,有新的功能:使用Function接口替换Consumer或Supplier - puppylpg
11个回答

126

Void已经成为表示你不感兴趣的通用参数的惯例。没有理由使用任何其他不可实例化类型,比如System

在例如Map值(尽管Collections.newSetFromMap使用Boolean,因为映射不必接受null值)和java.security.PrivilegedAction中也经常使用它。


那个规定是谁制定的?文档中说明http://docs.oracle.com/javase/tutorial/java/generics/types.html,“类型参数命名约定按照惯例,类型参数名称为单个大写字母。” - barlop
4
@barlop 是关于参数名的讨论,而不是参数类型。即,它是类型为 java.lang.Void。Josh Bloch 推广了这个约定,但一旦你看到它,它就是显而易见的选择。 - Tom Hawtin - tackline
6
那篇博客已经过时了。 - Michel Feinstein

56

你可以使用反射创建 Void 类型的实例,但它们并没有什么用处。Void 是一种表示泛型方法不返回任何值的方式。

Constructor<Void> constructor = Void.class.getDeclaredConstructor();
constructor.setAccessible(true);
Void v = constructor.newInstance();
System.out.println("I have a " + v);

打印出类似于

I have a java.lang.Void@75636731

26
+1 表示实例化一个文档中标明不可实例化的类。我也这样做过,认同实例化 Void 类是没用的。 - Luke Woodward
1
Constructor<Void> cv = Void.class.getDeclaredConstructor(); cv.setAccessible(true); Void v = cv.newInstance(); System.out.println(v); // ;) - Peter Lawrey
7
这是具体实现的内容,可能随时会发生变化。不能保证该类具有无参构造函数,即使有一个也可能执行任何操作(例如System.exit(0))。我倾向于编写带有构造函数的实用类,如private Void() { throw new Error(); }。有些人可能更喜欢使用无值枚举。 - Tom Hawtin - tackline
@LukeWoodward 在调用构造函数之前,应将isAccessible标志设置为true。 - Snicolas
@Snicolas:我试过了。但是,你不能通过在构造函数上调用setAccessible来实现这一点,因为AccessibleObject.setAccessible方法明确检查您是否尝试使Class构造函数可访问,并且如果您这样做,则会抛出SecurityException。您必须使AccessibleObject中的override字段可访问,然后将其设置为true,以使Class构造函数可访问。如果您尝试调用已经可访问的Class构造函数,则会出现我提到的异常。 - Luke Woodward
显示剩余6条评论

28

6
在 Guava 中通常使用 Future<?>,因为未来值通常具有合法的值(非 Void 类型),但在上下文中不关心该值。 - David Phillips
1
CompletableFuture<Void> 也同样非常好用。 - andrybak

20

所有原始包装类(IntegerByteBooleanDouble等)都包含对应原始类的引用,存储在静态的TYPE字段中。例如:

Integer.TYPE == int.class
Byte.TYPE == byte.class
Boolean.TYPE == boolean.class
Double.TYPE == double.class

Void最初被创建是为了存放指向void类型的引用:

Void.TYPE == void.class

然而,使用Void.TYPE并没有真正带来任何好处。当您使用void.class时,更清晰地表明您正在处理void类型。

另外,我上次尝试时,BeanShell没有识别void.class,因此您必须在那里使用Void.TYPE


8
有一个 Void.class 和一个 void.class! - Tom Anderson

20
考虑到没有公共构造函数,我认为它不能被赋予除null以外的任何值。正如你的示例所示,我仅将其用作“我不需要使用此泛型参数”的占位符。
根据它的Javadoc所说,它还可以用于反射:

Void类是一个无法实例化的占位符类,用于持有表示Java关键字void的Class对象的引用。


真有趣,看到Oracle/Sun这样描述null,因为null是所有类型的实例。无论如何,就像你所说的,它的纯意义是“我可以在这里写任何类型,因为我对它不感兴趣,所以我会放置Void,以确保读我的代码的人明确了解这一点”。 - Snicolas

11
当您使用访问者模式时,如果您想确保返回值为null,则使用Void而不是Object可能会更加清晰。
示例
public interface LeavesVisitor<OUT>
{
   OUT visit(Leaf1 leaf);

   OUT visit(Leaf2 leaf);
}
当您实现访问者时,您可以显式地将 OUT 设置为 Void,以便您知道您的访问者将始终返回 null,而不是使用 Object。
public class MyVoidVisitor implements LeavesVisitor<Void>
{
    Void visit(Leaf1 leaf){
        //...do what you want on your leaf
        return null;
    }

    Void visit(Leaf2 leaf){
        //...do what you want on your leaf
        return null;
    }
}

6
在泛型出现之前,它是为反射API创建的,用于保存Method.getReturnType()返回的void方法对应的TYPE,相应地对应其他原始类型类。
编辑:从Void的JavaDoc中可以看到:“Void类是一个不可实例化的占位符类,用于保存表示Java关键字void的Class对象的引用”。在泛型出现之前,我知道除了反射之外没有其他用途。

我不相信这个。我现在面前没有1.4或更早的JVM,但我相信Method.getReturnType()对于void方法始终返回void.class。 - Luke Woodward
@Pour:我想说的是,在泛型出现之前,我所知道的唯一用途就是保存类型(例如Void.TYPE),它被用于反射中的Method.getReturnType()来获取一个void方法的返回类型。 - Lawrence Dol
它不知何故没有返回 Void。请参阅 http://stackoverflow.com/questions/34248693/use-instanceof-void-in-java - Alireza Fattahi

3

由于无法实例化Void,因此您可以使用Apache commons Null对象,所以

Null aNullObject = ObjectUtils.Null;
Null noObjectHere = null;

在第一行中,您有一个对象,因此 aNullObject != null 成立,而在第二行中没有引用,因此 noObjectHere == null 成立。
回答原帖问题的用途是区分“nothing”和“the nothing”,它们是完全不同的东西。
PS:拒绝 Null 对象模式。

2

Void 用来包装原始类型 void。每种原始类型都有相应的引用类型。在实例化泛型类或使用泛型方法时,可以使用 Void 作为不感兴趣的泛型参数。以下是一个示例...

public void onNewRegistration() {
    newRegistrationService.createNewUser(view.getUsername(), view.getPassword(),
            view.getInitialAmount(), view.getCurrency(), new AsyncCallback<Void>() {
      @Override
      public void onFailure(Throwable caught) {
          
      }

      @Override
      public void onSuccess(Void result) {
        eventBus.fireEvent(new NewRegistrationSuccessEvent());
      }
    });
  } 

如您所见,在我请求创建新注册时,我不需要从服务器获取任何东西,但public interface AsyncCallback<T> { .... }是一个泛型接口,因此我提供Void,因为泛型不接受原始类型。


0

这可能是一个罕见的情况,但我曾经在切面类中使用过 Void

这是一个在带有 @Log 注释的方法之后运行的切面,如果方法返回类型不是 void,则记录方法返回和一些信息。

 @AfterReturning(value = "@annotation(log)", 
       returning = "returnValue", 
       argNames = "joinPoint, log, returnValue"
      )
    public void afterReturning(final JoinPoint joinPoint, final Log log,
            final Object returnValue) {

            Class<?> returnType = ((MethodSignature) joinPoint.getSignature())
            .getReturnType();
           if (Void.class.isAssignableFrom (returnType)) ) {
            //Do some log
         }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接