【问题标题】:How to work-around the restriction of primitive objects - like Integer, String - not being allowed to derive from?如何解决原始对象(如整数、字符串)不允许派生的限制?
【发布时间】:2023-03-13 03:37:01
【问题描述】:

我意识到不可能从原始对象派生,因为它们被声明为最终对象。如何解决此限制?我正在使用 JPA Criteria API 进行编程。几乎在所有地方,我都使用自己的方法处理具有整数/字符串参数的方法,以与表示数据库表行值的实体字段进行比较。对于这些参数中的任何一个,我都愿意接受QueryParameter<Integer>QueryParameter<String>。这样做我将不得不第二次创建方法来接受查询参数而不是文字。然而,考虑使用置换文字和查询参数的值列表(如在 QueryBuilder 的 in(...) 方法中),很难甚至不可能实现。

让我们假设我有一个实体Car 和一个方法withFeatures(StringRepresentation ... features),并且会有文字和查询参数派生自同一个超类StringRepresentation,它本身将派生自原始类型String。我愿意这样做:

myCar.withFeatures("Seat Heating", "Metallic Color", "Trailer Hitch");
myCar.withFeatures(new QueryParam<String>("MyFavourit"));
myCar.withFeatures("Seat Heating", new QueryParam<String>("LoveThatColor"), "Trailer Hitch");

有没有人为此提供方法甚至某种解决方案?

【问题讨论】:

  • FWIW,Integer 和 String 在 Java 中都不是“原始类型”。原始类型是非类类型:int、float 等。
  • 你是对的。然而,你不能派生自,因为它们是最终的。
  • 这是因为 Integer 和 Double 之类的东西只是对应原始类型的包装器。

标签: java generics inheritance criteria-api


【解决方案1】:

到目前为止,我花了一些时间从 Java 社区获取提示来解决这个问题。 当然,我是 Java 类型安全概念的追随者(感谢 plalx)。因此,我的解决方案可能与参数化类型有关。 而且我也像许多其他人一样欣赏设计模式的概念(感谢 tgdavies)。因此,对于每种类型的标准,我使用带有一种方法的构建器模式。我将接受为

实现汽车功能方法
  1. 使用 String 的普通旧文字
  2. 以及String的指定参数

即:

myCar.withFeatures("Seat Heating", "Metallic Color", "Trailer Hitch");

以及通过使用静态方法 sp(...) 以稍微复杂的方式指定(比方说)查询参数或某种字符串参数

myCar.withFeatures(sp("MyFavourit"));

当然也是两者的混合,引入另一种静态方法 sr(...) 来表示字符串:

myCar.withFeatures(sr("Seat Heating"), sp("LoveThatColor"), sr("Trailer Hitch"));

在我们想在方法签名中使用可变参数来指定这些表示的情况下,两者的混合很重要,在这种情况下是汽车特征。 可以看出,这几乎是我在上面发布这个问题时所说的用法。 我怎样才能做到这一点?

起初我设计了一个接口来实现我的不同字符串表示:

public interface ValueTypeRepresentation<T> {
    public Class<T> getClazz();
    public QueryParameter<T> getQueryParameter();
    public RepresentationType getRepresentationType();
    public T getValue();
}

方法是确定表示是字面量还是参数,并分别获取字面量的值。参数本身稍后使用其名称。 clazz 成员是为了简化 Java 通用类型推断的目的,因为我将使用参数化类型来实现不同的类型表示。正如我所说,String 只是演出的开始。

然后我设计了一个抽象类来派生不同原始对象的具体表示类:

abstract class AbstractValueTypeRepresentation<T> implements ValueTypeRepresentation<T>  {
    private Class<T> clazz;
    private RepresentationType representationType = RepresentationType.VALUE;
    private QueryParameter<T> queryParameter;
    private T value;

    public AbstractValueTypeRepresentation(Class<T> clazz, T value) {
        this.clazz = clazz;
        this.representationType = RepresentationType.VALUE;
        this.value = value;
    }

    public AbstractValueTypeRepresentation(QueryParameter<T> qp) {
        this.clazz = qp.getClazz();
        this.representationType = RepresentationType.PARAM;
        this.queryParameter = qp;
    }

    @Override
    public Class<T> getClazz() {
        return clazz;
    }

    @Override
    public QueryParameter<T> getQueryParameter() {
        return queryParameter;
    }

    @Override
    public RepresentationType getRepresentationType() {
        return representationType;
    }

    @Override
    public T getValue() {
        return value;
    }
}

为了区分该类型的文字和该类型的查询参数,我引入了这个枚举:

public enum RepresentationType {
    PARAM, VALUE;
}

然后我设计了第一个具体的表示,这里用于我的 StringRepresentation(派生自上面的抽象类):

public class StringRepresentation extends AbstractValueTypeRepresentation<String>  {
    public static StringRepresentation sr(String s) {
        return new StringRepresentation(s);
    }

    public static StringRepresentation sp(String name) {
        return new StringRepresentation(new QueryParameter<String>(String.class, name));
    }

    public StringRepresentation(String value) {
        super(String.class, value);
    }

    public StringRepresentation(QueryParameter<String> queryParameter) {
        super(queryParameter);
    }
}

显然,这很容易扩展到 Integer、Float、LocalDate 等的表示。

【讨论】:

    【解决方案2】:

    使用置换文字和查询参数,很难甚至不可能实现

    您可以将CharSequence 用于字符串,但我不确定这是个好主意...

    import lombok.RequiredArgsConstructor;
    
    public class Test {
        public static void main(String[] args) {
            withFeatures("test", new StringQueryParam("test2"));
        }
    
        @SafeVarargs
        public final static <T extends CharSequence> void withFeatures(T ...params) {
            // Wrap in StringQueryParam if not an instance of QueryParam<String>
        }
    
        interface QueryParam<T> {
        }
    
        @RequiredArgsConstructor
        static class StringQueryParam implements QueryParam<String>, CharSequence {
            private final CharSequence value;
    
            @Override
            public int length() {
                return value.length();
            }
    
            @Override
            public char charAt(int index) {
                return value.charAt(index);
            }
    
            @Override
            public CharSequence subSequence(int start, int end) {
                return value.subSequence(start, end);
            }
        }
    }
    

    将不太冗长的静态工厂方法(例如用于查询参数的QueryParam.ofQueryParam.all 等)与构建器或有效组合它们的方法混合使用会有所帮助。

    例如

    // Assuming Lists.union util method
    withFeatures(Lists.union(
        QueryParam.all("a", "b"),
        QueryParam.of("c")
    ));
    
    // With static imports
    withFeatures(union(params("a", "b"), param("c"));
    
    // With ParamsBuilder
    withFeatures(ParamsBuilder.of("a", "b").add(QueryParam.of("c").build())));
    
    

    希望这能给您一些关于如何设计 API 的想法!您也可以使用更复杂但更灵活的路线,其中整个标准只是一个 AST,因此 QueryParam 真的只是 AST 中的一种 Expression 允许创建复合等。如果您查看 QueryDSL 一切是DslExpression,并且您有访问者对树执行操作。

    【讨论】:

    • 非常感谢您的回答。它给了我很多启发。如果我可以将自己限制为 String,我会说“就是这样”。但弦乐只是演出的开场。我的数据库字段也有Integer、Float、LocalDate等类型。
    • 您必须在易用性和类型安全之间做出让步。我更喜欢这里的类型安全。
    【解决方案3】:

    我会为每种类型的标准使用一种方法的构建器模式。

    class Car {
      private Set<String> features = new HashSet();
    
      public Car withFeature(String f) {
        features.add(f);
        return this;
      }
    
      public Car withFeature(QueryParameter<String> q) {
        features.add(q.getStringRepresentation()); // or whatever
        return this;
      }
      ...
    }
    

    所以你可以说:

    myCar.withFeature("Seat Heating")
       .withFeature(new QueryParam<String>("MyFavourit");
    

    【讨论】:

    • 非常感谢您的回答。但是,如果涉及到与 withFeatures(String ... features) 一样的可变参数方法,这种方法并不能让我免于使用多重方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-23
    • 2015-04-20
    • 2018-09-26
    • 2012-09-14
    相关资源
    最近更新 更多