前言
前两个月一直写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/