Java的引用(Reference)数据类型概述

Java的引用(Reference)数据类型概述

Java中的引用类型包括类(class)、接口(interface)、数组(array)等,在Java中,除了基本数据类型(包括8种:byte、short、int、long、float、double、boolean、char),其余的都是引用类型。当我们声明一个引用类型的变量时,实际上是在栈内存中分配了一个引用变量,该变量指向堆内存中的对象。

引用类型主要包括以下几种:

  1. 类(Class:用户自定义的类型,通过class关键字定义。类的实例化将创建一个对象,这个对象的引用可以被赋值给引用变量。
  2. 接口(Interface:一种特殊的抽象类型,用interface关键字定义。它定义了一组方法规范,具体的类可以实现这些方法。
  3. 数组(Array:可以存储多个同类型元素的容器。数组本身是一个对象,因此数组的引用可以被赋值给引用变量。
  4. 枚举(Enum:使用enum关键字定义的一种特殊类类型,它包含了固定数量的常量。Java枚举(Enum)是在Java 5版本中引入的。

除了这些,Java还有几种特殊的引用类型:

  • 字符串(String:虽然字符串在Java中表现得像是基本数据类型,但它实际上是一个不可变的引用类型。
  • 泛型(Generics:泛型不是一种具体的引用类型,而是一种在编译时提供类型检查和消除类型强制转换的机制。泛型可以用在类、接口和方法中,创建类型安全的集合。
  • 集合框架(Collections Framework:包括List, Set, Map等接口及其实现类,这些都是引用类型。
  • 函数式接口(Functional Interface:任何接口,如果只包含一个抽象方法,那么它就是一个函数式接口。Java 8 引入了@FunctionalInterface注解来表示一个接口是函数式接口。这些接口通常用于lambda表达式和方法引用。
  • 记录(Record:Java 14 引入了记录(record),这是一种特殊的类,它是不可变的,并且自动实现了数据传输对象(DTO)的一些常规功能,如访问器、equals、hashCode和toString。
  • 异常类(Exception:异常对象表示程序执行中的错误情况,它们都是Throwable类的子类。

在Java中,所有的引用类型都继承自java.lang.Object类。这意味着任何引用类型的变量都可以指向一个Object类型的实例。引用类型的变量存储的是对象的引用(内存地址),而不是对象本身。

在Java中,当您声明一个引用类型的变量时,确实是在Java的栈内存(Stack Memory)中创建了一个引用变量。这个引用变量存储了一个指向堆内存(Heap Memory)中实际对象的地址。这里有一些补充和澄清的点:

  1. 栈内存(Stack Memory:这是一个线程私有的内存区域,用于存储局部变量和方法调用的上下文。当您在方法中声明一个引用类型的变量时,这个变量会被分配到调用该方法的线程的栈内存中。
  2. 堆内存(Heap Memory:这是一个应用程序共享的内存区域,用于存储由所有线程创建的对象。无论对象是由哪个线程创建,它们都存储在堆内存中。
  3. 引用变量:引用变量可以被视为指针,它指向堆内存中的对象。当您创建一个对象时(例如,使用new关键字),对象本身存储在堆内存中,而对象的引用(内存地址)存储在栈内存中的变量中。
  4. 引用的赋值:当您将一个引用变量赋值给另一个变量时,您实际上是在复制内存地址。这意味着两个变量现在都指向堆内存中的同一个对象。
  5. 垃圾收集(Garbage Collection:当堆内存中的对象不再被任何引用变量所指向时,这个对象就成为了垃圾收集器的回收目标。垃圾收集器会定期运行,释放这些不再使用的对象占用的内存。
  6. 空引用(Null Reference:引用变量可以被显式地赋值为null,表示它不指向堆内存中的任何对象。尝试通过一个null引用来访问对象的成员会导致NullPointerException。
  7. 传递机制:在Java中,对象引用作为参数传递给方法时,传递的是引用的副本,这意味着方法接收的是原始引用的一个拷贝。因此,方法可以通过这个引用来修改堆内存中的对象,但是方法不能改变传入的引用变量本身所指向的对象。

Java中基本类型直接存储值,它们的大小和操作是固定的。如:int: 32位有符号整数,long: 64位有符号整数等,这些类型的操作(如加法、减法、乘法等)也是由语言规范明确定义的。

引用类型存储的是对对象的引用,即对象在内存中的地址。对象本身可以包含多个值,并且可以拥有操作这些值的方法。

为了进一步说明这一点,可以使用一个简单的比喻:

基本类型就像是你口袋里的现金,你可以直接拿出来使用。

引用类型就像是你钱包里的信用卡,它代表了你的账户,而账户里可以有很多信息和资金,但你需要通过信用卡这个“引用”来访问这些内容。

下面通过一些简单的Java代码示例来说明基本类型和引用类型在赋值和传递参数方面的不同行为。

基本类型的赋值和传递参数

首先,我们来看一个基本类型的例子。在这个例子中,我们将演示基本类型变量的赋值和方法调用时的值传递。

public class PrimitiveExample {
    public static void main(String[] args) {
        int a = 10;
        int b = a; // 赋值操作,b现在是a的一个副本,与a相互独立
        System.out.println("Before change, a = " + a + " and b = " + b);
        
        changeValue(a); // 尝试在方法中改变a的值
        System.out.println("After change, a = " + a + " and b = " + b);
    }
    
    public static void changeValue(int number) {
        number = 50; // 这里改变的是number的副本,原始的a值不会改变
    }
}

当你运行这个程序时,你会看到输出结果是:

Before change, a = 10 and b = 10
After change, a = 10 and b = 10

这说明尽管我们在changeValue方法中尝试改变number的值,但实际上a的值并没有改变,因为基本类型是按值传递的。

这说明尽管我们在changeValue方法中尝试改变number的值,但实际上a的值并没有改变,因为基本类型是按值传递的。

引用类型的赋值和传递参数

接下来,我们来看一个引用类型的例子。在这个例子中,我们将演示引用类型变量的赋值和方法调用时的引用传递。

public class ReferenceExample {
    public static void main(String[] args) {
        int[] a = {10}; // 引用类型,数组
        int[] b = a; // 赋值操作,b现在是a的引用,指向同一个数组对象
        System.out.println("Before change, a[0] = " + a[0] + " and b[0] = " + b[0]);
        
        changeValue(a); // 尝试在方法中改变数组a的第一个元素
        System.out.println("After change, a[0] = " + a[0] + " and b[0] = " + b[0]);
    }
    
    public static void changeValue(int[] array) {
        array[0] = 50; // 改变的是数组对象的第一个元素,a和b都会受到影响
    }
}

当你运行这个程序时,你会看到输出结果是:

Before change, a[0] = 10 and b[0] = 10
After change, a[0] = 50 and b[0] = 50

这说明当我们在changeValue方法中改变数组对象的第一个元素时,由于a和b都引用同一个数组对象,它们都受到了影响。这是因为引用类型是按引用传递的。

你可能感兴趣的:(Java学习,java)