简体   繁体   中英

Java Casting method without knowing what to cast to

I was playing around with Java today, and I noticed something weird. Consider this code:

String foo = cast("hi");
int bar = cast("1");

The cast() method is here:

public static <T> T cast(Object value) {
    return (T) value;
}

Java seems to cast "hi" to a String, even if I didn't pass any hint that it would be a String , with the exception of the type. It does foo well, but fails on bar , because you can't case a String to an Integer. What is happening here?

I have two guesses:

  1. The cast method is returning an Object , and at initialization it automatically casts to the type.

    This doesn't make sense, as it would give me:

     Type mismatch: cannot convert from Object to int 
  2. Java seems to know that it needs to cast to String, or whatever the type of the variable is. But how does this work?

public static <T> T cast(Object value) {
    return (T) value;
}

The important part is the <T> on the method, it means that the method will return a type based on what the method call expect.

String foo = cast("hi"); // left side is a String, <T> will be typed as String


The second is a bit more tricky since Generic types cannot be primitive types. I guess it returns the most common type Object .

int bar = cast("1"); // left side is primitive
                     // <T> will be typed by with the corresponding non-primitive type.
                     // But it fails because "1" cannot be casted to java.lang.Integer

Your cast method does an unchecked conversion , which is handled specially in the JVM to maintain backward compatibility with non-generic code.

Such calls cannot be shown to be statically safe under the type system using generics. Rejecting such calls would invalidate large bodies of existing code, and prevent them from using newer versions of the libraries. JLS 5.1.9

Calling the method without explicit type parameters will cause the compiler to infer the type parameter of the invocations, in this case based on their expected return type. Type Inference , JLS 15.12.2.7. . This means that code is equvivalent to this:

String foo = Caster.<String>cast("hi"); // no exception
int bar = Caster.<Integer>cast("1");    // runtime ClassCastException

Primitive types will inferred to their boxed version:

If A is a primitive type, then A is converted to a reference type U via boxing conversion and this algorithm is applied recursively to the constraint U << F. JLS 15.12.2.7.

The JVM ensures type safety by doing runtime type checks on return values of functions containing unchecked casts, at the first point where the type information is not erased (I didn't find it explicitly stated in the specification, but things look to work this way, although it is mentioned in The Java Tutorials ). In this case, where you try to assign the value to a typed local variable, the type of the return value is checked, resulting in a ClassCastException .

To give some more idea when that enforced runtime type checking cast happens, here are a few more examples:

Object a3 = Caster.<String>cast(3); // no exception, a3 is now Integer
Object a4 = (String)Caster.<String>cast(3); // an explicit cast causes runtime ClassCastException

EDIT:

Here is a StackOverflow question about when are runtime type checks enforced: When is generic return value of function casted after type erasure?

I don't see any weird behaviour.

In:

String foo = cast("hi");

"hi" is an Object (as everything in Java), so it would be accepted by the compiler as an Object argument. After the call, the cast() function returns "hi", which is a string, and it is assigned to String foo . No weird behavior here.

In:

int bar = cast("1");

"1" is an object, and it is accepted by the compiler. The cast function returns "1", which is a string. Unlike other languages (eg Javascript), a string cannot be directly casted as an integer. No weird behavior here.

If you change the cast() function to:

public static <T> T cast(T value) {
    return value;
}

You will get an error in compile time, instead of execution time.

If anyone need to cast primitive types as well, can refer to this.

public class ReflectionTests {

@Test
public void TEST_PRIMITIVE_TYPE_CAST() {
    Object myNumber=123456;
    int number=ReflectionUtils.cast(myNumber);
    assertTrue(number==123456);

    int numberTest2=ReflectionUtils.castPrimitiveType(myNumber, myNumber.getClass().getName());
    assertTrue(numberTest2==123456);

    double numberTest3=ReflectionUtils.castPrimitiveType(myNumber, "double");
    assertTrue(numberTest3==123456);
}

 }

These tests passes, here below related part from my ReflectionUtils

public static <T> T cast(Object value) {
    if(value.getClass().isPrimitive())
        return castPrimitiveType(value, value.getClass().getName());
    return (T) value;
}

public static <T> T castPrimitiveType(Object value,String typeName) {       
    switch (typeName) {
    case "double":
        return (T) castToDouble(value);
    case "byte":
        return (T) castToByte(value);
    case "int":         
        return (T) castToInt(value);
    case "long":            
        return (T) castToLong(value);           
    case "float":           
        return (T) castToFloat(value);              
    default:
        break;
    }
    return (T) value;
}

public static Boolean castToBoolean(Object val) {
    return Boolean.valueOf(val.toString());
}

public static Byte castToByte(Object val) {     
    return Byte.valueOf(val.toString());
}

public static Character castToChar(Object val) {
    return Character.valueOf(val.toString().charAt(0));
}

public static Integer castToInt(Object val) {
    return Integer.valueOf(val.toString());
}

public static Long castToLong(Object val) {
    return Long.valueOf(val.toString());
}

public static Float castToFloat(Object val) {
    return Float.valueOf(val.toString());
}

public static Double castToDouble(Object val) {
    return Double.valueOf(val.toString());
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM