本篇深入分析并理解 Java
Class
对象,这对后续深入理解反射技术非常重要。🚀本篇章代码 Demo
# Class 对象深入理解
# RTTI 概念以及 Class 对象作用
- 我们先来了解一下
RTTI
概念,RTTI
全称是Run-Time Type Identification
是指运行时类型识别。 - 对于这个词一直是
C++
中的概念,至于会在Java
中出现RTTI
的说法来源于《Thinking in Java》
一书中。 - 其作用就是在运行时识别一个对象的类型和类的信息,这里分为两种:
- 第一种:传统的
RTTI
它假设我们在编译期已知道了所有类型 (在没有反射机制创建和使用类对象时,一般都是编译期确定其类型的,如new
对象时该类必须已经定义好)。 - 第二种:另外一种是反射机制,它允许我们在运行时发现和使用类型信息,在 Java 中用来表示运行时类型信息的对应类就是
Class
类,Class
类也是一个实实在在的类,它存在于JDK
中的java.lang
包下。
- 第一种:传统的
public final class Class<T> implements java.io.Serializable, GenericDeclaration, Type, AnnotatedElement, TypeDescriptor.OfField<Class<?>>, Constable { | |
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. | |
* This constructor is not used and prevents the default constructor being | |
* generated. | |
*/ | |
private Class(ClassLoader loader, Class<?> arrayComponentType) { | |
// Initialize final field for classLoader. The initialization value of non-null | |
// prevents future JIT optimizations from assuming this final field is null. | |
classLoader = loader; | |
componentType = arrayComponentType; | |
} | |
} |
Class
类被创建后对象就是Class
对象,注意:Class
对象表示的是自己手动编写的类的类型信息。- 比如创建一个
Student
类那么JVM
就会创建一个Student
对应的Class
类的类对象,该Class
对象保存了Student
类相关的类型信息。 - 实际上在
Java
中每个类都有一个Class
对象,当我们编写并编译一个新创建的类时就会产生一个对应的Class
对象并且这个Class
对象会被保存在同名的.class
文件里。 - 编译后的字节码文件保存的就是
Class
对象信息,为什么需要一个这样的Class
对象呢? - 每当我们
new
一个新对象或者引用静态成员变量时,Java
虚拟机也就是JVM
中的类加载器子系统会将对应的Class
对象加载到JVM
中,然后JVM
会再根据这个类型信息相关的Class
对象创建我们需要的对象实例或者提供静态变量的引用值。 - 特别需要注意的是手动编写的每个
class
类无论创建多少个实例对象,在JVM
中都只有一个Class
对象,即在内存中每个类有且只有一个相对应的Class
对象。 - 我们可以得出以下几点信息:
Class
类也是类的一种与class
关键字是不一样的。- 手动编写的类编译后将会产生一个
Class
对象,表示的是创建的类的类型信息,且这个Class
对象保存在同名的.class
文件中也就是字节码文件。 - 通过关键字
class
标识的类,在内存中有且只有一个与之对应的Class
对象来描述其类型信息,无论创建多少实例对象其内部指向的都是同一个Class
对象。 Class
类只存私有构造函数,因此对应Class
对象只能由JVM
创建和加载。Class
类的对象作用是运行时提供获得某个对象的类型信息,这点对于反射技术是很重要的。
# Class 对象的加载和获取方式
# Class 对象的加载
Class
对象是由JVM
来加载的,那么其加载的时机是什么时候呢?实际上所有的类都是在对其第一次使用动态加载到JVM
的。- 当程序创建第一个对类的静态成员引用时就会加载这个被使用的类,实际上加载的就是这个类的字节码文件,注意:使用
new
操作符创建的类的新实例对象也会被当作对类的静态成员的引用 (构造函数也是类的静态方法)。 - 由此看来
Java
程序在它们开始运行之前并非被完全加载到内存的,其各个部分是按需加载的,所以在使用该类时类加载器首先会检查这个类的Class
对象是否已经被加载 (类的实例对象创建时依据Class
对象中类型信息完成的)。 - 如果还没有加载,默认的类加载器就会先根据类名查找
.class
文件,编译后Class
对象被保存在同名的.class
文件中,在这个类的字节码文件被加载时,必须接受相关验证,确保其没有被破坏且不包含不良Java
代码。 - 这就是
Java
的安全机制检测,在检测完后完全没有问题才会被动态加载到内存中,此时相当于Class
对象也就被载入到了内存中,同时也就可以被用来创建这个类的所有实例对象了。
Class
对象被加载的时机问题代码示例如下:
package top.rem.rain.demo; | |
class Candy { | |
static { | |
System.out.println("Loading Candy"); | |
} | |
} | |
class Gum { | |
static { | |
System.out.println("Loading Gum"); | |
} | |
} | |
class Cookie { | |
static { | |
System.out.println("Loading Cookie"); | |
} | |
} | |
/** | |
* @Author: LightRain | |
* @Description: 类加载时机代码示例 | |
* @DateTime: 2023-11-12 21:22 | |
* @Version:1.0 | |
**/ | |
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(Gum.class.getName()); | |
} catch (ClassNotFoundException e) { | |
print("Couldn't find Gum"); | |
} | |
print("After Class.forName(\"top.rem.rain.demo.Gum\")"); | |
new Cookie(); | |
print("After creating Cookie"); | |
} | |
/* | |
结果如下: | |
inside main | |
Loading Candy | |
After creating Candy | |
Loading Gum | |
After Class.forName ("top.rem.rain.demo.Gum") | |
Loading Cookie | |
After creating Cookie | |
*/ | |
} |
- 上述代码中
Candy
、Gum
、Cookie
每个类中都存在一个static
语句,这个语句会在类第一次被加载时执行,static
的作用就是告诉我们该类在什么时候被加载。 - 从结果来看
new
一个Candy
对象和Cookie
构造函数将会被调用,属于静态方法的引用Candy
类的Class
对象和Cookie
的Class
对象肯定会被加载。 - 有意思的是
Class.forName("top.rem.rain.demo.Gum")
其中forName()
方法是Class
类中的一个static
成员方法,我们可以获取到Gum
类对应的Class
对象引用。 - 从打印结果上来看调用
forName()
方法将会导致Gum
类被加载,值得注意的是Gum
类从来没有被加载过。
# Class.forName 方法
- 上述的案例我们知道了
Class.forName()
方法的调用将会返回一个对应类的Class
对象,因此如果我们想获取一个类的运行时类型信息并加以使用时,可以调用Class。forName()
方法来获取Class
对象的引用。 - 这样做的好处就是无需通过持有该类的实例对象引用而去获取
Class
对象,下面是第二种获取方式:通过一个实例对象获取一个类的Class
对象,其中的getClass()
是从顶级类Object
继承而来的,它将返回表示该对象的实际类型的Class
对象引用。
package top.rem.rain.demo; | |
class Gum2 { | |
static { | |
System.out.println("Loading Gum2"); | |
} | |
} | |
/** | |
* @Author: LightRain | |
* @Description: getClassObject: 获取类对象 | |
* @DateTime: 2023-11-12 21:52 | |
* @Version:1.0 | |
**/ | |
public class GetClassObject { | |
public static void main(String[] args) { | |
try { | |
// 通过 Class.forName 获取 Gum2 类的 Class 对象 | |
Class clazz = Class.forName(Gum2.class.getName()); | |
System.out.println("forName clazz:" + clazz.getName()); | |
} catch (ClassNotFoundException e) { | |
e.printStackTrace(); | |
} | |
// 通过实例对象获取 Gum2 的 Class 对象 | |
Gum2 gum = new Gum2(); | |
Class cla = gum.getClass(); | |
System.out.println("new cla:" + cla.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
对象的引用不会触发类的初始化,在这里我们可以简单了解一下字面常量类加载的过程,看下图: Loading(加载)
类加载过程的一个阶段是通过一个类的完全限定名查找此类字节码文件,并利用字节码文件创建一个Class
对象。Linking(链接)
验证字节码的安全性和完整性是有必要的,准备阶段正式为静态区域分配储存空间,注意此时只是分配静态成员变量的储存空间,不包含实例成员变量。Initialization(初始化)
类加载的最后阶段若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量。
可见我们在获取字面常量的
Class
引用时触发的一个是加载阶段,因此在这个阶段Class
对象已被创建完成,获取其引用并不困难,从而无需触发类的最后阶段初始化。
import java.util.Random; | |
class Inevitable { | |
/** | |
* 编译期静态常量 | |
*/ | |
static final int STATIC_FINAL = 47; | |
/** | |
* 非编期静态常量 | |
*/ | |
static final int STATIC_FINAL2 = ClassInitialization.rand.nextInt(1000); | |
static { | |
System.out.println("Initializing Inevitable"); | |
} | |
} | |
class Inevitable2 { | |
/** | |
* 静态成员变量 | |
*/ | |
static int staticNonFinal = 147; | |
static { | |
System.out.println("Initializing Inevitable2"); | |
} | |
} | |
class Inevitable3 { | |
/** | |
* 静态成员变量 | |
*/ | |
static int staticNonFinal = 74; | |
static { | |
System.out.println("Initializing Inevitable3"); | |
} | |
} | |
/** | |
* @Author: LightRain | |
* @Description: 验证字面常量类加载执行顺序 | |
* @DateTime: 2023-11-13 00:19 | |
* @Version:1.0 | |
**/ | |
public class ClassInitialization { | |
public static Random rand = new Random(47); | |
public static void main(String[] args) throws Exception { | |
// 字面常量获取方式获取 Class 对象 | |
Class<Inevitable> initable = Inevitable.class; | |
System.out.println("After creating Inevitable ref"); | |
// 不触发类初始化 | |
System.out.println(Inevitable.STATIC_FINAL); | |
System.out.println("--------------------------------"); | |
// 会触发类初始化 | |
System.out.println(Inevitable.STATIC_FINAL2); | |
System.out.println("--------------------------------"); | |
// 会触发类初始化 | |
System.out.println(Inevitable2.staticNonFinal); | |
System.out.println("--------------------------------"); | |
//forName 方法获取 Class 对象 | |
Class<?> initable3 = Class.forName(Inevitable3.class.getName()); | |
System.out.println("After creating Inevitable3 ref"); | |
System.out.println(Inevitable3.staticNonFinal); | |
/* | |
执行结果: | |
After creating Inevitable ref | |
47 | |
-------------------------------- | |
Initializing Inevitable | |
258 | |
-------------------------------- | |
Initializing Inevitable2 | |
147 | |
-------------------------------- | |
Initializing Inevitable3 | |
After creating Inevitable3 ref | |
74 | |
*/ | |
} | |
} |
- 从打印结果来看可以发现通过字面常量获取方式来获取
Inevitable
类的Class
对象并没有触发Inevitable
类的初始化,这一点也就验证了前面的分析,同时调用Inevitable.STATIC_FINAL
变量时也没有触发初始化,这是因为static
final
属于编译期静态常量。 - 在编译阶段通过常量传播的方式将
Inevitable
类的常量static
final
储存到一个被称为NotInitialization
类的常量池中,在之后对Inevitable
类常量static
final
的引用都转化为对NotInitialization
类对自身常量池的引用了。 - 所以在编译期后,对编译期常量的引用都将在
NotInitialization
类的常量池中获取,这也就是引用编译期静态常量不会触发Inevitable
类初始化的主要原因。 - 在之后调用的
Inevitable.STATIC_FINAL2
常量后就会触发了Inevitable
类的初始化,注意:STATIC_FINAL2
虽然被static
final
修饰但其值在编译期间并不能被确定,因此STATIC_FINAL2
不是编译期常量,要想使用该常量必须先进行类初始化。 Inevitable2
和Inevitable3
类中都是静态成员变量并非编译期常量,引用都会触发类初始化操作,至于forName
方法获取的Class
对象肯定会触发类初始化操作,这点已在前面分析点击跳转。- 总结:
- 获取
Class
对象引用的三种方式- 第一种:通过继承自
Object
类的getClass
方法获取。 - 第二种:通过
Class
类的静态方法forName
来获取对象引用。 - 第三中:通过
.class
字面常量方式来获取对象引用。
- 第一种:通过继承自
- 其中实例类的
getClass
和Class
类的forName
静态方法都将会触发类的初始化操作,而字面常量获取Class
对象的方式则不会触发类初始化操作。 - 类初始化是类加载的最后一个阶段,准确来说就是在这个阶段后类就会被加载到内存中 (
Class
对象在加载阶段已被创建),此时可以对类进行各种操作了如:new
对象、调用静态成员变量等,注意:在这个阶段才真正开始执行类中定义的Java
程序代码或字节码。
- 获取
# 泛化的 Class 对象引用分析
Class
的引用总数指向某个类的Class
对象,利用Class
对象可以创建实例类,也就足以说明Class
对象的引用指向的对象确切的类型,在JavaSE5
中加入泛型后,可以使我们更方便的用泛型来表示Class
对象更具体的类型,即使在运行期间会被擦除,但编译期可以确保我们使用正确的对象类型。
/** | |
* @Author: LightRain | |
* @Description: 理解 Class 泛型在编译期的用途 | |
* @DateTime: 2023-11-13 01:16 | |
* @Version:1.0 | |
**/ | |
public class ClassDemo { | |
public static void main(String[] args) { | |
// 没有使用泛型 | |
Class strClass = String.class; | |
// 使用泛型的 Class 对象 | |
Class<String> stringClass = String.class; | |
// 没有使用泛型的约束,可以随意赋值 | |
strClass = double.class; | |
// 使用泛型后在编译期将会发生错误,无法通过编译 | |
// stringClass = double.class | |
} | |
} |
- 从上面代码可以看出声明普通的
Class
对象,在编译期间并不会检查Class
对象的确切类型是否符合所需要求,只要在运行时才会被发现。 - 但通过泛型声明并指明类型的
Class
对象,编译器会在编译期对带有泛型的类进行额外的类型检查,确保在编译期保证类型的正确性,从而提高编码效率和降低错误的发生。 - 上述代码中
String.class
就是一个Class<String>
类的对象,但下面的代码可能会令人疑惑。
// 无法通过编译 | |
Class<Number> numberClass=Integer.class; |
Integer
难道不是Number
的子类吗?但事实并没有这么简单,毕竟Integer
的.class
对象并非是Number
的Class
对象的子类,我们可以使用通配符?
来解决此问题,前面提到过,所有的Class
对象都只来源于Class
类。
// 可以通过编译 | |
Class<?> generalClass=Integer.class; | |
generalClass=String.class; | |
generalClass=Long.class; |
这样写的代码并没有问题,毕竟通配符指明的是所有类型都适用,那么为什么不直接使用
Class
还要用Class<?>
呢?这样做是告诉编译器,我们是采用任意类型的泛型而并非忘记使用泛型约束,因此Class<?>
是优于直接使用Class
的,接下来我们来看使用extends
关键字来告诉编译器接收某个类型的子类,来解决前面的Number
和Integer
问题。
// 可以通过编译 | |
Class<?extends Number> clazz=Integer.class; | |
// 赋予其它类型也是可以的,毕竟都是符合属于 Number 的子类 | |
clazz=double.class; | |
clazz=Number.class; |
- 上述代码因为使用了
extends
关键字来告诉编译器,只要是Number
的子类都可以进行赋值操作。 - 这一点与前面直接使用
Class<Number>
的性质是不一样的。 - 应该时刻记住向
Class
引用添加泛型约束是为了提供编译期类型检查,从而避免将错误延续到运行期间的一种手段。
# 类型转换问题
许多情况中我们需要强制使用类型转换,来将当前的类型转换到另一种类型,更多情况我们会使用直接强制类型转换。
/** | |
* 自定义动物接口 | |
* @author LightRain | |
*/ | |
public interface Animal { | |
//... | |
} |
import top.rem.rain.demo.impl.Animal; | |
/** | |
* @Author: LightRain | |
* @Description: 动物 - 猫实现动物接口方法 | |
* @DateTime: 2023-11-13 14:16 | |
* @Version:1.0 | |
**/ | |
public class Cat implements Animal { | |
//... | |
} |
/** | |
* @Author: LightRain | |
* @Description: 动物 - 狗并未实现动物接口方法 | |
* @DateTime: 2023-11-13 14:17 | |
* @Version:1.0 | |
**/ | |
public class Dog { | |
//... | |
} |
import top.rem.rain.demo.impl.Animal; | |
/** | |
* @Author: LightRain | |
* @Description: 强制类型转换示例 | |
* @DateTime: 2023-11-13 13:39 | |
* @Version:1.0 | |
**/ | |
public class ClassDemo2 { | |
public static void main(String[] args) { | |
Animal animal = new Cat(); | |
// 此处 (Cat) 就是属于直接使用强制类型转换 | |
Cat cat = (Cat) animal; | |
} | |
} |
想知道为什么可以强制转换,这全归功于
RTTI
要知道在Java
中,所有类型转换都是在运行时进行正确性检查的,利用RTTI
进行判断类型是否正确,从而保证了强制类型转换的完成,如果:类型转换失败将会抛出ClassCastException
类型转换异常,除了强制转换外在JavaSE5
中新增了一种使用Class
对象进行类型转的方式,代码如下:
import top.rem.rain.demo.impl.Animal; | |
/** | |
* @Author: LightRain | |
* @Description: 强制类型转换示例 | |
* @DateTime: 2023-11-13 13:39 | |
* @Version:1.0 | |
**/ | |
public class ClassDemo2 { | |
public static void main(String[] args) { | |
Animal animal = new Cat(); | |
// 此处 (Cat) 就是属于直接使用强制类型转换 | |
// Cat cat = (Cat) animal; | |
// 这两句等同于 Cat cat = (Cat) animal; | |
Class<Cat> catClass = Cat.class; | |
Cat Cat = catClass.cast(animal); | |
} | |
} |
利用
Class
对象的cast
方法,其参数就是接收一个参数对象并将其转换为Class
引用的类型,这种方式比之前的强制转换稍微麻烦一点,而且当类型不能正确转换时,仍然会抛出ClassCastException
异常,我们来看下源码。
@IntrinsicCandidate | |
public T cast(Object obj){ | |
if(obj!=null&&!isInstance(obj)) | |
throw new ClassCastException(cannotCastMsg(obj)); | |
return(T)obj; | |
} |
# 关键字 instanceof & isInstance 方法
- 关于
instanceof
关键字它返回的是一个boolean
类型的值,来告诉我们对象是不是某个特定的类型实例。 - 在强制转换前利用
instanceof
来对类型检测是否可以转换是很有必要的。 - 如下:使用
instanceof
检测 obj 是不是Animal
类型的实例对象,如果返回true
再进行类型转换,这样就避免了发生ClassCastException
类型转换的异常了。
import top.rem.rain.demo.impl.Animal; | |
/** | |
* @Author: LightRain | |
* @Description: instanceof 示例 | |
* @DateTime: 2023-11-13 14:19 | |
* @Version:1.0 | |
**/ | |
public class InstanceofDemo { | |
public static void main(String[] args) { | |
// 此处来演示 instanceof | |
Dog dog = new Dog(); | |
Cat cat = new Cat(); | |
//Dog 没有实现 Animal 接口所以不属于 Animal 类型此处:false | |
if (dog instanceof Animal) { | |
Animal animal = (Animal) dog; | |
} | |
//Cat 实现了 Animal 接口所以属于 Animal 类型此处:true | |
if (cat instanceof Animal) { | |
Animal animal = (Animal) cat; | |
} | |
} | |
} |
而
isInstance
方法则是Class
类中的一个Native
方法,也是用于来判断对象类型的。
import top.rem.rain.demo.impl.Animal; | |
/** | |
* @Author: LightRain | |
* @Description: instanceof 示例 | |
* @DateTime: 2023-11-13 14:19 | |
* @Version:1.0 | |
**/ | |
public class InstanceofDemo { | |
public static void main(String[] args) { | |
// 此处来演示 instanceof | |
Dog dog = new Dog(); | |
Cat cat = new Cat(); | |
//Dog 没有实现 Animal 接口所以不属于 Animal 类型此处:false | |
if (dog instanceof Animal) { | |
Animal animal = (Animal) dog; | |
} | |
//Cat 实现了 Animal 接口所以属于 Animal 类型此处:true | |
// 此处使用的是 isInstance 方法来判断 | |
if (Animal.class.isInstance(cat)) { | |
Animal animal = (Animal) cat; | |
} | |
} | |
} |
instanceof
和isInstance
方法产生的结果是相同的,对于instanceof
关键字只是被用于对象引用变量,检查左边对象是不是右边类或接口的实例化,如果被检测对象是null
则结果总是false
。
而isInstance
方法则是Class
类的Native
方法,其中 obj 是被测试的对象或变量,如果 obj 是调用这个方法的class
或接口的实例,则返回true
,如果被检测的对象是null
或者基本类型则返回值是false
。
// 判断这个对象是不是这种类型 | |
obj.instanceof(class); | |
// 判断这个对象能不能被转化为这个类 | |
class.inInstance(obj); |
# 反射技术深入理解
- 反射机制是在运行状态下可以获取任意一个类的所有属性和方法,能够调用任意方法和属性。
- 这种动态获取的信息及动态调用对象的方法功能称为
Java
语言中的反射机制。 - 反射技术可以说是
Java
中比较核心的机制,目前大部分框架如:Spring
、SpringBoot
、Mybatis
、Hibernate
等得以实现的支柱。 - 在
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 对象所表示的类的一个新实例 |
package top.rem.rain.demo; | |
import java.io.Serializable; | |
import java.lang.reflect.Constructor; | |
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; | |
} | |
public int getAge() { | |
return age; | |
} | |
public void setAge(int age) { | |
this.age = age; | |
} | |
public String getName() { | |
return name; | |
} | |
public void setName(String name) { | |
this.name = name; | |
} | |
@Override | |
public String toString() { | |
return "User{" + | |
"age=" + age + | |
", name='" + name + '\'' + | |
'}'; | |
} | |
} | |
/** | |
* @Author: LightRain | |
* @Description: Constructor 对象的使用 | |
* @DateTime: 2023-11-13 20:08 | |
* @Version:1.0 | |
**/ | |
public class ConstructorDemo implements Serializable { | |
public static void main(String[] args) throws Exception { | |
Class<?> clazz = null; | |
// 获取 Class 对象的引用 | |
clazz = Class.forName(User.class.getName()); | |
// 第一种方法,实例化默认构造方法,User 必须无参构造函数,否则将抛出异常 | |
User user = (User) clazz.getDeclaredConstructor().newInstance(); | |
user.setAge(18); | |
user.setName("小红"); | |
System.out.println(user); | |
System.out.println("--------------------------------------------"); | |
// 获取带 String 参数的 public 构造函数 | |
Constructor<?> constructor = clazz.getConstructor(String.class); | |
// 创建 User | |
User lightRainUser = (User) constructor.newInstance("LightRain"); | |
lightRainUser.setAge(19); | |
System.out.println("lightRainUser:" + lightRainUser); | |
System.out.println("--------------------------------------------"); | |
// 取得指定带 int 和 String 参数构造函数,该方法是私有构造 private | |
Constructor<?> constructor2 = clazz.getDeclaredConstructor(int.class, String.class); | |
// 由于是 private 必须设置可访问 | |
constructor2.setAccessible(true); | |
// 创建 user 对象 | |
User tomcat = (User) constructor2.newInstance(20, "tomcat"); | |
System.out.println("tomcat:" + tomcat.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(")"); | |
} | |
/* 运行结果: | |
User {age=18, name=' 小红 '} | |
-------------------------------------------- | |
lightRainUser:User {age=19, name='LightRain'} | |
-------------------------------------------- | |
tomcat:User {age=20, name='tomcat'} | |
-------------------------------------------- | |
构造函数 [0]:private top.rem.rain.demo.User (int,java.lang.String) | |
参数类型 [0]:(int,java.lang.String) | |
构造函数 [1]:public top.rem.rain.demo.User (java.lang.String) | |
参数类型 [1]:(java.lang.String) | |
构造函数 [2]:public top.rem.rain.demo.User () | |
参数类型 [2]:() | |
*/ | |
} | |
} |
Constructor
类本身的一些部分常用方法如下:
返回值 | 方法名称 | 说明 |
---|---|---|
Class<T> | getDeclaringClass() | 返回 Class 对象,该对象表示声明由此 Constructor 对象表示的构造方法的类,其实就是返回真实类型(不包含参数) |
Type[] | getGenericParameterTypes() | 按照声明顺序返回一组 Type 对象,返回的就是 Constructor 对象构造函数的形参类型 |
String | getName() | 以字符串形式返回此构造方法的名称 |
Class<?>[] | getParameterTypes() | 按照声明顺序返回一组 Class 对象,即返回 Constructor 对象所表示构造方法的形参类型 |
T | newInstance(Object... initargs) | 使用此 Constructor 对象表示的构造函数来创建新实例 |
String | toGenericString() | 返回描述此 Constructor 的字符串,其中包括类型参数 |
package top.rem.rain.demo; | |
import java.io.Serializable; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.Type; | |
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; | |
} | |
public int getAge() { | |
return age; | |
} | |
public void setAge(int age) { | |
this.age = age; | |
} | |
public String getName() { | |
return name; | |
} | |
public void setName(String name) { | |
this.name = name; | |
} | |
@Override | |
public String toString() { | |
return "User{" + | |
"age=" + age + | |
", name='" + name + '\'' + | |
'}'; | |
} | |
} | |
/** | |
* @Author: LightRain | |
* @Description: Constructor 对象的使用和 Constructor 类本身一些常用方法 | |
* @DateTime: 2023-11-13 20:08 | |
* @Version:1.0 | |
**/ | |
public class ConstructorDemo implements Serializable { | |
public static void main(String[] args) throws Exception { | |
//Class 类与 Constructor 相关的主要方法如下 | |
Class<?> clazz = null; | |
// 获取 Class 对象的引用 | |
clazz = Class.forName(User.class.getName()); | |
// 第一种方法,实例化默认构造方法,User 必须无参构造函数,否则将抛出异常 | |
User user = (User) clazz.getDeclaredConstructor().newInstance(); | |
user.setAge(18); | |
user.setName("小红"); | |
System.out.println(user); | |
System.out.println("--------------------------------------------"); | |
// 获取带 String 参数的 public 构造函数 | |
Constructor<?> constructor = clazz.getConstructor(String.class); | |
// 创建 User | |
User lightRainUser = (User) constructor.newInstance("LightRain"); | |
lightRainUser.setAge(19); | |
System.out.println("lightRainUser:" + lightRainUser); | |
System.out.println("--------------------------------------------"); | |
// 取得指定带 int 和 String 参数构造函数,该方法是私有构造 private | |
Constructor<?> constructor2 = clazz.getDeclaredConstructor(int.class, String.class); | |
// 由于是 private 必须设置可访问 | |
constructor2.setAccessible(true); | |
// 创建 user 对象 | |
User tomcat = (User) constructor2.newInstance(20, "tomcat"); | |
System.out.println("tomcat:" + tomcat.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(")"); | |
} | |
/* 执行结果: | |
User {age=18, name=' 小红 '} | |
-------------------------------------------- | |
lightRainUser:User {age=19, name='LightRain'} | |
-------------------------------------------- | |
tomcat:User {age=20, name='tomcat'} | |
-------------------------------------------- | |
构造函数 [0]:private top.rem.rain.demo.User (int,java.lang.String) | |
参数类型 [0]:(int,java.lang.String) | |
构造函数 [1]:public top.rem.rain.demo.User (java.lang.String) | |
参数类型 [1]:(java.lang.String) | |
构造函数 [2]:public top.rem.rain.demo.User () | |
参数类型 [2]:() | |
*/ | |
//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("-----getGenericString-----"); | |
// 返回描述此 Constructor 的字符串,其中包括类型参数。 | |
System.out.println("getGenericString():" + cs3.toGenericString()); | |
/* 执行结果: | |
-----getDeclaringClass----- | |
构造方法的类:top.rem.rain.demo.User | |
-----getGenericParameterTypes----- | |
参数名称 tp:int | |
参数名称 tp:class java.lang.String | |
-----getParameterTypes----- | |
参数名称:int | |
参数名称:java.lang.String | |
-----getName----- | |
getName:top.rem.rain.demo.User | |
-----getGenericString----- | |
getGenericString ():private top.rem.rain.demo.User (int,java.lang.String) | |
*/ | |
} | |
} |
# 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 的字段,包含继承字段 |
package top.rem.rain.demo; | |
import java.lang.reflect.Field; | |
class Person { | |
public int age; | |
public String name; | |
public int getAge() { | |
return age; | |
} | |
public void setAge(int age) { | |
this.age = age; | |
} | |
public String getName() { | |
return name; | |
} | |
public void setName(String name) { | |
this.name = name; | |
} | |
@Override | |
public String toString() { | |
return "Student{" + | |
"desc='" + desc + '\'' + | |
", score=" + score + | |
", age=" + age + | |
", name='" + name + '\'' + | |
'}'; | |
} | |
} | |
class Student extends Person { | |
public String desc; | |
private int score; | |
public String getDesc() { | |
return desc; | |
} | |
public void setDesc(String desc) { | |
this.desc = desc; | |
} | |
public int getScore() { | |
return score; | |
} | |
public void setScore(int score) { | |
this.score = score; | |
} | |
@Override | |
public String toString() { | |
return "Student{" + | |
"desc='" + desc + '\'' + | |
", score=" + score + | |
", age=" + age + | |
", name='" + name + '\'' + | |
'}'; | |
} | |
} | |
/** | |
* @Author: LightRain | |
* @Description: Field 与 Class 相关方法使用 | |
* @DateTime: 2023-11-13 22:47 | |
* @Version:1.0 | |
**/ | |
public class FieldDemo { | |
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException { | |
//Field 与 Class 相关方法使用 | |
Class<?> clazz = Class.forName(Student.class.getName()); | |
// 获取指定字段名称的 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 top.rem.rain.demo.Person.age | |
f:class top.rem.rain.demo.Student | |
f:class top.rem.rain.demo.Person | |
f:class top.rem.rain.demo.Person | |
================getDeclaredFields==================== | |
f2:class top.rem.rain.demo.Student | |
f2:class top.rem.rain.demo.Student | |
field2:public java.lang.String top.rem.rain.demo.Student.desc | |
*/ | |
} | |
} |
需要注意的是上面的代码如果我们不期望获取其父类的字段,则需要使用
Class
类的getDeclaredField
或getDeclaredFields
方法来获取字段即可,倘若需要获取到父类字段,那么就需要使用Class
类的getField
和getFields
方法,但是也只能获取到public
修饰的字段,无法获取到父类的private
修饰的字段,下面将通过Field
类本身方法对指定类属性赋值的代码。
package top.rem.rain.demo; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.InvocationTargetException; | |
class Person { | |
public int age; | |
public String name; | |
public int getAge() { | |
return age; | |
} | |
public void setAge(int age) { | |
this.age = age; | |
} | |
public String getName() { | |
return name; | |
} | |
public void setName(String name) { | |
this.name = name; | |
} | |
@Override | |
public String toString() { | |
return "Person{" + | |
"age=" + age + | |
", name='" + name + '\'' + | |
'}'; | |
} | |
} | |
class Student extends Person { | |
public String desc; | |
private int score; | |
public String getDesc() { | |
return desc; | |
} | |
public void setDesc(String desc) { | |
this.desc = desc; | |
} | |
public int getScore() { | |
return score; | |
} | |
public void setScore(int score) { | |
this.score = score; | |
} | |
@Override | |
public String toString() { | |
return "Student{" + | |
"desc='" + desc + '\'' + | |
", score=" + score + | |
", age=" + age + | |
", name='" + name + '\'' + | |
'}'; | |
} | |
} | |
/** | |
* @Author: LightRain | |
* @Description: Field 类本身的方法使用 | |
* @DateTime: 2023-11-13 22:47 | |
* @Version:1.0 | |
**/ | |
public class FieldDemo { | |
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { | |
//Field 类本身的方法使用 | |
// 获取 Class 对象引用 | |
Class<?> clazz = Class.forName(Student.class.getName()); | |
Student st = (Student) clazz.getDeclaredConstructor().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()); | |
// 获取字段值 | |
System.out.println(scoreField.get(st)); | |
/* | |
执行结果: | |
Student {desc='I am student', score=88, age=18, name='Lily'} | |
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 方法 |
package top.rem.rain.demo; | |
import java.lang.reflect.Method; | |
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; | |
} | |
} | |
/** | |
* @Author: LightRain | |
* @Description: Method 与 Class 相关方法使用 | |
* @DateTime: 2023-11-13 23:49 | |
* @Version:1.0 | |
**/ | |
public class MethodDemo { | |
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException { | |
Class<?> clazz = Class.forName(Circle.class.getName()); | |
// 根据参数获取 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 top.rem.rain.demo.Shape.draw (int,java.lang.String) | |
m::public int top.rem.rain.demo.Circle.getAllCount () | |
m::public void top.rem.rain.demo.Shape.draw () | |
m::public void top.rem.rain.demo.Shape.draw (int,java.lang.String) | |
m::public final void java.lang.Object.wait (long,int) throws java.lang.InterruptedException | |
m::public final void java.lang.Object.wait () throws java.lang.InterruptedException | |
m::public final native void java.lang.Object.wait (long) 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 top.rem.rain.demo.Circle.drawCircle () | |
m1::private void top.rem.rain.demo.Circle.drawCircle () | |
m1::public int top.rem.rain.demo.Circle.getAllCount () | |
*/ | |
} | |
} |
通过
getMethod
方法获取Method
对象时,也会获取到其父类中的方法,上面的执行结果中把Object
类的方法都打印了出来,而getDeclaredMethod
getDeclaredMethods
方法都只能获取当前类的方法。 下面是通过Method
对象调用指定类的方法。
package top.rem.rain.demo; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
class Shape2 { | |
public void draw() { | |
System.out.println("draw"); | |
} | |
public void draw(int count, String name) { | |
System.out.println("draw " + name + ",count=" + count); | |
} | |
} | |
class Circle2 extends Shape2 { | |
private void drawCircle() { | |
System.out.println("drawCircle"); | |
} | |
public int getAllCount() { | |
return 100; | |
} | |
} | |
/** | |
* @Author: LightRain | |
* @Description: 通过 Method 对象调用指定类的方法 | |
* @DateTime: 2023-11-14 00:02 | |
* @Version:1.0 | |
**/ | |
public class MethodDome2 { | |
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { | |
Class<?> clazz = Class.forName(Circle2.class.getName()); | |
// 创建对象 | |
Circle2 circle = (Circle2) clazz.getDeclaredConstructor().newInstance(); | |
// 获取指定参数的方法对象 Method | |
Method method = clazz.getMethod("draw", int.class, String.class); | |
// 通过 Method 对象的 invoke (Object obj,Object... args) 方法调用 | |
method.invoke(circle, 18, "小雨"); | |
// 对私有无参方法的操作 | |
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=18 | |
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 对象所表示的方法的形参类型,即返回方法的参数类型组成的数组 |
Typep[] | getGenericParameterTypes() | 按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型,也是返回方法的参数类型 |
String | getName() | 以 String 形式返回此 Method 对象表示的方法名称,即返回方法的名称 |
boolean | isVarArgs() | 判断方法是否带有可变参数,如果将此方法声明为带有可变数量的参数,则返回 true 否则返回 false |
String | toGenericString() | 返回描述此 Method 的字符串,包括类型参数 |
getReturnType
和getGenericReturnType
方法都是获取Method
对象表示的方法的返回类型,只不过前者返回的Class
类型,后者返回的是Type
,Type
只是一个接口而已,在Java8
中新增了一个默认的方法实现,返回的就是参数类型信息。
package java.lang.reflect; | |
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 top.rem.rain.demo; | |
import java.lang.reflect.Array; | |
/** | |
* @Author: LightRain | |
* @Description: Array 常用方法使用 | |
* @DateTime: 2023-11-14 15:50 | |
* @Version:1.0 | |
**/ | |
public class ArrayDemo { | |
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); | |
/* | |
执行结果: | |
1,2,3,4,5,6,7,8,9,0,0,0,0,0,0, | |
hello world! | |
*/ | |
} | |
} |
上述代码演示确实可以利用
Array
类和反射结合动态创建数组,也可以在运行时动态获取和设置数组中元素的值,其实除了以上的set
get
外Array
还专门为八种基本类型提供了特有的方法,如:setInt
、getInt
、setBoolean
、getBoolean
等,需要时查看 API 文档即可,除了上述动态修改数组长度或者动态创建数组和动态获取值和设置值外,可以利用泛型动态创建泛型数组代码如下:
/** | |
* @Author: LightRain | |
* @Description: Array 常用方法使用 | |
* @DateTime: 2023-11-14 15:50 | |
* @Version:1.0 | |
**/ | |
public class ArrayDemo { | |
/** | |
* 接收一个泛型数组,创建一个长度与接收的数组长度一样的泛型数组,并把接收的数组的元素复制到新创建的数组中,最后找出新数组中的最小元素,并打印出来 | |
* @param a 泛型数组 | |
* @param <T> 泛型 | |
*/ | |
public static <T extends Comparable<T>> void min(T[] a) { | |
// 通过反射创建相同类型的数组 | |
T[] b = (T[]) Array.newInstance(a.getClass().getComponentType(), a.length); | |
System.arraycopy(a, 0, b, 0, a.length); | |
T min = null; | |
boolean flag = true; | |
for (T t : b) { | |
if (flag) { | |
min = t; | |
flag = false; | |
} | |
if (t.compareTo(min) < 0) { | |
min = t; | |
} | |
} | |
System.out.println(min); | |
} | |
} |
毕竟我们无法直接创建泛型数组,有了
Array
的动态创建数组的方法这个文件也就迎刃而解了。
// 无法通过编译 | |
T[] a = new T[]; |
到这里反射的几个重要常用的就基本介绍完了,但更重要的是我们应该认识到反射机制并没有什么神奇之处,当通过反射与一个未知类型的对象打交道时,
JVM
只会简单的检查这个对象,判断该对象属于哪种类型,同时也应该知道,在使用反射机制创建对象前,必须确保已经加载了这个类的Class
对象,当然这点完全不必由我们来操作,毕竟只能由JVM
来加载,但必须确保该类的.class
文件已存在并且JVM
能够找到。
关于Class
类的方法在前面我们只分析了主要的一些方法,下面仅列出前面没有介绍过又可能用到的 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(); |