Home > OS >  Changing argument parameters Generics involved
Changing argument parameters Generics involved

Time:05-27

Goal: dst = src in main class

I tried all kinds of combinations to copy the source argument into the destination argument but I can't find a working one. How to solve this?

public class Bounds {
    public static <T extends Number> void copy(Paar<? extends Number> src, Paar<? extends Number> dst) {
        // src = (1, 2) dst = (1.1, 2.2)
        T c1 = (T) src.p1;
        T c2 = (T) src.p2;

        dst.p1 = c1;
//Error: 'Type mismatch: cannot convert from T to capture#3-of ? extends Number'
        dst.p2 = c2;

        System.out.println("Src:"   src);
        System.out.println("Dst:"   dst);
    }
}
public class BoundsMain {

    public static void main(String[] args) {

        Integer[] iFeld = { 1, 2, 3 };
        Number[] nFeld = { 1.1, 2.2, 3.3 };
        Paar<Integer> src = new Paar<>(iFeld[0], iFeld[1]);
        Paar<Number> dst = new Paar<>(nFeld[0], nFeld[1]);

        System.out.println("src="   src   "\ndst vor copy ="   dst );
        // src = (1, 2) dst = (1.1, 2.2)

        Bounds.copy(src, dst);
        System.out.println("src nach copy ="   src);
        System.out.println("dst nach copy ="   dst);
        // src = (1, 2) dst = (1.1, 2.2)
    }
}

CodePudding user response:

It can help to think of multiple valid 'values' for your type parameters and then think about what the method is supposed to mean for all values you thought of. If you find at least one that makes no sense - you now understand why javac won't compile your code.

You wrote:

<T extends Number> void copy(Paar<? extends Number> src, Paar<? extends Number> dst)

This method signature has 3 completely and utterly unrelated type variables:

  • T, which is completely unused and thus pointless, is specifiable (the caller can explicitly pick the value for T if it wants, the system will infer it if they do not), and has a name, so you can use it in multiple places. You use it in zero places (a cast to generics does not count, it generates no code, checks nothing, and converts nothing. It mostly just emits a warning).
  • The first use of ? extends Number. In generics terms, a ? is identical to: Make up a new variable, give it this bound, use the variable here and nowhere else, and make it non-specifiable.
  • The second use of ? extends Number.

Given that all 3 are completely unrelated, your code does not compile without crazy warnings and does not give you the type safety you desire: Casting the first argument to T is non-sensical (perhaps the first of the 3 is Integer, the second is Character - These are not type compatible!)


Quick sidebar:

There's a minority report-style alternate interpretation here: That your intent is for the copy method to convert one numeric type to another - that you intend for it to be possible to pass a Paar<Byte> as source and a Paar<Double> as target, and that the copy code will convert the bytes in source to doubles and then assign them to paar. I can see how the fact that these utterly unrelated tasks (type coercion or assertion, vs. primitive conversion) are muddled up in your brain as being somehow similar. They are not - they are completely unrelated. But in java they so happen to use the exact same syntax (the 'cast' operation: (SomeType) someExpr). Make no mistake. This is as relevant as the fact that in english, the word arm means both a body part and the act of handing someone a gun. Unrelated. Completely. I'm assuming this is not what you want.


Let's fix your problem

You simply want to link the source and destination. You want to tell the compiler that there must be some relationship between the typevars on the src and dest Paar such that copying the values from one to the other can't go wrong.

To do this, we obviously need to have only a single parameter, first:

public static <T extends Number> void copy(Paar<T> src, Paar<T> dst) {

Now it already compiles fine, though your main code still won't work, we'll fix that in a moment. The above code says: There is some type, we shall call it T and we have no idea what it is. We do restrict it: It's either Number or some subtype of Number. It's almost like there are a million versions of this method - one for Number and one for each and every subtype of it (and given that Number is not 'sealed', i.e. anybody can write code that extends Number, it's theoretically infinite, of course).

Point is, there is only one type var, so the caller can pick whatever type they want, as long as it is Number or a subtype of it, but, they only get to pick one type - both Paar objects have that as their T. Thus, you simply cannot call this method with e.g. a Paar<Double> and a Paar<Integer> - nothing can be picked for T that works at this point (generics are invariant, meaning, Number wouldn't work here. Only the exact type will do).

But this is not quite true, is it? There ARE different types available that still do 'work' - specifically, if the T of the source Paar is a subtype of the T of the dest paar, that does work, as in your example. So we need some slight tweak. You can pick one, it doesn't matter:

<T extends Number> void copy(Paar<T> src, Paar<? super T> dst)

<T extends Number> void copy(Paar<? extends T> src, Paar<T> dst)

Either one works. This is saying:

The caller gets to pick a type. Any type, as long as it is Number or a subtype of it. The caller is then on the hook to do the following:

For the first version:

  • For the first arg pass a Paar object whose T matches exactly with the chosen T for this method invocation.
  • For the second arg, pass a Paar object whose T is either identical to the chosen T for this method invocation, or any supertype of it.

For the second version:

  • For the first arg pass a Paar object whose T is either exactly the chosen T for this method invocation, or any subtype of that T.
  • For the second arg, pass a Paar object whose T matches exactly.

The nice thing is, generally the caller does not have to explicitly pick a T, the compiler will infer one that 'works'. They can explicitly pick if they want.

Now your code works - if you go with the first idea, then Bounds.copy(src, dst) will mean the compiler picks Integer for T, and then the invoke works out: The first arg must be exactly Paar<Integer> (it is), and the second arg must be Paar<X> where X is Integer or one of its supertypes. It is - Number is a supertype of Integer. Thus, the call works. Note that you need no casts to (T) any where in your copy code!

In the second case it also works - the compiler will pick Number, and then checks that the first arg is indeed a Paar<Y> where Y is Number or a subtype (it is; Integer is a subtype of Number), and if the second arg is exactly Paar<Number>, which it is.

  • Related