彻底区分:基本数据类型、引用数据类型和包装类型

1.是什么

        在Java中,基本数据类型、包装类型和引用数据类型是三种不同的数据类型,它们在创建方式、存储方式、默认值以及使用场景等方面存在显著差异。

        

一、基本数据类型(Primitive Types)

定义
        基本数据类型是指Java语言中内置的数据类型,这些类型不是对象,而是直接存储数据的值。Java中一共有8种基本类型,包括:

  • 整数类型:byte、short、int、long
  • 浮点类型:float、double
  • 字符类型:char
  • 布尔类型:boolean

#注意String 是一个独立的,存在于 java.lang 包中,它用于表示和处理字符串。虽然 String 类与基本数据类型的包装类一样,都是引用类型,但它并不对应于任何基本数据类型。String 类实际上是 Object 类的直接子类,因此它继承了 Object 类的所有方法,并添加了字符串操作特有的方法。

    String 是一个类,因此 String 类型的变量实际上是存储了一个引用,指向在堆(Heap)内存中的 String 对象。除了 String,其他引用数据类型还包括类(Class)、接口(Interface)和数组(Array)等。

特点

  1. 不可变性:基本类型的值是不可变的,即一旦赋值,其值就不能被改变(虽然可以通过重新赋值来改变变量所持有的值,但原值本身是不可变的)。
  2. 直接存储:基本类型的值直接存储在栈内存中,访问速度较快。
  3. 默认值:在声明时未显式初始化的基本类型变量会自动被赋予默认值(如int的默认值为0,boolean的默认值为false)。
  4. 基本数据类型的比较是值的比较
  5. 值传递:在方法调用时,基本类型是按值传递的,即传递的是变量值的副本。

举例

int a = 10; // 声明并初始化一个基本类型的int变量  
int b = a; // b是a的一个副本,改变b的值不会影响a

        尽管它们都存储了相同的值 10。重要的是要注意,b = a; 是一个值的复制操作,而不是引用或指针的赋值。这意味着如果你随后修改了 a 或 b 的值,另一个变量不会受到影响,因为它们在栈上有独立的存储空间。


注意区分:当基本数据类型作为的字段(属性)时,它们是作为对象的一部分存储在上的。以下是为什么基本数据类型的类属性存储在堆上的原因:

  1. 对象封装: 在 Java 中,类的实例是一个对象,它封装了一系列的状态和行为。一个对象的所有字段(无论是基本数据类型还是引用类型)都必须在对象的内存空间内,这样对象才能作为一个整体被处理。因此,当创建一个对象时,对象的内存空间是在堆上分配的,包括它所有的字段。

  2. 对象的生命周期: 对象的生命周期不由方法调用的栈帧决定,而是由垃圾回收器管理的。如果基本数据类型的字段存储在栈上,它们的生命周期将与栈帧的生命周期绑定,这会导致对象的状态和行为不一致,因为对象的某些部分可能会在方法调用结束后消失。

  3. 引用的一致性: 如果一个对象的字段存储在栈上,那么每次方法调用时都可能创建字段的不同副本,这会导致引用同一个对象的不同线程看到不同的状态,从而破坏线程间的数据一致性。

  4. 内存管理: 堆内存是 Java 虚拟机(JVM)管理对象实例的主要方式。JVM 有一个垃圾回收器(Garbage Collector, GC),它负责清理不再使用的对象实例。如果基本数据类型的字段存储在栈上,那么 JVM 将需要额外的机制来管理这些字段的内存,这将增加复杂性。

        总结来说,基本数据类型的类属性存储在堆上是为了保持对象的完整性、一致性以及由 JVM 统一管理内存。当基本数据类型作为局部变量时,它们存储在栈上,因为它们是方法调用的一部分,并且它们的生命周期仅限于当前栈帧。这是两种不同场景下的内存管理策略。

例子:

public class Example {
    public int number; // 基本数据类型属性
    public String text; // 对象引用类型属性

    public Example(int number, String text) {
        this.number = number; // 存储基本数据类型值
        this.text = text; // 存储对象引用
    }
}

在这个例子中,当创建 Example 类的实例时:

Example ex = new Example(10, "Hello");
  • ex.number 的值 10 是一个 int 类型的基本数据类型,它的值会直接存储在 Example 类实例的堆内存空间中。

  • ex.text 的值 "Hello" 是一个 String 对象的引用(引用地址),它存储在堆内存中的另一个位置(字符串常量池中)。ex.text 实际上存储的是指向该 String 对象的内存地址(即引用)。

基本数据类型和对象引用类型的主要区别在于存储方式:

  • 基本数据类型:值直接存储在对象实例的内存空间中。
  • 对象引用类型:存储的是指向实际对象的引用(内存地址),实际对象存储在堆内存的其他位置(字符串常量池中)

在 Java 中,基本数据类型的属性总是直接存储在它们所属的对象实例的堆内存空间中。


二、包装类型(Wrapper Classes)

定义
        包装类是将基本数据类型封装成对象的形式,以便能够使用对象的方法。Java为每一种基本类型都提供了对应的包装类,如Integer对应int,Double对应double等。

Java中的包装类型包括:

  • byte - Byte
  • short - Short
  • int - Integer
  • long - Long
  • float - Float
  • double - Double
  • char - Character
  • boolean - Boolean

#首字母都是大写

特点

  1. 对象性:包装类是对象,拥有方法和字段,通过引用对象的地址来调用。
  2. 引用传递:包装类型是按引用传递的,传递的是对象的引用而非对象本身。(重点!!!)
  3. 存储位置:包装类的对象存储在堆内存中,通过栈上的引用来访问。
  4. 默认值:包装类型在声明时未显式初始化的变量默认值为null。
  5. 自动装箱与拆箱:Java提供了自动装箱(将基本类型自动转换为包装类型)和自动拆箱(将包装类型自动转换为基本类型)的机制,简化了编码工作。
  6. 使用场景:包装类型主要用于需要将基本数据类型作为对象处理的场景,如将基本数据类型存储在集合中。

自动装箱和自动拆箱是什么?:

        自动装箱(Autoboxing)和自动拆箱(Unboxing)是Java语言提供的特性,它们允许基本数据类型(Primitive Types)和它们对应的包装类(Wrapper Classes)之间进行隐式的转换。装箱其实就是调⽤了 包装类的 valueOf() ⽅法,拆箱其实就是调⽤了 xxxValue() ⽅法

public class Test {
    public static void main(String[] args) {
        Integer a = new Integer(10); // 使用new关键字显式创建Integer对象
        Integer b = 10; // 自动装箱,等同于Integer b = Integer.valueOf(10);
        int c = b; // 自动拆箱,将Integer对象转换为int值
    }
}

//注意代码的注释部分!!!

例题:

public class Test {
    public static void main(String[] args) {
        Integer a = 1000;
        int b = 1000;
        System.out.println(a == b);
        System.out.println(a.equals(b));
    }
}

答案:true  true


补充:

  1. == 运算符:

    • 当用于基本数据类型(如 int)时,== 比较的是两个值的相等性。
    • 当用于对象引用时,== 比较的是两个引用是否指向内存中的同一个对象,即它们的地址是否相同。
  2. .equals() 方法:

    • .equals() 是 Object 类中的一个方法,用于比较两个对象的值。在 Integer 类中,.equals() 方法被重写,用于比较两个 Integer 对象的值(而不是它们的地址)。

解释:

  1. 自动装箱Integer a = 1000; 这行代码中,1000 是一个基本类型int的值,但是它被自动装箱成一个Integer对象。这是Java编译器自动完成的。

  2. 基本类型int b = 1000; 这行代码定义了一个基本类型int的变量b,并直接赋值为1000

现在来解释两个System.out.println调用:

  • System.out.println(a == b);

    • 在这里,==运算符用于比较两个操作数。由于a是一个对象,而b是一个基本类型,所以这里发生了隐式的拆箱,将Integer对象a转换成基本类型int,然后比较它们的值。因此,这个表达式的结果是true,因为ab的值都是1000
  • System.out.println(a.equals(b));

    • equals方法是Object类中的一个方法,被Integer类重写以比较两个Integer对象的值。在这个例子中,b是一个基本类型int的值,所以在调用equals方法时,b装箱成一个Integer对象,然后比较这个对象与a对象的值。由于ab的值都是1000,所以这个表达式的结果也是true

为了更好地理解上面的补充部分,我将再出一道例题:

public class Test {
    public static void main(String[] args) {
        Integer a = 1000;
        Integer b = 1000;
        System.out.println(a == b); 
        System.out.println(a.equals(b)); 

    }
}

答案:false    true

解释:== 比较的是两个对象的地址,因为它们是不同的对象,地址不同,所以输出 false

.equals() 方法用于比较两个 Integer 对象的值(而不是它们的地址)所以为true

图解:


三、引用数据类型(Reference Types)

定义
        除了基本类型和包装类型以外的所有类型都被称为引用数据类型,如类(Class)、接口(Interface)、数组(Array)等。引用数据类型存储的是对象在堆内存中的地址,通过这个地址来访问对象。

特点

  1. 对象性:引用数据类型是对象,可以添加属性和方法。它包括类(Class)、接口(Interface)、数组(Array)等,用于表示复杂的数据结构或行为。
  2. 引用传递:在方法调用时,引用数据类型是按引用传递的,即传递的是对象的引用(即对象的地址)。通过栈上的引用来访问堆上的对象。
  3. 存储位置:引用类型的对象存储在堆内存中,而引用(即对象的地址)存储在栈内存中。
  4. 默认值:引用类型的默认值也是null,表示不指向任何对象。
  5. 使用场景:引用数据类型通常用于表示复杂的数据结构或行为,如自定义的类、接口实现等。在集合框架、文件I/O、网络通信等场景中广泛使用。

解释第2点:

测试1:

public class Test {
    public static void main(String[] args) {
       int[] arr1={1,2,3};
       int[] arr2=arr1;
       arr1=null;
       System.out.println(arr2[0]);
       System.out.println(arr1[0]);
    }
}

答案:1,报错NullPointerException

解释:

        需要注意的是,数组变量之间的复制是浅复制(shallow copy),这意味着如果数组包含引用类型的元素,那么这些引用(地址)会被复制,但它们所引用的对象本身(对象存储在堆中)不会被复制。在上面的代码中,arr2arr1指向同一个数组,所以当arr1被设置为null时,arr2仍然指向同一个数组,因为它并没有被设置为null

测试2:

public class Test {
    public static void main(String[] args) {
       int[] arr1={1,2,3};
       int[] arr2=arr1;
        arr2=new int[5];
       System.out.println(arr2[0]);
       System.out.println(arr1[0]);
    }
}

答案:0,1

        由于arr2之前已经指向了arr1数组,这里实际上是在创建一个新的数组并让arr2指向它。因此,arr2不再指向原来的arr1数组,而是指向了这个新的数组。arr1arr2指向的是不同的数组,所以它们数组里的元素不同。

总结:引用数据类型赋值传递的是对象的引用(即对象的地址),(int[ ] arr2 = arr1;是将arr1的地址复制给arr2)除了改变对象arr2的数据值(即存储到堆里面的元素)会影响arr1的值,但是改变对象arr2的引用(设置为空null或者创建一个新的对象new int[5])只要不是改变堆里面的值,都不会影响arr1,因为他们是独立的。


总结:

基本数据类型包装类型引用数据类型
定义Java内置的数据类型将基本数据类型封装成对象除了基本类型和包装类型以外的所有类型
对象性不是对象是对象是对象
存储位置栈内存堆内存(对象),栈内存(引用)堆内存(对象),栈内存(引用)
默认值有默认值(如0、false)nullnull
传递方式值传递引用传递引用传递
示例int a = 10;Integer a = 10;(自动装箱)String a = new String("Hello");

         好啦,今天的干货就到这啦~    有什么建议或疑问的小伙伴欢迎到评论区留言!

猜你感兴趣的方面:

#你还能区分逻辑运算符吗?

#如何区分++i和i++???

#强制类型转换是什么?

#自动类型转换有哪些细节???

#JVM、JRE、JDK之间的关系

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值