# Java 数据类型 II

原文：http://zetcode.com/lang/java/datatypes2/

在 Java 教程的这一部分中，我们将继续介绍 Java 的数据类型。 我们介绍了包装器类，装箱和拆箱，默认值，转换和促销。

## Java 包装器类

包装器类是原始数据类型的对象表示。 需要`Object`时，包装器类用于表示原始值。 例如，Java 集合仅适用于对象。 它们不能采用原始类型。 包装器类还包括一些有用的方法。 例如，它们包括进行数据类型转换的方法。 将原始类型放入包装器类称为`boxing`。 反向过程称为`unboxing`。

通常，在有某种理由的情况下，我们使用包装器类。 否则，我们使用原始类型。 包装器类是不可变的。 创建它们后，就无法更改它们。 基本类型比装箱类型更快。 在科学计算和其他大规模数字处理中，包装类可能会严重影响性能。

| 原始类型 | 包装类 | 构造器参数 |
| --- | --- | --- |
| `byte` | `Byte` | `byte`或`String` |
| `short` | `Short` | `short`或`String` |
| `int` | `Integer` | `int`或`String` |
| `long` | `Long` | `long`或`String` |
| `float` | `Float` | `float`，`double`或`String` |
| `double` | `Double` | `double`或`String` |
| `char` | `Char` | `char` |
| `boolean` | `Boolean` | `boolean`或`String` |

Table: Primitive types and their wrapper class equivalents

`Integer`类将原始类型`int`的值包装在对象中。 它包含在处理`int`时有用的常量和方法。

`com/zetcode/IntegerWrapper.java`

```java
package com.zetcode;

public class IntegerWrapper {

    public static void main(String[] args) {

        int a = 55;
        Integer b = new Integer(a);

        int c = b.intValue();
        float d = b.floatValue();

        String bin = Integer.toBinaryString(a);
        String hex = Integer.toHexString(a);
        String oct = Integer.toOctalString(a);

        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
        System.out.println(d);

        System.out.println(bin);
        System.out.println(hex);
        System.out.println(oct);
    }
}

```

本示例适用于`Integer`包装器类。

```java
int a = 55;

```

该行创建一个整数原始数据类型。

```java
Integer b = new Integer(a);

```

`Integer`包装器类是从原始`int`类型创建的。

```java
int c = b.intValue();
float d = b.floatValue();

```

`intValue()`方法将`Integer`转换为`int`。 同样，`floatValue()`返回`float`数据类型。

```java
String bin = Integer.toBinaryString(a);
String hex = Integer.toHexString(a);
String oct = Integer.toOctalString(a);

```

这三种方法返回整数的二进制，十六进制和八进制表示形式。

```java
$ java IntegerWrapper.java
55
55
55
55.0
110111
37
67

```

这是程序输出。

集合是用于处理对象组的强大工具。 原始数据类型不能放入 Java 集合中。 将原始值装箱后，可以将它们放入集合中。

`com/zetcode/Numbers.java`

```java
package com.zetcode;

import java.util.ArrayList;
import java.util.List;

public class Numbers {

    public static void main(String[] args) {

        List<Number> ls = new ArrayList<>();

        ls.add(1342341);
        ls.add(new Float(34.56));
        ls.add(235.242);
        ls.add(new Byte("102"));
        ls.add(new Short("1245"));

        for (Number n : ls) {

            System.out.println(n.getClass());
            System.out.println(n);
        }
    }
}

```

在示例中，我们将各种数字放入`ArrayList`中。 `ArrayList`是动态的，可调整大小的数组。

```java
List<Number> ls = new ArrayList<>();

```

创建一个`ArrayList`实例。 在尖括号中，我们指定容器将容纳的类型。 `Number`是 Java 中所有五个数字基本类型的抽象基类。

```java
ls.add(1342341);
ls.add(new Float(34.56));
ls.add(235.242);
ls.add(new Byte("102"));
ls.add(new Short("1245"));

```

我们将五个数字添加到集合中。 请注意，整数和双精度值未装箱； 这是因为对于整数和双精度类型，编译器将执行自动装箱。

```java
for (Number n : ls) {

    System.out.println(n.getClass());
    System.out.println(n);
}

```

我们遍历容器并打印类名称及其每个元素的值。

```java
$ java Numbers.java
class java.lang.Integer
1342341
class java.lang.Float
34.56
class java.lang.Double
235.242
class java.lang.Byte
102
class java.lang.Short
1245

```

`com.zetcode.Numbers`程序给出该输出。 请注意，这两个数字是由编译器自动装箱的。

## Java 装箱

从原始类型转换为对象类型称为`boxing`。 `Unboxing`是相反的操作。 它正在将对象类型转换回原始类型。

`com/zetcode/BoxingUnboxing.java`

```java
package com.zetcode;

public class BoxingUnboxing {

    public static void main(String[] args) {

        long a = 124235L;

        Long b = new Long(a);
        long c = b.longValue();

        System.out.println(c);
    }
}

```

在代码示例中，我们将`long`值放入`Long`对象中，反之亦然。

```java
Long b = new Long(a);

```

该行执行拳击。

```java
long c = b.longValue();

```

在这一行，我们进行拆箱。

## Java 自动装箱

Java 5 引入了自动装箱。 `Autoboxing`是原始类型及其对应的对象包装器类之间的自动转换。 自动装箱使编程更加容易。 程序员不需要手动进行转换。

当一个值是原始类型而另一个值是包装器类时，将执行自动装箱和拆箱：

*   赋值
*   将参数传递给方法
*   从方法返回值
*   比较操作
*   算术运算

```java
Integer i = new Integer(50);

if (i < 100) {
   ...
}

```

在`if`表达式的方括号内，将`Integer`与`int`进行比较。 `Integer`对象被转换为原始`int`类型，并与 100 值进行比较。 自动取消装箱。

`com/zetcode/Autoboxing.java`

```java
package com.zetcode;

public class Autoboxing {

    private static int cube(int x) {

        return x * x * x;
    }

    public static void main(String[] args) {

        Integer i = 10;
        int j = i;

        System.out.println(i);
        System.out.println(j);

        Integer a = cube(i);
        System.out.println(a);
    }
}

```

此代码示例演示了自动装箱和自动拆箱。

```java
Integer i = 10;

```

Java 编译器在此代码行中执行自动装箱。 将`int`值装箱为`Integer`类型。

```java
int j = i;

```

在这里会自动开箱。

```java
Integer a = cube(i);

```

当我们将`Integer`传递给`cube()`方法时，便完成了自动拆箱。 当我们返回计算值时，将执行自动装箱，因为`int`转换回了`Integer`。

Java 语言不支持运算符重载。 当我们对包装类应用算术运算时，自动装箱由编译器完成。

`com/zetcode/Autoboxing2.java`

```java
package com.zetcode;

public class Autoboxing2 {

    public static void main(String[] args) {

        Integer a = new Integer(5);
        Integer b = new Integer(7);

        Integer add = a + b;
        Integer mul = a * b;

        System.out.println(add);
        System.out.println(mul);
    }
}

```

我们有两个`Integer`值。 我们对这两个值执行加法和乘法运算。

```java
Integer add = a + b;
Integer mul = a * b;

```

与 Ruby，C# ，Python，D 或 C++ 等语言不同，Java 没有实现运算符重载。 在这两行中，编译器调用`intValue()`方法，并将包装器类转换为`int`，然后通过调用`valueOf()`方法将结果包装回`Integer`。

## Java 自动装箱和对象内化

`Object intering`仅存储每个不同对象的一个​​副本。 该对象必须是不可变的。 不同的对象存储在内部池中。 在 Java 中，当将原始值装箱到包装对象中时，将插入某些值（任何布尔值，任何字节，0 到 127 之间的任何`char`以及 -128 和 127 之间的任何`short`或`int`），以及这两个值之一的任意装箱转换可以确保得到相同的对象。

根据 Java 语言规范，这些是最小范围。 因此，行为取决于实现。 对象交互可以节省时间和空间。 从字面值，自动装箱和`Integer.valueOf()`中获得的对象是内部对象，而使用`new`运算符构造的对象始终是不同的对象。

比较包装类时，对象交互会产生一些重要的后果。 `==`运算符比较对象的引用标识，而`equals()`方法比较值。

`com/zetcode/Autoboxing3.java`

```java
package com.zetcode;

public class Autoboxing3 {

    public static void main(String[] args) {

        Integer a = 5; // new Integer(5);
        Integer b = 5; // new Integer(5);

        System.out.println(a == b);
        System.out.println(a.equals(b));
        System.out.println(a.compareTo(b));

        Integer c = 155;
        Integer d = 155;

        System.out.println(c == d);
        System.out.println(c.equals(d));
        System.out.println(c.compareTo(d));
    }
}

```

该示例比较了一些`Integer`对象。

```java
Integer a = 5; // new Integer(5);
Integer b = 5; // new Integer(5);

```

两个整数装在`Integer`包装器类中。

```java
System.out.println(a == b);
System.out.println(a.equals(b));
System.out.println(a.compareTo(b));

```

使用三种不同的方法比较这些值。 `==`运算符比较两种盒装类型的引用标识。 由于对象的嵌入，该运算结果为`true`。 如果使用`new`运算符，则将创建两个不同的对象，并且`==`运算符将返回`false`。 `equals()`方法在数值上比较两个`Integer`对象。 它返回布尔值`true`或`false`（在我们的例子中为`true`）。

最后，`compareTo()`方法还对两个对象进行了数值比较。 如果此`Integer`等于参数`Integer`，则返回值 0； 如果此`Integer`在数值上小于参数`Integer`，则该值小于 0； 如果此`Integer`在数值上大于自变量`Integer`，则该值大于 0。

```java
Integer c = 155;
Integer d = 155;

```

我们还有两种盒装类型。 但是，这些值大于内化的最大值（127）； 因此，创建了两个不同的对象。 这次`==`运算符产生`false`。

```java
$ java Autoboxing3.java
true
true
0
false
true
0

```

这是程序的输出。

## Java 空类型

Java 具有特殊的`null`类型。 类型没有名称。 结果，不可能声明`null`类型的变量或将其强制转换为`null`类型。 `null`表示一个空引用，不引用任何对象。 `null`是引用类型变量的默认值。 不能为原始类型分配`null`字面值。

在不同的上下文中，`null`表示不存在对象，未知值或未初始化状态。

`com/zetcode/NullType.java`

```java
package com.zetcode;

import java.util.Random;

public class NullType {

    private static String getName() {

        Random r = new Random();
        boolean n = r.nextBoolean();

        if (n == true) {

            return "John";
        } else {

            return null;
        }
    }

    public static void main(String[] args) {

        String name = getName();

        System.out.println(name);

        System.out.println(null == null);

        if ("John".equals(name)) {

            System.out.println("His name is John");
        }
    }
}

```

我们在程序中使用`null`值。

```java
private static String getName() {

    Random r = new Random();
    boolean n = r.nextBoolean();

    if (n == true) {

        return "John";
    } else {

        return null;
    }
}

```

在`getName()`方法中，我们模拟了一种方法有时可以返回`null`值的情况。

```java
System.out.println(null == null);

```

我们比较两个空值。 表达式返回`true`。

```java
if ("John".equals(name)) {

    System.out.println("His name is John");
}

```

我们将名称变量与`"John"`字符串进行比较。 注意，我们在`"John"`字符串上调用了`equals()`方法。 这是因为如果名称变量等于`null`，则调用该方法将导致`NullPointerException`。

```java
$ java NullType.java
null
true
$ java NullType.java
null
true
$ java NullType.java
John
true
His name is John

```

我们执行该程序三次。

## Java 默认值

编译器会为未初始化的字段提供默认值。 最终字段和局部变量必须由开发者初始化。

下表显示了不同类型的默认值。

| 数据类型 | 默认值 |
| --- | --- |
| `byte` | `0` |
| `char` | `'\u0000'` |
| `short` | `0` |
| `int` | `0` |
| `long` | `0L` |
| `float` | `0f` |
| `double` | `0d` |
| `Object` | `null` |
| `boolean` | `false` |

Table: Default values for uninitialized instance variables

下一个示例将打印未初始化的实例变量的默认值。 实例变量是在类中定义的变量，该类的每个实例化对象都具有一个单独的副本。

`com/zetcode/DefaultValues.java`

```java
package com.zetcode;

public class DefaultValues {

    static byte b;
    static char c;
    static short s;
    static int i;
    static float f;
    static double d;
    static String str;
    static Object o;

    public static void main(String[] args) {

        System.out.println(b);
        System.out.println(c);
        System.out.println(s);
        System.out.println(i);
        System.out.println(f);
        System.out.println(d);
        System.out.println(str);
        System.out.println(o);
    }
}

```

在示例中，我们声明了八个成员字段。 它们未初始化。 编译器将为每个字段设置默认值。

```java
static byte b;
static char c;
static short s;
static int i;
...

```

这些是实例变量； 它们在任何方法外声明。 这些字段被声明为`static`，因为它们是通过`static` `main()`方法访问的。 （在本教程的后面，我们将更多地讨论静态变量和实例变量。）

```java
$ java DefaultValues.java
0

0
0
0.0
0.0
null
null

```

This is the output of the program.

## Java 类型转换

我们经常一次处理多种数据类型。 将一种数据类型转换为另一种数据类型是编程中的常见工作。 术语类型转换是指将一种数据类型的实体更改为另一种。 在本节中，我们将处理原始数据类型的转换。 引用类型的转换将在本章后面提到。 转换规则很复杂； 它们在 Java 语言规范的第 5 章中指定。

转换有两种类型：隐式转换和显式转换。 隐式类型转换，也称为强制，是编译器自动进行的类型转换。 在显式转换中，程序员直接在一对圆括号内指定转换类型。 显式转换称为类型转换。

转换发生在不同的上下文中：赋值，表达式或方法调用。

```java
int x = 456;
long y = 34523L;
float z = 3.455f;
double w = 6354.3425d;

```

在这四个分配中，没有转换发生。 每个变量都被分配了预期类型的​​字面值。

```java
int x = 345;
long y = x;

float m = 22.3354f;
double n = m;

```

在此代码中，Java 编译器隐式执行了两次转换。 将较小类型的变量分配给较大类型的变量是合法的。 该转换被认为是安全的，因为不会损失任何精度。 这种转换称为隐式加宽转换。

```java
long x = 345;
int y = (int) x;

double m = 22.3354d;
float n = (float) m;

```

在 Java 中，将较大类型的变量分配给较小类型是不合法的。 即使值本身适合较小类型的范围。 在这种情况下，可能会降低精度。 为了允许这种分配，我们必须使用类型转换操作。 这样，程序员说他是故意这样做的，并且他意识到可能会丢失一些精度这一事实。 这种转换称为显式变窄转换。

```java
byte a = 123;
short b = 23532;

```

在这种情况下，我们处理一种特定类型的分配转换。 123 和 23532 是整数字面值，`a`，`b`变量为`byte`和`short`类型。 可以使用铸造操作，但不是必需的。 字面值可以在赋值左侧的变量中表示。 我们处理隐式变窄转换。

```java
private static byte calc(byte x) {
...
}
byte b = calc((byte) 5);

```

以上规则仅适用于分配。 当我们将整数字面值传递给需要一个字节的方法时，我们必须执行强制转换操作。

## Java 数字提升

数值提升是隐式类型转换的特定类型。 它发生在算术表达式中。 数字提升用于将数字运算符的操作数转换为通用类型，以便可以执行操作。

```java
int x = 3;
double y = 2.5;
double z = x + y;

```

第三行中有一个加法表达式。 `x`操作数为`int`，`y`操作数为`double`。 编译器将整数转换为双精度值，然后将两个数字相加。 结果是两倍。 这是隐式扩展原始类型转换的情况。

```java
byte a = 120;
a = a + 1; // compilation error

```

此代码导致编译时错误。 在第二行的右侧，我们有一个字节变量`a`和一个整数字面值 1。该变量将转换为整数并添加值。 结果是一个整数。 以后，编译器尝试将值分配给`a`变量。 没有显式的强制转换运算符，就不可能将较大的类型分配给较小的类型。 因此，我们收到一个编译时错误。

```java
byte a = 120;
a = (byte) (a + 1);

```

此代码可以编译。 请注意`a + 1`表达式中使用圆括号。 `(byte)`强制转换运算符的优先级高于加法运算符。 如果要对整个表达式应用转换，则必须使用圆括号。

```java
byte a = 120;
a += 5;

```

复合运算符自动执行隐式转换。

```java
short r = 21;
short s = (short) -r;

```

将`+`或`-`一元运算符应用于变量，即可执行一元数提升。 `short`类型升级为`int`类型。 因此，必须使用强制转换运算符来使分配通过。

```java
byte u = 100;
byte v = u++;

```

如果是一元递增`++`或递减`--`运算符，则不会进行任何转换。 不需要铸造。

## Java 装箱，拆箱转换

装箱转换将原始类型的表达式转换为包装器类型的对应表达式。 拆箱转换将包装器类型的表达式转换为原始类型的相应表达式。 从`boolean`到`Boolean`或从字节到`Byte`的转换是装箱转换的示例。 反向转换，例如从`Boolean`到`boolean`或从`Byte`到`byte`的翻译是取消装箱转换的示例。

```java
Byte b = 124;
byte c = b;

```

在第一行代码中，自动装箱转换由 Java 编译器执行。 在第二行中，完成了拆箱转换。

```java
private static String checkAge(Short age) {
...
}
String r = checkAge((short) 5);

```

在这里，我们在方法调用的上下文中进行装箱转换。 我们将`short`类型传递给需要`Short`包装类型的方法。 该值已装箱。

```java
Boolean gameOver = new Boolean("true");
if (gameOver) {
    System.out.println("The game is over");
}

```

这是拆箱转换的示例。 在`if`表达式内部，调用`booleanValue()`方法。 该方法返回`Boolean`对象的值作为`boolean`原语。

## 对象引用转换

对象，接口和数组是引用数据类型。 任何引用都可以转换为`Object`。 对象类型确定在运行时使用哪种方法。 引用类型确定在编译时将使用哪种重载方法。

接口类型只能转换为接口类型或`Object`。 如果新类型是接口，则它必须是旧类型的超级接口。 可以将类类型转换为类类型或接口类型。 如果要转换为类类型，则新类型必须是旧类型的超类。 如果要转换为接口类型，则旧类必须实现该接口。 数组可以转换为类`Object`，接口`Cloneable`或`Serializable`或数组。

引用变量转换有两种类型：下播和上播。 正在向上转换（泛型或扩展）正在从子类型转换为父类型。 我们正在将单个类型转换为通用类型。 向下转换（专业化或缩小）正在从父类型转换为子类型。 我们正在将通用类型转换为单个类型。

向上转换缩小了对象可用的方法和属性的列表，向下转换可以扩展它。 向上转换是安全的，但是向下转换涉及类型检查，并且可能抛出`ClassCastException`。

`com/zetcode/ReferenceTypeConverion.java`

```java
package com.zetcode;

import java.util.Random;

class Animal {}
class Mammal extends Animal {}
class Dog extends Animal {}
class Cat extends Animal {}

public class ReferenceTypeConversion {

    public static void main(String[] args) {

        // upcasting
        Animal animal = new Dog();
        System.out.println(animal);

        // ClassCastException
        // Mammal mammal = (Mammal) new Animal();

        var returned = getRandomAnimal();

        if (returned instanceof Cat) {

            Cat cat = (Cat) returned;
            System.out.println(cat);
        } else if (returned instanceof Dog) {

            Dog dog = (Dog) returned;
            System.out.println(dog);
        } else if (returned instanceof Mammal) {

            Mammal mammal = (Mammal) returned;
            System.out.println(mammal);
        } else {

            Animal animal2 = returned;
            System.out.println(animal2);
        }
    }

    private static Animal getRandomAnimal() {

        int val = new Random().nextInt(4) + 1;

        Animal anim = switch (val) {

            case 2 -> new Mammal();
            case 3 -> new Dog();
            case 4 -> new Cat();
            default -> new Animal();
        };

        return anim;
    }
}

```

该示例执行引用类型转换。

```java
// upcasting
Animal animal = new Dog();
System.out.println(animal);

```

我们从子类型`Dog`转换为父类型`Animal`。 这是不安全的，并且始终是安全的。

```java
// ClassCastException
// Mammal mammal = (Mammal) new Animal();

```

从`Animal`向下广播到`Mammal`会导致`ClassCastException`。

```java
var returned = getRandomAnimal();

if (returned instanceof Cat) {

    Cat cat = (Cat) returned;
    System.out.println(cat);
} else if (returned instanceof Dog) {

    Dog dog = (Dog) returned;
    System.out.println(dog);
} else if (returned instanceof Mammal) {

    Mammal mammal = (Mammal) returned;
    System.out.println(mammal);
} else {

    Animal animal2 = returned;
    System.out.println(animal2);
}

```

为了执行合法的向下转换，我们需要首先使用`instanceof`运算符检查对象的类型。

```java
private static Animal getRandomAnimal() {

    int val = new Random().nextInt(4) + 1;

    Animal anim = switch (val) {

        case 2 -> new Mammal();
        case 3 -> new Dog();
        case 4 -> new Cat();
        default -> new Animal();
    };

    return anim;
}

```

`getRandomAnimal()`使用 Java 的`switch`表达式返回随机动物。

## Java 字符串转换

在数字和字符串之间执行字符串转换在编程中非常常见。 不允许进行强制转换操作，因为字符串和基本类型在根本上是不同的类型。 有几种执行字符串转换的方法。 `+`运算符还具有自动字符串转换功能。

本教程的“字符串”一章将介绍有关字符串转换的更多信息。

```java
String s = (String) 15; // compilation error
int i = (int) "25"; // compilation error

```

不能在数字和字符串之间进行强制转换。 相反，我们有各种方法可以在数字和字符串之间进行转换。

```java
short age = Short.parseShort("35");
int salary = Integer.parseInt("2400");
float height = Float.parseFloat("172.34");
double weight = Double.parseDouble("55.6");

```

包装类的`parse`方法将字符串转换为原始类型。

```java
Short age = Short.valueOf("35");
Integer salary = Integer.valueOf("2400");
Float height = Float.valueOf("172.34");
Double weight = Double.valueOf("55.6");

```

`valueOf()`方法从原始类型返回包装器类。

```java
int age = 17;
double weight = 55.3;
String v1 = String.valueOf(age);
String v2 = String.valueOf(weight);

```

`String`类具有用于将各种类型转换为字符串的`valueOf()`方法。

当使用`+`运算符并且一个运算符是一个字符串，另一个运算符不是一个字符串时，会自动进行字符串转换。 `+`的非字符串操作数将转换为字符串。

`com/zetcode/AutomaticStringConversion.java`

```java
package com.zetcode;

public class AutomaticStringConversion {

    public static void main(String[] args) {

        String name = "Jane";
        short age = 17;

        System.out.println(name + " is " +  age + " years old.\n");
    }
}

```

在示例中，我们有`String`数据类型和`short`数据类型。 使用`+`运算符将这两种类型连接成一个句子。

```java
System.out.println(name + " is " +  age + " years old.");

```

在表达式中，`age`变量被转换为`String`类型。

```java
$ java AutomaticStringConversion.java
Jane is 17 years old.

```

这是示例输出。

在 Java 教程的这一部分中，我们介绍了包装器类，装箱和拆箱，默认值，转换和促销。