【Java进阶篇】第八章 反射与注解

有时候,不是因为你没有能力,也不是因为你缺少勇气,只是因为你付出的努力还太少,所以,成功便不会走向你。而你所需要做的,就是坚定你的梦想,你的目标,你的未来,然后以不达目的誓不罢休的那股劲,去付出你的努力,成功就会慢慢向你靠近。

导读:本篇文章讲解 【Java进阶篇】第八章 反射与注解,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

一、反射机制概述

1、作用

通过Java中的反射机制,可以操作字节码文件,即读和修改字节码文件.class

2、相关类

java.lang.reflect.*

细分为:

  • java.lang.Class 代表字节码文件
  • java.lang.reflect.Method 代表字节码文件中的方法字节码
  • java.lang.reflect.Constructor 代表字节码文件中的构造方法字节码
  • java.lang.reflect.Field 代表字节码文件中的属性字节码

举例

二、反射

1、获取Class的三种方式

☀获取Class的第一种方式:

java.lang.Class类中的静态方法forName(),方法的参数是一个字符串,字符串需要的是一个完整的类名,即必须包含包名,如java.lang.String

try{
    Class c1 = Class.forName("java.lang.String");
    Class c = Class.forName("java.util.Date")
}catch(ClassNotFoundException e){
    e.printStackTrace();
}

c1代表String.class文件=,另外ClassNotFoundException异常的父类是ReflectOperationException,再父就是Exception

☀获取Class的第二种方式:

Java中任何一个对象都有一个方法getClass()(即Object中的方法)

String s = "abc";
Class c2 = s.getClass();
//和第一中方式做对比
//true,即对象内存地址一样
System.out.println(c1 == c2);

内存图
字节码装载到JVM的时候,只装载一份

☀获取Class的第三种方式:

Java中任何一种类型,包括基本数据类型,都有.class属性

Class c3 = String.class;
Class d = Date.class;
Class i = int.class;
//true
System.out.println(c2 == c3);

2、通过反射机制实例化对象

Class c = Class.forName("java.util.Date");
Object obj = c.newInstance();

newInstance()方法已过时,会调用对应类的无参构造,完成对象的创建。若这时手写了有参而未加无参构造,则这时异常:java.lang.InstantiationException

以上单独看有些鸡肋,搭配properties配置文件则很灵活:

反射机制的灵活性

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Date;
import java.util.Properties;

public class ReflectTest {
    public static void main(String[] args) {
        try {
            FileReader reader = new FileReader("thread/classInfo.properties");
            Properties pro = new Properties();
            pro.load(reader);
            String className = pro.getProperty("className");
            Class c = Class.forName(className);
            Object o = c.newInstance();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }catch (ClassNotFoundException e){
            e.printStackTrace();
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }

    }
}

注意:IDEA中的当前目录是项目的根目录

以上代码的核心部分是:
code
如此,创建、批量创建Java对象的时候,就不用改代码,只需修改配置文件就能实例化不同的对象。符合OCP原则,即对扩展开放,对修改关闭。

3、forName方法的另一个应用

若只希望某个类的静态代码块执行,不希望其他程序执行,则可用forName(“完整类名”),这个方法的执行会导致类加载,而类加载的时候,静态代码块执行。

public class MyClass{

...
static{
	System.out.println("静态代码块执行");
}

}

------
Class.forName("com.java.reflectMyclass");

4、获取类路径下文件的绝对路径

以上下代码使用相对路径(IDEA当前路径为project根路径),可移植性差。

FileReader reader = new FileReader("thread/classInfo.properties");

由此:

类路径:凡是在src下的都是类路径下,src是类的根路径

String path = Thread.currentThread().getContextClassLoader().getResource("classInfo.properties").getPath();


  • getContextClassLoader()是线程对象的方法,获取当前线程的类加载器对象
  • getResource()方法是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源,故该方法传参为src下的文件名字符串

由此,拿到了一个文件的绝对路径,最重要的是这是一个随移植环境而变得绝对路径

还可以直接以流得形式返回:

InputStream  reader2  = Thread.currentThread().getContextClassLoader().getResourceAsStream("classInfo.properties");

5、资源绑定器ResourceBundle

资源绑定器用于获取属性配置文件中的内容,此时属性配置文件xx.properties必须放在类路径下。

ResourceBundle bundle = ResourceBundle.getBundle("classInfo");

String className = bundle.getString("className");

注意点:

  • 资源绑定器只能绑定xx.properties文件,注意文件扩展名
  • 这个文件必须在类路径下
  • getBundle方法传参不写文件后缀名properties

6、类加载器

ClassLoader,专门负责加载类的一个命令/工具,JDK中自带了三个类加载器:

  • 启动类加载器:专门加载…jdk\jre\lib\rt.jar
  • 扩展类加载器:专门加载…jdk\lib\ext*.jar
  • 应用类加载器:专门加载classpath中的jar包(class文件)

代码执行前,会将需要的类全部加载到JVM中,先通过启动类加载器,有没加载到,通过扩展类加载器,还没直到,到应用类加载器。

Java中,为了保证类加载的安全,使用了双亲委派机制,优先从启动类加载器中加载(父),父无法加载,再从扩展加载器中加载(母)。否则,若应用类加载器中也植入了一个String类,则出现了安全问题。

三、反射与反编译

1、获取Field

import java.lang.reflect.*;

public class ReflectTest2 {
    public static void main(String[] args) {
        try {
            Class studentClass = Class.forName("Student");
            Field[] fields  = studentClass.getFields(); //获取类中的Field
            System.out.println(fields.length);
            System.out.println(fields[0]); //1
            String fieldName = fields[0].getName(); //只有一个public修饰的no

            Field[] fields1 = studentClass.getDeclaredFields(); //获取所有Field
            for(Field field:fields1){
                System.out.println("属性名:" + field.getName());
                Class fieldType = field.getType(); //获取属性的类型
                String typeStr = fieldType.getName();
                //获取属性的修饰符,返回int,每个数字为修饰符的代号。修饰符可能有多个,如private finally
                int i = field.getModifiers();
                //类Modifier中有静态方法static String toString(int mod)可返回修饰符字符串
                String modifiersStr = Modifier.toString(i);
                System.out.println("修饰符为:" + modifiersStr);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

class Student{
    /**
     * 定义4个Field,用不同的访问控制权限修饰符
     */
    public int no;
    private String name;
    protected int age;
    boolean sex;
}

运行结果:
run
另外,类Class中也有getName方法:

Class studentClass = Class.forName("Student");
//come.java.Student
String className = studentClass.getName();

//Student
String simpleName = studentClass.getSimpleName();

2、反编译Field

import java.lang.reflect.*;
import java.lang.reflect.Modifier;

public class ReflectTest3 {
    public static void main(String[] args) {
        try {
            //创建StringBuilder用来拼接字符串
            StringBuilder s = new StringBuilder();
            Class studentClass = Class.forName("Student");
            //下面就是从上往下边一点点拼接Student类的源码
            s.append(Modifier.toString(studentClass.getModifiers()) + "class" + " " + studentClass.getSimpleName() + "{ \n");
            
            Field[] fields = studentClass.getDeclaredFields();
            
            //拿到每一个Field的修饰符、类型、变量名,再拼起来
            for(Field field:fields){
                s.append("\t");
                String modifierStr = Modifier.toString(field.getModifiers());
                String typeStr = field.getType().getName();
                String fieldStr = field.getName();
                s.append(modifierStr + " " + typeStr + " " + fieldStr + ";");
                s.append("\n");
            }
            
            s.append("}");
            
            System.out.println(s);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
//最好直接append,创建这么多字符串浪费空间

运行结果:
run
靠,有bug,获取类型用getSimpleName():

String typeStr = field.getType().getSimpleName();

正常了:
run

3、通过反射机制访问对象的属性

Class studentClass = Class.forName("Student");
Object obj = studentClass.newInstance();

//getDeclaredFields()拿到的是一个Field集合,getDeclaredField(属性名)拿到的是Field
Field noField = studentClass.getDeclaredField("no"); 
noField.set(obj,9527);
System.out.println("noField的值:"+ noField.get(obj));

//访问私有属性会报错
Field nameField = studentClass.getDeclaredField("name");
nameField.set(obj,"llg");
System.out.println("私有属性name值:"+ nameField.get(obj));

运行报错:java.lang.IllegalAccessException
run
这时可以通过setAccessible(true)方法打破封装,但这样也同时带来了安全问题

Field nameField = studentClass.getDeclaredField("name");

nameField.setAccessible(true);

nameField.set(obj,"llg");
System.out.println("私有属性name值:"+ nameField.get(obj));

4、可变长参数☀☀☀

语法:
类型…(三个点)

举例:

public static void m(int...args){
   System.out.println("doSome!");
}

------------
调用时:
m();
m(10);
m(10,20,30);

注意:可变长度的参数必须在列表的最后一个

错误
args有length属性,说明它可以当作一个数组来看待

public static void m(int...args){
	  for(int i:args){
	      System.out.println(i);
	  }
}

----
调用时也可以传一个数组进去:
int[] num = {1,2,3,4};
m(num);

可变长参数的语法总结:

  • 可变长参数要求参数的个数是0~N个
  • 可变长参数在参数列表只能出现在最后一个位置,也即只能有一个可变长度参数
  • 可变长度参数可以当作一个数组看待

5、反射与反编译Method

/**
 * 用户业务类
 */
public class UserService {
    public boolean login(String name,String password){
        if("admin".equals(name) && "123qweASD".equals(password)){
            return true;
        }else{
            return false;
        }
    }
    public void logout(){
        System.out.println("用户退出登录!");
    }
}

反射Method:

 Class userServiceClass = Class.forName("UserService");
 
 Method[] methods = userServiceClass.getDeclaredMethods();
 
 for(Method method:methods){
 
     System.out.println(Modifier.toString(method.getModifiers())); //修饰符
     
     System.out.println(method.getReturnType().getSimpleName());  //返回值类型

     System.out.println(method.getName()); //方法名
     
     Class[] parameterTypes = method.getParameterTypes(); //所有参数的类型
     
     for(Class parameterType:parameterTypes){
         System.out.println(parameterType.getSimpleName());
     }
 }

运行结果:
run

反编译Method如下:

/**
 * 反编译
 */
class Decompile{
    public static void main(String[] args) throws ClassNotFoundException {
        StringBuilder s = new StringBuilder();
        Class userServiceClass = Class.forName("UserService");
        s.append(Modifier.toString(userServiceClass.getModifiers()) + " " + "class" + " "+ userServiceClass.getSimpleName() + "{");
        s.append("\n");
        Method[] methods = userServiceClass.getMethods();
        for(Method method:methods){
            //频繁创建字符串会占用很多空间,我直接append了
            s.append("\t");
            s.append(Modifier.toString(method.getModifiers()));
            s.append(" ");
            s.append(method.getReturnType().getSimpleName());
            s.append(" ");
            s.append(method.getName());
            s.append("(");
            Class[] parameterTypes = method.getParameterTypes();
            for(Class parameterType:parameterTypes){
                s.append(parameterType.getSimpleName());
                s.append(",");
            }
            //修复bug:对于有形参的,删掉上面加的最后多余的逗号
            if(parameterTypes.length != 0){
                s.deleteCharAt(s.length()-1); 
            }

            s.append("){}\n");
        }
        s.append("}");
        System.out.println(s);
    }

}

运行效果:
run
这玩意很难一次完美反编译出来,运行一下哪里有bug再调代码就好。

6、用反射机制调用对象的方法

Class userServiceClass = Class.forName("UserService");
Object obj = userServiceClass.newInstance();
//注意传参,即区分一个方法是靠方法名+参数
Method loginMethod = userServiceClass.getDeclaredMethod("login",String.class,String.class);
 /**
  * invoke方法
  * loginMethod是要调用的方法的Method对象
  * obj是调用方法的对象
  * 后面是传入的实参列表
  */
Object retValue = loginMethod.invoke(obj,"admin","123qweASD");

System.out.println(retValue); //true

反射机制+配置文件 = 灵活性,体现的OPC原则,对扩展开放,对修改关闭

7、反射与反编译Constructor

先写个素材类:
Vip类
反射就不写了,一些方法直接在反编译中体现:

/**
 * 反编译
 */
class DecompileVip{
    public static void main(String[] args) throws Exception {
        StringBuilder s = new StringBuilder();
        Class vipClass = Class.forName("Vip");
        s.append(Modifier.toString(vipClass.getModifiers()) + " " + "class" + vipClass.getSimpleName() +"{");
        
        Constructor[] constructors = vipClass.getDeclaredConstructors();
        
        for(Constructor constructor:constructors){
            s.append("\n");
            s.append("\t");
            s.append(Modifier.toString(constructor.getModifiers()));
            s.append(" ");
            s.append(vipClass.getSimpleName()); //构造方法名即类名
            s.append("(");
            Class[] parameterTypes = constructor.getParameterTypes();
            for(Class parameterType:parameterTypes){
                s.append(parameterType.getSimpleName());
                s.append(",");
            }
            //修复上面引入的bug,删除最后一个多余的,逗号
            if(parameterTypes.length != 0){
                s.deleteCharAt(s.length()-1);
            }
            s.append("){" + "\n" + "\t" + "}");
            s.append("\n");
        }
        s.append("}");
        System.out.println(s);
    }
}

运行效果:
run

8、用反射机制调用构造方法

Class VipClass = Class.forName("Vip");
Constructor constructor = VipClass.getDeclaredConstructor(int.class,String.class,boolean.class);
//这种调用无参构造的方式已过时
Object obj = VipClass.newInstance();
/**
 * 调用有参构造来new对象
 */
Object obj2 = constructor.newInstance(110,"llg",true);
/**
 * 调用无参构造
 */
Constructor constructor1 = VipClass.getDeclaredConstructor();
Object obj3 = constructor1.newInstance();

9、获取某类的父类以及实现了哪些接口

Class stringClass = Class.forName("java.lang.String");

Class superClass = stringClass.getSuperclass(); //获取父类
System.out.println(superClass.getSimpleName());

Class[] interfaces = stringClass.getInterfaces(); //获取这个类实现的接口
for(Class api:interfaces){
    System.out.println("String类实现的接口有:" + api);
}

运行结果:
在这里插入图片描述

四、注解

1、概述

  • 注释,注解,Annotation
  • 注解Annotation是一种引用数据类型,编译后也生成xx.class文件
  • 定义的语法格式为:
[修饰符列表] @ interface 注释类型名{
}
  • 注解的使用语法是:@注解类型名
  • 注解可以出现在类上、属性上、方法上、变量上等,还可以出现在注解类型上,默认情况下,注解可以出现在任意位置

2、JDK的内置注解

java.lang包下的注解

@Override 表示一个方法声明打算重写超类中的另一个方法声明,源码:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)

public @interface Override {
}

这是一个标识性注解,给编译器做参考的,和运行阶段无关,编译器看到这个注解,就会检查这个方法是否重写了父类的方法,若不是,则报错。故@Override只能注解方法

元注解

用来标注注解类型的注解,称元注解,常见的元注解有Target和Retention。

  • Target用来标注“被标注的注解”可以出现在哪些位置,如@Target(ElementType.METHOD)即被标注的注解只能出现在方法中,
  • Retention用来标注“被标注的注解”最终保存在哪里,如:
@Retention(RetentionPolicy.SOURCE) 表示该注解只能保留在Java源文件中

@Retention(RetentionPolicy.CLASS) 表示该注解只能保留在class文件中

@Retention(RetentionPolicy.RUNTIME) 表示该注解只能保留在class文件中,且可以被反射机制所读取

源码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

-------
public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations 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.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

表示过时的注解@Deprecated

方法或类等元素已过时的时候,可以用@Deprecated,从而传达给别人,已过时,当前有更好的解决方案。
过时
源码解析:
源码

3、注解中定义属性

自定义

public @interface MyAnnotation {
    //带括号,看着像方法,但这是MyAnnotation的一个属性
    String name();
    //这是一个有默认值的属性,使用注解时可以不用赋值
    int age() default 22;
}

使用时:

@MyAnnotation(name="llg")
public static void doSome(){

}

当注解中的属性只有一个的时候,使用注解就可以不用加属性名了

public @interface MyAnnotation {
	String value();
}

使用时:
@MyAnnotation("code9527")
...

注解中的属性的类型可以是:byte、short、int、long、float、double、char、String、Class、枚举类型以及以上每一种类型对应的数组形式,如:int[ ] value

public @interface OtherAnnotation {
    int age();
    String[] email();
    Hobby[] hobbyArray();
}
-------------
public enum Hobby {
    RAP,SING,RUN
}
-------------
@OtherAnnotation(age = 22,email = {"code@qq.com","9527@qq.com"},hobbyArray = {Hobby.SING,Hobby.RAP})  //若数组中只有一个元素,{}可省略
public static void doSome(){

}

4、反射注解

Class c = Class.forName("Test7");
//判断该类上是否有注解”OtherAnnotation“
System.out.println(c.isAnnotationPresent(OtherAnnotation.class));

if(c.isAnnotationPresent(OtherAnnotation.class)){
    //获取注解对象
    //getAnnotation方法返回的是Annotation类型,这里强转以匹配前面我写的变量类型
    OtherAnnotation otherAnnotation = (OtherAnnotation) c.getAnnotation(OtherAnnotation.class);
    int ageValue = otherAnnotation.age();
}

注意注解反射的前提是标记了@Retention(RetentionPolicy.RUNTIME)


整理完了,完结撒花!!! 2022-12-10 17:08

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/146090.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!