341 - 反射 - 2

1、Class类的理解

2、提供丰富的类

先提供一个父类和一个子类: —— 备用

父类Person:

public class Person { //作为一个父类
    private int age;
    public String name;

    private void eat(){
        System.out.println("Person---eat");
    }

    public void sleep(){
        System.out.println("Person---sleep");
    }
}

子类Student:

public class Student extends Person { //作为 子类
    //属性
    private int sno; //学号
    double height; //没有修饰符,则默认default修饰
    protected double weight;
    public double score;

    //方法
    public String showInfo(){
        return "我是一名三好学生";
    }
    private void work(){
        System.out.println("我以后会找工作--->成为码农");
    }
    //构造器
    public Student(){
        System.out.println("空参构造器");
    }
    private Student(int sno){
        this.sno = sno;
    }
    Student(int sno, double weight){
        this.sno = sno;
        this.weight = weight;
    }
}

3、获取字节码信息的四种形式

代码示例: —— 方式3最常用Class c3 = Class.forName("test2_class_.Person");

package test2_class_;

/**
 * @Auther: zhoulz
 * @Description: test2_class_ — 获取字节码信息的四种形式
 * @version: 1.0
 */
public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        //案例:以Person的字节码信息为案例
        //方式1:通过getClass()方法获取
        Person p = new Person();
        Class c1 = p.getClass();
        System.out.println(c1);

        //方式2:通过内置class属性
        Class c2 = Person.class;
        System.out.println(c2);

        //方式1和方式2得到的字节码是一个吗?是的。因为字节码信息的加载只会加载一次。
        //验证:
        System.out.println(c1 == c2); //true

        //注意:方式1和方式2 —— 不常用(因为都是通过Person类去获取的,而如果已经知道person类了,就没有必要再去获取其字节码信息了)

        //方式3:用的最多- 调用Class类提供的静态方法forName()
        Class c3 = Class.forName("test2_class_.Person");
        System.out.println(c3);

        //方式4:利用类的加载器(了解技能点)
        ClassLoader loader = Test.class.getClassLoader();//(通过Test类)获取系统的类加载器
        Class c4 = loader.loadClass("test2_class_.Person");
        System.out.println(c4);
    }
}

4、可以作为Class类的实例的种类

Class类的具体的实例:

(1)类:外部类,内部类

(2)接口

(3)注解

(4)数组

(5)基本数据类型

(6)void

验证: 

package test2_class_;

/**
 * @Auther: zhoulz
 * @Description: test2_class_
 * @version: 1.0
 */
public class Test2 {
    public static void main(String[] args) {
        /*
        Class类的具体的实例:
       (1)类:外部类,内部类
       (2)接口
       (3)注解
       (4)数组
       (5)基本数据类型
       (6)void
        */
        //依次对应上面
        Class c1 = Person.class;
        //System.out.println(c1);
        Class c2 = Comparable.class;
        //System.out.println(c2);
        Class c3 = Override.class;
        //System.out.println(c3);

        //数组
        int[] arr1 = {1,2,3};
        Class c4 = arr1.getClass();
        System.out.println(c4);
        int[] arr2 = {4,5,6};
        Class c5 = arr2.getClass();
        System.out.println(c5);
        System.out.println(c4 == c5); //结果:true .同一个维度,同一个元素类型,得到的字节码就是同一个

        Class c6 = int.class;
        System.out.println(c6);

        Class<Void> c7 = void.class;
        System.out.println(c7);
    }
}

5、获取运行时类的完整结构

(1)补充完善上面提供的丰富的类

父类Person:

//(随便)实现一个接口
public class Person implements Serializable{ //作为一个父类
    private int age;
    public String name;

    private void eat(){
        System.out.println("Person---eat");
    }

    public void sleep(){
        System.out.println("Person---sleep");
    }
}

子类Student:

package test2_class_;

import jdk.nashorn.internal.codegen.CompilerConstants;

/**
 * @Auther: zhoulz
 * @Description: test2_class_
 * @version: 1.0
 */

//使用自定义的注解 - 修饰类
@MyAnnotation(value = "hello")

//子类也实现一个接口(自定义的)
public class Student extends Person implements MyInterface{ //作为 子类
    //属性
    private int sno; //学号
    double height; //没有修饰符,则默认default修饰
    protected double weight;
    public double score;

    //方法
    //使用自定义的注解 - 修饰类
    @MyAnnotation(value = "himethod")
    public String showInfo(){
        return "我是一名三好学生";
    }
    //重载上面的showInfo()方法
    public String showInfo(int a, int b){
        return "重载方法===我是一名三好学生";
    }
    private void work(){
        System.out.println("我以后会找工作--->成为码农");
    }

    //加上default、protected修饰的方法
    void happy(){
        System.out.println("开心每一天!!!");
    }
    protected int getSno(){
        return sno;
    }

    //构造器
    public Student(){
        System.out.println("空参构造器");
    }
    private Student(int sno){
        this.sno = sno;
    }
    Student(int sno, double weight){ //default 修饰的
        this.sno = sno;
        this.weight = weight;
    }
    //再加一个protected修饰的
    protected Student(int sno, double height, double weight){
        this.sno = sno;
        this.height = height;
        this.weight = weight;
    }

    //重写的方法已经有注解@Override了,还能再加吗?可以。

    //重写接口-MyInterface中的方法
    @Override
    @MyAnnotation(value = "hellomyMethod")
    public void myMethod() {
        System.out.println("我重写了接口MyInterface中的方法!");
    }

    @Override
    public String toString() {
        return "Student{" +
                "sno=" + sno +
                ", height=" + height +
                ", weight=" + weight +
                ", score=" + score +
                '}';
    }
}

自定义的接口 MyInterface :

public interface MyInterface { //自定义的接口
    //随便定义一个抽象方法
    void myMethod();
}

自定义的注解 MyAnnotation:

package test2_class_;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;

/**
 * @Auther: zhoulz
 * @Description: test2_class_
 * @version: 1.0
 */

//参考:
//@SuppressWarnings()

/*
 * @Target:定义当前注解能够修饰程序中的哪些元素
 * @Retention:定义注解的声明周期
 */
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
//原先是:RetentionPolicy.SOURCE,其中的SOURCE是指:只在编译期进行判断
//而我现在写注解的目的是为了在运行时的时候能够获取到注解,故要用RUNTIME
public @interface MyAnnotation {
    String value(); //属性,这里的属性是带 () 的
}

上面,进行了如此的丰富(加构造器、方法、自定义注解、自定义接口等)

目的:为了后面通过反射(或者说字节码信息?)获取里面的东西 。

下面开始获取里面的东西: —— 首先得获取字节码信息

(2)获取构造器和创建对象

package test2_class_;

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

/**
 * @Auther: zhoulz
 * @Description: test2_class_
 * @version: 1.0
 */
public class Test3_get_constructor {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //获取字节码信息:
        Class cls = Student.class;

        //通过字节码信息可以获取构造器
        //getConstructors只能获取当前运行时类的被public修饰的构造器
        Constructor[] c1 = cls.getConstructors(); //返回值类型为Constructor[]数组
        //遍历:
        for (Constructor c : c1){
            System.out.println(c); // public test2_class_.Student()
        }
        System.out.println("---------");
        //上面的方法只能获取public获取的构造器
        //换其他的方法:
        //getDeclaredConstructors:获取运行时类的全部修饰符的构造器
        Constructor[] c2 = cls.getDeclaredConstructors();
        //遍历:
        for (Constructor c : c2){
            System.out.println(c);
        }

        System.out.println("获取指定的构造器-----");
        //注意:不是getConstructors()了,少了一个 s
        Constructor con1 = cls.getConstructor(); // ...代表可变参数,可以不填
        System.out.println(con1);//得到的是空构造器

        //再:得到两个参数的有参构造器
        Constructor con2 = cls.getConstructor(double.class, double.class);
        System.out.println(con2);
        //上面都是针对于public的
        //如:
        //现想,得到一个参数的有参构造器:并且是private修饰的,看行不行?
        //Constructor con3 = cls.getConstructor(int.class);
        //System.out.println(con3);// —— 运行,发现报错了
        //换一个:getDeclaredConstructor
        Constructor con3 = cls.getDeclaredConstructor(int.class);
        System.out.println(con3); // —— 可以正常获取

        //总结上面:加个declare,那些非public构造器的就都能获取了

        System.out.println("创建对象-----");
        //有了构造器以后(如上面的con1、con2、con3),我就可以创建对象:
        Object o1 = con1.newInstance();
        System.out.println(o1); //因为重写了toString()方法,所以会这么输出

        //再:两个参数的构造器
        Object o2 = con2.newInstance(175.5, 180.3);
        System.out.println(o2);
    }
}

(3)获取属性和对属性进行赋值

package test2_class_;

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

/**
 * @Auther: zhoulz
 * @Description: test2_class_ —— 可参考:获取构造器和创建对象
 * @version: 1.0
 */
public class Test4_get_property {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
        //获取运行时类的字节码信息
        Class cls = Student.class;

        //获取属性
        //getFields:获取运行时类和其父类中被public修饰的属性
        Field[] fields = cls.getFields();
        for (Field f : fields){
            System.out.println(f);
        }
        System.out.println("加了declare后-----");
        //getDeclaredFields:获取运行时类中的所有属性
        Field[] declaredFields = cls.getDeclaredFields();
        for (Field f:declaredFields){
            System.out.println(f);
        }

        System.out.println("获取指定的属性-----");
        Field score = cls.getField("score");
        System.out.println(score);

        //同“获取构造器”原理:
        //想获取public外的属性:—— 用带declare的
        Field sno = cls.getDeclaredField("sno");
        System.out.println(sno);

        System.out.println("获取-属性的具体结构---");
        //分为:(以 private int sno; 为例)
        //获取属性的修饰符
        int modifiers = sno.getModifiers();//有s的原因是,有可能修饰符不为一个,如public static int sno;
        System.out.println(modifiers);//2 — (2不是个数)而是不同的数值对应不同的修饰符
        //下面获取该修饰符的名称
        System.out.println(Modifier.toString(modifiers));
        //上面可整合成一句:
        System.out.println(Modifier.toString(sno.getModifiers()));

        //获取属性的数据类型
        Class type = sno.getType();
        System.out.println(type);

        //获取属性的名字
        String name = sno.getName();
        System.out.println(name);

        System.out.println("给属性赋值----");
        //现获取指定的属性
        Field sco = cls.getField("score");
        //通过set()方法赋值的时候,处理指定属性、设置属性值外,还要指定:给哪个对象的属性赋值
        //即:给属性赋值:(给属性设置值,必须要有对象)
        Object obj = cls.newInstance();
        sco.set(obj,66);//给obj这个对象的score属性设置具体的值,这个值为66
        System.out.println(obj);
    }
}

(4)获取方法和调用方法

package test2_class_;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
 * @Auther: zhoulz
 * @Description: test2_class_
 * @version: 1.0
 */
public class Test5_get_method {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        //获取字节码信息
        Class cls = Student.class;

        System.out.println("获取方法-----");
        //getMethods:获取运行时类的方法还有所有父类中的方法(被public修饰)
        Method[] methods = cls.getMethods();
        for (Method m:methods){
            System.out.println(m);
        }

        System.out.println("加了declare后-----");
        //getDeclaredMethods:获取运行时类(注意:没有父类的了)中的所有方法:
        Method[] declaredMethods = cls.getDeclaredMethods();
        for (Method m:declaredMethods){
            System.out.println(m);
        }
        //接下来,同样,要获取指定的方法
        System.out.println("获取指定的方法----");
        Method showInfo1 = cls.getMethod("showInfo");
        System.out.println(showInfo1);
        Method showInfo2 = cls.getMethod("showInfo", int.class, int.class);
        System.out.println(showInfo2);

        //同样,上面是针对于public的,下面要获取非public的方法
        //加上declare的就行了
        Method work = cls.getDeclaredMethod("work",int.class);
        System.out.println(work);

        System.out.println("获取方法的具体结构-----");
        /*
        @注解
        修饰符 返回值类型  方法名(参数列表) throws XXXXX{}
        */
        //名字
        System.out.println(work.getName());
        //修饰符
        int modifiers = work.getModifiers();
        System.out.println(Modifier.toString(modifiers));
        //返回值
        System.out.println(work.getReturnType());
        //参数列表
        Class[] parameterTypes = work.getParameterTypes();
        for (Class c:parameterTypes){
            System.out.println(c);
        }

        System.out.println("获取方法的注解------");
        //先获取方法myMethod() —— 其有两个注解
        Method myMethod = cls.getMethod("myMethod");
        Annotation[] annotations = myMethod.getAnnotations();
        //数组,遍历
        for (Annotation a:annotations){
            System.out.println(a); //只获取了一个,因为这个注解是RUNTIME的,运行时可获取
                                   //而设置了SOURCE的获取不到,SOURCE代表在编译器进行的一个检验
        }

        System.out.println("获取异常------");
        Class[] exceptionTypes = myMethod.getExceptionTypes();
        for (Class c:exceptionTypes){
            System.out.println(c);
        }

        //获取完后,要开始调用了
        System.out.println("方法的调用了----");
        //同样,首先要有一个对象
        Object o = cls.newInstance();
        myMethod.invoke(o);//调用o对象的mymethod方法,有参数记得传进去

        //showInfo2.invoke(o,77,88); //showInfo()方法有返回值
        System.out.println(showInfo2.invoke(o,77,88));
    }
}

(5)获取类的接口,所在包,注解

package test2_class_;

import java.lang.annotation.Annotation;

/**
 * @Auther: zhoulz
 * @Description: test2_class_ —— 获取类的接口,所在包,注解
 * @version: 1.0
 */
public class Test6_get_class {
    public static void main(String[] args) {
        //获取字节码信息
        Class<Student> cls = Student.class;
        //System.out.println(cls);

        System.out.println("获取运行时类的接口-----");
        Class<?>[] interfaces = cls.getInterfaces();
        for (Class c:interfaces){
            System.out.println(c); //interface test2_class_.MyInterface
        }
        //上面,能不能得到父类的接口?可以
        System.out.println("获取运行时类的父类的接口-----");
        //先得到父类的字节码信息
        Class<? super Student> superclass = cls.getSuperclass();
        //再得到接口
        Class<?>[] interfaces1 = superclass.getInterfaces();
        for (Class c : interfaces1){
            System.out.println(c);
        }

        System.out.println("获取运行时类所在的包-----");
        Package aPackage = cls.getPackage();
        System.out.println(aPackage);
        System.out.println(aPackage.getName());


        System.out.println("获取运行时类的注解-----");
        Annotation[] annotations = cls.getAnnotations();
        for (Annotation a : annotations){
            System.out.println(a);
        }
    }
}

6、关于反射的面试题

【1】问题1:创建Person的对象,以后用new Person()创建,还是用反射创建?

:一般情况下,创建对象还是按原先的方式。

    但是,有时候,只有在程序运行起来后,我们才知道要执行什么操作(做一件什么事),这种情况下可以用反射,体现了反射的一个动态性,此时可以用反射去创建对象。

参考:https://blog.csdn.net/weixin_39619451/article/details/113371162

【2】问题2:反射是否破坏了面向对象的封装性?

“破坏” —— 对于一些private的属性,通过反射也可以操作(调用)。

:封装是为了提高代码的安全性。如private修饰符是为了保护数据,建议外部不要用,但是仅仅是建议。另一方面,反射提出的本质原因也不是为了破坏这种封装性,它有自己的意义(动态性)。

反射机制提供了访问private修饰的属性、方法等,但是封装性(private)是不建议你用的。

(这样说也不算矛盾?)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值