Redesign around unverified throw warnings

My class will contain several different parser implementations for different objects. Although I can store parser implementations without any warnings, getting a parser from a card warns about the exception being thrown. The following is a simplified snippet:

private Map<Class<?>, Parser<?>> parsers = new HashMap<>(); public <T> void addParser(Class<T> type, Parser<T> parser) { parsers.put(type, parser); } private <T> Parser<T> parserFor(Class<T> type) { // Compiler complains about unchecked cast below return (Parser<T>) parsers.get(type); } 

Is there any other way to implement this logic without triggering an immediate warning alert?

+3
source share
4 answers

It is impossible to create Map<Class<...>, Parser<...>> , where ... -s can be any, but must match between the key and its value; so you cannot force the compiler to do a check for you, where getting a Class<T> guaranteed to give you a Parser<T> . However, your code itself is correct; You know that your actor is correct, although the compiler does not.

So, when you know that your actor is correct, but Java does not know this, what can you do?

The best and safest approach is to create a specific fragment of your code, as small as possible, which is responsible for processing the translation between the checked and uncontrolled logic and to ensure that the uncontrolled logic does not cause @SuppressWarnings you simply mark this code with the corresponding @SuppressWarnings annotation. For example, you may have something like this:

 public abstract class Parser<T> { private final Class<T> mType; protected Parser(final Class<T> type) { this.mType = type; } public final Class<T> getType() { return mType; } @SuppressWarnings("unchecked") public final <U> Parser<U> castToParserOf(final Class<U> type) { if (type == mType) { return (Parser<U>) this; } else { throw new ClassCastException("... useful message ..."); } } } 

This will allow you to write safely in your example:

 public <T> void addParser(final Parser<T> parser) { parsers.put(parser.getType(), parser); } private <T> Parser<T> parserFor(final Class<T> type) { return parsers.get(type).castToParserOf(type); } 
+1
source

Since your parsers map parsers is Parser<?> And the return method type is Parser<T> , this is clearly an error to pass the result of parsers.get(type) to T

One way to remove a compilation error is to specify the type Parser<T> :

 private <T> Parser<T> parserFor(Class<T> type) { return (Parser<T>)parsers.get(type); } 

In addition, you can change the return type to Parser<?> , Since you specified the parser map as Map<Class<?>, Parser<?>> . This will also fix a compilation error.

 private <T> Parser<?> parserFor(Class<T> type) { return parsers.get(type); } 

Or you can add a type parameter to your packaging class.

 public class YourClass<T> { private Map<Class<T>, Parser<T>> parsers = new HashMap<>(); public void addParser(Class<T> type, Parser<T> parser) { parsers.put(type, parser); } private Parser<T> parserFor(Class<T> type) { return parsers.get(type); } } 

I'm not sure what can be applied correctly, but try not to use type casting. Think about why we use generic.

+1
source

Consider using TypeToInstanceMap<Parser<?>> from Google Guava. This will allow you to do this without any warnings or compiler errors:

 TypeToInstanceMap<Parser<?>> parsers; parsers.put(new TypeToken<Parser<String>>(){}, makeStringParser()); Parser<Integer> intParser = parsers.get(new TypeToken<Parser<Integer>>(){}); 

This is essentially a library that does something very similar to the @ruakh answer under the hood.

The developer, who added <T> to Class<T> , Neil Gafter, discussed a fundamental issue on his blog shortly after Java 5 was released. He calls Class<T> "type token" and says:

[Y] ou just can't make type marker for generic type

... in other words, you cannot do Class<Parser<T>> .

+1
source

I did it differently. I myself am experimenting with generics and will be happy to receive criticism :)

What I did was add a tag interface for Parseable objects and then use it as the top border in Parser.

 public interface IParseable {} public class Parser<T extends IParseable> { T paresableObj; // do something with parseableObject here } 

And now, Parser Factory should not use wildcards or use casts.

 public class ParserFactory { private Map<Class<?>, Parser<? extends IParseable>> parsers = new HashMap<Class<?>, Parser<? extends IParseable>>(); public <T> void addParser(Class<T> type, Parser<? extends IParseable> parser) { if(parserFor(type) == null){ parsers.put(type, parser); }else{ //throw some excep } } private <T> Parser<? extends IParseable> parserFor(Class<T> type) { return parsers.get(type); } } 
0
source

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


All Articles