"Class.forName()" 和 "Class.forName().newInstance()" 有什么区别?

178

Class.forName()Class.forName().newInstance()有什么区别?

我不理解它们之间的重大区别(虽然我已经读了一些相关的内容!)。你能否帮助我理解一下吗?

9个回答

265

也许一个演示如何使用两种方法的示例会帮助您更好地理解事情。因此,请考虑以下类:

package test;

public class Demo {

    public Demo() {
        System.out.println("Hi!");
    }

    public static void main(String[] args) throws Exception {
        Class clazz = Class.forName("test.Demo");
        Demo demo = (Demo) clazz.newInstance();
    }
}

根据其javadoc的解释,调用Class.forName(String)方法返回与给定字符串名字关联的类或接口的Class对象,也就是返回test.Demo.class并将其赋值给类型为Class的变量clazz

接着,调用clazz.newInstance()方法创建一个由该Class对象所表示的类的新实例。该类的实例化就像使用空参数列表的new表达式一样。换句话说,这里等同于new Demo(),并返回Demo的一个新实例。

执行这个Demo类将打印以下输出:

Hi!

newInstance 与传统的 new 的最大区别在于,newInstance 允许你在运行时实例化一个你在编写代码时并不知道的类,使得你的代码更加动态。

一个典型的例子是JDBC API,它会在运行时加载所需的确切驱动程序。EJB容器、Servlet容器也是其他很好的例子:它们使用动态运行时加载来加载和创建在运行时之前根本不知道的组件。

事实上,如果你想进一步了解,请查看 Ted Neward 的论文《理解 Class.forName()》,我刚才的段落是对他的描述进行了改编。

编辑(回答OP在评论中提出的问题):JDBC驱动程序的情况有些特殊。正如在《JDBC API入门指南》DriverManager 章节中所解释的那样:

(...)有两种方式来加载 Driver 类,从而自动向 DriverManager 注册:

  1. 通过调用方法 Class.forName。这将显式地加载驱动程序类。由于它不依赖于任何外部设置,因此这种方式是使用 DriverManager 框架的推荐方式。以下代码加载了类 acme.db.Driver

     Class.forName("acme.db.Driver");
    

如果acme.db.Driver被编写成在加载时会创建一个实例并以该实例作为参数调用DriverManager.registerDriver(这是应该做的),那么它将出现在DriverManager的驱动程序列表中,并可用于创建连接。

  1. (...)

在这两种情况下,新加载的Driver类有责任通过调用DriverManager.registerDriver来注册自己。如前所述,当类被加载时,这应该自动完成。

通常,在初始化期间,JDBC驱动程序使用静态初始化块来注册自己,例如:

package acme.db;

public class Driver {

    static {
        java.sql.DriverManager.registerDriver(new Driver());
    }
    
    ...
}

调用 Class.forName("acme.db.Driver") 会导致初始化 acme.db.Driver 类,从而执行静态初始化块。而且 Class.forName("acme.db.Driver") 确实会“创建”一个实例,但这只是 (好的) JDBC 驱动程序实现的结果。

顺便提一下,自从 Java 7 引入了 JDBC 4.0(作为默认包),以及 JDBC 4.0 驱动程序的新自动加载功能,这一切都已经不再需要了。请参见 Java SE 6 中 JDBC 4.0 的增强功能


但仍然在此网站上:http://java.sun.com/docs/books/tutorial/jdbc/basics/connecting.html - Johanna
2
在上述网站中写道:“调用Class.forName会自动创建驱动程序的实例并将其注册到DriverManager中,因此您不需要创建类的实例。如果您创建自己的实例,则会创建一个不必要的副本,但它不会造成任何伤害。”这意味着通过Class.forName,您将自动创建一个实例,如果您想创建其他实例,它将创建一个不必要的实例。因此,Class.forName()和Class.forName().newInstance()都将创建驱动程序的实例! - Johanna
11
JDBC驱动程序很“特殊”,它们是用静态初始化块编写的,其中创建了一个实例,并将其作为参数传递给DriverManager.registerDriver。在JDBC驱动程序上调用Class.forName会导致其初始化,从而执行静态块。请参阅http://www.java2s.com/Open-Source/Java-Document/Database-DBMS/db-derby-10.2/org/apache/derby/jdbc/ClientDriver.java.htm以获得示例。因此,这实际上是由于驱动程序内部的一个特殊情况。 - Pascal Thivent
1
我注意到在另一个答案中,强烈不建议使用Class.newInstance()。相反,推荐使用Class.getConstructor(),然后依次使用Constructor.newInstance()。这样可以避免掩盖可能的异常。 - Mr. Lance E Sloan
"newInstance 允许在运行时实例化一个你不知道的类" 让我很开心。谢谢。 - Code Enthusiastic
+1 给详细的回答。然而,使用 JDBC 4.0,甚至不需要使用 Class.forName... Java 的服务提供者机制 SPM 自动加载数据库驱动程序。只需要在类路径中指定驱动程序 jar 包即可。请参考此链接:https://dev59.com/wITba4cB1Zd3GeqP75K9 - Chiseled

37

Class.forName()方法返回一个类对象,该对象可用于反射。该对象拥有的方法由Java定义,而不是编写该类的程序员定义的。它们对于每个类都是相同的。在此对象上调用newInstance()方法将返回该类的实例(即调用Class.forName("ExampleClass").newInstance()等效于调用new ExampleClass()),您可以在该实例上调用该类定义的方法、访问可见字段等。


33

在JDBC的世界中,正常的做法(根据JDBC API)是使用Class#forName()来加载JDBC驱动程序。 JDBC驱动程序应该在静态块内向DriverManager注册自己:

package com.dbvendor.jdbc;

import java.sql.Driver;
import java.sql.DriverManager;

public class MyDriver implements Driver {

    static {
        DriverManager.registerDriver(new MyDriver());
    }

    public MyDriver() {
        //
    }

}

调用 Class#forName() 方法将会执行所有的 静态初始化器。这样,DriverManager 可以在 getConnection() 方法期间通过连接 URL 找到已注册驱动程序相关的驱动程序,大致代码如下:

public static Connection getConnection(String url) throws SQLException {
    for (Driver driver : registeredDrivers) {
        if (driver.acceptsURL(url)) {
            return driver.connect(url);
        }
    }
    throw new SQLException("No suitable driver");
}

但是也存在一些有问题的JDBC驱动程序,例如众所周知的org.gjt.mm.mysql.Driver,它错误地在构造函数中注册自身,而不是在静态块中进行:

package com.dbvendor.jdbc;

import java.sql.Driver;
import java.sql.DriverManager;

public class BadDriver implements Driver {

    public BadDriver() {
        DriverManager.registerDriver(this);
    }

}
唯一让它能动态工作的方法是在之后调用newInstance()!否则,你会面临首次看起来无法解释的"SQLException:no suitable driver"。再说一遍,这是JDBC驱动程序中的错误,而不是你自己的代码问题。现在,没有一个JDBC驱动程序应该包含这个错误。因此,你可以(也应该)省略newInstance()

20

1:如果你只对类的静态块感兴趣,那么仅加载该类即可,并且会执行静态块,所需的全部内容为:

Class.forName("Somthing");

2:如果您有兴趣加载该类,执行其静态块,并希望访问其非静态部分,则需要一个实例,然后您需要:

2:如果您有兴趣加载该类,执行其静态块,并希望访问其非静态部分,则需要一个实例,然后您需要:
Class.forName("Somthing").newInstance();

优秀的答案!清晰简洁! - Gaurav

7

Class.forName()获取一个Class引用,Class.forName().newInstance()尝试使用Class的无参构造函数返回一个新实例。


4

“Class.forName()”返回给定名称的类类型。"newInstance()"返回此类的实例。

您不能直接调用类型上的任何实例方法,但只能使用反射来操作该类。如果您想要使用该类的对象,则必须创建其实例(与调用“new MyClass()”相同)。

“Class.forName()”的示例:

Class myClass = Class.forName("test.MyClass");
System.out.println("Number of public methods: " + myClass.getMethods().length);

"Class.forName().newInstance()" 的示例

MyClass myClass = (MyClass) Class.forName("test.MyClass").newInstance();
System.out.println("String representation of MyClass instance: " + myClass.toString());

3

补充以上答案,当我们有一个静态代码(即代码块与实例无关)需要存在内存中时,我们可以返回类,这样我们将使用 Class.forname("someName")。如果没有静态代码,我们可以使用 Class.forname().newInstance("someName"),因为它将加载对象级别的代码块(非静态)到内存中。


2
无论您调用多少次Class.forName()方法,静态块只会执行一次,而不是多次:
package forNameMethodDemo;

public class MainClass {

    public static void main(String[] args) throws Exception {
        Class.forName("forNameMethodDemo.DemoClass");
        Class.forName("forNameMethodDemo.DemoClass");
        Class.forName("forNameMethodDemo.DemoClass");
        DemoClass demoClass = (DemoClass)Class.forName("forNameMethodDemo.DemoClass").newInstance();
    }
}


public class DemoClass {
    
    static {
        System.out.println("in Static block");
    }

    {
        System.out.println("in Instance block");
    }
}

输出将会是:
in Static block
in Instance block

这个静态块中的语句只会被打印一次,而不是三次。


0

Class.forName()-->forName()是Class类的静态方法,它返回用于反射的Class类对象,而不是用户类对象,因此您只能在其上调用Class类方法,如getMethods()getConstructors()等。

如果您只关心运行您的(运行时给定的)类的静态块,并仅获取有关方法、构造函数、修饰符等的信息,则可以使用使用Class.forName()获得的对象来完成。

但是,如果您想要访问或调用您的类方法(在运行时给定的类),则需要拥有其对象,因此Class类的newInstance方法为您执行此操作。它创建该类的新实例并将其返回给您。您只需要将其强制转换为您的类即可。

例如:假设Employee是您的类,则

Class a=Class.forName(args[0]); 

//args[0]=cmd line argument to give class at  runtime. 

Employee ob1=a.newInstance();

a.newInstance() 类似于使用 new Employee() 创建对象。

现在您可以访问所有类中可见的字段和方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接