【问题标题】:Dynamic reference casting depending on actual object type取决于实际对象类型的动态引用转换
【发布时间】:2019-03-27 18:33:00
【问题描述】:

我有类层次结构,例如

ChildA extends Parent
ChildB extends Parent
ChildC extends Parent

然后在我的应用程序中,我通过父引用获取任何这个孩子的方法。

问题是所有这些孩子都有相同的方法,但他们的父母没有

所以,ChildA, ChildB, and ChildC 都有 getSomeValue() 吸气剂,但 Parent 没有。

现在我需要解析来自任何这个孩子的值,但 Parent 参考没有为我提供 API,所以我需要将 Parent 转换为特定的孩子类型。

下面是代表我正在尝试做的 sn-p:

private void processChildren(Parent parent) {
    ChildA childA = null;
    ChildB childB = null;
    ChildC childC = null;

    if (parent instanceof ChildA) {
        childA = parent;
    }

    if (parent instanceof ChildB) {
        childB = parent;
    }

    if (parent instanceof ChildC) {
        childC = parent;
    }

    String someValue;

    if (Objects.nonNull(childA)) {
        someValue = childA.getSomeValue();
    } // and the same checks and extracts for each of childs and for many methods

}

如您所见,为了仅提取一个值,我需要创建 3 个引用,然后检查它们以强制转换为特定类型,然后检查实际创建的类型以调用该方法。

问题是如何在运行时正确地将引用转换为特定的子引用? 我想可以使用反射来编写,尽管即使使用反射我也无法解决。

此外,即使可能 - 可以这样做吗?

仅供参考:我正在开发一个遗留应用程序,因此我无法更改以前编写的代码,因此我无法在父类中添加此 API。此外,这些类是从外部 jar 提供的。

【问题讨论】:

  • 您能否将父类的引用更改为子类扩展的不同子类?像 ChildA --> SubParent --> Parent 这样的东西
  • 您可以使用 instanceof 运算符来检查确切的实例类型
  • 您能否添加一个代码示例,您如何通过父引用获取此子项的任何方法
  • getSomeValue()的返回类型是什么?
  • 这有关系吗?字符串

标签: java dynamic reflection


【解决方案1】:

作为一种可能的解决方案,您可以创建一个由特定子类参数化的映射作为键,供应商作为值。每个供应商都负责铸造和处理特定的方法。

Map<Class<? extends Parent>, Supplier> getValueMap = new HashMap<>();

getValueMap.put(ChildA.class, () -> { return ((ChildA) parent).getValue(); });
getValueMap.put(ChildB.class, () -> { return ((ChildB) parent).getValue(); });
getValueMap.put(ChildC.class, () -> { return ((ChildC) parent).getValue(); });

getValueMap.get(parent.getClass()).get();

【讨论】:

  • 哇!伟大的解决方案! :) 尽管您必须在 Map 中为每个子类创建条目。
【解决方案2】:

问题是如何正确转换肯定是的参考 这种类型中的哪一种要具体参考?

使用 instanceof 运算符检查 Parent 引用的确切实例类型。

    if (parent instanceof ChildA) {
        ChildA childA = (ChildA) parent;
    } else if (parent instanceof ChildB) {
        ChildB childB = (ChildB) parent;
    } else if (parent instanceof ChildC) {
        ChildC childAC = (ChildC) parent;
    }

【讨论】:

  • 嗨,谢谢,但问题包含“正确”一词,所以如果我有一百个孩子,而不是你的解决方案,我会有一个很长的方法。我正在寻找一个好的解决方案
  • 你能像我在上一条评论中问的那样对父类进行子类化吗? ChildA --> SubParent --> Parent 然后在您的应用程序中使用 SubParent 引用而不是 Parent 引用。
  • 对不起,这些类是从外部 jar 提供的,所以我无权访问它们
  • 你的意思是所有的类,即所有的孩子和父母都来自外部罐子吗?
  • 是的。我可以以任何方式使用它们,但不能改变
【解决方案3】:

如果你确定所有的孩子都有 getSomeValue 方法,你可以称他们为

parent.getClass().getMethod("getSomeValue").invoke(parent);

但是,这不是一个好的做法。类设计违反了 Liskov 的替换原则here

【讨论】:

    【解决方案4】:

    我的方法是通过缺少的接口扩展Parent。为此,我定义了一个声明缺失方法的接口:

    public interface HasSomeValue {
    
        String getSomeValue();
    
    }
    

    因为不可能让Parent 自己实现这个接口,所以我为Parent 定义了一个包装器:

    private static class ParentWithSomeValue implements HasSomeValue {
    
        private Parent parent;
    
        public ParentWithSomeValue(Parent parent) {
            this.parent = parent;
        }
    
        @Override
        public String getSomeValue() {
            try {
                Method method = parent.getClass().getMethod("getSomeValue");
                return (String) method.invoke(parent);
            } catch (Exception e) {
                return null;
            }
        }
    }
    

    此外,ParentWithSomeValue 委托给Parent 已经拥有的其他方法。那么ParentWithSomeValue提供了Parent的完整接口,可以替换。

    getSomeValue 是使用反射实现的。如果parent 具有被调用的方法,则调用将使用反射委托给parent。否则返回默认值。在我的例子中null。如果parent 必须具有被调用的方法,另一种选择是抛出异常。

    使用这种方法,您还可以扩展已实现的接口或添加更多接口并在每个方法的基础上实现委托。

    将此应用于您的代码:

    private void processChildren(Parent parent) {
        if (Objects.nonNull(parent)) {
            ParentWithSomeValue parentWithSomeValue = new ParentWithSomeValue(parent);
            String someValue = parentWithSomeValue.getSomeValue();
            // go on
        } 
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-28
      • 2018-10-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多