反射与注解

一、反射

1.1反射引入

• 编译时知道类或对象的具体信息,此时直接对类和对象进行操作即可,无需反射(reflection)
• 如果编译不知道类或对象的具体信息,此时应该如何做呢?使用反射来实现。比如类的名称放在XML文件中,属性和属性值放在XML文件中,需要在运行时读取XML文件,动态获取类的信息
• 反射的应用场合
o 在编译时根本无法知道该对象或类可能属于哪些类,程序只依靠运行时信息来发现该对象和类的真实信息
o 比如log4j,Servlet、SSM框架技术都用到了反射
• 反射的作用
o 动态创建对象
o 动态操作属性
o 动态调用方法
o 动态操作泛型和注解
• 在JDK中,主要由以下类来实现Java反射机制,都位于java.lang.reflect包中
o Class类:代表一个类
o Constructor 类:代表类的构造方法
o Field 类:代表类的成员变量(属性)
o Method类:代表类的成员方法

1.2反射的入口—Class类

• Class类是Java 反射机制的起源和入口
• 用于获取与类相关的各种信息
• 提供了获取类信息的相关方法
• Class类继承自Object类

【示例】认识Class类

public class TestClass1 {
    public static void main(String[] args) throws Exception {
        //1.获取一个类的结构信息
        Class clazz = Class.forName("com.why.Dog");
        //2.从类对象中获取类的各种结构信息
        //2.1 获取基本结构信息
        clazz.getName());
        clazz.getSimpleName());
        clazz.getSuperclass());
        Arrays.toString(clazz.getInterfaces()));
        //2.2 获取构造方法
        //只能得到public修 饰的构造方法
		//Constructor constructor = clazz.getConstructor();//获取无参数构造方法
		//Constructor constructor = clazz.getConstructor(String.class);
		        //Constructor[] constructors = clazz.getConstructors();
		//任何权限修饰符都可获取
        Constructor[] constructors = clazz.getDeclaredConstructors(); 
        //2.3 获取属性Public
        Field[] fields = clazz.getFields();   // 获取属性Public
        Field [] fields = clazz.getDeclaredFields(); //本类的任何权限修饰符都可获取,父类不行
        //2.3 获取方法
       //Method[] methods = clazz.getMethods();
        Method [] methods = clazz.getDeclaredMethods();  //本类
        //Method m = clazz.getMethod("shout",String.class);
        //Method m = clazz.getMethod("run");//public
        Method m = clazz.getDeclaredMethod("run");
    }
}

Class类的常用方法

getFields() 获得类的public类型的属性。
getDeclaredFields() 获得类的所有属性
getField(String name) 获得类的指定属性
getMethods() 获得类的public类型的方法
getMethod (String name,Class [] args) 获得类的指定方法
getConstrutors() 获得类的public类型的构造方法
getConstrutor(Class[] args) 获得类的特定构造方法
newInstance() 通过类的无参构造方法创建对象
getName() 获得类的完整名字
getPackage() 获取此类所属的包
getSuperclass() 获得此类的父类对应的Class对象

获取一个类的类对象的多种方式

1、Class.forName(“oracle.jdbc.driver.OracleDriver”);
2、String.class;
Student.class;
int.class
3、String str=“sxt";
Class clazz = str.getClass();
4、Student stu = new Student();
Class c1 = stu.getClass();
Class c2 = stu.getSuperClass();
5、Class c1 = Integer.TYPE; (内部基本数据类型的Class对象)

1.3使用反射创建对象

【示例】通过Class的newInstance()方法创建对象

Object obj = clazz.newInstance();

【示例】通过Constructor的newInstance()方法创建对象

public class TestConstructor3 {
    public static void main(String[] args) throws Exception {
        //1.读取配置文件,或者类的完整路径字符串
        String className = "com.why.Dog";
        //2.根据类的完整路径字符串获取Class信息
        Class clazz = Class.forName(className);
        //3.从Class信息中获取有参数构造方法
        // Constructor con = clazz.getConstructor(String.class,String.class);
        Constructor con = clazz.getDeclaredConstructor(String.class,String.class);//指定形参
        //4.使用反射创建对象
		//突破封装性的限制,即使是private、默认的也可以访问
        con.setAccessible(true); 
        Object obj = con.newInstance("旺财1","黑色2");//传递实参
        System.out.println(obj);
    }
}

反射优点

功能强大
1)编码时不知道具体的类型,可以使用反射动态操作
2) 突破封装的限制,即使private的成员也可以进行操作

反射缺点:

1).代码繁琐,可读性差
2).突破封装的限制,即使private的成员也可以进行操作(既是优点也是缺点)

2.1 使用反射操作属性

【示例】使用反射操作属性

public class TestField {
    public static void main(String[] args) throws Exception{
        //1.获取类的完整路径字符串
        String className = "com.why.Dog";
        //2.得到类对象
        Class clazz = Class.forName(className);
        //3.使用反射创建对象
        Object dog = clazz.getConstructor().newInstance();
        //4.获取属性
        Field f1 =  clazz.getField("color");
        Field f2 = clazz.getDeclaredField("age");
        //5.给属性赋值
        f1.set(dog,"黑色1"); //  dog.color ="黑色";
        f2.setAccessible(true);//突破权限的控制
        f2.set(dog,10);
        //6.输出给属性
        System.out.println(f1.get(dog)); //dog.color
        System.out.println(f2.get(dog)); //dog.age
        System.out.println(dog);
    }
}

2.2 使用反射执行方法

• 通过Class对象的getMethods() 方法可以获得该类所包括的全部public方法, 返回值是Method[]
• 通过Class对象的getMethod()方法可以获得该类所包括的指定public方法, 返回值是Method
• 每个Method对象对应一个方法,获得Method对象后,可以调用其invoke() 来调用对应方法
• Object invoke(Object obj,Object [] args):obj代表当前方法所属的对象的名字,args代表当前方法的参数列表,返回值Object是当前方法返回值,即执行当前方法的结果。

【示例8】使用反射执行方法

public class TestMethod {
    public static void main(String[] args) throws Exception{
        //1.获取类的完整路径字符串
        String className = "com.why.Dog";
        //2.得到类对象
        Class clazz = Class.forName(className);
        //3.使用反射创建对象
        //Object dog = clazz.newInstance();
        Object dog = clazz.getConstructor().newInstance();
        //4.获取方法
        Method m1 = clazz.getMethod("shout");
        Method m2 = clazz.getMethod("add",int.class,int.class);
        //5.使用反射执行方法
        m1.invoke(dog);//dog.shout();
        Object result = m2.invoke(dog,10,20);   
        System.out.println(result);
    }
}

2.3 使用反射操作泛型

【示例】使用反射获取泛型类型

public class TestGeneric {
public void method1(Map<Integer, Student> map, List<Student> list, String str) {}
    public Map<Integer, Student> method2() {     return null;  }
    
public static void main(String[] args) throws NoSuchMethodException {
        Class clazz = TestGeneric.class;
        Method method1 =
                clazz.getMethod("method1", Map.class, List.class, String.class);
        //获取参数类型(不带泛型)
        Class[] paramTypes = method1.getParameterTypes();
        //获取参数类型(带泛型)
        Type[] types = method1.getGenericParameterTypes();
        for (Type type : types) {
            if (type instanceof ParameterizedType) {
                Type typeArgs[] =
                        ((ParameterizedType) type).getActualTypeArguments();
            }
        }

给集合添加泛型后,可以限制元素类型,提高安全性。使用反射还可以突破泛型的限制;

【示例】使用反射突破泛型的限制

public class TestGeneric {
    public static void main(String[] args) throws Exception {
        //不是反射
        List<String> list = new ArrayList<String>();
        list.add("Java");
        list.add("MySQL");
        list.add("MyBatis");
        //list.add(new Date());
        //list.add(100);
        //使用反射调用add
        //先得到List的结构信息Class
        Class clazz = list.getClass();
         //获取add方法
        Method method = clazz.getMethod("add",Object.class);
        //使用反射调用add方法
        method.invoke(list,100);
        method.invoke(list,new Date());
        System.out.println(list);
    }
}

二、注解

2.1 认识注解

Annotation ,JDK1.5新提供的技术
我们在编程中经常会使用到注解,作用有:
1)编译检查:比如@SuppressWarnings, @Deprecated 和 @Override 都具有编译检查作用
2)替代配置文件:使用反射来读取注解信息
目前大部分框架(如Spring)都使用了注解简化代码并提高编码的效率(使用注解之前使用的xml进行配置)
注解其实就是代码里的特殊标记,它用于替代配置文件:传统方式通过配置文件告诉类如何运行,有了注解技术后,开发人员可以通过注解告诉类如何运行。
在Java技术里注解的典型应用是:可以通过反射技术去得到类里面的注解,以决定怎么去运行类。 注解可以标记在包、类、属性、方法,方法参数以及局部变量上,且同一个地方可以同时标记多个注解。
注解可以在编译(source),类加载(class),运行时(runtime)被读取,并执行相应的处理,以便于其他工具补充信息或者进行部署

2.2 内置注解

主要有三个内置注解

 @Override - 检查该方法是否是重载方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
 @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
 @SuppressWarnings - 指示编译器去忽略注解中声明的警告。

从 Java 7 开始,额外添加了 3 个注解:
 @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
 @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
 @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。

2.3 元注解
元注解是指注解的注解,在JDK 1.5中提供了4个标准的用来对注解类型进行注解的注解类。可以使用这4个元注解来对我们自定义的注解类型进行注解

  1. @Retention用来约束注解的生命周期,分别有三个值,源码级别(source),类文件级别(class)或者运行时级别(runtime),若没有 @Retention,则默认是 RetentionPolicy.CLASS。其含有如下:
     SOURCE:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里)
     CLASS:注解在class文件中可用,但会被VM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机中)。
     RUNTIME:注解信息将在运行期(JVM)也保留,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息),如SpringMvc中的@Controller、@Autowired、@RequestMapping等。
  2. @Target -用来约束注解可以应用的地方(如方法、类或字段),其中ElementType是枚举类型。若没有 @Target,则该 Annotation 可以用于任何地方。
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
}
  1. @Documented - 标记这些注解是否包含在用户文档中。Java -d doc xx.java
  1. @Inherited - 指示注解类型被自动继承。当@InheritedAnno注解加在某个类A上时,假如类B继承了A,则B也会带上该注解。

2.4 自定义注解

【示例】自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(value= {ElementType.METHOD,ElementType.TYPE})
public @interface MyAnnoation {
    int id() default 0;
    String name() default "";
    double [] scoreArr() default {};
}
public @interface MyAnnotation2 {
    String value();    
}
@MyAnnotation2("aaa")
@MyAnnoation
public class TestAnnotation {
    @MyAnnoation(id=5,name="张三",scoreArr = {78,89,34})
    public static void main(String[] args) {
    }
    @MyAnnotation2(value="bbb")
    public void method1(){
    }
}

总结:

 定义注解的关键字是@interface
 自定义注解中可以定义多个配置参数,不是成员方法,不是成员变量;说明参数的名称,以及参数值的类型
 如果只有一个配置参数,一般命名为value
 如果配置参数是value,并且只有一个配置参数,value可以省略

注意:

 定义注解时,意味着它实现了 java.lang.annotation.Annotation 接口,即该注解就是一个Annotation。
 和我们通常 implements实现接口的方法不同。Annotation 接口的实现细节都由编译器完成。通过 @interface 定义注解后,该注解不能继承其他注解或接口。

2.5 使用反射读取注解

目前大部分框架(如Spring、MyBatis、SpringMVC)都使用了注解简化代码并提高编码的效率(使用注解之前使用的xml进行配置)。
ORM,Object-Relationl Mapping,对象关系映射,它的作用是在关系型数据库和对象之间作一个映射,这样我们在具体的操作数据库的时候,只要像平时操作对象一样操作它就可以了,ORM框架会根据映射完成对数据库的操作,就不需要再去和复杂的SQL语句打交道了。常用的ORM框架有MyBatis和Hibernate。
在ORM中,数据库表对应Java实体类,数据库表的字段对应Java实体类的成员变量,数据库表的记录对应Java实体类的对象。
其实ORM可以借助注解来进行映射,并使用反射读取注解信息完成最终的操作。

【示例】模拟实现MyBatis的注解并使用反射读取

@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.TYPE)
public @interface Table {
    String value();
}

@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.FIELD)
public @interface Column {
    String columnName(); //列名
    String columnType(); //列类型
    int length(); //列长度
    int precision() default 0;//小数位数
}

@Table(value = "t_student")
public class Student {
    @Column(columnName="id",columnType = "int",length=6)
    private int id;
    @Column(columnName = "sname",columnType = "varchar",length = 10)
    private String name;
    @Column(columnName = "score",columnType = "double",length = 4,precision = 1)
    private double score;
}

public class TestORM {
    public static void main(String[] args) throws Exception {
        String className ="com.annotation3.Student";
        Class clazz = Class.forName(className);
        //获取类的所有注解
        Annotation [] annotations = clazz.getAnnotations();
        for (Annotation annotation:annotations  ) {
            System.out.println(annotation);
        }
        //获取类的指定注解
        Table annotation =(Table) clazz.getAnnotation(Table.class);
        System.out.println(annotation);
        System.out.println(annotation.value());
        //获取id属性的注解
        Field idField = clazz.getDeclaredField("id");
        Column  idColumn =(Column)idField.getAnnotation(Column.class);
        System.out.println(idColumn.columnName());
        System.out.println(idColumn.columnType());
        System.out.println(idColumn.length());
        System.out.println(idColumn.precision());

        //获取name属性的注解
        //获取score属性的注解
        //拼接create DDL语句,通过JDBC创建数据库表 excuteUpdate()
        //根据Student类id、name、score的值,对T_Student表进行添 //加、修改、删除操作;将T_Student表的一条记录的各列的数据取出来,存//入一个Student对象中
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AloneDrifters

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值