开发者

enums and generic methods in java

开发者 https://www.devze.com 2022-12-08 16:24 出处:网络
I still have trouble with some corner cases in the java generics system. I have this method (I\'m only interested in the signature) :

I still have trouble with some corner cases in the java generics system.

I have this method (I'm only interested in the signature) :

 interface Extractor<RETURN_TYPE> {
    public <U extends Enum<U>> RETURN_TYPE extractEnum(final Class<U> enumType);
}

(think about an interface whose implementations sometimes extracts an EnumSet sometimes an implementation extract a JComboBox etc.)

and I want to call it with a class obtained at runtime, so I simply call it this way :

 public static <RETURN_TYPE> RETURN_TYPE extractField(final Extractor<RETURN_TYPE> extractor, final Field field) {
    final Class<?> type = field.getType();
    if (type.isEnum())
        return extractor.extractEnum(/* error here*/type.asSubclass(Enum.class));
    throw new RuntimeException("the rest of the visitor is not necessary here");
}

and I get a strange error message : incompatible types found : java.lang.Object required: RETURN_TYPE

the location of the message if just after the opening braket of the call, before the "t" of type.

if I call it from a non-generic context, it works :

    Integer extractField(final Extractor<Integer> extractor, final Field field) {
        final Class<?> type = field.开发者_开发技巧getType();
        if (type.isEnum())
            return extractor.extractEnum(type.asSubclass(Enum.class));
        throw new RuntimeException("the rest of the visitor is not necessary here");
    }

Does anybody have an explanation and a solution to this problem please ?

Here is a complete file for people wanting to play with it :

public class Blah {
    interface Extractor<RETURN_TYPE> {
        public <U extends Enum<U>> RETURN_TYPE extractEnum(final Class<U> enumType);
    }

    public static <RETURN_TYPE> RETURN_TYPE extractField(final Extractor<RETURN_TYPE> extractor, final Field field) {
        final Class<?> type = field.getType();
        if (type.isEnum())
            return extractor.extractEnum(/* error here*/type.asSubclass(Enum.class));
        throw new RuntimeException("the rest of the visitor is not necessary here");
    }

    public static Integer extractField(final Extractor<Integer> extractor, final Field field) {
        final Class<?> type = field.getType();
        if (type.isEnum())
            return extractor.extractEnum(type.asSubclass(Enum.class));
        throw new RuntimeException("the rest of the visitor is not necessary here");
    }
}

thanks in advance,

Nico


I would not be surprised if this is a bug in your compiler, actually. Through serious use of generics (the kind of thing you're doing, combining parameterised methods, bounded wildcards and other "advanced" uses of generics) I've encountered two or three issues in the last year in javac (annoyingly, the same unit often compiled fine in the IDE).

In your case I'm fairly sure it's a bug, since the part that the compiler is complaining about is that extractor.extractEnum is returning an Object rather than a RETURN_TYPE. And regardless of what crazy inference it does with your enum method arguments... it knows from the type signature that the Extractor is an Extractor<RETURN_TYPE>, so you should always be able to say return extractor.extractEnum(...);.

The damning evidence is that even if you call the method with a null argument (thus completely removing any potential complications from the enum generics in the argument), the compiler still complains. In particular, it now says that it thinks the return type from the Extractor is U<RETURN_TYPE> which is clearly rubbish.

In general the solution to working around these issues is throwing in some explicit casts. Is the compiler happy if you cast the output of extractEnum to RETURN_TYPE? Edit: no, it's really not - it complains that U<RETURN_TYPE> and RETURN_TYPE are inconvertible - eep...

If you're using a recent 1.6 compiler, I suggest you report this to Sun as this is quite a big problem with javac. Here's a very short test case that exercises it:

public class Test {
  interface Sub<O> {
    public <I extends Enum<I>> O method(final Class<I> enumType);
  }

  public static <O> O go(final Sub<O> sub) {
    return sub.method(null);
  }
}

P.S. it's general convention to use a single uppercase letter to designate generic type parameters. I'm not going to say "I'm right, you're wrong", but bear in mind that I found your code much harder to read and follow than if you had used Extractor instead. (And judging by Hemal's phrasing of his answer it's the same for him too.)


I haven't been able to infer the original problem.

Am I correct that Extractor.extract has two type parameters, U which must be an Enum and T which is an arbitrary type? In the generic call, VV is both T and U? If U is VV than the parameter has to be Class<VV>, not Class<Enum>. The following compiles for me, but as you can see the generic method needs to be provides instance of Class<VV>

class Outer {
  static class Extractor<T> {
    public <U extends Enum<U>> T extract(final Class<U> lala) {
      return null;
    }
    // two type parameters, T and U
    // U must be an enum
    // T is arbitrary class
  }

  static <VV extends Enum<VV>> VV extract(final Extractor<VV> extractor, Class<VV> vvClass) {
    final Class<?> type = null;
    return extractor.extract(vvClass);
    // Outer.extract returns VV 
    // T -> VV
    // it seems VV is also U
  }
}


Looks like your Field.getType( ) will only return a generic Class. Because you will try to put a type with already erased type information this function will have to emit an "unchecked" warning, and ALL type information on generic interface WILL get erased.

Remember, that with type erasure your interface looks like this:

interface Extractor {
    public Object extractEnum( final Class enumType );
}

So, because all type information is erased, the return type of extractEnum is java.lang.Object, so you have to add a specific cast. And this is precisely the error message that you've got.

Here is the modified example of your code.

@SuppressWarnings( "unchecked" )
public static <RETURN_TYPE> RETURN_TYPE extractField(
       final Extractor<RETURN_TYPE> extractor,
       final Field field
    )
{
    final Class type = field.getType(); // unchecked

    if (type.isEnum())
    {
        // needs cast
        return (RETURN_TYPE) extractor.extractEnum( type ); // unchecked
    }
    throw new RuntimeException("the rest of the visitor is not necessary here");
}

DISREGARD THIS COMMENT: To answher the original question on why you have a compile error. This is a square peg into a round hole kind of problem. type.asSubclass( Enum.class ) returns Class< ? extends Enum >, it is still not the same as Class< U > which the interface call expects.

0

精彩评论

暂无评论...
验证码 换一张
取 消