Parameters of a parameterized type?

I am trying to create a library with a container that frees instances of the objects contained in it according to the descriptors it passed. I would like the descriptor to determine the type of the returned object, but the descriptor can specify a limited type. How to implement this? For example, the closest I can get:

/*Block 1 - First Attempt. Compiles, but forces user to cast*/ interface ItemDescriptor<I> { Class<? extends I> getType(); } interface ArchiveContainer<I, D extends ItemDescriptor<? extends I>> { Iterable<? extends D> getDescriptors(); I getItem(D descriptor); } //Implementations class ChannelItemDescriptor<I extends ByteChannel> implements ItemDescriptor<I> { final Class<? extends I> type; ChannelItemDescriptor(Class<I> type) { this.type = type; } @Override Class<? extends I> getType() {return type;} } class ChannelArchive implements ArchiveContainer<ByteChannel, ChannelItemDescriptor<? extends ByteChannel>> { @Override ByteChannel getItem(ChannelItemDescriptor<? extends ByteChannel> descriptor) {...} } 

The above code compiles, but the ChannelArchive getItem problem may return a SeekableByteChannel . The user of this library knows this at compile time (because they know a parameter of type descriptor), so I try not to add a parameter of a method of type Class to force the user to explicitly map the return value to SeekableByteChannel if necessary. I cannot figure out how to get getItem in order to return a specific ByteChannel subtype without forcing the user to quit. I want to do this:

 /*Block 2 - Test code*/ ChannelArchive archive = ...; ChannelItemDescriptor<SeekableByteChannel> desc = ...; ChannelItemDescriptor<ByteChannel> otherDesc = ...; SeekableByteChannel sbc = archive.getItem(desc); SeekableByteChannel sbc = archive.getItem(otherDesc); //Should fail to compile, or compile with warning ByteChannel bc = archive.getItem(otherDesc); 

I can add the parameter Class<? extends I> Class<? extends I> for each method, but the method code completely ignores the parameter of the Class method! The only goal is to help developers deduce types. I think this just obfuscates the code so much that it would be easier to just use the user instanceof validation and drop.

I tried this:

 /*Block 3 - Failed attempt.*/ class ChannelArchive implements ArchiveContainer<ByteChannel, ChannelItemDescriptor<? extends ByteChannel>> { //Won't compile, getItem doesn't override @Override <II extends ByteChannel> II getItem(ChannelItemDescriptor<II> descriptor) {...} } 

but this does not work: ChannelArchive is not abstract and does not override abstract method getItem(ChannelItemDescriptor<? extends ByteChannel>) in ArchiveContainer . I suppose this is because the parameter of the second type <II extends ByteChannel> differs by erasing the type than <? extends ByteChannel> <? extends ByteChannel> ?

I also tried this by compiling:

 /*Block 4 - Almost specific enough*/ interface ArchiveContainer<I, D extends ItemDescriptor<? extends I>> { Iterable<? extends D> getDescriptors(); <II extends I, DD extends ItemDescriptor<II>> II getItem(DD descriptor); } class ChannelArchive implements ArchiveContainer<ByteChannel, ChannelItemDescriptor<? extends ByteChannel>> { @Override <II extends ByteChannel, DD extends ItemDescriptor<II>> II getItem(DD descriptor) {...} } 

Although it compiles, it will not work because I need a ChannelItemDescriptor inside this method, and the result will defeat the goal of using the added generic type safety.

I do not understand why I cannot do this because the correct types are known at compile time. What I really need in this interface is the ArchiveContainer , a parameter of a parameterized type, for example: <II extends I, DD extends D<II>> . What am I doing wrong?

NOTE. I don't actually use ByteChannel and SeekableByteChannel , but what I use is pretty similar.


In order to ruch, I settled on the code in block 4. In my case, it is very unlikely that the user would send the wrong ItemDescriptor swap in the getItem call, especially because all the descriptors were returned from the ArchiveContainer itself via getDescriptors !

+4
source share
2 answers

I think this code, which (almost?) Matches your third attempt, is as good as you are going to get:

 // in ArchiveContainer: <II extends I, DD extends ItemDescriptor<II>> II getItem(DD descriptor); // in ChannelArchive: public <II extends ByteChannel, DD extends ItemDescriptor<II>> II getItem(DD descriptor) { ... } 

Generics offer a way to declare a type variable with two separate upper bounds:

 public <T extends Foo & Bar> Foo fooBar(T t) { ... } 

but apparently this is not allowed when one of the upper bounds is a type parameter and not a class or interface:

Type variables have an optional boundary, T and i 1 ... i n . This boundary consists of either a variable of type or class or type of interface T , followed by additional types of interfaces i 1 , ..., i n . [& hellip;] This is a compile-time error if any of the types i 1 ... i n is a type or class type variable. [ link ]

(emphasizes mine). I do not know why this is so.

But I do not think this should be a big problem. Note that even after Map was created for Map<K,V> , its get method still assumed the type Object . Naturally, this method will always return null if you pass a reference to an object that is not of type K (since such an object should never be inserted into the map), but this does not harm type safety.

+1
source

I know this is probably not what you want to hear, but although Java generics look syntactically similar to C ++ templates, they are slightly different from how they work.

Check out java type erasure on your favorite search engine. Just because the type is known at compile time, unfortunately, does not mean that the type is restored at runtime or even at subsequent stages of compilation.

0
source

Source: https://habr.com/ru/post/1397701/


All Articles