Improvement/s to my Java generic console input met

2019-02-17 17:23发布

问题:

Using Java Generics, I tried to implement a generic console input method.

public static <T> T readFromInput(String message, Class<?> c) throws Exception{
        System.out.println(message);
        Scanner scanner = new Scanner(System.in);
        try {
            if(c == Integer.class)
                return (T) Integer.valueOf(scanner.nextInt());
            if(c == String.class)
                return (T) scanner.nextLine();
            if(c == Double.class)
                return (T) Double.valueOf(scanner.nextDouble());
            if(c == Float.class)
                return (T) Float.valueOf(scanner.nextFloat());
        } catch (InputMismatchException e) {
            throw new Exception(e);
        }
        return null;
    }

I'm having a warning "Type safety: Unchecked cast from Integer to T". Aside from @SuppressWarnings, is it possible to avoid this warning?

Are there better ways to implement my method? Thanks in advance

回答1:

You can use the Class#castmethod instead, but should leave some comments, because even though cast does not create a warning, it can throw a ClassCastException at runtime if the cast is not possible.

public static <T> T readFromInput(String message, Class<T> c) throws Exception{
    System.out.println(message);
    Scanner scanner = new Scanner(System.in);
    try {
        if(c == Integer.class)
            // the next cast to Integer is safe
            return c.cast(Integer.valueOf(scanner.nextInt()));
        if(c == String.class)
            // the next cast to String is safe
            return c.cast(scanner.nextLine());
        if(c == Double.class)
            // the next cast to Double is safe
            return c.cast(Double.valueOf(scanner.nextDouble()));
        if(c == Float.class)
            // the next cast to Float is safe
            return c.cast(Float.valueOf(scanner.nextFloat()));
    } catch (InputMismatchException e) {
        throw new Exception(e);
    }
    return null;
}

Note that I've changed the method signature slightly - it should be Class<T> and not Class<?> to guarantee, that the Class instance is consistent with the type parameter.



回答2:

Others have shown how you can do it with Class.cast, but how should you do it?

I suggest readInt, readString, readFloat and readDouble methods. Also, I suspect Scanner may buffer, which could lead you into trouble.



回答3:

I think you might be trying to over abstract the problem. What's wrong with just doing this?

    Scanner scanner = new Scanner(System.in);

    System.out.println("Give me a boolean:");
    boolean bool = scanner.nextBoolean();

    System.out.println("Give me an integer:");
    int integer = scanner.nextInt();

No cast required and you still have to handle the exception either way........

Remember KISS, "Keep It Simple Stupid"...



回答4:

Do it like this:

public static <T> T readFromInput(String message, Class<T> c) throws Exception{
    System.out.println(message);
    Scanner scanner = new Scanner(System.in);
    try {
        if(c == Integer.class)
            return c.cast(scanner.nextInt());
        if(c == String.class)
            return c.cast(scanner.nextLine());
        if(c == Double.class)
            return c.cast(scanner.nextDouble());
        if(c == Float.class)
            return c.cast(scanner.nextFloat());
    } catch (InputMismatchException e) {
        throw new Exception(e);
    }
    return null;
}


回答5:

Reading from this post, Java generic function: how to return Generic type I got rid of my warning:

public static <T> T readFromInput(String message, Class<T> c) throws Exception{
        System.out.println(message);
        Scanner scanner = new Scanner(System.in);
        try {
            if(c == Integer.class)
                return c.cast(scanner.nextInt());
            if(c == String.class)
                return c.cast(scanner.nextLine());
            if(c == Double.class)
                return c.cast(scanner.nextDouble());
            if(c == Float.class)
                return c.cast(scanner.nextFloat());
        } catch (InputMismatchException e) {
            throw new Exception(e);
        }
        return null;
    }


回答6:

There's no general way to avoid "Unchecked cast" warning other than using @SuppressWarnings (unchecked) annotation.

In particular case you get this warning because there's no warranty that parameter Class<?> c may be cast to T since Java's generics are checked only in compilation and no checks may be done in runtime.



回答7:

You can do the following:

    public static <T> T readFromInput(String message, Class<T> c) throws Exception{ 
       System.out.println(message); 
       Scanner scanner = new Scanner(System.in); 
       try { 
           if(c == Integer.class) 
               return c.cast(scanner.nextInt()); 
           if(c == String.class) 
               return c.cast(scanner.nextLine()); 
           if(c == Double.class) 
               return c.cast(scanner.nextDouble()); 
           if(c == Float.class) 
               return c.cast(scanner.nextFloat()); 
       } catch (InputMismatchException e) { 
           throw new Exception(e); 
       } 
       return null; 
   } 

However, I strongly recommend not throwing Exception. Throw a more specific exception (either the original runtime exception or some appropriate checked exception).



回答8:

You can get rid of the warning by using the concrete class you pass in to cast the object:

    public static <T> T readFromInput(String message, Class<T> c) throws Exception{
        ..
            return c.cast(Integer.valueOf(scanner.nextInt()));
        ..
    }

I would tempted in this case to implement multiple readFromInput methods overridden with your desired types, e.g. public static Float readFromInput(String message, Class c) public static Integer readFromInput(String message, Class c) etc.