02-java反射(一)

2022-07-18

02-java反射(一)

java反射(一)

一. 反射的基本操作原理

1.1 实例化Class对象的方法

  • Object类中的getClass()方法

    public final Class<?> getClass()
    

    此方法不能被子类所重写,且所有类的实例化对象都可以使用

  • 利用包.类.class的形式实例化Class对象

    例如java.util.Date.class,在一些开源框架中会大量使用

  • 利用Class类中的forName()方法

    主要可以用在工厂类上,jdbc驱动加载

1.2 Class对象的应用

  • newInstance()

    public T newInstance()
                  throws InstantiationException,
                         IllegalAccessException
    

    InstantiationException -> 没有无参构造,类名错误

    IllegalAccessException -> 构造方法私有化

    该方法只能调用类中的无参构造方法,相当于使用new进行对象的实例化操作-

    面试题: new实例化对象和使用反射实例化对象有什么区别?

    	如果只是一个单纯的类进行对象的实例化,那么两者的区别不大,如果非要强调区别,那么利用反射实例化对象的灵活度更高没只需要"包.类"名称的字符串就可以取得实例化对象
    	如果说现在是一个子类需要为父接口进行对象实例化,那么使用new会造成接口对象耦合性增加的问题.因为一个接口在使用中就与固定的一个子类进行绑定了,而最早的解耦方式是利用`工厂设计模式`.但是为了让一个工厂类可以适用所有接口子类的扩展要求,则可以利用反射完成.
    
    • 传统方式使用接口

      package test1;
      
      public class TestDemo {
          public static void main(String[] args) {
              //直接实例化对象,会造成接口与子类耦合
              Message message = new News();
              message.print("今天有小雨");
          }
      }
      
      interface Message{
          void print(String str);
      }
      
      class News implements Message{
          public void print(String str){
              System.out.println("新闻消息: " + str);
          }
      }
      
      class Email implements Message{
          public void print(String str){
              System.out.println("邮件消息说: " + str);
          }
      }
      
      

      客户端代码需要负责具体的子类操作

    • 利用工厂类解决客户端的接口对象的耦合问题

      class Factory{
          public static Message getInstance(String className){
              if ("news".equalsIgnoreCase(className)){
                  return new News();
              }else if("email".equalsIgnoreCase(className)){
                  return new Email();
              }else{
                  return null;
              }
          }
      }
      
      

      虽然实现了工厂设计模式,但是却有缺点(需要根据子类扩充),不方便维护

    • 利用反射解决工厂类的设计问题

      class Factory{
          public static Message getInstance(String className){
              try {
                  return (Message) Class.forName(className).newInstance();
              } catch (Exception e) {
                  e.printStackTrace();
              }
              return null;
          }
      }
      
      虽然解决了工厂类的固化问题,但是有一个新问题,客户端代码还是固定的.客户端必须明确写出操作的类名称,这样一来代码就会造成新的耦合.
      
      最好的设计方案永远不是点对点直达,而是通过key找到value.普通用户是不能修改程序逻辑代码,但是可以修改文件内容.因此可以使用属性文件的方式解决这种耦合问题.
      
    • 使用properties配置文件

      • load()

        读文件

      • store()

        写文件

        package test1;
        
        public class TestProperties {
            public static void main(String[] args) {
                System.getProperties().list(System.out);
            }
        }
        

        显示一些info

    • 使用ResourceBundle类

      package test1;
      
      import java.util.ResourceBundle;
      
      public class TestResourceBundle {
          public static void main(String[] args) {
              ResourceBundle rb = ResourceBundle.getBundle("info");
              System.out.println(rb.getString("name"));
          }
      }
      

      可以直接获取classpath下的properties文件

二. Annotation操作原理

2.1 Annotation的范围

在java.lang.annotation.RetentionPolicy中定义

Enum Constant and Description
CLASSAnnotations are to be recorded in the class file by the compiler but need not be retained by the VM at run time. 保存在类之中
RUNTIMEAnnotations are to be recorded in the class file by the compiler and retained by the VM at run time, so they may be read reflectively. 程序运行期间起作用
SOURCEAnnotations are to be discarded by the compiler. 在源代码之中起作用

如果我们想编写的注解在运行时起作用,应该使用RUNTIME类型

2.2 自定义注解

  • 取得自定义注解的值

    package test1;
    
    import java.io.Serializable;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    @Retention(value = RetentionPolicy.RUNTIME)
    @interface MyFactory{
        String name() default "mhn";
        String value();
    }
    
    
    @MyFactory(value = "is me")
    class Student implements Serializable{
    
    }
    
    public class TestAnnotation {
        public static void main(String[] args) {
            Class<?> cls = Student.class;
            MyFactory factory = cls.getAnnotation(MyFactory.class);
            System.out.println(factory.name());
            System.out.println(factory.value());
        }
    }
    
  • 利用注解改造工厂方法

    package test1;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    @Retention(RetentionPolicy.RUNTIME)
    @interface Factory{
        String className();
    }
    
    interface Message{
        void print(String str);
    }
    
    class News implements Message{
        public void print(String str){
            System.out.println("新闻消息说: " + str);
        }
    }
    
    class Email implements Message{
        public void print(String str){
            System.out.println("邮件消息说: " + str);
        }
    }
    
    @Factory(className = "test1.Email")
    public class TestDemo {
        public static void main(String[] args) throws Exception {
            Class<?> cls = TestDemo.class;
            Factory factory = cls.getAnnotation(Factory.class);
            String className = factory.className();
            Object obj = Class.forName(className).newInstance();
            Message message = (Message) obj;
            message.print("今天不下雨");
        }
    }
    

三. 利用反射调用其他结构

3.1 操作构造方法

在Class类中提供有以下方法:

  • 取得全部构造方法

    public Constructor<?>[] getConstructors()
                                     throws SecurityException
    
  • 取得指定参数类型的构造

    public Constructor<T> getConstructor(Class<?>... parameterTypes)
                                  throws NoSuchMethodException,
                                         SecurityException
    

    反射是一个只认类型不认具体对象的工具类。包括在方法重载的时候认的也只是方法名称和参数类型。

  • java.lang.reflect.Constructor

    有如下方法

    • 取得构造方法的名称(全名)

      public String getName()
      
    • 取得构造方法的修饰符

      public int getModifiers()
      

      所有的修饰符都是通过数字编号取得的,想要还原,需要使用java.lang.reflect.Modifier

    • 取得参数类型

      public Class<?>[] getParameterTypes()
      

      以声明的顺序返回形式参数的类型,若无参,返回空数组

    • 取得构造方法上的全部异常

      public Class<?>[] getExceptionTypes()
      

    构造方法代码全部如下

    package test1;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Modifier;
    
    class Book{
        private String title;
        private Double price;
    
        public Book(String title, Double price) throws Exception, NumberFormatException{
            this.title = title;
            this.price = price;
        }
    
        public Book(String title){
            this.title = title;
        }
        public Book(){
    
        }
    }
    
    public class TestConstructor {
        public static void main(String[] args) throws Exception{
            Class<?> cls = Class.forName("test1.Book");
            Constructor<?>[] constructors = cls.getConstructors();
            for (int i = 0; i < constructors.length; i++) {
                System.out.print(Modifier.toString(constructors[i].getModifiers()) + " ");
                System.out.print(constructors[i].getName() + "(");
                Class<?>[] params  = constructors[i].getParameterTypes();
                if (params.length > 0){
                    for (int j = 0; j < params.length; j++) {
                        System.out.print(params[j].getSimpleName() + " arg" + j + ((j < params.length - 1) ? ",":")"));
                    }
                }
                Class<?> [] exps  = constructors[i].getExceptionTypes();
                if (exps.length > 0){
                    System.out.print(" throws ");
                    for (int j = 0; j < exps.length; j++) {
                        System.out.print(exps[j].getSimpleName() + ((j < exps.length - 1) ? ",":""));
                    }
                }
                System.out.println();
            }
    
        }
    }
    

    开发工具代码提示都采用了这样的方式,但是如果只用这种方法提示是很慢的。可以采取建立索引的方式加快速度。

    • 实例化对象的方法(这是取得Constructor类的重要作用)

      即调用有参构造

      public T newInstance(Object... initargs)
                    throws InstantiationException,
                           IllegalAccessException,
                           IllegalArgumentException,
                           InvocationTargetException
      

      使用如下

       public static void main(String[] args) throws Exception{
              Class<?> cls = Book.class;
              Constructor<?> constructor = cls.getConstructor(String.class, Double.class);
              Book book = (Book) constructor.newInstance("java大全", 23.5);
              System.out.println(book.toString());
          }
      

      有如下弊端,构造方法参数不能确定,因此最好是无参或者全参构造。

3.2 操作类中的方法

取得普通方法有两组方法

  • 第一组方法

    public Method[] getDeclaredMethods() throws SecurityException
    
    public Method getDeclaredMethod(String name,Class<?>... parameterTypes) throws NoSuchMethodException,SecurityException
    

    取得本类中定义的所有方法,与继承的无关

  • 第二组方法

    public Method[] getMethods() throws SecurityException
    
    public Method getMethod(String name,Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
    

    取得一个类中所有定义的方法,包括继承而来的方法

  • 详细输出方法信息(Method类)

    • 取得返回值类型

      public Class<?> getReturnType()
      
    • 取得方法的名称

      public String getName()
      
    • 取得方法的修饰符

      public int getModifiers()
      

      所有的修饰符都是通过数字编号取得的,想要还原,需要使用java.lang.reflect.Modifier

    • 取得参数类型

      public Class<?>[] getParameterTypes()
      

      以声明的顺序返回形式参数的类型,若无参,返回空数组

    • 取得方法上的全部异常

      public Class<?>[] getExceptionTypes()
      

    全部代码如下

    package test1;
    
    import java.lang.reflect.Method;
    import java.lang.reflect.Modifier;
    
    interface Message1{
        void print(String str, Double num) throws Exception;
    }
    
    abstract class Test{
        public abstract void get();
    }
    
    class Impl extends Test implements Message1{
    
        @Override
        public void print(String str, Double num) throws Exception{
    
        }
    
        @Override
        public void get() {
    
        }
    
        public void set(){
    
        }
    }
    
    public class TestMethod {
        public static void main(String[] args) throws Exception{
            Class<?> cls = Class.forName("test1.Impl");
            Method[] methods = cls.getMethods();
            for (Method method : methods) {
                System.out.print(Modifier.toString(method.getModifiers()) + " ");
                System.out.print(method.getReturnType().getSimpleName() + " ");
                System.out.print(method.getName() + "(");
                Class<?>[] params = method.getParameterTypes();
                for (int i = 0; i < params.length; i++) {
                    System.out.print(params[i].getSimpleName() + ((i < params.length - 1) ? ",":""));
                }
                System.out.print(")");
                Class<?>[] exps = method.getExceptionTypes();
                if (exps.length > 0){
                    System.out.print(" throws ");
                    for (int i = 0; i < exps.length; i++) {
                        System.out.print(exps[i].getSimpleName());
                    }
                }
                System.out.println();
            }
        }
    }
    
    • 调用本类方法

      public Object invoke(Object obj, Object... args) throws IllegalAccessException,IllegalArgumentException,InvocationTargetException
      
      • 无返回值

        package test1;
        
        import java.lang.reflect.Method;
        
        class Test{
            public void print(String str){
                System.out.println(str);
            }
        }
        
        public class TestMethod {
            public static void main(String[] args) throws Exception{
                Class<?> cls = Class.forName("test1.Test");
                Object obj = cls.newInstance();
                Method method = cls.getMethod("print", String.class);
                method.invoke(obj, "woaini");
            }
        }
        
      • 有返回值,有参数

        package test1;
        
        import java.lang.reflect.Method;
        
        class Test{
            public String echo(String str){
                return "ECHO: " + str;
            }
        }
        
        public class TestMethod {
            public static void main(String[] args) throws Exception{
                Class<?> cls = Class.forName("test1.Test");
                Object obj = cls.newInstance();
                Method method = cls.getMethod("echo", String.class);
                Object val = method.invoke(obj, "woaini");
                System.out.println(val);
            }
        }
        
      • 调用get,set方法

        package test;
        import util.StringUtils;
        
        import java.lang.reflect.Field;
        import java.lang.reflect.Method;
        
        class Person{
        
            private String name;
        
            public String getName() {
                return name;
            }
        
            public void setName(String name) {
                this.name = name;
            }
        
        }
        public class TestField {
            public static void main(String[] args) throws Exception{
                String className = "test.Person";
                String property = "name";
                String value = "mahaonan";
                Class<?> cls = Class.forName(className);   //获取反射对象
                Field field = cls.getDeclaredField(property);
                Method setMethod = cls.getDeclaredMethod("set" + StringUtils.initcap(property), field.getType());
                Method getMethod = cls.getDeclaredMethod("get" + StringUtils.initcap(property));
                Object obj = cls.newInstance();
                setMethod.invoke(obj, value);
                System.out.println(getMethod.invoke(obj));
            }
        }
        

        使用反射的前提,必须有以下几个条件:

        类名称,属性名称,内容
        

3.3 操作类中的成员

Class类中提供了两组取得成员的方法

  • 第一组方法(得到全部成员, 包括继承.无法取得私有的)

    public Field[] getFields() throws SecurityException
    
    public Field getField(String name) throws NoSuchFieldException,SecurityException
    
  • 第二组方法(得到本类)

    public Field[] getDeclaredFields() throws SecurityException
    
    public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException
    
  • Field类中的方法

    • 得到属性类型

      public Class<?> getType()
      
    • 得到属性内容

      public Object get(Object obj) throws IllegalArgumentException,IllegalAccessException
      
    • 设置属性内容

      public void set(Object obj,Object value) throws IllegalArgumentException, IllegalAccessException
      
  • 反射调用属性

    Constructor,Method, Field都继承自java.lang.reflect.AccessibleObject, 这个类中有如下两个重要的方法

    public Annotation[] getAnnotations()		//获取全部注解
    
    public void setAccessible(boolean flag) throws SecurityException	//设置是否可见
    

    因此可以设置为True使私有成员可见(一般不要使用)


标题:02-java反射(一)
作者:mahaonan
地址:https://mahaonan.fun/articles/2022/07/18/1658147077456.html