Java笔记40
创建运行时类的对象
获取运行时类的完整结构
- 我们可以通过反射来获取运行时类的完整结构,这包括:
- 实现的全部接口(Interface)
- 所继承的父类(Superclass)
- 全部的构造器(Constructor)
- 全部的方法(Method)
- 全部的属性/字段(Field)
- 注解(Annotation)
- ……
- 下面我们写一段代码来练习一下获取类的信息:
- 首先我们创建一个类:
Dog.java
package com.clown.reflection;
public class Dog {
//属性
public String name;
private int age;
private String ownerName;
//无参构造
public Dog() {
}
//有参构造
public Dog(String name, int age, String ownerName) {
this.name = name;
this.age = age;
this.ownerName = ownerName;
}
//get() & set()
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getOwnerName() {
return ownerName;
}
public void setOwnerName(String ownerName) {
this.ownerName = ownerName;
}
//重写 toString()
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
", ownerName='" + ownerName + '\'' +
'}';
}
public void run() {
System.out.println(this.getName() + "正在奔跑");
}
protected void eat() {
System.out.println(this.getName() + "正在吃东西");
}
private void pee() {
System.out.println(this.getName() + "正在尿尿");
}
}
- 然后我们来尝试获取
Dog
类中的信息:
Test08.java
package com.clown.reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
//获得类的信息
public class Test08 {
public static void main(String[] args) throws Exception {
Class c1 = Class.forName("com.clown.reflection.Dog");
//获得类的名字
System.out.println("=============================== 获得类的名字 ===============================");
System.out.println("包名+类名: " + c1.getName()); //getName(); 获得包名 + 类名
System.out.println("--------------------------------------------------------------");
System.out.println("类名: " + c1.getSimpleName()); //getSimpleName(); 获得类名
//获得类的属性
System.out.println("=============================== 获得类的属性 ===============================");
Field[] fields = c1.getFields(); //getFields(); 获得本类的所有 public属性
for (Field field : fields) {
System.out.println("public属性: " + field);
}
System.out.println("--------------------------------------------------------------");
fields = c1.getDeclaredFields(); //getDeclaredFields(); 获得本类的所有属性
for (Field field : fields) {
System.out.println("所有属性: " + field);
}
System.out.println("--------------------------------------------------------------");
Field name = c1.getDeclaredField("age"); //getDeclaredField(String name); 获得本类中指定的属性
System.out.println("指定属性: " + name);
//获得类的方法
System.out.println("=============================== 获得类的方法 ===============================");
Method[] methods = c1.getMethods(); //getMethods(); 获得本类及其父类的所有 public方法
for (Method method : methods) {
System.out.println("public方法: " + method);
}
System.out.println("--------------------------------------------------------------");
methods = c1.getDeclaredMethods(); //getDeclaredMethods(); 获得本类的所有方法
for (Method method : methods) {
System.out.println("所有方法: " + method);
}
System.out.println("--------------------------------------------------------------");
Method getName = c1.getMethod("getName", null); //getMethod("XXX", XXX); 获得本类中指定的方法
System.out.println("指定方法: " + getName);
Method setName = c1.getMethod("setName", String.class);
System.out.println("指定方法: " + setName);
/*
getMethod(String name, Class<?>... parameterTypes); 获得本类中指定的方法
参数:
name参数是一个 String,它指定了所需方法的简单名称。
parameterTypes参数是以声明顺序标识方法的形式参数类型的类对象的数组。
如果 parameterTypes是 null ,它被视为一个空数组。
*/
//获得类的构造器
System.out.println("============================== 获得类的构造器 ==============================");
Constructor[] constructors = c1.getConstructors(); //getConstructors(); 获得本类的 public构造器
for (Constructor constructor : constructors) {
System.out.println("public构造器: " + constructor);
}
System.out.println("--------------------------------------------------------------");
constructors = c1.getDeclaredConstructors(); //getDeclaredConstructors(); 获得本类的所有构造器
for (Constructor constructor : constructors) {
System.out.println("所有构造器: " + constructor);
}
System.out.println("--------------------------------------------------------------");
Constructor constructor = c1.getConstructor(String.class, int.class, String.class); //getConstructor(xxx) 获得本类中指定的构造器
System.out.println("指定构造器: " + constructor);
/*
getConstructor(Class<?>... parameterTypes); 获得本类中指定的构造器
参数:
parameterTypes参数是以声明顺序标识构造函数的形式参数类型的类对象的数组
*/
}
}
- 运行结果:
获取了Class对象,通过反射我们能做什么?
一、反射创建类的对象
-
调用
Class
对象的newlnstance()
方法- (1)类必须有一个无参数的构造器。
- (2)类的构造器的访问权限需要足够
-
思考:难道没有无参的构造器就不能创建对象了吗?
-
只要在操作的时候明确的调用类中的构造器,调用构造器对象的
newInstance(Object ... initargs)
方法,并将参数传递进去,就可以实例化对象了。具体步骤如下:- (1)通过
Class
类的getConstructor(Class<?>... parameterTypes)
取得本类的指定形参类型的构造器 - (2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
- (3)通过构造器(
Constructor
)实例化对象
- (1)通过
二、反射调用类中的方法
-
通过
Method
类完成。- 通过
Class
类的getMethod(String name, Class<?>... parameterTypes)
方法取得一个Method
对象,并设置此方法操作时所需要的参数类型。 - 之后使用
public Object invoke(Object obj, Object... args)
方法进行调用,并向方法中传递要设置的obj
对象的参数信息。
- 通过
-
Object invoke(Object obj, Object … args)
Object
对应原方法的返回值,若原方法无返回值,此时返回null
- 若原方法为静态方法,此时形参
Object obj
可为null
- 若原方法形参列表为空,则
Object[] args
为null
- 若原方法声明为
private
,则需要在调用此invoke()
方法前,显式调用方法对象的setAccessible(true)
方法,将可访问private
的方法。
-
setAccessible(boolean flag)
Method
、Fied
和Constructor
对象都有setAccessible()
方法。setAccessible()
的作用是启动和禁用访问安全检查的开关。- 参数值为
true
则指示反射的对象在使用时应该取消 Java 语言访问检查。- 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为
true
。 - 使得原本无法访问的私有成员也可以访问。
- 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为
- 参数值为
false
则指示反射的对象应该实施 Java 语言访问检查。
三、反射操作类中的属性
-
通过
Field
类完成。- 通过
Class
类的getDeclaredField(String name)
方法取得一个Field
对象。 - 之后使用
public void set(Object obj, Object value)
方法将obj
对象参数上的此Field
对象表示的属性设置为指定的新值。
- 通过
-
下面我们来写一段程序来测试一下通过反射创建类的对象、调用类中的方法以及操作类中的属性。我们使用上面创建的
Dog
类创建Class
对象:
package com.clown.reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test09 {
public static void main(String[] args) throws Exception {
//获得 Class对象
Class c1 = Class.forName("com.clown.reflection.Dog");
//通过反射,动态的创建对象
//通过调用 Class对象的 newInstance()创建对象
System.out.println("================== 通过Class对象的newInstance()方法创建对象 ==================");
//newInstance(); 创建由此类对象表示的类的新实例
// Object obj = c1.newInstance();
// Dog dog1 = (Dog)obj;
Dog dog1 = (Dog)c1.newInstance(); //本质上是调用了 Dog类的无参构造器
System.out.println(dog1);
//通过构造器对象的 newInstance(Object ... initargs)方法创建对象
System.out.println("=============== 通过构造器对象的newInstance(参数值)方法创建对象 ===============");
Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, String.class);
//newInstance(Object ... initargs); 使用由此 Constructor对象表示的构造函数,用指定的初始化参数创建和初始化构造函数的声明类的新实例
Dog dog2 = (Dog)constructor.newInstance("旺财", 5, "张三");
System.out.println(dog2);
//通过反射调用普通方法
System.out.println("============================= 通过反射调用方法 ==============================");
Dog dog3 = (Dog)c1.newInstance();
Method setName = c1.getDeclaredMethod("setName", String.class);
//invoke(Object obj, Object... args); 在具有指定参数的 Method对象上调用此 Method对象表示的基础方法。
//参数: obj - 从底层方法被调用的对象 args - 用于方法调用的参数值
setName.invoke(dog3, "大黄");
System.out.println(dog3.getName());
Method pee = c1.getDeclaredMethod("pee", null);
//注意: 不能直接操作私有(private)的方法或属性,我们需要先关闭程序的安全检测 - setAccessible(true)
//setAccessible(boolean flag); 将此对象的 accessible标志设置为指定的布尔值
//当值为 true时表示反射对象应该在使用时抑制程序的的安全检查。 值为 false时表示反射的对象应该强制执行程序的的安全检查。
pee.setAccessible(true);
pee.invoke(dog3, null);
//通过反射操作属性
System.out.println("============================= 通过反射操作属性 ==============================");
Dog dog4 = (Dog)c1.newInstance();
Field age = c1.getDeclaredField("age");
age.setAccessible(true); //关闭程序的安全检测
//set(Object obj, Object value); 将指定对象参数上的此 Field对象表示的字段设置为指定的新值
age.set(dog4, 8);
System.out.println(dog4.getAge());
}
}
- 运行结果:
使用不同方式调用方法的性能测试
- 在上面我们说过使用
setAccessible(boolean flag)
方法关闭程序的检测能提高反射的效率,那么是否真是如此呢?又大概提高了多少呢?下面我们就写一段程序来测试一下使用不同的方式调用方法对性能的影响:
package com.clown.reflection;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
//测试性能
public class Test10 {
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
test01();
test02();
test03();
}
//普通方式调用
public static void test01() {
Dog dog = new Dog();
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
dog.getName();
}
long endTime = System.currentTimeMillis();
System.out.println("普通方式执行10亿次的时间: " + (endTime - startTime) + "毫秒");
}
//反射方式调用
public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Dog dog = new Dog();
//获取 Class对象
Class c1 = dog.getClass();
//获取 Method对象
Method getName = c1.getDeclaredMethod("getName", null);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(dog, null); //调用方法
}
long endTime = System.currentTimeMillis();
System.out.println("反射方式执行10亿次的时间: " + (endTime - startTime) + "毫秒");
}
//反射方式调用,关闭检测
public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Dog dog = new Dog();
//获取 Class对象
Class c1 = dog.getClass();
//获取 Method对象
Method getName = c1.getDeclaredMethod("getName", null);
getName.setAccessible(true); //关闭检测
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(dog, null); //调用方法
}
long endTime = System.currentTimeMillis();
System.out.println("反射方式并关闭检测执行10亿次的时间: " + (endTime - startTime) + "毫秒");
}
}
- 运行结果:
四、拓展:反射操作泛型
- Java 采用泛型擦除的机制来引入泛型,Java 中的泛型仅仅是给编译器 Javac 使用的,确保数据的安全性和免去强制类型转换问题,但是,一旦编译完成,所有和泛型有关的类型全部擦除。
- 为了通过反射操作这些类型,Java新增了
ParameterizedType
,GenericArrayType
,TypeVariable
和WildcardType
几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。ParameterizedType
:表示一种参数化类型,比如Collection<String>
。GenericArrayType
:表示一种元素类型是参数化类型或者类型变量的数组类型。TypeVariable
:是各种类型变量的公共父接口。WildcardType
:代表一种通配符类型表达式。
- 下面我们编写代码来练习一下如何通过反射来获取泛型:
package com.clown.reflection;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
//通过反射获取泛型
public class Test11 {
public static void main(String[] args) throws NoSuchMethodException {
System.out.println("=========================== 获取方法test01()的形式参数类型 ============================");
Method method1 = Test11.class.getDeclaredMethod("test01", Map.class, List.class);
//getGenericParameterTypes(); 返回一个 Type对象的数组,Type[]以声明顺序表示由该 Method对象表示的方法的形式参数类型
Type[] genericParameterTypes = method1.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println("test01()方法的形式参数的类型: " + genericParameterType);
//ParameterizedType接口: 表示一个泛型,如 Collection <String>
if (genericParameterType instanceof ParameterizedType) { //判断该形式参数是否为泛型
//getActualTypeArguments(); 返回一个表示此类型的实际类型参数的数组 Type对象
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("此泛型的实际类型参数类型: " + actualTypeArgument);
}
}
}
System.out.println("============================ 获取方法test02()的返回值类型 ============================");
Method method2 = Test11.class.getDeclaredMethod("test02", null);
//getGenericReturnType(); 返回一个 Type对象,它表示由该 Method对象表示的方法的正式返回类型
Type genericReturnType = method2.getGenericReturnType();
System.out.println("test02()方法的返回值的类型: " + genericReturnType);
if (genericReturnType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("此泛型的实际类型参数类型: " + actualTypeArgument);
}
}
}
//定义一个方法,它的形式参数为泛型
public void test01(Map<String, Dog> map, List<Dog> list) {
System.out.println("test01");
}
//定义一个方法,它的返回值类型类型为泛型
public Map<String, Dog> test02() {
System.out.println("test02");
return null;
}
}
- 运行结果:
了解什么是ORM
- Object Relationship Mapping --> 对象关系映射
- 类和表结构对应
- 属性和字段对应
- 对象和记录对应
- 要求:利用注解和反射完成类和表结构的映射关系
五、反射操作注解
- 我们知道通过
Class
类的getAnnotations()
方法可以获取该类的所有注解。 - 但是怎样才能获取类中指定的注解呢?
- 例如:我们想获得类中某个属性(
Filed
)的注解,那我们只需要先获得该属性的Filed
对象,然后再使用该Filed
对象的getAnnotation(Class<A> annotationClass)
方法,并传入我们想要获取的注解的Class
对象,即可获得该注解。同理,若我们想获得某个类(Class
)/方法(Method
)等的指定的注解也是使用getAnnotation(Class<A> annotationClass)
方法。
package com.clown.reflection;
import java.lang.annotation.*;
import java.lang.reflect.Field;
//通过反射操作注解
//ORM
public class Test12 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
//获得 Class对象
Class c1 = Class.forName("com.clown.reflection.Student2");
//通过反射获得注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//获得注解的 value的值
TableClown tableClown = (TableClown) c1.getAnnotation(TableClown.class);
String value = tableClown.value();
System.out.println(value);
System.out.println("===================================================");
//获得类中的指定的注解
Field f = c1.getDeclaredField("id");
FiledClown filedClown = f.getAnnotation(FiledClown.class);
System.out.println(filedClown);
//获取并打印注解的参数的值
System.out.println(filedClown.columnName());
System.out.println(filedClown.type());
System.out.println(filedClown.length());
}
}
//创建一个实体类 --> 学生类
@TableClown("db_student")
class Student2 {
//属性
@FiledClown(columnName = "bd_id", type = "int", length = 10)
private int id;
@FiledClown(columnName = "bd_age", type = "int", length = 3)
private int age;
@FiledClown(columnName = "bd_name", type = "varchar", length = 4) //在数据库中,"String" 一般用 "varchar" 表示
private String name;
//无参构造
public Student2() {
}
//有参构造
public Student2(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
//get() & set()
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
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;
}
//重写 toString()
@Override
public String toString() {
return "Student2{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
//自定义创建一个【类】的注解
@Target(ElementType.TYPE) //@TableClown可以放在【类】上
@Retention(RetentionPolicy.RUNTIME) //@TableClown在源码时、编译为 class时以及运行时都有效
@interface TableClown {
String value(); //参数: 数据库名
}
//自定义创建一个【属性】的注解
@Target(ElementType.FIELD) //@FiledClown可以放在【属性】上
@Retention(RetentionPolicy.RUNTIME) //@FiledClown在源码时、编译为 class时以及运行时都有效
@interface FiledClown {
String columnName(); //参数: 列名
String type(); //参数: 类型
int length(); //参数: 长度
}
- 运行结果: