Java基础概览和常用知识(六)

基本数据类型

Java中的几种基本数据类型:

Java 中有 8 种基本数据类型,分别为:

  • 6 种数字类型:
    4 种整数型:byteshortintlong
    2 种浮点型:floatdouble
  • 1 种字符类型:char
  • 1 种布尔型:boolean

这 8 种基本数据类型的默认值以及所占空间的大小如下:

基本类型 位数 字节 默认值 取值范围
byte 8 1 0 -128 ~ 127
short 16 2 0 -32768(-2^15) ~ 32767(2^15 - 1)
int 32 4 0 -2147483648 ~ 2147483647
long 64 8 0L -263-1
char 16 2 '\u0000' 0 ~ 65535(2^16 - 1)
float 32 4 0.0f 1.4E-45 ~ 3.4028235E38
double 64 8 0.0d 4.9E-324 ~ 1.7976931348623157E308
boolean 1 false true、false

可以看到,像 byteshortintlong能表示的最大正数都减 1 了。这是为什么呢?这是因为在二进制补码表示法中,最高位是用来表示符号的(0 表示正数,1 表示负数),其余位表示数值部分。所以,如果我们要表示最大的正数,我们需要把除了最高位之外的所有位都设为 1。如果我们再加 1,就会导致溢出,变成一个负数。

对于 boolean,官方文档未明确定义,它依赖于 JVM 厂商的具体实现。逻辑上理解是占用 1 位,但是实际中会考虑计算机高效存储因素。

另外,Java 的每种基本类型所占存储空间的大小不会像其他大多数语言那样随机器硬件架构的变化而变化。这种所占存储空间大小的不变性是 Java 程序比用其他大多数语言编写的程序更具可移植性的原因之一(《Java 编程思想》2.2 节有提到)。

注意:

  1. Java 里使用 long 类型的数据一定要在数值后面加上 L,否则将作为整型解析。
  2. Java 里使用 float 类型的数据一定要在数值后面加上 f 或 F,否则将无法通过编译。
  3. char a = 'h'char :单引号,String a = "hello" :双引号。

这八种基本类型都有对应的包装类分别为:ByteShortIntegerLongFloatDoubleCharacterBoolean

基本类型和包装类型的区别?

1. 本质特性

  • 基本类型:不是对象,不具备对象的特性,例如不能调用方法。它们直接映射到硬件,因此更高效。
  • 包装类型:是对象,拥有方法和字段。对象的调用是通过引用对象的地址来实现的。

2. 传递方式

  • 基本类型:在赋值或作为参数传递时,传递的是值的副本,即值的传递。
  • 包装类型:在赋值或作为参数传递时,传递的是对象的引用,即引用的传递。

3. 声明与内存分配

  • 基本类型 :声明时不需要使用new关键字,直接在栈中分配内存空间。
  • 包装类型 :声明时需要使用new关键字,在堆中分配内存空间。

4. 存储位置

  • 基本类型:直接将值保存在栈内存中。
  • 包装类型:将对象放在堆内存中,然后通过对象的引用来调用它们。

5. 初始值

  • 基本类型 :有默认初始值,例如int的初始值为0,boolean的初始值为false
  • 包装类型 :默认初始值为null,因为它们是对象,而对象在未初始化时默认为null

6. 使用方式

  • 基本类型:直接赋值使用,适用于大多数情况下的变量声明和计算。
  • 包装类型 :主要用于需要将基本数据类型作为对象处理的情况,例如在集合框架(如CollectionMap)中存储基本数据类型时,需要使用对应的包装类型。

7. 额外功能

  • 基本类型:功能简单,仅支持基本的算术运算和类型转换。
  • 包装类型:提供了一些额外的方法和属性,可以执行一些基本数据类型无法实现的操作,例如字符串转换、数据比较等。此外,包装类型还支持自动装箱和拆箱功能,即自动将基本数据类型转换为对应的包装类对象,以及将包装类对象自动转换为对应的基本数据类型。

为什么说是几乎所有对象实例都存在于堆中呢?

这是因为 HotSpot 虚拟机引入了 JIT 优化之后,会对对象进行逃逸分析,如果发现某一个对象并没有逃逸到方法外部,那么就可能通过标量替换来实现栈上分配,而避免堆上分配内存

注意:基本数据类型存放在栈中是一个常见的误区! 基本数据类型的存储位置取决于它们的作用域和声明方式。如果它们是局部变量,那么它们会存放在栈中;如果它们是成员变量,那么它们会存放在堆/方法区/元空间中。

java 复制代码
public class Test {
    // 成员变量,存放在堆中
    int a = 10;
    // 被 static 修饰的成员变量,JDK 1.7 及之前位于方法区,1.8 后存放于元空间,均不存放于堆中。
    // 变量属于类,不属于对象。
    static int b = 20;

    public void method() {
        // 局部变量,存放在栈中
        int c = 30;
        static int d = 40; // 编译错误,不能在方法中使用 static 修饰局部变量
    }
}

包装类型的缓存机制

Java 基本数据类型的包装类型的大部分都用到了缓存机制来提升性能。

缓存范围:

  • 整型包装类(Byte、Short、Integer、Long):通常缓存了从-128到127(包括-128和127)之间的数值。这个范围是根据实际应用中整型数据的常用范围来设定的,能够覆盖大多数常用情况。
  • 字符型包装类(Character):缓存了从0到127之间的字符。这是因为ASCII字符集只定义了128个字符,而Unicode字符集的前128个字符与ASCII字符集完全相同。
  • 布尔型包装类(Boolean):只缓存了true和false两个对象。
  • 需要注意的是,浮点数类型的包装类(Float和Double)并没有实现缓存机制,主要是因为浮点数的表示范围非常大,且使用场景多样,缓存效果并不明显。

Integer 缓存源码:

java 复制代码
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static {
        // high value may be configured by property
        int h = 127;
    }
}

Character 缓存源码:

java 复制代码
public static Character valueOf(char c) {
    if (c <= 127) { // must cache
      return CharacterCache.cache[(int)c];
    }
    return new Character(c);
}

private static class CharacterCache {
    private CharacterCache(){}
    static final Character cache[] = new Character[127 + 1];
    static {
        for (int i = 0; i < cache.length; i++)
            cache[i] = new Character((char)i);
    }

}

Boolean 缓存源码:

java 复制代码
public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

如果超出对应范围仍然会去创建新的对象,缓存的范围区间的大小只是在性能和资源之间的权衡。

两种浮点数类型的包装类 Float,Double 并没有实现缓存机制。

java 复制代码
Integer i1 = 33;
Integer i2 = 33;
System.out.println(i1 == i2);// 输出 true

Float i11 = 333f;
Float i22 = 333f;
System.out.println(i11 == i22);// 输出 false

Double i3 = 1.2;
Double i4 = 1.2;
System.out.println(i3 == i4);// 输出 false

下面的代码的输出结果是 true 还是 false 呢?

java 复制代码
Integer i1 = 40;
Integer i2 = new Integer(40);
System.out.println(i1 == i2);

Integer i1 = 40 这一行代码会发生装箱,也就是说这行代码等价于 Integer i1=Integer.valueOf(40) 。因此,i1 直接使用的是缓存中的对象。

Integer i2 = new Integer(40) 会直接创建新的对象。(答案是 false 。你答对了吗?)

记住:所有整型包装类对象之间值的比较,全部使用 equals 方法比较

【强制】 所有整型包装类对象之间值的比较,全部使用 equals方法比较。

说明:对于Integer var=?在 -128 至 127 之间的赋值,Integer对象是在 IntegerCache.cache 产生会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用 equals方法进行判断。

相关推荐
Theodore_10222 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
冰帝海岸3 小时前
01-spring security认证笔记
java·笔记·spring
世间万物皆对象4 小时前
Spring Boot核心概念:日志管理
java·spring boot·单元测试
没书读了4 小时前
ssm框架-spring-spring声明式事务
java·数据库·spring
----云烟----4 小时前
QT中QString类的各种使用
开发语言·qt
lsx2024064 小时前
SQL SELECT 语句:基础与进阶应用
开发语言
小二·4 小时前
java基础面试题笔记(基础篇)
java·笔记·python
开心工作室_kaic5 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
向宇it5 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
懒洋洋大魔王5 小时前
RocketMQ的使⽤
java·rocketmq·java-rocketmq