你能将类型参数<T>限制为多个特定类吗?

huangapple go评论190阅读模式
英文:

Can you restrict a type parameter <T> to multiple specific classes

问题

我正在编写一个带有类型参数 T 的通用类 Bla

我是否可以限制 T,以便只能使用我想要支持的类?

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

我希望 Bla 支持大多数基本类(枚举、布尔、整数、字符串等),还有我自己的接口 Supportable

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

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

public void do() { 
	if (Enum.class.isAssignableFrom(fooClazz)) {
		// 执行枚举特定代码
	} else if (Boolean.class.isAssignableFrom(fooClazz)) {
		// 执行布尔特定代码
	} else if (Integer.class.isAssignableFrom(fooClazz)) {
		// 执行整数特定代码
	} else if (String.class.isAssignableFrom(fooClazz)) {
		// 执行字符串特定代码
	} else if (Supportable.class.isAssignableFrom(fooClazz)) {
		((Supportable) foo).doSpecific();
	} else {
		throw new UnsupportedDataTypeException(fooClazz + "不受支持");
	}
}

我知道我可以这样做。

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 {}
英文:

I am writing a generic class Bla with type parameter T.

Can I restrict T, so that only classes I want to support can be used?

public class Bla&lt;T&gt; {
	private T foo;
	private Class&lt;T&gt; fooClazz;
}

I want Bla to support most primitive classes (Enum, Boolean, Integer, String, ...), and also my own interface Supportable.

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

Bla has a method do(), which handles the supported classes, or throws an exception if a class that I don't support is used.

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 + &quot;is not supported&quot;);
	}
}

I know I can do this.

public class Bla&lt;T extends Number&gt; {}

So only classes that extend Number can be used, but is there something like this?

public class Bla&lt;T extends Number | String&gt; {}

So that also a String is possible?

The only solution I can think of, is to make multiple Bla classes for the different types.

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

答案1

得分: 2

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

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

    private Bla(T foo, Class<T> fooClazz) { // 必须是私有的
        this.foo = foo;
        this.fooClazz = fooClazz;
    }

    @SuppressWarnings("unchecked")
    public static <E extends Enum<E>> Bla<E> of(E foo) { // 注意:不能处理 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<T>,例如:

// 使用构造函数(旧的方式)
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);
// 使用静态工厂方法(新的方式)
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);
// 不支持的类型是不允许的
Bla<Double> i1 = Bla.of(3.14); // 错误:在类型 Bla 中,方法 of(E) 对于参数 (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) { // 必须是私有的
        this.foo = foo;
        this.fooClazz = fooClazz;
    }

    @SuppressWarnings("unchecked")
    public static <E extends Enum<E>> Bla<E> of(E foo) { // 注意:不能处理 null
        return new Bla<>(foo, (Class<E>) foo.getClass()) {
            @Override
            public void do() {
                // 执行枚举特定的代码
            }
        };
    }
    public static Bla<Boolean> of(Boolean foo) {
        return new Bla<>(foo, Boolean.class) {
            @Override
            public void do() {
                // 执行布尔特定的代码
            }
        };
    }
    public static Bla<Integer> of(Integer foo) {
        return new Bla<>(foo, Integer.class) {
            @Override
            public void do() {
                // 执行整数特定的代码
            }
        };
    }
    public static Bla<String> of(String foo) {
        return new Bla<>(foo, String.class) {
            @Override
            public void do() {
                // 执行字符串特定的代码
            }
        };
    }
    public static Bla<Supportable> of(Supportable foo) {
        return new Bla<>(foo, Supportable.class) {
            @Override
            public void do() {
                foo.doSpecific(this);
            }
        };
    }

    public abstract void do(); // 现在是抽象的

    // ...
}

当然,你可以创建(私有的)静态嵌套类,或者(包私有的)顶级类,代替匿名类,如果你更喜欢这样做。

使用子类允许在多个方法中执行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) { // 必须是私有的
        this.foo = foo;
        this.fooClazz = fooClazz;
        this.doImpl = doImpl;
    }

    @SuppressWarnings("unchecked")
    public static <E extends Enum<E>> Bla<E> of(E foo) { // 注意:不能处理 null
        return new Bla<>(foo, (Class<E>) foo.getClass(), bla -> {
            // 执行枚举特定的代码
        });
    }
    public static Bla<Boolean> of(Boolean foo) {
        return new Bla<>(foo, Boolean.class, bla -> {
            // 执行布尔特定的代码
        });
    }
    public static Bla<Integer> of(Integer foo) {
        return new Bla<>(foo, Integer.class, bla -> {
            // 执行整数特定的代码
        });
    }
    public static Bla<String> of(String foo) {
        return new Bla<>(foo, String.class, bla -> {
            // 执行字符串特定的代码
        });
    }
    public static Bla<Supportable> of(Supportable foo) {
        return new Bla<>(foo, Supportable.class, foo::doSpecific);
    }

    public void do() {
        doImpl.accept(this);
    }

    // ...
}
英文:

One way to restrict it, is to use static overloaded factory methods to construct the object.

public class Bla&lt;T&gt; {
	private final T foo;
	private final Class&lt;T&gt; fooClazz;

	private Bla(T foo, Class&lt;T&gt; fooClazz) { // Must be private
		this.foo = foo;
		this.fooClazz = fooClazz;
	}

	@SuppressWarnings(&quot;unchecked&quot;)
	public static &lt;E extends Enum&lt;E&gt;&gt; Bla&lt;E&gt; of(E foo) { // Caveat: Cannot handle null
		return new Bla&lt;&gt;(foo, (Class&lt;E&gt;) foo.getClass());
	}
	public static Bla&lt;Boolean&gt; of(Boolean foo) {
		return new Bla&lt;&gt;(foo, Boolean.class);
	}
	public static Bla&lt;Integer&gt; of(Integer foo) {
		return new Bla&lt;&gt;(foo, Integer.class);
	}
	public static Bla&lt;String&gt; of(String foo) {
		return new Bla&lt;&gt;(foo, String.class);
	}
	public static Bla&lt;Supportable&gt; of(Supportable foo) {
		return new Bla&lt;&gt;(foo, Supportable.class);
	}

	public void do() {
		// ...
	}

	// ...
}

It changes how the caller constructs an instance, but actually simplifies it too, since the caller doesn't have to pass in a Class&lt;T&gt;, e.g.

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

However, rather than using a multi-way if statement in the do() method, it should be using subclasses. The subclasses are hidden from the caller, so it makes no external difference, but it eliminates the need for multi-way if statement / switch statement:

public abstract class Bla&lt;T&gt; {
	private final T foo;
	private final Class&lt;T&gt; fooClazz;

	private Bla(T foo, Class&lt;T&gt; fooClazz) { // Must be private
		this.foo = foo;
		this.fooClazz = fooClazz;
	}

	@SuppressWarnings(&quot;unchecked&quot;)
	public static &lt;E extends Enum&lt;E&gt;&gt; Bla&lt;E&gt; of(E foo) { // Caveat: Cannot handle null
		return new Bla&lt;&gt;(foo, (Class&lt;E&gt;) foo.getClass()) {
			@Override
			public void do() {
				// Do Enum specific code
			}
		};
	}
	public static Bla&lt;Boolean&gt; of(Boolean foo) {
		return new Bla&lt;&gt;(foo, Boolean.class) {
			@Override
			public void do() {
				// Do Boolean specific code
			}
		};
	}
	public static Bla&lt;Integer&gt; of(Integer foo) {
		return new Bla&lt;&gt;(foo, Integer.class) {
			@Override
			public void do() {
				// Do Integer specific code
			}
		};
	}
	public static Bla&lt;String&gt; of(String foo) {
		return new Bla&lt;&gt;(foo, String.class) {
			@Override
			public void do() {
				// Do String specific code
			}
		};
	}
	public static Bla&lt;Supportable&gt; of(Supportable foo) {
		return new Bla&lt;&gt;(foo, Supportable.class) {
			@Override
			public void do() {
				foo.doSpecific(this);
			}
		};
	}

	public abstract void do(); // Is now abstract

	// ...
}

You can of course create (private) static nested classes, or (package-private) top-level classes, instead of the anonymous classes, if you prefer.

Using subclasses allow fooClass-specific actions in multiple methods. If you only have one method, you can use lambdas expressions and/or method references instead:

public class Bla&lt;T&gt; {
	private final T foo;
	private final Class&lt;T&gt; fooClazz;
	private final Consumer&lt;Bla&lt;T&gt;&gt; doImpl;

	private Bla(T foo, Class&lt;T&gt; fooClazz, Consumer&lt;Bla&lt;T&gt;&gt; doImpl) { // Must be private
		this.foo = foo;
		this.fooClazz = fooClazz;
		this.doImpl = doImpl;
	}

	@SuppressWarnings(&quot;unchecked&quot;)
	public static &lt;E extends Enum&lt;E&gt;&gt; Bla&lt;E&gt; of(E foo) { // Caveat: Cannot handle null
		return new Bla&lt;&gt;(foo, (Class&lt;E&gt;) foo.getClass(), bla -&gt; {
			// Do Enum specific code
		});
	}
	public static Bla&lt;Boolean&gt; of(Boolean foo) {
		return new Bla&lt;&gt;(foo, Boolean.class, bla -&gt; {
			// Do Boolean specific code
		});
	}
	public static Bla&lt;Integer&gt; of(Integer foo) {
		return new Bla&lt;&gt;(foo, Integer.class, bla -&gt; {
			// Do Integer specific code
		});
	}
	public static Bla&lt;String&gt; of(String foo) {
		return new Bla&lt;&gt;(foo, String.class, bla -&gt; {
			// Do String specific code
		});
	}
	public static Bla&lt;Supportable&gt; of(Supportable foo) {
		return new Bla&lt;&gt;(foo, Supportable.class, foo::doSpecific);
	}

	public void do() {
		doImpl.accept(this);
	}

	// ...
}

答案2

得分: 1

你想要支持的类的共同超类型,以及考虑到你的 Supportable 类型,是 Object。然而,如果你定义的类继承自 Object(默认情况下是这样),并且实现了 Supportable 接口,就没有办法以这种方式限制泛型类。

如果我是你,我会使用工厂模式,并编写一个方法,该方法接受任何东西(Object 的子类)和其类型,以实例化 Bla 的相关实例。Create 方法应该是泛型的,但在这里你无法强加任何限制,只能在类型不可接受时抛出异常。我知道,这可能不是你预期的答案类型。但在你的情况下,没有其他方法。

附言:对于那些认为楼主在做错事,并且我们不应该在现实世界中采用这种设计的人,我想举个例子。假设你将要编写一个类,该类应该创建一个内存中的表(类似于我们在数据库中所做的,但使用 Java 数据类型)。你还想支持用户的数据类型!那么你会如何做呢?

英文:

The common supertype of the classes you want to support — and considering your Supportable type — is Object however, if you define your class to inherit from Object (which is by default) and implements Supportable there is no way you can restrict your generic class this way.

if I were you, I would have used a Factory pattern and write a method that accepts any thing (subclass of Object) and its type to instantiate a relevant instance of Bla . The Create method should be generic but you can not impose any restriction here only you may be able to throw an exception if the type is not acceptable. I know, it is not the type of answer that you are probably expecting. But there is no other way (in your case).

P.S For those who think OP is doing something wrong, and we should not have such a design in real world. I would like to introduce an example. Assume you are about to write a class which suppose to create an in-memory table (like what we do in databases but with java data types). You also want to support user’s data types! So how would you do that?

答案3

得分: 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() { // 执行 Enum 特定代码 }
    }
    public static class BooleanBla extends Bla<Boolean> {
        public BooleanBla() { super(); }
        @Override
        protected void do() { // 执行 Boolean 特定代码 }
    }
    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,因为在那里不需要它。
此外,我不能完全删除“嵌套”的条件语句,因为我想要能够创建没有所需 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+ "] 不是支持的 Bla foo 类型\n\t\t" +
        "支持的类型包括 " +
        "[" + Enum.class + "] " +
        "[" + Boolean.class + "] " +
        "[" + Supportable.class + "]");
}
英文:

So to resume my progress.

After the tip of @Iman to use the factory pattern, i started to implement it using static overloaded factory methods.
Here is my resulting code using Enum, Boolean and the Supportable interface as examples. It's similar to the code @Andreas posted.

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

I decided to make static subclasses of Bla for the types i want to support.

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

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

I don't have a have fooClazz in BooleanBla since it's not needed there.
Also i can't completely remove "nested" if statements, because i want to give the ability to create Bla without an instance of the wanted foo type.

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

huangapple
  • 本文由 发表于 2020年10月25日 23:22:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/64525384.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定