【转】Get a load of that name! (很不错的文章!)
Get a load of that name!
Subtle differences in various ways you can dynamically load a class
By Vladimir Roubtsov Printer-friendly version |
Mail this to a friend
March 14, 2003
What is the difference between
Class.forName() and
ClassLoader.loadClass()?
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.
Printer-friendly version |
Mail 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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述