Search code examples
javaoverloadingredundancy

Why is it redundant to cast both arguments of a function call in Java if the function parameters are the same type?


I am curious to know how casting works in function calls. I have two overloads of a specific function in my class called execute. The first overload is the base implementation and accepts double type parameters. The second overload accepts int type parameters. The second overload also calls the base overload and does so by casting the parameters to double.

The base implementation looks like this:

public double execute(double leftVal, double rightVal) throws IOException {
    ...
    return solution;
}

and the overloaded version looks like this:

public int execute(int leftVal, int rightVal) throws IOException {
    return (int) execute((double) leftVal, (double) rightVal);
}

Why is the above, specifically the (double) leftVal, (double) rightVal part, a redundancy and why does it work with one of the casts removed? The call to the first overload works no matter which order the casting is in. Both execute(leftVal, (double)rightVal) and execute((double) leftVal, rightVal) execute normally, producing no errors. I have always thought Java was very strict on explicitly identifying types. I would expect some warning or error that there isn't a function that exists to accept call like execute(double, int). I have an idea, though, that the first cast helps the compiler determine which overload to choose so the second cast is implicit. Maybe because the types are primitive easily cast-able? Also mini-question, are both functions called overloads or is every definition after the first an overload of the original?


Solution

  • Why is the above, specifically the (double) leftVal, (double) rightVal part, a redundancy?

    the cast you made above is necessary for you to explicitly state that you want to call that base-level, overloaded function. Otherwise, the code will just assume you want to recurse in that same method. So yes, you are right in saying "the first cast helps the compiler determine which overload to choose"

    So the second cast is implicit. Maybe because the types are primitive easily cast-able?

    So if you are going from a primitive type that requires a smaller amount of bits (int is 32 bits) to a primitive type that accommodates a larger amount of bits (double is 64 bits), Java knows to do this at runtime. However, if you go from a double (64 bit) to an int (32 bit), you as a developer need to explicitly cast since you can run into information loss here. This is why that (int) cast is necessary on the return; you are going from a double to an int which can be lossy.

    Some Example Code:

    public class Test {
    
        public int intMethod(int intArg) {
            return intArg;
        }
    
        public long longMethod(long longArg) {
            // will NOT compile, since we didn't explicitly cast to narrower int type (potentially lossy)
            return intMethod(longArg);
        }
    }
    
    public class Test {
    
        public int intMethod(int intArg) {
            return intArg;
        }
    
        public long longMethod(long longArg) {
            // WILL compile, since we explicitly cast to narrower int type. Also note we do not need to cast result, since result is going from int --> long, which is not lossy
            return intMethod((int) longArg); 
        }
    }
    
    public class Test {
    
        public int intMethod(int intArg) {
            // WILL compile. don't need to specify arg since its not lossy, but we do specify return type since its lossy
            return (int) longMethod(intArg);
        }
    
        public long longMethod(long longArg) {
            return 3L;
        }
    }
    

    I have always thought Java was very strict on explicitly identifying types

    Primitives are a much more nuanced feature of the Java language. I think by types, you're implicitly referring to Object's in Java. You are right that Java is much more strict with respect to Object-casting. If we take the above example and use Long and Integer objects, they won't compile, and will flag a warning Cannot case java.lang.Long to java.lang.Integer;

    Examples that Won't Compile due to Invalid Casting

    public class Test {
    
        public Integer intMethod(Integer intArg) {
            return intArg;
        }
    
        public Long longMethod(Long longArg) {
            return intMethod((Integer) longArg);
        }
    }
    
    public class Test {
    
        public Integer intMethod(Integer intArg) {
            return longMethod((Long) intArg);
        }
    
        public Long longMethod(Long longArg) {
            return 3L;
        }
    }
    

    ps: it's standard to refer to all functions w/ the same method name as overloaded; not common to refer to one method as the "base" and say the rest overload that method