Java反射面试

本文详细介绍了Java反射机制,包括静态与动态语言的区别、反射的概念、作用及其优缺点。讲解了Class对象的获取方式,反射在类初始化、获取类结构、动态创建对象以及操作泛型等方面的应用。同时,探讨了类加载器的工作原理,并提供了相关代码示例。
摘要由CSDN通过智能技术生成

静态语言和动态语言

在说反射前,先说一下静态语言和动态语言的区别。

  • 动态语言:

在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数 可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。

主要动态语言:Object-C、 C#、 JavaScript、 PHP、 Python。

  • 静态语言:

与动态语言相对应的,运行时结构不可变的语言就是静态语言。Java不是动态语言,但Java 可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制获得类似动态语言的特性。Java的动态性让编程的时候更加灵活!

主要静态语言:Java、C、C++。

反射是什么?

Reflection (反射) 是Java被视为动态语言的关键,反射机制允许程序在执行期借助于 Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。 创建对象时,JVM会从本地磁盘中将class加载到内存中,之后会在堆内存的方法区中产生了一个Class类型的对象(一个类只有一个Class 对象,这点很重要),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子, 透过这个镜子可以看到类的结构,所以,我愿称之为“反射”(阿凯,你是最强的体术忍者)。

如何评价反射?

反射是框架设计的灵魂。

获取Class方式有哪些?

  • Class.forName(“类的路径”)
Class.forName("com.example.puff.Test");
  • 类名.class
Test.class;
  • 对象名.getClass()
test.getClass();
  • 内置基本数据类型可以通过包装类.TYPE获取
Integer.TYPE;

反射的作用?

通过反射机制,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象,都能够调用它的任意一个方法。在java 中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。

反射机制提供的功能

  • 运行时判断对象所属的类。
  • 运行时构造一个类的对象。
  • 运行时获得一个类的方法和属性。
  • 运行时获取泛型信息。
  • 运行时处理注解。
  • 生成动态代理。

反射的优点和缺点

优点:可以实现动态创建对象和编译,体现出很大的灵活性。
缺点:反射是一种解释性操作,速度较慢。

所以通过new创建对象的效率比较高。通过反射时,先找查找类资源,使用类加载器创建,过程比较繁琐,效率较低。

Class类常用方法

方法描述
static ClassforName(String name)返回指定类名name的Class对象
Object newlnstance()调用缺省构造函数,返回Class对象的一个实例
调用缺省构造函数,返回Class对象的一个实例返回此Class对象所表示的实体(类,接口,数组类或void)的名称。
Class getSuperClass()Class getSuperClass() 返回当前Class对象的父类的Class对象
Class[] getinterfaces()获取当前Class对象的接口
ClassLoader getClassLoader()返回该类的类加载器
Constructor[] getConstructors()返回一个包含某些Constructor对象的数组
Method getMothed(String name,Class… T)返回Field对象的一个数组
Field[] getDeclaredFields()返回Field对象的一个数组

什么时候会发生类的初始化?

类的主动引用(一定会发生类的初始化)

  • 当虚拟机启动,先初始化main方法所在的类
  • new一个类的对象
  • 调用类的静态成员(除了final常量)和静态方法
  • 使用java.lang.reflect包的方法对类进行反射调用
  • 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类

类的被动引用(不会发生类的初始化)

  • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,父类会被初始化,不会导致子类初始化
  • 通过数组定义类引用,不会触发此类的初始化
  • 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)

类加载器:将类(class)加载进内存
在这里插入图片描述

  • 引导类加载器(Bootstap Classloader):用C++编写的, JVM自带的类加载器,负责Java平台核心库,用来装载核心类库。该加载器无法直接获取
  • 扩展类加载器(Extension Classloader):负责/e/ib/ext目录下的jar包或-D java.ext.dirs指定目录下的jar包装入工作库
  • 系统类加载器(System Classloader):负责java -classpath或-D java.class. path所指的目录下的类与jar包装入工作,是最常用的加载器
//获取系统类的加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
//获取系统类加载器的父类加载器-->扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);
//获取扩展类加载器的父类加载器-->根加载器(C/c++)     无法获得
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);
//测试当前类是哪个加载器加载的             系统加载器
ClassLoader classLoader = Class.forName("com.sia.User").getClassLoader();
System.out.println(classLoader); 
//测试JDK内置的类是哪个加载器加载的           根加载器 无法获得
classLoader = Class.forName ("java.lang.0bject").getClassLoader();
System.out.println(classLoader);
//如何获得系统类加载器可以加载的路径
System.out.println(System.getProperty("java.class.path"));

通过反射获取类结构

Class c = Class.forName("com.sia.User");
c.getName();       //获取包名+类名
c.getSimpleName();  //获取类名
c.getFields();  //获取public属性
c.getDeclaredFields();  //获取全部属性
c.getDeclaredFields("fieldName"); //获取指定属性
c.getMethods();  //获取本类及父类全部public方法
c.getDeclaredMethods();  //获取本类所有方法
//传入类型是因为重载
c.getDeclaredMethods("methodName","type");  //获取指定方法
c.getConstructor();  //获取本类所有的public构造器
c.getConstructor();  //获取本类所有构造器
c.getDeclaredConstructor("type","type"...);  //获取指定构造器

动态创建对象

//创建pojo类
public class User{
    private String name;
    private int age;
    
    public User(){
        
    }
    public User(String name,int age){
        
    }
    public void setName(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }
    public void setAge(int age){
        this.age = age;
    }
    public int getAge(){
        return age;
    }
}
//通过class类创造对象
Class c = Class.forName(com.sia.User);
User user1 = (User)c.newInstance();
//[注意]:该方法调用无参构造方法,必须有无参构造方法

//通过构造器创建
Constructor constructor = c.getDeclaredConstructor(String.class,int.class);
User user2 = (User)constructor.newIstance("胡图图","18");
//通过反射获取一个方法
Method setName = c.getDeclaredMethods("setName",String.class);
setName.invoke(user2,"胡图图");

//通过反射获取属性
Field age = c.getDeclaredField("age");
age.setAccessible("true"); //关闭安全监测 允许访问私有方法(可以提高效率)
age.set(user2,"18");

反射操作泛型

  1. Java采用泛型擦除的机制来引入泛型, Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题, 但是,一旦编译完成,所有和泛型有关的类型全部擦除
  2. 为了通过反射操作这些类型, Java新增了ParameterizedType ,GenericArrayType,TypeVariable和WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。
  • ParameterizedType:表示一种参数化类型,比如Collection<String>
  • GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型 。
  • TypeVariable:是各种类型变量的公共父接口 。
  • WildcardType:代表一种通配符类型表达式 。

获取泛型类型

public static void main(String[] args) throws NoSuchMethodException {
//通过反射拿到方法
Method method = Test2.class.getMethod ( name: "test", Map.class, List.class) ;
//获得方法的泛型参数类型
Type[] genericParameterTypes = method.getGenericParameterTypes();
//遍历
for (Type genericParameterType : genericParameterTypes) {
//判断是否是参数化类型
if (genericParameterType instanceof ParameterizedType){
//获得真实类型    
Type[] actualTypeArguments = ((ParameterizedType)genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument :actualTypeArguments) {
   System.out.println(actualTypeArgument); 
      }
    }
}

获取返回值类型

public static void main(String[] args) throws NoSuchMethodException {
//通过反射拿到方法
Method method = Test2.class.getMethod ( name: "test", null) ;
//获得方法的返回值参数类型
Type[] genericParameterTypes = method.getGenericReturnTypes();
//遍历
for (Type genericParameterType : genericParameterTypes) {
//判断是否是返回值类型
if (genericParameterType instanceof ParameterizedType){
//获得真实类型    
Type[] actualTypeArguments = ((ParameterizedType)genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument :actualTypeArguments) {
   System.out.println(actualTypeArgument); 
      }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值