Java之Class

简介

   我们通常使用类来描述对象, Class类就是对类的描述。就类似于概念用于描述、定义事物, 但是概念本身也需要被描述的。

  每个类被加载之后, 系统会为该类生成一个Class对象, 一旦获得某个类的Class对象, 程序就可以调用Class对象的方法来获得该类对象和该类的真实信息。简单来说, Class类的对象作用是运行时提供或获得某个对象对应的类的信息。 And对象的实例就是通过Class对象创建的。

  Class对象对应着java.lang.Class, Class对象在类加载时由虚拟机创建。

  类加载或类的初始化分为3个阶段:

  1. 加载.通过一个类的全限定名来获取定义的二进制字节流(通常我们从class文件获取), 将字节流所代表的静态存储结构转化为方法区的运行时数据, 然后在Java堆中生成一个代表此类的java.lang.Class对象。JDK 1.7的Class对象在堆中——Class实例在堆中还是方法区中?

  2. 连接. (1)验证: 字节码的来源并不一定从class文件获取, 因此需要验证确保Class字节流中的信息符合当前虚拟机的要求. (2) 准备: 为类变量分配内存, 并设置默认初始值. (3) 在需要时将二进制的符号引用替换成直接引用

  3. 初始化.在这个阶段, 才真正开始执行Java程序代码. 例如, 在定义静态变量时指定初始值、静态方法块。

  在类加载的过程中, 除了在加载阶段可以允许使用自定义的类加载器 And 加载的最后一个阶段初始化之外, 其余动作完全由虚拟机主导.
  通常情况下, 当一个类被使用时才会被加载到内存(懒加载), 并非一次就加载了所有的类。但是Java虚拟机规范允许系统预先加载某些类。

创建Class对象

创建Class对象的3种方式:

  • Class.forName(String className)——className指的是类的全限定名(包括包名)
  • 类名.class——比如Person.class
  • 对象.getClass()——getClass()是Objetc类的方法,所以每个对象都能调用。

第一、第二种都是可以根据类来获取Class对象。相比之下, 第二种方式有以下优势:
(1) 代码更安全。在编译阶段就可以检查需要访问的class是否存在。
(2) 性能更好。因为这种方式不需要调用方法。

在什么场景下使用第一种方式?
当需要根据一个字符串来获取Class对象时. 该方法可能抛出一个ClassNotFoundException异常。

第三种方式则需要先获取该类对象, 然后才能调用getClass()来获取Class对象的引用。

Class.forName

执行该方法时, 若第一次使用这个类, 那么会触发虚拟机对这个类进行加载。

class Gum {
    static {
        System.out.println("Loading Gum");
    }
}

class Cookie {

    static {
        System.out.println("Loading Cookie");
    }
}


public class ClassTest {

    public static void main(String[] args) {
        System.out.println("inide main");
        new Gum();
        System.out.println("after loading Gum");

        try {
            Class.forName("test.Lang_learn.Cookie");
            System.out.println("after loading Cookie");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 输出:
 * inide main
 * Loading Gum
 * after loading Gum
 * Loading Cookie
 * after loading Cookie
 **/

类名.class

使用这种方法创建Class对象时, 不会触发类的加载, 直到访问该类的静态成员(变量或方法)。

class Cookie {

    static int value = 10;

    static {
        System.out.println("Loading Cookie");
    }
}


public class ClassTest {
    public static void main(String[] args) {
        System.out.println("inide main");
        new Gum();
        System.out.println("after loading Gum");

        Class cl = Cookie.class;
        System.out.println("after invoke Cookie.class ");

        int v = Cookie.value;
    }
}

/**输出
 * inide main
 * Loading Gum
 * after loading Gum
 * after invoke Cookie.class
 * Loading Cookie
 **/

此外, 若一个类变量使用fianl修饰, 那么在访问时也不会触发类初始化。

class Cookie {

    static final int value = 10;

    static {
        System.out.println("Loading Cookie");
    }

    static void fun(){}
}


public class ClassTest {

    public static void main(String[] args) {
        System.out.println("inide main");

        Class cl = Cookie.class;
        System.out.println("after invoke Cookie.class ");
        int v = Cookie.value;
    }
}
/**
 * 输出:
 * inide main
 * after invoke Cookie.class
 **/

原因在于:使用final修饰的静态变量, 它的值在编译期就可以确定了, 在程序其它地方访问这个变量时, 其实并没有使用该变量, 而是相当于使用常量。

反之,若final修饰的静态变量在编译期不能确定, 则需要等到运行期才能确定该变量的值, 如果通过类来访问类变量, 则会导致类被初始化(加载)。

class Cookie {

    static final String string;

    static {
        string = "ss";
        System.out.println("Loading Cookie");
    }

    static void fun() {
    }
}

public class ClassTest {

    public static void main(String[] args) {
        System.out.println("inide main");

        Class cl = Cookie.class;
        System.out.println("after invoke Cookie.class ");

        String v = Cookie.string;
    }
}


/**
 * 输出:
 * inide main
 * after invoke Cookie.class
 * Loading Cookie
 **/

基本类型的class对象

想要获取基本类型的class对象, 直接使用type.class的形式, 例如int.class
基本类型的class和包装类型的TYPE属性是等价的:
int.class == Integer.TYPE
在这里插入图片描述
博客—Java中Class对象详解


小结

当触发类加载时会创建Class对象, 但是创建Class对象不一定会触发类加载(className.class的方式)。并且Class对象只会创建一次, 不管多少次创建Class对象, 它返回的都是堆中的同一地址。

public class ClassTest {

    public static void main(String[] args) {
        System.out.println("inside main");
        Class cl = ClassTest.class;
        Class ct = new ClassTest().getClass();

        System.out.println(cl == ct);// true
    }
}

  对于任意一个类, 都需要由加载它的类加载器和这个类本身一同确定其在Java虚拟机中的唯一性。这句话可以表达得更通俗一些: 比较这两个类时否"相等", 只有在这两个类由同一个类加载器加载的前提下才有意义, 否则, 即使这两个类时来源于同一个Class文件, 只要加载它们的类加载器不同, 这两个类就必定不相等。
  这里的"相等", 包括了类的Class对象的equals方法, isAssignableFrom()方法, isInstance()方法返回的结果, 也包括了使用instanceof关键字做对象所属关系判定等情况。——《深入理解Java虚拟机JVM高级特性与最佳实践(第2版)》

Class方法

Class类提供了很多方法获取Class对象对应类的信息, 其中有一些方法是重载的, 下面就列举一些方法.

方法作用
getConstructor获取Class对象对应类的构造器
getDeclaredMethod获取Class对象对应类的方法
getAnnotation获取Class对象对应类的注解
getDeclaredClasses()获取Class对象对应类的全部内部类
getInterfaces()获取Class对象对应类实现的接口
getName()获取Class对象对应类的名称
getPackage()获取Class对象对应类所在包名
isArray()判断Class对象对应类是否是数组类
getFields()获取Class对象对应类的public 修饰的成员变量
// 测试类与接口

interface Inter {
    public void interfFun();
}

@Deprecated
class Cookie implements Inter {
    private int value;

    public Cookie(int value) {
        this.value = value;
    }

    private class Inner {
    }

    public void fun() {
    }

    @Override
    public void interfFun() {
        System.out.println("Implements function");
    }
}
// 测试代码
public class ClassTest {

    public static void main(String[] args) {
        Class cl = Cookie.class;
        Constructor[] constructors = cl.getConstructors();
        System.out.println("构造器:" + Arrays.toString(constructors));

        Method[] methods = cl.getMethods();
        System.out.println("方法:" + Arrays.toString(methods));

        Annotation[] annotations = cl.getAnnotations();
        System.out.println("注解:" + Arrays.toString(annotations));

        Class[] classes = cl.getDeclaredClasses();
        System.out.println("内部类:" + Arrays.toString(classes));

        Class[] interfaces = cl.getInterfaces();
        System.out.println("实现接口:" + Arrays.toString(interfaces));

         Field[] fields = cl.getFields();
        System.out.println("成员变量" + Arrays.toString(fields));

        String className = cl.getName();
        System.out.println("类名:" + className);
    }
}

/**
 * 输出:
 * 构造器:[public test.Lang_learn.Cookie(int)]
 * 方法:[public void test.Lang_learn.Cookie.fun(), public void test.Lang_learn.Cookie.interfFun(), public final native java.lang.Class java.lang.Object.getClass(), public native int java.lang.Object.hashCode(), public boolean java.lang.Object.equals(java.lang.Object), public java.lang.String java.lang.Object.toString(), public final native void java.lang.Object.notify(), public final native void java.lang.Object.notifyAll(), public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException, public final void java.lang.Object.wait() throws java.lang.InterruptedException, public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException]
 * 注解:[@java.lang.Deprecated()]
 * 内部类:[class test.Lang_learn.Cookie$Inner]
 * 实现接口:[interface test.Lang_learn.Inter]
 * 成员变量[public int test.Lang_learn.Cookie.value]
 * 类名:test.Lang_learn.Cookie
 **/

getMethods()不仅把当前类定义的方法列出来, 把继承得到的方法也列出来了, 示例中列出的是Object的方法。

getName、getCanonicalName与getSimpleName的区别

getName: 获取类的全限定名, 可用于类的动态加载, Class.forName
getCanonicalName: 返回易于理解的表示形式。 普通类的表示形式与getName相同, 但是对于数组和接口来说有所区别。
getSimpleName: 只获取类名.

public class ClassTest {

    interface In{

    }

    public static void main(String[] args) {

        Integer[] integers = new Integer[2];

        System.out.println(ClassTest.class.getName()); // test.Lang_learn.ClassTest
        System.out.println(ClassTest.class.getSimpleName()); // ClassTest
        System.out.println(ClassTest.class.getCanonicalName()); // test.Lang_learn.ClassTest

        System.out.println(In.class.getName()); // test.Lang_learn.ClassTest$In
        System.out.println(In.class.getSimpleName()); // In
        System.out.println(In.class.getCanonicalName()); // test.Lang_learn.ClassTest.In

        System.out.println(integers.getClass().getName()); // [Ljava.lang.Integer;
        System.out.println(integers.getClass().getSimpleName()); // Integer[]
        System.out.println(integers.getClass().getCanonicalName()); // java.lang.Integer[]
    }
}

参考资料:
1.《深入理解Java虚拟机JVM高级特性与最佳实践(第2版)》
2.博客——Java中Class对象详解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值