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
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.java
55
55
55
55.0
110111
37
67
这是程序输出。
集合是用于处理对象组的强大工具。 原始数据类型不能放入 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.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
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.java
true
true
0
false
true
0
这是程序的输出。
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.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
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.java
0
0
0
0.0
0.0
null
null
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) {
// 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;
}
}
该示例执行引用类型转换。
// upcasting
Animal 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 error
int 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.java
Jane is 17 years old.
这是示例输出。
在 Java 教程的这一部分中,我们介绍了包装器类,装箱和拆箱,默认值,转换和促销。