框架学习必备!java基础学习之《反射》
动态语言和静态语言
动态语言
- 是一类运行时可以改变结构的语言,例如新的函数,对象,甚至代码可以被引进,已有的函数可以被删除或者是其它结构上的变化
- 主要动态语言:Objcet-C,C#,JavaScript,PHP,Python
静态语言
- 运行时结构不可改变的语言,Java,C,C++
- Java不是动态语言,但是可以称之为准动态语言,具有一定的动态性,我们可以通过反射机制获得类似动态语言的特性,时代码更加灵活
反射定义
反射能够分析类能力的程序叫做反射
反射机制可以用来:
- 在运行时分析类的能力
- 在运行时检查对象,例如,编写一个适用于所有类的tostring方法
- 实现泛型数组操作代码
- 利用Method对象,这个对象很像C++中的函数指针
Class类
在程序运行期间,java运行时系统始终为所有对象维护一个运行时类型标识,这个信息会跟踪每个对象所属的类,虚拟机利用运行时类型信息选择要执行的正确方法。而使用一个特殊的java类来访问这些信息,保存这些信息的类名为Class,Object类中的getClass()方法可以返回一个Class类型的实例。请看如下代码:
编写一个父类Student:
public class Student {
private String name;
private String school;
private String sex;
private int age;
}
编写一个子类XiaoMing继承Student:
public class XiaoMing extends Student{
private String home;
private String phone;
}
编写测试类主函数:
import java.util.Random;
public class Test {
/**
* 反射机制的测试
* @param args
*/
public static void main(String[] args) {
Student s = new Student();
Class<Student> aClass = s.getClass();
System.out.println(aClass.getName());
//输出“Student”
System.out.println(aClass);
//输出“class Student”
Student s2=new XiaoMing();
Class aClass2 = s2.getClass();
System.out.println(aClass2.getName());
//输出“XiaoMing”
System.out.println(aClass2);
//输出“class XiaoMing”
//如果对象在一个包里,那么包名也可以作为类名的一部分
Random random=new Random();
Class cl=random.getClass();
System.out.println(cl.getName());
//输出java.util.Random
//还可以使用静态方法Class.formName()获得类名对应的Class对象
String name="java.util.Random";
try {
Class c=Class.forName(name);
System.out.println(c);
//输出class java.util.Random,代表获得这个包下对应类的Class
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
获得Class类其实有另一种更加便捷的方法,如果T是任意类型的java类型(或者void关键字),T.Class就是代表匹配的类对象。
//Class其实是个泛型类,只不过我们将他省略了
Class c3=Random.class;
System.out.println(c3);
//输出java.util.Random
Class c4=int.class;
System.out.println(c4);
//输出int
虚拟机为每个类型管理一个唯一的Class对象,可以用运算符”==“来实现两个类的对象比较:
同样是上面的例子,看如下代码
public class Test2 {
public static void main(String[] args) {
Student student=new Student();
Student student1=new XiaoMing();
System.out.println(student.getClass()==Student.class);//输出true
System.out.println(student1.getClass()==XiaoMing.class);//输出true
System.out.println(student1.getClass()==Student.class);//输出false
/**
*1.instanceof 的左右两边必须是引用类型,java 八大基本数据类型无法使用 instanceof 来进行对比
2.instanceof 用来判定左边的引用类型是否与右边的引用类型的类型是否相同,或左边引用类型是右边引用类型的子类或实现类(右边引用类型可以是类、抽象 类、接口)
3.instanceof 的对比结果为 boolean 类型,如果左右两边比对成功,返回 true ;否则返回 flase
4.null 与任何引用类型进行 instanceof 对比的结果都是 flase,null 不属于任何类型,更不属于 object 基类的派生类(子类),需要特别注意
*/
System.out.println(student1 instanceof Student);//输出true
XiaoMing xiaoMing=new XiaoMing();
System.out.println(xiaoMing.getClass()==XiaoMing.class);//输出true
/**
* 思考:那么, System.out.println(xiaoMing.getClass()==Student.class);会输出什么呢?
*/
}
}
分析:
- 父类声明,父类实现变量student,当打印是否和Student.class相等时,因为是父类型的对象,应该打印true
- 父类声明,子类实现变量student1,与instanceof不同的是,当与Student比较时,会输出false,应为每一个类型是唯一的Class对象,子类实现,那么就是子类的类型,与父类进行比较,那么一定会输出false
- 子类声明,子类实现xiaoMing,与XiaoMing比较时,一定会输出true
思考题:
会报错,类型无法比较,其实运算符左边返回的是Class,但是运算符右边是Class,Class是一个泛型类,只不过我们通常不写,在这里进行比较时,泛型变量不一致,所以无法比较
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yom3TZPA-1627018937020)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210721122720970.png)]
创建类实例的另一种办法
如果又一个Class类型的对象,可以用它来创造实例,看一下代码
import java.lang.reflect.InvocationTargetException;
public class Test3 {
public static void main(String[] args) {
/**
* 创建类实例的另一种方法
*/
String s="Student";
Object student=null;
try {
Class cl=Class.forName(s);
student=cl.getConstructor().newInstance();
} catch (ClassNotFoundException | NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
//判断是否真的创建了一个Student实例
System.out.println(student.getClass()==Student.class);//输出true
}
}
可以通过Class.formName来找到Class对象,再通过getConstrutor来创建一个Constructor对象,再用newInstance方法来实例化对象
一个类在内存中只有一个Class
public class test {
public static void main(String[] args) throws ClassNotFoundException {
Class c1 = Class.forName("User");
System.out.println(c1);
Class c2 = Class.forName("User");
Class c3 = Class.forName("User");
Class c4 = Class.forName("User");
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
System.out.println(c4.hashCode());
}
}
class User{
private String name;
private int id;
private int age;
public User(){
}
(此处为了简洁,省略有参构造方法以及get,set方法,请读者务必写上)
}
//输出class User
//1163157884
//1163157884
//1163157884
利用反射分析类的能力
三个类:
- Field:这个类描述被分析类的实例字段
- Method:这个类描述被分析类的类方法
- Construtor:这个类描述被分析类的构造器
例如:
Field[] field=Student.class.getDeclaredFields();
for (Field f:
field) {
System.out.println(f.getName().toString());
}
/**
* 输出
* name
* school
* sex
* age
*/
具体可以查看api文档
调用任何方法和设置属性值
这一块内容在学习Spring时需要用到,而且底层基本上不会看见new这个关键字,所以我整理了一部分,按照这样的方法将field,method,和Construtor都自己学一遍是很有必要的,所以,请认真体会并且一定要实操,否则过目则忘!
User
public class User {
private String name;
public String sex;
public User() {
}
(此处为了简洁,省略有参构造方法以及get,set方法,请读者务必写上)
}
通过反射获取对象的属性以及直接对其属性赋值
import java.lang.reflect.Field;
public class Main {
/**
* 关于对象属性的操作需要掌握的几个方法以及传参的意义
*/
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchFieldException {
User user=User.class.newInstance(); //new一个新对象,利用无参构造器
System.out.println(user);//输出“User{name='null', sex='null'}”,此时对象是空
/**
* 测试getDeclaredField方法
*/
Field field=User.class.getDeclaredField("name");//获得指定参数名属性的Filed,private 和 public 都可以获取到
field.setAccessible(true);//可以理解为将对象的属性设置为可编辑,不然会抛出一个IllegalAccessException异常提示:modifiers "private"
field.set(user,"芜湖");//将用无参构造器构造出来的user设置name属性值为”芜湖“
System.out.println(user);//此时输出为:{name='芜湖', sex='null'}
System.out.println(field);//输出“private java.lang.String User.name”
System.out.println(field.getName());//输出“name”,只输出名字
/**
* 测试getField方法
*/
Field field1=User.class.getField("sex");//获得指定参数名属性的Filed,只能获取public
field1.set(user,"男");//此时输出为User{name='芜湖', sex='男'},表名以及成功赋值
System.out.println(user);
/**
* 测试getDeclaredFields方法
*/
Field[] fields = User.class.getDeclaredFields();//获得对象类中的全部属性,private 和 public 都可以获取到
Field[] fields1 = User.class.getFields();//获得对象类中的公共属性,只能获取 public
System.out.println(field1);//输出“public java.lang.String User.sex”
System.out.println(field1.getName());//输出“sex”
for (Field field2 : fields) {
/* 输出:
name
sex
*/
System.out.println(field2.getName());
}
for (Field field2 : fields1) {
/*
输出:
sex
*/
System.out.println(field2.getName());
}
}
}
通过反射机制调用方法
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Main2 {
/**
* 关于对象的方法需要掌握的一些方法以及使用的技巧
*/
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
User user=User.class.newInstance();
/**
* 测试 getMethod 方法,此方法第一个参数为对象中的方法名,第二个参数为需要传入的值的类型class对象,例如传入String就是String.class,如果不需要传入就输入null
* 执行方法需要使用获取到的Method中的invoke方法,返回对应的参数
* invoke第一个参数是对象的实例,第二个参数是传入的值
* 每次执行invoke都有返回值,返回值是Object,如果是void函数,那么返回的是个null,否则为该方法的实际返回值
*/
Method method=user.getClass().getMethod("getName",null);
Method method1=user.getClass().getMethod("setName", String.class);
System.out.println(method);//输出”public java.lang.String User.getName()“
Object invoke = method.invoke(user, null);
System.out.println(invoke);//输出”null“
Object a = method1.invoke(user, "芜湖");
System.out.println(a);//输出”null“
System.out.println(user);//输出”User{name='芜湖', sex='null'}“
}
}
打印操作示例代码
import java.lang.reflect.Method;
public class Test03 {
public static void main(String[] args) throws ClassNotFoundException {
Class c1 = Class.forName("pojo");
Method[] declaredMethods = c1.getDeclaredMethods();//本类中所有的方法甚至私有方法的也能打印
for(Method method:declaredMethods)
{
System.out.println(method.toString());
}
System.out.println("===============================================");
Method[] methods = c1.getMethods();//本类中所有的方法以及父类中所有的方法,但是都只能打印public方法,不能打印私有方法
for(Method method:methods)
{
System.out.println(method.toString());
}
/**
* 类似的还有Field,Construtor都是一样的用法,这里就不一一赘述,declared作为前缀的都是可以打印私有方法的
*/
}
}
class pojo{
private int a;
private int b;
private int c;
(此处为了简洁,省略有参,无参构造方法以及get,set方法,请读者务必写上)
}
反射操作注解
ORM:对象映射关系,例如
class Student{
int id; String name;
int age;
}
对应表
利用反射和注解完成类和表结构的映射关系
综合代码:
import java.lang.annotation.*;
import java.lang.reflect.Field;
public class Test04 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
//获得Class
Class c1=Class.forName("student");
//通过反射机制获得类注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation.toString());//输出:”@TableAnnotation(value=db_student)“
}
//通过反射机制获得类注解的value值
TableAnnotation annotation = (TableAnnotation)c1.getAnnotation(TableAnnotation.class);
System.out.println(annotation.value());//输出:”db_student“
//通过反射机制获取类中属性的注解
Field[] declaredFields = c1.getDeclaredFields();//这是获取全部属性
for (Field field : declaredFields) {
FieldAnnotation annotation1 = field.getAnnotation(FieldAnnotation.class);
System.out.println(annotation1);
/*
输出:
@FieldAnnotation(columnName=id, columnType=int, length=10)
@FieldAnnotation(columnName=name, columnType=varchar, length=10)
@FieldAnnotation(columnName=age, columnType=int, length=10)
*/
}
Field name = c1.getDeclaredField("name");//获取单个属性的注解,再输出单个注解的属性值
FieldAnnotation annotation12 = name.getAnnotation(FieldAnnotation.class);
System.out.println(annotation12.columnName());//输出:”name“
System.out.println(annotation12.columnType());//输出:”varchar“
System.out.println(annotation12.length());//输出:”10“
}
}
@TableAnnotation("db_student")//从这里可以读出,在数据库中,存储实体类的叫db_student
class student{
@FieldAnnotation(columnName = "id",columnType = "int",length = 10)//从这里可以读出,在数据库中该字段名是columnName的值也就是id,数据库中的存储类型是int,长度是10
private int id;
@FieldAnnotation(columnName = "name",columnType = "varchar",length = 10)
private String name;
@FieldAnnotation(columnName = "age",columnType = "int",length = 10)
private int age;
(此处为了简洁,省略有参构造方法以及get,set方法,请读者务必写上)
}
//自定义注解,用于存储表名,作用在类上
@Target(ElementType.TYPE)//类注解
@Retention(RetentionPolicy.RUNTIME)//运行时有效
@interface TableAnnotation{
String value();
}
//自定义注解,作用于类属性,可以用于表示数据库中字段的基本信息
@Target(ElementType.FIELD)//作用于属性
@Retention(RetentionPolicy.RUNTIME)
@interface FieldAnnotation{
//规范:属性类型+属性名()
String columnName();
String columnType();
int length();
}