Java 类型转换
上次修改时间:2025 年 5 月 7 日
Java 中的类型转换(type casting)指的是将一个数据类型的值更改为另一个数据类型。 当您想以不同的格式使用数据、将其分配给另一种类型的变量或将其传递给期望不同类型的方法时,这是必需的。 Java 支持多种用于原始类型和对象类型的转换方法。
理解类型转换可以帮助您避免 Java 程序中的运行时错误和数据丢失。
原始类型转换
Java 的原始类型(byte
、short
、int
、long
、float
、double
、char
、boolean
)可以相互转换。 主要有两种类型:隐式转换(扩大)和显式转换(缩小)。
隐式转换(扩大或自动类型转换)
当较小的类型转换为较大的类型时,会发生扩大转换。 Java 会自动执行此操作,因为它是安全的,并且不会丢失数据。
典型的扩大转换顺序是
byte -> short -> int -> long -> float -> double
此外,char
可以扩大到 int
、long
、float
或 double
。
package com.zetcode; public class WideningConversionDemo { public static void main(String[] args) { int myInt = 100; long myLong = myInt; // int to long (implicit) float myFloat = myLong; // long to float (implicit) double myDouble = myFloat; // float to double (implicit) System.out.println("Original int: " + myInt); System.out.println("Widened to long: " + myLong); System.out.println("Widened to float: " + myFloat); System.out.println("Widened to double: " + myDouble); char myChar = 'A'; int charToInt = myChar; // char to int (implicit) System.out.println("Original char: " + myChar); System.out.println("Widened char to int: " + charToInt); // Prints 65 } }
在此示例中,值会自动提升为更大的类型,而无需显式强制转换。
显式转换(缩小或手动类型转换)
当较大的类型转换为较小的类型时,会发生缩小转换。 这必须使用强制转换运算符 (targetType)
显式完成,因为它可能导致数据或精度损失。
典型的缩小转换顺序与扩大相反
double -> float -> long -> int -> short -> char -> byte
package com.zetcode; public class NarrowingConversionDemo { public static void main(String[] args) { double myDouble = 123.456; float myFloat = (float) myDouble; // double to float (explicit) long myLong = (long) myFloat; // float to long (explicit, fractional part lost) int myInt = (int) myLong; // long to int (explicit) short myShort = (short) myInt; // int to short (explicit, potential overflow) byte myByte = (byte) myShort; // short to byte (explicit, potential overflow) char myChar = (char) myInt; // int to char (explicit, takes lower 16 bits) System.out.println("Original double: " + myDouble); System.out.println("Narrowed to float: " + myFloat); System.out.println("Narrowed to long: " + myLong); System.out.println("Narrowed to int: " + myInt); System.out.println("Narrowed to short: " + myShort); System.out.println("Narrowed to byte: " + myByte); System.out.println("Narrowed int to char: " + myChar); // Example of data loss due to overflow int largeInt = 300; byte smallByte = (byte) largeInt; // 300 is 100101100 in binary. Byte takes last 8 bits. // 300 % 256 = 44. System.out.println("Original large int: " + largeInt); System.out.println("Narrowed large int to byte: " + smallByte); // Output: 44 double preciseDouble = 99.99; int truncatedInt = (int) preciseDouble; System.out.println("Original precise double: " + preciseDouble); System.out.println("Narrowed to int (truncation): " + truncatedInt); // Output: 99 } }
缩小转换时,如果该值超出目标类型的范围,则可能会丢失高位。 对于浮点数到整数的转换,小数部分将被简单地删除。
表达式中的类型提升
Java 在表达式中将较小的类型提升为较大的类型,以防止计算过程中出现数据丢失。 规则是
- 如果一个操作数是
double
,则另一个操作数将转换为double
。 - 否则,如果一个操作数是
float
,则另一个操作数将转换为float
。 - 否则,如果一个操作数是
long
,则另一个操作数将转换为long
。 - 否则(
byte
、short
、char
、int
),两个操作数都将转换为int
。
package com.zetcode; public class ExpressionPromotionDemo { public static void main(String[] args) { byte b1 = 10; byte b2 = 20; // byte resultByte = b1 + b2; // Error: b1 + b2 results in an int int resultInt = b1 + b2; // Correct: byte + byte -> int byte resultByteCasted = (byte) (b1 + b2); // Explicit cast needed for byte result System.out.println("b1 + b2 (as int): " + resultInt); System.out.println("b1 + b2 (casted to byte): " + resultByteCasted); short s = 5; float f = 10.5f; // short shortResult = s + f; // Error: s + f results in a float float floatResult = s + f; // Correct: short + float -> float System.out.println("s + f (as float): " + floatResult); int i = 100; double d = 20.75; // int intResultSum = i + d; // Error: i + d results in double double doubleResult = i + d; // Correct: int + double -> double System.out.println("i + d (as double): " + doubleResult); } }
对象类型转换(Casting)
对于对象类型(类和接口),强制转换允许您将对象视为其继承树中的另一种类型。
向上转型(隐式)
向上转型意味着将子类对象视为其超类。 这始终是安全且自动的,因为子类是其超类的一种。
package com.zetcode.casting; class Animal { void makeSound() { System.out.println("Generic animal sound"); } } class Dog extends Animal { @Override void makeSound() { System.out.println("Woof woof"); } void fetch() { System.out.println("Dog is fetching."); } } class Cat extends Animal { @Override void makeSound() { System.out.println("Meow"); } void scratch() { System.out.println("Cat is scratching."); } } public class ObjectCastingDemo { public static void main(String[] args) { // Upcasting Dog myDog = new Dog(); Animal myAnimalFromDog = myDog; // Implicit upcasting (Dog to Animal) Cat myCat = new Cat(); Animal myAnimalFromCat = myCat; // Implicit upcasting (Cat to Animal) System.out.print("myAnimalFromDog (originally Dog) says: "); myAnimalFromDog.makeSound(); // Calls Dog's overridden makeSound() System.out.print("myAnimalFromCat (originally Cat) says: "); myAnimalFromCat.makeSound(); // Calls Cat's overridden makeSound() // myAnimalFromDog.fetch(); // Compile error: Animal type doesn't have fetch() method // The reference type determines accessible methods. // Using an array of superclass type Animal[] animals = new Animal[2]; animals[0] = new Dog(); // Upcasting animals[1] = new Cat(); // Upcasting for (Animal animal : animals) { animal.makeSound(); // Polymorphism in action } } }
向上转型时,您只能使用超类中的方法和字段(或子类覆盖的方法和字段)。 子类特定的方法无法通过超类引用访问。
向下转型(显式)
向下转型意味着将超类对象视为子类。 这是有风险的,必须是显式的,通常在使用 instanceof
检查后进行,以避免 ClassCastException
。
package com.zetcode.casting; // Assuming Animal, Dog, Cat are in the same package or imported public class ObjectDowncastingDemo { public static void main(String[] args) { Animal myAnimal = new Dog(); // Upcasted to Animal, actual object is Dog // Animal anotherAnimal = new Animal(); // Actual object is Animal // Attempting to downcast myAnimal to Dog if (myAnimal instanceof Dog) { Dog myDog = (Dog) myAnimal; // Explicit downcasting System.out.print("Downcasted Dog object says: "); myDog.makeSound(); // Calls Dog's makeSound() myDog.fetch(); // Now fetch() is accessible } else { System.out.println("Cannot downcast myAnimal to Dog."); } Animal generalAnimal = new Animal(); // Attempting to downcast generalAnimal to Dog (will fail if not checked) if (generalAnimal instanceof Dog) { Dog specificDog = (Dog) generalAnimal; // This line won't be reached specificDog.fetch(); } else { System.out.println("generalAnimal is not an instance of Dog. Cannot downcast."); } // Example leading to ClassCastException Animal possiblyCat = new Cat(); // Dog notADog = (Dog) possiblyCat; // This would throw ClassCastException if executed // because a Cat is not a Dog. try { Animal anAnimal = new Cat(); // Actual object is Cat Dog aDog = (Dog) anAnimal; // Attempting to cast Cat to Dog aDog.fetch(); } catch (ClassCastException e) { System.err.println("Error during downcasting: " + e.getMessage()); } } }
instanceof
运算符检查对象是否为特定类型或其子类。 始终在向下转型之前使用 instanceof
以避免错误。
涉及字符串的转换
字符串是 Java 中的对象。 在字符串和原始类型或其他对象之间进行转换是很常见的。
原始类型转换为字符串
- 使用
String.valueOf
:此方法针对所有原始类型重载(例如,String.valueOf(int)
、String.valueOf(double)
)。 - 使用包装类
toString
方法:例如,Integer.toString(int)
、Double.toString(double)
。 - 与空字符串连接:例如,
"" + myInt
。 虽然简单,但这可能会因字符串连接开销而效率较低。
字符串转换为原始类型
这通常通过包装类的 parseXxx
方法完成(例如,Integer.parseInt(String)
)。 如果字符串无效,则会抛出 NumberFormatException
。
对象转换为字符串
- 每个对象都有一个
toString
方法(从Object
类继承)。 最好在您的类中覆盖toString
以提供有意义的字符串表示形式。 String.valueOf(Object obj)
:如果obj
不为 null,则此方法在内部调用obj.toString
。 如果obj
为 null,则返回字符串“null”(避免NullPointerException
)。
package com.zetcode; import java.util.Date; public class StringConversionDemo { public static void main(String[] args) { // Primitive to String int num = 123; String strNum1 = String.valueOf(num); String strNum2 = Integer.toString(num); String strNum3 = "" + num; System.out.println("Primitive to String: " + strNum1 + ", " + strNum2 + ", " + strNum3); double val = 45.67; String strVal = String.valueOf(val); System.out.println("Double to String: " + strVal); // String to Primitive String strInt = "100"; String strDouble = "200.50"; String strInvalid = "abc"; try { int parsedInt = Integer.parseInt(strInt); double parsedDouble = Double.parseDouble(strDouble); System.out.println("String to int: " + parsedInt); System.out.println("String to double: " + parsedDouble); // int invalidParse = Integer.parseInt(strInvalid); // This would throw NumberFormatException } catch (NumberFormatException e) { System.err.println("Error parsing string: " + e.getMessage()); } // Boolean parsing String trueStr = "true"; String falseStr = "FalSe"; // Case-insensitive for "true" boolean bTrue = Boolean.parseBoolean(trueStr); // true boolean bFalse = Boolean.parseBoolean(falseStr); // false (only "true" ignoring case is true) System.out.println("String 'true' to boolean: " + bTrue); System.out.println("String 'FalSe' to boolean: " + bFalse); // Object to String Date today = new Date(); String dateStr = today.toString(); // Uses Date's overridden toString() System.out.println("Date object to String: " + dateStr); Object nullObj = null; String nullStr = String.valueOf(nullObj); // Safely handles null, returns "null" System.out.println("String.valueOf(nullObj): " + nullStr); // String problematicNullStr = nullObj.toString(); // This would throw NullPointerException! } }
自动装箱和拆箱
自动装箱是指 Java 自动将原始类型转换为其包装类(例如,int
转换为 Integer
)。 拆箱是相反的。 这使得代码更简单、更易读。
此功能使您可以编写 Integer myIntegerObject = 100;
而不是 Integer myIntegerObject = new Integer(100);
(构造函数已弃用;请改用 valueOf
)。
package com.zetcode; import java.util.ArrayList; import java.util.List; public class AutoboxingUnboxingDemo { public static void main(String[] args) { // Autoboxing: primitive int to Integer object Integer integerObject = 100; // Compiler converts to Integer.valueOf(100) System.out.println("Autoboxed Integer object: " + integerObject); // Unboxing: Integer object to primitive int int primitiveInt = integerObject; // Compiler converts to integerObject.intValue() System.out.println("Unboxed primitive int: " + primitiveInt); // Example in collections List<Integer> numberList = new ArrayList<>(); numberList.add(10); // Autoboxing: 10 (int) becomes Integer.valueOf(10) numberList.add(20); int firstElement = numberList.get(0); // Unboxing: Integer object from list to int System.out.println("First element from list (unboxed): " + firstElement); // Pitfall: NullPointerException during unboxing Integer nullInteger = null; try { // int problematicInt = nullInteger; // This would throw NullPointerException // as it tries to call nullInteger.intValue() if (nullInteger != null) { // Always check for null before unboxing int safeInt = nullInteger; System.out.println("Safely unboxed: " + safeInt); } else { System.out.println("Cannot unbox a null Integer object."); } } catch (NullPointerException e) { System.err.println("Error during unboxing: " + e.getMessage()); } } }
虽然方便,但在对性能至关重要的代码中,自动装箱/拆箱会创建不必要的对象。 拆箱 null
包装器对象会导致 NullPointerException
。
关键转换和潜在问题摘要
转换类型 | 描述 | 机制 | 潜在问题 |
---|---|---|---|
原始类型扩大 | 较小到更大的数字类型(例如,int 到 long) | 隐式(自动) | 无(安全) |
原始类型缩小 | 较大到较小的数字类型(例如,double 到 int) | 显式强制转换 (type)value |
数据丢失、精度丢失、溢出 |
对象向上转型 | 子类到超类/接口 | 隐式(自动) | 无(安全,但会通过超类引用失去对子类特定成员的访问权限) |
对象向下转型 | 超类/接口到子类 | 显式强制转换 (SubclassType)object |
如果对象不是目标类型的实例,则会抛出 ClassCastException |
原始类型转换为字符串 | 例如,int 到 String | String.valueOf 、Wrapper.toString 、"" + primitive |
通常安全 |
字符串转换为原始类型 | 例如,String 到 int | Wrapper.parseXxx |
如果字符串格式无效,则会抛出 NumberFormatException |
对象转换为字符串 | 任何对象到 String | obj.toString 、String.valueOf(obj) |
如果在 null 上调用 obj.toString ,则会抛出 NullPointerException (String.valueOf 可以安全地处理 null) |
自动装箱 | 原始类型到包装器(例如,int 到 Integer) | 隐式(自动) | 如果不小心,循环中会产生性能开销 |
拆箱 | 包装器到原始类型(例如,Integer 到 int) | 隐式(自动) | 如果包装器对象为 null,则会抛出 NullPointerException |
类型转换的最佳实践
- 首选扩大: 尽可能使用扩大转换,因为它们是安全且自动的。
- 谨慎使用缩小: 了解数据丢失或溢出的可能性。 显式强制转换,并确保该值在目标类型的范围内(如果至关重要)。
- 使用
instanceof
进行向下转型: 始终在使用instanceof
检查后进行对象向下转型,以防止ClassCastException
。 - 处理异常: 将字符串解析为数字 (
parseXxx
) 时,请准备好捕获NumberFormatException
。 - Null 检查: 在拆箱包装器对象或在对象引用上调用诸如
toString
之类的方法之前,请检查null
以避免NullPointerException
。 当可能出现 null 值时,String.valueOf(Object)
对于将对象转换为字符串更安全。 - 覆盖
toString
: 通过覆盖toString
方法为您的自定义类提供有意义的字符串表示形式。 - 理解自动装箱: 了解自动装箱和拆箱发生的位置,尤其是在对性能敏感的代码中或在处理
null
包装器对象时。
来源
Java 的类型转换系统非常灵活,允许数据在原始类型和对象类型之间移动。 隐式转换是无缝的,但显式转换需要小心,以避免数据丢失或运行时错误。 了解规则和陷阱可以帮助您编写更安全、更可靠的 Java 代码。
作者
列出所有Java教程。