学习Java第41天,反射的理解与Class类

通过使用反射前后的例子的对比,回答:

1. 面向对象中创建对象,调用指定结构(属性、方法)等功能,可以不使用反射,也可以使用反射。请问有什么区别?

不使用反射,我们需要考虑封装性。比如:出了Person类之后,就不能调用Person类中私有的结构
使用反射,我们可以调用运行时类中任意的构造器、属性、方法。包括了私有的属性、方法、构造器。


2. 以前创建对象并调用方法的方式,与现在通过反射创建对象并调用方法的方式对比的话,哪种用的多?
   场景是什么?

> 从我们作为程序员开发者的角度来讲,我们开发中主要是完成业务代码,对于相关的对象、方法的调用都是确定的。
  所以,我们使用非反射的方式多一些。

> 因为反射体现了动态性(可以在运行时动态的获取对象所属的类,动态的调用相关的方法),所以我们在设计框架的时候,
  会大量的使用反射。意味着,如果大家需要学习框架源码,那么就需要学习反射。

  框架 = 注解 + 反射 + 设计模式

3. 单例模式的饿汉式和懒汉式中,私有化类的构造器了! 此时通过反射,可以创建单例模式中类的多个对象吗?

是的!


4. 通过反射,可以调用类中私有的结构,是否与面向对象的封装性有冲突?是不是Java语言设计存在Bug?

不存在bug!

封装性:体现的是是否建议我们调用内部api的问题。比如,private声明的结构,意味着不建议调用。
反射:体现的是我们能否调用的问题。因为类的完整结构都加载到了内存中,所有我们就有能力进行调用。
public class ReflectionTest {
    /*
     * 使用反射之前可以执行的操作
     * */
    @Test
    public void test1(){

        //1.创建Person类的实例
//        public Person()
        Person p1 = new Person();
        System.out.println(p1);

        //2.调用属性
        //public int age;
        p1.age = 10;
        System.out.println(p1.age);

        //3.调用方法
        //public void show()
        p1.show();

    }

    /*
     * 使用反射完成上述的操作
     * */
    @Test
    public void test2() throws Exception{
        //1.创建Person类的实例
//        public Person()
        Class clazz = Person.class;
        Person p1 = (Person)clazz.newInstance();
        System.out.println(p1);

        //2.调用属性
        //public int age;
        Field ageField = clazz.getField("age");
        ageField.set(p1,10);
        System.out.println(ageField.get(p1));

        //3.调用方法
        //public void show()
        Method showMethod = clazz.getMethod("show");
        showMethod.invoke(p1);

    }

    /*
     * 出了Person类之后,就不能直接调用Person类中声明的private权限修饰的结构(属性、方法、构造器)
     * 但是,我们可以通过反射的方式,调用Person类中私有的结构  ---> 暴力反射
     *
     * */
    @Test
    public void test3() throws Exception {
        //1. 调用私有的构造器,创建Person的实例
        //private Person(String name, int age)
        Class clazz = Person.class;
        Constructor cons = clazz.getDeclaredConstructor(String.class,int.class);
        cons.setAccessible(true);
        Person p1 = (Person) cons.newInstance("Tom",12);
        System.out.println(p1);

        //2. 调用私有的属性
        //private String name;
        Field nameField = clazz.getDeclaredField("name");
        nameField.setAccessible(true);
        nameField.set(p1,"Jerry");
        System.out.println(nameField.get(p1));

        //3. 调用私有的方法
        //private String showNation(String nation)
        Method showNationMethod = clazz.getDeclaredMethod("showNation",String.class);
        showNationMethod.setAccessible(true);
        String info = (String) showNationMethod.invoke(p1,"CHN");
        System.out.println(info);
    }
}
1. Class类的理解(掌握)
(如下以Java类的加载为例说明)
针对于编写好的.java源文件进行编译(使用javac.exe),会生成一个或多个.class字节码文件。接着,我们使用
java.exe命令对指定的.class文件进行解释运行。这个解释运行的过程中,我们需要将.class字节码文件加载(使用类的加载器)
到内存中(存放在方法区)。加载到内存中的.class文件对应的结构即为Class的一个实例。

比如:加载到内存中的Person类或String类或User类,都作为Class的一个一个的实例

Class clazz1 = Person.class;  //运行时类
Class clazz2 = String.class;
Class clazz3 = User.class;
Class clazz4 = Comparable.class;

说明:运行时类在内存中会缓存起来,在整个执行期间,只会加载一次。

2. 体会:Class看做是反射的源头

3. 获取Class实例的几种方式(掌握前三种)
见代码


4. Class的实例都可以指向哪些结构呢?(熟悉)
简言之,所有Java类型!
(1)class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
(2)interface:接口
(3)[]:数组
(4)enum:枚举
(5)annotation:注解@interface
(6)primitive type:基本数据类型
(7)void



5. 类的加载过程(了解)
过程1:类的装载(loading)
将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成

过程2:链接(linking)
> 验证(Verify):确保加载的类信息符合JVM规范,例如:以cafebabe开头,没有安全方面的问题。
> 准备(Prepare):正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
> 解析(Resolve):虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。

过程3:初始化(initialization)
执行类构造器<clinit>()方法的过程。
类构造器<clinit>()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。



5. 关于类的加载器(了解、JDK8版本为例)
5.1 作用:负责类的加载,并对应于一个Class的实例。

5.2 分类(分为两种):
> BootstrapClassLoader:引导类加载器、启动类加载器
     > 使用C/C++语言编写的,不能通过Java代码获取其实例
     > 负责加载Java的核心库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路径下的内容)

> 继承于ClassLoader的类加载器
    > ExtensionClassLoader:扩展类加载器
            > 负责加载从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下加载类库
    > SystemClassLoader/ApplicationClassLoader:系统类加载器、应用程序类加载器
            > 我们自定义的类,默认使用的类的加载器。
    > 用户自定义类的加载器
            > 实现应用的隔离(同一个类在一个应用程序中可以加载多份);数据的加密。

5.3 以上的类的加载器是否存在继承关系? No!

class ClassLoader{
    ClassLoader parent;

    public ClassLoader(ClassLoader parent){
        this.parent = parent;
    }
}


//测试代码:
ClassLoader loader0 = new ClassLoader();
ClassLoader loader1 = new ClassLoader(loader0);
我们就把loader0叫做loader1的父类加载器。

6. (掌握)使用类的加载器获取流,并读取配置文件信息
/*
* 需求:通过ClassLoader加载指定的配置文件
* */
@Test
public void test3() throws IOException {
    Properties pros = new Properties();

    //通过类的加载器读取的文件的默认的路径为:当前module下的src下
    InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("info1.properties");

    pros.load(is);

    String name = pros.getProperty("name");
    String pwd = pros.getProperty("password");
    System.out.println(name + ":" +pwd);
}
public class ClassTest {
    /*
     * 获取Class实例的几种方式(掌握前三种)
     * */
    @Test
    public void test1() throws ClassNotFoundException {
        //1.调用运行时类的静态属性:class
        Class clazz1 = User.class;
        System.out.println(clazz1);

        //2. 调用运行时类的对象的getClass()
        User u1 = new User();
        Class clazz2 = u1.getClass();

        //3. 调用Class的静态方法forName(String className)
        String className = "com.atguigu02._class.User"; //全类名
        Class clazz3 = Class.forName(className);

        System.out.println(clazz1 == clazz2);//true
        System.out.println(clazz1 == clazz3);//true

        //4. 使用类的加载器的方式 (了解)
        Class clazz4 = ClassLoader.getSystemClassLoader().loadClass("com.atguigu02._class.User");
        System.out.println(clazz1 == clazz4);//true
    }

    @Test
    public void test2(){
        Class c1 = Object.class;
        Class c2 = Comparable.class;
        Class c3 = String[].class;
        Class c4 = int[][].class;
        Class c5 = ElementType.class;
        Class c6 = Override.class;
        Class c7 = int.class;
        Class c8 = void.class;
        Class c9 = Class.class;

        int[] a = new int[10];
        int[] b = new int[100];
        Class c10 = a.getClass();
        Class c11 = b.getClass();
        // 只要元素类型与维度一样,就是同一个Class
        System.out.println(c10 == c11);
    }
}

ps:来源尚硅谷

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值