10

In order to schedule the execution of a job, i get the name of a class as a string-input.
This class may be in one of two packages, but i don't know which one so i have to check this.

By now, i have two try-catch-blocks

Class<Job> clazz;
String className; //the above mentioned string, will be initialized

try {
    clazz = (Class<Job>) Class.forName("package.one." + className);
} catch (ClassNotFoundException ex) {
    try {
        clazz = (Class<Job>) Class.forName("package.two." + className);
    } catch (ClassNotFoundException ex1) {
      //some error handling code here, as the class is
      //in neither of the two packages
    }
}

For more packages this will get uglier and more unreadable. Furthermore, it is - for me - against the concept of exceptions, as exceptions should'nt be expected/used for flow-control!
Is there any way to rewrite this without the utilization of the ClassNotFoundException?

KeyNone
  • 8,745
  • 4
  • 34
  • 51
  • 1
    why don't u use a loop to check every package with a break if you don't reach exception? – wxyz Nov 06 '13 at 10:33
  • or you can call recursively the same method in catch block with another classname – mishadoff Nov 06 '13 at 10:34
  • @wxyz, this won't be a (complete) solution, because he may be interested in a class, that is within some library. If there is a way to list all the loaded classes in the classpath at Runtime... – Konstantin Yovkov Nov 06 '13 at 10:34
  • Why can't you expect fully qualified name instead just a class name ?. Who is giving class name ? instead they can pass fully qualified class name right ? – Jayasagar Nov 06 '13 at 10:35
  • @Jayasagar the class name is stored in a database and it's not a small project. may have plenty of side-effects to switch to fully-qualified classnames – KeyNone Nov 06 '13 at 10:41

4 Answers4

6

I'd stick to the Class.forName method for that.

You can store the class and package names in Collections or Sets and loop through those elements. When you get a ClassNotFoundException, you just continue your search. If you don't get an exception, you exit the loop using break, as you have found the class you were looking for.

The reason I'd go for Class.forName is that it loads the class for you if it had not been already loaded by the VM. This is quite a powerful side effect.

It basically relieves you of the trouble of digging through the whole CLASSPATH and looking for class files to load in the VM and dealing with issues such as whether the class has already been loaded or not by the VM.

EDIT:

Jakob Jenkov has written some really great articles/tutorials on Java. I've found them extremely useful when dealing with reflection, class loaders and concurrency (some of the "hardest" aspects of Java).

Here's a good article on the Java class loader by him, for if you still decide not to use Class.forName.

Sean McManus
  • 137
  • 2
  • 11
lucian.pantelimon
  • 3,673
  • 4
  • 29
  • 46
  • Sounds reasonable! +1 for the side effect on `Class.forName`. – KeyNone Nov 06 '13 at 10:43
  • Just noticed the comment on your question about class names being stored in a DB. The article in I've linked to in my response also covers class reloading and it might help you, should you need to reload classes in your project. – lucian.pantelimon Nov 06 '13 at 10:48
  • Thanks for the background information on class loading. In my case, this isn't neccessary. I think i will stick to `forName` and utilize a loop as you wrote and the other answers implemented. – KeyNone Nov 06 '13 at 10:54
  • The link in the post is broken as the page has moved, here's the working link: https://jenkov.com/tutorials/java-reflection/dynamic-class-loading-reloading.html – Sean McManus Sep 15 '22 at 12:37
4
public Class<Job> getClass(String className) {
    String packages[] = { "package.one.", "package.two." };
    for (int j = 0; j < packages.length; j++) {
        try {
            return (Class<Job>) Class.forName(packages[j] + className);
        } catch (ClassNotFoundException ex) {
            System.out.println("Package "+packages[j]+" is not worked");
        }
    }
    return null;
}
pasha701
  • 6,831
  • 1
  • 15
  • 22
4

You can use Guava's Reflection utilities to get the ClassInfo of every class loaded in the classpath.

ClassLoader classLoader = ClassLoader.getSystemClassLoader();
ClassPath classPath = ClassPath.from(classLoader);
ImmutableSet<ClassInfo> set = classPath.getTopLevelClasses();
for (ClassInfo ci : set) {
    System.out.println(ci.getName());
}

In the loop you can implement your custom logic to load the class with the className you're providing.

Konstantin Yovkov
  • 62,134
  • 8
  • 100
  • 147
  • 2
    Definitely a piece of cake, but including another third-party-lib will be a bit over the top for this specific project and problem. – KeyNone Nov 06 '13 at 11:19
2

In this case, I wouldn't worry about using the ClassNotFoundException. While in general a case can be made to not use exceptions for flow control, here it hardly counts as such.

I'd probably wrap it in a function, like so

public static String getPackageForClass(String className, String... packageNames) {
    for (String packageName : packageNames) {
        try {
            Class.forName(packageName + className);
            return packageName;
        } catch (ClassNotFoundException ignored) {
        }
    }
    return "";
}

or return the class directly, if you so wish

public static Class getPackageForClass(String className, String... packageNames) {
    for (String packageName : packageNames) {
        try {
            return Class.forName(packageName + className);
        } catch (ClassNotFoundException ignored) {
        }
    }
    return null;
}
SQB
  • 3,926
  • 2
  • 28
  • 49