// some silly classes for our list elements class A { void m1() { System.out.println("a1"); } } class B extends A { void m1() { System.out.println("b1"); } void m2() { System.out.println("b2"); } } // version one: subtyping class ListSub { Object hd; ListSub tl; ListSub(Object hd, ListSub tl) { this.hd = hd; this.tl = tl; } static ListSub copy(ListSub src) { // a while also works if(src.tl==null) return new ListSub(src.hd,null); return new ListSub(src.hd, copy(src.tl)); } } class ClientSub { public static void main(String[] args) { ListSub lstB = new ListSub(new B(), new ListSub(new B(), null)); ListSub lstA = new ListSub(new A(), ListSub.copy(lstB)); System.out.println("---"); for(ListSub l=lstB; l!=null; l = l.tl) ((B)l.hd).m1(); System.out.println("---"); for(ListSub l=lstA; l!=null; l = l.tl) ((A)l.hd).m1(); System.out.println("---"); for(ListSub l=lstB; l!=null; l = l.tl) ((A)l.hd).m1(); System.out.println("---"); for(ListSub l=lstA; l!=null; l = l.tl) ((B)l.hd).m1(); // run-time ClassCastException System.out.println("---"); } } // version two: parametric polymorphism class ListPoly { T hd; ListPoly tl; ListPoly(T hd, ListPoly tl) { this.hd = hd; this.tl = tl; } static ListPoly copy(ListPoly src) { // a while also works if(src.tl==null) return new ListPoly(src.hd,null); return new ListPoly(src.hd, copy(src.tl)); } } class ClientPoly { public static void main(String[] args) { ListPoly lstB = new ListPoly(new B(), new ListPoly(new B(), null)); // this does not type-check (look at copy's type)! // ListPoly lstA = new ListPoly(new A(), ListPoly.copy(lstB)); // this happens to work because Java does not actually check things // at run-time -- but it really is "wrong", get a warning at least // and it is bad style (you are casting around the polymorphism) ListPoly lstA = new ListPoly(new A(), ListPoly.copy(lstB)); // lstB = lstA -- no type-check, elements don't have m2 // lstA = lstB -- no type-check, allows mutating elements not to have m2 System.out.println("---"); for(ListPoly l=lstB; l!=null; l = l.tl) l.hd.m1(); // no cast needed System.out.println("---"); for(ListPoly l=lstA; l!=null; l = l.tl) l.hd.m1(); System.out.println("---"); //for(ListPoly l=lstB; l!=null; l = l.tl) // no type-check // l.hd.m1(); //System.out.println("---"); //for(ListPoly l=lstA; l!=null; l = l.tl) // no type-check // l.hd.m1(); //System.out.println("---"); } } // version three: combining the two ("bounded polymorphism") // whole point is copy can return a list whose elements are a // supertype of the argument list's elements class ListBounded { T hd; ListBounded tl; ListBounded(T hd, ListBounded tl) { this.hd = hd; this.tl = tl; } static ListBounded copy(ListBounded src) { if(src.tl==null) return new ListBounded(src.hd,null); return new ListBounded(src.hd, ListBounded.copy(src.tl)); } } class ClientBounded { public static void main(String[] args) { ListBounded lstB = new ListBounded(new B(), new ListBounded(new B(), null)); // this type-checks now! ListBounded lstA = new ListBounded(new A(),ListBounded.copy(lstB)); // lstB = lstA -- no type-check, elements don't have m2 // lstA = lstB -- no type-check, allows mutating elements not to have m2 System.out.println("---"); for(ListBounded l=lstB; l!=null; l = l.tl) l.hd.m1(); // no cast needed System.out.println("---"); for(ListBounded l=lstA; l!=null; l = l.tl) l.hd.m1(); System.out.println("---"); } }