一、反射介绍
1.反射介绍
反射(Reflect):Java中提供一种可以在运行时操作任意类中属性和方法的技术。
反射在运行之前是不需要类中结构的,运行过程中,只要能够获取该类的字节码文件,就可以随意修改类中属性的值,随意调用类中的方法。让程序变得更灵活。
2.反射的优缺点
优点:灵活性和扩展性
缺点:性能问题。因为反射是在运行过程中操作字节码文件,要要比直接使用代码操作内容慢得多。
总结:反射主要使用在对灵活性和扩展性要求比较高的框架中。普通代码不建议使用。
我们暂时也不需要写灵活性非常高的框架,为什么还要学习反射?
1. 反射是Java中非常重要特性。
2. 虽然我们暂时不写灵活度非常高的框架。但是我们要学一些框架。这些框架大部分使用了反射技术。所以学好反射对以后学习框架底层有非常大的帮助。
二、Class类介绍
java.lang.Class 表示类的类型。一个Class类对象就代表了某个类的字节码对象。获取到这个类的字节码对象后该类中所有的内容都会被知道,然后就可以对这个类中的内容进行操作。
Class类是Java 反射机制的起源和入口 ,用于获取与类相关的各种信息,提供了获取类信息的相关方法,Class类继承自Object类。
1.数据准备
1.Person
package com.bjsxt.pojo;
public class Person {
public String personName;
private String personSex;
public void personEat(){}
private void personPlay(){}
//省略有参,无惨构造方法,get/set方法,toString方法
}
2.Man
public class Man extends Person{
public String manName;
private String manSex;
public void manEat(){}
private void manPlay(){}
//省略有参,无惨构造方法,get/set方法,toString方法
}
2.获取Class对象
1.通过class关键字
可以通过类名.class获取到这个类的字节码对象。
Class<People> peopleClass = People.class;
2.通过forName()方法
可以通过Class类中的静态方法forName()获取到类的字节码对象。
forName方法抛出了Checked异常ClassNotFoundException。如果加载类时,没有发现这个类的字节码文件,就会出现这个异常。
public static Class<?> forName(String className) throws ClassNotFoundException {
//...
}
代码示例:
forName(String)参数是类的全限定路径(包名+类名)
Class<?> aClass = Class.forName("com.bjsxt.pojo.Person");
3.通过getClass()方法
Person person = new Person();
Class<? extends Person> aClass = person.getClass();
4.Class对象常用方法
//1.获取一个类的结构信息(类对象 Class对象)
Class clazz = Class.forName("com.bjsxt.pojo.Person");
//2.从类对象中获取类的各种结构信息
//2.1 获取包名+类名
System.out.println(clazz.getName());
//2.2 获取类名
System.out.println(clazz.getSimpleName());
//2.3 获取父类
System.out.println(clazz.getSuperclass());
//2.4 获取实现的接口
System.out.println(Arrays.toString(clazz.getInterfaces()));
3.获取属性对象
1.getField(String name)
解释:根据属性名获取本类中和父类中的public修饰的属性的属性对象,有访问权限修饰符的限制。
代码实例:
Class<Man> aClass = Man.class;
Field field1 = aClass.getField("manName");
Field field2 = aClass.getField("personName");
2.getFields()
解释:获取本类中和父类中的被public修饰的属性的属性对象,有访问权限修饰符的限制。
代码实例;
Class<Man> aClass = Man.class;
Field[] fields = aClass.getFields();
3.getDeclaredField(String name)
解释:根据属性名获取本类中属性的属性对象,没有访问权限修饰符的限制。
代码示例:
Class<Man> aClass = Man.class;
Field field = aClass.getDeclaredField("manSex");
4.getDeclaredFields()
解释:根据属性名获取本类中所有属性的属性对象,没有访问权限修饰符的限制。
代码示例:
Class<Man> aClass = Man.class;
Field[] fields = aClass.getDeclaredFields();
4.获取方法对象
1.getMethod(String name,Class<?>...parameterTypes)
解释:根据方法名,参数类型获取本类中和父类中public修饰的方法对象,有访问权限修饰符的限制。
代码实例:
Class<?> aClass = Class.forName("com.bjsxt.pojo.Man");
Method manEat = aClass.getMethod("manEat");
Method personEat = aClass.getMethod("personEat");
2.getMethods()
解释:获取本类中和父类中public修饰的所有方法的对象,有访问权限修饰符的限制。
代码示例:
Class<?> aClass = Class.forName("com.bjsxt.pojo.Man");
Method[] methods = aClass.getMethods();
3.getDeclaredMethod(String name.Class<?>...parameterTypes)
解释:根据方法名,参数类型获取本类中方法的方法对象,没有访问权限修饰符的限制。
代码实例:
Class<?> aClass = Class.forName("com.bjsxt.pojo.Man");
Method manPlay = aClass.getDeclaredMethod("manPlay");
4.getDeclaredMethods()
解释:获取本类中所有方法的方法对象,没有访问权限修饰符的限制。
代码实例:
Class<?> aClass = Class.forName("com.bjsxt.pojo.Man");
Method[] manPlay = aClass.getDeclaredMethods();
5.获取构造方法对象
注意:只能获取本类中的构造方法。
1.getConstructor(Class<?>...parameterTypes)
解释:获取本类中无参数|有参数构造方法,有访问权限修饰符的限制。
Class<?> aClass = Class.forName("com.bjsxt.pojo.Man");
Constructor<?> constructor = aClass.getConstructor();
Constructor<?> constructor1 = aClass.getConstructor(String.class, String.class);
2.getConstructors()
解释:获取本类中所有无参数|有参数构造方法,有访问权限修饰符的限制。
Class<?> aClass = Class.forName("com.bjsxt.pojo.Man");
Constructor<?>[] constructors = aClass.getConstructors();
3.getDeclaredConstructor(Class<?>...parameterTypes)
解释:获取本类中无参数|有参数构造方法,没有访问权限修饰符的限制。
Class<?> aClass = Class.forName("com.bjsxt.pojo.Man");
Constructor<?> constructor = aClass.getDeclaredConstructor();
Constructor<?> constructor1 = aClass.getDeclaredConstructor(String.class, String.class);
4.getDeclaredConstructors()
解释:获取本类中所有无参数|有参数构造方法,没有访问权限修饰符的限制。
Class<?> aClass = Class.forName("com.bjsxt.pojo.Man");
Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
5.通过Class对象实例化对象
1.通过Class的newInstance()方法创建对象
类必须有无参构造方法
执行newInstance()执行无参构造方法来创建该类的实例化对象
//正常创建对象
Man man = new Man();
//使用反射创建对象
Class<?> aClass = Class.forName("com.bjsxt.pojo.Man");
Object o = aClass.newInstance();
2.通过数构造方法对象创建对象
通过Constructor对象的newInstance()方法创建对象
先使用Class 对象获取Constructor对象
再调用Constructor对象的newInstance()创建Class对象对应类的对象
Class<?> aClass = Class.forName("com.bjsxt.pojo.Man");
Object o = aClass.getConstructor().newInstance();
Object o = aClass.getConstructor(String.class, String.class).newInstance("zs", "男");
三、操作属性对象(Field)
1.介绍
-
java中提供的对反射支持的类都在java.lang.reflet包中。
-
java.lang.reflect.Field 表示类中属性的属性对象。类中每一个属性对应一个属性对象。
-
属性对象是无法被外部实例化的。它只提供了默认访问权限修饰符的构造方法。所以基本上都是通过Class对象获取Field对象。
2.常用方法
1.set(Object,Object)
set(Object,Object)参数一:为哪个对象的属性赋值。参数二:属性值。
Class<People> aClass = People.class;
//获取对象
People people = aClass.getConstructor().newInstance();
//获取属性对象
Field field = aClass.getDeclaredField("name");
//设置属性值
field.set(people, "zs");
注意:
2.setAccessible(boolean)
setAccessible(booolean)如果一个属性是private,即使获得到了这个属性对象,也不允许被赋值的。在赋值之前,设置setAccessible(true)。表示即使为private,也能赋值。
这点着实很变态,完全破坏了Java中封装特性。也就是说对于反射,类中没有任何秘密而言。
Class<People> aClass = People.class;
People people = aClass.getConstructor().newInstance();
Field field = aClass.getDeclaredField("name");
field.setAccessible(true);
field.set(people, "zs");
3.get(Object)
get(Object)参数:获取那个对象的属性值。返回值:属性值(类型为Object)。
还支持getInt(Object)、getFloat(Object)、getChar(Object)等多种返回具体类型的方法。
Class<People> aClass = People.class;
People people = aClass.getConstructor().newInstance();
Field field = aClass.getDeclaredField("name");
//设置属性值
field.setAccessible(true);
field.set(people, "zs");
//获取属性值
Object name = field.get(people);
System.out.println(name);
3.案例优化
1.需求
2.实现
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
System.out.println("请输入包名+类名");
String next = sc.next();
System.out.println("请输入赋值的属性名称");
String filed = sc.next();
System.out.println("请给这个属性赋值");
String value = sc.next();
//获取类对象
Class<?> aClass = Class.forName(next);
//通过类对象创建实例化对象
Object o = aClass.getConstructor().newInstance();
//获取输入属性的属性对象
Field declaredField = aClass.getDeclaredField(filed);
//设置突破权限的限制
declaredField.setAccessible(true);
//获取属性类型对象
Class<?> type = declaredField.getType();
//获取属性类型名
String simpleName = type.getSimpleName(); // String int double ...
System.out.println(simpleName);
Object val = null;
//判断属性的数据类型
switch (simpleName){
case "String":
val = value;
break;
case "int":
System.out.println("-------------");
val = Integer.parseInt(value);
break;
case "double":
val = Double.parseDouble(value);
break;
case "boolean":
val = Boolean.parseBoolean(value);
}
//设置属性值
declaredField.set(o, val);
System.out.println(o);
}
四、操作方法对象(Method)
1.介绍
-
java.lang.reflect.Method表示类中方法的方法对象。类中每个方法对应一个Method对象。
-
方法对象是无法被外部实例化的。它只提供了默认访问权限修饰符的构造方法。所以基本上都是通过Class对象获取Method对象。
2.常用方法
1.invoke
-
通过Class对象的getMethods() 方法可以获得该类和父类所包括的全部public方法, 返回值是Method[]。
-
通过Class对象的getMethod()方法可以获得该类所包括的指定public方法, 返回值是Method。
-
每个Method对象对应一个方法,获得Method对象后,可以调用其invoke() 来执行对应方法。
-
invoke(Object obj,Object [] args):
-
obj代表当前方法所属对象的名字。
-
args代表调用方法传递的实参。
-
代码实例:
public static void main(String[] args) throws Exception {
//获取类对象
Class<?> aClass = Class.forName("com.bjsxt.pojo.People");
//通过类对象创建实例化对象
Object o = aClass.getConstructor().newInstance();
//获取方法对象
Method method = aClass.getDeclaredMethod("setName", String.class);
//执行方法
method.invoke(o, "ls");
System.out.println(o);
}
五、反射总结
反射优点:
-
功能强大。
-
编码时不知道具体的类型,可以使用反射动态操作。
-
突破封装的限制,即使private的成员也可以进行操作。
反射缺点:
-
代码繁琐,可读性差。
-
突破封装的限制,即使private的成员也可以进行操作(既是优点也是缺点)。
六、注解(Annotation)
1.介绍
注解是从Java 5开始提供的一种技术。使用@+名称 就可以对类、方法、参数等内容添加一个描述功能。
在没有注解之前,很多技术都需要编写大量的XML配置。由于XML文档的结构性问题,想要编写一个文本节点,可能需要2层、3层甚至更多的嵌套才能实现。但是这些问题可以通过注解简化。
2.常见内置注解
1.@Override
在方法重写时,可以在方法上添加@Override注解来检查方法是否满足重写的语法要求。简单方便。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
2.@Deprecated
如果在方法上添加这个注解,表示该方法已经过时。
过时的方法现在能使用,但是已经不再维护或将来会提供其他的使用方法。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
public @interface Deprecated {
}
3.@SuppressWarnings
指示编译器去忽略注解中声明的警告
@SuppressWarnings(value={"all"})
public static void main(String[] args) {
Date date = new Date();
System.out.println(date.toLocaleString());
}
4.@Functionallnterface
Java 8 开始支持,标识一个匿名函数或函数式接口。
七、元注解
1.介绍
在上面源码中发现,每个注解名称上面都还有几个其他注解。这些注解称为元注解(Meta-annotation)。(修饰注解的注解)
元注解是对注解的注释。
Java中一共包含四个元注解:
@Target
@Retention
@Documented
@Inherited
这四个注解不是必须同时都被使用的。一般只需要用到@Target和@Retention。另外两个元注解根据自己的情况添加。
2.@Target
指定注解可以使用的位置(如:类 方法 属性...)
@Target用来描述注解的使用范围。注解里面包含了ElementType[ ]
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
ElementType 是一个枚举类型
public enum ElementType {
/**标明该注解可以用于类、接口(包括注解类型)或enum声明*/
TYPE,
/** 标明该注解可以用于字段(域)声明,包括enum实例 */
FIELD,
/** 标明该注解可以用于方法声明 */
METHOD,
/** 标明该注解可以用于参数声明 */
PARAMETER,
/** 标明注解可以用于构造函数声明 */
CONSTRUCTOR,
/** 标明注解可以用于局部变量声明 */
LOCAL_VARIABLE,
/** 标明注解可以用于注解声明(应用于另一个注解上)*/
ANNOTATION_TYPE,
/** 标明注解可以用于包声明 */
PACKAGE,
/**
* 标明注解可以用于类型参数声明(1.8新加入)
* @since 1.8
*/
TYPE_PARAMETER,
/**
* 类型使用声明(1.8新加入)
* @since 1.8
*/
TYPE_USE
/**
* Module declaration.
*
* @since 9
*/
MODULE
}
3.@Retention
什么时候起作用
定义注解被保留到什么时候。默认值为RetentionPolicy.CLASS
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
里面value取值为RetentionPoolicy。也是枚举类型,可取值为三个
public enum RetentionPolicy {
//源文件
SOURCE,
//字节码
CLASS,
//运行时 常用
RUNTIME
}
SOURCE:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里)
CLASS:注解在class文件中可用,但会被JVM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机中)。
RUNTIME:注解信息将在运行期(JVM)也保留,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息),如SpringMvc中的@Controller、@Autowired、@RequestMapping等。
4.@Documented
生成帮助文档(javadoc)后,是否保留注解信息。如果希望保留就添加这个注解。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
5.@Inherited
如果一个注解添加了@Inherited,那么一个类被这个注解修饰后,其子类也自动添加这个注解。
当@InheritedAnno注解加上某个类A上时,假如类B继承了A,则B也会带上该注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited
{
}
八、自定义注解
1.介绍
通过阅读常用注解可以发现,JDK中定义的注解就是在其上面添加了几个元注解。在根据自己的要求定义注解可以包含哪些属性。
我们也可以利用元注解进行自定义注解。合理的自定义注解可以简化应用程序开发。同时通过学习自定义注解也可以更好的理解后面框架中的注解。
2.自定义注解关键字
定义注解关键字:@interface
注解名称和类型命名要求相同。命名规范相同。
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotaion {
}
3.定义注解中的内容
注解中可以包含常量和方法:
常量:定义后对外部没有太多实际意义。外部使用注解时无法对该值修改。常量默认使用public static final修饰。
方法:必须有返回值,且返回值类型只能是基本类型、String、Class、枚举、注解类型及以上类型的一维数组。方法可以有默认返回值,也可以没有。方法默认使用public abstract修饰。
虽然定义注解时是方法,但是对于外部使用时当做属性使用。
@Target({ElementType.METHOD, ElementType.FIELD}) //用在方法上, 属性上
@Retention(RetentionPolicy.RUNTIME) //运行时生效
public @interface MyAnnotaion {
//属性
public static final int a = 10;
public abstract int max();
public abstract int min() default 1;
}
4.使用注解
可以在任意类的属性中使用@MyAnnotation注解。
public class People {
private String name;
@MyAnnotaion(max = 100, min = 1)
private int age;
//...
}
5.使用反射实现注解的功能
使用反射读取注解中配置的值,判断对象属性值是否满足要求。如果不满足要求抛出异常。一般实际项目中会专门针对这个定义一个异常。
public class MyScan {
public static boolean scan(Object obj){
try {
//1.获取类对象
Class<? extends Object> aClass = obj.getClass();
//2.通过类对象获取所有属性对象
Field[] declaredFields = aClass.getDeclaredFields();
//3.遍历所有的属性对象, 获取每一个属性对象
for (Field declaredField : declaredFields) {
declaredField.setAccessible(true);
//4.判断 属性是否含有@MyAnnotaion 注解
if (declaredField.isAnnotationPresent(MyAnnotaion.class)) {
System.out.println("存在注解的属性: " + declaredField.getName());
//5.获取注解中最大值和最小值
MyAnnotaion annotation = declaredField.getAnnotation(MyAnnotaion.class);
int min = annotation.min();
int max = annotation.max();
if (declaredField.getInt(obj) < min){
throw new RuntimeException(declaredField.getName()+"的最小值为1, 当前值为: "+declaredField.getInt(obj));
}
if (declaredField.getInt(obj) > max){
throw new RuntimeException(declaredField.getName()+"的最大值为100, 当前值为: "+declaredField.getInt(obj));
}
}
}
return true;
} catch (IllegalAccessException e) {
e.printStackTrace();
return false;
}
}
}
6.测试效果
public class Test {
public static void main(String[] args) {
People student = new People("zs", 10);
MyScan.scan(student);
}
}
7.控制台结果
8.以后的注解
在以后JavaEE会学习Filter,学习了框架会学习AOP、Interceptor等技术。这些技术可以实现在执行一个方法时,在前面或后面自动执行一个方法。
目前在Java SE时,还需要手动调用一下MyScan的scan()方法,以后这个方法是不需要手动调用的。可以自动判断注解。
另外我们写的实现注解功能在以后都是框架帮助我们实现的。经常直接使用框架定义好的注解,框架已经把注解封装好了。