理解java.lang.Class类

本文深入解析Java中的Class类,解释其如何表示运行时类和接口的抽象,以及如何获取Class对象实例。文中详细介绍了Class类的构造方法、常用方法如forName()、newInstance()等,并探讨了Class对象的应用。

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

Java Class类理解:

首先,Class是一个java类,跟Java API中定义的诸如Thread、Integer类、我们自己定义的类是一样,也继承了Object(Class是Object的直接子类)。总之,必须明确一点,它其实只是个类,只不过名字比较特殊。更进一步说,Class是一个java中的泛型类型。

 

对于我们自己定义的类,我们用类来抽象现实中的某些事物,比如我们定义一个名称为Car的类来抽象现实生活中的车,然后可以实例化这个类,用这些实例来表示我的车、你的车、黄的车、红的车等等。

好了,现在回到Class 类上来,这个类它抽象什么了?它的实例又表示什么呢?

在一个运行的程序中,会有许多类和接口存在。我们就用Class这个来来表示对这些类和接口的抽象,而Class类的每个实例则代表运行中的一个类。例如,运行的程序有A、B、C三个类,那么Class类就是对A、B、C三个类的抽象。所谓抽象,就是提取这些类的一些共同特征,比如说这些类都有类名,都有对应的hashcode,可以判断类型属于class、interface、enum还是annotation。这些可以封装成Class类的域,另外可以定义一些方法,比如获取某个方法、获取类型名等等。这样就封装了一个表示类型(type)的类。


需要注意的是,这个特殊的Class类没有公开的构造函数,那怎么获取Class类的实例呢?有几个途径。

1.      当Java虚拟机载入一个类的时候,它就会自动创建一个Class类的实例来表示这个类。例如,虚拟机载入Car这个来的时候,它就会创建一个Class类的实例。然后可以通过以下方法获得这个Class对象:

java.lang.Class classObj = ClassName.class;

2. 可以通过调用类加载器(ClassLoader)的defineClass()方法来得到一个实例。这个方法接受一个byte数组,载入这个byte数组否成的class类,同时实例化一个Class对象。

3. ClassName.class( )   ClassName.getClass( )

现在来分析一下Class类的源码(java.lang.Class):

public final

    class Class<T>implementsjava.io.Serializable,

                            java.lang.reflect.GenericDeclaration,

                             java.lang.reflect.Type,

                             java.lang.reflect.AnnotatedElement {…

 

ClassClass<T>,前一个Class表示这是一个类的声明,第二个Class是类的名称,<T>表示这是一个泛型类,带有参数T.同时,Class类实现了许多接口。

 

紧接着定义了几个静态变量:

private static final int ANNOTATION= 0x00002000;

private static final int ENUM      = 0x00004000;

private static final int SYNTHETIC = 0x00001000;

 

接着定义一个本地方法registerNatives(),并在静态块中调用:

private static native void registerNatives();

    static {

        registerNatives();

}

 

私有的构造函数:

private Class() {}

访问修饰符是private,程序员是无法直接调用这个构造函数,只能通过JVM来调用它,构造一个Class实例。

  public String toString() {

        return (isInterface() ?"interface " : (isPrimitive() ? "" : "class"))

            + getName();

}

这是Class对象实例的字符串表示方法,应该不陌生。那么,它返回什么东西呢?

>如果这个Class对象实例所表示的是一个Java类,则返回class full_classname.

例如java.lang.Math.java这个类,它所对应的Class实例的toString方法返回的就是class java.lang.Math

>如果是接口,将class改成interface。还有一种特殊情况,如果Class实例表示的是void类型,则发挥void。如果是基本类型,一样的返回基本类型的名称。

 

静态方法forName:

public static Class<?> forName(String className)

                throws ClassNotFoundException{

        returnforName0(className,true, ClassLoader.getCallerClassLoader());

}

根据给定的类名参数className,查找与className相对应的Class实例,然后加载、连接该实例对象,之后返回这个Class实例。其中例如以下代码段将输出:

class java.lang.Thread

public class ClassTest {

 

    /**

     * @param args

     */

    public static void main(String[] args) {

      

       try {

           System.out.println( Class.forName("java.lang.Thread") );

       } catch (ClassNotFoundException e) {

           e.printStackTrace();

           System.out.println("No ClassNamed java.lang.Thread");

       }

 

    }

 

}

 

forName方法重载:

    public static Class<?> forName(String name, boolean initialize,

                                   ClassLoaderloader)

        throws ClassNotFoundException

    {

        if (loader ==null) {

            SecurityManager sm = System.getSecurityManager();

            if (sm !=null) {

                ClassLoader ccl = ClassLoader.getCallerClassLoader();

                if (ccl !=null) {

                    sm.checkPermission(

                        SecurityConstants.GET_CLASSLOADER_PERMISSION);

                }

            }

        }

        returnforName0(name, initialize,loader);

    }

注意到这个forName重载方法中多了两个方法参数,其中initialize这个boolean类型指定是否要初始化对应的Class实例,loader指定加载Class实例的加载器。留意这个方法可能抛出的异常还是比较多的,比如连接失败、找不到对应的类等等。

 

forName0本地方法:

private static native Class<?> forName0(String name,boolean initialize,

                                            ClassLoaderloader)

        throws ClassNotFoundException;

这是一个本地方法,在前面的静态方法forName的两个版本中都调用了这个本地方法。

 

newInstance()方法:

public T newInstance()          // T是个泛型参数,是Class实例所表示的Java类

        throws InstantiationException,IllegalAccessException

    {

        if (System.getSecurityManager()!=null) {

            checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader());

        }

        return newInstance0();     //这是一个本地方法,也在Class类中定义

}

注意这是一个实例方法,必须由Class类的实例对象调用。例如,有一个代表java.lang.Thread类的Class实例对象objec1,也就是说,泛型参数T此时就是Thread,object1这个实例代表Thread这个类。好了,现在调用object1的newInstance方法,即object1.newInstance(),此时这个调用将返回一个Thread类的对象。简单验证:

public class ForName {

 

    /**

     * @paramargs

     * @throwsIllegalAccessException

     * @throwsInstantiationException

     */

    public static void main(String[] args) throws InstantiationException, IllegalAccessException {

       Class<?> c = null ;

       try {

           c = Class.forName("java.lang.Thread");

       } catch (ClassNotFoundException e) {

           e.printStackTrace();

       }

       Thread thread = (Thread) c.newInstance(); //类型转化一下

       System.out.println(thread.getId());

 

    }

 

}

在我机子中,上述代码输出 8 。即c.newInstance产生一个ID为8的新线程。

 

getClassLoader:

public ClassLoader getClassLoader() {

        ClassLoader cl = getClassLoader0();

        if (cl ==null)

            return null;   // Bootstrap

        SecurityManager sm = System.getSecurityManager();

        if (sm !=null) {

            ClassLoader ccl = ClassLoader.getCallerClassLoader();

            if (ccl !=null && ccl != cl&& !cl.isAncestor(ccl)) {

               sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);

            }

        }

        return cl;

    }

 

    //Package-private to allow ClassLoader access

    native ClassLoadergetClassLoader0();   // 本地方法

这个方法返回该Class对象代表的类的类加载器。如果类的加载器是Bootstrap,则返回null。下面的代码输出:The ClassLoader of Thread Class is Bootstrap

 

Class<?> classObj= Thread.class;

       ClassLoader loader = classObj.getClassLoader();

       if (loader ==null) {

           System.out.println("TheClassLoader of Thread Class is Bootstrap");

       } else {

           System.out.println(loader);

       }

 

获取父类方法:getSuperclass()

public native Class<? super T> getSuperclass();

这是一个本地方法,这里的逻辑有点饶,方法返回的是这个Class对象所代表的Java类的父类对应的的Class 对象。

例如: Thread.class.getSuperclass()将返回一个代表Thread类的Class对象,Thread.class.getSuperclass().toString()则输出这个Class对象的字符串表示:classjava.lang.Object。其实这里的关系无非就是说Thread的超类是Object。只是饶了Class对象这个弯子,至于这么绕有什么好处,还没有深刻体会。

 

(持续更新中)

<think>嗯,我现在遇到了Java程序中的NoClassDefFoundError和ClassNotFoundException问题,需要解决。让我先理清楚这两个异常的区别和可能的原因。根据用户提供的引用,比如引用[1]提到ClassNotFoundException通常是在编译时找不到,而NoClassDefFoundError可能是因为静态变量加载失败或者路径配置错误。引用[3]也提到,如果某个在编译时存在但在运行时缺失,就会引发NoClassDefFoundError。 首先,用户提到的问题可能出现在路径配置上。例如,引用[1]和引用[3]都指出,路径中缺少必要的JAR文件会导致这些问题。比如,Maven项目可能依赖未正确添加,或者构建时没有包含所有依赖。这时候需要检查项目的构建配置,比如pom.xml文件,确保所有依赖项都正确声明,并且执行了mvn clean install来重新构建。 另外,引用[2]中的例子显示,缺少javax.servlet.Filter,这可能是因为项目没有包含Servlet API的相关依赖。例如,在Spring Boot中,可能需要添加Tomcat的依赖或者指定正确的scope。这时候应该检查依赖管理,确认是否遗漏了必要的库。 还有,引用[4]和引用[5]提到的Sqoop问题,是因为路径中缺少commons-lang的旧版本jar包。解决方法是将正确的jar包添加到路径中,或者升级相关依赖。这说明有时候第三方库的版本兼容性问题也会导致找不到的错误,需要检查依赖版本是否匹配。 接下来,我需要考虑用户可能的解决步骤。首先,用户应该检查路径是否正确配置,确保所有需要的JAR文件都在路径中。例如,如果是Maven项目,可能需要运行mvn dependency:tree来查看依赖树,确认是否有冲突或缺失。然后,清理和重新构建项目,比如使用mvn clean install或IDE的Rebuild功能,确保编译后的文件包含所有需要的。 如果问题依旧,可能需要检查运行时环境。例如,应用服务器或容器的加载机制是否导致某些未被加载。这时候需要确认部署配置是否正确,比如WEB-INF/lib目录是否包含所有依赖的JAR。 另外,静态初始化失败也可能导致NoClassDefFoundError,如引用[1]所述。用户需要检查的静态代码块或静态变量是否有错误,导致初始化失败。这时候查看日志中的更早异常,找出根本原因。 还有,动态加载的情况,比如使用Class.forName()时拼写错误或名不正确,会导致ClassNotFoundException。用户需要检查代码中动态加载的地方,确保名正确,并且相关路径中可用。 最后,如果依赖冲突导致某个版本的被覆盖,可能需要排除冲突的依赖。例如,使用Maven的exclusion标签,或者调整依赖的顺序,确保正确的版本被加载。 总结来说,解决步骤包括:检查路径配置,重新构建项目,检查依赖项,查看静态初始化错误,验证动态加载的名,处理依赖冲突,以及检查部署环境配置。用户应该逐步排查这些可能性,结合具体的错误日志和引用中的案例,找到问题根源并进行修复。</think>### 解决 `java.lang.NoClassDefFoundError` 和 `java.lang.ClassNotFoundException` 的步骤 #### 1. **理解异常型** - **`ClassNotFoundException`**:发生在**编译时或运行时动态加载**失败,例如通过 `Class.forName()` 加载时未找到目标[^1][^3]。 - **`NoClassDefFoundError`**:发生在**运行时初始化失败**或存在但**路径缺失**(编译时存在,运行时丢失)[^1][^3]。 #### 2. **检查路径配置** - **依赖缺失**:确保项目依赖的所有 JAR 文件已正确添加到路径中。 - **Maven/Gradle 项目**:检查 `pom.xml` 或 `build.gradle` 是否声明了缺失的依赖(如 `javax.servlet-api`[^2] 或 `commons-lang`[^4])。 - **手动添加依赖**:若使用非构建工具,需将 JAR 文件手动放入 `WEB-INF/lib`(Web 项目)或 `-classpath` 参数中。 - **示例**: 若报错 `ClassNotFoundException: javax.servlet.Filter`,需在 Maven 中添加: ```xml <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> ``` #### 3. **清理并重新构建项目** - **IDE 操作**:执行 `Clean Project` 和 `Rebuild Project`,确保编译生成的文件包含所有依赖项。 - **Maven 命令**:运行 `mvn clean install` 重新下载依赖并构建项目[^1][^3]。 #### 4. **验证静态初始化错误** - **检查静态代码块或变量**:若在初始化时抛出异常(如静态变量加载失败),会导致 `NoClassDefFoundError`。查看日志中是否有更早的异常堆栈[^1]。 #### 5. **处理动态加载问题** - **检查 `Class.forName()` 参数**:确保动态加载的名拼写正确且存在。 ```java // 错误示例:名错误或未导入包 Class.forName("com.example.NonExistentClass"); ``` #### 6. **解决依赖冲突** - **依赖树分析**:运行 `mvn dependency:tree` 检查是否存在多个版本的同一依赖。 ```shell mvn dependency:tree -Dincludes=commons-lang ``` - **排除冲突依赖**:在 `pom.xml` 中排除旧版本: ```xml <dependency> <groupId>org.apache.sqoop</groupId> <artifactId>sqoop-core</artifactId> <version>1.4.7</version> <exclusions> <exclusion> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> </exclusion> </exclusions> </dependency> ``` #### 7. **检查部署环境** - **服务器路径配置**:确保应用服务器(如 Tomcat)的 `lib` 目录包含所需依赖。 - **示例**:Sqoop 报错 `ClassNotFoundException: org.apache.commons.lang.StringUtils`,需将 `commons-lang-2.6.jar` 添加到 Sqoop 的 `lib` 目录[^4]。 #### 8. **特殊场景处理** - **动态代理或反射**:若通过反射生成,需确保生成逻辑正确。 - **模块化项目(Java 9+)**:检查 `module-info.java` 是否导出了缺失的模块。 --- ### 常见问题案例 1. **Sqoop 导入数据报错** - **现象**:`ClassNotFoundException: Class bond_code not found`[^5] - **原因**:未指定数据库驱动或驱动未添加到路径。 - **解决**:下载 MySQL JDBC 驱动并放入 Sqoop 的 `lib` 目录。 2. **Spring Boot 启动报错** - **现象**:`ClassNotFoundException: javax.servlet.Filter`[^2] - **原因**:未包含 Servlet API 依赖。 - **解决**:添加 `spring-boot-starter-web` 依赖。 --- ### 总结流程图 ``` 检查异常型 → 验证路径 → 清理构建 → 检查静态代码 → 处理动态加载 → 排查依赖冲突 → 验证部署环境 ``` 相关问题
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值