Java学习第十章(三)

视频链接:https://www.bilibili.com/video/BV1Rx411876f?p=1

视频范围P810 - P22

1.反射机制概念

  1. 作用:通过Java语言中的反射机制可以操作字节码文件,优点类似于黑客(可以读和修改字节码文件);通过反射机制可以操作代码片段(class文件)
  2. 反射机制的相关类在java.lang.reflect.*;包下
  3. 反射机制相关重要类
反射机制相关重要类作用
java.lang.Class代表整个字节码,代码一个类型,代表整个类
java.lang.reflect.Method代表字节码中的方法字节码 ,代表类中的方法
java.lang.reflect.Constructor代表字节码中的构造方法字节码 ,代表类中的构造方法
java.lang.reflect.Field代表字节码中的属性字节码,代表类中的成员变量(静态变量+实例变量)

举例

//java.lang.Class:
    public class User{
      //Field
      int no;

      //Constructor
      public User(){
      }
      public User(int no){
         this.no = no;
      }

      //Method
      public void setNo(int no){
         this.no = no;
      }
      public int getNo(){
        return no;
      }
 }

2.获取Class的三种方式

2.1 第一种 Class.forName()

Class c = Class.forName(“完整类名带包名”);

  1. 静态方法
  2. 方法的参数是一个字符串
  3. 字符串需要的是一个完整类名
  4. 完整类名必须带有包名,java.lang包也不能省略
package Reflect;

public class ReflectTest01 {
    public static void main(String[] args) {
        try {
            Class c1 = Class.forName("java.lang.String");//c1代表String.class文件,或者说c1代表String类型
            Class c2 = Class.forName("java.util.Date");//c2代表Date类型
            Class c3 = Class.forName("java.lang.Integer");//c3代表Integer类型
            Class c4 = Class.forName("java.lang.System");//c4代表System类型
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

2.2 第二种 getClass()

Class c = 对象.getClass();

package Reflect;

public class ReflectTest01 {
    public static void main(String[] args) {
        Class c1 = null;
        try {
            c1 = Class.forName("java.lang.String");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        //java中任何一个对象都有一个方法:getClass()
        String s = "abc";
        Class x = s.getClass();//x代表String.class字节码文件,s代表String类型
        System.out.println(c1 == x);//true (== 判断的是对象的内存地址)
    }
}

内存演示图

在这里插入图片描述

2.3 第三种 .class

Java语言中任何一种类型,包括基本数据类型,它都有.class属性
Class c = 任何类型.class;

package Reflect;

import java.util.Date;

public class ReflectTest01 {
    public static void main(String[] args) {

        String s = "abc";
        Class x = s.getClass();

        Class z = String.class;//z代表String类型
        //Class k = Date.class;//k代表Date类型
        //Class f = int.class;//f代表int类型
        //Class e = double.class;//e代表double类型
        System.out.println(x == z);// true

    }
}

3.通过反射实例化对象

问题:获取到Class,能干什么?
:通过Class的newInstance()方法来实例化对象
注意:newInstance()方法内部实际上调用了无参数构造方法,必须保证无参数构造存在才可以

用户类

package bean;

public class User {
    public User(){
        System.out.println("无参数构造方法!");
    }

    //定义了有参数的构造方法,也要保证无参数构造方法存在
    public User(String s){
        
    }
}

测试类

package Reflect;

import bean.User;

public class ReflectTest02 {
    public static void main(String[] args) {

        //这是不使用反射机制,创建对象
        User user = new User();
        System.out.println(user);//输出为:无参数构造方法!bean.User@119d7047

        //下面这段代码是以反射机制的方式创建对象
        try {
            //通过反射机制,获取Class,通过Class来实例化对象
            Class c = Class.forName("bean.User");//c代表User类型

            //newInstance()这个方法会调用User这个类的无参数构造方法,完成对象的创建
            //重点:newInstance()调用的是无参构造,必须保证无参构造是存在的!
            Object obj = c.newInstance();

            System.out.println(obj);//输出为:无参数构造方法!bean.User@119d7047
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

4.通过读属性文件实例化对象

  1. 验证反射机制的灵活性
  2. Java代码写一遍,在不改变Java源代码的基础之上,可以做到不同对象的实例化,非常之灵活【符合OCP开闭原则:对扩展开发,对修改关闭】
  3. 后期需要学习高级框架,而在工作过程中,也都是使用高级框架,包括:ssh【Spring,Struts,Hibernate】,ssm【Spring,SpringMVC,MyBatis】
  4. 高级框架底层实现原理:都采用了反射机制,所以反射机制还是重要的,学会了反射机制有利于理解剖析框架底层的源代码

属性配置文件

className=bean.User

在这里插入图片描述
测试代码类

package Reflect;

import bean.User;

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

public class ReflectTest03 {
    public static void main(String[] args) throws Exception{
        //这种方式代码就写死了,只能创建一个User类型的对象
        User user = new User();

        //以下代码是灵活的,代码不需要改动,可以修改配置文件,配置文件修改之后,可以创建出不同的实例对象
        //通过IO流读取classinfo.properties文件
        FileReader reader = new FileReader("reflect/classinfo.properties");
        //创建属性类对象Map
        Properties pro = new Properties();//key value 都是String
        //加载
        pro.load(reader);
        //关闭流
        reader.close();

        //通过key获取value
        String className = pro.getProperty("className");
        System.out.println(className);

        //通过反射机制实例化对象
        Class c = Class.forName(className);
        Object obj = c.newInstance();
        System.out.println(obj);
    }
}

运行结果

在这里插入图片描述

5.只让静态代码块执行:forName()

  1. 如果只希望一个类的静态代码块执行,其它代码一律不执行,可以使用:Class.forName(“完整类名”);
  2. 这个方法的执行会导致类加载,类加载时,静态代码块执行
  3. JDBC技术中需要使用
package Reflect;

public class ReflectTest04 {
    public static void main(String[] args) {
        try {
            //Class.forName()这个方法的执行会导致:类加载
            Class.forName("Reflect.MyClass");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

class MyClass{
    //静态代码块在类加载时执行,并且只执行一次
    static {
        System.out.println("MyClass类的静态代码块执行了!");
    }
}

运行结果

在这里插入图片描述

6.获取类路径下文件的绝对路径

以下讲解的这种方式是通用的,但前提是:文件需要在类路径下,才能用这种方式

配置文件

className=bean.User

在这里插入图片描述

测试类代码

//解析:
//Thread.currentThread() 当前线程对象
//getContextClassLoader() 是线程对象的方法,可以获取到当前线程的类加载器对象
//getResource() 【获取资源】这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源
String path = Thread.currentThread().getContextClassLoader()
                .getResource("默认从类的根路径下作为起点").getPath();
package Reflect;

import java.io.FileReader;

public class AboutPath {
    public static void main(String[] args) throws Exception{
        //这种方式的路径缺点是:移植性差,在IDEA中默认的当前路径是project的根
        //这个代码假设离开了IDEA,换到了其它位置,可能当前路径就不是project的根了,这时这个路径就无效了
        //FileReader reader = new FileReader("reflect/classinfo2.properties");

        //接下来说一种比较通用的一种路径,即使代码换位置了,但这样编写仍然时通用的
        //注意:使用一下通用方式的前提:这个文件必须在类路径下
        //类路径:放置在src下的都是类路径【src是类的根路径】
        String path = Thread.currentThread().getContextClassLoader()
                .getResource("classinfo2.properties").getPath();//这种方式获取文件绝对路径是通用的
        //采用以上代码可以拿到一个文件的绝对路径
        System.out.println(path);
    }
}

运行结果

在这里插入图片描述

7.以流的形式返回绝对路径

当不以流的形式返回

package Reflect;

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

public class IoPropertiesTest {
    public static void main(String[] args) throws Exception {
        
        String path = Thread.currentThread().getContextClassLoader()
        .getResource("classinfo2.properties").getPath();
        FileReader reader = new FileReader(path);
        
        Properties pro = new Properties();
        pro.load(reader);
        reader.close();
        //通过key获取value
        String className = pro.getProperty("className");
        System.out.println(className);//输出为:bean.User

    }
}

当以流的形式返回

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

public class IoPropertiesTest {
    public static void main(String[] args) throws Exception {

        InputStream reader = Thread.currentThread().getContextClassLoader()
        .getResourceAsStream("classinfo2.properties");
       
        Properties pro = new Properties();
        pro.load(reader);
        reader.close();
        //通过key获取value
        String className = pro.getProperty("className");
        System.out.println(className);//输出为:bean.User

    }
}

8.资源绑定器 ResourceBundle

  1. java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容
  2. 使用以下这种方式的时候,属性配置文件xxx.properties必须放到类路径下
package Reflect;

import java.util.ResourceBundle;

public class ResourceBundleTest {
    public static void main(String[] args) {
        //资源绑定器,只能绑定xxx.properties文件,并且这个文件必须在类路径下
        //文件扩展名也必须是properties
        //并且在写路径的时候,路径后面的扩展名不能写
        ResourceBundle bundle = ResourceBundle.getBundle("classinfo2");

        String className = bundle.getString("className");
        System.out.println(className);//输出为:bean.User
    }
}

9.JDK中自带的类加载器概述

  1. 类加载器:专门负责加载类的命令/工具 ClassLoader
  2. JDK中自带了3个类加载器:启动类加载器、扩展类加载器、应用类加载器
  3. 举例:假设有这样一段代码:String s = “abc”;
代码在开始执行之前,会将所需要类全部加载到JVM当中
通过类加载器加载,看到以上代码类加载器会找String.class文件,找到就加载。
加载步骤:
1.首先通过"启动类加载器"加载
注意:启动类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\rt.jar
rt.jar中都是JDK最核心的类库
2.如果通过"启动类加载器"加载不到的时候,会通过"扩展类加载器"加载
注意:扩展类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\*.jar
3.如果"扩展类加载器"没有加载到,那么会通过"应用类加载器"加载
注意:应用类加载器专门加载:classpath中的类

10.双亲委派机制

java中为了保证类加载的安全,使用了双亲委派机制

  1. 优先从启动类加载器中加载,这个称为“父”
  2. “父”无法加载到,再从扩展类加载器中加载,这个称为“母”
  3. 双亲委派,如果都加载不到,才会考虑从应用类加载器中加载,直到加载到为止
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值