Java Factory using Generics, .class vs. .getClass() -


i searched , tried more day , not find solution common problem have in java. reason obvious - type erasure. question have is: there no solution problem in java? willing investigate more time since kind of problem pops every once in time.

the error is:

the method dostrategy(capture#2-of ? extends i) in type istrategy<capture#2-of ? extends i> not applicable arguments (i)

so simplified problem following example.

imagine model:

package model;  public interface {  //there actual 30 classes implementing i... }  public class implements {      public void somespecificmagicfora(){         system.out.println("a");     } }  public class b implements {      public void somespecificmagicforb() {         system.out.println("b");     }  } 

and selection logic

package strategy;  import model.a;  public interface istrategy<t> {      public void dostrategy(t t); }  public class astrategy implements istrategy<a> {      @override     public void dostrategy(a a) {         a.somespecificmagicfora();     } }  public class bstrategy implements istrategy<b> {      @override     public void dostrategy(b b) {         b.somespecificmagicforb();     } } 

and generic strategy factory

package strategy;  import java.util.hashmap; import java.util.map;  import model.a; import model.b;  public class strategyfactory {      static {         strategies.put(a.class, astrategy.class);         strategies.put(b.class, bstrategy.class);     }     private static final map<class<?>, class<? extends istrategy<?>>> strategies = new hashmap<>();      @suppresswarnings("unchecked") // fine suppress warning     public <t> istrategy<t> createstategy(class<t> clazz){         class<? extends istrategy<?>> strategyclass = strategies.get(clazz);          assert(strategyclass != null);          try {             return (istrategy<t>) strategyclass.newinstance();         } catch (instantiationexception | illegalaccessexception e) {             e.printstacktrace();             return null;         }     } } 

and here test

import java.util.arraylist; import java.util.list;  import junit.framework.testcase; import model.a; import model.b; import model.i; import strategy.istrategy; import strategy.strategyfactory;   public class testcases extends testcase {      public void testwithconcretetype(){          b b = new b();          strategyfactory factory = new strategyfactory();         istrategy<b> createstategy = factory.createstategy(b.class);         createstategy.dostrategy(b); //awesome     }      public void testwithgenerictype(){          list<i> instances = createtestdata(); // image business data          strategyfactory factory = new strategyfactory();          (i current : instances){             istrategy<? extends i> createstategy = factory.createstategy(current.getclass());             createstategy.dostrategy(current); //meh             //the method dostrategy(capture#2-of ? extends i) in type istrategy<capture#2-of ? extends i>              //is not applicable arguments (i)         }     }      private list<i> createtestdata(){         a = new a();         b b = new b();          list<i> instances = new arraylist<>();         instances.add(a);         instances.add(b);          return instances;     } } 

i have tried approach using guava typetokens (https://github.com/google/guava/wiki/reflectionexplained). did not manage working since have no <t> since collection of instances implementing interface.

i have working , not bad solution though using visitor-pattern. since there have real classes

...visitor class public void visit(a a){    dovisit(a.class, a); //private generic method works of course } 

everything fine again @ compile time. in particular case took me quite time implement visitor more 30 sub-classes of i. have better solution future.

any comments appreciated.

the problem has nothing erasure. java's type system not strong enough reason statically relationship between current , current.getclass() without help.

in code:

for (i current : instances){   istrategy<? extends i> createstategy = factory.createstategy(current.getclass());   createstategy.dostrategy(current); } 

the result of current.getclass() object of type class<? extends i>; is, subtype of i don't know statically. programmers know whatever type is, that's concrete type of current because we've read documentation getclass, type system doesn't know that. when istrategy<? extends i>, know it's strategy works on some subtype of i, not i itself. add fact wildcard types (the types ?) designed lose more information, , type system doesn't know our strategy accepts same type result of getclass().

so make program typecheck, need (a) give type system non-wildcard name specific subtype of i current is, , (b) convince value of current has type. news is, can both.

to give name wildcard type, can use technique called "wildcard capture", create private helper function job give specific type variable name type otherwise wildcard. we'll pull out body of test loop own function critically takes type parameter:

private <t> void generictesthelperdraft1(class<t> currentclass, t current) {   strategyfactory factory = new strategyfactory();   istrategy<t> createstrategy = factory.createstategy(t);   createstrategy.dostrategy(current); // works } 

the job of function introduce type parameter t, lets java know intend refer same unknown type t everywhere use it. information can understand strategy our factory works on same type our input class has, same type current current has in type signature.

unfortunately, when go call method we'll still compile error:

for (i current : instances){   generictesthelperdraft1(current.getclass(), current);   // type error because current not of type "capture of ? extends i" } 

the problem here type system doesn't know current has own type! java's type system doesn't understand relationship between current , current.getclass(), doesn't know whatever type current.getclass() returns, can treat current value of type. fortunately can fix simple downcast, since (the programmers) do know current has own type. have within our helper, since outside helper don't have name subtype want assert current has. can change code so:

private <t> void generictesthelperdraft2(class<t> t, object current) {   t currentdowncast = t.cast(current);   strategyfactory factory = new strategyfactory();   istrategy<t> createstrategy = factory.createstategy(t);   createstrategy.dostrategy(currentdowncast); } 

now can change loop within test to:

for (i current : instances){   generictesthelperdraft2(current.getclass(), current); } 

and works.


Comments

Popular posts from this blog

sql - VB.NET Operand type clash: date is incompatible with int error -

SVG stroke-linecap doesn't work for circles in Firefox? -

python - TypeError: Scalar value for argument 'color' is not numeric in openCV -