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

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

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

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

  1. 类(Class:用户自定义的类型,通过class关键字定义。类的实例化将创建一个对象,这个对象的引用可以被赋值给引用变量。以及Java库中的类,如String、Scanner、ArrayList等。
  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位有符号整数等,这些类型的操作(如加法、减法、乘法等)也是由语言规范明确定义的。

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

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

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

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

扫描二维码关注公众号,回复: 17261604 查看本文章

下面通过一些简单的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都引用同一个数组对象,它们都受到了影响。这是因为引用类型是按引用传递的。

补充

1.Java中处理字符串的类

在Java中,处理字符串的类主要有以下几种:

String:这是一个不可变的字符序列。由于其不可变性,每次修改String都会生成新的String对象。【https://docs.oracle.com/javase/8/docs/api/java/lang/String.html

StringBuilder:这是一个可变的字符序列,提供了一系列方法来修改字符串的内容,适用于单线程环境下的字符串操作。【https://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html

StringBuffer:StringBuffer和StringBuilder非常相似,也是一个可变的字符序列,但它是线程安全的。StringBuffer的方法都是同步的,可以在多线程环境下安全使用,但这也意味着它比StringBuilder在性能上稍慢。

这三种是Java中最常用的字符串类型。通常,如果不需要可变字符串或者线程安全,就使用String;如果需要可变字符串且运行在单线程环境下,就使用StringBuilder;如果需要在多线程环境中操作字符串,就使用StringBuffer。

不可变(Immutable):不可变对象的状态在创建后就不能被改变。对于String类来说,这意味着一旦一个String对象被创建,它所包含的字符序列就不能被改变。如果你尝试修改String对象,实际上会创建一个新的String对象,原来的字符串对象内容不会改变。这就是为什么执行字符串连接操作时,会消耗更多的内存,因为每次连接操作实际上都在创建新的字符串对象。

可变(Mutable):相反,可变对象可以在创建后改变其状态。StringBuilder类就是一个可变的字符串类,它提供了各种方法来修改字符串的内容,如append、insert、delete等,而不需要每次都创建一个新的对象。这使得StringBuilder在执行大量字符串修改操作时比String更加高效。

2.Java“不可变”(Immutable)和“可变” (Mutable

Java中,“不可变”和“可变”对象的概念是根据对象状态是否可以在创建后被改变来定义的。下面是一些Java中常见的不可变和可变对象的例子:

不可变对象(Immutable Objects):

  1. String:如前所述,String对象一旦创建,其值就不能被改变。
  2. 包装类:所有的基本类型包装类,如Integer、Long、Double等【注,基本数据类型及其对应的包装类:Integer包装类的官方文档链接https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html 】,都是不可变的。
  3. BigInteger 和 BigDecimal:用于高精度计算的类,它们也是不可变的。
  4. java.time 包下的类:Java 8 引入的日期和时间类,如LocalDateLocalTimeLocalDateTimeZonedDateTime等,都是不可变的。

可变对象(Mutable Objects):

  1. StringBuilder 和 StringBuffer:这两个类提供了可变的字符序列。
  2. 数组:任何类型的数组,如int[]Object[]等,都是可变的,因为你可以改变数组中的元素。
  3. 集合类:大多数Java集合类,如ArrayListLinkedListHashSetHashMap等,都是可变的,因为你可以添加、删除或更改集合中的元素。
  4. java.util.Date 和 Calendar:这些旧的日期时间类是可变的,可以通过各种方法修改它们的状态。

猜你喜欢

转载自blog.csdn.net/cnds123/article/details/134341745