一、反射介绍
1. 反射的引入
在项目中有People类,包含name和age属性,并生成getter/setter和toString方法。
如果现在给People中添加address属性,并让程序支持对address属性赋值,需要如何完成?
可以使用Java中的反射技术. 解决程序在运行过程中改变属性值。
2. 反射介绍
反射(Reflect):Java中提供一种可以在运行时操作任意类中的属性和方法的技术。
反射在运行之前是不需要类中结构的,运行过程中,只要能够获取该类的字节码文件,就可以随意修改类中属性的值,随意调用类中方法。让程序变得非常灵活。
3. 反射的优缺点
优点:灵活性和扩展性。
缺点:性能问题。因为反射是在运行过程中操作字节码文件,要比直接使用代码操作内容慢很多。
总结:反射主要使用在对灵活性和扩展性要求比较高的框架中。普通代码不建议使用。
我们暂时也不需要写灵活性非常高的框架,为什么还要学习反射?
1. 反射是Java中非常重要特性。
2. 虽然我们暂时不写灵活度非常高的框架。但是我们要学一些框架。这些框架大部分使用了反射技术。所以学好反射对以后学习框架底层有非常大的帮助。
二、Class类介绍
java.lang.Class 表示类的类型。一个Class类对象就代表了某个类的字节码对象。获取到这个类的字节码对象后该类中所有的内容都会被知道,然后就可以对这个类中的内容进行操作。
Class类是Java 反射机制的起源和入口 ,用于获取与类相关的各种信息,提供了获取类信息的相关方法,Class类继承自Object类。
1. 数据准备
1.1 Person
public class Person implements Serializable {
private String name;
private int age;
public String address;
public Date bir;
public static void aa(){}
public Person(){}
public Person(String name, int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
", bir=" + bir +
'}';
}
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;
}
}
1.2 Man
public class Man extends Person{
private String name;
private int age;
public Man() {
}
public Man(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Man{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
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;
}
}
2. 获取Class对象
2.1 通过class关键字
可以通过类名.class 获取到这个类的字节码对象。
Class<People> peopleClass = People.class;
2.2 通过forName()方法
可以通过Class类中静态方法forName()获取到类的字节码对象。
forName 方法抛出了Checked异常ClassNotFoundException。如果加载类时,没有发现这个类的字节码文件,就会出现这个异常。
public static Class<?> forName(String className) throws ClassNotFoundException {
//...
}
2.3 通过getClass()方法
Person person = new Person();
Class<? extends Person> aClass = person.getClass();
2.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. 获取属性对象
3.1 getField(String name)
解释:根据属性名获取本类中和父类中public修饰的属性的属性对象,有访问权限修饰符的限制。
3.2 getFields()
解释:获取本类中和父类中public修饰的所有属性的属性对象,有访问权限修饰符的限制。
3.3 getDeclaredField(String name)
解释:根据属性名获取本类中属性的属性对象,没有访问权限修饰符的限制。
3.4 getDeclaredFields()
解释:获取本类中所有属性的属性对象,没有访问权限修饰符的限制。
public class FieldTest {
public static void main(String[] args) throws Exception{
Class<?> aClass = Class.forName("com.sh.课后练习.pojo.Person");
//获取可访问的属性
//java.lang.NoSuchFieldException
//私有化的访问不到,只能访问可访问的
Field nameField = aClass.getField("name");
System.out.println(nameField);
//获取能访问到所有属性
Field[] fields = aClass.getFields();
for (Field field : fields) {
System.out.println(field);
}
//获取本类中的属性,不看权限修饰符
Field name = aClass.getDeclaredField("name");
//获取本类中的所有属性,不看权限修饰符
Field[] fields1 = aClass.getDeclaredFields();
for (Field field : fields1) {
System.out.println(field);
}
}
}
4. 获取方法对象
4.1 getMethod(String name, Class<?>... parameterTypes)
解释:根据方法名,参数类型获取本类中和父类中public修饰的方法的方法对象,有访问权限修饰符的限制。
4.2 getMethods()
解释:获取本类中和父类中public修饰的所有方法的方法对象,有访问权限修饰符的限制。
4.3 getDeclaredMethod(String name, Class<?>... parameterTypes)
解释:根据方法名,参数类型获取本类中方法的方法对象,没有访问权限修饰符的限制。
4.4 getDeclaredMethods()
解释:获取本类中所有方法的方法对象,没有访问权限修饰符的限制。
public class MethodTest {
public static void main(String[] args) throws Exception{
Class<?> aClass = Class.forName("com.sh.课后练习.pojo.Person");
//参数类型获取本类中和父类中public修饰的方法的方法对象,有访问权限修饰符的限制。
Method getName = aClass.getMethod("getName");
System.out.println(getName);
//获取能访问的所有的方法
Method[] methods = aClass.getMethods();
for (Method method : methods) {
System.out.println(method);
}
//获取本类中的方法,不受权限修饰符的影响
Method bb = aClass.getDeclaredMethod("bb");
System.out.println(bb);
//获取本类中的所有方法,不受权限修饰符的影响
Method[] methods1 = aClass.getDeclaredMethods();
for (Method method : methods1) {
System.out.println(method);
}
}
}
5. 获取构造方法对象
注意:只能获取本类中构造方法。
5.1 getConstructor(Class<?>... parameterTypes)
解释:获取本类中无参数|有参数构造方法,有访问权限修饰符的限制。
5.2 getConstructors()
解释:获取本类中所有无参数|有参数构造方法,有访问权限修饰符的限制。
5.3 getDeclaredConstructor(Class<?>... parameterTypes)
解释:获取本类中无参数|有参数构造方法,没有访问权限修饰符的限制。
5.4 getDeclaredConstructors()
解释:获取本类中所有无参数|有参数构造方法,没有访问权限修饰符的限制。
public class ConstructorTest {
public static void main(String[] args) throws Exception {
//1.普通创建对象,普通调用构造方法
//无参构造
Man man1 = new Man();
//有参构造
Man man = new Man("zs",123);
//2.通过反射创建对象,通过反射调用构造方法
//无参构造
Class<?> aClass = Class.forName("com.sh.课后练习.pojo.Man");
Object o = aClass.newInstance();
//有参构造
aClass.getConstructor(String.class , int.class).newInstance("zs",123);
}
}
三、操作属性对象(Field)
1. 介绍
-
java中提供的对反射支持的类都在java.lang.reflet包中。
-
java.lang.reflect.Field 表示类中属性的属性对象。类中每一个属性对应一个属性对象。
-
属性对象是无法被外部实例化的。它只提供了默认访问权限修饰符的构造方法。所以基本上都是通过Class对象获取Field对象。
2. 常用方法
2.1 set(Object,Object)
set(Object, Object) 参数一:为哪个对象的属性赋值。参数二:属性值。
2.2 setAccessible(boolean)
setAccessible(boolean) 如果一个属性是private,即使获得到了这个属性对象,也不允许被赋值的。在赋值之前,设置setAccessible(true)。表示即使为private,也能赋值。完全破坏了Java中封装特性。
2.3 get(Object)
get(Object) 参数:获取那个对象的属性之。返回值:属性值(类型为Object)。
还支持getInt(Object)、getFloat(Object)、getChar(Object)等多种返回具体类型的方法。
public class SetFieldTest {
public static void main(String[] args) throws Exception{
Class<?> aClass = Class.forName("com.sh.课后练习.pojo.Man");
//使用反射来给属性赋值
Object o = aClass.newInstance();
Field name = aClass.getDeclaredField("name");
//突破访问权限修饰符
name.setAccessible(true);
//知道对象,知道属性,修改属性值
name.set(o,"haha");
System.out.println(o);
//Man{name='haha', age=0}结果
//知道对象,知道属性,获取属性值
Object o1 = name.get(o);
System.out.println(o1);
}
}
四、操作方法对象(Method)
1. 介绍
-
java.lang.reflect.Method表示类中方法的方法对象。类中每个方法对应一个Method对象。
-
方法对象是无法被外部实例化的。它只提供了默认访问权限修饰符的构造方法。所以基本上都是通过Class对象获取Method对象。
2. 常用方法
2.1 invoke
-
通过Class对象的getMethods() 方法可以获得该类和父类所包括的全部public方法, 返回值是Method[]。
-
通过Class对象的getMethod()方法可以获得该类所包括的指定public方法, 返回值是Method。
-
每个Method对象对应一个方法,获得Method对象后,可以调用其invoke() 来执行对应方法。
-
invoke(Object obj,Object [] args):
-
obj代表当前方法所属对象的名字。
-
args代表调用方法传递的实参。
-
public class SetMethodTest {
public static void main(String[] args) throws Exception{
Class<?> aClass = Class.forName("com.sh.课后练习.pojo.Man");
Object o = aClass.newInstance();
//参数一:方法名 参数二:方法参数
Method setAge = aClass.getDeclaredMethod("ha",String.class,int.class);
//invoke()方法执行方法,参数一:对象 参数二:方法的参数
setAge.invoke(o,"zs",123);
System.out.println(o);
}
}
五、反射总结
反射优点:
-
功能强大。
-
编码时不知道具体的类型,可以使用反射动态操作。
-
突破封装的限制,即使private的成员也可以进行操作。
反射缺点:
-
代码繁琐,可读性差。
-
突破封装的限制,即使private的成员也可以进行操作(既是优点也是缺点)。
六、注解(Annotation)
1. 介绍
注解是从Java 5开始提供的一种技术。使用@+名称 就可以对类、方法、参数等内容添加一个描述功能。
在没有注解之前,很多技术都需要编写大量的XML配置。由于XML文档的结构性问题,想要编写一个文本节点,可能需要2层、3层甚至更多的嵌套才能实现。但是这些问题可以通过注解简化。
2. 常见内置注解
2.1 @Override
在方法重写时,可以在方法上添加@Override注解来检查方法是否满足重写的语法要求。简单方便。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
2.2 @Deprecated
如果在方法上添加这个注解,表示该方法已经过时。
过时的方法现在能使用,但是已经不再维护或将来会提供其他的使用方法。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
public @interface Deprecated {
}
2.3 @SuppressWarnings
指示编译器去忽略注解中声明的警告
@SuppressWarnings(value={"all"})
public static void main(String[] args) {
Date date = new Date();
System.out.println(date.toLocaleString());
}
2.4 @FunctionalInterface
Java 8 开始支持,标识一个匿名函数或函数式接口。
七、元注解
1. 介绍
在上面源码中发现,每个注解名称上面都还有几个其他注解。这些注解称为元注解(Meta-annotation)。(修饰注解的注解)
元注解是对注解的注释。
Java中一共包含四个元注解:
@Target
@Retention
@Documented
@Inherited
这四个注解不是必须同时都被使用的。一般只需要用到@Target和@Retention。另外两个元注解根据自己的情况添加。
@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
}
@Retention
定义什么时候起作用
定义注解被保留到什么时候。默认值为RetentionPolicy.CLASS
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
里面value取值为RetentionPolicy。也是枚举类型,可取值为三个
public enum RetentionPolicy {
//源文件
SOURCE,
//字节码
CLASS,
//运行时 常用
RUNTIME
}
SOURCE:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里)
CLASS:注解在class文件中可用,但会被JVM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机中)。
RUNTIME:注解信息将在运行期(JVM)也保留,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息),如SpringMvc中的@Controller、@Autowired、@RequestMapping等。
@Documented
生成帮助文档(javadoc)后,是否保留注解信息。如果希望保留就添加这个注解。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
@Inherited
如果一个注解添加了@Inherited,那么一个类被这个注解修饰后,其子类也自动添加这个注解。
当@InheritedAnno注解加在某个类A上时,假如类B继承了A,则B也会带上该注解。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited
{
}
八、自定义注解
1. 介绍
通过阅读常用注解可以发现,JDK中定义的注解就是在其上面添加了几个元注解。在根据自己的要求定义注解可以包含哪些属性。
我们也可以利用元注解进行自定义注解。合理的自定义注解可以简化应用程序开发。同时通过学习自定义注解也可以更好的理解后面框架中的注解。
2. 自定义注解关键字
定义注解关键字:@interface
注解名称和类型命名要求相同。命名规范相同。
3. 定义注解中内容
注解中可以包含常量和方法:
常量:定义后对外部没有太多实际意义。外部使用注解时无法对该值修改。常量默认使用public static final修饰。
方法:必须有返回值,且返回值类型只能是基本类型、String、Class、枚举、注解类型及以上类型的一维数组。方法可以有默认返回值,也可以没有。方法默认使用public abstract修饰。
虽然定义注解时是方法,但是对于外部使用时当做属性使用。
//在方法和属性上使用的注解
@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME) //运行时生效
public @interface MyAnnotation {
//属性默认都是公共静态常量
public static final int a = 10;
//方法默认都是公共抽象方法
public abstract int max();
//注解中的方法相当于一个属性
public abstract int min() default 1;
}
4. 使用注解
public class Test {
private String name;
@MyAnnotation(max = 100,min = 1)
private int age = 50;
}
public class AnnotationTest {
public static void main(String[] args) throws Exception{
//1.获取类对象
Class<?> aClass = Class.forName("com.sh.课后练习.自定义注解.Test");
Object o = aClass.newInstance();
//2.获取类对象的所有属性对象
Field[] declaredFields = aClass.getDeclaredFields();
//3.遍历所有的属性对象,获取每一个属性对象
for (Field declaredField : declaredFields) {
declaredField.setAccessible(true);
//4.判断是否还有MyAnnotation注解
//调用isAnnotationPresent(注解.class)
if (declaredField.isAnnotationPresent(MyAnnotation.class)) {
System.out.println("存在注解的属性: " + declaredField.getName());
//获取注解中的最大值和最小值
MyAnnotation annotation = declaredField.getAnnotation(MyAnnotation.class);
int max = annotation.max();
int min = annotation.min();
if (declaredField.getInt(o) < min){
throw new RuntimeException(declaredField.getName()+"的最小值为1, 当前值为: "+declaredField.getInt(o));
}
if (declaredField.getInt(o) > max){
throw new RuntimeException(declaredField.getName()+"的最大值为100, 当前值为: "+declaredField.getInt(o));
}
}
}
}
}
5. 以后的注解
在以后JavaEE会学习Filter,学习了框架会学习AOP、Interceptor等技术。这些技术可以实现在执行一个方法时,在前面或后面自动执行一个方法。
目前在Java SE时,还需要手动调用一下MyScan的scan()方法,以后这个方法是不需要手动调用的。可以自动判断注解。
另外我们写的实现注解功能在以后都是框架帮助我们实现的。经常直接使用框架定义好的注解,框架已经把注解封装好了。