【问题标题】:Can you restrict a type parameter <T> to multiple specific classes您可以将类型参数 <T> 限制为多个特定类吗
【发布时间】:2021-02-08 00:24:26
【问题描述】:

我正在编写一个泛型类Bla,类型参数为T

我可以限制T,只使用我想支持的类吗?

public class Bla<T> {
    private T foo;
    private Class<T> fooClazz;
}

我希望 Bla 支持大多数 原始 类(Enum、Boolean、Integer、String...),以及我自己的接口 Supportable

public interface Supportable {
    void doSpecific(Bla _bla);
}

Bla 有一个方法do(),它处理支持的类,如果使用了我不支持的类,则抛出异常。

public void do() { 
    if (Enum.class.isAssignableFrom(fooClazz)) {
        // Do Enum specific code
    } else if (Boolean.class.isAssignableFrom(fooClazz)) {
        // Do Boolean specific code
    } else if (Integer.class.isAssignableFrom(fooClazz)) {
        // Do Integer specific code
    } else if (String.class.isAssignableFrom(fooClazz)) {
        // Do String specific code
    } else if (Supportable.class.isAssignableFrom(fooClazz)) {
        ((Supportable) foo).doSpecific();
    } else {
        throw new UnsupportedDataTypeException(fooClazz + "is not supported");
    }
}

我知道我能做到。

public class Bla<T extends Number> {}

所以只能使用扩展Number 的类,但是有这样的吗?

public class Bla<T extends Number | String> {}

所以String 也是可能的?

我能想到的唯一解决方案是为不同类型创建多个 Bla 类。

public class BlaEnum {}
public class BlaBoolean {}
public class BlaInteger {}
public class BlaString {}
public class BlaSupportable {}

【问题讨论】:

  • 你确定泛型是正确的解决方案吗,do() 方法看起来像是泛型类中不需要的东西。
  • 这些“支持的类”有什么共同点?为什么你想要一个“通用”类,它只支持有限的一组没有共同点的类?
  • 您可以尝试为这些类中的每一个(class BlaAdapter&lt;T&gt;class BlaEnum extends BlaAdapter&lt;Enum&gt;)制作适配器,但我会先听从 tucuxi 的建议
  • 我想将 Bla 用作 Message 类,并希望将 T foo 用作带有 getter T getID() 的标识符,这给了我指定的类型。我想使用泛型的原因是我不必对所有消息只使用 String 或 int。并且在Object getID() 之后没有将其转换为 (String, int, ...)。

标签: java class generics bounded-wildcard


【解决方案1】:

所以继续我的进度。

在@Iman 提示使用工厂模式之后,我开始使用静态重载工厂方法来实现它。 这是我使用EnumBooleanSupportable 接口作为示例生成的代码。它类似于 @Andreas 发布的代码。

public static <E extends Enum<E>> Bla<E> createBla(E id) {
    return new Bla.EnumBla<E>((Class<E>) id.getClass()).setID(id);
}
public static Bla<Boolean> createBla(boolean id) {
    return new Bla.BooleanBla().setID(id);
}
public static <S extends Supportable> Bla<S> createBla(S id) {
    return new Bla.SupportableBla<S>((Class<S>) id.getClass()).setID(id);
}

我决定为我想要支持的类型创建 Bla 的静态子类。

public abstract class Bla<T>   {
    private T foo;
    private Bla() {}
    public T getFoo() { return foo; }
    public Bla<T> setFoo(T foo) { this.foo = foo; return this; }
    public abstract void do();

    public static class EnumBla<E extends Enum<E>> extends Bla<E> {
        private final Class<E> fooClazz;
        public EnumBla(Class<E> fooClazz) { super(); this.fooClazz = fooClazz; }
        @Override
        protected void do() { // Do Enum specific code}
    }
    public static class BooleanBla extends Bla<Boolean> {
        public BooleanBla() { super(); }
        @Override
        protected void do() { // Do Boolean specific code }
    }
    public static class SupportableBla<S extends Supportable> extends Bla<S> {
        private final Class<S> fooClazz;
        public SupportableBla(Class<S> fooClazz) { super(); this.fooClazz = fooClazz; }
        @Override
        protected void do() { if(super.id != null) super.id.doSpecific(this); }
    }
}

我在BooleanBla 中没有fooClazz,因为那里不需要它。 此外,我无法完全删除“嵌套”if 语句,因为我希望能够在没有所需 foo 类型实例的情况下创建 Bla

    public static <C, E extends Enum<E>, S extends Supportable> Bla<C> createBla(Class<C> fooClazz) throws UnsupportedDataTypeException {
        if (Enum.class.isAssignableFrom(fooClazz))
            return (Bla<C>) new Bla.EnumBla<E>((Class<E>) fooClazz);
        if (fooClazz == Boolean.class || fooClazz == boolean.class)
            return (Bla<C>) new Bla.BooleanBla();
        if (Supportable.class.isAssignableFrom(idClazz))
            return (Bla<C>) new Bla.SupportableBla<S>((Class<S>) fooClazz);
        throw new UnsupportedDataTypeException(
                "[" + fooClazz+ "] is not a supported Bla foo\n\t\t" +
                    "supported types are " +
                    "[" + Enum.class + "] " +
                    "[" + Boolean.class + "] " +
                    "[" + Supportable.class + "]");
    }

【讨论】:

    【解决方案2】:

    限制它的一种方法是使用静态重载工厂方法来构造对象。

    public class Bla<T> {
        private final T foo;
        private final Class<T> fooClazz;
    
        private Bla(T foo, Class<T> fooClazz) { // Must be private
            this.foo = foo;
            this.fooClazz = fooClazz;
        }
    
        @SuppressWarnings("unchecked")
        public static <E extends Enum<E>> Bla<E> of(E foo) { // Caveat: Cannot handle null
            return new Bla<>(foo, (Class<E>) foo.getClass());
        }
        public static Bla<Boolean> of(Boolean foo) {
            return new Bla<>(foo, Boolean.class);
        }
        public static Bla<Integer> of(Integer foo) {
            return new Bla<>(foo, Integer.class);
        }
        public static Bla<String> of(String foo) {
            return new Bla<>(foo, String.class);
        }
        public static Bla<Supportable> of(Supportable foo) {
            return new Bla<>(foo, Supportable.class);
        }
    
        public void do() {
            // ...
        }
    
        // ...
    }
    

    它改变了调用者构造实例的方式,但实际上也简化了它,因为调用者不必传入Class&lt;T&gt;,例如

    // With constructor (old way)
    Bla<MyEnum> e2 = new Bla<>(MyEnum.A, MyEnum.class);
    Bla<Boolean> b2 = new Bla<>(true, Boolean.class);
    Bla<Integer> i2 = new Bla<>(42, Integer.class);
    Bla<String> s2 = new Bla<>("", String.class);
    Bla<Supportable> su2 = new Bla<>(supportable, Supportable.class);
    
    // With static factory method (new way)
    Bla<MyEnum> e1 = Bla.of(MyEnum.A);
    Bla<Boolean> b1 = Bla.of(true);
    Bla<Integer> i1 = Bla.of(42);
    Bla<String> s1 = Bla.of("");
    Bla<Supportable> su1 = Bla.of(supportable);
    
    // Unsupported types are not allowed
    Bla<Double> i1 = Bla.of(3.14); // Error: The method of(E) in the type Bla is not applicable for the arguments (double)
    

    但是,与其在do() 方法中使用多路if 语句,不如使用子类。子类对调用者是隐藏的,因此它没有外部区别,但它消除了对多路if 语句/switch 语句的需要:

    public abstract class Bla<T> {
        private final T foo;
        private final Class<T> fooClazz;
    
        private Bla(T foo, Class<T> fooClazz) { // Must be private
            this.foo = foo;
            this.fooClazz = fooClazz;
        }
    
        @SuppressWarnings("unchecked")
        public static <E extends Enum<E>> Bla<E> of(E foo) { // Caveat: Cannot handle null
            return new Bla<>(foo, (Class<E>) foo.getClass()) {
                @Override
                public void do() {
                    // Do Enum specific code
                }
            };
        }
        public static Bla<Boolean> of(Boolean foo) {
            return new Bla<>(foo, Boolean.class) {
                @Override
                public void do() {
                    // Do Boolean specific code
                }
            };
        }
        public static Bla<Integer> of(Integer foo) {
            return new Bla<>(foo, Integer.class) {
                @Override
                public void do() {
                    // Do Integer specific code
                }
            };
        }
        public static Bla<String> of(String foo) {
            return new Bla<>(foo, String.class) {
                @Override
                public void do() {
                    // Do String specific code
                }
            };
        }
        public static Bla<Supportable> of(Supportable foo) {
            return new Bla<>(foo, Supportable.class) {
                @Override
                public void do() {
                    foo.doSpecific(this);
                }
            };
        }
    
        public abstract void do(); // Is now abstract
    
        // ...
    }
    

    如果您愿意,当然可以创建(私有)静态嵌套类或(包私有)顶级类,而不是匿名类。

    使用子类允许在多个方法中执行fooClass 特定的操作。如果您只有一种方法,则可以改用 lambda 表达式和/或方法引用:

    public class Bla<T> {
        private final T foo;
        private final Class<T> fooClazz;
        private final Consumer<Bla<T>> doImpl;
    
        private Bla(T foo, Class<T> fooClazz, Consumer<Bla<T>> doImpl) { // Must be private
            this.foo = foo;
            this.fooClazz = fooClazz;
            this.doImpl = doImpl;
        }
    
        @SuppressWarnings("unchecked")
        public static <E extends Enum<E>> Bla<E> of(E foo) { // Caveat: Cannot handle null
            return new Bla<>(foo, (Class<E>) foo.getClass(), bla -> {
                // Do Enum specific code
            });
        }
        public static Bla<Boolean> of(Boolean foo) {
            return new Bla<>(foo, Boolean.class, bla -> {
                // Do Boolean specific code
            });
        }
        public static Bla<Integer> of(Integer foo) {
            return new Bla<>(foo, Integer.class, bla -> {
                // Do Integer specific code
            });
        }
        public static Bla<String> of(String foo) {
            return new Bla<>(foo, String.class, bla -> {
                // Do String specific code
            });
        }
        public static Bla<Supportable> of(Supportable foo) {
            return new Bla<>(foo, Supportable.class, foo::doSpecific);
        }
    
        public void do() {
            doImpl.accept(this);
        }
    
        // ...
    }
    

    【讨论】:

      【解决方案3】:

      您想要支持的类的通用超类型(考虑到您的 Supportable 类型)是 Object 但是,如果您将类定义为继承自 Object(默认情况下)并实现 Supportable,则你不能以这种方式限制你的泛型类。

      如果我是你,我会使用工厂模式并编写一个接受任何事物(Object 的子类)及其类型的方法来实例化 Bla 的相关实例。 Create 方法应该是通用的,但您不能在此处施加任何限制,只有当类型不可接受时您才能抛出异常。我知道,这不是您可能期望的答案类型。但是没有其他方法(在你的情况下)。

      P.S 对于那些认为 OP 做错了什么的人,我们不应该在现实世界中进行这样的设计。我想介绍一个例子。假设您要编写一个假设创建内存表的类(就像我们在数据库中所做的那样,但使​​用 java 数据类型)。您还想支持用户的数据类型!那你会怎么做呢?

      【讨论】:

      • 我目前正在研究在这里实现工厂模式。到目前为止,它适用于整数,我看起来没问题,但我不确定我是否要为每种支持的类型重载 create 方法,或者有一个方法是我测试提供的参数是哪种类型,一旦我完成它对于我所有的类型,我会发布一个关于我是如何做到的答案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-06-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多