避免在 List & Map 中使用 Java 中的 '?' 关键字进行非常长的类型参数



我一直在做一个类,在那里我遇到了具有非常大的 Type 值的地图和列表。例如

Map<String, Map<String, Map<String, ...>>> map = new HashMap<>();

List<List<Map<String,...>>> list = new ArrayList<>();

循环访问此类数据类型会使样板代码看起来非常丑陋!

for(Map<String, Map<String, ...>> e : map.entrySet()){
    //do something...
    for(Map<String, ..> e1 : e.entrySet()){
       ..more such loops
    }
}

我想出了一个解决方案来使用"?"关键字减小模板的大小以下是我的解决方案

Map<String, Map<String, Map<String, Long>>> map = ....
for(Entry<String, ?> e : map.entrySet()){
    Map<String , ?> mapLevel1 = (Map<String, ?>)e.getValue();
    for(Entry<String, ?> e1 : mapLevel1.entrySet()){
        Map<String, ?> mapLevel2Map = (Map<String, ?>) e1.getValue();
        for(Entry<String, ?> e2 : mapLevel2Map.entrySet()){
            Long data =  (Long)e2.getValue();
            .....
        }
    }
}

采用这种方法会有什么潜在的问题吗?谢谢期待!

?的意思是"某种类型,但我不知道是哪种类型"(大约)。由于您确实知道类型是什么,因此它并不真正合适。

采用这种方法是否存在任何潜在问题

getValue之后到处都需要的演员阵容(没有?就不需要)是一个非常重要的问题,我什至不会称之为"潜力"。如果类型的任何部分发生变化,祝你好运,找到你需要改变的演员表和什么。

编辑:从Java 10开始,你可以做

for(var e : map.entrySet()){
    var mapLevel1 = e.getValue();
    for(var e1 : mapLevel1.entrySet()){
        var mapLevel2Map = e1.getValue();
        for(var e2 : mapLevel2Map.entrySet()){
            // or var again
            Long data = e2.getValue();
            .....
        }
    }
}

让编译器推断类型并检查一切是否有意义。

正如其他人指出的那样,使用?更糟糕。 别这样。

您应该在 IDE 中启用所有编译器警告(如果在命令行上构建,请使用 -Xlint)。 这将通知您强制转换为泛型类型是一种不安全的操作。

保持整洁的一个好方法是创建封装这些 Map 的实际数据类。

例如,您可以替换以下内容:

Map<String, Map<String, Map<String, Boolean>>> map = new HashMap<>();

有了这个:

Person person = new Person();

受以下三个类支持:

public class Person {
    private final Map<String, Address> addressesByType = new HashMap<>();
    public Set<String> getAddressTypes() {
        return new HashSet<>(addressesByType.keySet());
    }
    public Address getAddress(String type) {
        return addressesByType.get(type);
    }
    public void addAddress(String type,
                           Address address) {
        Objects.requireNonNull(type);
        Objects.requireNonNull(address);
        addressesByType.put(type, address);
    }
}

和:

public class Address {
    public static final String TYPE_HOME = "Home";
    public static final String TYPE_WORK = "Work";
    private final Map<String, Vehicle> vehiclesByType = new HashMap<>();
    public Set<String> getVehicleTypes() {
        return new HashSet<>(vehiclesByType.keySet());
    }
    public Vehicle getVehicle(String type) {
        return vehiclesByType.get(type);
    }
    public void addVehicle(String type,
                           Vehicle vehicle) {
        Objects.requireNonNull(type);
        Objects.requireNonNull(vehicle);
        vehiclesByType.put(type, vehicle);
    }
}

最后:

public class Vehicle {
    public static final String TYPE_PERSONAL = "Personal";
    public static final String TYPE_BUSINESS = "Business";
    private final Map<String, Boolean> inspectionsByDate = new HashMap<>();
    public Set<String> getInspectionDates() {
        return inspectionsByDate.keySet();
    }
    public Boolean getInspectionOutcome(String date) {
        return inspectionsByDate.get(date);
    }
    public void addInspection(String date,
                              boolean outcome) {
        Objects.requireNonNull(date);
        inspectionsByDate.put(date, outcome);
    }
}

然后,您的循环将如下所示:

for (String addressType : person.getAddressTypes()) {
    Address address = person.getAddress(addressType);
    for (String vehicleType : address.getVehicleTypes()) {
        Vehicle vehicle = address.getVehicle(vehicleType);
        for (String date : vehicle.getInspectionDates()) {
            boolean outcome = vehicle.getInspectionOutcome(date);
            // ...
        }
    }
}

(以上只是一个例子。 显然,在现实生活中,键将是枚举值,日期将是LocalDate或Date对象,并且人们可以拥有多个地址和多个用于特定目的的车辆。

您可以以类似的方式封装列表; 例如,请参阅节点列表。

采用这种方法会有什么潜在的问题吗? 谢谢期待!

代码不可读和泛型丢失的好处是,您必须强制转换Map方法返回的值,就像使用原始类型一样。

在任何情况下,拥有如此重要的深层结构可能会产生运行时错误,因为潜在的实例化缺失,并使代码难以阅读和维护。

您应该改进设计并引入自定义类来包装映射并提供用于添加和检索数据的逻辑方法。
您还应该将图书馆视为番石榴。
例如,Table 是一个很好的候选项,可以在您的操作类型中引入一些抽象。

最新更新