概述

在Java的世界中,null 类型无处不在,不理解它就难以有效使用这门语言。通常情况下,我们直观地认为它代表空或缺乏某个东西,但这并不足以深入探讨。本教程将深入探讨null类型的工作原理以及它与其他类型的关系。

2. 类型是什么?

在回答关于null类型的问题之前,我们需要先定义什么是类型。这是一个复杂的问题,因为有许多不同的定义。对我们最有用的定义是值空间的定义:类型是由它可以持有的可能值集合定义的

例如,如果我们声明一个boolean变量:

boolean valid;

我们声明了一个名为“valid”的变量,它将持有两个可能的值:truefalse。可能值的集合只有两个元素。如果我们声明一个int变量,可能值的集合会大得多,但仍然明确:从-2^31到2^31-1的所有可能数字。

3. null是什么类型?

null是一种特殊的类型,它只有一个可能的值。换句话说,可能值的集合只有一个元素。这个特性使得null类型非常独特。通常,变量的作用是能持有不同的值。null引用只有一个,所以一个null类型的变量只能持有这个特定的引用,除了表示变量存在外,它没有提供任何其他信息。

null类型的一个可用特性是**null引用可以被转换为任何其他引用类型**。这意味着我们可以把它当作特殊常量来处理,它可以是任何非基本类型。实际上,null引用扩展了这些类型的有效可能值范围。

这就解释了为什么我们可以将同一个null引用赋给完全不同的引用类型的变量:

Integer age = null;
List<String> names = null;

这也解释了为什么不能将null值赋给像boolean这样的基本类型变量:

Boolean validReference = null // this works fine
boolean validPrimitive = null // this does not

这是因为null引用可以转换为引用类型,但不能转换为基本类型。boolean变量的可能值集合始终有两个元素。

4. null作为方法参数

让我们看两个简单的例子,它们都有一个参数,但类型不同:

void printMe(Integer number) {
  System.out.println(number);
}

void printMe(String string) {
  System.out.println(string);
}

由于Java的多态性,我们可以这样调用这些方法:

printMe(6);
printMe("Hello");

编译器会理解我们引用的是哪个方法。但是,以下语句会导致编译错误:

printMe(null); // does not compile

为什么会这样?因为null可以转换为StringInteger——编译器不知道该选择哪个方法。

5. NullPointerException

正如我们已经看到的,尽管null本质上是一个独立的、不同的类型,但我们仍然可以将null引用分配给引用类型的变量。如果我们试图使用该变量的一些属性,就好像它不是null一样,我们会得到一个运行时异常——NullPointerException这是因为null引用并不是我们引用的对象类型,不具备我们期望的属性:

String name = null;
name.toLowerCase(); // will cause exception at runtime

在Java 14之前,NullPointerException通常是简短的,只指出错误发生在代码的哪一行。如果那行代码复杂且包含一系列调用,这种信息可能不够具体。然而,从Java 14开始,我们可以依赖所谓的有用的NullPointerException

6. 总结

在这篇文章中,我们详细研究了null类型的工作原理。首先,我们定义了类型,然后发现null类型如何适应这个定义。最后,我们了解了null引用如何可以转换为任何其他引用类型,使其成为我们熟知并使用的工具。