Sunday, December 1, 2024

Java Generic Wildcards

this text is best read in a fixed width terminal 


C++ compiles code for each required parameterized type
Java compiles only one instance of the parameterized type, using Object as the type

because generic types in java erase information on type
  and still the type must be verified by the compiler
  there are cases when the code should compile but it doesn't
  the solution was to relax type checks at compile time


the '?' wildcard can only appear on the left hand side of an assignment
    and only when the variable or parameter is defined
  it imposes constraints on the right hand side of the assignment

a variable of <? extends MyClass> type can only refer objects of types that inherit MyClass
    this sets an upper bound
  you can't write to it because
    the compiler can't ensure that what you write is a derivate of its current content
    and therefore it cannot enforce homogeneity (?!)
  you can read <? super MyClass>'s from it
  this variable is a producer

a variable of <? super MyClass> type can only refer objects of types that are inherited by MyClass
    this sets a lower bound
  you can write <? extends MyClass>'s to it
  you can only read in Object's from it because the compiler only knows that it is a derivate of Object
  this variable is a consumer

variables of <?> type can refer objects of any type
  you can not write to it because
    the compiler can't ensure that what you write is a derivate of its current content
    and therefore it cannot enforce homogeneity (?!)
  you can only read in Object's from it because the compiler only knows that it is a derivate of Object

these constraints are verified at compile time


class Wild {
  public static void main(String args[]) {

    /*
    all errors are compile time

    as long as the type of right hand side in the variable declaration
        is compatible with the variable type,
      its type does not influence future compatibilities of the variable
      because its type in the assigned variable is not known to the compiler at the compile time

    of course, anything can be done with explicit type casts
    */


    MyGen<? extends MyClass> ge = new MyGen<MyClass>(new MyClass(1, 2));
      // ge = MyGen, extends
    // ge.e cannot be written but it can be read
    //   ge.e cannot be written because it may hold any class equal to or below MyClass
    //   and they need to ensure ge.e's homogeneity (?!)
    // ge.e is a producer
    // ge.e = new MyBase(3);
      // ByBase cannot be converted to CAP#1
      //   CAP#1 extends MyClass from capture of ? extends MyClass
    // ge.e = new MyClass(4, 5);
      // MyClass cannot be converted to CAP#1
      //   CAP#1 extends MyClass from capture of ? extends MyClass
    // ge.e = new MyDeriv(6, 7, 8);
      // MyDeriv cannot be converted to CAP#1
      //   CAP#1 extends MyClass from capture of ? extends MyClass
    MyBase geb = ge.e;
    MyClass gec = ge.e;
    // MyDeriv ged = ge.e;
      // incompatible types, ge.e can have any type equal or below MyClass
    System.out.println("ge.e = " + ge.e + "\t\tge.e.i = " + ge.e.i + "\tge.e.j = " + ge.e.j);


    MyGen<? super MyClass> gs = new MyGen<MyClass>(new MyClass(1, 2));
      // gs = MyGen, super
    // gs.e can be written but it can be read only into an Object
    //   gs.e can be read only into an Object because it may hold any class equal or above MyClass
    // gs.e is a consumer
    // gs.e = new MyBase(3);
      // incompatible types, gs.e can have any type equal to or above MyClass
    gs.e = new MyClass(4, 5);
    gs.e = new MyDeriv(6, 7, 8);
    // MyBase gsb = gs.e;
      // CAP#1 cannot be converted to MyBase
      //   CAP#1 extends Object super: MyClass from capture of ? super MyClass
    // MyClass gsc = gs.e;
      // CAP#1 cannot be converted to MyClass
      //   CAP#1 extends Object super: MyClass from capture of ? super MyClass
    // MyDeriv gsd = gs.e;
      // CAP#1 cannot be converted to MyDeriv
      //   CAP#1 extends Object super: MyClass from capture of ? super MyClass
    Object gso = gs.e;
    // the type of gs.e is known at run time
    System.out.println("gs.e class is: " + gs.e.getClass().getName());
      // gs.e class is: MyDeriv
    // but not at compile time, when gs.e is only known to be of type Object
    // System.out.println("gs.e = " + gs.e + "\t\tgs.e.i = " + gs.e.i + "\tgs.e.j = " + gs.e.j +
    //                      "\tgs.e.k = " + gs.e.k);
      // cannot find symbols gs.e.i, gs.e.j and gs.e.k


    MyGen<?> g = new MyGen<MyClass>(new MyClass(1, 2));
    // g = MyGen
    // we know nothing about the type of g
    //   so the only allowed operation is to read it into an Object
    // g.e = new MyBase(3);
      // MyBase cannot be converted to CAP#1
      //   CAP#1 extends Object from capture of ?
    // g.e = new MyClass(4, 5);
      // MyClass cannot be converted to CAP#1
      //   CAP#1 extends Object from capture of ?
    // g.e = new MyDeriv(6, 7, 8);
      // MyDeriv cannot be converted to CAP#1
      //   CAP#1 extends Object from capture of ?
    // MyBase gb = g.e;
      // CAP#1 cannot be converted to MyBase
      //   CAP#1 extends Object from capture of ?
    // MyClass gc = g.e;
      // CAP#1 cannot be converted to MyClass
      //   CAP#1 extends Object from capture of ?
    // MyDeriv gd = g.e;
      // CAP#1 cannot be converted to MyDeriv
      //   CAP#1 extends Object from capture of ?
    Object go = g.e;
    // System.out.println("g.e = " + g.e + "\t\tg.e.i = " + g.e.i + "\tg.e.j = " + g.e.j);
      // cannot find symbols g.e.i and g.e.j
  }
}


// my generic class: it stores an object of a generic type
class MyGen<T> {
  T e;
  // element
  MyGen(T e) {
    this.e = e;
  }
}

class MyBase {
  int i;
  MyBase(int i) {
    this.i = i;
  }
}

class MyClass extends MyBase {
  int j;
  MyClass(int i, int j) {
    super(i);
    this.j = j;
  }
}

class MyDeriv extends MyClass {
  int k;
  MyDeriv(int i, int j, int k) {
    super(i, j);
    this.k = k;
  }
}