详解数据转换类(对象)(DTO) / 详解@JSONField
package xxx; import com.alibaba.fastjson.annotation.JSONField; ... @Data public class Container { @JSONField(name = "containerNeedList") private List<RequestContainer> requestContainer; public Set<ExCtnRequest> getObject() { Set<ExCtnRequest> exCtnRequests = new HashSet<>(); if (CollectionUtils.isNotEmpty(requestContainer)) { for (RequestContainer item : requestContainer) { ExCtnRequest exCtnRequest = new ExCtnRequest(); exCtnRequest.setIsSoc("SOC".equals(item.getSupplierType()) ? OrderConstants.YES : OrderConstants.NO); exCtnRequest.setQuantityOfCtn(item.getQuantity()); ... exCtnRequest.setFclLclEmpty("F"); exCtnRequests.add(exCtnRequest); } } return exCtnRequests; } }
而前面代码提到的RequestContainer类的定义如下:
package xxx; import xxx; @Data public class RequestContainer { // xxx private BigDecimal quantity; // xxx private String supplierType; ... }
一、@JSONField注解
@JSONField(name = "containerNeedList")是Fastjson库中一个注解,它用于将Java对象的字段与JSON数据中的字段进行映射。
这个注解的主要作用是指定Java类的字段在序列化和反序列化时与JSON数据的键名进行对应。
具体来说,它告诉Fastjson库在序列化或反序列化时,如何将Java类的属性与JSON中的字段进行匹配。
详细解读:
1.注解来源:@JSONField是Fastjson提供的一个注解,用于控制JSON和Java对象之间的映射。
它属于com.alibaba.fastjson.annotation.JSONField包。
2.作用:name属性指定了Java对象字段对应的JSON字段名称。
也就是说,
当Java对象被转换为JSON字符串时,containerNeedList这个Java字段会映射成JSON字符串中的"containerNeedList"字段。
同样,当JSON字符串被反序列化回Java对象时,JSON中"containerNeedList"字段会被映射到Java类的containerNeedList字段上。
3.示例:假设有如下的Java类:
public class Container {
@JSONField(name = "containerNeedList")
private List<RequestContainer> requestContainer;
// getter, setter
}
在这个例子中,
requestContainer是Java类中的字段名,
而@JSONField(name = "containerNeedList")注解表明该字段将被映射为JSON中的"containerNeedList"。
序列化示例:假设我们有一个Container对象,其requestContainer字段包含了某些数据:
Container container = new Container();
container.setRequestContainer(someList); //someList是一个List<RequestContainer>类型的数据
使用 Fastjson 库进行序列化时:
String json = JSON.toJSONString(container);
生成的 JSON 字符串将会是这样的:
{
"containerNeedList": [ ... ] // 对应的 List<RequestContainer> 内容
}
即便 Java 类中字段的名称是 requestContainer,
但 JSON 中的字段名却是 "containerNeedList",
这是因为@JSONField(name = "containerNeedList")注解指定了映射关系。
4.用途:
字段名称不一致:当Java类中的字段名称与JSON中的字段名称不一致时,使用@JSONField可以轻松解决映射问题。
兼容性:在进行跨系统数据交换时,可能会遇到字段命名规范不同的情况。使用@JSONField可以保证数据在传输过程中不受影响。
反序列化:当JSON数据反序列化成Java对象时,Fastjson会根据@JSONField注解自动进行字段匹配。
二、@JSONField注解是作用在类Container上了还是requestContainer上了?
注解作用范围:
注解一般作用于它所修饰的元素。
在Java中,注解是直接应用在类、字段、方法、参数等元素上的。
在你提到的例子中:@JSONField注解作用于requestContainer字段。
如果你把注解写在方法、类或者变量声明上,它只会影响被注解的那个方法、类或字段。注解不会影响它下面的代码行,注解的作用范围仅限于紧接着它的元素。
三、如果不写@JSONField注解,它俩能一一对应地映射上吗?
默认映射规则:
- 如果 JSON 字段名和 Java 类中的属性名一致,则可以不写 @JSONField 注解,Fastjson 会自动映射。
- 如果 JSON 字段名和 Java 类中的属性名不一致,则需要写 @JSONField 注解来显式指定映射关系。
四、@JSONField注解既可以作用在类(级别)上,又可以作用在属性(级别)上吗?
@JSONField注解可以用于类字段或类上,依赖于它被应用的具体位置,作用范围也有所不同。
1. @JSONField注解用于类字段(属性):
- 作用:当@JSONField注解应用于类的字段(即属性)时,它控制该字段与JSON数据中某个字段的映射。
- 使用场景:如果Java类中的属性名与JSON字段名不一致,可以通过@JSONField注解来显式地指定映射关系。
例如:
public class Container { @JSONField(name = "containerNeedList") private List<RequestContainer> requestContainer; }
- 在这个例子中,requestContainer属性会映射为JSON 中的 "containerNeedList" 字段。无论该类其他部分如何,只有被标注的字段(requestContainer)与containerNeedList进行映射。
2. @JSONField注解用于类:
- 作用:@JSONField注解也可以应用于类上,通常用于在序列化时改变JSON对象的根元素名称(即类的名称)。
- 使用场景:如果你希望 JSON 数据的根对象名和类名不一致,可以在类级别上使用 @JSONField 注解来指定。
例如:
@JSONField(name = "container") public class Container { private List<RequestContainer> requestContainer; }
- 在这个例子中,整个Container类的对象在序列化时,JSON对象的根字段将被命名为 container,而不是类名Container。这意味着Container类本身的映射会变成JSON中的container。
五、代码解读
首先,代码定义了一个名为Container的类,包含了一个列表,名为requestContainer,类型是List<RequestContainer>,以及一个getObject方法,返回一个Set<ExCtnRequest>类型的集合。
getObject方法的解读:
1.初始化exCtnRequests:
创建了一个HashSet<ExCtnRequest>类型的exCtnRequests集合。这将用于存储转换后的ExCtnRequest对象。
关于Set<ExCtnRequest> exCtnRequests = new HashSet<>();
(1)为啥不能new Set<>()而是new HashSet()<>?
因为:
Set<ExCtnRequest> exCtnRequests = new Set<>(); // ❌ 语法错误
- Set 是一个接口(interface),接口不能直接被实例化。
- 你必须 new它的一个具体实现类,比如HashSet、LinkedHashSet、TreeSet等。
✅ 正确用法:
Set<ExCtnRequest> exCtnRequests = new HashSet<>();
(2)为啥是用Set<ExCtnRequest>类型,而不是 List、Map、或者别的?
因为在业务需求中,你只想要一个"无重复元素"的集合,而不是顺序列表或键值对。
- Set 的特点:不允许元素重复。
- List:允许重复元素,有顺序。
- Map:是键值对结构。
在这段代码里:
exCtnRequests.add(exCtnRequest);
可能会有多个重复的ExCtnRequest对象。如果你不希望这些重复的数据被加入,就用Set。
也就是说:使用Set的目的是为了自动去重!
(3)为啥是HashSet?
- HashSet是最常用的Set实现,底层是哈希表,插入和查找的性能非常高(平均O(1))。
- 它不保证顺序(也就是你加入元素的顺序,取出来时可能变了)。
- 如果你想要一个有顺序的Set,可以考虑LinkedHashSet。
- 如果你想要一个自动排序的Set,可以考虑TreeSet。
所以:
Set<ExCtnRequest> exCtnRequests = new HashSet<>();
这是一种非常常见、性能优良、去重需求明确的写法。
(4)是不是实例化Set的固定搭配就是new HashSet<>()?
是的,如果你没有特殊的顺序需求,new HashSet<>()是最常见、最推荐的默认搭配。
总结一波:
为什么不能new Set()? | 因为Set是接口,不能被实例化。 |
为什么要用Set<E>而不是List<E>? | 因为你需要去重的集合。 |
为什么用HashSet? | 因为它是Set最常用的实现,性能好,且你不关心元素顺序。 |
Set<E> = new HashSet<>()是不是固定搭配? | 是,除非你有顺序或排序的特殊需求。 |
2.解读 for(RequestContainer item: requestContainer) {}:
这个item根本没有被定义,直接使用不会报错吗?
在这段代码中,item是在for循环中隐式定义的,它是requestContainer列表中的每一个元素。
具体来说,requestContainer是List<RequestContainer> 类型的集合,而RequestContainer是一个对象类型。
因此,item是指requestContainer列表中每次迭代时取出的一个RequestContainer 对象。
看一下这一行:
for (RequestContainer item : requestContainer)
这里使用了增强的for循环(也叫for-each循环)。在 Java 中,增强的for循环的语法如下:
for (类型 变量名 : 集合/数组) { // 循环体 }
在这个例子中,RequestContainer item指定了循环中每个元素的类型和变量名。
item变量就代表了requestContainer列表中的每一个RequestContainer对象。
在每一次循环迭代时,item就会被赋值为requestContainer中的一个RequestContainer元素,直到列表遍历结束。
为什么不会报错?
item是由Java编译器隐式定义的变量,它在for循环内自动赋值。
只要requestContainer是一个合法的List<RequestContainer>,item就可以直接使用,不需要提前单独定义。
如果没有定义requestContainer或requestContainer的类型不是List<RequestContainer>,那肯定会报错。
这个item变量只有在requestContainer被正确初始化并包含了RequestContainer类型的元素时才会生效。
所以,item的定义是隐式的,是循环的一部分,并且它的作用域仅限于for循环内部。
3.这句增强for循环语句里,只有item是可以未曾事先定义的
✅ 增强for循环的语法:
for (类型 变量名 : 集合或数组) { // 循环体 }
🔹 各部分说明:
(1). 类型:
- ✅ 必须是集合(Collection)或数组中元素的类型。
- ❗必须是一个已经定义过的类型(可以是类、接口或基本类型)。
- 会自动根据右边集合中的每个元素类型来匹配。
例如:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); for (String name : names) { System.out.println(name); // name 是 String 类型 }
(2). 变量名:
- ✅ 是临时变量名,不需要提前定义。
- ❗只在循环内部有效,相当于每次从集合/数组中取出来的元素临时放在这个变量里。
你可以随便起名,只要有意义就好:
for (int score : scores) // score 是每次取出的元素
(3). 集合/数组:
- ✅ 必须是一个已经存在且可以遍历的集合或数组。
- ❗如果是集合,必须实现了Iterable接口(比如 List、Set、Queue 都行)。
- ❗这个集合/数组变量必须已经赋过值,否则会报错或空指针。
例如:
int[] numbers = {1, 2, 3, 4}; for (int n : numbers) { System.out.println(n); }
🔁 增强 for 循环做了什么?
其实它背后是自动帮你完成了这种结构:
for (int i = 0; i < arr.length; i++) { int n = arr[i]; // do something }
或者对于集合就是用迭代器:
for (Iterator<Type> it = collection.iterator(); it.hasNext(); ) { Type element = it.next(); // do something }
✅ 总结你说的三点:
类型需要提前定义? | ✅ 是的 | 必须是集合/数组中的元素类型 |
变量名不需要提前定义? | ✅ 是的 | 只在循环里起作用 |
集合/数组要预先定义? | ✅ 是的 | 否则不知道从哪里取数据 |
4.其余代码的解读
(1) exCtnRequest.setIsSoc("SOC".equals(item.getSupplierType()) ? OrderConstants.YES : OrderConstants.NO);
exCtnRequest.setIsSoc("SOC".equals(item.getSupplierType()) ? OrderConstants.YES : OrderConstants.NO);
这行代码的目的是:根据item.getSupplierType()是否等于字符串"SOC",来设置exCtnRequest的isSoc字段的值。
如果 "SOC"==item.getSupplierType(),则 isSoc = OrderConstants.YES;
如果"SOC" !=item.getSupplierType(),则 isSoc = OrderConstants.NO;
什么是OrderConstants.YES,什么是OrderConstants.NO,怎么看起来怪怪的?
别忘了OrderConstants是一个常量类:
public class OrderConstants { public static final String YES = "Y"; public static final String NO = "N"; }
这回理解了吧,相当于就是 isSoc = "Y" || isSoc = "N"了。
(2) 其余
然后每个生成的ExCtnRequest对象会被添加到exCtnRequests集合中。
最后返回exCtnRequests:循环完成后,返回exCtnRequests集合,该集合包含了所有转换后的ExCtnRequest对象。