一.JAVA反射机制
1.反射是java的动态机制,允许程序在"运行期间"再确定如:对象的实例化,方法的调用,属性的操作等。
2.反射机制可以提高代码的灵活性和适应性。但是会带来较多的系统开销和较慢的运行效率。
3.因此反射机制不能过度被依赖。
二.反射机制使用的第一步:获取待操作的类的类对象
1. 类对象:Class类的实例
2.JVM内部每个被加载的类都有且只有一个Class的实例与之对应。
3.JVM加载一个类时会读取该类的.class文件然后将其载入到JVM内部,与此同时会实例化一个Class的实例,用该实例记录被加载的类的信息(类名,方法,构造器等)
三. 获取一个类的类对象方式:
1:类名.class
Class cls = String.class;//获取String的类对象
Class cls = int.class;//获取int的类对象(基本类型只有这一种方式获取类对象)
2:Class.forName(String className)
根据类的完全限定(包名.类名)名加载并获取该类的类对象
Class cls = Class.forName("java.lang.String");
3:ClassLoader类加载器方式
四. 反射对象:
1. Class,它的每一个实例用于表示一个类的信息
2. Package,它的每一个实例用于表示一个包的信息
3.Method,它的每一个实例用于表示一个方法
4. Constructor,它的每一个实例用于表示一个构造器
5.Filed,它的每一个实例用于表示一个属性
//案例1
Scanner scanner = new Scanner(System.in);
System.out.println("请输入一个类名:");
String className = scanner.nextLine();
Class cls = Class.forName(className);
//获取类对象所表示的类的完全限定名
String name = cls.getName();
System.out.println(name);
//仅获取类名
name = cls.getSimpleName();
System.out.println(name);
// Package getPackage() 通过类对象获取其表示的类的包信息
String packageName = cls.getPackage().getName();
System.out.println("包名:"+packageName);
//通过类对象获取其表示的类中的所有公开方法(包含从超类继承的方法)
Method[] methods = cls.getMethods();
for(Method method : methods){
//每个Method对象也提供了许多get方法可以获取其表示的方法的相关信息
System.out.println(method.getName());//输出方法名
}
五.反射机制实例化
1:获取待实例化类的类对象
2:通过类对象的newInstance()方法实例化
Person person = new Person();
System.out.println(person);
/* java.util.Date (日期类,new一个实例表示实例化是的系统时间)
reflect.Person
reflect.Student
java.util.HashMap */
Scanner scanner = new Scanner(System.in);
System.out.println("请输入一个类名:");
String className = scanner.nextLine();
Class cls = Class.forName(className);
/* Class提供了可以实例化其表示的类的实例的方法:
Object newInstance()
该方法会使用其表示的类的公开的,无参构造器进行实例化 */
Object obj = cls.newInstance();
System.out.println(obj);
六. getConstructor()获取构造器
Class cls = Class.forName("reflect.Person");
Constructor construtor = cls.getConstructor();//获取无参构造器
construtor.newInstance();
//Person(String name)
Constructor constructor1 = cls.getConstructor(String.class);
Object obj = constructor1.newInstance("李四");//new Person("李四");
System.out.println(obj);
//Person(String name,int age)
Constructor constructor2 = cls.getConstructor(String.class,int.class);
Object obj2 = constructor2.newInstance("王五",33);//new Person("王五",33);
System.out.println(obj2);
七. 使用反射机制调用方法
//案例2
Class cls = Class.forName("reflect.Person");
Object obj = cls.newInstance();
//getMethod()第一个参数为方法名,从第二个参数开始为该方法的参数列表
Method m1 = cls.getMethod("say",String.class);//say(String info)
//invoke()第一个参数为方法所属对象,从第二个参数开始就是调用该方法时需要出入的实参
m1.invoke(obj,"大家好!");//p.say("大家好!");
Method m2 = cls.getMethod("say",String.class,int.class);
m2.invoke(obj,"嘿嘿嘿",5);//p.say("嘿嘿嘿",5);
八. 通过反射机制访问类的私有成员
Person p = new Person();
p.heihei();//编译不通过!类的外部不能访问类的私有成员
Class cls = Class.forName("reflect.Person");
Object obj = cls.newInstance();
/* java.lang.NoSuchMethodException
没有这个方法异常
原因: Class的getMethod()和getMethods()仅能获取该类对象所表示的类的公开方法(包含从超类继承的) */
// Method method = cls.getMethod("heihei");
// method.invoke(obj);// p.heihei();
/* Class提供了一组:getDeclaredXXXX()都是用于获取本类定义的内容
getDeclaredMethod用来获取Class表示的类的自己定义的方法(包含私有方法) */
Method method = cls.getDeclaredMethod("heihei");
method.setAccessible(true);
method.invoke(obj);// p.heihei();
method.setAccessible(false);
/ *案例3
* 调用与当前类Test2在同一个包中那些类的所有公开的无参方法
* 提示:文件名与其中定义的类名一致 */
//定位的是Test2.class文件所在的目录
File dir = new File(
Test2.class.getResource(".").toURI()
);
File[] subs = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().endsWith(".class") ;
}
});
//获取该目录中的所有class文件
for(File sub : subs){
//ReflectDemo1.class
String fileName = sub.getName();
//ReflectDemo1
String className = fileName.substring(0,fileName.indexOf("."));
//缺点:包名写死了
// Class cls = Class.forName("reflect."+className);
//dir.getName()--->reflect
// Class cls = Class.forName(dir.getName()+"."+className);
String packName = Test2.class.getPackage().getName();
Class cls = Class.forName(packName+"."+className);
System.out.println(cls.getName());
Object obj = cls.newInstance();
Method[] methods = cls.getDeclaredMethods();
for(Method method : methods){
if(method.getParameterCount()==0//是否无参
&&method.getModifiers()== Modifier.PUBLIC//该方法是否为公开方法
){
method.invoke(obj);
}
九.反射机制中访问注解,判断一个类是否被某个注解标注了
-注解是JDK5之后推出的特性,可以辅助我们在反射中做更多操作
-注解被大量的应用于框架中
1.元注解:JAVA定义了许多元注解,用于说明我们定义的注解的一些特性
1) @Target:用于指定当前注解可以被应用于哪里,不指定时,注解可以被应用于任何可被使用的地方
* 可以使用ElementType来指定注解可以被应用的位置
* 例如:
* ElementType.TYPE 注解在类上可以被使用
* ElementType.METHOD 注解可以在方法上被使用
* ElementType.FIELD 注解可以在属性上被使用
* ElementType.CONSTRUCTOR 注解可以在构造器上被使用
2) @Retention:用来说明当前注解的保留级别,级别有三种
* RetentionPolicy.SOURCE 注解仅保留在源代码中
* RetentionPolicy.CLASS 注解保留在字节码文件中但不能被反射机制使用
* RetentionPolicy.RUNTIME 注解保留在字节码文件中且可以被反射机制使用
* 当不指定@Retention时,注解默认的保留级别为CLASS
2.【所有反射对象】都提供了用于判断是否被某个注解标注的方法
boolean isAnnotationPresent(Class cls)判断当前反射对象表示的内容是否被参数类对象表示的注解标注了
//需求:判断Person类是否被注解@AutoRunClass标注
Class cls = Class.forName("reflect.Person");
//判断cls表示的类是否被参数类对象表示的注解AutoRunClass标注了?
boolean mark=cls.isAnnotationPresent(AutoRunClass.class);
System.out.println("Person是否被@AutoRunClass标注了:"+mark);
3. 注解可以定义参数
格式:
类型 参数名() [default 默认值] //[]中的内容可有可无
参数如果指定了默认值,那么使用该注解时可以不指定参数。
如果没有指定默认值,则使用该注解时必须指定参数
如果注解中只声明了一个参数时,参数名推荐使用"value"
原因:
正常情况下,当我们在注解中声明一个参数时,外面使用该注解为该参数赋值时
格式如下:
@注解名(参数名=参数值)
例如:
当前注解@AutoRunMethod中定义了一个int行的参数,名字为count:
public @interface AutoRunMethod {
int count() default 1;
}
那么外面在使用该注解时,格式如下
@AutoRunMethod(count=3)
public void sayHi(){....}
如果注解定义了多个参数,比如:
public @interface AutoRunMethod {
int count() default 1;
String name();
}
外界使用时,为参数赋值的格式
1:
@AutoRunMethod(count=3,name="张三")
public void sayHi(){....}
2:
@AutoRunMethod(name="张三",count=3)
public void sayHi(){....}
3:
@AutoRunMethod(name="张三") //因为count指定了默认值
public void sayHi(){....}
多个参数时,上述写法优点:可读性强 缺点:啰嗦
但是对于只有一个参数的情况下:
@AutoRunMethod(count=1) 对于一个参数时,不存在混淆问题,之带来啰嗦
@AutoRunMethod(1) 优雅~
为了解决该问题,java建议当注解只有一个参数时,参数名取名为value,此时
允许我们不指定参数名:@AutoRunMethod(1)
对于多参数时,就算某个参数取名为value,使用该直接为参数赋值时也不能
省略参数名:
public @interface AutoRunMethod {
int value();
String name();
}
@AutoRunMethod(value=1,name="XXXX") 可以
@AutoRunMethod(name="XXXX",value=1) 可以
@AutoRunMethod(name="XXXX",1) 不可以
@AutoRunMethod(1,name="XXXX") 不可以
//案例4
//需求:获取Person类中sayHi方法的注解@AutoRunMethod中的参数
Class cls = Class.forName("reflect.Person");
Method method = cls.getMethod("sayHi");
//确定该方法被指定注解标注了
if(method.isAnnotationPresent(AutoRunMethod.class)) {
/* 所有的反射对象都支持获取注解的方法:
getAnnotation(Class cls) */
AutoRunMethod arm = method.getAnnotation(AutoRunMethod.class);
int value = arm.value();//获取注解中"value"参数的值
System.out.println(value);
}