目录
介绍
一、原生类与包装类
1.自动装箱(Autoboxing)
2.自动拆箱(Unboxing
3.空值(null)
4.泛型支持
5.方法重载
6.性能
7.存储位置
栈(Stack)
堆(Heap)
引用
重点
8.默认值
二、注意项
1. float和double
2. 1、l、|
3.boolean的存储大小
Java中,数据类型分为基本数据类型(或叫做原生类、内置类型)和引用数据类型。原生类是指基本数据类型。Java不是纯的面向对象的语言,不纯的地方就是这些基本数据类型不是对象。当然初期Java的运行速度很慢,基本数据类型能在一定程度上改善性能。如果你想编写纯的面向对象的程序,用包装器类是取代基本数据类型就可以了。
Java不被认为是纯粹的面向对象编程语言,主要是因为它支持基本类型,这些类型不是对象。在纯粹的面向对象语言中,一切都是对象,包括数字、字符和布尔值。
在Java早期版本中,性能是一个重要的考虑因素,因为Java程序运行在Java虚拟机(JVM)上,而JVM本身就带来了一定的性能开销。使用基本类型可以减少内存占用,并提高处理速度,因为它们存储在栈上,并且可以直接访问,不需要通过引用。相比之下,包装类型是存储在堆上的对象,它们需要更多的内存,并且访问时可能涉及到指针解引用。
为了提供面向对象的一致性,Java为每个基本类型提供了对应的包装类。这些类提供了一些有用的方法,比如将字符串转换为数值,或者将数值转换为不同的基本类型。此外,由于Java 5引入了自动装箱和自动拆箱,使用包装类型变得更加方便,因为编译器可以自动在基本类型和它们的包装类型之间转换,让开发者能够更专注于业务逻辑。
但是,即使有自动装箱和拆箱,使用包装类型仍然有其代价。每次装箱操作都可能涉及到创建新的对象,这可能会导致额外的垃圾回收(GC)开销。因此,对于大量数值操作的性能敏感应用,如科学计算和高频交易系统,建议直接使用基本类型。
随着Java语言的发展,性能优化一直是JVM和Java语言本身的一个重要方向。JVM的即时编译器(JIT)和垃圾收集器(GC)都经过了大量优化,以提高性能和降低延迟。这些优化使得包装类型的性能开销相对于早期的Java版本有了显著的减少。
除了性能考虑之外,包装类型在Java的集合框架中也扮演着关键角色。由于Java的集合类(如List
、Set
和Map
)不支持基本类型,所以在这些集合中存储基本类型的数据时,必须使用相应的包装类型。这就是为什么在使用泛型集合时,我们使用Integer
而不是int
,使用Double
而不是double
等等。
基本类型 | 包装类型 | 举例 | 基本类型的存储空间 | 范围 | 基本类型的默认值 |
boolean | Boolean | true | 没有明确定义 | 用于表示逻辑值true (真)或false (假) |
false |
byte | Byte | 100 | 8位 | -128 ~ 127 | 0 |
char | Character | 'A' | 16位 | 16位Unicode字符 | '\u0000'(空字符) |
short | Short | 5000 | 16位 | -32768 ~ 32767 | 0 |
int | Integer | 100000 | 32位 | -2^31 ~ 2^31-1 | 0 |
long | Long | 15000000000L | 64位 | -2^63 ~ 2^63-1 | 0L |
float | Float | 5.99f | 32位 | 32位单精度浮点数 | 0.0f |
double | Double | 19.99 | 64位 | 64位双精度浮点数 | 0.0d |
没有String,记住了
基本类型与包装类型之间的关系主要体现在以下几个方面:
一个基本类型自动转换成对应的包装类型时,称为自动装箱。这是Java编译器提供的一个特性,可以让我们在编写代码时不必显式地进行类型转换。
Integer integer = 10;
当一个包装类型自动转换成对应的基本类型时,称为自动拆箱。这同样是Java编译器的特性。
int i = integer;
包装类型是对象,因此它们可以赋值为null,表示没有任何对象与该变量关联。基本类型的变量不能赋值为null,它们总是有一个默认值,例如int的默认值是0。
Java的泛型不支持基本类型,只支持对象类型。因此,在使用泛型时,必须使用包装类型。
当一个方法被重载,并且参数类型分别是基本类型和对应的包装类型时,基本类型的方法会被优先调用。
class TestTest {
@Test
public void mainTest(){
method(1);
}
public void method(int i){
System.out.println("基元类型方法被调用");
}
public void method(Integer i){
System.out.println("包装类型方法被调用");
}
}
基本类型通常比包装类型更高效,因为包装类型需要额外的内存来存储对象的元数据,并且可能涉及到额外的方法调用。在性能敏感的场合,推荐使用基本类型。
基本类型的数据通常存储在栈上,而包装类型的对象存储在堆上。
这里对JVM的内存模型提一嘴:
在Java中,虚拟机(JVM)的内存模型定义了不同的区域来存储不同类型的数据。其中两个主要区域是栈(Stack)和堆(Heap)。理解这两个区域如何工作有助于深入了解Java程序的内存管理。
栈是一种线程私有的内存区域,每个线程创建时都会获得自己的栈,用于存储局部变量和部分控制信息。在栈中,数据的存储遵循后进先出(LIFO)的原则。栈内存主要用于执行方法调用,每当一个方法被调用时,就会创建一个新的栈帧(Stack Frame)来存储该方法的局部变量、操作数栈、动态链接信息和方法返回值。
堆是Java虚拟机中一个共享的内存区域,用于存放对象实例和数组。所有线程共享访问堆内存,因此堆内存中的对象可以在不同线程之间共享。当代码中创建一个对象时(例如使用
new
关键字),对象的数据会被分配到堆上。
new
关键字创建的对象都存储在堆内存中。在Java中,当你创建一个对象时,实际上你获得的是一个引用,这个引用指向堆内存中的对象。引用本身是存储在栈上的(作为局部变量的一部分),但它指向的对象数据存储在堆上。
类的成员变量的包装类型默认值为null,而基本类型的成员变量有其预定义的默认值,例如int为0,boolean为false。
float
类型是单精度32位IEEE 754浮点数,其最大精度大约是7个十进制数位。
double
类型是双精度64位IEEE 754浮点数,其最大精度大约是15个十进制数位。
这里的“大约”是因为浮点数的表示是基于二进制的,而我们通常讨论的精度是基于十进制的。由于二进制和十进制不是完全对应的,所以精度的值是一个近似值。
精度是指在转换成二进制数时能够保留的有效数字的位数。由于浮点数的存储方式包含了指数部分和尾数部分,所以它们并不是用来精确存储数字的,而是用来表示一个范围内的近似值。
在处理需要高精度的计算时,通常不推荐使用float
和double
,因为它们可能会引入舍入误差。对于要求高精度的应用,应该使用BigDecimal
类,它提供了任意精度的浮点数运算。
误差演示:
@Test
public void mainTest(){
double d = 0.00000000000011;
for (int i = 0; i < 10; i++) {
d += 0.00000000000001;
System.out.println(d);
}
}
这是老生常谈的问题了,在写以1结尾的数字时,不要以小写的L作为后缀
如果使用小写的l
作为long
类型的后缀,它很容易与数字1
混淆(1、l、|)。同样,虽然不常见,但如果在数字后使用大写的D
,它可能与字母O
或数字0
混淆(在某些字体下特别容易混淆)。
Java 语言规范并没有明确指定
boolean
类型的确切大小。这是因为 JVM 的实现可以自由选择最适合其性能的方式来表示boolean
值。
在Stack Overflow上的讨论中,有关于Java中boolean
变量占用多少字节的问题。高赞回答中提供了一个测试类,通过创建大量的boolean
和int
变量的数组,并测量它们的内存使用情况,来估算boolean
和int
类型的平均大小。
测试结果显示,boolean
数组的平均大小约为82.57544字节,而int
数组的平均大小约为335.99984字节。这表明在数组中,每个boolean
大约占用1个字节的空间。
关键点:
boolean
值的字节码指令。在编译后,boolean
值使用Java虚拟机中的int
数据类型来表示。boolean
类型的数组,使用byte
类型数组的指令来访问和修改boolean
数组。boolean
值使用int
数据类型来代替,而int
是4个字节;boolean
数组中的每个元素使用1个字节。在数组中,boolean
占用1个字节,在其他情况下(如单独的boolean
变量),可能占用4个字节。具体还要看虚拟机的实现,因为Java规范本身并没有明确指出boolean
的大小。