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的奥秘!

更多推荐