有没有更好(更安全)的方法来获取参数化类型的类?

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

Is there a better (more safe) way to get the class of the parameterized type?

问题

我想创建一个返回参数化类型的类的方法。

考虑一个interface

private static interface MyInterface<T> {
    void run(T parameter);
}

还有一个实现:

private static class MyInterfaceImplString implements MyInterface<String> {
    @Override
    public void run(String parameter) {     }
}

现在,我想将MyInterfaceImplString.class传递给一个方法,这个方法将返回String.class

我正在打印周围的东西,我可以看到信息存在,但我只是无法获取它。或者至少以一种更安全的方式获取它。

public class TypesTest {

    public static void main(String[] args) {
        Class<?> genericParameter1 = getGenericParameterType(MyInterfaceImplVoid.class);
        System.out.println(genericParameter1); // 期望这里是Void

        Class<?> genericParameter2 = getGenericParameterType(MyInterfaceImplString.class);
        System.out.println(genericParameter2); // 期望这里是String
    }

    private static Class<?> getGenericParameterType(Class<? extends MyInterface<?>> clazz) {
        for (Method m : clazz.getMethods()) {
            if ("run".equals(m.getName()) && m.getParameterCount() == 1) {
                System.out.println(TypeLiteral.get(clazz).getParameterTypes(m));
            }
        }

        return null;
    }

    private static class MyInterfaceImplVoid implements MyInterface<Void> {

        @Override
        public void run(Void parameter) {    		}
    }

    private static class MyInterfaceImplString implements MyInterface<String> {

        @Override
        public void run(String parameter) {    		}

    }

    private static interface MyInterface<T> {
        void run(T parameter);
    }
}

忽略方法中的null返回值。我只是想打印出来,看看我得到了什么,然后返回它。然而,我的当前实现似乎有些不规范,因为类可能有多个与MyInterface无关的run方法。

如果涉及到XY问题,我希望能够识别哪些实现包含一个参数化类型,该类型extends Employee(假设)。我间接调用接口的run方法,给定一个HourlyPaidEmployeeMonthlyPaidEmployee。因此,如果底层实现是MyInterface<Employee>,我可以注入我的实际雇员(无论是按小时计薪还是按月计薪的)。但是如果实现是MyInterface<HourlyEmployee>,我不能注入按月计薪的雇员。因此,获取参数化类型的类帮助我知道可以安全地注入到run方法的雇员类型。

我使用的是Java 8和classpath中的Guice依赖项,其中包含guava依赖项。

编辑: @Glorfindel在他的回答中提出了一个很好的观点。但是如果实现看起来像这样:

private static class MyInterfaceImplVoid implements MyInterface<Void> {

    @Override
    public void run(Void parameter) {
    }

    public void run(Integer par) {
    }
}

我无法知道与接口相关的方法是哪一个,因此他的答案可能会返回Integer.class

英文:

I want to create a method that returns the class of parameterized type.

Consider an interface:

private static interface MyInterface&lt;T&gt; {
	void run(T parameter);
}

And one implementation:

private static class MyInterfaceImplString implements MyInterface&lt;String&gt; {
	@Override
	public void run(String parameter) {    	}
}

Now, I want to pass MyInterfaceImplString.class to a method and this method will return String.class.

I am printing things around, and I can see the information be there but I am just unable to get it. Or at least with a safer way.

public class TypesTest {

	public static void main(String[] args) {
		Class&lt;?&gt; genericParameter1 = getGenericParamaterType(MyInterfaceImplVoid.class);
		System.out.println(genericParameter1); //expect Void here

		Class&lt;?&gt; genericParameter2 = getGenericParamaterType(MyInterfaceImplString.class);
		System.out.println(genericParameter2); //expect String here
	}

	private static Class&lt;?&gt; getGenericParamaterType(Class&lt;? extends MyInterface&lt;?&gt;&gt; clazz) {
		for (Method m : clazz.getMethods()) {
			if (&quot;run&quot;.equals(m.getName()) &amp;&amp; m.getParameterCount() == 1) {
				System.out.println(TypeLiteral.get(clazz).getParameterTypes(m));
			}
		}

		return null;
	}

	private static class MyInterfaceImplVoid implements MyInterface&lt;Void&gt; {

		@Override
		public void run(Void parameter) {    		}
	}

	private static class MyInterfaceImplString implements MyInterface&lt;String&gt; {

		@Override
		public void run(String parameter) {    		}

	}

	private static interface MyInterface&lt;T&gt; {
		void run(T parameter);
	}
}

Ignore the null return value in the method. I just want to print things, see what I get and then return it. However, my current implementation seems kind of unorthodox because the class might have more than one run method that has nothing to do with MyInterface.

In case of a XY problem, I want be to able to recognize which of these implementations contain a parameterized type that extends Employee (let's say). I call the run method of the interface indirectly given an HourlyPaidEmployee or a MonthlyPaidEmployee. So, if the underlying implementation is MyInterface&lt;Employee&gt;, I can inject my actual employee (either is monthly or hourly paid). But if the implementation is MyInterface&lt;HourlyEmployee&gt;, I cannot inject a monthly paid one. So, getting the class of the parameterized type, helps me know what types of Employees I can inject safely to run method.

I am with java-8 and Guice dependency in classpath, which contains guava dependency.

EDIT: @Glorfindel made good point in his answer. But if the implementation looks like:

private static class MyInterfaceImplVoid implements MyInterface&lt;Void&gt; {

	@Override
	public void run(Void parameter) {
	}

	public void run(Integer par) {
	}
}

I am not able to know what is the method that refers to the interface, hence his answer might return to me Integer.cass.

答案1

得分: 5

以下是您要的翻译内容:

你想要找出 类型 的实际类型参数时,不应处理方法。您可以将其实现为与您的使用情况一样简单的方案

public class TypesTest {
    public static void main(String[] args) {
        Type genericParameter1 = getGenericParameterType(MyInterfaceImplVoid.class);
        System.out.println(genericParameter1+", "+(genericParameter1==Void.class));

        Type genericParameter2 = getGenericParameterType(MyInterfaceImplString.class);
        System.out.println(genericParameter2+", "+(genericParameter2==String.class));
    }

    private static Type getGenericParameterType(Class<? extends MyInterface<?>> clazz) {
        for(Type interfaceType: clazz.getGenericInterfaces()) {
            if(interfaceType == clazz)
                throw new IllegalArgumentException("raw implementation");
            if(interfaceType instanceof ParameterizedType) {
                ParameterizedType pt = (ParameterizedType)interfaceType;
                if(pt.getRawType() == MyInterface.class)
                    return pt.getActualTypeArguments()[0];
            }
        }
        throw new UnsupportedOperationException("not implemented directly");
    }
}
class java.lang.Void, true
class java.lang.String, true

但请注意,返回类型是 Type,而不是类,因为不能保证类型参数是 Class(可具体化的类型或原始类型)。它既可以是自身的参数化类型,也可以是类型变量。

考虑以下情况:

abstract class InTheMiddle<T> implements MyInterface<T> {}
class Implementation extends InTheMiddle<String> {
    @Override
    public void run(String parameter) {
    }
}

这可以通过遍历类型层次结构,查询声明的类型变量并在目标类型变量出现时将它们与实际类型参数进行匹配来解决。

但要注意,以下情况也是可能的:

abstract class Outer<T> {
    class Inner implements MyInterface<T> {
        @Override
        public void run(T parameter) {
        }
    }
}
class SubclassOfOuter extends Outer<Integer> {
}
SubclassOfOuter outer = new SubclassOfOuter();
MyInterface<Integer> object = outer.new Inner();

这需要遍历类型层次结构和词法范围的构件(除了外部类,实现类还可以是嵌套在另一个方法的泛型类内的泛型方法内的局部类...)。

然后,由于类型擦除,有些构造在运行时根本无法查询:

class GenericImpl<X> implements MyInterface<X> {
    @Override
    public void run(X parameter) {
    }
}
GenericImpl<String> a = new GenericImpl<>();
GenericImpl<Integer> b = new GenericImpl<>();

或者

MyInterface<Thread> another = t -> {};

因此,在使代码依赖于能够在运行时确定实际类型参数的能力之前,请三思而行。

英文:

You should not deal with methods when you want to find out the actual type arguments of the type. You can implement it for the scenario as simple as your using

public class TypesTest {
    public static void main(String[] args) {
        Type genericParameter1 = getGenericParameterType(MyInterfaceImplVoid.class);
        System.out.println(genericParameter1+&quot;, &quot;+(genericParameter1==Void.class));

        Type genericParameter2 = getGenericParameterType(MyInterfaceImplString.class);
        System.out.println(genericParameter2+&quot;, &quot;+(genericParameter2==String.class));
    }

    private static Type getGenericParameterType(Class&lt;? extends MyInterface&lt;?&gt;&gt; clazz) {
        for(Type interfaceType: clazz.getGenericInterfaces()) {
            if(interfaceType == clazz)
                throw new IllegalArgumentException(&quot;raw implementation&quot;);
            if(interfaceType instanceof ParameterizedType) {
                ParameterizedType pt = (ParameterizedType)interfaceType;
                if(pt.getRawType() == MyInterface.class)
                    return pt.getActualTypeArguments()[0];
            }
        }
        throw new UnsupportedOperationException(&quot;not implemented directly&quot;);
    }
}
class java.lang.Void, true
class java.lang.String, true

But note that the return type is Type, not class, as the type argument is not guaranteed to be a Class (a reifiable type or a raw type). It could be a parameterized type on its own, but also a type variable.

Consider a case like

abstract class InTheMiddle&lt;T&gt; implements MyInterface&lt;T&gt; {}
class Implementation extends InTheMiddle&lt;String&gt; {
    @Override
    public void run(String parameter) {
    }
}

This would be solvable be traversing the type hierarchy, querying the declared type variables and match them with the actual type arguments when they appear at the target type variable you want to resolve.

But mind that the following also is possible:

abstract class Outer&lt;T&gt; {
    class Inner implements MyInterface&lt;T&gt; {
        @Override
        public void run(T parameter) {
        }
    }
}
class SubclassOfOuter extends Outer&lt;Integer&gt; {
}
SubclassOfOuter outer = new SubclassOfOuter();
MyInterface&lt;Integer&gt; object = outer.new Inner();

which would require traversing the type hierarchy and the artifacts of the lexical scope (besides outer classes, implementation classes can be local classes of a generic method placed inside a generic class that may be local to another method…).

Then, there are the constructs that are impossible to query at runtime at all, due to type erasure:

class GenericImpl&lt;X&gt; implements MyInterface&lt;X&gt; {
    @Override
    public void run(X parameter) {
    }
}
GenericImpl&lt;String&gt; a = new GenericImpl&lt;&gt;();
GenericImpl&lt;Integer&gt; b = new GenericImpl&lt;&gt;();

or

MyInterface&lt;Thread&gt; another = t -&gt; {};

So think twice before you make your code dependent on the ability to determine the actual type argument at runtime.

答案2

得分: 0

如果我将以下代码中的部分:

System.out.println(TypeLiteral.get(clazz).getParameterTypes(m));

替换为:

return m.getParameterTypes()[0];

我会得到以下输出:

class java.lang.Void
class java.lang.String

似乎不需要使用Guice/Guava,普通的Java代码似乎可以正常工作。我对另一个问题没有一个好的解决方案;似乎反射甚至不能够检测一个方法是覆盖(override)的,而另一个方法则不是。如果你对实现类有足够的控制权,我建议为另一个方法使用一个不同的名称,而不是 `run`。
英文:

If I replace

System.out.println(TypeLiteral.get(clazz).getParameterTypes(m));

with

return m.getParameterTypes()[0];

I get the following output:

class java.lang.Void  
class java.lang.String

No need for Guice/Guava, plain old Java seems to work fine. I do not know a good solution for the other problem; it seems reflection isn't even able to detect that one method is an override and the other one is not. If you have sufficient control over the implementation classes, I'd advice simply to use another name instead of run for the other method.

答案3

得分: 0

您的 T 是未绑定的。

这意味着,即使是您的 MyInterfaceImplVoid,此接口的每个实现,也将拥有一个 run(Object) 方法,即使您根本没有编写该方法 - 它会将参数强制转换为在这种情况下的 Void,然后将调用传递到 run(Void)。因此,如果您想确保调用接口方法,请始终调用 run(Object),并发送正确类型的对象(根据 @Glorfindel 的答案找出类型)。

请注意,通常弄清楚绑定是一个不好的方法。当我编写这个类时,您将怎么做:

class DynamicMyInterface<Z> implements MyInterface<Z> {
    private Class<Z> forcedType;

    public DynamicMyInterface(Class<Z> type) {
        this.forcedType = type;
    }

    public void run(Z in) {
        forcedType.cast(in);
        ....
    }
}

由于类型擦除,您的 .getGenericType() 等方法将永远无法弄清楚它是什么。您将无法超越 Z extends Object

英文:

Your T is unbound.

That means every impl of this interface, even your MyInterfaceImplVoid, will have a run(Object) method, even if you didn't write one at all - and it will cast the argument to in this case Void and pass the call onwards to run(Void). So, if you want to be sure you call the interface method, always call run(Object), and send an object of the right type (as figured out via @Glorfindel 's answer).

Note that this 'trick' of figuring out what the bound is usually a bad plan. What are you going to do when I write this class:

class DynamicMyInterface&lt;Z&gt; implements MyInterface&lt;Z&gt; {
    private Class&lt;Z&gt; forcedType;

    public DynamicMyInterface(Class&lt;Z&gt; type) {
        this.forcedType = type;
    }

    public void run(Z in) {
        forcedType.cast(in);
        ....
    }
}

Your jaunt into .getGenericType() and such will never be able to figure out what it is, because of erasure. You won't get any further than Z extends Object.

答案4

得分: 0

我找到了一种获取参数化类型的类的方法,使用了这个“不太美观”的 - “到底是怎么回事”的方法:

private static Class<?> getGenericParamaterType(Class<? extends MyInterface<?>> clazz) {
    for (TypeToken<?> typeToken : TypeToken.of(clazz).getTypes()) {
        Class<?> rawType = typeToken.getRawType();
        if (rawType == MyInterface.class) {
            TypeLiteral<?> typeLiteral = TypeLiteral.get(typeToken.getType());
            for (Method method : rawType.getMethods()) {
                if ("run".equals(method.getName())) {
                    TypeLiteral<?> typeLiteral2 = typeLiteral.getParameterTypes(method).get(0);
                    return typeLiteral2.getRawType();
                }
            }
        }
    }
    return null;
}

它按照我想要的方式正常工作。但是,我对其性能不太确定。另外,我确实认为Guava提供了一种更简洁的方式来实现我想要的功能。然而,我没有找到它。

以下是一个完整的示例,展示了它的工作方式。即使在实现中实现了多个run方法,甚至在不实现该方法的抽象类中也可以正常工作。

public class TypesTest {

    public static void main(String[] args) {
        Class<?> genericParameter1 = getGenericParamaterType(MyInterfaceImplVoid.class);
        System.out.println(genericParameter1); //期望在这里得到Void

        Class<?> genericParameter2 = getGenericParamaterType(MyInterfaceImplString.class);
        System.out.println(genericParameter2); //期望在这里得到String

        Class<?> genericParameter3 = getGenericParamaterType(AbstractImpl.class);
        System.out.println(genericParameter3); //期望在这里得到Integer
    }

    // 其他代码...

    private static interface MyInterface<T> {
        void run(T parameter);

        void someOtherMethod();
    }
}

我不会将这个答案标记为已接受,因为我相当确定有一种更方便的方法来实现这一点。

英文:

I found a way to get the class of the parameterized type with this "ugly" - "what the heck" method:

private static Class&lt;?&gt; getGenericParamaterType(Class&lt;? extends MyInterface&lt;?&gt;&gt; clazz) {
	for (TypeToken&lt;?&gt; typeToken : TypeToken.of(clazz).getTypes()) {
		Class&lt;?&gt; rawType = typeToken.getRawType();
		if (rawType == MyInterface.class) {
			TypeLiteral&lt;?&gt; typeLiteral = TypeLiteral.get(typeToken.getType());
			for (Method method : rawType.getMethods()) {
				if (&quot;run&quot;.equals(method.getName())) {
					TypeLiteral&lt;?&gt; typeLiteral2 = typeLiteral.getParameterTypes(method).get(0);
					return typeLiteral2.getRawType();
				}
			}
		}
	}
	return null;
}

It works exactly as I want. However, I am not sure for its performance. Plus, I really think that Guava provides a more concise way to achieve what I want. In the other hand, I was not able to find it.

Below is the full example that shows that it works. Even in complex situations where multiple run methods are implemented by the implementation and even in abstract classes that do not implement the method.

public class TypesTest {

	public static void main(String[] args) {
		Class&lt;?&gt; genericParameter1 = getGenericParamaterType(MyInterfaceImplVoid.class);
		System.out.println(genericParameter1); //expect Void here

		Class&lt;?&gt; genericParameter2 = getGenericParamaterType(MyInterfaceImplString.class);
		System.out.println(genericParameter2); //expect String here

		Class&lt;?&gt; genericParameter3 = getGenericParamaterType(AbstractImpl.class);
		System.out.println(genericParameter3); //expect Integer here
	}

	private static Class&lt;?&gt; getGenericParamaterType(Class&lt;? extends MyInterface&lt;?&gt;&gt; clazz) {
		for (TypeToken&lt;?&gt; typeToken : TypeToken.of(clazz).getTypes()) {
			Class&lt;?&gt; rawType = typeToken.getRawType();
			if (rawType == MyInterface.class) {
				TypeLiteral&lt;?&gt; typeLiteral = TypeLiteral.get(typeToken.getType());
				for (Method method : rawType.getMethods()) {
					if (&quot;run&quot;.equals(method.getName())) {
						TypeLiteral&lt;?&gt; typeLiteral2 = typeLiteral.getParameterTypes(method).get(0);
						return typeLiteral2.getRawType();
					}
				}
			}
		}
		return null;
	}

	private static class MyInterfaceImplVoid extends JPanel implements MyInterface&lt;Void&gt;, Runnable {

		@Override
		public void run(Void parameter) { 
		}

		public void run(Integer par) {   		}

		public void run(String s) {   		}

		@Override
		public void someOtherMethod() {    		}

		@Override
		public void run() {    		}

	}

	private static abstract class AbstractImpl implements MyInterface&lt;Integer&gt; {

	}

	private static class MyInterfaceImplString implements MyInterface&lt;String&gt; {

		@Override
		public void run(String parameter) {    		}

		@Override
		public void someOtherMethod() {    		}

	}

	private static interface MyInterface&lt;T&gt; {
		void run(T parameter);

		void someOtherMethod();
	}
}

I will not mark this answer as accepted since I am quite sure there is a more convenient way to do it.

huangapple
  • 本文由 发表于 2020年10月17日 17:01:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/64400741.html
匿名

发表评论

匿名网友

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

确定