原文: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
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包装器类。
int a = 55;
该行创建一个整数原始数据类型。
Integer b = new Integer(a);
Integer包装器类是从原始int类型创建的。
int c = b.intValue();float d = b.floatValue();
intValue()方法将Integer转换为int。 同样,floatValue()返回float数据类型。
String bin = Integer.toBinaryString(a);String hex = Integer.toHexString(a);String oct = Integer.toOctalString(a);
这三种方法返回整数的二进制,十六进制和八进制表示形式。
$ java IntegerWrapper.java55555555.01101113767
这是程序输出。
集合是用于处理对象组的强大工具。 原始数据类型不能放入 Java 集合中。 将原始值装箱后,可以将它们放入集合中。
com/zetcode/Numbers.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是动态的,可调整大小的数组。
List<Number> ls = new ArrayList<>();
创建一个ArrayList实例。 在尖括号中,我们指定容器将容纳的类型。 Number是 Java 中所有五个数字基本类型的抽象基类。
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);}
我们遍历容器并打印类名称及其每个元素的值。
$ java Numbers.javaclass java.lang.Integer1342341class java.lang.Float34.56class java.lang.Double235.242class java.lang.Byte102class java.lang.Short1245
com.zetcode.Numbers程序给出该输出。 请注意,这两个数字是由编译器自动装箱的。
Java 装箱
从原始类型转换为对象类型称为boxing。 Unboxing是相反的操作。 它正在将对象类型转换回原始类型。
com/zetcode/BoxingUnboxing.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对象中,反之亦然。
Long b = new Long(a);
该行执行拳击。
long c = b.longValue();
在这一行,我们进行拆箱。
Java 自动装箱
Java 5 引入了自动装箱。 Autoboxing是原始类型及其对应的对象包装器类之间的自动转换。 自动装箱使编程更加容易。 程序员不需要手动进行转换。
当一个值是原始类型而另一个值是包装器类时,将执行自动装箱和拆箱:
- 赋值
- 将参数传递给方法
- 从方法返回值
- 比较操作
- 算术运算
Integer i = new Integer(50);if (i < 100) {...}
在if表达式的方括号内,将Integer与int进行比较。 Integer对象被转换为原始int类型,并与 100 值进行比较。 自动取消装箱。
com/zetcode/Autoboxing.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);}}
此代码示例演示了自动装箱和自动拆箱。
Integer i = 10;
Java 编译器在此代码行中执行自动装箱。 将int值装箱为Integer类型。
int j = i;
在这里会自动开箱。
Integer a = cube(i);
当我们将Integer传递给cube()方法时,便完成了自动拆箱。 当我们返回计算值时,将执行自动装箱,因为int转换回了Integer。
Java 语言不支持运算符重载。 当我们对包装类应用算术运算时,自动装箱由编译器完成。
com/zetcode/Autoboxing2.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值。 我们对这两个值执行加法和乘法运算。
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
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对象。
Integer a = 5; // new Integer(5);Integer b = 5; // new Integer(5);
两个整数装在Integer包装器类中。
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。
Integer c = 155;Integer d = 155;
我们还有两种盒装类型。 但是,这些值大于内化的最大值(127); 因此,创建了两个不同的对象。 这次==运算符产生false。
$ java Autoboxing3.javatruetrue0falsetrue0
这是程序的输出。
Java 空类型
Java 具有特殊的null类型。 类型没有名称。 结果,不可能声明null类型的变量或将其强制转换为null类型。 null表示一个空引用,不引用任何对象。 null是引用类型变量的默认值。 不能为原始类型分配null字面值。
在不同的上下文中,null表示不存在对象,未知值或未初始化状态。
com/zetcode/NullType.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值。
private static String getName() {Random r = new Random();boolean n = r.nextBoolean();if (n == true) {return "John";} else {return null;}}
在getName()方法中,我们模拟了一种方法有时可以返回null值的情况。
System.out.println(null == null);
我们比较两个空值。 表达式返回true。
if ("John".equals(name)) {System.out.println("His name is John");}
我们将名称变量与"John"字符串进行比较。 注意,我们在"John"字符串上调用了equals()方法。 这是因为如果名称变量等于null,则调用该方法将导致NullPointerException。
$ java NullType.javanulltrue$ java NullType.javanulltrue$ java NullType.javaJohntrueHis 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
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);}}
在示例中,我们声明了八个成员字段。 它们未初始化。 编译器将为每个字段设置默认值。
static byte b;static char c;static short s;static int i;...
这些是实例变量; 它们在任何方法外声明。 这些字段被声明为static,因为它们是通过static main()方法访问的。 (在本教程的后面,我们将更多地讨论静态变量和实例变量。)
$ java DefaultValues.java0000.00.0nullnull
This is the output of the program.
Java 类型转换
我们经常一次处理多种数据类型。 将一种数据类型转换为另一种数据类型是编程中的常见工作。 术语类型转换是指将一种数据类型的实体更改为另一种。 在本节中,我们将处理原始数据类型的转换。 引用类型的转换将在本章后面提到。 转换规则很复杂; 它们在 Java 语言规范的第 5 章中指定。
转换有两种类型:隐式转换和显式转换。 隐式类型转换,也称为强制,是编译器自动进行的类型转换。 在显式转换中,程序员直接在一对圆括号内指定转换类型。 显式转换称为类型转换。
转换发生在不同的上下文中:赋值,表达式或方法调用。
int x = 456;long y = 34523L;float z = 3.455f;double w = 6354.3425d;
在这四个分配中,没有转换发生。 每个变量都被分配了预期类型的字面值。
int x = 345;long y = x;float m = 22.3354f;double n = m;
在此代码中,Java 编译器隐式执行了两次转换。 将较小类型的变量分配给较大类型的变量是合法的。 该转换被认为是安全的,因为不会损失任何精度。 这种转换称为隐式加宽转换。
long x = 345;int y = (int) x;double m = 22.3354d;float n = (float) m;
在 Java 中,将较大类型的变量分配给较小类型是不合法的。 即使值本身适合较小类型的范围。 在这种情况下,可能会降低精度。 为了允许这种分配,我们必须使用类型转换操作。 这样,程序员说他是故意这样做的,并且他意识到可能会丢失一些精度这一事实。 这种转换称为显式变窄转换。
byte a = 123;short b = 23532;
在这种情况下,我们处理一种特定类型的分配转换。 123 和 23532 是整数字面值,a,b变量为byte和short类型。 可以使用铸造操作,但不是必需的。 字面值可以在赋值左侧的变量中表示。 我们处理隐式变窄转换。
private static byte calc(byte x) {...}byte b = calc((byte) 5);
以上规则仅适用于分配。 当我们将整数字面值传递给需要一个字节的方法时,我们必须执行强制转换操作。
Java 数字提升
数值提升是隐式类型转换的特定类型。 它发生在算术表达式中。 数字提升用于将数字运算符的操作数转换为通用类型,以便可以执行操作。
int x = 3;double y = 2.5;double z = x + y;
第三行中有一个加法表达式。 x操作数为int,y操作数为double。 编译器将整数转换为双精度值,然后将两个数字相加。 结果是两倍。 这是隐式扩展原始类型转换的情况。
byte a = 120;a = a + 1; // compilation error
此代码导致编译时错误。 在第二行的右侧,我们有一个字节变量a和一个整数字面值 1。该变量将转换为整数并添加值。 结果是一个整数。 以后,编译器尝试将值分配给a变量。 没有显式的强制转换运算符,就不可能将较大的类型分配给较小的类型。 因此,我们收到一个编译时错误。
byte a = 120;a = (byte) (a + 1);
此代码可以编译。 请注意a + 1表达式中使用圆括号。 (byte)强制转换运算符的优先级高于加法运算符。 如果要对整个表达式应用转换,则必须使用圆括号。
byte a = 120;a += 5;
复合运算符自动执行隐式转换。
short r = 21;short s = (short) -r;
将+或-一元运算符应用于变量,即可执行一元数提升。 short类型升级为int类型。 因此,必须使用强制转换运算符来使分配通过。
byte u = 100;byte v = u++;
如果是一元递增++或递减--运算符,则不会进行任何转换。 不需要铸造。
Java 装箱,拆箱转换
装箱转换将原始类型的表达式转换为包装器类型的对应表达式。 拆箱转换将包装器类型的表达式转换为原始类型的相应表达式。 从boolean到Boolean或从字节到Byte的转换是装箱转换的示例。 反向转换,例如从Boolean到boolean或从Byte到byte的翻译是取消装箱转换的示例。
Byte b = 124;byte c = b;
在第一行代码中,自动装箱转换由 Java 编译器执行。 在第二行中,完成了拆箱转换。
private static String checkAge(Short age) {...}String r = checkAge((short) 5);
在这里,我们在方法调用的上下文中进行装箱转换。 我们将short类型传递给需要Short包装类型的方法。 该值已装箱。
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
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) {// upcastingAnimal 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;}}
该示例执行引用类型转换。
// upcastingAnimal animal = new Dog();System.out.println(animal);
我们从子类型Dog转换为父类型Animal。 这是不安全的,并且始终是安全的。
// ClassCastException// Mammal mammal = (Mammal) new Animal();
从Animal向下广播到Mammal会导致ClassCastException。
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运算符检查对象的类型。
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 字符串转换
在数字和字符串之间执行字符串转换在编程中非常常见。 不允许进行强制转换操作,因为字符串和基本类型在根本上是不同的类型。 有几种执行字符串转换的方法。 +运算符还具有自动字符串转换功能。
本教程的“字符串”一章将介绍有关字符串转换的更多信息。
String s = (String) 15; // compilation errorint i = (int) "25"; // compilation error
不能在数字和字符串之间进行强制转换。 相反,我们有各种方法可以在数字和字符串之间进行转换。
short age = Short.parseShort("35");int salary = Integer.parseInt("2400");float height = Float.parseFloat("172.34");double weight = Double.parseDouble("55.6");
包装类的parse方法将字符串转换为原始类型。
Short age = Short.valueOf("35");Integer salary = Integer.valueOf("2400");Float height = Float.valueOf("172.34");Double weight = Double.valueOf("55.6");
valueOf()方法从原始类型返回包装器类。
int age = 17;double weight = 55.3;String v1 = String.valueOf(age);String v2 = String.valueOf(weight);
String类具有用于将各种类型转换为字符串的valueOf()方法。
当使用+运算符并且一个运算符是一个字符串,另一个运算符不是一个字符串时,会自动进行字符串转换。 +的非字符串操作数将转换为字符串。
com/zetcode/AutomaticStringConversion.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数据类型。 使用+运算符将这两种类型连接成一个句子。
System.out.println(name + " is " + age + " years old.");
在表达式中,age变量被转换为String类型。
$ java AutomaticStringConversion.javaJane is 17 years old.
这是示例输出。
在 Java 教程的这一部分中,我们介绍了包装器类,装箱和拆箱,默认值,转换和促销。
