Get a load of that name! (很不错的文章!)

本文探讨了Java中Class.forName()与ClassLoader.loadClass()两种动态加载类的方法之间的差异,包括它们使用的类加载器不同及是否初始化加载后的类。此外,还讨论了如何利用这些差异以及类初始化错误的处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Get a load of that name!

Subtle differences in various ways you can dynamically load a class

<!--<BLOCKQUOTE><STRONG>Summary</STRONG><BR>--><!--</BLOCKQUOTE>--> By Vladimir Roubtsov


Printer-friendly versionPrinter-friendly version | Send this article to a friendMail this to a friend


<!-- REPLACE PAGECOUNT -->

<!-- START BIG AD (336x280) jw-articles-336x280.txt -->

<!-- END BIG AD (336x280) -->

March 14, 2003

Q What is the difference between Class.forName() and ClassLoader.loadClass()?

A Both methods try to dynamically locate and load a java.lang.Class object corresponding to a given class name. However, their behavior differs regarding which java.lang.ClassLoader they use for class loading and whether or not the resulting Class object is initialized.

The most common form of Class.forName(), the one that takes a single String parameter, always uses the caller's classloader. This is the classloader that loads the code executing the forName() method. By comparison, ClassLoader.loadClass() is an instance method and requires you to select a particular classloader, which may or may not be the loader that loads that calling code. If picking a specific loader to load the class is important to your design, you should use ClassLoader.loadClass() or the three-parameter version of forName() added in Java 2 Platform, Standard Edition (J2SE): Class.forName(String, boolean, ClassLoader).

Additionally, Class.forName()'s common form initializes the loaded class. The visible effect of this is the execution of the class's static initializers as well as byte code corresponding to initialization expressions of all static fields (this process occurs recursively for all the class's superclasses). This differs from ClassLoader.loadClass() behavior, which delays initialization until the class is used for the first time.

You can take advantage of the above behavioral differences. For example, if you are about to load a class you know has a very costly static initializer, you may choose to go ahead and load it to ensure it is found in the classpath but delay its initialization until the first time you need to make use of a field or method from this particular class.

The three-parameter method Class.forName(String, boolean, ClassLoader) is the most general of them all. You can delay initialization by setting the second parameter to false and pick a given classloader using the third parameter. I recommend always using this method for maximum flexibility.

Class initialization errors are tricky
Just because you successfully load a class does not mean there won't be any more problems. Recollect that static initialization code can throw an exception, and it will get wrapped in an instance of java.lang.ExceptionInInitializerError, at which point, the class becomes unusable. Thus, if it is important to process all such errors at a known point in code, you should use a Class.forName() version that performs initialization.

Furthermore, if you handle ExceptionInInitializerError and take measures so that the initialization can be retried, it will likely not work. This code demonstrates what happens:

public class Main
{
public static void main (String [] args) throws Exception
{
for (int repeat = 0; repeat < 3; ++ repeat)
{
try
{
// "Real" name for X is outer class name+$+nested class name:
Class.forName ("Main$X");
}
catch (Throwable t)
{
System.out.println ("load attempt #" + repeat + ":");
t.printStackTrace (System.out);
}
}
}

private static class X
{
static
{
if (++ s_count == 1)
throw new RuntimeException ("failing static initializer...");
}

} // End of nested class

private static int s_count;

} // End of class

This code attempts to load the nested class X three times. Even though X's static initializer fails only on the first attempt, all of them fail:

>java Main
load attempt #0:
java.lang.ExceptionInInitializerError
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:140)
at Main.main(Main.java:17)
Caused by: java.lang.RuntimeException: failing static initializer...
at Main$X.<clinit>(Main.java:40)
... 3 more
load attempt #1:
java.lang.NoClassDefFoundError
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:140)
at Main.main(Main.java:17)
load attempt #2:
java.lang.NoClassDefFoundError
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:140)
at Main.main(Main.java:17)

It is slightly surprising that the errors on subsequent load attempts are instances of java.lang.NoClassDefFoundError. What happens here is that the JVM has already noted the fact that X has been loaded (before the initialization is attempted), and the class cannot be unloaded until the current classloader is garbage collected. So, on subsequent calls to Class.forName(), the JVM does not attempt initialization again but, rather misleadingly, throws an instance of NoClassDefFoundError.

The proper way to reload such a class is to discard the original classloader instance and create a new one. Of course, this can be done only if you had anticipated that and used the proper three-parameter form of forName().

The hidden Class.forName()
I am sure you have used Java's X.class syntax to obtain a Class object for a class whose name is known at compile time. Less well known is how this is implemented at the byte-code level. The details are different across compilers, but all of them generate code that uses the one-parameter form of Class.forName() behind the scenes. For example, javac from J2SE 1.4.1 translates Class cls = X.class; into the following equivalent form:

...
// This is how "Class cls = X.class" is transformed:
if (class$Main$X == null)
{
class$Main$X = class$ ("Main$X");
}
Class cls = class$Main$X;

...

static Class class$ (String s)
{
try
{
return Class.forName (s);
}
catch (ClassNotFoundException e)
{
throw new NoClassDefFoundError (e.getMessage());
}
}

static Class class$Main$X; // A synthetic field created by the compiler

Of course, everything mentioned above about Class.forName()'s short form always initializing the class in question applies to X.class syntactic form as well. The details are different when such syntax is used to get Class objects for primitive and array types, and I leave that as an exercise for curious readers.

Fun with Sun's javac
In the previous example, you saw that the result of loading the class was cached in a special package-private static field artificially created by the compiler, and a synthetic helper method executed Class.forName(). The reason this is convoluted may be because the syntax used was unavailable in early Java versions, so the feature was added on top of the Java 1.0 byte-code instruction set.

Armed with this insight, you can have a bit of fun at the compiler's expense. Compile this tongue-in-cheek code snippet using javac from J2SE 1.3.1:

public class Main
{
public static void main (String [] args) throws Exception
{
System.out.println ("String class: " + String.class);
class$java$lang$String = int.class;
System.out.println ("String class: " + String.class);
}

static Class class$java$lang$String;

} // End of class

If you run it, you will get the following, which is ridiculous at best:

>java Main
String class: class java.lang.String
String class: int

At least the compiler in J2SE 1.4.1 will reject the code above. But you can still fool it by setting the field reflectively:

public static void main (String [] args) throws Exception
{
System.out.println ("String class: " + String.class);
Main.class.getDeclaredField ("class$java$lang$String").set (null, int.class);
System.out.println ("String class: " + String.class);
}

So, next time you code the familiar Class.forName() incantation, you should know what it entails and what alternatives exist.


<!-- REPLACE PAGEURLS --><!-- REPLACE TALKBACK -->

Printer-friendly versionPrinter-friendly version | Send this article to a friendMail this to a friend

About the author
Vladimir Roubtsov has programmed in a variety of languages for more than 13 years, including Java since 1995. Currently, he develops enterprise software as a senior developer for Trilogy in Austin, Texas.

Resources
Jeff Friesen's "Class and Object Initialization" Java 101 lesson ( JavaWorld, November 2001) studies initialization in some detail:
http://www.javaworld.com/javaworld/jw-11-2001/jw-1102-java101.html
Want more? See the Java Q&A index page for the full Q&A catalog:
http://www.javaworld.com/columns/jw-qna-index.shtml
For more than 100 insightful Java tips, visit JavaWorld's Java Tips index page:
http://www.javaworld.com/columns/jw-tips-index.shtml
Browse the Core Java section of JavaWorld's Topical Index:
http://www.javaworld.com/channel_content/jw-core-index.shtml
Get more of your questions answered in our Java Beginner discussion:
http://forums.devworld.com/webx?50@@.ee6b804
Sign up for JavaWorld's free weekly email newsletters:
http://www.javaworld.com/subscribe
You'll find a wealth of IT-related articles from our sister publications at IDG.net
### 回答1: I'm sorry, but it seems like you are encountering an issue with a specific Java class, `GulimailProductApplicationTests`, that cannot be loaded. This error message suggests that the Java Virtual Machine (JVM) cannot find the class in your codebase. There could be several reasons why this error is happening. Here are a few things you can try to resolve this issue: 1. Check if the class `GulimailProductApplicationTests` is present in your project's codebase. If it is not present, you may need to create the class and ensure that it is in the correct package and directory. 2. Ensure that the class `GulimailProductApplicationTests` is correctly defined with the right package and class name. You should check that the class name is spelled correctly and that it is located in the right package. 3. If you are using an IDE like Eclipse, IntelliJ or NetBeans, try cleaning your project and rebuilding it. This can help refresh your project's dependencies and ensure that all the classes are compiled correctly. 4. Check that your project dependencies are correctly defined. If the class `GulimailProductApplicationTests` is in a different module or library, you may need to add the appropriate dependency to your project's build file. 5. Check that your project is correctly configured to use the correct version of Java. If the class `GulimailProductApplicationTests` requires a specific version of Java that is not installed on your system, you may need to install it or update your project's configuration to use a different version. If none of these solutions work, you may need to provide more information about your project and the error message you are seeing to get more specific help. ### 回答2: Could not load class with name: com.example.gulimail.product.GulimailProductApplicationTests 是一个错误信息,它意味着无法加载名为 com.example.gulimail.product.GulimailProductApplicationTests 的类。 通常,这种错误信息表示测试类无法在给定的包路径下找到或加载。出现此错误的原因可能有以下几种: 1. 类路径问题:请确保测试类的路径正确,即 com.example.gulimail.product.GulimailProductApplicationTests 类在指定的包路径下。 2. 缺少依赖项:如果测试类依赖于其他类或库,需要确保这些依赖项已经正确地包含在项目中。 3. 编译错误:如果在编译测试类时存在错误,可能会导致无法加载该类。请检查测试类中是否存在语法错误或其他编译错误,并确保修复它们。 4. 缺少测试框架:如果测试类使用了特定的测试框架,例如JUnit或TestNG,需要确保该框架已经正确地添加到项目依赖中。 为了解决这个问题,你可以尝试以下几个步骤: 1. 确认类路径和包路径是否正确,检查测试类的位置是否正确。 2. 检查项目的依赖项,确保所有必需的类和库已经包含在项目中。 3. 检查测试类中是否存在语法错误或其他编译错误,并修复它们。 4. 确保所使用的测试框架已经正确地添加到项目依赖中。 如果尝试了以上步骤仍然无法解决问题,可能需要进一步检查项目配置和环境设置,或者查看错误日志以获取更详细的错误信息。 ### 回答3: 无法加载类名为com.example.gulimail.product.GulimailProductApplicationTests的类。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值