Loading...
墨滴

楼仔

2021/11/09  阅读:19  主题:橙心

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

前言

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

Java的值传递和引用传递

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

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

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

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

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

引用传递

我们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/

欢迎大家多多点赞,更多文章,请关注微信公众号“楼仔进阶之路”,点关注,不迷路~~

楼仔

2021/11/09  阅读:19  主题:橙心

作者介绍

楼仔