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 
a the '?' wildcard can only appear as the type of a variable or formal parameter declaration 
  it imposes constraints on the right hand side of the assignment or on the actual parameter 
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 
you can combine generics and wildcards: https://www.baeldung.com/java-generics-type-parameter-vs-wildcard
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; 
  } 
} 
