java基础--反射机制
天使总在想象中,魔鬼总在细节处
反射是框架设计的灵魂
一、反射机制的概述
java反射机制是在运行状态中,对任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取信息以及动态调用对象的方法的功能称为Java语言的反射机制。
反射就是把Java类中的各种成分映射成一个个的java对象。例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把各个组成部分映射成一个个对象。
二、类的加载过程
Class对象的由来是程序将class文件读入内存中,并为之创建一个class对象
三、反射的优缺点
优点 | 提高了程序的灵活性和扩展性,更加容易实现面向对象开发 |
---|---|
缺点 | 反射会消耗一定的系统资源;反射操作会模糊程序的内部逻辑,比如反射可以忽略权限检查机制,破坏其封装,造成安全问题 ,且增加维护的难度 |
四、实现反射的方法
4.1、动态加载类的三种方法
- 直接通过类名获得这个实例对象(任何数据类型包括基本数据类型,都有一个“静态的”Class属性)
Class s1 = Student.class - 通过类的对象获得该类的实例对象
Student student = new Student();
Class s2 = student.getClass(); - 使用forName(“全类名”)
Class s3 = Class.forName(“student”)
类的加载只会发生一次,所以存在于JVM中的类对象是唯一的,所以在程序运行中,使用反射多次获得的类对象是同一个。
4.2、 获得类的构造方法并调用
批量:
Public Constructor[] getConstructors();
获得所有的“公有的”构造方法
Public Constructor getConstructor(Class…parameter Types);
获得所有的构造方法(包括私有、受保护、默认、公有)
单个:
Public Constructor getConstructor(Class…parameterType);
获得单个的“公有的”构造方法
Public Constructor getDeclaredConstructor(Class…parameterTypes);
获得“单个构造方法”可以是私有的,或受保护的、默认、公有
调用构造方法:
Public Object newInstance(Object…initargs);
参数说明:
initargs:实参
newInstance是Constructor类的方法(管理构造函数的类)
具体调用方法
public class Student{
//--构造方法--
//(默认的构造方法)
Student(String str){
System.out.println("(默认的)构造方法 s = "+str);
}
//(无参构造方法)
public Student(){
System.out.println("调用了公有、无参构造方法执行了。。。");
}
//(有一个参数的构造方法)
public Student(char name){
System.out.println("姓名:"+name);
}
//(有多个参数的构造方法)
public Student(String name,int age){
System.out.println("姓名:"+name+"年龄:"+age);
}
//(受保护的构造方法)
protected Student(Boolean n){
System.out.println("受保护的构造方法 n="+n);
}
//(私有构造方法)
private Student(int age){
System.out.println(“私有的构造方法 年龄:”+age);
}
}
//获得Class对象
Class stuClass = Class.forName("fanshe.method.Student");
//获得私有构造方法,并调用
Constructor con = stClass.getDeclaredConstructor(char.class);
//忽略掉访问修饰符 暴力访问;(访问私有资源的核心方法)
con.setAccessible(true);
//调用构造方法
Object obj = con.newInstance('男');
//也可以直接调用Class对象的newInstance()方法来创建Class对象对应类的实例
Class<?> c = String.class;
Object str = c.newInstance();
4.3 获得类的成员方法并调用
批量:
public Method[] getMethods();
获得类的所有public的方法,包括继承父类的
public Method[] getDeclaredMethods()
获得类的所有自己声明的方法
单个:
public Method getMethod(String name,Class<?>…parameterTypes);
获得类的某一个public的方法,包括继承父类的
public Method getDeclaredMethod(String name,Class<?> parameterTypes);
获得类的某一个自己声明的方法
参数说明:
name: 方法名;
class:形参的class类型对象
调用:
public Object invoke(Object obj,Object…args);
参数说明
obj:要调用的方法的对象;
args:调用方法时所传递的实参;
具体调用方法
public class Student{
//****成员方法****
public void show1(){
System.out.println("调用了:公有的,String参数的show1():s="+s);
}
protected void show2(){
System.out.println("调用了:受保护的,无参的show2()");
}
void show3(){
System.out.println("调用了:默认的,无参的show3()");
}
private String show4(int age){
System.out.println("调用了,私有的,并且有返回值的,int参数的show4():age="+age);
return "abcd";
}
}
//获得Class对象
Class stuClass = Class.forName("fanshe.method.Student");
//动态获得该对象的某一个方法(方法名,形参的class类型对象)
Method m = stuClass.getMethod("show1",String.class);
//实例化一个Student对象(方法在调用时,需要传入该实例对象)
Object obj = stuClass.getConstructor().newInstance();
//方法的具体调用
m.invoke(obj,"刘德华");
特殊例子
public class Student{
public static void main(){
System.out.println("main方法执行了。。。");
}
}
/**
*获取Student类的main方法、不要与当前的main方法搞混了
*/
public class Main{
public static void main(String[] args){
try{
//1.获取Student对象的字节码
Class clazz = Class.forName("fanshe.main.Student");
//2.获取main方法
Method methodMain = clazz.getMethod("main",String[].calss);
//第一个参数:方法名称; 第二个参数:方法形参的类型
//3.调用main方法
methodMain.invoke(null,(Object)new String[]{"a","b","c"});
//第一个参数,对象类型,因为方法时static静态的,所以为null也可以,第二个参数是String数组,这里拆的时候会将 new String[]{"a","b","c"}拆成3个对象。所以需要将它强转。
methodMain.invoke(null,new Object[]{new String[]{"a","b","c"}});//方式二
}catch(Exception e){
e.printStackTrace();
}
}
}
4.4获取类的成员变量并调用
批量:
Field[] getFields();
获得所有的“公有字段”
Field[] getDeclaredFields();
获取所有字段,包括:私有、受保护、默认、公有
单个:
public Field getField(String fieldName);
获取某个“公有的”字段
public Field getDeclaredFields(String fieldName);
获取某个字段(可以是私有的)
参数说明:
fieldName:获取某个“公有的”成员变量
调用设置某个字段的值
public void set(Object obj,Object value);
参数说明:
obj:要设置的字段所在的对象
value:要为字段设置的值
具体调用方法
public class Student{
public Student(){
}
//****字段****//
public String name;
protected int age;
char sex;
private String phoneNum;
@Override
public String toString(){
return "Student[name="+name+",age="+age+",sex="+sex+",phoneNum="+phoneNum+"]";
}
}
//获得Class对象
Class stuClass = Class.forName("fanshe.method.Student");
//获得某个字段
Field f = stuClass.getDeclaredField("phoneNum");
//忽略掉访问修饰符 暴力访问
f.setAccessible(true);
//为字段设置值
f.set(obj,"1111");
五、实际运用
5.1 利用反射创建数组
public static void testArray() throws ClassNotFoundException{
Class<?> cls = Class.forName(“java.lang.String”);
Object array = Array.newInstance(cls,25);
//往数组里添加内容
Array.set(array,0,“hello”);
Array.set(array,1,“java”);
Array.set(array,2,“fuck”);
Array.set(array,3,“Scala”);
Array.set(array,4,“Clojure”);
//获得某一项的内容
System.out.println(Array.get(array,3));
}
5.2 利用反射使之越过泛型检查
泛型用在编译期,编译过后泛型擦除(消失掉)。所以是可以通过反射越过泛型检查的
/*
*通过反射越过泛型检查
*
*例如:有一个String泛型的集合,怎样能向这个集合中添加一个Integer类型的值?
*/
public class Demo {
public static void main(String[] args) throws Exception{
ArrayList<String> strList = new ArrayList<>();
strList.add("aaa");
strList.add("bbb");
//获取ArrayList的Class对象,反向的调用add()方法,添加数据
Class listClass = strList.getClass();//得到strList 对象的字节码对象
//获取add()方法
Method m = listClass.getMethod("add",Object.class);
//调用add()方法
m.invoke(strList,100);
//遍历集合
for(Object obj : strList){
System.out.println(obj);
}
}
}