很多同学会混淆强类型与静态语言的概念
- 强类型 vs. 弱类型:区别是类型转换的严格性和一致性,强类型语言要求开发者在类型转换时显式说明,而弱类型语言可能允许隐式转换
- 动态语言 vs. 静态语言:区别是类型绑定的时机;动态语言的类型在运行时决定,而静态语言在编译时确定
JavaScript 是弱类型的动态语言,Python 是强类型的动态语言,Java 是强类型的静态语言
基础类型
Java是一种强类型语言,拥有八种基础类型,每种类型在内存中的存储方式和用途各不相同。基础类型直接存储在栈内存中,具有高效的性能和固定的内存占用
类型 | 描述 | 位数 | 默认值 |
---|---|---|---|
整型 | byte``short``int``long | 8, 16,32, 64 | 0 |
浮点型 | float``double | 32, 64 | 0.0 |
字符型 | char | 16 | \u0000 |
布尔型 | boolean | - | false |
public class PrimitiveTypesExample {
public static void main(String[] args) {
byte b = 100;
short s = 10000;
int i = 100000;
long l = 10000000000L;
float f = 3.14F;
double d = 3.141592653589793;
char c = 'J';
boolean bool = true;
System.out.println("Byte: " + b);
System.out.println("Short: " + s);
System.out.println("Int: " + i);
System.out.println("Long: " + l);
System.out.println("Float: " + f);
System.out.println("Double: " + d);
System.out.println("Char: " + c);
System.out.println("Boolean: " + bool);
}
}
包装类型
包装类型(Wrapper Types)是对基础类型的封装,提供了对象形式的基础类型。每种基础类型都有一个对应的包装类,如int
对应Integer
,double
对应Double
等
基础类型 | 包装类型 | 位数 |
---|---|---|
byte | Byte | 8 |
short | Short | 16 |
int | Integer | 32 |
long | Long | 64 |
float | Float | 32 |
double | Double | 64 |
char | Character | 16 |
boolean | Boolean | - |
Java的集合框架如ArrayList
、HashMap
等设计基于泛型(Generics),而泛型类型参数必须是对象类型(Reference Types),无法使用基础类型(Primitive Types),这就需要使用对应的包装类型来存储和操作基础类型的数据
import java.util.ArrayList;
import java.util.List;
public class WrapperTypesExample {
public static void main(String[] args) {
// 使用包装类型存储基础类型数据
List<Integer> integerList = new ArrayList<>();
integerList.add(10); // 自动装箱:int -> Integer
integerList.add(20);
integerList.add(30);
// 迭代输出
for (Integer num : integerList) {
System.out.println(num);
}
}
}
自动装箱与自动拆箱
Java 5 引入了自动装箱(Autoboxing)和自动拆箱(Unboxing)机制,使基础类型和包装类型之间的转换更加便捷,极大地提升了 Java 的可用性和代码的简洁性
自动装箱是指将基础类型自动转换为对应的包装类型的过程。Java编译器会在需要对象的场景下自动完成这种转换,无需手动调用构造器或静态方法。
public class AutoboxingExample {
public static void main(String[] args) {
int primitiveInt = 10;
// 自动装箱:int 转换为 Integer
Integer wrapperInteger = primitiveInt;
System.out.println("Wrapper Integer: " + wrapperInteger);
}
}
自动拆箱是指将包装类型自动转换为对应的基础类型的过程。当需要基础类型的场景时,Java会自动进行解包处理。
public class UnboxingExample {
public static void main(String[] args) {
Integer wrapperInteger = 20;
// 自动拆箱:Integer 转换为 int
int primitiveInt = wrapperInteger;
System.out.println("Primitive Int: " + primitiveInt);
}
}
Java 作为面向对象语言,只要没有基本数据类型就不会面对拆箱/装箱问题了,这可以说是 Java 延续 C++ 带来的一个遗留问题,Ruby、Scala 等语言中所有数据类型都是对象。在 Java 中有些场景不得不使用包装类对象
- 包装类最常见的使用就是在集合中,因为集合不允许存储基本类型的数据,只能存储引用类型的数据
- 不止集合,泛型整体不允许使用基本数据类型作为类型参数,只能使用包装类作为类型参数
- 反射只能对类对象进行操作,而基本数据类型没有对应的类对象,只能使用包装类对象
- 对基本数据类型进行 null 值处理,需要使用包装类对象
为了防范 NPE(NullPointerException)问题《阿里巴巴 Java 开发手册》建议
- POJO 类属性必须使用包装数据类型
- RPC 方法的返回值和参数必须使用包装数据类型
在大多数情况下,拆箱和装箱操作不会对程序的性能产生显著的影响,因为现代的JVM对这些操作进行了优化。但是,在某些特定的场景(高并发或大数据)下这些操作可能会导致程序的性能下降,尽可能地避免使用不必要的装箱和拆箱操作,而是直接使用基本类型
包装类型的缓存机制
为优化性能和内存使用,某些包装类型实现了缓存机制。当创建特定范围内的包装对象时,Java会返回相同的对象实例,避免重复创建相同值的对象。
Integer 缓存
Integer 类实现了对象缓存机制,对于数值在-128到127范围内的Integer实例,Java 会缓存这些对象,避免重复创建。
public class IntegerCacheExample {
public static void main(String[] args) {
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // 输出 true,指向同一缓存对象
Integer c = 200;
Integer d = 200;
System.out.println(c == d); // 输出 false,超出缓存范围,创建了不同的对象
}
}
Character 缓存
Character 类也实现了类似的缓存机制,默认缓存范围为'\u0000'
到'\u007F'
(0 到 127 的Unicode值)。
- 在高频使用的数值或字符范围内,依赖缓存机制可以减少内存开销和提升性能
- 超出缓存范围时,Java会创建新的对象实例
public class CharacterCacheExample {
public static void main(String[] args) {
Character x = 'A';
Character y = 'A';
System.out.println(x == y); // 输出 true,指向同一缓存对象
Character m = '中';
Character n = '中';
System.out.println(m == n); // 输出 false,超出缓存范围,创建了不同的对象
}
}
Boolean 缓存
Boolean 类只缓存两个实例:Boolean.TRUE
和Boolean.FALSE
public class BooleanCacheExample {
public static void main(String[] args) {
Boolean bool1 = Boolean.TRUE;
Boolean bool2 = Boolean.TRUE;
Boolean bool3 = new Boolean(true);
System.out.println(bool1 == bool2); // 输出 true,指向同一缓存对象
System.out.println(bool1 == bool3); // 输出 false,新创建的对象与缓存对象不同
}
}
比较基础类型与包装类型的相等性
在 Java 中基础类型和包装类型的比较需要谨慎处理,尤其是在使用==
运算符和.equals()
方法时。误用可能导致逻辑错误或意外的行为
使用 == 比较
- 基础类型:使用
==
运算符比较的是数值本身,比较结果基于值的相等性 - 包装类型:使用
==
运算符比较的是对象引用,只有在引用指向同一个对象时,才返回true
public class EqualityComparison {
public static void main(String[] args) {
// 基础类型比较
int a = 100;
int b = 100;
System.out.println(a == b); // 输出 true
// 包装类型比较
Integer c = 100;
Integer d = 100;
System.out.println(c == d); // 输出 true (在缓存范围内)
Integer e = 200;
Integer f = 200;
System.out.println(e == f); // 输出 false (超出缓存范围)
}
}
使用 .equals() 比较
- 基础类型:基础类型不支持
.equals()
方法,因为它们不是对象 - 包装类型:
.equals()
方法比较的是对象的值,适用于比较包装类型的数值相等性
public class EqualsComparison {
public static void main(String[] args) {
Integer a = 100;
Integer b = 100;
System.out.println(a.equals(b)); // 输出 true
Integer c = 200;
Integer d = 200;
System.out.println(c.equals(d)); // 输出 true
}
}