web-dev-qa-db-ja.com

Javaジェネリック引数なしのジェネリックメソッド

C#では、実際にこれを行うことができます:

//This is C#
static T SomeMethod<T>() where T:new()
{
  Console.WriteLine("Typeof T: "+typeof(T));
  return new T();
}

//And call the method here
SomeMethod<SomeClassName>();

しかし、何らかの理由で、Javaで動作させることができません。

私がやりたいのは、スーパークラスで静的メソッドを作成し、サブクラスをXMLに変換できるようにすることです。

//This is Java, but doesn't work
public static T fromXml<T>(String xml) {
  try {
    JAXBContext context = JAXBContext.newInstance(T.class);
    Unmarshaller um = context.createUnmarshaller();
    return (T)um.unmarshal(new StringReader(xml));
  } catch (JAXBException je) {
    throw new RuntimeException("Error interpreting XML response", je);
  }
}

//Also the call doesn't work...
fromXml<SomeSubObject>("<xml/>");
35
doekman
public static <T> T fromXml(Class<T> clazz, String xml) {

と呼ばれる:

Thing thing = fromXml(Thing.class, xml);

またはより明示的に:

Thing thing = MyClass.<Thing>fromXml(Thing.class, xml);

さらに混乱させるために、ジェネリック型を構築するコンストラクターとジェネリックパラメーター自体を持つコンストラクターを持つことができます。構文を思い出せず、怒りの中で使用されたことを見たことがない(とにかく静的な作成方法をお勧めします)。

キャスト (T)は安全ではなく、T.classを書くことはできません。そのため、T.classを引数として含めます(JAXBContext.newInstanceはそうです)、タイプが間違っている場合、関連する例外をスローします。

public static <T> T fromXml(Class<T> clazz, String xml) {
    try {
        JAXBContext context = JAXBContext.newInstance(clazz);
        Unmarshaller um = context.createUnmarshaller();
        Object obj = um.unmarshal(new StringReader(xml));
        try {
            return clazz.cast(obj);
        } catch (ClassCastException exc) {
             throw new RelevantException(
                 "Expected class "+clazz+
                  " but was "+obj.getClass()
             );
        }
    } catch (JAXBException exc) {
        throw new RelevantException(
            "Error unmarshalling XML response",
            exc
         );
    }
}

JAXBの次のバージョン(6u14で?)には、JAXBクラスのこの種の便利なメソッドがいくつかあると思います。

52

Javaでは、ジェネリックはコンパイル時のみのデータであり、実行時に失われます。そのため、そのようなメソッドを呼び出した場合、JVMはT.classだった。これを回避する通常の方法は、次のように、メソッドにパラメーターとしてクラスインスタンスオブジェクトを渡すことです。

public static <T> T fromXml(Class<T> clazz, String xml) {
  try {
    JAXBContext context = JAXBContext.newInstance(clazz);
    Unmarshaller um = context.createUnmarshaller();
    return (T)um.unmarshal(new StringReader(xml));
  } catch (JAXBException je) {
    throw new RuntimeException("Error interpreting XML response", je);
  }
}

fromXml(SomeSubObject.class, "<xml/>");
5
Avi

JavaのCollections.emptySet()のようなメソッドには、次のようなシグネチャがあります。

_public static final <T> Set<T> emptySet()
_

そして、このように呼び出されます:

_Set<Foo> foos = Collections.<Foo>emptySet();
_

MockitoのanyObject()メソッドは別の例です。個人的には、どちらの構文も非常に素晴らしいとは思いません。メソッドの引数として型を渡すことはできますが、常に気味が悪いと感じました。 emptySet()が行う方法でパラメーターを提供することはよりクリーンに見えますが、メソッドが型を指定できることは必ずしも明らかではありません。

2
spaaarky21

現在の答えはより安全ですが、Java7以降では「Class clazz」引数は必要ないため、技術的には時代遅れです。このタイプは、予期される戻りタイプから推測されます。安全でないキャストに関する警告が表示されるだけです。

public class Main {

    private static class Dog {
        public String toString() { return "dog"; }
    }

    private static class Cat {
        public String toString() { return "cat"; }
    }

    public static void main(String[] args) {
        Cat cat = parse("cat");
        Dog dog = parse("dog");
        System.out.println("the cat object is a " + cat);
        System.out.println("the dog object is a " + dog);
    }

    private static Object untypedParse(String stringToParse) {
        if(stringToParse.equals("dog")) {
            return new Dog();
        } else if(stringToParse.equals("cat")) {
            return new Cat();
        } else {
            throw new RuntimeException("not expected");
        }
    }

    public static <T> T parse(String stringToParse) {
        return (T)untypedParse(stringToParse);
    }

}


~/test/generics$ javac Main.Java
Note: Main.Java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
~/test/generics$ Java Main
the cat object is a cat
the dog object is a dog
1
joseph

あなたがやろうとしていることは、単にJavaでは機能しないのではないかと心配しています。ジェネリック型の新しいインスタンスを作成できないことは、.NETが提供する「必須」機能の1つですが、Javaが欠落しているだけです。 ArrayList.toArray(T)を参照として出力する方法:基本的に、作成しようとしているオブジェクトへの参照を渡して、実行時にどのクラスをインスタンス化するかを知る必要があります。 ArrayList.toArray(T)から:


public <T> T[] toArray(T[] a)
{
   if (a.length < size)
   {
      a = (T[]) Java.lang.reflect.Array.
         newInstance(a.getClass().getComponentType(), size);
   }
   System.arraycopy(elementData, 0, a, 0, size);
   if (a.length > size)
   {
      a[size] = null;
   }
   return a;
}
1
Bogdan