导读:很多优秀的高级框架都是通过反射完成的,反射的重要性,由此可见一斑。反射机制可以使得程序更加灵活,只有学习好反射的基础语法,这样才能自己写出优秀的框架。好了一起打卡学习吧,别忘记了素质三连哦!

1.反射机制概述

  • 反射机制有什么作用?
    • java语言中反射机制可以操作字节码文件。
    • 优点:可以直接读和修改字节码文件。通过反射机制可以操作class文件。
    • 反射在框架中使用很多,掌握它,你注定不凡。
  • 在java.lang.reflect.*;包下。
  • 反射机制相关重要的类:
    • java.lang.Class:代表字节码,代表一个类型(整个类)。
    • java.lang.reflect.Method:代表字节码中的方法字节码(类中的方法)
    • java.lang.reflect.Constructor:代表字节码中的构造方法字节码(类中的构造方法)
    • java.lang.reflect.Field:代表字节码中的属性字节码。(类中的静态变量和成员变量)
  • 要操作一个类的字节码,需要首先获取到这个类的字节码,那如何获取java.lang.Class实例呢?
    • 有三种方式:
    • 第一种:class.forName(“ ”)方法:静态方法,方法的参数是一个字符串(完整的类名),不能省略包。返回一个Class类型.(lang.class字节码文件)。使用这个方法会加载类到JVM,使static静态代码块执行(只执行一次),如果你只想让程序执行static静态代码块,只用这个方法就好了。(重点,JDBC中会用)
    • 第二种:getClass()方法,任何一个对象都有这个方法。(返回字节码文件)同一个类的对象返回的java.lang.Class字节码相同(所指的地址相同)。
    • 第三种:java语言中任何一中类型,包括基本数据类型,他都有.class属性。例如:
      • Class x = String.class;
      • Class a = int.class;

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

  • 通过反射机制,获取Class,通过Class来实例化对象.
    • newInstance()方法:创建一个和之前反射相同类型的对象,会自动调用类的无参构造方法。没有无参构造方法,会报异常。(JDK8之后过时了,不过现在主要用的就是JDK8,sun公司长期支持JDK8和JDK11)
    • 举个栗子:
package Day2;


public class ReflectTest1 {
    public static void main(String[] args) {
        Class s1 = null;
        try {
            //通过反射机制获取Class
             s1= Class.forName("Day2.User");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        Object a=null;
        try {
           a =s1.newInstance();//用反射获得的Class,创建一个对象
            System.out.println(a);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

    }
}
class User{

    public User() {
        System.out.println("无参构造方法!");
    }
}
  • 使用反射机制创建对象有什么作用呢?
    • 反射机制让程序更加灵活。
    • java代码写一遍,在不改变java源代码的基础上,可以做到不同对象的实例化。(符合OCP原则:对外开放,修改关闭。)
    • 高级框架:底层实现原理:采用了反射机制。
    • 举个栗子:(使用IO流,properties集合,通过属性文件来实例化对象)(重点)
package Day2;

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

public class ReflectTest2 {
    public static void main(String[] args) throws Exception {
        //通过IO流读取flie.properties文件
        FileReader reader = new FileReader("file.properties");
        //创建属性类对象(Properties对象,Map集合,key和value都是字符串类型
        Properties pro = new Properties();
        //将文件中的数据加载到Map集合中加载
        pro.load(reader);
        reader.close();//这个流不能在没加载数据之前就关闭了
        //通过key获取value
        String a = pro.getProperty("ClassName");
        Class bb = Class.forName(a);//反射获得Class
        Object c = bb.newInstance();//通过Class,新建一个对象
        System.out.println(c);
    }
}
class User1 {
    public User1() {
        System.out.println("无参构造方法!");
    }
}
  • 如何获取类路径下文件的绝对路径?
    • 为什么聊这个?因为上述代码可能换一个位置,当前路径名就失效了,(即移植性差)所以我们的聊聊这个。
    • 使用以下通用方式,这个文件必须保存在类路径下,(即src目录下)。
    • String path = Thread.currentThread().getContextClassLoader().getResource(” “).getPath();(重点)使用这个方法可以获取文件的绝对路径。(适用于各种操作系统)
    • 哈哈,看不懂?别急解释一下上述程序:
    • Thread.currentThread()当前线程对象
    • getContextClassLoader()使对象的方法,可以获取当前线程的类加载器对象。
    • getResource()这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源。
    • 示例:
public class Path1 {
    public static void main(String[] args) {
        String path = Thread.currentThread().getContextClassLoader().getResource("File2").getPath();
        System.out.println(path);
    }
}
  • 从执行结果可以看出:源文件和字节码文件存放在不同的位置,执行后与之对应的文件保存在out/production目录下(IDEA中)。
  • 直接以流的方式读取文件,省去获取绝对路径的环节。
    • InputStream reader = Thread.currentThread().getContextClassLoader().getResoutceAsStream(“文件名“(类路径下))
    • 举个栗子:
package Day2;


import java.io.InputStream;
import java.util.Properties;

public class Path2 {
    public static void main(String[] args) throws Exception {
        //创建一个流,读取文件
        InputStream reader = Thread.currentThread().getContextClassLoader().getResourceAsStream("File2");
        //创建一个Properties集合,保存数据
        Properties pro = new Properties();
            pro.load(reader);//将文件数据加载到集合中
            reader.close();
        String f =pro.getProperty("NewName");
        Class bb = Class.forName(f);//反射获得Class
        Object cc = bb.newInstance();//通过Class实例化对象
        System.out.println(cc);

    }
}
class User2 {
    public User2() {
        System.out.println("无参构造方法!");
    }
}
  • 资源绑定器(重点)
    • 在java.util包下,便于获取属性配置文件的内容。
    • 使用这种方式也必须将属性配置文件放置类路径下。
    • 只能绑定xxx.properties文件。并且不用写路径的扩展名。
    • 此方法简洁简单。所以,以后就用这个方法就好了,集合配合流的方法就不需要用了。
    • 举个栗子:
import java.util.ResourceBundle;

public class Path3 {
    public static void main(String[] args) {
        ResourceBundle bundle = ResourceBundle.getBundle("File2");
        String Name = bundle.getString("NewName");
        System.out.println(Name);
    }
}

3.反射Field(属性)获取字节码

  • 反射属性之前,应该获取整个类。(Class.forName(” “)方法获取。)
  • 获取整个类中的所有的Field。
    • **getFields()方法;用之前获取的整个类调用;**其获取所有public修饰的属性,返回到Field[]数组中。
    • **getDeclaredFields()方法;获取所有属性。**不受修饰符现在,返回到Field[]数组中。
    • getName()方法:可以获取属性名字。对于类也有名字,也用这个方法,对于反射获得的类还有simpleName()方法,来获取简单名字。
    • **getType()方法:获取属性的类型,返回值为类,**然后我们再通过这个得到的类用getName方法获取属性类型的名字。
    • getModifiers()方法:获取属性的修饰符列表,返回的是int类型,每个修饰符的数字是,修饰符的代号。我们可以用toString方法(Modifier.toString),Modefier为reflect的子类,返回一个字符串类型。
    • 举个栗子:
package Day1;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class FieldTest1 {
    public static void main(String[] args) {
        Class First=null;

        try {
            First = Class.forName("Day1.Student");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        Field[] fields = First.getDeclaredFields();
        System.out.println(fields.length);
        for(Field arg:fields) {
            int a = arg.getModifiers();
            System.out.println(Modifier.toString(a));//Modefier为reflect的子类
            Class ff = arg.getType();
            System.out.println(ff.getName());
            System.out.println(arg.getName());
        }
    }
}
class Student{
    public String name;
    private int age;
    protected double scroe;
    boolean flag;
}
  • 通过反射机制,反编译一个类的属性Field
  • 怎么通过反射机制访问对象属性?(赋值,获取属性的值)
    • 首先获取整个Class。
    • 然后通过Class实例化对象。
    • 获取对象的属性;依据名字。
    • 属性.set(对象,222)方法:以获取的属性调用,给对象的属性赋值。
    • 反射机制代码复杂,但灵活。
    • 读取属性的值:属性.get(对象)。
    • 举个栗子:
package Day1;

import java.lang.reflect.Field;

public class FieldTest2 {
    public static void main(String[] args) {
        Class fio = null;//获取Class
        Object ff = null;
        {
            try {
                fio = Class.forName("Day1.Student");
                ff = fio.newInstance();//实例化对象
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
        //获取属性
        Field fl = null;
        {
            try {
                fl = fio.getDeclaredField("name");//通过类获取属性,无法通过对象获取
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        }
        //给fio对象的fl属性赋值
        try {
            fl.set(ff,"sfdsa");
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        //获取属性的值
        try {
            System.out.println(fl.get(ff));
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

  • 可以用上述方法访问私有属性吗?
    • 不可以?当时我们可以用:属性.setAccessible(true)方法打破封装。
    • 因此安全性差。

4.反射Mothod(方法)

反射Mothod

  • 首先获取Class。
  • 获取所有的Method方法。
  • Method[] methods=类.getDeclaredMethod();方法:获取所有的Method.
  • 获取方法的返回值类型:方法.getReturnType().getSimpleName();
  • 获取修饰符列表:Modifer.toString(方法.getModifiers();
  • 获取方法参数的类型:方法.getParameterTypes();返回Class[]数组。再调用getSimpleName()即可。
  • Mothod的反编译(不要求掌握,有兴趣的小伙伴可以自行了解一下)
  • 通过反射机制怎么调用对象的方法。(重点)
  • 1.获取Calss,并用Calss实例化对象。
  • 2.获取Method:使用Method logMethod = 类.getDeclaredMethod(“方法名“,类型名.class,类型名.class);方法。
  • 3.调用方法:(对象,方法名,参数,返回值)
    • Object value = 方法.invoke(“对象”,“参数值”,“参数值”);invoke方法返回一个Object类型。
    • 举个栗子:
package Day1;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MethodTest1 {
    public static void main(String[] args) {
        Class fos =null;
        try {
             fos = Class.forName("Day1.Student1");//获取Class
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        Object fis =null;
        try {
            fis = fos.newInstance();//创建对象
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        Method method =null;
        try {
             method = fos.getDeclaredMethod("GetScore",String.class,double.class);//获取方法
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        Object value  = null;
        try {
            value = method.invoke(fis,"jia",520);//调用方法

        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        System.out.println(value);
    }
}
class Student1{
    private String name;
    private  int age;
    private double score;
    public String   GetScore(String name,double score){
        if(score>500){
            System.out.println("恭喜成功!");
        }
        else {
            System.out.println("继续加油!");
        }
        return  name;
    }
}
  • 反射机制的优点:让代码具有通用性,可变化的内容都是写到配置文件中,将来修改配置文件之后,创建的对象不一样了,调用的方法也不同了,但是java代码不需要做任何修改。非常的灵活。

5.反射Constructor(构造方法)

  • 反射机制怎么调用构造方法?
  • 使用反射机制创建对象?
    • 1.调用无参构造方法。(newInstance方法)
    • 2.调用有参构造方法(也可以用这种方法调用无参构造方法)。
    • 第一步:先获取这个有参构造方法;Constructor con = 类.getDeclaredConstructor(int.class,String.classs)第二步:调用构造方法new对象。Object aa =con.newInstance(参数列表)。
    • 举个栗子:
package Day1;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class ConstructorTest1 {
    public static void main(String[] args){
        Class fio=null;
        Constructor con=null;
        Object cc = null;
        try {
            //获取类Class
            fio = Class.forName("Day1.Student3");
            //获取构造方法,参数很重要
            con =fio.getDeclaredConstructor(int.class,String.class);
            cc= con.newInstance(11,"Jia");//调用构造方法new对象,这种方法没有过期
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        //输出对象
        System.out.println(cc);//自动调用toString方法

    }
}
class Student3{
    private  int age;
    private  String name;
//构造方法
    public Student3() {
    }

    public Student3(int age, String name) {
        this.age = age;
        this.name = name;
        System.out.println("调用成功!");
    }
}

6.通过反射获取父类和父接口

  • 1.获取类的Class
  • 2.获取Class父类:Class superClass = 子类.getSuperclass();
  • 获取子类实现的所有接口:Class[] interfaces = 子类.getInterfaces();方法。
  • 举个栗子:
package Day1;

public class SuperClassTest1 {
    public static void main(String[] args){
        Class fos =null;
        Class superClass=null;
        try {
            //获取类的Class
            fos= Class.forName("Day1.Student5");
            //获取父类的Class
            superClass = fos.getSuperclass();
            //实例化父类对象
            Object cc = superClass.newInstance();
            System.out.println(cc);
            //获取子类的所有接口
            Class[] interfaces = superClass.getInterfaces();
            for(Class ff : interfaces){
                //获取接口的简称,即不含包名
                System.out.println(ff.getSimpleName());
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }
}
//学生类
class Student5 extends Parents{
    private  int age;
    private  String name;
    //构造方法
    public Student5() {
    }

    public Student5(int age, String name) {
        this.age = age;
        this.name = name;
        System.out.println("调用成功!");
    }
}
//家长类
class Parents implements Study,Sport{
    public Parents() {
        super();
    }
}
//接口
interface Study{

}
interface Sport{

}

7.扩展知识

1.类加载器

  • 什么是类加载器?
    • 专门负责加载类的命令/工具。
    • ClassLoader
  • JDK中自带了3个类加载器
    • 启动类加载器(加载jre\lib\rt.jar)父
    • 扩展类加载器(母)
    • 应用类加载器
  • 假设有这样一段代码:
  • String s = “abc”;
    • 代码在开始执行之前,会将所需要的类全部加载JVM。看到以上代码,类加载器会找到String.class文件,找到就加载,那么是怎么进行加载的呢?
    • 首先通过:启动类加载器”加载。
    • rt.jar中都是Jdk最基本的核心类库。
    • 如果通过“启动类加载器”加载不到的时候:会通过”扩展类加载器“加载。
    • 注意:扩展类加载器专门加载:jre\lib\ext*.jar
    • 如果扩展类加载器没有加载到,那么会通过“应用类加载器”加载。
    • 应用类加载器:classpath中的jar包的类。(class文件)
  • java中为了保证类加载的安全,使用了双亲委派机制。
    • 优先从启动类,(父)然后是扩展类(母),最后是应用类加载器。

2.可变长度参数

  • int…args:这就是可变长度参数。个数为0到N个。
  • 可变长只能在参数的最后一个,且只能有一个。
  • 可变成长度参数可以当做数组看待,具有length属性,有下标,调用的时候可以传一个数组。

3.注解

  • 注解:或者叫做注释(Annotation)
  • 注解有什么作用?
    • 对程序的注释。
  • 注解是一个引用数据类型。编译之后也是生成xxx.class文件。
  • 怎么自定义注解呢?语法格式是怎么样的?
    • [修饰符列表] @interface 注解类型名{
      }
  • 注解怎么使用,用在什么地方?
  • 注解使用时语法格式时:@注解类型名
  • 可以用在构造方法,类,属性,接口,方法,注解等,注解可以修饰注解。
  • 自定义注解:
    • 如果注解中有属性,必须给其赋值。可以在注释的时候@注解名(属性=值),属性名为value时并且只有一个属性的时候,注解的时候可以不写value。定义属性的时候后面需要小括号,可以用default给属性赋默认值。
  • 注解的属性可以是哪些类型?
    • byte,short,int, long,double,boolean,char,float,String,枚举,Class类型,和其数组类型。如果数组中只有一个元素给其赋值时大括号可以省略。
  • 什么是元注解?
    • 用来标注“注解类型”的注解。
  • 常见的元注解:
    • Target注解:(用来标注,被注解的注解可以出现在哪个位置上,其由后面的小括号参数决定。)如:@Target({ElementType.Type,ElementType.MEYHOD})只允许该注解标注类,方法
    • Retention注解:用来标注“被注解的注解”最终保存在哪里。
    • @Retention(RetentionPolicy,SOURCE)表示只被保留在java源文件中。
    • @Retention(RetentionPolicy,CLASS)class文件中。
    • @Retention(RetentionPolicy,RUNTIME)class文件中,并能被注解。//RetentionPolicy是一个枚举类型。
  • JDK内置的注解。
  • java.lang包下:
  • 1.Override注解:只能注解方法,只给编译器参考。如果这个方法不是重写父类的方法,编译器报错。(标识性注解)
  • 2.Deprecated注解:表示某个类,方法已过时。为了告知别人某个类,方法等已过时。

反射注解

如何获取注解类的属性?
1.获取类Class
* Class dd = Class.forName(“完整类名”);
2.判断类上面是否存在特定的注解。
* if(dd(类).isAnnotationPresent(注解类名.class)
3.如果存在特定的注解,则获取注解对象。
* 注解类名 fs = (注解类名)dd.getAnnotation(注解类名.class);
4.获取注解对象的属性。
* 注解对象.属性();
如果获取方法上的注解信息?
1.获取类Class。
2.获取方法。
3.判断方法上是否存在该注解。存在的话,获取方法上的注解。调用方式为:方法.getAnnotation(注解类名.class)
4.再通过得到的注解获得属性。
注解在开发中的使用场景:
举个栗子:假设存在一个注解,只能出现在类上面;当这个类有这个注解:类必须有int类型的属性,否则报异常。

原创不易,码字不易,点个赞再走吧!

版权声明:本文为huangjiahuan1314520原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/huangjiahuan1314520/p/12698754.html