String to Class, Round 2

edited April 2018 in Questions about Code

I've previously posted this question, but now that I'm coming to understand java better I'm better able to ask it more fruitfully.

The Problem: I have a string. I want to instantiate a class from it. But I can't.

I've come across many pages that provide what seems to be a fairly straightforward solution, but I cannot get it to work. I'm kind of aware that all the code being executed in a processing session is wrapped in a superclass of some sort, so this could be a knotty reflection problem, but hopefully not an insurmountable one. (Once my class is instantiated I can use reflection to access its fields, so that's good...)

Being able to do this is the crux of my pipeline. That's why, with tail between my legs, I come back seeking the answer again. I do have a workaround using a switch block, but being able to do this with Class.forName(string) would obviously be far more efficient.

Anyways, without further ado, here is the code. Any help solving what to me is an intractable problem is very appreciated!

import java.lang.reflect.Field;

void setup(){

  String thisProc = this.getClass().getCanonicalName();

  ArrayList<Model> models = new ArrayList<Model>();
  String[] sa = {"Box","Rect"};

  for (String m : sa){

    Model newModel = null;

    /* this is what I want to use, but it doesn't work */
    try {

      String curClass = thisProc+"."+m; // fully qualified class name
      Class clazz = Class.forName(curClass);
      newModel = (Model) clazz.newInstance();
      println(newModel.getClass());

    } catch (ClassNotFoundException e){
      e.printStackTrace();
    } catch (InstantiationException e){
      e.printStackTrace();
    } catch (IllegalAccessException e){
      e.printStackTrace();
    }

    /* this is what I currently use -- it works, but it's definitely not optimal */
    switch(m){
      case "Box":
        newModel = new Box();
        break;
      case "Rect":
        newModel = new Rect();
        break;
    }

    models.add(newModel);

  }

  for (Model m : models){
    try {
        Field field = m.getClass().getDeclaredField("testVar");
        field.set(m, m.getClass().getName());
    } catch (NoSuchFieldException e) { println(m.getClass()," has no field testVar");
    } catch (Exception e) { throw new IllegalStateException(e); }

    m.hello();    
  }
}


interface Model {
  String testVar = "";
  void hello();
}

class Box implements Model {
  String testVar;
  void hello(){ println("hello from",testVar); }
}

class Rect implements Model {
  String testVar;
  void hello(){ println("hello from",testVar); }
}

Answers

  • edited April 2018 Answer ✓

    You have two main problems and one surplus constant.

    Problems
    1) Your 'fully qualified class name' is incorrectly formed
    2) Box and Rect are inner classes but the instantiation method used is only suitable for top-level classes.

    Surplus constant
    In the Model interface the String testVar = ""; will be treated as a constant so has no link to or relevance to the field with the same name in the Box and Rect classes. In fact it is only likely to cause confusion so Delete it.

    So here is the code that works.

    import java.lang.reflect.*;
    
    void setup() {
    
      String thisProc = this.getClass().getCanonicalName();
    
      ArrayList<Model> models = new ArrayList<Model>();
      String[] sa = {"Box", "Rect"};
    
      // get Enclosing class
    
    
      for (String m : sa) {
        Model newModel = null;
    
        try {
          Class<?> innerClass = Class.forName(thisProc + "$" + m);
          Constructor<?> ctor = innerClass.getDeclaredConstructor( this.getClass());
          newModel = (Model) ctor.newInstance(this);
          println(newModel.getClass());
        } 
        catch (ClassNotFoundException e) {
          e.printStackTrace();
        } 
        catch (NoSuchMethodException e) {
          e.printStackTrace();
        } 
        catch (InvocationTargetException e) {
          e.printStackTrace();
        } 
        catch (InstantiationException e) {
          e.printStackTrace();
        } 
        catch (IllegalAccessException e) {
          e.printStackTrace();
        }
    
        models.add(newModel);
      }
    
      for (Model m : models) {
        try {
          Field field = m.getClass().getDeclaredField("testVar");
          field.set(m, m.getClass().getName());
        } 
        catch (NoSuchFieldException e) { 
          println(m.getClass(), " has no field testVar");
        } 
        catch (Exception e) { 
          throw new IllegalStateException(e);
        }
    
        m.hello();
      }
    }
    
    
    interface Model {
      void hello();
    }
    
    class Box implements Model {
      String testVar;
      void hello() { 
        println("hello from", testVar);
      }
    }
    
    class Rect implements Model {
      String testVar;
      void hello() { 
        println("hello from", testVar);
      }
    }
    
  • OMFG! Thank you!

    Were I to sire an heir I would name him Quark.

  • D'OH! So close yet so far!

    If I put the instantiating try block in a different class it seems to fail because it's looking for the constructor in itself, not the outer class...?

    import java.lang.reflect.*;
    
    ArrayList<Model> models = new ArrayList<Model>();
    String[] sa = {"Box", "Rect"};
    String thisProc;
    
    void setup() {
      noLoop();
      thisProc = this.getClass().getCanonicalName();
    }
    
    void draw(){
      SetModels sm = new SetModels();
      sm.stringsToModels();
    
      for (Model m : models) {
        m.hello();
      }
    }
    
    class SetModels {
    
      void stringsToModels(){
        for (String m : sa) {
          Model newModel = null;
    
          try {
            Class<?> innerClass = Class.forName(thisProc + "$" + m);
            Constructor<?> ctor = innerClass.getDeclaredConstructor( this.getClass()); // 'this' refers to SetModels now, not the outer class
            newModel = (Model) ctor.newInstance(this);
            println(newModel.getClass());
          } 
          catch (ClassNotFoundException e) {
            e.printStackTrace();
          } 
          catch (NoSuchMethodException e) {
            e.printStackTrace();
          } 
          catch (InvocationTargetException e) {
            e.printStackTrace();
          } 
          catch (InstantiationException e) {
            e.printStackTrace();
          } 
          catch (IllegalAccessException e) {
            e.printStackTrace();
          }
    
          models.add(newModel);
        }
    
        for (Model m : models) {
          try {
            Field field = m.getClass().getDeclaredField("testVar");
            field.set(m, m.getClass().getName());
          } 
          catch (NoSuchFieldException e) { 
            println(m.getClass(), " has no field testVar");
          } 
          catch (Exception e) { 
            throw new IllegalStateException(e);
          }
        } 
      }
    }
    
    
    interface Model {
      void hello();
    }
    
    class Box implements Model {
      String testVar;
      void hello() { 
        println("hello from", testVar);
      }
    }
    
    class Rect implements Model {
      String testVar;
      void hello() { 
        println("hello from", testVar);
      }
    }
    
  • Answer ✓

    Not sure why you are doing this, but always keep in mind the KISS concept.

    Below is a suggestion of how your class should look. However, not sure why I am doing this...

    Kf

    class SetModels {
    
      PApplet p;
    
      SetModels(PApplet pa){
        p=pa;
      }
    
      void stringsToModels(){
        for (String m : sa) {
          Model newModel = null;
    
          try {
            Class<?> innerClass = Class.forName(thisProc + "$" + m);
            Constructor<?> ctor = innerClass.getDeclaredConstructor( p.getClass()); // 'this' refers to SetModels now, not the outer class
            newModel = (Model) ctor.newInstance(p);
            println(newModel.getClass());
          } 
          catch (ClassNotFoundException e) {
            e.printStackTrace();
          } 
          catch (NoSuchMethodException e) {
            e.printStackTrace();
          } 
          catch (InvocationTargetException e) {
            e.printStackTrace();
          } 
          catch (InstantiationException e) {
            e.printStackTrace();
          } 
          catch (IllegalAccessException e) {
            e.printStackTrace();
          }
    
          models.add(newModel);
        }
    
        for (Model m : models) {
          try {
            Field field = m.getClass().getDeclaredField("testVar");
            field.set(m, m.getClass().getName());
          } 
          catch (NoSuchFieldException e) { 
            println(m.getClass(), " has no field testVar");
          } 
          catch (Exception e) { 
            throw new IllegalStateException(e);
          }
        } 
      }
    }
    
  • Thank you for the reply, kfrajer.

    How do I instantiate the class within draw()? I've not used PApplet before...

  • Answer ✓

    You need to think about what this is referring to in your previous code.

    SetModels sm = new SetModels(this);

    The obejct sm is now aware of the current sketch.

    Kf

  • Yes, of course! Thank you!

  • Thank you both, quark & kfrajer, very much -- I've been stuck on this for a long time, and now things are working! Life can continue...

Sign In or Register to comment.