繁体   English   中英

Java generics 铸造及使用

[英]Java generics cast and usage

我对 generics inheritance 有一点问题。 下面是依赖树:

public class Attributes {
}

public abstract class Feature<T extends Attributes> {
    
    private T attributes;

    public T getAttributes() {
        return attributes;
    }

    public void setAttributes(T attributes) {
        this.attributes = attributes;
    }
}

public abstract class AbstractFeatureRepository<T extends Feature<? extends Attributes>> {
    public abstract String getType();

    public abstract boolean create(T feature);
}

我有这些功能存储库的实现,如下所示:

public class NodeAttributes extends Attributes {
    
    private String startPoint;

    public String getStartPoint() {
        return startPoint;
    }

    public void setStartPoint(String startPoint) {
        this.startPoint = startPoint;
    }
}

public class Node extends Feature<NodeAttributes> {
}

public class NodeRepository extends AbstractFeatureRepository<Node> {
    public String getType() {
        return "Node";
    }
    public boolean create(Node node) {
        return true;
    }
}

public class LinkAttributes extends Attributes {
    
    private String uri;

    public String getUri() {
        return uri;
    }

    public void setUri(String uri) {
        this.uri = uri;
    }

}

public class Link extends Feature<LinkAttributes> {
}

public class LinkRepository extends AbstractFeatureRepository<Link> {
    public String getType() {
        return "Link";
    }
    public boolean create(Link link) {
        return true;
    }
}

我通过构造函数将这些存储库与 Spring 注入到 Controller 中(但在此示例中,为了简单起见,我在构造函数中手动创建):

public class Controller {
    
    private final Map<String, AbstractFeatureRepository<? extends Feature>> featureRepositories;
    
    public Controller() {
        this.featureRepositories = Arrays.asList(new NodeRepository(), new LinkRepository()).stream()
                .collect(Collectors.toMap(AbstractFeatureRepository::getType, Function.identity()));
    }
    
    public Node createNode() {
        Node newNode = new Node();
        newNode.getAttributes().setStartPoint("Berlin");
        createFeature("Node", newNode);
        return newNode;
    }

    public Link createLink() {
        Link newLink = new Link();
        newLink.getAttributes().setUri("/home/local");
        createFeature("Link", newLink);
        return newLink;
    }
    
    
    private void createFeature(String type, Feature<? extends Attributes> feature) {
        featureRepositories.get(type).create(feature);
    }

}

一切都很好,直到我想在通用“createFeature”中调用“create”方法,我得到编译错误

AbstractFeatureRepository<capture#5-of? 类型中的方法 create(capture#5-of? extends Feature) extends Feature> 不适用于 arguments(Feature<capture#6-of? extends Attributes>)

我做错了什么? 这是因为我可能会传递某种其他类型的“功能”而不是特定的“存储库”可以使用吗? 那么我应该如何在 Controller 中更改我的 map 存储库,以便编译器不会抱怨? 我虽然应该操作精确的类作为 map 的关键,但不知道如何使所有这些工作。 有什么建议么?

谢谢你。

更新 1 我将 Map 更改为

private final Map<Class<?>, AbstractFeatureRepository<? extends Feature>> featureRepositories;

更改了 AbstractFeatureRepository,现在看起来是这样的:

public abstract class AbstractFeatureRepository<T extends Feature> {
    
    public abstract Class<T> getClazz();
    
    public abstract boolean create(T feature);
}

并更改了 controller 中的方法:

    public Link createLink() {
        Link newLink = new Link();
        createFeature(Link.class, newLink);
        return newLink;
    }
    
    
    private <T extends Feature> void createFeature(Class<T> class1, T feature) {
        AbstractFeatureRepository<? extends Feature> abstractFeatureRepository = featureRepositories.get(feature.getClass());
        abstractFeatureRepository.create(abstractFeatureRepository.getClazz().cast(feature));
    }

它仍然不允许我这样做。

这段代码:

featureRepositories.get(type)

根据 java.util.Map 的文档,返回一个java.util.Map ,其类型是Map<K, V> V的 V 。 在您的代码中,这意味着表达式的类型为AbstractFeatureRepository<? extends Feature<? extends Attributes>> AbstractFeatureRepository<? extends Feature<? extends Attributes>> AbstractFeatureRepository<? extends Feature<? extends Attributes>>

让我们稍微简化一下,假设我们有List<? extends Number> List<? extends Number>

这是有效的 java 代码:

List<? extends Number> list = new ArrayList<Integer>();

这是什么意思? extends ? extends ,真的。 这不会编译:

List<Number> list = new ArrayList<Integer>();

现在,想象一下你在你的List<? extends Number> List<? extends Number>

List<? extends Number> list = new ArrayList<Integer>();
Number n = Double.valueOf(5.0);
list.add(n);

呃。 我的整数列表中有一个非整数。 哎呀。

这就是为什么你根本不能在这里调用add()的原因。 您不能在List<? extends whatever>上调用add List<? extends whatever> ,完全 任何将T作为参数的方法,其中您的类型是Foo<? extends T> Foo<? extends T>不能被调用*。

现在让我们 go 回到您的代码:

你有一个AbstractFeatureRepository<? extends Feature<? extends Attributes>> AbstractFeatureRepository<? extends Feature<? extends Attributes>> AbstractFeatureRepository<? extends Feature<? extends Attributes>> - 因此AbstractFeatureRepository具有的任何采用T的方法都不能从中调用。 一点也不。 create就是这样一种方法。

解决方案有点棘手。 您可以使用类型安全的容器,如果您以某种方式引用了代表 T 的 class(小心;事物可能是不能是 Class 的TList<Integer>是 T,但只有List.class存在,不能写List<Integer>.class : - 你可以使用它:

public <T extends Attribute> void createFeature(Class<T> typeDesired, Feature<T> feature) {
    featureRepositories.get(type).create(typeDesired.cast(feature));
}

是一种方式。

一般来说,您的方法签名是有问题的:无法保证您的String type暗示所需的功能类型Feature<? extends Attribute> Feature<? extends Attribute>由与该类型匹配的存储库处理。

第二种选择是让每个AbstractFeatureRepository负责处理类型不匹配。 在这种情况下,您可以更新接口以改为读取create(Object feature) throws TypeMismatchException 或者,让它返回一个类型( public Class<T> getType() ),你可以 go 返回到cast构造。

*) 好吧,你可以调用它,如果你按字面传递null ,因为 null 是所有数据类型。 但这显然不是您打算在这里做的,因此不相关。

如果您在 Map 中只有 2 个东西(或 N 个东西,其中 N 是一个小数字,您在评论中提到 4),并且您使用固定键来指示特定类型,使用 Map 比必要的。

映射必然是同质类型的,也就是说,所有的键都有一个共同的超类型,所有的值都有一个共同的超类型。 您面临的问题是您希望键的类型与值的类型相关:这可以通过类型安全的异构容器来完成(本质上是:您构建的 map 以便您可以对关系施加约束类型之间)。 但即使这对于所描述的问题来说也过于复杂。

改为使用两个 (N) 字段:

public class Controller {
    private final NodeRepository nodeRepository = new NodeRepository();
    private final LinkRepository linkRepository = new LinkRepository();

这仍然是一种 map:键是字段,值是字段值。

但这样做的好处是您保留了存储库的具体类型,因此您可以将它们传递给方法:

private <A extends Attributes> void createFeature(AbstractFeatureRepository<A> repo, Feature<A> feature) {
    repo.create(feature);
}

例如

public Node createNode() {
    Node newNode = new Node();
    newNode.getAttributes().setStartPoint("Berlin");
    // Or, easier, nodeRepository.create(newNode);
    createFeature(nodeRepository, newNode);
    return newNode;
}

public Link createLink() {
    Link newLink = new Link();
    newLink.getAttributes().setUri("/home/local");
    // Or, easier, linkRepository.create(newNode);
    createFeature(linkRepository, newLink);
    return newLink;
}

为了得到一个尽可能接近原始代码的可行解决方案,我进行了三到四个相对较小的重构。

最显着的变化是Controller.createFeature() ......

private <T extends Feature<?>> void createFeature(Class<T> class1, T feature) {
    AbstractFeatureRepository<T> abstractFeatureRepository = (AbstractFeatureRepository<T>)featureRepositories.get(feature.getClass());
    abstractFeatureRepository.create(feature);
}

在我看来,演员阵容是最简单、最安全的解决方案。 我确信演员表是类型安全的原因是因为如果演员表不存在你会得到编译错误:

Controller.java:31: error: incompatible types: AbstractFeatureRepository<CAP#1> cannot be converted to AbstractFeatureRepository<T>
    AbstractFeatureRepository<T> abstractFeatureRepository = featureRepositories.get(feature.getClass());

where T is a type-variable:
    T extends Feature<?> declared in method <T>createFeature(Class<T>,T)
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Feature<?> from capture of ? extends Feature<?>
1 error

如果您仔细阅读错误消息的底部,您会发现T extends Feature<?>CAP#1 extends Feature<?>之间的唯一区别是两个类型变量的名称。 它们都具有相同的上限( extends Feature<?> )。 这告诉我有理由推断强制转换是类型安全的。

所以,我用SuppressWarnings("unchecked")注释了该方法。

为了确认该解决方案是可用的,我用toString()充实了NodeLink类。 解决方案演示中调用Controller.createNode()Controller.createLink()可以让您……

Node: [NodeAttributes - Start Point: 'Berlin']

Link: [LinkAttributes - URI: 'urn::foo::bar']

我不得不承认,您要解决的问题对我来说不是很清楚。 因此,我仅根据我的一般 Java 知识做出了一些假设。 请让我知道解决方案是否满足您的要求?

这是我使用的方法:

public class Controller {

    private final Map<Class<?>, AbstractFeatureRepository<? extends Feature>> featureRepositories;

    public Controller3(List<AbstractFeatureRepository<? extends Feature>> featureRepositories) {
        this.featureRepositories = featureRepositories.stream()
                .collect(Collectors.toMap(AbstractFeatureRepository::getClazz, Function.identity()));
    }

    public Node createNode() {
        Node newNode = new Node();
        createFeature(Node.class, newNode);
        return newNode;
    }

    public Link createLink() {
        Link newLink = new Link();
        createFeature(Link.class, newLink);
        return newLink;
    }

    private <T extends Feature> void createFeature(Class<T> clazz, T feature) {
        AbstractFeatureRepository<T> repository = getRepository(clazz);
        repository.create(feature);
    }
    
    @SuppressWarnings("unchecked")
    private <T extends Feature, V extends AbstractFeatureRepository<T>> V getRepository(Class<T> clazz) {
        return (V) featureRepositories.get(clazz);
    }

    public static void main(String[] args) {
        Controller3 controller = new Controller3();
        controller.createLink();
    }
}

它不完全满足无强制转换的方法(尤其是没有@SuppressWarnings),但它对我来说是最不邪恶的,因为强制转换仅在 controller 中的一种方法中完成,所有 rest 方法都没有强制转换,也没有@SuppressWarnings。

尝试

private static <T extends AbstractFeatureRepository> void createFeature(Class<T> clazz, Feature<? extends Attributes> feature) {
    ((T) featureRepositories.get(clazz)).create(feature);
}

您应该相应地修改 featureRepositories

private static final Map<Class<?>, AbstractFeatureRepository<? extends Feature>> featureRepositories

但我不建议像这样使用 generics 。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM