java面试之基础(五)

1. 反射

java反射是在运行状态中,对于java任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;或者说是将类的各个组成部分封装为其他对象。

作用

在java中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。
这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

优缺点

  • 能够运行时动态获取类的实例,提高灵活性
  • 使用反射性能较低,反射相当于一系列解释操作,通知JVM要做的事情,这类操作总是慢于直接执行java代码。
package com.linln.boot;
import java.lang.reflect.Method;
public class Test {
	public static void main(String[] args) throws Exception {
        Student stu = new Student();
        long start=System.currentTimeMillis();
        for(int i=0;i<=100000000;i++)
        	stu.eat();
        long end=System.currentTimeMillis();
        System.out.println("普通方式创建对象耗时:"+(end-start));//3
        
        long start1=System.currentTimeMillis();
        Class stu2=Class.forName("com.linln.boot.Student");
        Object o=stu2.newInstance();
        Method md=stu2.getMethod("eat");
        for(int i=0;i<=100000000;i++)
        	md.invoke(o);
        long end1=System.currentTimeMillis();
        System.out.println("反射方式创建对象耗时:"+(end1-start1));//180
        
        long start2=System.currentTimeMillis();
        //关闭安全访问检查能够一定程度上优化反射机制
        md.setAccessible(true);
        for(int i=0;i<=100000000;i++)
        	md.invoke(o);
        long end2=System.currentTimeMillis();
        System.out.println("反射方式优化创建对象耗时:"+(end2-start2));//144
    }
}

AccessibleObject类是Field,Method和Constructor对象的基类。

setAccessible()作用是启动和禁用安全访问检查的开关。true的值表示反射对象应该在使用时抑制Java语言访问检查, false的值表示反映对象执行Java语言访问检查。

场景

  • 使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序
  • 反射是框架设计的灵魂
  • 模块化的开发,通过反射去调用对应的字节码
  • 动态代理设计模式也采用了反射机制
  • 日常使用的 Spring、Hibernate 等框架也大量使用到了反射机制

2.获取 Class 对象

  • Class.forName(“全类名”) 将字节码文件加载进内存,返回Class对象多用于配置文件,将类名定义在配置文件中
  • 类名.class 通过类名的属性class获取多用于参数的传递
  • 对象名.getClass() getClass()方法是定义在Objec类中的方法多用于对象的获取字节码的方式
  • 基本类型的包装类,可以调用包装类的Type属性来获得该包装类的Class对象 包装类型.TYPE
  • 基本数据类型.class
package com.linln.boot;
public class Student {
	private int id;
    String name;
    protected boolean sex;
    public float score;
}
package com.linln.boot;
public class Test {
	public static void main(String[] args) throws ClassNotFoundException {
        //方式一(通过建立对象)
        Student stu = new Student();
        Class classobj1 = stu.getClass();
        com.linln.boot.Student
        System.out.println(classobj1.getName());
        //方式二
        Class classobj2 = Class.forName("com.linln.boot.Student");
        System.out.println(classobj2.getName());
        //方式三(通过类名)
        Class classobj3 = Student.class;
        System.out.println(classobj3.getName());
        
        System.out.println(classobj1 == classobj2);//true
        System.out.println(classobj1 == classobj3);//true
        
        Class<Character> char1=char.class;
        Class<Boolean> bool=Boolean.TYPE;
        System.out.println(char1);//char
        System.out.println(bool);//boolean
    }
}

3.反射原理

  • Source源代码阶段:.java被编译成.class字节码文件

  • Class类对象阶段:.class字节码文件被类加载器加载进内存,并将其封装成Class对象(用于在内存中描述字节码文件),Class对象将原字节码文件中的成员变量抽取出来封装成数组Field[],将原字节码文件中的构造函数抽取出来封装成数组Construction[],在将成员方法封装成Method[]。当然Class类内不止这三个,还封装了很多

    静态加载:编译时加载

    动态加载:运行时加载

    public class Main{
    	public static void main(String args[]){
    		if("A".equals(args[0])){
    			A a=new A();
    			a.start();
    		}
    		if("B".equals(args[0])){
    			B b=new B();
    			b.start();
    		}
    	}
    }
    

    我们并不一定用到A功能或B功能,可是编译却不能通过,new 是静态加载类,在编译时刻就需要加载所有可能使用到的功能。

    public class Main{
    	public static void main(String args[]){
    		try{
    			
    			Class c=Class.forName(args[0]);
    		
    			All a=(All)c.newInstance();
    			a.start();
    		}catch(Exception e){
    			e.printStackTrace();
    		}
    	}
    class A implements All{
    	public void start(){
    		System.out.println("A....START");
    	}
    }
    class B implements All{
    	public void start(){
    		System.out.println("B....START");
    	}
    }
    //接口
    interface All{
    	public void start();
    }
    

    使用动态加载类时,我们不用定义多种功能,只需要通过实现某种标准(实现某个接口)。希望用到哪个就加载哪个,不用不加载,就需要动态加载类。

  • RunTime运行时阶段:创建对象的过程new。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.API

Class 类

反射的核心类,可以获取类的属性,方法等信息。

  • Class也是类,继承Object类,存放在堆
  • Class类的二进制数据放在方法区,成为类的元数据
  • Class类对象不是new出的,而是系统创建的,ClassLoader的loadClass方法创建
  • 同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,无论通过哪一种方式获取的Class对象都是同一个
public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
    //同步标志
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

常用方法

  • getFields()获取本类公共属性,包括继承的public属性
  • getDeclaredMethods() 获取本类所有属性,不包含继承属性
  • getMethods()获取本类公共方法,包括继承的public方法
  • getDeclaredMethods() 获取本类所有方法,但不包括继承的方法
  • getConstructors()获取本类公共构造器,不包括继承的
  • getDeclaredConstructors() 获取本类所有构造器,但不包括继承的
  • getSuperCalss()返回父类类对象、getInterfaces()返回所有接口类对象
package com.linln.boot;

import java.lang.reflect.Field;

public class Test {
	public static void main(String[] args) throws Exception {
		Class stu=Student.class;
		//获取的类名是全类名 com.linln.boot.Student
		System.out.println(stu.getName());
		//getClass()方法是Object类的方法 class java.lang.Class
		System.out.println(stu.getClass());
		//获取包名 com.linln.boot
		System.out.println(stu.getPackage().getName());
		//创建由此类对象表示的类的新实例。  Student [id=0, name=null, sex=false, score=0.0]
		Object o=stu.newInstance();
		System.out.println(o);
		//获取指定名称的 public修饰的成员变量
		/*
		Field idField=stu.getField("id");
		idField.setAccessible(true);//暴力反射
		//私有的,即使暴力反射,也会报错java.lang.NoSuchFieldException: id
		System.out.println(idField.get(o));
		*/
		Field scoreField=stu.getField("score");
		System.out.println(scoreField.get(o));//0.0
		scoreField.set(o, 90.0f);
		System.out.println(scoreField.get(o));//90.0
		//获取所有public修饰的成员变量
		Field[] fileds=stu.getFields();
		for (Field field : fileds) {
			System.out.println(field.getName());//scor age
		}
		//获取指定名称修饰的成员变量,不考虑修饰符
		Field idField=stu.getDeclaredField("id");
		//对于私有变量虽然能会获取到,但不能直接set和get
		idField.setAccessible(true);//暴力反射
		System.out.println(idField.get(o));//0
		//获取所有的成员变量,不考虑修饰符
		Field[] filed1s=stu.getDeclaredFields();
		for (Field field : filed1s) {
			System.out.println(field.getName());//id、name、sex、score、age
		}
		//构造器、方法都类似
    }
}

Field 类

Java.lang.reflec 包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。

  • getModifiers获取本类属性修饰符,以int返回修饰符(默认0,public 1,private 2 protected 4 static 8 final 16)
  • getType返回该属性类型的类对象
  • getName返回属性名、get获取属性值、set设置属性值
  • 如果是静态属性,set、get参数可以是null

Method 类

Java.lang.reflec 包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。

  • getModifiers获取本类方法修饰符,以int返回修饰符(默认0,public 1,private 2 protected 4 static 8 final 16)
  • getReturnType是返回类型的类对象,getParameterTypes是返回方法参数类型的类对象
  • getName返回方法名、invoke执行对象方法

Constructor类

Java.lang.reflec 包中的类,表示类的构造方法。

  • Constructor类内提供了初始化方法newInstance()方法
  • 如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
  • getModifiers获取本类构造器修饰符,以int返回修饰符(默认0,public 1,private 2 protected 4 static 8 final 16)
  • getParameterTypes是返回构造器参数类型的类对象、getName返回构造器名

5.那些类型有Class对象

  • 外部类、内部类
  • 接口、数组、枚举
  • 注解、基本数据类型、包装类
  • void、Class
package com.lymn;

import java.util.List;

public class Test {
    public static void main(String[] args) {
        Class<String> stringClass = String.class;//外部类
        Class<List> listClass = List.class;//接口
        Class<int[]> aClass = int[].class;//数组
        Class<Thread.State> stateClass = Thread.State.class;//枚举
        Class<Override> overrideClass = Override.class;//注解
        Class<Integer> integerClass = int.class;//基本数据类型
        Class<Integer> integerClass1 = Integer.class;//包装类
        Class<Void> voidClass = void.class;//void
        Class<Class> classClass = Class.class;//class类对象
        System.out.println(stringClass);//class java.lang.String
        System.out.println(listClass);//interface java.util.List
        System.out.println(aClass);//class [I
        System.out.println(stateClass);//class java.lang.Thread$State
        System.out.println(overrideClass);//interface java.lang.Override
        System.out.println(integerClass);//int
        System.out.println(integerClass1);//class java.lang.Integer
        System.out.println(voidClass);//void
        System.out.println(classClass);//class java.lang.Class
    }
}

6.Class.forName和ClassLoader

Class.forName和ClassLoader都可以对类进行加载。

ClassLoader只负责加载 Java 类的字节代码到 Java 虚拟机中,不会执行初始化,只有在newInstance才会去执行。

Class.forName其实是调用了ClassLoader,如下:Class<?> forName0(String name, boolean initialize, ClassLoader loader)这里面,forName0的第二个参数为true,表示对加载的类进行初始化。

所以Class.forName和ClassLoader的区别是在类加载的时候,class.forName有参数控制是否对类进行初始化。

7.反射案例

不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法。

package com.linln.boot;

public class Student {
	private int id;
    String name;
    protected boolean sex;
    public float score;
    public void eat(){
    	System.out.println("吃");
    }
}
package com.linln.boot;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;

public class Test {
	public static void main(String[] args) throws Exception {
		Properties pro = new Properties();
		//1获取class目录下的配置文件  使用类加载器加载配置文件
        ClassLoader classLoader = Test.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("set.properties");
        pro.load(is);
        //2.获取配置文件中定义的数据
        String className = pro.getProperty("className");
        String methodName = pro.getProperty("methodName");
        //3.加载该类进内存
        Class cls = Class.forName(className);
        //4.创建对象
        Object obj = cls.newInstance();
        //5.获取方法对象
        Method method = cls.getMethod(methodName);
        //6.执行方法
        method.invoke(obj);//吃
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值