反射机制基础详解

java反射机制笔记

一:反射基础

  1. 反射机制有两个好处
    1. 通过反射可以将.class文件反编译成.java文件
    2. 通过反射机制访问对象的方法,属性,构造方法等。
  2. 关于Class类的使用3种方法

    注:下面的Class类型的对象等于出来的,就是说所指向的都是类

    1. 通过forName();

      Class a = Class.forName("your class path");//里面写的是完整的类的路径
    2. 通过类名.class

      Class b = Student.class

      这里的b就是Student类

    3. 通过类的对象的.getClass()方法
      这个方法是Object对象所拥有的,也就是所有对象所共有的,当然,它只能通过对象去调用

      Student student = new Student();
      Class c = student.getClass();
    4. 区别

      发现一个问题
      假如有这样一个代码

      class A {
          static {
              System.out.println("A....");
          }
      }
      public class demo1{
      public static void main(String [] args) throws Exception{
          //使用forName()方式加载类
          Class.forName("A");//会输出A....,这就代表A中静态语句块被执行,也就是说明,A这个类被加载到jvm中了
      
          //但是我们使用.class时候
          Class a = A.class;//这里是不会输出A....的,就代表并没有加载到jvm中
      
          //使用对象调用.getClass()方法的时候肯定会被执行,因为声明对象的时候就已经被调用了
          A b = new A();
          Class c = b.getClass();
      
      
      }
      }

    注意:forName()会将类加载到jvm中,而.class不会

  3. 通过forName() 和newInstance()来实例化对象
    public class Student {
    
        String name ;
        static {
            System.out.println("静态语句块被执行");
        }
        Student(){
            System.out.println("无参数构造方法被执行");
        }
        Student(String name){
            this.name= name;
            System.out.println("有参数构造方法被执行");
        }
        public void fun() {
            System.out.println("方法");
        }
    
    }
        import java.util.Date;
        import java.text.SimpleDateFormat;
        public class MainTest {
    
            public static void main(String[] args) throws Exception {
                //使用.forName()来实例化对象的步骤
    
                //这里的a 就是指向了类Student的地址,因为加载到jvm中的类只能有一个,所以,无论forName多少次,指向的都是同一个地址
                Class a =Class.forName("Student");
                //实例化对象a
                Object b = a.newInstance();
                //判断这个b是否属于Stduent类型的对象,如果属于,就强制类型转化
                if(b instanceof Student) {
                    Student c =(Student)b;
                    c.fun();
                }
    
                //同样,我们对Date做次试验
                Class date = Class.forName("java.util.Date");
                Object oDate = date.newInstance();
                if(oDate instanceof Date) {
                    Date rDate  = (Date)oDate;
                    System.out.println(new SimpleDateFormat("YYYY-MM-dd HH-mm-ss").format(rDate));
                }
    
    
    
            }
    
        }

    注意:使用newInstance()方法的一定要是个Class对象,并且返回的是Object对象,需要做强制类型转换。

二:可变长参数

  1. 可变长参数使用 int… yourname 的方式来实现
  2. 一个方法中只能有一个可变长参数,且如果还有其它单个参数的时候可变长参数必须放在最后面
  3. 形参是可变长参数,实参除了通过多个相同数据类型的对象来传给形参,还可以直接传一个数组给形参

代码中我写了详细的注释

public class Student {

    String name ;
    static {
        System.out.println("静态语句块被执行");
    }
    Student(){
        System.out.println("Student无参数构造方法被执行");
    }
    Student(String name){
        this.name= name;
        System.out.println("有参数构造方法被执行");
    }
    public void fun() {
        System.out.println("方法");
    }

}
import java.util.Date;
public class CanLong {

    //可变长参数的使用

    //如果只是一个实参传递过来,会精确匹配对应的方法,而不是调用可变长参数
    public static void m1(int i) {
        System.out.println(i);
    }
    public static void m1(int... i) {
        System.out.println("可变长参数执行");
    }
    //可变长参数能否变成一个数组来输出
    public static void stringTest(String... str) {
        //直接使用数组的形式将str这个可变长参数来输出
        for(int i=0;i<str.length;i++) {
            System.out.println(str[i]);
        }

    }

    //可变长参数使用的位置问题
    //一个方法里面只能出现一个可变长参数,如果和其他的单个类型一起出现,可变长参数的位置一定要放在最后面
    public static void stringTest2(int i,String... str) {
        System.out.println(i);
        for(int s=0 ;s<str.length;s++) {
            System.out.println(str[s]);
        }

    }

    //在可变长参数中对Class对象的使用
    public static void classTest(Class... one) throws Exception {
        for(int i=0;i<one.length;i++) {
            System.out.println(one[i].newInstance());

        }
    }


    public static void main(String[] args) throws Exception {
        //可变长参数匹配问题
        m1(2,3,4);//结果是输出可变长参数执行
        m1(1);//结果是输出1

        //可变长参数传参和传数组问题
        stringTest("编程","学习","发疯");//输出编程 学习 发疯
            //传数组
            String[] str = {"编程","学习","发疯"};
            stringTest(str);//输出编程 学习 发疯,也就是说,可变长参数可以传多个相同类型的参数,也可以传递对应类型的数组

        //使用位置的问题
            stringTest2(55,"学习","编程","很快乐");//输出55 学习 编程 很快乐

        //Class对象使用的问题
            classTest(Student.class,Date.class);

        }
    }

三:Properties属性对象和io实现对配置文件的加载和设置

  1. 写法与规则

    一般在面对需要变换的参数较多时候,比如数据库的用户名和密码,我们会单独建立一个配置文件,这个文件的后缀可以自己命名的,但是,依据java规范,最好是写成.properties结尾
    *properties文件的规范
    使用类似 username=什么神奇 的方式
    或者 username:什么神奇
    或者 username 什么神奇 (中间是一个空格)

    如果出现 user:name=什么神奇 的情况,永远只是第一个符号起作用,这里起作用的就是user后的”:”了,这样我们的key的值 就是user了

  2. 读取properties文件的方法
    • 首先是创建Properties对象
    • 然后使用FileInputStream去读properties文件
    • 转换FileInputStream字节流对象成utf-8形式(就是使用InputStreamReader(new FileInpurStream(),"utf-8"))防止中文乱码,(当然也可以使用中间流的形式BufferedReader),
    • 将上面的InputStreamReader对象加载到properties对象中 .load()
    • 通过properties对象的 .getProperty("key")方法去得到value

    下面是代码

    import java.io.FileInputStream;
    import java.io.InputStreamReader;
    import java.util.Properties;
    
    public class PropertiesDemo1 {
    
        public static void main(String[] args) throws Exception {
            //创建Properties对象
        Properties pro = new Properties();
        //创建文件字节输入流
        FileInputStream stream=  new FileInputStream("G:\\aaa\\uers.properties");
        //因为碰到中文乱码问题,我们将用到InputStreamReader来将上面的字节流,转换成utf-8的字节流
        InputStreamReader reader = new InputStreamReader(stream,"utf-8");
        //将流加载到Properties对象中
        pro.load(reader);
        //关闭流
        reader.close();
        stream.close();
        //获得properties文件中的信息
        String username =pro.getProperty("username");
        String password =pro.getProperty("password");
        System.out.println(username+" "+password);
    
        }
    }
  3. 将Properties对象写入到文件中

    直接上代码,代码中写好了注释,但是,这里的.store()方法里面,第二个参数是生成的文件中第一行的注释信息,这里我无法转换成utf-8

    import java.io.FileOutputStream;
    import java.io.OutputStreamWriter;
    import java.util.Properties;
    
    public class PropertiesDemo2 {
    
        public static void main(String[] args) throws Exception{
            //创建Properties对象
            Properties pro =  new Properties();
            //创建字节输出流对象
            FileOutputStream stream = new FileOutputStream("G:\\aaa\\write.properties");
            //将这个字节流转换成utf-8的字符流
            OutputStreamWriter Wstream = new OutputStreamWriter(stream, "utf-8");
    
            //设置Properties对象中的数据
            pro.setProperty("username", "什么神奇");
            pro.setProperty("password", "yz1998");
            //调用Properties对象的store方法绑定到一个字节输出流上
            String s = "这是一个配置文件";
            pro.store(Wstream,s);//第一个参数 是要绑定的输出流,第二个是注释,会显示在文件的第一行,这里的注释的中文是unicode字符
    
            Wstream.close();
            stream.close();
        }
    
    }

四:反射机制+io+Properties联合使用

直接上代码,代码中写好了注释

import java.io.FileInputStream;
        import java.io.IOException;
        import java.io.InputStreamReader;
        import java.util.Properties;

    public class PropertiesDemo3 {

        public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
            //反射机制+IO+Properties的联合使用
            //创建properties对象
            Properties pro = new Properties();
            //创建字符输入流
            FileInputStream stream  = new FileInputStream("G:\\aaa\\test.properties");
            InputStreamReader reader = new InputStreamReader(stream,"utf-8");
            //加载到pro对象中
            pro.load(reader);
            //平常的输出方式
            String username =pro.getProperty("username");
            System.out.println(username);
            //使用反射机制来动态获取配置文件中的类
            String className =pro.getProperty("className");
            Class relClassName = Class.forName(className);
            Object name = relClassName.newInstance();
            System.out.println(name);


            reader.close();

        }

    }

五:使用 java.lang.reflect.Field; java.lang.reflect.Modifier;去获得反编译一个类,去获得里面的属性

  1. 获得类名和改类的修饰符
    Class c = Class.forName(Student);
    c.getName();//获取类名
    c.getModifiers();//这个返回的是修饰符的整数表示
    Modifier.toString(c.getModifiers());//获取修饰符的String形式,这才会将对应的修饰符的整数转换成字符串
  2. 获得一个类中的所有属性
    //将所有的属性变成一个数组
    Field[] field = c.getDeclaredFiels();
    //如果是以下
    Field[] field = c.getFields();//这是只获得public修饰的属性
    
    //获取某属性的修饰符
    Modifiers.toString(field.getModifiers());
     //获取属性的类型
    field.getType().getSimpleName();//如果是.getName()的话,就会连同包一起进行输出
    //获取属性名
    field.getName();

下面来看完整的代码

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

public class fanbianyeDemo4 {

    public static void main(String[] args) throws ClassNotFoundException {
        Class c = Class.forName("Student");
        //获得Student类中的所有属性
        Field[] fields = c.getDeclaredFields();
        //反编译
        StringBuffer sb = new StringBuffer();
        sb.append(Modifier.toString(c.getModifiers())+" class "+c.getName()+"{\n");
        for(Field field :fields) {
            sb.append("\t");
            sb.append(
            Modifier.toString(field.getModifiers())+" "+
            field.getType().getSimpleName()+" "+
            field.getName()+";\n");
        }
        sb.append("}");
        System.out.println(sb);

    }

}

这样如果联合properties,就能随意反编译你想要的类的代码

六:得到和设置某个类中的属性值

明确以下几点
1. 为类中属性赋值需要通过对象实例化,然后通过对象才能调用属性或者是方法为属性赋值(当然静态的不在考虑中)
比如

Student student = new Student();
student.name="什么神奇";

2. 引出以下方法

    //对某个特定属性的赋值
    //首先就是要获得对应的属性
    Class c = Class.forName("Student");
    //获取这个类中的某个属性
    Field field = c.getDeclaredField("name");
    //因为有可能是private的,我们需要打破封装
    field.setAccessible(true);
    //还记得我们上面说过,为某个属性赋值,我们需要一个对象才能对属性进行操作
    //将Student类进行实例化
    Object cl = c.newInstance();
    field.set(cl,"什么神奇");//第一个参数是要赋值的对象,第二个是要给这个属性赋上什么值,这个方法会返回空

    //获取某个属性的值
    field.get(cl);//代表的是获取cl对象的name属性的值,返回的是一个Object,可以强制转化

下面是完整的代码:

        import java.io.FileInputStream;
        import java.io.InputStreamReader;
        import java.lang.reflect.Field;
        import java.util.Properties;

    public class GetOneAttributeDemo5 {

        public static void main(String[] args) throws Exception{
            //下面是获得某个类中的某个属性的值,和设置某个类中某个属性的值
            //创建Properties对象
            Properties pro = new Properties();
            //创建输入流
            FileInputStream stream = new FileInputStream("G:\\aaa\\test.properties");
            //将字符流转换成utf-8的字符流
            InputStreamReader reader = new InputStreamReader(stream,"utf-8");
            //将字符输入流加载到Properties对象中
            pro.load(reader);

            String className =pro.getProperty("className");

            Class c = Class.forName(className);
            //获取名字叫做name的属性
            Field field  = c.getDeclaredField("name");
            //为该属性赋值
            //首先凡是为某个属性赋值都需要对象实例化来进行操作
            Object k = c.newInstance();
            //因为name属性是private的,所以必须先打破封装,不然,获取不了private的属性值
            field.setAccessible(true);
            //设置属性值
            field.set(k, "什么神奇");
            //得到属性值
            String  getString =(String)field.get(k);
            System.out.println(getString);
        }

    }

七:反编译某个类中所有的方法

因为操作和field极其相似,我这里就写出了要注意的地方。
1. 方法是有返回值的,这里不能直接用getType()了,要使用getReturnType()
2. 方法是有形参的参数列表的,所有相对于属性来说,多了一个东西
使用
Class[] classes = method.getParameterTypes();获得这个方法中的形参的类,放在Class数组中
然后使用classes[i].getSimpleName()去得到对应的类的名字(形式参数类型名字)

下面来看完整的代码

import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Properties;

public class fanbianyiMethodDemo6 {

    public static void main(String[] args) throws Exception{
        //获取某个类中所有的方法
        Properties pro = new Properties();
        FileInputStream stream = new FileInputStream("G:\\aaa\\test.properties"); 
        InputStreamReader reader = new InputStreamReader(stream,"utf-8");
        pro.load(reader);
        String className= pro.getProperty("className");
        Class c = Class.forName(className);

        StringBuffer buffer = new StringBuffer();
        buffer.append(Modifier.toString(c.getModifiers())+" class "+ c.getName()+"{\n");
        //获取方法数组
        Method[] methods = c.getDeclaredMethods();

        for(Method method :methods) {
            buffer.append("\t"+Modifier.toString(method.getModifiers())+" ");
            buffer.append(method.getReturnType().getSimpleName()+" ");
            buffer.append(method.getName()+"(");
            Class[] classes = method.getParameterTypes();
            for(int i=0;i<classes.length;i++) {
                if(i==classes.length-1)
                    buffer.append(classes[i].getSimpleName());
                else
                    buffer.append(classes[i].getSimpleName()+",");
                }
            buffer.append("){}\n");
        }

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

}

八:通过发射机制,来调用某个类中的方法

注意:
1. 和设置属性相同,使用方法,也需要对象,所以,这里也肯定要 用到对象的
2. 获取的方法,在使用getDeclaredMethod("Method name",Class...args)
第一个参数是要获取的方法名,第二个是可变长参数,这里写的是改方法的所有形式参数列表
3. 使用该方法
凡是用到方法都要使用到对象
声明对象
Object k = c.newInstance();
通过invoke方法
method.invoke(k,Object...)第二个参数是Object类型的可变长参数,相当于给方法传参数值,对应要传给方法的实参

下面来看完整的代码

public class Student {

    private String name ;
    private static String usrname;
    public int a ;
    static {
        System.out.println("静态语句块被执行");
    }
    Student(){
        System.out.println("Student无参数构造方法被执行");
    }
    Student(String name){
        this.name= name;
        System.out.println("有参数构造方法被执行");
    }
    public void fun() {
        System.out.println("方法"+name);
    }
    public String getStrings(String a,String b) {
        return a+""+b;
    }
    private String setK() {
        return "yy";
    }

}
import java.lang.reflect.Method;

public class getUseOneMethodDemo7 {

    public static void main(String[] args) throws Exception {
        Class c = Class.forName("Student");
        Method method= c.getDeclaredMethod("getStrings", String.class,String.class);
        Object k = c.newInstance();
        String str =(String)method.invoke(k,"什么神奇","很天才");
        System.out.println(str);
    }

}

九:通过反射机制获得某个类中的构造方法

需要知道的一点,就是Constructor关键字通过Constructor[] con = c.getConstructors();来获得里面所有的构造方法,放到数组中其它的,与前面的获得方法的形式差不多

直接看具体代码

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;

public class GetConstructor {

    public static void main(String[] args) throws Exception{
        //反编译某个类的构造方法
        Class c = Class.forName("java.lang.String");
        //得到该类中的所有构造方法
        Constructor[] cons = c.getDeclaredConstructors();
        StringBuffer sb = new StringBuffer();
        sb.append(Modifier.toString(c.getModifiers())+" class "+ c.getSimpleName()+"{\n");
        //循环迭代数组Con
        for(Constructor con : cons) {
            sb.append("\t");
            sb.append(Modifier.toString(con.getModifiers())+" ");
            sb.append(c.getSimpleName()+" ");
            sb.append("(");
            //循环迭代形参列表
            Class[] parameterTypes  =con.getParameterTypes();
            for(int i=0;i<parameterTypes.length;i++) {
                if(i==parameterTypes.length)
                    sb.append(parameterTypes[i].getSimpleName());
                else
                    sb.append(parameterTypes[i].getSimpleName()+",");
            }

            sb.append("){}\n");
        }

        sb.append("}");

        System.out.println(sb);
    }

}

十:通过某个有参数的构造方法实例化对象

注:
我们上面是通过

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

去实例化对象,但是,这只针对于无参数的构造方法的实例化,如果是有参数的呢?我们就需要通过Constructor 对象去实例化了,看完整的代码

import java.lang.reflect.Constructor;

public class GetOneConstructorDemo9 {

    public static void main(String[] args) throws Exception{
        //获得某个类的某一个特定的构造方法
        Class c = Class.forName("Uers");
        //里面的参数写的是某个构造方法对应的形式参数的类型,因为构造方法名字都一样,不一样的只是参数
        //类型和个数,所以没必要传名字了
        Constructor con = c.getDeclaredConstructor(String.class,int.class);
        //不同于前面提到过的方法的调用,
        //方法和属性的调用都需要用到对象,因此,必须要将类通过c.newInstance()实例化,但是
        //这个实例化也只对无参数的构造方法进行的,如果要用有参数的构造方法
        //那么必须要通过con 去实例化了
        Object obj = con.newInstance("什么神奇",20);//毫无疑问,实例化里面肯定传实参进去
        System.out.println(obj);
    }

}

class Uers {
    String name;
    int age;
    public Uers(String name ,int age){
        this.name=name;
        this.age=age;
    }
    public String toString() {
        return this.name+" : "+this.age;
    }
}

十一:获得父类的类名和接口

  1. c.getSuperclass()
  2. c.getInterfaces()

看下面的代码

public class getSuperClassAndInterfaceDemo10 {

    public static void main(String[] args) throws Exception{
        Class c = Class.forName("java.lang.String");
        Class superClass =c.getSuperclass();
        System.out.println(superClass.getName());

        Class[] inter = c.getInterfaces();
        for(Class a :inter) {
            System.out.println(a.getName());
        }
    }

}

反射机制基础告一段落

猜你喜欢

转载自blog.csdn.net/qq_26024867/article/details/82528361