Java中的反射机制和lambda表达式
1.反射
反射是java中在程序运行时可以获取到类的名称,方法,构造,字段的一种机制,根据获取到的类获取类的实例化对象,进而可使用类中的方法,访问类的成员。
在反射机制中有几个重要的类:Class,Constructor,Method,Filed(类,构造,方法,字段),以下通过这几个类来具体演示反射的过程。
1.1 Class类
java中所有类的对象都是Class的对象,使用Class获取需要反射的类对象有三种方法。
1.找到反射类的路径,使用Class类中的forName方法,参数为类的路径;
2.找到反射类的类名,使用类名+ class的方法获取;
3.创建反射类的实例化对象,通过对象调用getClass方法;
例如常见一个学生类,成员变量有 age 和 name,实现构造方法,重写toString方法
class Student{
//成员变量/字段
public int age;
private String name ;
//构造方法
public Student(int age,String name){
this.age = age;
this.name = name;
}
public Stydent{};
//重写toString方法
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
1.使用Class.forName方法获取类对象。
Class<?> stu1 = Class.forName("Student");
2.通过类名 + class的方式返回类对象
Class<?> stu2 = Student.class;
3.先创建一个Student的对象引用,调用getClass方法获取。
//创建对象引用
Student s = new Student(10,"lisi");
//调用getClass方法
Class<?> stu3 = s.getClass();
测试三个方式获取的对象是否相同。
class Student{
//成员变量/字段
public int age;
private String name ;
//构造方法
public Student(int age,String name){
this.age = age;
this.name = name;
}
//重写toString方法
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
public class reflectionDemo {
//forName方法会报异常,需要在main方法中声明:类型无法查找异常
public static void main(String[] args) throws ClassNotFoundException {
Class<?> stu1 = Class.forName("Student");
Class<?> stu2 = Student.class;
Student s = new Student(10,"lisi");
Class<?> stu3 = s.getClass();
System.out.println(stu2 == stu1);//true
System.out.println(stu1 == stu3);//true
System.out.println(stu2 == stu3);//true
}
}

1.2 Constructor类
在Class类获取到需要反射的类后,就可以调用getConstructor方法获取到需要反射的构造方法,方法的返回类型的Constructor,可以使用一个Contructor类型的引用接收;
使用getConstructor方法可以先看一下Java中提供的源码。
@CallerSensitive
public Constructor<T> getConstructor(Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException
可以看到获取的构造方法中需要参数列表,每一个参数类型都是Class<?>,在上面获取反射类对象中使用的第二个方法反射类的类名名 + class就可以返回Class<?>,在参数列表中也可以使用,这个方法进行传参,只需要指定参数的类型 + class,例如:String.class,Integer.class…
现在我们需要反射的类是Student,类的构造方法有两个参数,分别是age和name,类型分别是Integer和String,使用在传参的时候就可以使用int.class和String.class,以下来做具体展示。
//由于获取的构造方法可能不存在,因此需要先声明异常:NoSuchMethodExceptioni
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
//获取反射类对象
Class<?> student = Class.forName("Student");
//获取构造方法
Constructor<?> constructor = student.getConstructor(Integer.class,String.class);
}
获取到Constructor后就可以使用构造方法,对成员变量进行初始化赋值,需要先实例化一个对象,调用newInstance方法,参数的类型的与构造方法的参数列表一致。
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//获取反射类对象
Class<?> student = Class.forName("Student");
//获取构造方法
Constructor<?> constructor = student.getConstructor(int.class,String.class);
//调用newInstance方法实例化对象
Student s1 = (Student) constructor.newInstance(18,"李四");
}
由于newInstance是的不安全的,需要先声明一些异常:InvocationTargetException(调用目标异常),InstantiationException(实例化异常),IllegalAccessException (非法设置异常)。
在Student类中重写了toString方法,输出获取的s1。
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//获取反射类对象
Class<?> student = Class.forName("Student");
//获取构造方法
Constructor<?> constructor = student.getConstructor(int.class,String.class);
//调用newInstance方法实例化对象
Student s1 = (Student) constructor.newInstance(18,"李四");
//输出
System.out.println(s1);
}

1.3 Method类
需要获取反射类中的方法,可以调用Class类中的getMethod方法,返回类型使用Method类型接收,可以先看Java中getMethod方法。
@CallerSensitive
public Method getMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException
getMethod方法需要的参数列表有两部分,第一个是需要反射的方法名,第二个是方法中的参数列表,类型是Class<?>,如果获取的方法是没有参数的,只需要将方法名传入即可。
在Student类中创建一个func方法,分别实现带参数和不带参数。
不带参数func方法输出“获取到不带参数的方法func”。
//不带参数的func
public void func(){
System.out.println("获取到不带参数的func方法");
}
带参数的func方法输出 参数 + 获取到带参数的func方法
//带参数的func方法
public void func(String pragma){
System.out.println(pragma + "获取到带参数的func方法");
}
获取需要反射的类对象和方法类
public static void main(String[] args) {
//获取反射类对象
Class<?> student = Class.forName("Student");
//获取方法
Method method1 = student.getMethod("func");//不带参数
Method method2 = student.getMethod("func",String.class);//带一个参数
}
由于使用forName和getMethod方法需要捕获异常,可以使用try_cacth捕获。
public static void main(String[] args) {
try{
//获取反射类对象
Class<?> student = Class.forName("Student");
//获取方法
Method method1 = student.getMethod("func");//不带参数
Method method = student.getMethod("func",String.class);
}catch (ClassNotFoundException e){
e.printStackTrace();
}catch (NoSuchMethodException e){
e.printStackTrace();
}
}
反射的机制在Java中是不安全的,获取方法前需要调用setAccessible方法,确保反射行为是合法操作的,可以先查看setAccessible方法。
@Override
@CallerSensitive
//参数为true表示允许调用,false表示不可调用
public void setAccessible(boolean flag) {
//检查是否调用对象是否合法
AccessibleObject.checkPermission();
//传入的参数为true执行,允许调用
if (flag) checkCanSetAccessible(Reflection.getCallerClass());
//设置为false,不可调用
setAccessible0(flag);
}
调用getAccessible方法,参数为true。
//设置可以修改或者获取
method.setAccessible(true);
执行反射的方法,需要调用invake方法,先查看invoke方法的参数列表。
@CallerSensitive
@ForceInline // to ensure Reflection.getCallerClass optimization
@IntrinsicCandidate
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
invoke参数有两部分,第一个是反射类的实例化对象,第二部分是方法的参数列表;有了具体的操作对象就可以调用对应的方法。
public class reflectionDemo {
public static void main(String[] args) {
try{
//获取反射类对象
Class<?> student = Class.forName("Student");
//获取方法,带参数的func
Method method2 = student.getMethod("func",String.class);
//获取不带参数的func
Method method1 = student.getMethod("func");
//设置可以修改或者获取
method2.setAccessible(true);
//设置为可修改
method1.setAccessible(true);
//实例化对象引用
Student s = (Student) student.newInstance();
//调用方法,s的反射类的引用,‘gg‘是给func方法传参
method2.invoke(s,"gg");
method1.invoke(s);
}catch (ClassNotFoundException e){
e.printStackTrace();
}catch (NoSuchMethodException e){
e.printStackTrace();
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
输出结果结果如下:

1.4 Field类
获取到反射类的对象需要访问类中的字段,就可以使用Filed类。
1.获取类的对象;
try{
//获取反射的类对象
Class<?> student = Class.forName("Student");
}catch (ClassNotFoundException e){
e.printStackTrace();
}
2.调用Class类中的getFiled或者getDeclaredFiled方法。
getFiled方法和getDeclaredFiled方法的参数都是一个,就是获取字段的名,getField方法获取的public修饰的字段,而getDeclaredFiled获取的是所有访问权限的字段(包括private修饰)。
使用getFiled方法反射age(public修饰),getDeclaredFiled反射name(private修饰)。
try{
//需要反射的字段
Field field1 = student.getField("age");
Field field2 = student.getDeclaredField("name");
}catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
3.调用getAccessible方法设置为true
try{
//设置可修改
field1.setAccessible(true);
field2.setAccessible(true);
}catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
4.实例化对象和调用set方法
当获取到需要反射的字段,需要先确定反射的哪一个对象,所以需要通过getInstance方法获取一个实例化对象,如果需要修改字段或者初始化字段,需要调用set方法,以下是Java中的set方法。
@CallerSensitive
@ForceInline // to ensure Reflection.getCallerClass optimization
public void set(Object obj, Object value)
throws IllegalArgumentException, IllegalAccessException
set方法的参数第一个反射类的对象引用,第二个参数是字段的值。例如实例化的对象age = 18,name = “lirays”,使用set方法如下。
try{
//实例化对象
Student s = (Student) student.newInstance();
//调用set方法
field1.set(s,20);
field2.set(s,"lirays");
}catch (InstantiationException e) {
throw new RuntimeException(e);
}
5.输出反射对象的信息
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
class Student{
//成员变量/字段
public int age;
private String name;
//构造方法
public Student(int age,String name){
this.age = age;
this.name = name;
}
public Student(){
}
//不带参数的func
public void func(){
System.out.println("获取到不带参数的func方法");
}
//带参数的func方法
public void func(String pragma){
System.out.println(pragma + "获取到带参数的func方法");
}
//重写toString方法
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
public class reflectionDemo {
public static void main4(String[] args){
try{
//获取反射的类对象
Class<?> student = Class.forName("Student");
//需要反射的字段
Field field1 = student.getField("age");
Field field2 = student.getDeclaredField("name");
//设置可修改
field1.setAccessible(true);
field2.setAccessible(true);
//实例化对象
Student s = (Student) student.newInstance();
//调用set方法
field1.set(s,20);
field2.set(s,"lirays");
//输出
System.out.println(s);
}catch (ClassNotFoundException e){
e.printStackTrace();
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}

2.Lambda表达式
Lambda表达式是Java 8引入的一项重要功能,它允许我们以更简洁和灵活的方式编写代码。Lambda表达式可以看作是一种匿名函数,可以像数据一样传递和使用。它可以替代传统的匿名类,使代码更加简洁,并支持函数式编程。
1.lambda表达式用于函数式接口
在Java中提供一种定义函数式接口的方法,使用@FuntionalInterface定义,例如定义一个加法的方法,在接口中声明方法。
@FunctionalInterface
interface AddFun{
int add(int x,int y);
}
如果不使用Lambda表达式实现需要先创建一个类使用AddFun接口或者实现一个匿名内部类使用AddFun接口,再重写接口中的方法,最后调用add方法。
public static void main(String[] args) {
//匿名内部类:使用AddFun接口,并重新add方法
AddFun a = new AddFun(){//匿名内部类
@Override
public int add(int x,int y) {
return x + y;
}
};
}
如果使用lambda表达式,需要先了解lambda表达式使用的方式;
(参数) -> {表达式};
lambda表达式由两个部分组成:第一给部分括号()内的参数列表,第二个部分大括号{}内的具体表达式(方法的具体实现)。
在AddFun中的add方法由两个参数,所以()内需要传入两个参数,并且不用指定类型,编译器会自动匹配,add方法的主要功能是将两个参数计算的和返回,在lambda表达式中不关心方法的声明,返回的类型,只需要具体的实现逻辑或者返回结果,所以在大括号{}内直接返回add的结果即可,使用lambda表达式的实现如下。
//匿名内部类实现AddFun接口,实现add方法,(x,y)是参数列表 return x - y实现加法函数的实现
AddFun b = (x,y) -> {
return x + y;
};
使用lambda表达式和不使用的情况可以看出lambda表达式可以使程序更加简洁,同样可以实现add方法的使用。
@FunctionalInterface
interface AddFun{
int add(int x,int y);
}
public class Lambda {
public static void main(String[] args) {
//匿名内部类:使用AddFun接口,并重新add方法
AddFun a = new AddFun(){
@Override
public int add(int x,int y) {
return x + y;
}
};
System.out.println(a.add(1,2));
AddFun b = (x,y) -> {
return x + y;
};
System.out.println(b.add(1,2));
}
}

在优先级队列中存在排序的方法,在实例化PriorityQueue对象时,构造方法中需要传入的比较大小的比较器,这个比较器就可以使用lambda表达式实现。
先在Java中看到PriorityQueue的构造方法,参数类型是一个Comparator的比较器。
public PriorityQueue(Comparator<? super E> comparator) {
this(DEFAULT_INITIAL_CAPACITY, comparator);
}
如果不使用lambda表达式,需要在构造方法的参数中传入一个Conparator比较器,并重写compare方法,而如果使用lambda表达式,只需要将重写的逻辑实现即可,即在()内传参,在{}内实现比较的逻辑,默认是升序的,参数假设是o1和o2,返回o1和o2的差值即可;以下是两种实现方式。
public class Lambda {
public static void main(String[] args) {
//创建使用Comparator接口的子类,重写compare方法
PriorityQueue<Integer> queue = new PriorityQueue(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return (Integer) o1 - (Integer) o2;
}
});
//实现参数和结果即可
PriorityQueue<Integer> q = new PriorityQueue<>((a,b) ->{
return a - b;
});
}
}
3.小结
本篇文章主要介绍了反射中如何获取到反射类的对象,使用三种方法,获取后就可以使用反射类的对象实例化一个新的对象;
获取反射类中的构造方法调用getConstructor方法,使用Constructor类型接收;
获取反射类的方法调用getMetgod方法,使用Method类型接收;
获取反射类中的字段调用getField或者getDeclaredField方法,获取公开或者私有的字段,使用Filed类型接收;
在获取的构造方法,成员方法,字段需要修改时,需要调用setAccissble方法,表示是安全的设置,同时实例化对象,确定获取的是哪一个对象的构造方法,成员方法,字段,最后分别使用invoke和set方法执行方法和字段的修改,完成反射的基本过程。
在lambda表达式中主要需要确定的是()内是方法的参数,{}内的具体的实现逻辑,不需要关系方法的声明,返回值的类型。
欢迎在评论区分享你的使用经验或疑问,如果对相关技术感兴趣,下一篇文章将深入探讨这些内容。
期待与你再见,共同探索更多Java的奥秘!
更多推荐
所有评论(0)