# 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 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 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 教程的这一部分中,我们介绍了包装器类,装箱和拆箱,默认值,转换和促销。