精华内容
下载资源
问答
  • 深入理解Java类型信息(Class对象)与反射机制

    万次阅读 多人点赞 2017-05-01 23:19:19
    【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) ...深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解

    【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)
    http://blog.csdn.net/javazejian/article/details/70768369
    出自【zejian的博客】

    关联文章:

    深入理解Java类型信息(Class对象)与反射机制

    深入理解Java枚举类型(enum)

    深入理解Java注解类型(@Annotation)

    深入理解Java并发之synchronized实现原理

    深入理解Java内存模型(JMM)及volatile关键字

    深入理解Java类加载器(ClassLoader)

    本篇主要是深入对Java中的Class对象进行分析,这对后续深入理解反射技术非常重要,主要内容如下:

    深入理解Class对象

    RRTI的概念以及Class对象作用

    认识Class对象之前,先来了解一个概念,RTTI(Run-Time Type Identification)运行时类型识别,对于这个词一直是 C++ 中的概念,至于Java中出现RRTI的说法则是源于《Thinking in Java》一书,其作用是在运行时识别一个对象的类型和类的信息,这里分两种:传统的”RRTI”,它假定我们在编译期已知道了所有类型(在没有反射机制创建和使用类对象时,一般都是编译期已确定其类型,如new对象时该类必须已定义好),另外一种是反射机制,它允许我们在运行时发现和使用类型的信息。在Java中用来表示运行时类型信息的对应类就是Class类,Class类也是一个实实在在的类,存在于JDK的java.lang包中,其部分源码如下:

    public final class Class<T> implements java.io.Serializable,GenericDeclaration,Type, AnnotatedElement {
        private static final int ANNOTATION= 0x00002000;
        private static final int ENUM      = 0x00004000;
        private static final int SYNTHETIC = 0x00001000;
    
        private static native void registerNatives();
        static {
            registerNatives();
        }
    
        /*
         * Private constructor. Only the Java Virtual Machine creates Class objects.(私有构造,只能由JVM创建该类)
         * This constructor is not used and prevents the default constructor being
         * generated.
         */
        private Class(ClassLoader loader) {
            // Initialize final field for classLoader.  The initialization value of non-null
            // prevents future JIT optimizations from assuming this final field is null.
            classLoader = loader;
        }

    Class类被创建后的对象就是Class对象,注意,Class对象表示的是自己手动编写类的类型信息,比如创建一个Shapes类,那么,JVM就会创建一个Shapes对应Class类的Class对象,该Class对象保存了Shapes类相关的类型信息。实际上在Java中每个类都有一个Class对象,每当我们编写并且编译一个新创建的类就会产生一个对应Class对象并且这个Class对象会被保存在同名.class文件里(编译后的字节码文件保存的就是Class对象),那为什么需要这样一个Class对象呢?是这样的,当我们new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。需要特别注意的是,手动编写的每个class类,无论创建多少个实例对象,在JVM中都只有一个Class对象,即在内存中每个类有且只有一个相对应的Class对象,挺拗口,通过下图理解(内存中的简易现象图):

    到这我们也就可以得出以下几点信息:

    • Class类也是类的一种,与class关键字是不一样的。

    • 手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件),比如创建一个Shapes类,编译Shapes类后就会创建其包含Shapes类相关类型信息的Class对象,并保存在Shapes.class字节码文件中。

    • 每个通过关键字class标识的类,在内存中有且只有一个与之对应的Class对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象。

    • Class类只存私有构造函数,因此对应Class对象只能有JVM创建和加载

    • Class类的对象作用是运行时提供或获得某个对象的类型信息,这点对于反射技术很重要(关于反射稍后分析)。

    Class对象的加载及其获取方式

    Class对象的加载

    前面我们已提到过,Class对象是由JVM加载的,那么其加载时机是?实际上所有的类都是在对其第一次使用时动态加载到JVM中的,当程序创建第一个对类的静态成员引用时,就会加载这个被使用的类(实际上加载的就是这个类的字节码文件),注意,使用new操作符创建类的新实例对象也会被当作对类的静态成员的引用(构造函数也是类的静态方法),由此看来Java程序在它们开始运行之前并非被完全加载到内存的,其各个部分是按需加载,所以在使用该类时,类加载器首先会检查这个类的Class对象是否已被加载(类的实例对象创建时依据Class对象中类型信息完成的),如果还没有加载,默认的类加载器就会先根据类名查找.class文件(编译后Class对象被保存在同名的.class文件中),在这个类的字节码文件被加载时,它们必须接受相关验证,以确保其没有被破坏并且不包含不良Java代码(这是java的安全机制检测),完全没有问题后就会被动态加载到内存中,此时相当于Class对象也就被载入内存了(毕竟.class字节码文件保存的就是Class对象),同时也就可以被用来创建这个类的所有实例对象。下面通过一个简单例子来说明Class对象被加载的时机问题(例子引用自Thinking in Java):

    package com.zejian;
    
    class Candy {
      static {   System.out.println("Loading Candy"); }
    }
    
    class Gum {
      static {   System.out.println("Loading Gum"); }
    }
    
    class Cookie {
      static {   System.out.println("Loading Cookie"); }
    }
    
    public class SweetShop {
      public static void print(Object obj) {
        System.out.println(obj);
      }
      public static void main(String[] args) {  
        print("inside main");
        new Candy();
        print("After creating Candy");
        try {
          Class.forName("com.zejian.Gum");
        } catch(ClassNotFoundException e) {
          print("Couldn't find Gum");
        }
        print("After Class.forName(\"com.zejian.Gum\")");
        new Cookie();
        print("After creating Cookie");
      }
    }

    在上述代码中,每个类Candy、Gum、Cookie都存在一个static语句,这个语句会在类第一次被加载时执行,这个语句的作用就是告诉我们该类在什么时候被加载,执行结果:

    inside main
    Loading Candy
    After creating Candy
    Loading Gum
    After Class.forName("com.zejian.Gum")
    Loading Cookie
    After creating Cookie
    
    Process finished with exit code 0

    从结果来看,new一个Candy对象和Cookie对象,构造函数将被调用,属于静态方法的引用,Candy类的Class对象和Cookie的Class对象肯定会被加载,毕竟Candy实例对象的创建依据其Class对象。比较有意思的是

    Class.forName("com.zejian.Gum");

    其中forName方法是Class类的一个static成员方法,记住所有的Class对象都源于这个Class类,因此Class类中定义的方法将适应所有Class对象。这里通过forName方法,我们可以获取到Gum类对应的Class对象引用。从打印结果来看,调用forName方法将会导致Gum类被加载(前提是Gum类从来没有被加载过)。

    Class.forName方法

    通过上述的案例,我们也就知道Class.forName()方法的调用将会返回一个对应类的Class对象,因此如果我们想获取一个类的运行时类型信息并加以使用时,可以调用Class.forName()方法获取Class对象的引用,这样做的好处是无需通过持有该类的实例对象引用而去获取Class对象,如下的第2种方式是通过一个实例对象获取一个类的Class对象,其中的getClass()是从顶级类Object继承而来的,它将返回表示该对象的实际类型的Class对象引用。

    public static void main(String[] args) {
    
        try{
          //通过Class.forName获取Gum类的Class对象
          Class clazz=Class.forName("com.zejian.Gum");
          System.out.println("forName=clazz:"+clazz.getName());
        }catch (ClassNotFoundException e){
          e.printStackTrace();
        }
    
        //通过实例对象获取Gum的Class对象
        Gum gum = new Gum();
        Class clazz2=gum.getClass();
        System.out.println("new=clazz2:"+clazz2.getName());
    
      }

    注意调用forName方法时需要捕获一个名称为ClassNotFoundException的异常,因为forName方法在编译器是无法检测到其传递的字符串对应的类是否存在的,只能在程序运行时进行检查,如果不存在就会抛出ClassNotFoundException异常。

    Class字面常量

    在Java中存在另一种方式来生成Class对象的引用,它就是Class字面常量,如下:

    //字面常量的方式获取Class对象
    Class clazz = Gum.class;

    这种方式相对前面两种方法更加简单,更安全。因为它在编译器就会受到编译器的检查同时由于无需调用forName方法效率也会更高,因为通过字面量的方法获取Class对象的引用不会自动初始化该类。更加有趣的是字面常量的获取Class对象引用方式不仅可以应用于普通的类,也可以应用用接口,数组以及基本数据类型,这点在反射技术应用传递参数时很有帮助,关于反射技术稍后会分析,由于基本数据类型还有对应的基本包装类型,其包装类型有一个标准字段TYPE,而这个TYPE就是一个引用,指向基本数据类型的Class对象,其等价转换如下,一般情况下更倾向使用.class的形式,这样可以保持与普通类的形式统一。

    boolean.class = Boolean.TYPE;
    char.class = Character.TYPE;
    byte.class = Byte.TYPE;
    short.class = Short.TYPE;
    int.class = Integer.TYPE;
    long.class = Long.TYPE;
    float.class = Float.TYPE;
    double.class = Double.TYPE;
    void.class = Void.TYPE;

    前面提到过,使用字面常量的方式获取Class对象的引用不会触发类的初始化,这里我们可能需要简单了解一下类加载的过程,如下:

    • 加载:类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象

    • 链接:验证字节码的安全性和完整性,准备阶段正式为静态域分配存储空间,注意此时只是分配静态成员变量的存储空间,不包含实例成员变量,如果必要的话,解析这个类创建的对其他类的所有引用。

    • 初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量。

    由此可知,我们获取字面常量的Class引用时,触发的应该是加载阶段,因为在这个阶段Class对象已创建完成,获取其引用并不困难,而无需触发类的最后阶段初始化。下面通过小例子来验证这个过程:

    import java.util.*;
    
    class Initable {
      //编译期静态常量
      static final int staticFinal = 47;
      //非编期静态常量
      static final int staticFinal2 =
        ClassInitialization.rand.nextInt(1000);
      static {
        System.out.println("Initializing Initable");
      }
    }
    
    class Initable2 {
      //静态成员变量
      static int staticNonFinal = 147;
      static {
        System.out.println("Initializing Initable2");
      }
    }
    
    class Initable3 {
      //静态成员变量
      static int staticNonFinal = 74;
      static {
        System.out.println("Initializing Initable3");
      }
    }
    
    public class ClassInitialization {
      public static Random rand = new Random(47);
      public static void main(String[] args) throws Exception {
        //字面常量获取方式获取Class对象
        Class initable = Initable.class;
        System.out.println("After creating Initable ref");
        //不触发类初始化
        System.out.println(Initable.staticFinal);
        //会触发类初始化
        System.out.println(Initable.staticFinal2);
        //会触发类初始化
        System.out.println(Initable2.staticNonFinal);
        //forName方法获取Class对象
        Class initable3 = Class.forName("Initable3");
        System.out.println("After creating Initable3 ref");
        System.out.println(Initable3.staticNonFinal);
      }
    }

    执行结果:

    After creating Initable ref
    47
    Initializing Initable
    258
    Initializing Initable2
    147
    Initializing Initable3
    After creating Initable3 ref
    74

    从输出结果来看,可以发现,通过字面常量获取方式获取Initable类的Class对象并没有触发Initable类的初始化,这点也验证了前面的分析,同时发现调用Initable.staticFinal变量时也没有触发初始化,这是因为staticFinal属于编译期静态常量,在编译阶段通过常量传播优化的方式将Initable类的常量staticFinal存储到了一个称为NotInitialization类的常量池中,在以后对Initable类常量staticFinal的引用实际都转化为对NotInitialization类对自身常量池的引用,所以在编译期后,对编译期常量的引用都将在NotInitialization类的常量池获取,这也就是引用编译期静态常量不会触发Initable类初始化的重要原因。但在之后调用了Initable.staticFinal2变量后就触发了Initable类的初始化,注意staticFinal2虽然被static和final修饰,但其值在编译期并不能确定,因此staticFinal2并不是编译期常量,使用该变量必须先初始化Initable类。Initable2和Initable3类中都是静态成员变量并非编译期常量,引用都会触发初始化。至于forName方法获取Class对象,肯定会触发初始化,这点在前面已分析过。到这几种获取Class对象的方式也都分析完,ok~,到此这里可以得出小结论:

    • 获取Class对象引用的方式3种,通过继承自Object类的getClass方法,Class类的静态方法forName以及字面常量的方式”.class”。

    • 其中实例类的getClass方法和Class类的静态方法forName都将会触发类的初始化阶段,而字面常量获取Class对象的方式则不会触发初始化。

    • 初始化是类加载的最后一个阶段,也就是说完成这个阶段后类也就加载到内存中(Class对象在加载阶段已被创建),此时可以对类进行各种必要的操作了(如new对象,调用静态成员等),注意在这个阶段,才真正开始执行类中定义的Java程序代码或者字节码。

    关于类加载的初始化阶段,在虚拟机规范严格规定了有且只有5种场景必须对类进行初始化

    • 使用new关键字实例化对象时、读取或者设置一个类的静态字段(不包含编译期常量)以及调用静态方法的时候,必须触发类加载的初始化过程(类加载过程最终阶段)。

    • 使用反射包(java.lang.reflect)的方法对类进行反射调用时,如果类还没有被初始化,则需先进行初始化,这点对反射很重要。

    • 当初始化一个类的时候,如果其父类还没进行初始化则需先触发其父类的初始化。

    • 当Java虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机会先初始化这个主类

    • 当使用JDK 1.7 的动态语言支持时,如果一个java.lang.invoke.MethodHandle 实例最后解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应类没有初始化时,必须触发其初始化(这点看不懂就算了,这是1.7的新增的动态语言支持,其关键特征是它的类型检查的主体过程是在运行期而不是编译期进行的,这是一个比较大点的话题,这里暂且打住)

    理解泛化的Class对象引用

    由于Class的引用总数指向某个类的Class对象,利用Class对象可以创建实例类,这也就足以说明Class对象的引用指向的对象确切的类型。在Java SE5引入泛型后,使用我们可以利用泛型来表示Class对象更具体的类型,即使在运行期间会被擦除,但编译期足以确保我们使用正确的对象类型。如下:

    /**
     * Created by zejian on 2017/4/30.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class ClazzDemo {
    
        public static void main(String[] args){
            //没有泛型
            Class intClass = int.class;
    
            //带泛型的Class对象
            Class<Integer> integerClass = int.class;
    
            integerClass = Integer.class;
    
            //没有泛型的约束,可以随意赋值
            intClass= double.class;
    
            //编译期错误,无法编译通过
            //integerClass = double.class
        }
    }
    

    从代码可以看出,声明普通的Class对象,在编译器并不会检查Class对象的确切类型是否符合要求,如果存在错误只有在运行时才得以暴露出来。但是通过泛型声明指明类型的Class对象,编译器在编译期将对带泛型的类进行额外的类型检查,确保在编译期就能保证类型的正确性,实际上Integer.class就是一个Class<Integer>类的对象。面对下述语句,确实可能令人困惑,但该语句确实是无法编译通过的。

    //编译无法通过
    Class<Number> numberClass=Integer.class;

    我们或许会想Integer不就是Number的子类吗?然而事实并非这般简单,毕竟Integer的Class对象并非Number的Class对象的子类,前面提到过,所有的Class对象都只来源于Class类,看来事实确实如此。当然我们可以利用通配符“?”来解决问题:

    Class<?> intClass = int.class;
    intClass = double.class;

    这样的语句并没有什么问题,毕竟通配符指明所有类型都适用,那么为什么不直接使用Class还要使用Class<?>呢?这样做的好处是告诉编译器,我们是确实是采用任意类型的泛型,而非忘记使用泛型约束,因此Class<?>总是优于直接使用Class,至少前者在编译器检查时不会产生警告信息。当然我们还可以使用extends关键字告诉编译器接收某个类型的子类,如解决前面Number与Integer的问题:

    //编译通过!
    Class<? extends Number> clazz = Integer.class;
    //赋予其他类型
    clazz = double.class;
    clazz = Number.class;

    上述的代码是行得通的,extends关键字的作用是告诉编译器,只要是Number的子类都可以赋值。这点与前面直接使用Class<Number>是不一样的。实际上,应该时刻记住向Class引用添加泛型约束仅仅是为了提供编译期类型的检查从而避免将错误延续到运行时期。

    关于类型转换的问题

    在许多需要强制类型转换的场景,我们更多的做法是直接强制转换类型:

    package com.zejian;
    
    /**
     * Created by zejian on 2017/4/30.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class ClassCast {
    
     public void cast(){
    
         Animal animal= new Dog();
         //强制转换
         Dog dog = (Dog) animal;
     }
    }
    
    interface Animal{ }
    
    class Dog implements  Animal{ }

    之所可以强制转换,这得归功于RRTI,要知道在Java中,所有类型转换都是在运行时进行正确性检查的,利用RRTI进行判断类型是否正确从而确保强制转换的完成,如果类型转换失败,将会抛出类型转换异常。除了强制转换外,在Java SE5中新增一种使用Class对象进行类型转换的方式,如下:

    Animal animal= new Dog();
    //这两句等同于Dog dog = (Dog) animal;
    Class<Dog> dogType = Dog.class;
    Dog dog = dogType.cast(animal)

    利用Class对象的cast方法,其参数接收一个参数对象并将其转换为Class引用的类型。这种方式似乎比之前的强制转换更麻烦些,确实如此,而且当类型不能正确转换时,仍然会抛出ClassCastException异常。源码如下:

    public T cast(Object obj) {
        if (obj != null && !isInstance(obj))
             throw new ClassCastException(cannotCastMsg(obj));
         return (T) obj;
      }

    instanceof 关键字与isInstance方法

    关于instanceof 关键字,它返回一个boolean类型的值,意在告诉我们对象是不是某个特定的类型实例。如下,在强制转换前利用instanceof检测obj是不是Animal类型的实例对象,如果返回true再进行类型转换,这样可以避免抛出类型转换的异常(ClassCastException)

    public void cast2(Object obj){
        if(obj instanceof Animal){
              Animal animal= (Animal) obj;
          }
    }

    而isInstance方法则是Class类中的一个Native方法,也是用于判断对象类型的,看个简单例子:

    public void cast2(Object obj){
            //instanceof关键字
            if(obj instanceof Animal){
                Animal animal= (Animal) obj;
            }
    
            //isInstance方法
            if(Animal.class.isInstance(obj)){
                Animal animal= (Animal) obj;
            }
      }

    事实上instanceOf 与isInstance方法产生的结果是相同的。对于instanceOf是关键字只被用于对象引用变量,检查左边对象是不是右边类或接口的实例化。如果被测对象是null值,则测试结果总是false。一般形式:

    //判断这个对象是不是这种类型
    obj.instanceof(class)

    而isInstance方法则是Class类的Native方法,其中obj是被测试的对象或者变量,如果obj是调用这个方法的class或接口的实例,则返回true。如果被检测的对象是null或者基本类型,那么返回值是false;一般形式如下:

    //判断这个对象能不能被转化为这个类
    class.inInstance(obj)

    最后这里给出一个简单实例,验证isInstance方法与instanceof等价性:

    class A {}
    
    class B extends A {}
    
    public class C {
      static void test(Object x) {
        print("Testing x of type " + x.getClass());
        print("x instanceof A " + (x instanceof A));
        print("x instanceof B "+ (x instanceof B));
        print("A.isInstance(x) "+ A.class.isInstance(x));
        print("B.isInstance(x) " +
          B.class.isInstance(x));
        print("x.getClass() == A.class " +
          (x.getClass() == A.class));
        print("x.getClass() == B.class " +
          (x.getClass() == B.class));
        print("x.getClass().equals(A.class)) "+
          (x.getClass().equals(A.class)));
        print("x.getClass().equals(B.class)) " +
          (x.getClass().equals(B.class)));
      }
      public static void main(String[] args) {
        test(new A());
        test(new B());
      } 
    }

    执行结果:

    Testing x of type class com.zejian.A
    x instanceof A true
    x instanceof B false //父类不一定是子类的某个类型
    A.isInstance(x) true
    B.isInstance(x) false
    x.getClass() == A.class true
    x.getClass() == B.class false
    x.getClass().equals(A.class)) true
    x.getClass().equals(B.class)) false
    ---------------------------------------------
    Testing x of type class com.zejian.B
    x instanceof A true
    x instanceof B true
    A.isInstance(x) true
    B.isInstance(x) true
    x.getClass() == A.class false
    x.getClass() == B.class true
    x.getClass().equals(A.class)) false
    x.getClass().equals(B.class)) true

    到此关于Class对象相关的知识点都分析完了,下面将结合Class对象的知识点分析反射技术。

    理解反射技术

    反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。一直以来反射技术都是Java中的闪亮点,这也是目前大部分框架(如Spring/Mybatis等)得以实现的支柱。在Java中,Class类与java.lang.reflect类库一起对反射技术进行了全力的支持。在反射包中,我们常用的类主要有Constructor类表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象、Field表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private)、Method表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private),下面将对这几个重要类进行分别说明。

    Constructor类及其用法

    Constructor类存在于反射包(java.lang.reflect)中,反映的是Class 对象所表示的类的构造方法。获取Constructor对象是通过Class类中的方法获取的,Class类与Constructor相关的主要方法如下:

    方法返回值 方法名称 方法说明
    static Class<?> forName(String className) 返回与带有给定字符串名的类或接口相关联的 Class 对象。
    Constructor<T> getConstructor(Class<?>... parameterTypes) 返回指定参数类型、具有public访问权限的构造函数对象
    Constructor<?>[] getConstructors() 返回所有具有public访问权限的构造函数的Constructor对象数组
    Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 返回指定参数类型、所有声明的(包括private)构造函数对象
    Constructor<?>[] getDeclaredConstructor() 返回所有声明的(包括private)构造函数对象
    T newInstance() 创建此 Class 对象所表示的类的一个新实例。

    下面看一个简单例子来了解Constructor对象的使用:

    package reflect;
    
    import java.io.Serializable;
    import java.lang.reflect.Constructor;
    
    /**
     * Created by zejian on 2017/5/1.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class ReflectDemo implements Serializable{
        public static void main(String[] args) throws Exception {
    
            Class<?> clazz = null;
    
            //获取Class对象的引用
            clazz = Class.forName("reflect.User");
    
            //第一种方法,实例化默认构造方法,User必须无参构造函数,否则将抛异常
            User user = (User) clazz.newInstance();
            user.setAge(20);
            user.setName("Rollen");
            System.out.println(user);
    
            System.out.println("--------------------------------------------");
    
            //获取带String参数的public构造函数
            Constructor cs1 =clazz.getConstructor(String.class);
            //创建User
            User user1= (User) cs1.newInstance("xiaolong");
            user1.setAge(22);
            System.out.println("user1:"+user1.toString());
    
            System.out.println("--------------------------------------------");
    
            //取得指定带int和String参数构造函数,该方法是私有构造private
            Constructor cs2=clazz.getDeclaredConstructor(int.class,String.class);
            //由于是private必须设置可访问
            cs2.setAccessible(true);
            //创建user对象
            User user2= (User) cs2.newInstance(25,"lidakang");
            System.out.println("user2:"+user2.toString());
    
            System.out.println("--------------------------------------------");
    
            //获取所有构造包含private
            Constructor<?> cons[] = clazz.getDeclaredConstructors();
            // 查看每个构造方法需要的参数
            for (int i = 0; i < cons.length; i++) {
                //获取构造函数参数类型
                Class<?> clazzs[] = cons[i].getParameterTypes();
                System.out.println("构造函数["+i+"]:"+cons[i].toString() );
                System.out.print("参数类型["+i+"]:(");
                for (int j = 0; j < clazzs.length; j++) {
                    if (j == clazzs.length - 1)
                        System.out.print(clazzs[j].getName());
                    else
                        System.out.print(clazzs[j].getName() + ",");
                }
                System.out.println(")");
            }
        }
    }
    
    
    class User {
        private int age;
        private String name;
        public User() {
            super();
        }
        public User(String name) {
            super();
            this.name = name;
        }
    
        /**
         * 私有构造
         * @param age
         * @param name
         */
        private User(int age, String name) {
            super();
            this.age = age;
            this.name = name;
        }
    
      //..........省略set 和 get方法
    }

    运行结果:

    User [age=20, name=Rollen]
    --------------------------------------------
    user1:User [age=22, name=xiaolong]
    --------------------------------------------
    user2:User [age=25, name=lidakang]
    --------------------------------------------
    构造函数[0]:private reflect.User(int,java.lang.String)
    参数类型[0]:(int,java.lang.String)
    构造函数[1]:public reflect.User(java.lang.String)
    参数类型[1]:(java.lang.String)
    构造函数[2]:public reflect.User()
    参数类型[2]:()

    关于Constructor类本身一些常用方法如下(仅部分,其他可查API),

    方法返回值 方法名称 方法说明
    Class<T> getDeclaringClass() 返回 Class 对象,该对象表示声明由此 Constructor 对象表示的构造方法的类,其实就是返回真实类型(不包含参数)
    Type[] getGenericParameterTypes() 按照声明顺序返回一组 Type 对象,返回的就是 Constructor对象构造函数的形参类型。
    String getName() 以字符串形式返回此构造方法的名称。
    Class<?>[] getParameterTypes() 按照声明顺序返回一组 Class 对象,即返回Constructor 对象所表示构造方法的形参类型
    T newInstance(Object... initargs) 使用此 Constructor对象表示的构造函数来创建新实例
    String toGenericString() 返回描述此 Constructor 的字符串,其中包括类型参数。

    代码演示如下:

    Constructor cs3=clazz.getDeclaredConstructor(int.class,String.class);
    
    System.out.println("-----getDeclaringClass-----");
    Class uclazz=cs3.getDeclaringClass();
    //Constructor对象表示的构造方法的类
    System.out.println("构造方法的类:"+uclazz.getName());
    
    System.out.println("-----getGenericParameterTypes-----");
    //对象表示此 Constructor 对象所表示的方法的形参类型
    Type[] tps=cs3.getGenericParameterTypes();
    for (Type tp:tps) {
        System.out.println("参数名称tp:"+tp);
    }
    System.out.println("-----getParameterTypes-----");
    //获取构造函数参数类型
    Class<?> clazzs[] = cs3.getParameterTypes();
    for (Class claz:clazzs) {
        System.out.println("参数名称:"+claz.getName());
    }
    System.out.println("-----getName-----");
    //以字符串形式返回此构造方法的名称
    System.out.println("getName:"+cs3.getName());
    
    System.out.println("-----getoGenericString-----");
    //返回描述此 Constructor 的字符串,其中包括类型参数。
    System.out.println("getoGenericString():"+cs3.toGenericString());
    /**
     输出结果:
     -----getDeclaringClass-----
     构造方法的类:reflect.User
     -----getGenericParameterTypes-----
     参数名称tp:int
     参数名称tp:class java.lang.String
     -----getParameterTypes-----
     参数名称:int
     参数名称:java.lang.String
     -----getName-----
     getName:reflect.User
     -----getoGenericString-----
     getoGenericString():private reflect.User(int,java.lang.String)
     */

    其中关于Type类型这里简单说明一下,Type 是 Java 编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。getGenericParameterTypesgetParameterTypes 都是获取构成函数的参数类型,前者返回的是Type类型,后者返回的是Class类型,由于Type顶级接口,Class也实现了该接口,因此Class类是Type的子类,Type 表示的全部类型而每个Class对象表示一个具体类型的实例,如String.class仅代表String类型。由此看来Type与 Class 表示类型几乎是相同的,只不过 Type表示的范围比Class要广得多而已。当然Type还有其他子类,如:

    • TypeVariable:表示类型参数,可以有上界,比如:T extends Number

    • ParameterizedType:表示参数化的类型,有原始类型和具体的类型参数,比如:List<String>

    • WildcardType:表示通配符类型,比如:?, ? extends Number, ? super Integer

    通过以上的分析,对于Constructor类已有比较清晰的理解,利用好Class类和Constructor类,我们可以在运行时动态创建任意对象,从而突破必须在编译期知道确切类型的障碍。

    Field类及其用法

    Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。同样的道理,我们可以通过Class类的提供的方法来获取代表字段信息的Field对象,Class类与Field对象相关方法如下:

    方法返回值 方法名称 方法说明
    Field getDeclaredField(String name) 获取指定name名称的(包含private修饰的)字段,不包括继承的字段
    Field[] getDeclaredField() 获取Class对象所表示的类或接口的所有(包含private修饰的)字段,不包括继承的字段
    Field getField(String name) 获取指定name名称、具有public修饰的字段,包含继承字段
    Field[] getField() 获取修饰符为public的字段,包含继承字段

     
    下面的代码演示了上述方法的使用过程

    /**
     * Created by zejian on 2017/5/1.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class ReflectField {
    
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
            Class<?> clazz = Class.forName("reflect.Student");
            //获取指定字段名称的Field类,注意字段修饰符必须为public而且存在该字段,
            // 否则抛NoSuchFieldException
            Field field = clazz.getField("age");
            System.out.println("field:"+field);
    
            //获取所有修饰符为public的字段,包含父类字段,注意修饰符为public才会获取
            Field fields[] = clazz.getFields();
            for (Field f:fields) {
                System.out.println("f:"+f.getDeclaringClass());
            }
    
            System.out.println("================getDeclaredFields====================");
            //获取当前类所字段(包含private字段),注意不包含父类的字段
            Field fields2[] = clazz.getDeclaredFields();
            for (Field f:fields2) {
                System.out.println("f2:"+f.getDeclaringClass());
            }
            //获取指定字段名称的Field类,可以是任意修饰符的自动,注意不包含父类的字段
            Field field2 = clazz.getDeclaredField("desc");
            System.out.println("field2:"+field2);
        }
        /**
          输出结果: 
         field:public int reflect.Person.age
         f:public java.lang.String reflect.Student.desc
         f:public int reflect.Person.age
         f:public java.lang.String reflect.Person.name
    
         ================getDeclaredFields====================
         f2:public java.lang.String reflect.Student.desc
         f2:private int reflect.Student.score
         field2:public java.lang.String reflect.Student.desc
         */
    }
    
    class Person{
        public int age;
        public String name;
        //省略set和get方法
    }
    
    class Student extends Person{
        public String desc;
        private int score;
        //省略set和get方法
    }

    上述方法需要注意的是,如果我们不期望获取其父类的字段,则需使用Class类的getDeclaredField/getDeclaredFields方法来获取字段即可,倘若需要连带获取到父类的字段,那么请使用Class类的getField/getFields,但是也只能获取到public修饰的的字段,无法获取父类的私有字段。下面将通过Field类本身的方法对指定类属性赋值,代码演示如下:

    //获取Class对象引用
    Class<?> clazz = Class.forName("reflect.Student");
    
    Student st= (Student) clazz.newInstance();
    //获取父类public字段并赋值
    Field ageField = clazz.getField("age");
    ageField.set(st,18);
    Field nameField = clazz.getField("name");
    nameField.set(st,"Lily");
    
    //只获取当前类的字段,不获取父类的字段
    Field descField = clazz.getDeclaredField("desc");
    descField.set(st,"I am student");
    Field scoreField = clazz.getDeclaredField("score");
    //设置可访问,score是private的
    scoreField.setAccessible(true);
    scoreField.set(st,88);
    System.out.println(st.toString());
    
    //输出结果:Student{age=18, name='Lily ,desc='I am student', score=88} 
    
    //获取字段值
    System.out.println(scoreField.get(st));
    // 88

    其中的set(Object obj, Object value)方法是Field类本身的方法,用于设置字段的值,而get(Object obj)则是获取字段的值,当然关于Field类还有其他常用的方法如下:

    方法返回值 方法名称 方法说明
    void set(Object obj, Object value) 将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
    Object get(Object obj) 返回指定对象上此 Field 表示的字段的值
    Class<?> getType() 返回一个 Class 对象,它标识了此Field 对象所表示字段的声明类型。
    boolean isEnumConstant() 如果此字段表示枚举类型的元素则返回 true;否则返回 false
    String toGenericString() 返回一个描述此 Field(包括其一般类型)的字符串
    String getName() 返回此 Field 对象表示的字段的名称
    Class<?> getDeclaringClass() 返回表示类或接口的 Class 对象,该类或接口声明由此 Field 对象表示的字段
    void setAccessible(boolean flag) 将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性

     
    上述方法可能是较为常用的,事实上在设置值的方法上,Field类还提供了专门针对基本数据类型的方法,如setInt()/getInt()、setBoolean()/getBoolean、setChar()/getChar()等等方法,这里就不全部列出了,需要时查API文档即可。需要特别注意的是被final关键字修饰的Field字段是安全的,在运行时可以接收任何修改,但最终其实际值是不会发生改变的。

    Method类及其用法

    Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法可能是类方法或实例方法(包括抽象方法)。下面是Class类获取Method对象相关的方法:

    方法返回值 方法名称 方法说明
    Method getDeclaredMethod(String name, Class<?>... parameterTypes) 返回一个指定参数的Method对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。
    Method[] getDeclaredMethod() 返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
    Method getMethod(String name, Class<?>... parameterTypes) 返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
    Method[] getMethods() 返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。

    同样通过案例演示上述方法:

    import java.lang.reflect.Method;
    
    /**
     * Created by zejian on 2017/5/1.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class ReflectMethod  {
    
    
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
    
            Class clazz = Class.forName("reflect.Circle");
    
            //根据参数获取public的Method,包含继承自父类的方法
            Method method = clazz.getMethod("draw",int.class,String.class);
    
            System.out.println("method:"+method);
    
            //获取所有public的方法:
            Method[] methods =clazz.getMethods();
            for (Method m:methods){
                System.out.println("m::"+m);
            }
    
            System.out.println("=========================================");
    
            //获取当前类的方法包含private,该方法无法获取继承自父类的method
            Method method1 = clazz.getDeclaredMethod("drawCircle");
            System.out.println("method1::"+method1);
            //获取当前类的所有方法包含private,该方法无法获取继承自父类的method
            Method[] methods1=clazz.getDeclaredMethods();
            for (Method m:methods1){
                System.out.println("m1::"+m);
            }
        }
    
    /**
         输出结果:
         method:public void reflect.Shape.draw(int,java.lang.String)
    
         m::public int reflect.Circle.getAllCount()
         m::public void reflect.Shape.draw()
         m::public void reflect.Shape.draw(int,java.lang.String)
         m::public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
         m::public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
         m::public final void java.lang.Object.wait() throws java.lang.InterruptedException
         m::public boolean java.lang.Object.equals(java.lang.Object)
         m::public java.lang.String java.lang.Object.toString()
         m::public native int java.lang.Object.hashCode()
         m::public final native java.lang.Class java.lang.Object.getClass()
         m::public final native void java.lang.Object.notify()
         m::public final native void java.lang.Object.notifyAll()
    
         =========================================
         method1::private void reflect.Circle.drawCircle()
    
         m1::public int reflect.Circle.getAllCount()
         m1::private void reflect.Circle.drawCircle()
         */
    }
    
    class Shape {
        public void draw(){
            System.out.println("draw");
        }
    
        public void draw(int count , String name){
            System.out.println("draw "+ name +",count="+count);
        }
    
    }
    class Circle extends Shape{
    
        private void drawCircle(){
            System.out.println("drawCircle");
        }
        public int getAllCount(){
            return 100;
        }
    }

    在通过getMethods方法获取Method对象时,会把父类的方法也获取到,如上的输出结果,把Object类的方法都打印出来了。而getDeclaredMethod/getDeclaredMethods方法都只能获取当前类的方法。我们在使用时根据情况选择即可。下面将演示通过Method对象调用指定类的方法:

    Class clazz = Class.forName("reflect.Circle");
    //创建对象
    Circle circle = (Circle) clazz.newInstance();
    
    //获取指定参数的方法对象Method
    Method method = clazz.getMethod("draw",int.class,String.class);
    
    //通过Method对象的invoke(Object obj,Object... args)方法调用
    method.invoke(circle,15,"圈圈");
    
    //对私有无参方法的操作
    Method method1 = clazz.getDeclaredMethod("drawCircle");
    //修改私有方法的访问标识
    method1.setAccessible(true);
    method1.invoke(circle);
    
    //对有返回值得方法操作
    Method method2 =clazz.getDeclaredMethod("getAllCount");
    Integer count = (Integer) method2.invoke(circle);
    System.out.println("count:"+count);
    
    /**
        输出结果:
        draw 圈圈,count=15
        drawCircle
        count:100
    */

    在上述代码中调用方法,使用了Method类的invoke(Object obj,Object... args)第一个参数代表调用的对象,第二个参数传递的调用方法的参数。这样就完成了类方法的动态调用。

    方法返回值 方法名称 方法说明
    Object invoke(Object obj, Object... args) 对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。
    Class<?> getReturnType() 返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型,即方法的返回类型
    Type getGenericReturnType() 返回表示由此 Method 对象所表示方法的正式返回类型的 Type 对象,也是方法的返回类型。
    Class<?>[] getParameterTypes() 按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。即返回方法的参数类型组成的数组
    Type[] getGenericParameterTypes() 按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的,也是返回方法的参数类型
    String getName() 以 String 形式返回此 Method 对象表示的方法名称,即返回方法的名称
    boolean isVarArgs() 判断方法是否带可变参数,如果将此方法声明为带有可变数量的参数,则返回 true;否则,返回 false。
    String toGenericString() 返回描述此 Method 的字符串,包括类型参数。

     
    getReturnType方法/getGenericReturnType方法都是获取Method对象表示的方法的返回类型,只不过前者返回的Class类型后者返回的Type(前面已分析过),Type就是一个接口而已,在Java8中新增一个默认的方法实现,返回的就参数类型信息

    public interface Type {
        //1.8新增
        default String getTypeName() {
            return toString();
        }
    }

    而getParameterTypes/getGenericParameterTypes也是同样的道理,都是获取Method对象所表示的方法的参数类型,其他方法与前面的Field和Constructor是类似的。

    反射包中的Array类

    在Java的java.lang.reflect包中存在着一个可以动态操作数组的类,Array,它提供了动态创建和访问 Java 数组的方法。Array 允许在执行 get 或 set 操作进行取值和赋值。在Class类中与数组关联的方法是:

    方法返回值 方法名称 方法说明
    Class<?> getComponentType() 返回表示数组元素类型的 Class,即数组的类型
    boolean isArray() 判定此 Class 对象是否表示一个数组类。

    java.lang.reflect.Array中的常用静态方法如下:

    方法返回值 方法名称 方法说明
    static Object set(Object array, int index) 返回指定数组对象中索引组件的值。
    static int getLength(Object array) 以 int 形式返回指定数组对象的长度
    static object newInstance(Class<?> componentType, int... dimensions) 创建一个具有指定类型和维度的新数组。
    static Object newInstance(Class<?> componentType, int length) 创建一个具有指定的组件类型和长度的新数组。
    static void set(Object array, int index, Object value) 将指定数组对象中索引组件的值设置为指定的新值。

    下面通过一个简单例子来演示这些方法

    package reflect;
    
    import java.lang.reflect.Array;
    
    /**
     * Created by zejian on 2017/5/1.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class ReflectArray {
    
        public static void main(String[] args) throws ClassNotFoundException {
            int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            //获取数组类型的Class 即int.class
            Class<?> clazz = array.getClass().getComponentType();
            //创建一个具有指定的组件类型和长度的新数组。
            //第一个参数:数组的类型,第二个参数:数组的长度
            Object newArr = Array.newInstance(clazz, 15);
            //获取原数组的长度
            int co = Array.getLength(array);
            //赋值原数组到新数组
            System.arraycopy(array, 0, newArr, 0, co);
            for (int i:(int[]) newArr) {
                System.out.print(i+",");
            }
    
            //创建了一个长度为10 的字符串数组,
            //接着把索引位置为6 的元素设为"hello world!",然后再读取索引位置为6 的元素的值
            Class clazz2 = Class.forName("java.lang.String");
    
            //创建一个长度为10的字符串数组,在Java中数组也可以作为Object对象
            Object array2 = Array.newInstance(clazz2, 10);
    
            //把字符串数组对象的索引位置为6的元素设置为"hello"
            Array.set(array2, 6, "hello world!");
    
            //获得字符串数组对象的索引位置为5的元素的值
            String str = (String)Array.get(array2, 6);
            System.out.println();
            System.out.println(str);//hello
        }
        /**
         输出结果:
         1,2,3,4,5,6,7,8,9,0,0,0,0,0,0,
         hello world!
         */
    }

    通过上述代码演示,确实可以利用Array类和反射相结合动态创建数组,也可以在运行时动态获取和设置数组中元素的值,其实除了上的set/get外Array还专门为8种基本数据类型提供特有的方法,如setInt/getInt、setBoolean/getBoolean,其他依次类推,需要使用是可以查看API文档即可。除了上述动态修改数组长度或者动态创建数组或动态获取值或设置值外,可以利用泛型动态创建泛型数组如下:

    /**
      * 接收一个泛型数组,然后创建一个长度与接收的数组长度一样的泛型数组,
      * 并把接收的数组的元素复制到新创建的数组中,
      * 最后找出新数组中的最小元素,并打印出来
      * @param a
      * @param <T>
      */
     public  <T extends Comparable<T>> void min(T[] a) {
         //通过反射创建相同类型的数组
         T[] b = (T[]) Array.newInstance(a.getClass().getComponentType(), a.length);
         for (int i = 0; i < a.length; i++) {
             b[i] = a[i];
         }
         T min = null;
         boolean flag = true;
         for (int i = 0; i < b.length; i++) {
             if (flag) {
                 min = b[i];
                 flag = false;
             }
             if (b[i].compareTo(min) < 0) {
                 min = b[i];
             }
         }
         System.out.println(min);
     }

    毕竟我们无法直接创建泛型数组,有了Array的动态创建数组的方式这个问题也就迎刃而解了。

    //无效语句,编译不通
    T[] a = new T[];

    ok~,到这反射中几个重要并且常用的类我们都基本介绍完了,但更重要是,我们应该认识到反射机制并没有什么神奇之处。当通过反射与一个未知类型的对象打交道时,JVM只会简单地检查这个对象,判断该对象属于那种类型,同时也应该知道,在使用反射机制创建对象前,必须确保已加载了这个类的Class对象,当然这点完全不必由我们操作,毕竟只能JVM加载,但必须确保该类的”.class”文件已存在并且JVM能够正确找到。关于Class类的方法在前面我们只是分析了主要的一些方法,其实Class类的API方法挺多的,建议查看一下API文档,浏览一遍,有个印象也是不错的选择,这里仅列出前面没有介绍过又可能用到的API:

     /** 
      *    修饰符、父类、实现的接口、注解相关 
      */
    
    //获取修饰符,返回值可通过Modifier类进行解读
    public native int getModifiers();
    //获取父类,如果为Object,父类为null
    public native Class<? super T> getSuperclass();
    //对于类,为自己声明实现的所有接口,对于接口,为直接扩展的接口,不包括通过父类间接继承来的
    public native Class<?>[] getInterfaces();
    //自己声明的注解
    public Annotation[] getDeclaredAnnotations();
    //所有的注解,包括继承得到的
    public Annotation[] getAnnotations();
    //获取或检查指定类型的注解,包括继承得到的
    public <A extends Annotation> A getAnnotation(Class<A> annotationClass);
    public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);
    
    /** 
      *   内部类相关
      */
    //获取所有的public的内部类和接口,包括从父类继承得到的
    public Class<?>[] getClasses();
    //获取自己声明的所有的内部类和接口
    public Class<?>[] getDeclaredClasses();
    //如果当前Class为内部类,获取声明该类的最外部的Class对象
    public Class<?> getDeclaringClass();
    //如果当前Class为内部类,获取直接包含该类的类
    public Class<?> getEnclosingClass();
    //如果当前Class为本地类或匿名内部类,返回包含它的方法
    public Method getEnclosingMethod();
    
    /** 
      *    Class对象类型判断相关
      */
    //是否是数组
    public native boolean isArray();  
    //是否是基本类型
    public native boolean isPrimitive();
    //是否是接口
    public native boolean isInterface();
    //是否是枚举
    public boolean isEnum();
    //是否是注解
    public boolean isAnnotation();
    //是否是匿名内部类
    public boolean isAnonymousClass();
    //是否是成员类
    public boolean isMemberClass();
    //是否是本地类
    public boolean isLocalClass(); 

    ok~,本篇到此完结。


    展开全文
  • Java中Class对象详解

    万次阅读 多人点赞 2018-06-01 14:50:40
    从某种意义上来说,java有两种对象:实例对象和Class对象。每个类的运行时的类型信息就是用Class对象表示的。它包含了与类有关的信息。其实我们的实例对象就通过Class对象来创建的。Java使用Class对象执行其RT...

    https://blog.csdn.net/mcryeasy/article/details/52344729

    待优化整理 总结

    Class类简介

      在java世界里,一切皆对象。从某种意义上来说,java有两种对象:实例对象和Class对象。每个类的运行时的类型信息就是用Class对象表示的。它包含了与类有关的信息。其实我们的实例对象就通过Class对象来创建的。Java使用Class对象执行其RTTI(运行时类型识别,Run-Time Type Identification),多态是基于RTTI实现的。

      每一个类都有一个Class对象,每当编译一个新类就产生一个Class对象,基本类型 (boolean, byte, char, short, int, long, float, and double)有Class对象,数组有Class对象,就连关键字void也有Class对象(void.class)。Class对象对应着java.lang.Class类,如果说类是对象抽象和集合的话,那么Class类就是对类的抽象和集合。

      Class类没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。一个类被加载到内存并供我们使用需要经历如下三个阶段:

    1. 加载,这是由类加载器(ClassLoader)执行的。通过一个类的全限定名来获取其定义的二进制字节流(Class字节码),将这个字节流所代表的静态存储结构转化为方法去的运行时数据接口,根据字节码在java堆中生成一个代表这个类的java.lang.Class对象。

    2. 链接。在链接阶段将验证Class文件中的字节流包含的信息是否符合当前虚拟机的要求,为静态域分配存储空间并设置类变量的初始值(默认的零值),并且如果必需的话,将常量池中的符号引用转化为直接引用。

    3. 初始化。到了此阶段,才真正开始执行类中定义的java程序代码。用于执行该类的静态初始器和静态初始块,如果该类有父类的话,则优先对其父类进行初始化。

      
      所有的类都是在对其第一次使用时,动态加载到JVM中的(懒加载)。当程序创建第一个对类的静态成员的引用时,就会加载这个类。使用new创建类对象的时候也会被当作对类的静态成员的引用。因此java程序程序在它开始运行之前并非被完全加载,其各个类都是在必需时才加载的。这一点与许多传统语言都不同。动态加载使能的行为,在诸如C++这样的静态加载语言中是很难或者根本不可能复制的。

      在类加载阶段,类加载器首先检查这个类的Class对象是否已经被加载。如果尚未加载,默认的类加载器就会根据类的全限定名查找.class文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良java代码。一旦某个类的Class对象被载入内存,我们就可以它来创建这个类的所有对象。


    如何获得Class对象

    有三种获得Class对象的方式:

    1. Class.forName(“类的全限定名”)
    2. 实例对象.getClass()
    3. 类名.class (类字面常量)

    Class.forName 和getClass()

    我们先看看如下的例子:

    package com.cry;
    class Dog {
        static {
            System.out.println("Loading Dog");
        }
    }
    class Cat {
        static {
            System.out.println("Loading Cat");
        }
    }
    public class Test {
        public static void main(String[] args){
            System.out.println("inside main");
            new Dog();
            System.out.println("after creating Dog");
            try {
                Class cat=Class.forName("com.cry.Cat");
            } catch (ClassNotFoundException e) {
                System.out.println("Couldn't find Cat");
            }
            System.out.println("finish main");
        }
    }
    /* Output:
        inside main
        Loading Dog
        after creating Dog
        Loading Cat
        finish main
     */
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

      上面的Dog、Cat类中都有一个静态语句块,该语句块在类第一次被加载时候被执行。这时会有相应的信息打印出来,告诉我们这个类什么时候被加载了。从输出中可以看到,Class对象仅在需要的时候才被加载,static初始化是在类加载时进行的。

      Class.forName方法是Class类的一个静态成员。forName在执行的过程中发现如果类Dog还没有被加载,那么JVM就会调用类加载器去加载Dog类,并返回加载后的Class对象。Class对象和其他对象一样,我们可以获取并操作它的引用。在类加载的过程中,Dog类的静态语句块会被执行。如果Class .forName找不到你要加载的类,它会抛出ClassNotFoundException异常。

      Class.forName的好处就在于,不需要为了获得Class引用而持有该类型的对象,只要通过全限定名就可以返回该类型的一个Class引用。如果你已经有了该类型的对象,那么我们就可以通过调用getClass()方法来获取Class引用了,这个方法属于根类Object的一部分,它返回的是表示该对象的实际类型的Class引用:

    package com.cry;
    class Dog {
        static {
            System.out.println("Loading Dog");
        }
    }
    public class Test {
        public static void main(String[] args) {
            System.out.println("inside main");
            Dog d = new Dog();
            System.out.println("after creating Dog");
            Class c = d.getClass();
            System.out.println("finish main");
        }
    }
    /* Output:
        inside main
        Loading Dog
        after creating Dog
        finish main
     */
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    利用new操作符创建对象后,类已经装载到内存中了,所以执行getClass()方法的时候,就不会再去执行类加载的操作了,而是直接从java堆中返回该类型的Class引用。

    类字面常量

      java还提供了另一种方法来生成对Class对象的引用。即使用类字面常量,就像这样:Cat.class,这样做不仅更简单,而且更安全,因为它在编译时就会受到检查(因此不需要置于try语句块中)。并且根除了对forName()方法的调用,所有也更高效。类字面量不仅可以应用于普通的类,也可以应用于接口、数组及基本数据类型。

    注意:基本数据类型的Class对象和包装类的Class对象是不一样的:

    Class c1 = Integer.class;
    Class c2 = int.class;
    System.out.println(c1);
    System.out.println(c2);
    System.out.println(c1 == c2);
    /* Output
    class java.lang.Integer
    int
    false
    */
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    但是在包装类中有个一个字段TYPE,TYPE字段是一个引用,指向对应的基本数据类型的Class对象,如下所示,左右两边相互等价: 
    这里写图片描述 
      
      用.class来创建对Class对象的引用时,不会自动地初始化该Class对象(这点和Class.forName方法不同)。类对象的初始化阶段被延迟到了对静态方法或者非常数静态域首次引用时才执行:

    package com.cry;
    class Dog {
        static final String s1 = "Dog_s1";
        static  String s2 = "Dog_s2";
        static {
            System.out.println("Loading Dog");
        }
    }
    class Cat {
        static String s1 = "Cat_s1";
        static {
            System.out.println("Loading Cat");
        }
    }
    public class Test {
        public static void main(String[] args) throws ClassNotFoundException {
            System.out.println("----Star Dog----");
            Class dog = Dog.class;
            System.out.println("------");
            System.out.println(Dog.s1);
            System.out.println("------");
            System.out.println(Dog.s2);
            System.out.println("---start Cat---");
            Class cat = Class.forName("com.cry.Cat");
            System.out.println("-------");
            System.out.println(Cat.s1);
            System.out.println("finish main");
        }
    }
    /* Output:
    ----Star Dog----
    ------
    Dog_s1
    ------
    Loading Dog
    Dog_s2
    ---start Cat---
    Loading Cat
    -------
    Cat_s1
    finish main
     */
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

      从上面我们可以看到,如果仅使用.class语法来获得对类的Class引用是不会引发初始化的。但是如果使用Class.forName来产生引用,就会立即进行了初始化,就像Cat所看到的。

      如果一个字段被static final修饰,我们称为”编译时常量“,就像Dog的s1字段那样,那么在调用这个字段的时候是不会对Dog类进行初始化的。因为被static和final修饰的字段,在编译期就把结果放入了常量池中了。但是,如果只是将一个域设置为static 或final的,还不足以确保这种行为,就如调用Dog的s2字段后,会强制Dog进行类的初始化,因为s2字段不是一个编译时常量。

    通过javap -c -v对Dog的字节码进行反汇编:

    {
      static final java.lang.String s1;
        flags: ACC_STATIC, ACC_FINAL
        ConstantValue: String Dog_s1
      static java.lang.String s2;
        flags: ACC_STATIC
      com.cry.Dog();
        flags:
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 3: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                   0       5     0  this   Lcom/cry/Dog;
      static {};
        flags: ACC_STATIC
        Code:
          stack=2, locals=0, args_size=0
             0: ldc           #2                  // String Dog_s2
             2: putstatic     #3                  // Field s2:Ljava/lang/String;
             5: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
             8: ldc           #5                  // String Loading Dog
            10: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            13: return
          LineNumberTable:
            line 6: 0
            line 9: 5
            line 10: 13
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

      从上面可以看出s1在编译后被ConstantValue属性修饰 ConstantValue: String Dog_s1,表示即同时被final和static修饰。而s2并没有被ConstantValue修饰,因为它不是一个编译时常量。在static{}中表示类的初始化操作,在操作中我们看到只有s2字段进行了赋值,而却没有s1的踪影,因此调用s1字段是不会触发类的初始化的。

    小结

      一旦类被加载了到了内存中,那么不论通过哪种方式获得该类的Class对象,它们返回的都是指向同一个java堆地址上的Class引用。jvm不会创建两个相同类型的Class对象:

    package com.cry;
    class Cat {
        static {
            System.out.println("Loading Cat");
        }
    }
    public class Test {
        public static void main(String[] args) throws ClassNotFoundException {
            System.out.println("inside main");
            Class c1 = Cat.class;
            Class c2= Class.forName("com.cry.Cat");
            Class c3=new Cat().getClass();
            Class c4 =new Cat().getClass();
            System.out.println(c1==c2);
            System.out.println(c2==c3);
            System.out.println("finish main");
        }
    }
    /* Output:
    inside main
    -------
    Loading Cat
    true
    true
    finish main
     */
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    从上面我们可以看出执行不同获取Class引用的方法,返回的其实都是同一个Class对象。

      其实对于任意一个Class对象,都需要由它的类加载器和这个类本身一同确定其在就Java虚拟机中的唯一性,也就是说,即使两个Class对象来源于同一个Class文件,只要加载它们的类加载器不同,那这两个Class对象就必定不相等。这里的“相等”包括了代表类的Class对象的equals()、isAssignableFrom()、isInstance()等方法的返回结果,也包括了使用instanceof关键字对对象所属关系的判定结果。所以在java虚拟机中使用双亲委派模型来组织类加载器之间的关系,来保证Class对象的唯一性。


    泛型Class引用

      Class引用表示的就是它所指向的对象的确切类型,而该对象便是Class类的一个对象。在JavaSE5中,允许你对Class引用所指向的Class对象的类型进行限定,也就是说你可以对Class对象使用泛型语法。通过泛型语法,可以让编译器强制指向额外的类型检查:

    public final class Class<T> implements java.io.Serializable,
                                  GenericDeclaration,
                                  Type,
                                  AnnotatedElement {
    • 1
    • 2
    • 3
    • 4
       Class<Integer> c1 = int.class;
       c1=Integer.class;
       //c1=Double.class; 编译报错
    • 1
    • 2
    • 3

    虽然int.class和Integer.class指向的不是同一个Class对象引用,但是它们基本类型和包装类的关系,int可以自动包装为Integer,所以编译器可以编译通过。

    泛型中的类型可以持有其子类的引用吗?不行:

    Class<Number> c1 = Integer.class;  //编译报错
    • 1

    虽然Integer继承自Number,但是编译器无法编译通过。

    为了使用泛化的Class引用放松限制,我们还可以使用通配符,它是Java泛型的一部分。通配符的符合是”?“,表示“任何事物“:

     Class<?> c1 = int.class;
      c1= double.class;
    • 1
    • 2

    Class

            Class<? extends Number> c1 = Integer.class;
            c1 = Number.class;
            c1 = Double.class;
            // c1=String.class; 报错,不属于Number类和其子类
    • 1
    • 2
    • 3
    • 4

    通配符?不仅可以与extend结合,而且还可以与super关键字相结合,表示被限定为某种类型,或该类型的任何父类型:

            Class<? super Integer> c1 = Integer.class;
            c1 = Number.class;
            c1 = Object.class;
            c1=Integer.class.getSuperclass();
    • 1
    • 2
    • 3
    • 4

    向Class引用添加泛型语法的原因仅仅是为了提供编译期类型检查。


    Class类的方法

    方法名说明
    forName()(1)获取Class对象的一个引用,但引用的类还没有加载(该类的第一个对象没有生成)就加载了这个类。
    (2)为了产生Class引用,forName()立即就进行了初始化。
    Object-getClass()获取Class对象的一个引用,返回表示该对象的实际类型的Class引用。
    getName()取全限定的类名(包括包名),即类的完整名字。
    getSimpleName()获取类名(不包括包名)
    getCanonicalName()获取全限定的类名(包括包名)
    isInterface()判断Class对象是否是表示一个接口
    getInterfaces()返回Class对象数组,表示Class对象所引用的类所实现的所有接口。
    getSupercalss()返回Class对象,表示Class对象所引用的类所继承的直接基类。应用该方法可在运行时发现一个对象完整的继承结构。
    newInstance()返回一个Oject对象,是实现“虚拟构造器”的一种途径。使用该方法创建的类,必须带有无参的构造器
    getFields()获得某个类的所有的公共(public)的字段,包括继承自父类的所有公共字段。 类似的还有getMethods和getConstructors。
    getDeclaredFields获得某个类的自己声明的字段,即包括public、private和proteced,默认但是不包括父类声明的任何字段。类似的还有getDeclaredMethods和getDeclaredConstructors。

    package com.cry;
    import java.lang.reflect.Field;
    interface I1 {
    }
    interface I2 {
    }
    class Cell{
        public int mCellPublic;
    }
    class Animal extends  Cell{
        private int mAnimalPrivate;
        protected int mAnimalProtected;
        int mAnimalDefault;
        public int mAnimalPublic;
        private static int sAnimalPrivate;
        protected static int sAnimalProtected;
        static int sAnimalDefault;
        public static int sAnimalPublic;
    }
    class Dog extends Animal implements I1, I2 {
        private int mDogPrivate;
        public int mDogPublic;
        protected int mDogProtected;
        private int mDogDefault;
        private static int sDogPrivate;
        protected static int sDogProtected;
        static int sDogDefault;
        public static int sDogPublic;
    }
    public class Test {
        public static void main(String[] args) throws IllegalAccessException, InstantiationException {
            Class<Dog> dog = Dog.class;
            //类名打印
            System.out.println(dog.getName()); //com.cry.Dog
            System.out.println(dog.getSimpleName()); //Dog
            System.out.println(dog.getCanonicalName());//com.cry.Dog
            //接口
            System.out.println(dog.isInterface()); //false
            for (Class iI : dog.getInterfaces()) {
                System.out.println(iI);
            }
             /*
              interface com.cry.I1
              interface com.cry.I2
             */
    
            //父类
            System.out.println(dog.getSuperclass());//class com.cry.Animal
            //创建对象
            Dog d = dog.newInstance();
            //字段
            for (Field f : dog.getFields()) {
                System.out.println(f.getName());
            }
            /*
                mDogPublic
                sDogPublic
                mAnimalPublic
                sAnimalPublic
                mCellPublic  //父类的父类的公共字段也打印出来了
             */
            System.out.println("---------");
            for (Field f : dog.getDeclaredFields()) {
                System.out.println(f.getName());
            }
            /** 只有自己类声明的字段
             mDogPrivate
             mDogPublic
             mDogProtected
             mDogDefault
             sDogPrivate
             sDogProtected
             sDogDefault
             sDogPublic
             */
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    getName、getCanonicalName与getSimpleName的区别:

    getSimpleName:只获取类名 
    getName:类的全限定名,jvm中Class的表示,可以用于动态加载Class对象,例如Class.forName。 
    getCanonicalName:返回更容易理解的表示,主要用于输出(toString)或log打印,大多数情况下和getName一样,但是在内部类、数组等类型的表示形式就不同了。

    package com.cry;
    public class Test {
        private  class inner{
        }
        public static void main(String[] args) throws ClassNotFoundException {
            //普通类
            System.out.println(Test.class.getSimpleName()); //Test
            System.out.println(Test.class.getName()); //com.cry.Test
            System.out.println(Test.class.getCanonicalName()); //com.cry.Test
            //内部类
            System.out.println(inner.class.getSimpleName()); //inner
            System.out.println(inner.class.getName()); //com.cry.Test$inner
            System.out.println(inner.class.getCanonicalName()); //com.cry.Test.inner
            //数组
            System.out.println(args.getClass().getSimpleName()); //String[]
            System.out.println(args.getClass().getName()); //[Ljava.lang.String;
            System.out.println(args.getClass().getCanonicalName()); //java.lang.String[]
            //我们不能用getCanonicalName去加载类对象,必须用getName
            //Class.forName(inner.class.getCanonicalName()); 报错
            Class.forName(inner.class.getName());
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    参考: 
    java中的Class对象和new关键字解析 
    Thinking in Java


    展开全文
  • Class类是我们再熟悉不过的东西,但是对于Class对象,很多人却是一脸懵逼。 Class对象到底是什么呢?今天我们就来深入了解一下它。 1.RTTI的概念 RTTI(Run-Time Type Identification),即运行时类型识别,这...

    Class类是我们再熟悉不过的东西,但是对于Class对象,很多人却是一脸懵逼。

    Class对象到底是什么呢?今天我们就来深入了解一下它。

    1.RTTI的概念

    RTTI(Run-Time Type Identification),即运行时类型识别,这个词一直是 C++ 中的概念,至于Java中出现RRTI的说法则是源于《Thinking in Java》一书,其作用是在运行时识别一个对象的类型和类的信息。

    RTTI分两种:传统的”RTTI”与反射机制。

    对于传统的“RTTI”,它假定我们在编译期已知道了所有类型(在没有反射机制创建和使用类对象时,一般都是编译期已确定其类型,如new对象时该类必须已定义好)。

    另外一种是反射机制,它允许我们在运行时发现和使用类型的信息。在Java中用来表示运行时类型信息的对应类就是Class类。


    2.Class类

    Class类也是一个实实在在的类,存在于JDK的java.lang包中,其部分源码如下:

    public final class Class<T> implements java.io.Serializable,GenericDeclaration,Type, AnnotatedElement {
        private static final int ANNOTATION= 0x00002000;
        private static final int ENUM      = 0x00004000;
        private static final int SYNTHETIC = 0x00001000;
    
        private static native void registerNatives();
        static {
            registerNatives();
        }
    
        /*
         * Private constructor. Only the Java Virtual Machine creates Class objects.(私有构造,只能由JVM创建该类)
         * This constructor is not used and prevents the default constructor being
         * generated.
         */
        private Class(ClassLoader loader) {
            // Initialize final field for classLoader.  The initialization value of non-null
            // prevents future JIT optimizations from assuming this final field is null.
            classLoader = loader;
        }
    

    对象是类的实例化,那么既然Class类是真实存在,那么自然也可以被实例化。而Class的创建的实例就是Class对象。

    3.Class对象

    上文说到Class类被创建后的对象就是Class对象,这里需要注意,Class对象表示的是自己手动编写类的类型信息。

    这是什么意思呢?比如创建一个Searchin类,那么,JVM就会创建一个Searchin对应Class类的Class对象,该Class对象则保存了Searchin类相关的类型信息。

    实际上在Java中每个类都有且只有一个Class对象。

    每当我们编写并且编译一个新创建的类就会产生一个对应Class对象并且这个Class对象会被保存在同名.class文件里。说到.class文件,大家可能很熟悉,因为Java程序编译之后就会有.class文件。事实上,编译后的字节码文件保存的就是Class对象。

    那为什么需要这样一个Class对象呢?

    是这样的,当我们new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。也就是说,Class对象对于类的实例化具有非常重要的意义。没它就没法new新对象和引用静态成员变量。

    这里需要再次重点提醒一下,上文说到“Java中每个类只有一个Class对象”,这句话是什么意思呢?

    对于手动编写的每个Class类,无论创建多少个实例对象,在JVM中都只有一个Class对象,即在内存中每个类有且只有一个相对应的Class对象。

    这里给大家画个简单的图,方便大家理解。

    4.总结

    通过上文所提及知识,我们可以得出以下几点信息:

    • Class类也是类的一种,与class关键字是不一样的。
    • 手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,该Class对象保存在同名.class的文件中(即编译后得到的字节码文件)。
    • 每个通过关键字class标识的类,在内存中有且只有一个与之对应的Class对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象。
    • Class类只存私有构造函数,因此对应Class对象只能有JVM创建和加载
    • Class类的对象的作用是运行时提供或获得某个对象的类型信息,这点对于反射技术很重要。

     

     

     

     

    Biu~~~~~~~~~~~~~~~~~~~~宫å´éªé¾ç«è¡¨æå|é¾ç«gifå¾è¡¨æåä¸è½½å¾ç~~~~~~~~~~~~~~~~~~~~~~pia!

    参考文章:https://blog.csdn.net/javazejian/article/details/70768369

    展开全文
  • 说明:内容复制粘贴https://www.cnblogs.com/cunlau/p/3624878.html而来 Class对象的生成方式如下:1.类名.class 说明: JVM将使用类装载器, 将类装入内存(前提是:类还没有装入内存),不做类的初始化工作.返回Class的...

    说明:
    1.内容复制粘贴https://www.cnblogs.com/cunlau/p/3624878.html而来
    2.通过class对象创建实体类看本文补充部分

    Class对象的生成方式如下:

    1.类名.class 说明: JVM将使用类装载器, 将类装入内存(前提是:类还没有装入内存),不做类的初始化工作.返回Class的对象

    2.Class.forName(“类名字符串”) (注:类名字符串是包名+类名) 说明:装入类,并做类的静态初始化,返回Class的对象

    3.实例对象.getClass() 说明:对类进行静态初始化、非静态初始化;返回引用运行时真正所指的对象(因为:子对象的引用可能会赋给父对象的引用变量中)所属的类的Class的对象

    结论:
    我们知道,静态的方法属性初始化,是在加载类的时候初始化。而非静态方法属性初始化,是new类实例对象的时候加载。

    因此,这段程序说明,三种方式生成Class对象,其实只有一个Class对象。在生成Class对象的时候,首先判断内存中是否已经加载。

    所以,生成Class对象的过程其实是如此的:

    当我们编写一个新的java类时,JVM就会帮我们编译成class对象,存放在同名的.class文件中。在运行时,当需要生成这个类的对象,JVM就会检查此类是否已经装载内存中。若是没有装载,则把.class文件装入到内存中。若是装载,则根据class文件生成实例对象。

    public  static void main(String[] args){    
        try {
                //测试.class
                Class testTypeClass=TestClassType.class;
                System.out.println("testTypeClass---"+testTypeClass);
                //测试Class.forName()(***代表包路径)
                Class testTypeForName=Class.forName("***.TestClassType");
                System.out.println("testTypeForName---"+testTypeForName);
                //测试Object.getClass()
                TestClassType testTypeGetClass= new TestClassType();
                System.out.println("testTypeGetClass---"+testTypeGetClass.getClass());
            } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
                e.printStackTrace();
            }
    }
    public class TestClassType {
        //构造函数
        public TestClassType(){
            System.out.println("----构造函数---");
        }
        //静态的参数初始化
        static{
            System.out.println("---静态的参数初始化---");
        }
        //非静态的参数初始化
        {
            System.out.println("----非静态的参数初始化---");
        }
    }

    说明:main函数里的代码可以分开执行看一下,思考理解一下。
    补充
    java生成对象有多种方式,这里只针对上文说明的三种生成class对象方式来反向创建实体类。
    通过class对象转换成对应实体类,这里使用newInstance()函数,不同的是生成class方式不同,调用newInstance()之后返回类型不同

    Object obj1=testTypeClass.newInstance();
    TestClassType o1 = (TestClassType) obj1;
    Object obj2=testTypeForName.newInstance();
    TestClassType o2 = (TestClassType) obj2;
    TestClassType o3=testTypeGetClass.getClass().newInstance();
    展开全文
  • Class类 和 class对象(运行时的类型信息)

    千次阅读 多人点赞 2018-12-05 11:28:07
    某种意义上来说,java有两种对象:实例对象和Class对象。每个类的运行时的类型信息就是用Class对象表示的。它包含了与类有关的信息。其实我们的实例对象就通过Class对象来创建的。Java使用Class对象执行其RTTI(运行...
  • 如何创建Class对象

    千次阅读 2019-01-15 20:19:48
    在java中,要获得类的Class对象,可以通过以下三种方式来实现。 第一种是通过Class类的forName()方法,如进行数据库操作时就是通过这种方式加载数据库驱动的。在使用Class类的forName()方法时,需要为其传递一个...
  • java中Class对象详解

    千次阅读 2015-04-15 10:51:41
    java中把生成Class对象和实例对象弄混了,更何况生成Class对象和生成instance都有多种方式。所以只有弄清其中的原理,才可以深入理解。首先要生成Class对象,然后再生成Instance。那Class对象的生成方式有哪些呢,...
  • Java深入 | 对象如何创建&Class对象

    千次阅读 2016-04-27 21:07:54
    类型信息在运行时如何表示(RTTI),由Class对象的特殊对象完成,Class对象包含与类有关的信息。 类作为程序中的一部分,每个类都有一个Class对象(即,每当编写且编译了一个新类,就会产生一个Class对象,这个对象...
  • 类型信息之Class对象

    千次阅读 2016-12-04 16:21:55
    Java中每个类都有一个Class对象,编译时便会产生(.class文件)。为了生成这个对象,JVM调用”类加载器”完成。每个类第一次使用时(创建类的静态成员的引用,构造器也是静态方法)动态加载到JVM。Class对象在需要时...
  • Class对象就是.class文件?那么这个对象存在哪里呢?本地硬盘?不是内存中吗
  • Java获取泛型的Class对象

    万次阅读 2017-10-31 14:32:39
    在做解析excel的时候,想通过泛型把解析和转java bean的过程封装起来,后面发现java里面只能获取到父类的泛型Class。这样的话,就意味着如果获取泛型Class来实例化bean...这里记录一下Java获取泛型的Class对象的demo
  • 假如我有一个类,绝对路径是D:\com\csdn\Test.class。 我怎样才能获取到这个类的全称,也就是com.csdn.Test。 我最终想要的是Test.class 的Class对象
  • 【Java】Class 对象

    千次阅读 2015-03-25 15:53:14
    每个类都有一个Class对象Java中一切皆对象,各种各样的对象提供了丰富的功能,今天说说”对象的对象”。java.lang.Object java.lang.Class每个类都有一个相应的Class对象,该Class对象包含了创建对应类所需的各种...
  • 获取Class对象的三种方法

    千次阅读 2018-06-03 15:16:40
    1、调用Object类的getClass()方法来得到Class对象,这也是最常见的产生Class对象的方法。例如: MyObject x; Class c1 = x.getClass(); 2、使用Class类的中静态forName()方法获得与字符串对应的Class对象。例如:...
  • 看到class对象: 测试的两个类: public class Father { private String sex; public String identity; } @RestController @RequestMapping("/") public class HelloController extends Father{ private String ...
  • Class对象的生成方式如下:   1.类名.class 说明: JVM将使用类装载器, 将类装入内存(前提是:类还没有装入内存),不做类的初始化工作.返回Class的对象   2.Class.forName("类名字符串") (注:类名...
  • 1.Class对象获取的三种方式 上一章节已经说过,一般情况下,Java反射操作都需要首先获取Class对象。获取Class对象的方式有三种。 公有属性class 方法getClass() Class.forName() 下面是示例代码: /** * &...
  • 获取Class对象的三种方式

    千次阅读 2017-08-31 00:16:17
    但是如果平时注重业务开发,而比较少关注很多开源框架如Spring、Hibernate等的源码,我们一般会比较少看到Class对象的身影。今天我们就来看看这个神秘的Class对象
  • Java反射 获取Class对象的三种方式

    千次阅读 2018-05-19 18:31:48
    获取一个类对应的Class类的方法1.使用Object.getClass ()方法----引用类型的对象的获取方式如果我们已经拿到了一个对象,可以使用这个对象...//使用已经存在的对象的getClass()方法获取Class对象 Class class1=str...
  • Java之反射机制(获取Class对象的三种方式) 开发工具与关键技术:MyEclipse 10,java 撰写时间:2019-06-14 如何得到各个字节码对应的实例对象? 每个类被加载后,系统会为该类生成对应的 Class对象,通过 Class ...
  • JVM的Class对象的存储位置和作用

    千次阅读 2019-06-14 09:17:24
    JVM的Class对象的存储位置和作用 (2018-03-25 16:50:16) 分类: Java 在弄清这个问题前必须的明白类型信息和JVM方法区。 类型信息是一个java类的描述信息(class mate),classloader加载一个类时从class文件中提取出来...
  • Java 获取Class对象的三种方法

    千次阅读 2016-09-10 12:59:24
    虚拟机为每种类型管理一个独一无二的对象,即Class对象,当类被加载时,对应的Class类型对象会通过调用类加载器中的 defineClass 方法自动构造自动创建。注意Class对象是单实例的,在运行时,如果该Clas
  • (一)对反射的认识 反射:对对象的反向处理,通过对象获取对象的来源信息(即可以知道这个对象是...(二)Class对象的三种获取方式 序号 方式 1 通过对象的getClass()方法获取Class对象。 2 通过类名...
  • 转载请标明出处:...以下所示页码,是指《java编程思想》第四版的页码Class对象的生成方式如下:1.类名.class 说明: JVM将使用类装载器, 将类装入内存(前提是:类还没有装入内存),
  • java 获取数组元素类型的class对象

    万次阅读 2015-12-23 16:31:10
    每一个类被jvm加载到内存中后,都会生成一个class对象,而且每一个类的实例对象都会有一个引用指针指向该class对象。基于这个class对象我们可以做很多事情,同时它也是整个反射机制的基础。 这里,我只想说一下,...
  • 深入理解Class对象 ...认识Class对象之前,先来了解一个概念,RTTI(Run-Time Type Identification)运行时类型识别,对于这个词一直是 C++ 中的概念,至于Java中出现RRTI的说法则是源于《Thin...
  • JS原生获取class对象(getElementsByClassName)
  • Class对象的生成方式如下:   1.类名.class 说明: JVM将使用类装载器, 将类装入内存(前提是:类还没有装入内存),不做类的初始化工作.返回Class的对象   2.Class.forName("类名字符串") (注:类名字符串是包名+...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 235,609
精华内容 94,243
关键字:

class对象