zejian博客:深入分析Java中的Class对象,探究RRTI概念及作用助力理解反射技术

[id_[id_[id_[id_16[id_786013169]1376283]12[id_1245039917]41351]029045016]276531097]

zejian博客:深入分析Java中的Class对象,探究RRTI概念及作用助力理解反射技术

[id_438[id_68820440]9291]

[id_1[id_709615379]7277920]

[id_1198674712]

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

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

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

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

2. 阐述该分析对后续深入理解反射技术的重要性。

深入理解 Class 对象的概念,理解 RRTI 的概念,同时明白 Class 对象的作用

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

publicfinal 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();
    }
    /*
私有构造。只有 Java 虚拟机能够创建类对象。
     * generated.
     */
    private Class(ClassLoader loader) {
        初始化类加载器的最终字段。非空的初始化值
        防止未来的即时编译(JIT)优化假定这个最终字段为 null。
        classLoader = loader;
    }

Class 类创建后的对象即为 Class 对象。需注意,Class 对象表示的是手动编写类的类型信息。例如创建一个 Shapes 类,此时 JVM 会创建一个与 Shapes 类对应的 Class 类的 Class 对象,该 Class 对象保存了 Shapes 类的相关类型信息。在 Java 中,每个类都拥有一个 Class 对象。每当我们编写并编译一个新创建的类时,就会生成一个与之对应的 Class 对象,并且这个 Class 对象会被保存在同名的.class 文件中(编译后的字节码文件保存的就是 Class 对象)。那么,为什么需要这样一个 Class 对象呢?当我们创建一个新对象或者引用静态成员变量的时候,Java 虚拟机(JVM)中的类加载器子系统会把对应的 Class 对象加载到 JVM 里,接着 JVM 会依据这个类型信息相关的 Class 对象来创建我们所需要的实例对象或者提供静态变量的引用值。特别要注意的是,每个手动编写的 class 类,即便创建了很多实例对象,在 JVM 中也仅有一个 Class 对象。也就是说,在内存中,每个类都有且仅有一个与之相对应的 Class 对象,这有点拗口,通过下面的图(内存中的简易现象图)来理解。

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

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

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

package com.zejian;
class Candy {
  static[id_2133225803]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(Class.forName("com.zejian.Gum") 之后);
    new Cookie();
    print("After creating Cookie");
  }
}

在上述代码里,类 Candy 存在一个 static 语句,该语句会在类首次被加载时执行,其作用是告知我们该类在何时被加载;类 Gum 也存在一个 static 语句,此语句会在类首次被加载时执行,作用是告知我们该类在何时被加载;类 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

从结果角度来看,创建一个 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 对象已经创建完成,获取其引用并不困难,所以无需触发类的最后阶段初始化。下面通过一个小例子来验证这个过程:

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对象
Initable.class 被赋值给了类 initable 。
    System.out.println("After creating Initable ref");
    //不触发类初始化
System 输出 Initable 的 staticFinal 。
    //会触发类初始化
System.out 会打印出 Initable 类中的 staticFinal2 成员。
    //会触发类初始化
System 输出 Initable2 的 staticNonFinal 字段的值。
    //forName方法获取Class对象
通过 Class.forName 方法初始化类 initable3 。 首先获取类的引用,然后可以基于该引用进行后续的操作,比如创建对象等。 这种方式在 Java 编程中常用于动态加载和使用类。 它提供了一种在运行时根据需要加载类的灵活性。"Initable3");
    System.out.println(创建了 Initable3 引用之后);
System 输出 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 对象的方式都已经分析完毕,好了~由此可以得出一个小结论:

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

理解泛化的Class对象引用

Class 的引用总数会指向某个类的 Class 对象,因为这样,利用 Class 对象就能够创建实例类,这能够充分说明 Class 对象的引用所指向的对象的确切类型。在 Java SE5 引入泛型之后,我们可以借助泛型来表示 Class 对象更具体的类型,尽管在运行期间这些类型会被擦除,但在编译期足以保证我们使用的是正确的对象类型。如下:

/**
在 2017 年 4 月 30 日,zejian 完成了创建行为。
Blog 的地址是 http://blog.csdn.net/javazejian ,请尊重原创。
 */
public class ClazzDemo {
    public static void main(String[] args){
        //没有泛型
        Class intClass = int.class;
        //带泛型的Class对象
        Class integerClass = int.class;
        integerClass = Integer.class;
        //没有泛型的约束,可以随意赋值
        intClass= double.class;
        //编译期错误,无法编译通过
        //integerClass = double.class
    }
}

从代码能够得知,对于普通 Class 对象的声明,在编译器那里不会去检查 Class 对象的准确类型是否满足要求。并且,如果存在错误的话,只有在运行的时候才会被暴露出来。编译器在编译期会对带泛型的类进行额外的类型检查,这是通过泛型声明指明类型的 Class 对象来实现的,这样就能在编译期保证类型的正确性。实际上,Integer.class 就是一个 Class 类的对象。面对下述语句时,人们确实可能会感到困惑,而且该语句确实无法编译通过。

//编译无法通过
Class 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 的问题:

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

上述代码是可行的。extends 关键字的作用在于告知编译器,只要是 Number 的子类,都能够进行赋值。这与前面直接使用 Class 的情况有所不同。实际上,我们应当始终牢记,向 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();
第二句则进一步解释了这种等同关系。
Class dogType = Dog.class;
Dog dog = dogType.cast(animal)

使用 Class 对象的 cast 方法,该方法的参数接收一个参数对象,然后将其转换为 Class 引用的类型。这种方式相较于之前的强制转换,看起来要更麻烦一些,确实是这样的。并且在类型无法正确转换时,依然会抛出 ClassCastException 异常。源码如下:

public T cast(Object obj) {
    if (obj != null && !isInstance(obj))
         throw newClassCastException 发生在尝试将某个对象进行类型转换的时候,转换失败的原因是无法将该对象转换为指定的类型,这是由于对象的实际类型与期望的类型不匹配所导致的。
     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;
      }
}

Class 类中有一个 Native 方法叫做 isInstance 方法,它也是用来判断对象类型的。来看一个简单的例子:

public void cast2(Object obj){
        //instanceof关键字
        if(obj instanceof Animal){
            Animal animal= (Animal) obj;
        }
        //isInstance方法
        if如果 obj 是 Animal 类的实例,那么就满足条件。即 obj 具有 Animal 类的特征和行为,能够被视为 Animal 类的对象。
            Animal animal= (Animal) obj;
        }
  }

事实上,instanceOf 方法与 isInstance 方法所产生的结果是一样的。instanceOf 是一个关键字,它仅被用于对象引用变量,用于检查左边的对象是否为右边类或接口的实例化。如果被测对象为 null 值,那么测试结果始终为 false。其一般形式为:

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

Class 类有一个 Native 方法叫 isInstance 方法。其中 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 的类等于 A 的类。+
x 所属于的类等同于 A 类。
    print(x 的类与 B 的类相等。 +
x 的类等于 B 类;x 的类与 B 类相等;x 的类和 B 类是相同的;x 的类恰好是 B 类;x 的类刚好是 B 类;x 的类就是 B 类
  }
  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类及其用法

Constructor 类处于反射包(java.lang.reflect)内,它所反映的是 Class 对象所代表的类的构造方法。通过 Class 类中的方法能够获取 Constructor 对象,Class 类中与 Constructor 相关的主要方法有以下这些:

方法返回值方法名称方法说明

static Class

forName(String className)

获取与具有给定字符串名的类或接口相联系的 Class 对象。

Constructor

获取构造函数(参数类型类...)

返回指定参数类型、具有public访问权限的构造函数对象

Constructor[]

getConstructors()

获取所有具有 public 访问权限的构造函数所对应的 Constructor 对象,并组成数组

Constructor

获取声明的构造函数(参数类型为 Class... )

返回指定参数类型的构造函数对象,这些构造函数对象是所有被声明的(包含 private 的)构造函数对象

Constructor[]

getDeclaredConstructor()

返回所有声明的(包括private)构造函数对象

newInstance()

创建此 Class 对象所表示的类的一个新实例。

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

package reflect;
import java.io.Serializable;
importjava.lang 中的 reflect 包下的 Constructor 类。
/**
在 2017 年 5 月 1 日,zejian 进行了创建操作。
 * 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 对象,通过 clazz 类的 newInstance()方法来实现。
        user.setAge(20);
        user.setName("Rollen");
        System.out.println(user);
        System.out.println(请提供需要改写的句子呀,你只给了“"--------------------------------------------"”,没有具体内容呢。);
        //获取带String参数的public构造函数
获取类 clazz 的构造函数,该构造函数的参数类型为 String.class,然后将其赋值给构造函数 cs1。
        //创建User
User 创建了一个名为 user1 的对象,这个对象是通过调用 cs1 的 newInstance() 方法创建的。"xiaolong");
        user1.setAge(22);
        System.out.println("user1:"+user1.toString());
        System.out.println("请提供需要改写的句子呀,你没有给出具体的内容呢。");
        取得一个带 int 和 String 参数的指定构造函数,并且这个方法是私有的构造函数,为 private 。
构造函数 cs2 等于 clazz 的声明构造函数,该构造函数的参数为空。构造函数 cs2 用于创建 clazz 的实例对象。通过调用 clazz 的 getDeclaredConstructor 方法,可以获取到 clazz 的声明构造函数,并将其赋值给 cs2。这样,就可以使用 cs2 来创建 clazz 的实例对象了。int.class,String.class);
        //由于是private必须设置可访问
        cs2.setAccessible(true);
        //创建user对象
User user2 等于通过 (User) cs2 来创建新的实例。25,"lidakang");
        System.out.println("user2:"+user2.toString());
        System.out.println("--------------------------------------------");
        //获取所有构造包含private
        Constructor获取到的声明构造函数被存储在 cons 数组里。
        // 查看每个构造方法需要的参数
        for (int i = 0; i < cons.length; i++) {
            //获取构造函数参数类型
            Classclazzs 等于 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)
打印出 clazzs[j] 的名称;获取 clazzs[j] 的名称并进行打印;将 clazzs[j] 的名称打印出来;输出 clazzs[j] 的名称;把 clazzs[j] 的名称予以打印;对 clazzs[j] 的名称进行打印输出;打印 clazzs[j] 的具体名称;把 clazzs[j] 的名称打印显示出来;打印由 clazzs[j] 所代表的类的名称;将 clazzs[j] 所属类的名称进行打印;打印出与 clazzs[j] 相关联的类的名称;把 clazzs[j] 对应的类的名称打印出来;打印 clazzs[j] 所对应的类的名称信息;将 clazzs[j] 所指类的名称进行打印展示;打印由 clazzs[j] 指向的类的名称;把 clazzs[j] 所关联类的名称打印出来;打印出与 clazzs[j] 有联系的类的名称;将 clazzs[j] 所涉及类的名称进行打印;打印 clazzs[j] 所涉及到的类的名称;把 clazzs[j] 所包含类的名称打印出来;打印出 clazzs[j] 所包含的类的名称;将 clazzs[j] 所涵盖类的名称进行打印;打印出 clazzs[j] 所涵盖的类的名称;把 clazzs[j] 所具有类的名称打印出来;打印出 clazzs[j] 所具有的类的名称;将 clazzs[j] 所具备类的名称进行打印;打印出 clazzs[j] 所具备的类的名称。
                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

getDeclaringClass()

返回一个 Class 对象,这个 Class 对象所表示的类,就是声明了由这个 Constructor 对象所表示的构造方法的类,实际上就是返回真实的类型(不包含参数)

Type[]

getGenericParameterTypes()

按照声明顺序会返回一组 Type 对象,而返回的这些 Type 对象就是 Constructor 对象构造函数的形参类型。

String

getName()

以字符串形式返回此构造方法的名称。

Class[]

getParameterTypes()

按照声明顺序去返回一组 Class 对象,也就是返回 Constructor 对象所表示的构造方法的形参类型。

创建一个新的实例,传入的参数为 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(获取泛型参数类型。);
此 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
这样有助于在代码中更灵活地处理泛型相关的逻辑。
 参数名称tp:int
 参数名称tp:class java.lang.String
 -----getParameterTypes-----
 参数名称:int
 参数名称:java.lang.String
 -----getName-----
 getName:reflect.User
 -----getoGenericString-----
getoGenericString() 是一个私有方法,它接受两个参数,一个是整数类型,另一个是字符串类型(java.lang.String)。
 */

其中关于 Type 类型要进行简单说明。Type 在 Java 编程语言中是所有类型的公共高级接口。这些类型包含原始类型、参数化类型、数组类型、类型变量以及基本类型。getGenericParameterTypes 和 getParameterTypes 都能获取构成函数的参数类型。getGenericParameterTypes 返回的是 Type 类型,而 getParameterTypes 返回的是 Class 类型。因为 Type 是顶级接口,Class 也实现了该接口,所以 Class 类是 Type 的子类。Type 表示的是全部类型,而每个 Class 对象表示一个具体类型的实例,例如 String.class 就仅代表 String 类型。从这来看,Type 和 Class 在表示类型方面几乎是一样的,只是 Type 所表示的范围比 Class 大很多罢了。当然,Type 还有其他的子类,例如:

以上分析让我们对 Constructor 类有了较为清晰的理解。用好 Class 类和 Constructor 类,我们能够在运行时动态地创建任意对象,这样就能突破必须在编译期知晓确切类型的限制。

Field类及其用法

Field 能提供关于类或接口的单个字段的相关信息,并且能实现对其的动态访问权限。反射的字段有可能是类(静态)字段,也有可能是实例字段。同理,我们可以借助 Class 类所提供的方法来获取能够代表字段信息的 Field 对象,Class 类中与 Field 对象相关的方法如下:

方法返回值方法名称方法说明

Field

getDeclaredField(String name)

获取指定 name 名称的字段,其中包含被 private 修饰的字段,并且不包括继承而来的字段。

Field[]

getDeclaredField()

获取 Class 对象所表示的类或接口的所有字段,这些字段包含 private 修饰的,并且不包括继承的字段。

Field

getField(String name)

获取具有 public 修饰且名为指定 name 的字段,此字段包含继承字段

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) throwsClassNotFoundException 以及 NoSuchFieldException 。
        Class clazz = Class.forName("reflect.Student");
        获取 Field 类,该类为指定的字段名称,需注意的是,此字段的修饰符必须是 public,并且必须存在该字段。
        // 否则抛NoSuchFieldException
        Field field = clazz.getField("age");
        System.out.println("field:"+field);
        获取所有字段,这些字段的修饰符为 public,并且包含父类字段,需要注意的是,只有修饰符为 public 才会被获取。
获取类 clazz 的字段并存储在字段数组 fields 中。
        for (Field f:fields) {
            System.out.println("f:"+f.getDeclaringClass());
        }
        System.out.println(获取声明的字段。);
        获取当前类的字段,这里的字段包含 private 字段,需要注意的是,不包含父类的字段。
从 clazz 中获取其声明的字段并将这些字段放置于 fields2 数组中。
        for (Field f:fields2) {
            System.out.println("f2:"+f.getDeclaringClass());
        }
        获取指定字段名称的 Field 类,此 Field 类可以是带有任意修饰符的自动类型,需要注意的是,该 Field 类不包含父类的字段。
Field 是一个类的属性,通过 clazz 获取到名为 field2 的属性,即 clazz.getDeclaredField(获取到的就是 Field field2 。"desc");
        System.out.println("field2:"+field2);
    }
    /**
      输出结果: 
字段为:公共的整数类型,属于 reflect.Person 类的 age 属性
f 表示公开的 Java 语言中的字符串,它涉及到 reflect 包下的 Student 类的 desc 属性。
public 修饰的 int 类型是 Person 的 age 属性,可通过反射获取该 age 的值。
public java.lang.String 是 Person 的 name 属性通过反射获取到的内容
通过这个方法可以获取到关于类或接口的字段信息。
在 public 修饰的 java.lang.String 中,包含了 reflect.Student 的 desc 信息。 这个 java.lang.String 与 reflect.Student 的 desc 相关联。 它是关于 reflect.Student 的 desc 的一种表示形式。 可以通过这个 java.lang.String 来获取 reflect.Student 的 desc 相关内容。 它是用于反映 reflect.Student 的 desc 的一个字符串。 此 java.lang.String 承载着 reflect.Student 的 desc 的相关信息。 它是与 reflect.Student 的 desc 紧密相关的一个字符串。 这个字符串与 reflect.Student 的 desc 存在特定的联系。 它是用来体现 reflect.Student 的 desc 的一个部分。 它是与 reflect.Student 的 desc 相关的一个字符串表现形式。
f2 表示私有整型的 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");
创建一个学生对象 st,它是通过 clazz 类的 newInstance()方法实例化得到的。
//获取父类public字段并赋值
获取类 clazz 的字段 Field ageField ,即通过 clazz 获取到名为 Field ageField 的字段 。"age");
ageField.set(st,18);
获取类 clazz 的字段 Field nameField ,具体通过 clazz.getField() 方法来实现。"name");
nameField.set(st,"Lily");
//只获取当前类的字段,不获取父类的字段
获取类 clazz 中声明的名为 fieldName 的字段的 Field 对象,即 Field descField = clazz.getDeclaredField(fieldName);"desc");
descField.set(st,"I am student");
具体来说,就是通过某种方式在 clazz 这个类中找到名为 Field scoreField 的字段,并将其获取到。"score");
//设置可访问,score是private的
scoreField.setAccessible(true);
scoreField.set(st,88);
System.out.println(st.toString());
Student 的年龄是 18,名字是 'Lily',描述是 'I am student',成绩是 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 对象,此 Class 对象标识了此 Field 对象所表示的字段的声明类型。

boolean

isEnumConstant()

如果此字段属于枚举类型的元素,那么就返回 true;倘若不是,就返回 false

String

toGenericString()

返回一个描述此 Field(包括其一般类型)的字符串

String

getName()

返回此 Field 对象表示的字段的名称

Class

getDeclaringClass()

获取表示类或接口的 Class 对象,此 Class 对象所声明的类或接口包含由该 Field 对象所表示的字段

void

setAccessible(boolean flag)

将此对象的 accessible 标志进行设置,设置为所指示的布尔值,以此来设置其可访问性。

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

Method类及其用法

Method 会提供关于类或接口上单独某个方法的信息,同时还会告知如何访问该方法。所反映出的方法可以是类方法,也可以是实例方法,其中还包括抽象方法。以下是 Class 类获取 Method 对象的相关方法:

方法返回值方法名称方法说明

Method

获取声明的方法,该方法的名称为 String 类型的 name,参数类型为 Class 类型的可变参数 parameterTypes

获取一个指定参数的 Method 对象,此对象能体现出此 Class 对象所指代的类或接口的特定已声明方法。

Method[]

getDeclaredMethod()

返回一个 Method 对象的数组,此数组反映了此 Class 对象所表示的类或接口声明的所有方法。这些方法包括公共方法、保护方法、默认(包)访问方法和私有方法,然而不包括继承的方法。

Method

获取名为 name 的方法,并且参数类型为 parameterTypes 数组中的每个元素所对应的类

返回一个 Method 对象,此 Method 对象反映了由该 Class 对象所表示的类或接口的特定公共成员方法。

Method[]

getMethods()

返回一个数组,此数组包含某些 Method 对象。这些 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,其中包含从父类继承而来的方法。
通过 clazz 获取到的 Method 为 method ,即 clazz.getMethod() 的返回值是 method 。"draw",int.class,String.class);
        System.out.println("method:"+method);
        //获取所有public的方法:
获取类 clazz 的方法,将这些方法存储在 Method 数组中,即 Method[] methods = clazz.getMethods();
        for (Method m:methods){
            System.out.println("m::"+m);
        }
        System.out.println("=========================================",没有具体的句子呢。);
        当前类的方法中包含 private 修饰符,这种情况下该方法无法获取到从父类继承而来的 method 。
Method method1 等于 clazz 获取的声明方法(这里的方法名等信息未明确给出)。"drawCircle");
        System.out.println("method1::"+method1);
        获取当前类的所有方法,其中包含 private 方法,此方法无法获取到继承自父类的 method。
从类 clazz 中获取其声明的方法,这些方法被存储在 Method 数组 methods1 中。
        for (Method m:methods1){
            System.out.println("m1::"+m);
        }
    }
/**
     输出结果:
方法为:公开的 void 类型的 reflect.Shape.draw 方法,该方法带有两个参数,一个是 int 类型的参数,另一个是 java.lang.String 类型的参数。
public int reflect.Circle.getAllCount() 表示获取 Circle 类中所有的数量。它是一个用于获取特定类中相关数据的方法调用。这个方法可能在 Circle 类中定义,用于统计或返回与 Circle 相关的某种计数信息。
m 所具有的公开 void 特性 reflect.Shape 进行绘制事宜。
m 中的 public 方法 reflect.Shape.draw 带有两个参数,一个是 int 类型,另一个是 java.lang.String 类型。
public final void java.lang.Object.wait(long timeout, int nanos) throws java.lang.InterruptedException;  // 等待指定的毫秒数和纳秒数,直到被通知或超时,期间可能会抛出 InterruptedException 异常。
4. 该方法会抛出 java.lang.InterruptedException 异常。
public final void 使当前线程进入等待状态() throws InterruptedException;
java.lang.Object 的这个方法 boolean equals(java.lang.Object) 是公共的。
java.lang.Object 的 public java.lang.String 类型的 toString() 方法
- 该方法返回一个整数类型的值。
java.lang.Object 有一个公共的最终的本地方法 java.lang.Class getClass()。
这些线程会从等待状态中被唤醒并继续执行。
- `java.lang.Object.notifyAll()` 是 `java.lang.Object` 类中的一个方法,用于通知所有等待在该对象上的线程。
请提供需要改写的句子呀,你没有给出具体的横线下的句子呢。
这个方法是私有的,它属于 reflect 类中的 Circle 部分,用于绘制圆形。具体来说,就是执行绘制圆形的操作。
public int reflect.Circle 获取所有的数量。
reflect.Circle 负责绘制圆形。
     */
}
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;
    }
}

在获取 Method 对象时使用 getMethods 方法,这样能把父类的方法也获取到,就像上述输出结果那样,把 Object 类的方法都打印出来了。而 getDeclaredMethod 和 getDeclaredMethods 方法仅能获取当前类的方法。我们在使用时需根据具体情况进行选择。下面将演示通过Method对象调用指定类的方法:

Class clazz = Class.forName("reflect.Circle");
//创建对象
创建一个 Circle 类的实例对象,将其赋值给 Circle 变量 circle,即 circle = (Circle) clazz.newInstance();
//获取指定参数的方法对象Method
通过 clazz 获取到 Method 对象,具体的获取方式是调用 clazz 的 getMethod 方法。"draw",int.class,String.class);
调用 Method 对象的 invoke(Object obj,Object... args)方法,以此来进行相关操作。
method.invoke(circle,15,"圈圈");
//对私有无参方法的操作
Method method1 等于 clazz 获取到的声明方法(  )"drawCircle");
//修改私有方法的访问标识
method1.setAccessible(true);
method1.invoke(circle);
//对有返回值得方法操作
Method method2 等于 clazz 获取的声明方法(这里获取的方法具体是指某个特定的方法,该方法的具体信息由 clazz 决定)。"getAllCount");
通过 method2 调用 circle 后,将结果转换为 Integer 类型并赋值给 count 。
System.out.println("count:"+count);
/**
    输出结果:
    draw 圈圈,count=15
    drawCircle
    count:100
*/

在上述代码里调用方法时,运用了 Method 类的 invoke 方法,其第一个参数用以代表被调用的对象,第二个参数则用于传递调用方法所需的参数。通过这样的方式,就实现了类方法的动态调用。

方法返回值方法名称方法说明

Object

调用(Object obj,Object... args);通过给定的对象 obj 和参数数组 args 来触发相关操作;利用 obj 和 args 来执行特定的功能;以 obj 和 args 为参数来进行调用动作;借助 obj 与 args 来实现调用行为;通过提供的 obj 和 args 来引发相关的调用过程;利用给定的 obj 以及 args 来启动调用操作;以 obj 和 args 为依据进行调用的动作;借助 obj 及 args 来促使调用行为的发生

调用由 Method 对象表示的底层方法,该 Method 对象针对带有指定参数的指定对象。

Class

getReturnType()

返回一个 Class 对象,此 Class 对象描述了此 Method 对象所表示的方法的正式返回类型,也就是方法的返回类型。

Type

getGenericReturnType()

返回一个 Type 对象,这个 Type 对象表示由该 Method 对象所表示的方法的正式返回类型,并且它也是方法的返回类型。

Class[]

getParameterTypes()

按照声明顺序返回一个数组,这个数组是由 Class 对象组成的。这些 Class 对象描述了此 Method 对象所表示的方法的形参类型。也就是说,返回的是方法的参数类型所组成的数组。

Type[]

getGenericParameterTypes()

按照声明顺序返回一个数组,数组中的元素是 Type 对象。这些 Type 对象描述了此 Method 对象所表示的方法的形参类型,同时也是返回方法的参数类型。

String

getName()

以 String 形式返回此 Method 对象所表示的方法的名称,也就是返回该方法的名称。

boolean

isVarArgs()

判断一个方法是否带有可变参数,若此方法被声明为带有可变数量的参数,那么就返回 true;若没有被声明为带有可变数量的参数,就返回 false。

String

toGenericString()

返回描述此 Method 的字符串,包括类型参数。

getReturnType 方法和 getGenericReturnType 方法的作用都是获取 Method 对象所表示的方法的返回类型。区别在于,getReturnType 方法返回的是 Class 类型,而 getGenericReturnType 方法返回的是 Type 类型(前面已做过分析)。Type 只是一个接口,在 Java8 中新增了一个默认的方法实现,该方法用于返回参数类型信息。

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

getParameterTypes 和 getGenericParameterTypes 有着同样的道理。它们都是用于获取 Method 对象所表示的方法的参数类型。其他方法与前面的 Field 和 Constructor 较为类似。

反射包中的Array类

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

方法返回值方法名称方法说明

Class

getComponentType()

返回表示数组元素类型的 Class,即数组的类型

boolean

isArray()

判定此 Class 对象是否表示一个数组类。

4. 用于将数组转换为指定类型的数组。

方法返回值方法名称方法说明

static Object

set(Object array, int index)

返回指定数组对象中索引组件的值。

static int

getLength(Object array)

以 int 形式返回指定数组对象的长度

static object

创建一个新的实例,其类类型为 componentType,维度为多个整数(即...dimensions)

创建一个具有指定类型和维度的新数组。

static Object

创建一个新的实例,其类类型为 componentType,长度为 length。

创建一个具有指定的组件类型和长度的新数组。

static void

设置对象数组中的指定索引位置的元素为给定值。具体来说,就是将指定的对象数组 array 中索引为 index 的位置的元素设置为 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 。
        //创建一个具有指定的组件类型和长度的新数组。
        //第一个参数:数组的类型,第二个参数:数组的长度
创建一个名为 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 表示数组的元素类型,创建的数组长度为 0。10);
        将字符串数组对象中索引位置是 6 的那个元素设定为“hello”。
        Array.set(array2, 6, "hello world!");
        //获得字符串数组对象的索引位置为5的元素的值
String str 等于从 array2 中通过 Array.get 方法获取到的元素,该元素被转换为 String 类型。6);
        System.out.println();
        System.out.println(str);//hello
    }
    /**
     输出结果:
10. 这十个数字包含 1、2、3、4、5、6、7、8、9、0,0 出现了 6 次。
     hello world!
     */
}

通过上述代码演示,能够利用 Array 类与反射相配合来动态创建数组。同时,也能够在运行时动态地获取和设置数组中元素的值。实际上,除了上述提到的 set/get 方法之外,Array 还专门为 8 种基本数据类型提供了特有的方法,像 setInt 和 getInt,以及 setBoolean 和 getBoolean 等,其他的以此类推。如果需要使用这些方法,查看 API 文档即可。除了上述能够动态修改数组长度,或者能够动态创建数组,或者能够动态获取值,或者能够设置值之外,还可以利用泛型来动态创建泛型数组,具体如下:

/**
接收一个泛型数组,接着创建一个长度和接收的数组长度相同的泛型数组。
  * 并把接收的数组的元素复制到新创建的数组中,
  * 最后找出新数组中的最小元素,并打印出来
  * @param a
  * @param 
  */
 public  > void min(T[] a) {
     //通过反射创建相同类型的数组
创建一个数组 b,其类型为 T[],通过调用 Array.newInstance 方法来创建一个新的数组实例。该新数组的组件类型是 a 的类的组件类型,数组的长度与 a 的长度相同。
     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 Classsuper T> getSuperclass();
对于类,要声明自己实现的所有接口;对于接口,要明确直接扩展的接口,不能包含通过父类间接继承来的接口。
public native Class[] getInterfaces();
//自己声明的注解
public Annotation[] getDeclaredAnnotations();
//所有的注解,包括继承得到的
public Annotation[] getAnnotations();
//获取或检查指定类型的注解,包括继承得到的
public  A getAnnotation(Class annotationClass);
public boolean isAnnotationPresent(Class annotationClass);
/** 
  *   内部类相关
  */
获取所有属于 public 的内部类以及接口,并且这里面包含从父类继承而得到的那些。
public Class[] getClasses();
//获取自己声明的所有的内部类和接口
public Class[] getDeclaredClasses();
如果当前的 Class 属于内部类,那么就获取声明这个类的最外部的 Class 对象。
public Class getDeclaringClass();
//如果当前Class为内部类,获取直接包含该类的类
public Class getEnclosingClass();
如果当前的 Class 是本地类,那么就返回包含它的方法;如果当前的 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中什么是冒泡,冒泡算法

2026年09月06日

28阅读

java中冒号,java双冒号

2026年09月06日

26阅读

java中的->,java中的是什么意思

2026年09月06日

26阅读

java中的super用法,Java中super的说明

2026年09月06日

28阅读

java中反射的应用,java中反射的应用场景

2026年09月06日

26阅读

java中开关的定义,Java关键字有哪些

2026年09月06日

26阅读