String to Class: ClassNotFoundException

edited March 2018 in Questions about Code

Hi there.

As many before have no doubt tried, I'm attempting to create a class instance from a string. I've read that it's possible, and even seems fairly straightforward, but it's not working for me. Any help appreciated! Here's the code:

import java.lang.Class;

String model = "Ribbon";

void setup(){
  noLoop(); 

  // test 1 -- successfully prints the instance of a Ribbon class
  Object ribbon1 = new Ribbon();
  println(ribbon1.getClass());

  // test 2 -- uncommenting the following line gives a ClassNotFoundException
  //Object ribbon2  = Class.forName(model);
}

class Ribbon {
  Ribbon(){
    println("New Instance of Ribbon!");
  }
}

Answers

  • edited March 2018 Answer ✓
    1. Java already imports the package java.lang automatically.
      That import statement is completely unnecessary!
    2. As it is the case for all reflexive operations, Class.forName():
      https://Docs.Oracle.com/javase/9/docs/api/java/lang/Class.html#forName-java.lang.String-
      Demands to be inside a try {} / catch () {} structure:
      https://Processing.org/reference/try.html
      B/c it may throw a ClassNotFoundException:
      https://Docs.Oracle.com/javase/9/docs/api/java/lang/ClassNotFoundException.html
    3. Also, the String parameter from method Class.forName() gotta be a fully qualified name for the target class. That includes its fully package name besides its name.
      However, b/c your target class Ribbon is also a nested inner class, all of its enclosing classes must be included in its fully qualified name too!
      Take a look at the sketch example from this old forum thread link below:
      https://Forum.Processing.org/two/discussion/7036/multiple-screens#Item_9
  • Once again I thank GoToLoop for the quick reply!

    1) Didn't know that.

    2) Didn't know that it was mandatory.

    3) Ok, so now I've learned that Ribbon is a nested inner class of Sketch, I assume. The example was helpful, but I don't understand how, given my lone string variable, I can pass it as an arg to Class.forName() as a fully qualified name.

    So, to be blunt, what does the line look like?

  • edited March 2018
    /**
     * Reflexive Inner Class Instantiation (v1.1)
     * GoToLoop (2018/Mar/24)
     *
     * Forum.Processing.org/two/discussion/27164/
     * string-to-class-classnotfoundexception#Item_3
     */
    
    void setup() {
      Class<?> appCls = getClass(), innerCls = null;
      try {
        innerCls = Class.forName(appCls.getName() + "$Inner");
        //innerCls = Class.forName(appCls.getDeclaredClasses()[0].getName());
      }
      catch (final ClassNotFoundException ex) {
        System.err.println(ex);
      }
      println(innerCls);
    
      Inner inner = null;
      try {
        // Works only if Inner is a static class:
        //inner = (Inner) innerCls.newInstance();
    
        // If Inner is an inner class:
        inner = (Inner) innerCls.getDeclaredConstructor(appCls).newInstance(this);
      }
      catch (final ReflectiveOperationException ex) {
        System.err.println(ex);
      }
      println(inner);
    
      exit();
    }
    
    //static
    class Inner {
      @ Override String toString() {
        return getClass().getName();
      }
    }
    
  • edited March 2018

    Wow, I can't wrap my head around that, I'm not there yet. 8-}

    Ok, since you were so immensely helpful to me last time, let me tell you what my goal is in case you have a simpler solution.

    A csv file has a field name that contains a string of a class I want to instantiate (along with its parms). I can parse it in a giant switch block to instantiate the right class, but after reading about Java's reflexive capabilities I thought it might be possible to do it that way. Well, perhaps it might be, but the hoops to jump through seem too onerous to negotiate...

  • /**
     * Reflexive Inner Class Instantiation (v2.0)
     * GoToLoop (2018/Mar/24)
     *
     * Forum.Processing.org/two/discussion/27164/
     * string-to-class-classnotfoundexception#Item_5
     */
    
    void setup() {
      final Class<?> appCls = getClass(), innerCls = appCls.getDeclaredClasses()[0];
      println(innerCls);
    
      Inner inner = null;
      try {
        inner = (Inner) innerCls.getDeclaredConstructor(appCls).newInstance(this);
      }
      catch (final ReflectiveOperationException ex) {
        System.err.println(ex);
      }
      println(inner);
    
      exit();
    }
    
    class Inner {
      @ Override String toString() {
        return getClass().getName();
      }
    }
    
  • ... but the hoops to jump through seem too onerous to negotiate...

    In Java, the official & preferable way to instantiate classes & interfaces is via the operator new:
    https://Processing.org/reference/new.html

    In Java, all of its members should be known at compile-time.

    Reflective operations is an advanced & complicated way to inquire whether some member exists at runtime.

  • ... in case you have a simpler solution.

    W/o knowing your ".csv" file, the classes you made to represent them, how similar those classes are to each other, there's no way to tailor the most possible simplest solution. :-@

  • edited March 2018

    But the try block still needs to cast inner as (Inner). If I have many inner classes, wouldn't each inner class need its own try block to properly cast the assignment? That rather defeats the point of a generalized solution. (I've gotten too accustomed to a simple eval() to do such things. I must jettison the idea that I can use such handy tricks in Java, it appears.)

    It seems that a switch block is probably the simplest and most versatile solution. Oh well...

    Thank you for your responses, as always.

  • Answer ✓

    The try/catch blocks are always mandatory in order to call newInstance(), no matter whether it's a regular, nested or inner class! =;

    You can of course place the whole try/catch blocks inside some function which accepts a Class argument. *-:)

    Then you can (cast) the returning Object to its actual datatype. :-bd

    But reflective operations should be used as a last recourse only, when the regular means aren't enough! L-)

  • edited March 2018 Answer ✓

    I've gotten too accustomed to a simple eval() to do such things.

    https://Forum.Processing.org/two/discussion/7147/some-simple-pulse-equations-using-trig-#Item_8

  • Cool! Thx!

  • Proof of concept test, in case anyone else encounters the same problem:

    String[] sa = {"Ribbon","Box","Circle","Ribbon"};
    
    void setup(){
    
      Shape[] shapes = new Shape[sa.length];
    
      int i = 0;
      for (String s : sa){
        if (s.equals("Ribbon")){
          shapes[i] = new Ribbon();
        }
        if (s.equals("Box")){
          shapes[i] = new Box();
        }
        if (s.equals("Circle")){
          shapes[i] = new Circle();
        }
        i++;
      }
    
      for (Shape s : shapes){
        s.test();
      }
    }
    
    
    interface Shape {
      void test();
    }
    
    public class Ribbon implements Shape {
       void test(){ 
         println("Hello Ribbon.test!"); 
       }
    }
    
    public class Circle implements Shape {
       void test() {
        println("Hello Circle.test!");
      }
    }
    
    public class Box implements Shape {
       void test() {
        println("Hello Box.test!");
      }
    }
    
  • The answer I originally sought can be found here.

Sign In or Register to comment.