Java 反射之根基 Class 类

Java中反射机制很重要,Java的动态语言就是靠反射机制实现的,反射技术也是程序员走的更远必不可少的一个技能。一般情况下我们都是通过类来创建对象,如果要求通过一个对象找到一个类,就需要用到反射机制,各种开源框架的编写更是离不开反射的机制。

一、了解反射机制

一般我们写代码,按照如下的步骤使用一个类:

1、使用关键字 import 导入类所在的包;

2、使用类名称或者接口名称定义对象;

3、通过关键字 new 进行类对象实例化;

4、使用【对象.属性】进行类中属性的调用;

5、使用【对象.方法】进行类中方法的调用;

而 Java 中的反射过程就是指:在程序的运行过程中,把 Java 类中的各种元素映射成对应的类,并能动态的执行这些类的方法。

Java 反射机制里重要的几个类:

类:java.lang.Class

属性:java.lang.reflect.Field

构造方法:java.lang.reflect.Constructor

方法:java.lang.reflect.Method

用反射可以轻松的获取一个类中的以上元素,并可以动态地对其进行调用,在不知道别人写的代码的时候,可以通过配置来调用别人的代码,很多开源框架都离不开反射机制,反射的魅力也正如此。

二、认识 Class 类

1、了解 Object 类:

我们知道如果一个类没有明确的声明集成自哪个父类时,那么它默认继承 Object 类,Object 类是所有类的父类。

Object 类中的 getClass()方法的源码:

public final native Class<?> getClass();

getClass()方法返回的类型是一个 Class 类,Class 类就是 Java 反射的源头。以上的源码用到了 native 关键字和泛型,使用 native 关键字说明这个方法是原生函数,也就是这个方法是用 C/C++ 语言实现的,由 Java 通过 JNI 去调用的本地接口;如果不让程序出现警告信息,可以在使用时指定其操作的泛型具体类型,或者直接用“?”来取代。

2、获取 Class 类的 4 种实例化方式:

1、通过类本身的 class 属性实例化【类.class】;

2、通过 Object 类的 getClass()方法实例化【对象.getClass()】;

3、通过【Class.forName()方法】实例化,这种方式最常用,体现出了反射的动态性;

4、通过类的加载器获取;

/**
* 如何获取Class类的实例的4种方式
*/
@Test
 public void TestClass() throws Exception {
   // 1、调用运行时类本身的.class属性
   Class<?> clazz = User.class;
   System.out.println(clazz);

   // 2、通过运行时类的对象获取
   User p = new User();
   Class<?> clazz1 = p.getClass();
   System.out.println(clazz1);

   // 3、通过Class的静态方法获取,体现了反射的动态性,
   // 传入className就能创建Class类的实例
   String className = "com.jpm.reflection.User";
   Class<?> clazz3 = Class.forName(className);
   System.out.println(clazz3);

   // 4、通过类的加载器获取
   ClassLoader classLoader = this.getClass().getClassLoader();
   Class clazz4 = classLoader.loadClass(className);
   System.out.println(clazz4);
 }

/**
* User类,用于演示反射,为了不影响体验,代码见文章末尾
*/

运行结果:

class com.jpm.reflection.User
class com.jpm.reflection.User
class com.jpm.reflection.User
class com.jpm.reflection.User

三、类的加载器 ClassLoader

1、了解 Class 类的 3 种加载器:

1、系统类加载器(AppClassLoader):加载自定义类和普通 jar 包;

2、扩展类加载器(ExtClassLoader):加载 jre/lib/ext 目录下的类;

3、启动类加载器(BootStrapClassLoader):加载 jdk 的核心类库,启动类加载器 Java 代码获取不到;

/**
   * 类的加载器测试
   */
  @Test
  public void testClassLoader() throws Exception {
    ClassLoader loader1 = this.getClass().getClassLoader();
    // 获取系统类加载器:sun.misc.Launcher$AppClassLoader@7852e922
    System.out.println(loader1);

    ClassLoader loader2 = loader1.getParent();
    // 获取系统类加载器的父类,扩展类加载器:sun.misc.Launcher$ExtClassLoader@330bedb4
    System.out.println(loader2);

    ClassLoader loader3 = loader2.getParent();
    // 获取扩展类的加载器的父类为null,说明引导类加载器无法在代码中获取
    System.out.println(loader3);

    Class clazz1 = User.class;
    ClassLoader loader4 = clazz1.getClassLoader();
    // 普通类用系统类加载器加载:sun.misc.Launcher$AppClassLoader@7852e922
    System.out.println(loader4);

    Class clazz2 = Class.forName("java.lang.String");
    ClassLoader loader5 = clazz2.getClassLoader();
    // String类是jdk的核心类库,用引导类加载器加载,返回null,说明类加载器无法在代码中获取
    System.out.println(loader5);
  }

运行结果:

sun.misc.Launcher$AppClassLoader@7852e922
sun.misc.Launcher$ExtClassLoader@330bedb4
null
sun.misc.Launcher$AppClassLoader@7852e922
null

2、类加载器的应用案例

1、获取 Java 包里的配置文件

@Test
  public void test() throws IOException {
    // 类加载器的应用,获取java包里的配置文件,但是不能获取工程目录下的文件
    ClassLoader loader = this.getClass().getClassLoader();
    InputStream is = loader.getResourceAsStream("configs//jdbc.properties");
    Properties properties = new Properties();
    properties.load(is);
    is.close();
    String user = properties.getProperty("user");
    System.out.println(user);
    String pwd = properties.getProperty("pwd");
    System.out.println(pwd);

    // 文件名不一定是properties后缀,也可以是txt等
    InputStream is1 = loader.getResourceAsStream("configs//jdbc.txt");
    Properties properties1 = new Properties();
    properties1.load(is1);
    is1.close();
    String user1 = properties1.getProperty("user");
    System.out.println(user1);
    String pwd1 = properties1.getProperty("pwd");
    System.out.println(pwd1);
  }

配置文件内容:


运行结果:

properties
jdbc.properties
txt
jdbc.txt

2、获取工程下的配置文件

上面的类加载器获取 java 包里的配置文件,但是不能获取工程目录下的文件,要想获取工程目录下的文件,可以使用如下的代码:


@Test
  public void test2() throws IOException {
    // 工程下的文件获取,文件名不一定是properties后缀,也可以是txt等
    InputStream is = new BufferedInputStream(new FileInputStream(new File("jdbc2.properties")));
    Properties pro = new Properties();
    pro.load(is);
    is.close();
    System.out.println(pro.getProperty("user"));
    System.out.println(pro.getProperty("pwd"));
  }

运行结果:

root
root123

四、Java 反射初体验

下面一个例子用来演示如何通过 Java 的反射机制,动态创建 User 类对象,并调用 User 类对象的属性赋值以及调用它的方法。

/**
   * 反射的初步体验
   */
  @Test
  public void TestReflection() throws Exception {
    Class<?> clazz = Class.forName("com.jpm.reflection.User");
    // 创建运行时类User的对象
    Object user = clazz.newInstance();
    System.out.println(user);

    // 通过反射调用运行时类的指定public属性
    Field f1 = clazz.getField("name");
    f1.set(user, "JPM");
    System.out.println(user);

    // 通过反射调用运行时类的指定private属性
    Field f2 = clazz.getDeclaredField("age");
    f2.setAccessible(true); // 为私有属性赋值
    f2.set(user, 18);
    System.out.println(user);

    // 通过反射调用运行时类的指定方法
    Method m1 = clazz.getMethod("show");
    m1.invoke(user);

    Method m2 = clazz.getMethod("display", String.class);
    m2.invoke(user, "BJ");
  }
  
/**
 * User类,用于演示反射
 */
public class User {

  public String name;
  private int age;

  // 创建类时尽量写一个空参的构造器
  public User() {
    super();
  }

  public User(String name) {
    super();
    this.name = name;
  }

  public User(String name, int age) {
    super();
    this.name = name;
    this.age = age;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  @Override
  public String toString() {
    return "User [name=" + name + ", age=" + age + "]";
  }

  public void show() {
    System.out.println("show method run!");
  }

  public void display(String address) {
    System.out.println("display method run,address=" + address);
  }
}

运行结果:

User [name=null, age=0]
User [name=JPM, age=0]
User [name=JPM, age=18]
show method run!
display method run,address=BJ

本文主要介绍了反射的概念、反射的源头 Class 类、类的 3 种加载器、类加载器ClassLoader 的应用场景、用Java 反射获取用户自定义类User,初步体验了 Java 反射机制的魅力。

后面我会用 2 篇文章来说明 Java 反射机制的深入体验,反射的高级应用动态代理的实现,敬请期待......

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容