Java基础篇笔记(四):Java中的反射机制

一、引入反射概念

在运行状态中,对于任何一个类,我们都能够知道这个类有哪些方法和属性;对于任何一个对象,我们都能够知道它的方法和属性来进行调用。我们把这种动态获取对象信息和调用对象方法的功能称为反射机制。

二、反射的功能

1.获取某个对象的属性。
2.获得某个类的静态属性。
3.执行某对象的方法。
4.执行某个类的静态方法。
5.新建类的实例。

三、获取Class对象

每个类被加载之后,系统就会为该类生成一个对应的Class对象,通过该Class对象就可以访问到JVM中的这个类。在Java程序中获得Class对象通常有如下三种方式:
1.使用Class类的forName(String className)静态方法。该方法需要传入字符串参数,该字符串参数的值是某个类的全限定类名(必须添加完整包名)。Class.forName(String className);(最常用的)
2.调用某个类的class属性来获取该类对应的Class对象。例如,className.class将会返回className对应的Class对象。className.class;
3.调用某个对象的getClass()方法。该方法是java.lang.Object类中的一个方法,所以所有的Java对象都可以调用该方法,该方法将会返回该对象所属类对应的Class对象。new className().getClass();
ps:对于第一种和第二种方式都是直接根据类来获取该类的Class对象,相比之下,第二种方法有两种优势(代码更安全:程序在编译阶段就可以检查需要访问的Class对象是否存在。程序性能更好:因为这种方式无须调用方法,所以性能更好。)

四、常见应用

如下为程序获取了ClassTest类对应的Class对象后,通过调用该Class对象的不同方法来得到该Class对象的详细信息:

public class ClassTest {
    private ClassTest(){}
    public ClassTest(String name){
        System.out.println("执行有参数的构造器");
    }
    public void info(){
        System.out.println("执行无参数的info方法");
    }
    public void info(String str){
        System.out.println("执行有参数的info方法"+",其str值为:"+str);
    }
    class Inner{}
    public static void main(String[] args) throws Exception{
        Class<ClassTest> clazz=ClassTest.class;
        Constructor[] ctors=clazz.getDeclaredConstructors();
        System.out.println("ClassTest的全部构造器如下:");
        for(Constructor c:ctors){
            System.out.println(c);
        }
        Constructor[] publicCtors=clazz.getConstructors();
        System.out.println("ClassTest的全部public构造器如下:");
        for(Constructor c:publicCtors){
            System.out.println(c);
        }
        Method[] mtds=clazz.getMethods();
        System.out.println("ClassTest的全部public方法如下:");
        for(Method md:mtds){
            System.out.println(md);
        }
        System.out.println("ClassTest里带一个字符串参数的info方法为:"+clazz.getMethod("info",String.class));
        Class<?>[] inners=clazz.getDeclaredClasses();
        System.out.println("ClassTest的全部内部类如下:");
        for(Class c:inners){
            System.out.println(c);
        }
        Class inClazz=Class.forName("ClassTest$Inner");
        System.out.println("inClazz对应类的外部类为:"+inClazz.getDeclaringClass());
        System.out.println("ClassTest的包为:"+clazz.getPackage());
        System.out.println("ClassTest的父类为"+clazz.getSuperclass());
    }
}
输出结果:
ClassTest的全部构造器如下:
private ClassTest()
public ClassTest(java.lang.String)
ClassTest的全部public构造器如下:
public ClassTest(java.lang.String)
ClassTest的全部public方法如下:
public static void ClassTest.main(java.lang.String[]) throws java.lang.Exception
public void ClassTest.info(java.lang.String)
public void ClassTest.info()
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
ClassTest里带一个字符串参数的info方法为:public void ClassTest.info(java.lang.String)
ClassTest的全部内部类如下:
class ClassTest$Inner
inClazz对应类的外部类为:class ClassTest
ClassTest的包为:package 
ClassTest的父类为class java.lang.Object

不仅仅有这些方法,还有很多其他的方法,可以通过查阅API文档来查看。

使用反射还可以越过泛型的检查,如下代码:

public class QAQTest {
    public static void main(String[] args) throws Exception{
        ArrayList<String> list=new ArrayList<>();
        list.add("???");
        list.add("!!!");
        /*在此处写一list.add(5)报错*/
        /*获取ArrayList的Class对象,反向调用add()方法*/
        Class listClass=list.getClass();
        //获取add()方法
        Method m=listClass.getMethod("add",Object.class);
        //调用add()方法
        m.invoke(list,5);
        for(Object obj:list){
            System.out.println(obj);
        }
    }
}
输出结果:
???
!!!
5

利用反射,创建一个对象:
如下代码实现了一个简单的对象池,该对象池会根据配置文件读取key-value对,然后创建这些对象,并将这些对象放入一个HashMap中:


public class ObjectPoolFactory{
    //定义一个对象池,前面是对象名,后面是实际对象
    private Map<String , Object> objectPool = new HashMap<>();
    //定义一个创建对象的方法,只要传入一个字符串类名,程序可以根据该类名生成Java对象
    private Object createObject(String clazzName) throws Exception
    {
        //根据字符串来获取对应的Class对象
        Class<?> clazz = Class.forName(clazzName);
        //使用clazz对应的默认构造器创建实例(用了newInstance()方法)
        return clazz.getConstructor().newInstance();
    }
    //定义一个根据指定文件来初始化对象池,根据配置文件来创建对象
    public void initPool(String fileName)
    {
        try(FileInputStream fis = new FileInputStream(fileNmae))
        {
            Properties prop = new Properties();
            prop.load(fis);
            for(String name : props.stringPropertyNames())
            {
                //每取出一对key-value对,就根据value创建一个对象
                //调用上面的createObject()创建对象,并将对象添加到对象池中
                objectPool.put(name , createObject(props.getProperty(name)));
            }
        }
        catch(Exception e)
        {
            System.out.println("读取" + fileNmae + "异常");
        }
    }
    public Object getObject(String name)
    {
        //从对象池中取出name对应的对象
        return objectPool.get(name);
    }
    public static void main(String[] args) throws Exception
    {
        ObjectPoolFactory opf = new ObjectPoolFactory();
        /* 提供一个obj.txt配置文件,内容为
           a=java.util.Date
           b=javax.swing.JFrame */
        opf.initPool("obj.txt");
        System.out.println(opf.getObject("a"));
        System.out.println(opf.getObject("b"));
    }
}
输出结果:
系统当前时间和一个JFrame对象。

在很多Java EE框架中都需要根据配置文件信息来创建Java对象,从配置文件读取的只是某个类的字符串类名,程序需要根据该字符串来创建对应的实例,就必须使用反射。这种使用配置文件来配置对象,然后由程序根据配置文件来创建对象的方式非常有用,Spring框架就采用这种方式大大Java EE应用的开发(Spring采用的是XML配置文件)。


利用反射访问成员变量值:通过Class对象的getFields()或getField()方法可以获取该类所包含的全部成员变量或指定成员变量。Field还提供了了两组方法来读取或设置成员变量值(getXxx(Object obj)获取obj对选哪个的该成员变量的值。此处的Xxx对应8种基本类型,如果该成员变量的类型是引用类型,就直接get;setXxx(Object obj,Xxx val)将obj对象的该成员设置成val值。如果该成员变量的类型是引用类型,则取消set后面的Xxx)使用这两个方法可以随意的访问指定对象的所有成员变量,包括private修饰的。

class Person
{
    private String name;
    private int age;
    public String toString()
    {
        return "Person[name:" + name + ",age:" + age + "]";
    }
public class FieldTest
{
    public static void main(String[] args) throws Exception
    {
        Person p = new Person();
        //获取Person类对应的Class对象
        Class<Person> personClazz = Person.class;
        //获取Person的名为name的成员变量
        //使用getDeclaredField()方法表明可获取各种访问控制符的成员变量
        Field nameField = personClazz.getDeclaredField("name");
        //设置通过反射访问该成员变量时取消访问权限检查,即可以访问private
        nameField.setAccessible(true);
        //调用set()方法为p对象的name成员变量设置值
        nameField.set(p , "huanghe");
        //获取Person的名为age的成员变量
        Field ageField = personClazz.getDeclaredField("age");
        ageField.setAccessible(true);
        ageField.setInt(p , 18);
        System.out.println(p);
    }
}
输出结果:
Person[name:huanghe,age:18]

五、反射的特点

尽管反射机制带来了极大的灵活性及方便性,但是反射也有许多缺点。反射机制的功能非常强大,但不能滥用。在能不使用反射就不要使用

  • 性能问题:Java反射机制中包含了一些动态类型,所以Java虚拟机不能够对这些动态代码进行优化。因此,反射操作的效率要比正常操作效率低很多。我们应该避免在对性能要求很高的程序或经常被执行的代码中使用反射。而且如何使用反射决定了性能的高低。如果它作为程序中较少运行的一部分,性能将不会成为一个问题。
  • 安全限制:使用反射通常需要程序的运行没有安全方面的限制。如果一个程序对安全性提出要求,最好不要使用反射。
  • 程序健壮性:反射允许代码执行一些通常不被允许的操作,所以使用反射有可能会导致意想不到的后果。反射代码破坏了Java程序结构的抽象性,所以当程序运行的平台发生变化的时候,由于抽象的逻辑就够不能被识别,代码产生的效果与之前会产生差异。

不过反射也有许多好处:

  • 反射机制极大的提高了程序的灵活性和扩展性,降低模块的耦合性,提高自身的适应能力。
  • 通过反射机制可以让程序创建和控制任何类的对象,无须提前硬编码目标类。
  • 使用反射机制能够在运行时构造一个类的对象,判断一个类所具有的的成员变量和方法,调用一个对象的方法并生成动态代理。
  • 反射机制时构建框架技术的基础所在,使用反射可以避免将代码写死在框架中。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ava实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),可运行高分资源 Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现
C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发。C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。下面详细介绍C语言的基本概念和语法。 1. 变量和数据类型 在C语言,变量用于存储数据,数据类型用于定义变量的类型和范围。C语言支持多种数据类型,包括基本数据类型(如int、float、char等)和复合数据类型(如结构体、联合等)。 2. 运算符 C语言常用的运算符包括算术运算符(如+、、、/等)、关系运算符(如==、!=、、=、<、<=等)、逻辑运算符(如&&、||、!等)。此外,还有位运算符(如&、|、^等)和指针运算符(如、等)。 3. 控制结构 C语言常用的控制结构包括if语句、循环语句(如for、while等)和switch语句。通过这些控制结构,可以实现程序的分支、循环和多路选择等功能。 4. 函数 函数是C语言用于封装代码的单元,可以实现代码的复用和模块化。C语言定义函数使用关键字“void”或返回值类型(如int、float等),并通过“{”和“}”括起来的代码块来实现函数的功能。 5. 指针 指针是C语言用于存储变量地址的变量。通过指针,可以实现对内存的间接访问和修改。C语言定义指针使用星号()符号,指向数组、字符串和结构体等数据结构时,还需要注意数组名和字符串常量的特殊性质。 6. 数组和字符串 数组是C语言用于存储同类型数据的结构,可以通过索引访问和修改数组的元素。字符串是C语言用于存储文本数据的特殊类型,通常以字符串常量的形式出现,用双引号("...")括起来,末尾自动添加'\0'字符。 7. 结构体和联合 结构体和联合是C语言用于存储不同类型数据的复合数据类型。结构体由多个成员组成,每个成员可以是不同的数据类型;联合由多个变量组成,它们共用同一块内存空间。通过结构体和联合,可以实现数据的封装和抽象。 8. 文件操作 C语言通过文件操作函数(如fopen、fclose、fread、fwrite等)实现对文件的读写操作。文件操作函数通常返回文件指针,用于表示打开的文件。通过文件指针,可以进行文件的定位、读写等操作。 总之,C语言是一种功能强大、灵活高效的编程语言,广泛应用于各种领域。掌握C语言的基本语法和数据结构,可以为编程学习和实践打下坚实的基础

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值