记录一个Java引用传递的问题

简介: 前两个月一直写PHP去了,感觉好久没有写Java代码,因为最近要重构公司的Java项目,又把Java捡起来。在写代码过程中,有一个点之前其实没太注意,差点踩一脚,就通过这篇文章简单记录一下。

KJA8I@8{(GHK(5P$[Y1~%3H.jpg


前言


前两个月一直写PHP去了,感觉好久没有写Java代码,因为最近要重构公司的Java项目,又把Java捡起来。在写代码过程中,有一个点之前其实没太注意,差点踩一脚,就通过这篇文章简单记录一下。


Java值传递和引用传递


Java数据类型分为两大类,基本类型和对象类型。相应的,变量也有两种类型:基本类型和引用类型。

  • 基本类型的变量保存原始值,即它代表的值就是数值本身;
  • 引用类型的变量保存引用值,"引用值"指向内存空间的地址,代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置。

值传递只局限于四类八种基本数据类型:

image.gifIG8B15K2N__5)4HA25XWQ7S.png

除掉这四类八种基本类型,其它的都是对象,也就是引用类型,包括:类类型,接口类型和数组。

至于堆栈的内存分配,这个我就不画,也不举例,网上的资料很多。


引用传递


我们ArrayList为例看看“引用传递”,我定义了下面两个对象:

@Data
@NoArgsConstructor
public class DeptDTO {
    /**
     * 部门ID dept_code
     */
    private String deptCode;
    /**
     * 部门名称 dept_name
     */
    private String deptName;
    /**
     * 部门级别 dept_level
     */
    private Integer deptLevel;
}

示例代码:

public static void main(String[] args) {
    DeptDTO dept1 = new DeptDTO();
    DeptDTO dept2 = new DeptDTO();
    dept1.setDeptCode("111");;
    dept2.setDeptCode("222");;
    List<DeptDTO> deptList = new ArrayList<>();
    deptList.add(dept1);
    deptList.add(dept2);
    for (DeptDTO deptRecord : deptList) {
        System.out.println("Print deptCode:" + deptRecord.getDeptCode());
    }
    // 获取里面的元素,其实是获取的引用,修改该元素值,会影响personList的值
    DeptDTO dept = deptList.get(0);
    dept.setDeptCode("333");
//        // 修改该值,会直接影响deptList的值,证明deptList.add()存入的是对象的引用
//        dept.setDeptCode("444");
    for (DeptDTO deptRecord : deptList) {
        System.out.println("Print deptCode:" + deptRecord.getDeptCode());
    }
}
// 输出:
// Print deptCode:111
// Print deptCode:222
// Print deptCode:333
// Print deptCode:222

我们发现,获取deptList里面的值,然后进行修改,会直接影响到deptList里面的值,是因为我们拿到是deptList里面值的引用。可能大家觉得这个非常简单,但是写习惯Go和PHP的同学,会有些不适应,比如PHP获取数组的数据,是值传递,不是引用传递,如果你需要修改里面的数据,需要通过deptList对应的索引值直接进行修改。

我们放开上面的注释:

// 修改该值,会直接影响deptList的值,证明deptList.add()存入的是对象的引用
dept1.setDeptCode("444");
// 输出:
// Print deptCode:111
// Print deptCode:222
// Print deptCode:444
// Print deptCode:222

可以发现dept、dept1和deptList.get(0),永远都指向同一块地址区域,我们简单验证一下,最后面加入代码:

System.out.println(System.identityHashCode(dept));
System.out.println(System.identityHashCode(dept1));
System.out.println(System.identityHashCode(deptList.get(0)));
// 输出:
// 1612799726
// 1612799726
// 1612799726


问题引入


其实我的问题主要是使用ArrayList和HashMap时遇到引用传递的问题,我们再加一个对象:

@Data
public class DeptCalcuateDTO  extends DeptDTO {
    /**
     * 计算数据
     */
    private Integer calculateNumber;
}

其实我要的功能很简单,就是有一个List< DeptDTO > A,现在我需要将A中的数据转为DeptTreeDTO B,我写了如下代码:

DeptCalcuateDTO deptCalcuateDTO = (DeptCalcuateDTO) dept1;
deptCalcuateDTO.setDeptCode("333");

假如上面的语法成立,因为是引用传递,所以修改deptCalcuateDTO会影响到A中的数据,但是A是基础数据,是不允许修改的,这样就违背了我的初衷。

其实后来才发现是我想多了,因为我写Deom用例时才发现上面的强制转换行不通,因为会抛出ClassCastException()异常,但是C++支持这种类型的强制转换。


解决问题


其实就是一个拷贝的问题,我可以直接把A中的数据赋值给B,但是实际情况是里面的属性太多了,我不想每次都转换,就借用了一个工具库,感觉很好用,就记录一下。

先引入包:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.2.0.Final</version>
</dependency>

添加实体转换接口:

@Mapper(componentModel = "spring")
public interface AppDeptConverter {
    AppDeptConverter INSTANCE = Mappers.getMapper(AppDeptConverter.class);
    DeptCalcuateDTO toDeptCalcuate(DeptDTO deptDTO);
}

测试用例:

public static void main(String[] args) {
    DeptDTO dept1 = new DeptDTO();
    DeptDTO dept2 = new DeptDTO();
    dept1.setDeptCode("111");
    dept2.setDeptCode("222");
    List<DeptDTO> deptList = new ArrayList<>();
    deptList.add(dept1);
    deptList.add(dept2);
    DeptCalcuateDTO deptCalcuateDTO =  AppDeptConverter.INSTANCE.toDeptCalcuate(dept1);
    deptCalcuateDTO.setDeptCode("333");
    for (DeptDTO deptRecord : deptList) {
        System.out.println("Print deptCode:" + deptRecord.deptCode);
    }
}
// 输出:
// Print deptCode:111
// Print deptCode:222

其实这个工具做的事情很简单,生成一个DeptCalcuateDTO对象,然后将A的值依次赋值给B,详细使用方式可以参考:https://mapstruct.org/

相关文章
|
安全 Java
【面试题精讲】为什么 Java 不引入引用传递呢?
【面试题精讲】为什么 Java 不引入引用传递呢?
Java Exception异常信息怎么打印、记录,几种方式自己选
Java Exception异常信息怎么打印、记录,几种方式自己选
645 0
Java Exception异常信息怎么打印、记录,几种方式自己选
|
3月前
|
Java
java是值传递还是引用传递
本文澄清了Java中参数传递的常见误解,总结出Java采用“值传递”的方式。对于基本类型,传递其值的拷贝,方法内修改不影响原值;而对于对象类型,则传递其引用地址的拷贝,尽管是拷贝,但因指向同一对象,故方法内的修改会影响原对象状态。形参仅在方法内部有效,而实参则是调用方法时传递的具体值。通过示例和比喻(如复刻仓库钥匙),形象地解释了值传递、引用传递及Java特有的“共享对象传递”概念,帮助理解不同情况下参数传递的行为差异。
|
3月前
|
Java
java中的值传递和引用传递
【8月更文挑战第3天】在Java中,值传递用于基本数据类型,传递的是值的副本,因此方法内的修改不影响原值;而引用传递用于对象和数组,虽传递的是引用的副本,但对对象内容的修改会影响原始对象。理解这两者对于正确处理方法调用及参数至关重要。
|
3月前
|
Java
java中的值传递和引用传递
【8月更文挑战第2天】在Java中,基本数据类型如`int`、`double`等采用值传递,传递的是变量值的副本,因此方法内的修改不影响原变量。对象类型则通过引用传递,传递的是对象引用的副本,允许方法内修改原对象。例如,对`StringBuilder`对象的修改会影响原始对象。
|
5月前
|
Java
Java的值传递与“引用传递”辨析
Java的值传递与“引用传递”辨析
26 0
|
6月前
|
JavaScript 前端开发 Java
【JAVA面试题】什么是引用传递?什么是值传递?
【JAVA面试题】什么是引用传递?什么是值传递?
|
6月前
|
Java
每日一道Java面试题:Java是值传递还是引用传递?
每日一道Java面试题:Java是值传递还是引用传递?
35 1
|
6月前
|
存储 搜索推荐 Java
Java 数组、二维数组、值传递和引用传递的区别
Java 数组、二维数组、值传递和引用传递的区别
72 0
|
Java
JAVA参数传值机制中值传递和引用传递
JAVA参数传值机制中值传递和引用传递
94 0