Java:【反射】、【枚举】、【lambda表达式】

一、【反射】的定义

Java的反射机制:

是在【运行状态】中,对于任意一个类,能够知道这个类的【所有属性和方法】;对于任何一个对象,能够调用它的【任意方法和属性】;这种【动态获取信息】以及【动态调用对象方法】的功能称为Java语言的【反射机制】

二、反射相关的类

  从简答层次上去理解【反射机制】,需要我们从以下四个类去研究,换句话说,只要我们弄懂了以下的类,我们就可以更加透彻地理解什么是【反射机制】。

类名用途
Class类

代表类的实体,在运行时的Java应用程序中代表类和接口

Field类代表类的成员变量/类的属性
Method类代表类的方法
Constructor类代表类的构造方法

三、Class类

class类:代表类的实体,在运行时的Java应用程序中代表类和接口

   在起初学习Java的时候,我们知道,Java文件被编译后,会生成【.class文件】,JVM此时去解读这个【.class文件】,被编译后的java文件【.class】也被JVM解析为一个【对象】,这个对象就是【java.lang.Class】。这样当程序运行时,每一个java文件最终变成了Class类对象的一个【实例】。

1、获取Class对象的三种方式:

   在反射之前,我们需要做的第一步就是先拿到当前需要反射的类的Class对象,然后通过Class对象的核心方法,达到反射的目的。(注:Class也其他类一样,拥有很多方法,可以让我们去调用)

   下面我们介绍获取Class对象的三种方法:

   在正式介绍之前,我们先做一些工作,在创建一个【reflectdemo包】,在这个包底下创建一个Java Class文件【Test.java】;

  该Test.java文件里面包含一个Student类,我们希望该类中包含:成员变量、构造方法、普通方法和toString方法(访问权限最好既有public又有private)

class Student{
    //私有属性name
    private String name="xiaoMin";
    //公有属性age
    public int age=18;

    //私有属性的构造方法
    public Student(){
        System.out.println("Student()");
    }
    //公有属性的构造方法
    private Student(String name,int age){
        this.name=name;
        this.age=age;
        System.out.println("Student()");
    }

    //普通方法
    private void eat(){
        System.out.println("i am eat");
    }
    public void sleep(){
        System.out.println("i am sleep");
    }
    private void function(String str){
        System.out.println(str);
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }


}

第一种:使用Class.forName(“类的路径名”);静态方法

  如果这么使用这个方法,它会报【编译型异常】,这个时候,通常的做法是使用try……catch……语句!

修改为: 

   第二种:使用.class方法

 

   第三种:使用类对象的getClass()方法

 


   此时,通过调用三种方法,我们获得了三个Class对象,但是我要告诉你们,这三个对象其实是同一个对象!因为一个类在JVM中只有一个Class对象!

 



2、 使用反射【实例化一个对象】(一): 

   我们先在reflectdemo这个包底下新建一个Java Class文件【ReflectClassDemo.java】

  1、首先,因为我们要借助反射实例化一个Student类的对象,所以我们需要获得Student类的Class对象,这里我们使用上述三种方式中的【方法1】

2、调用Class类中的方法,实例化Student类的对象! 

方法名用途
newInstance()创建类的实例

 当我们去调用该方法时,会看到这个方法【被划了一条线】,这表示该方法已经现在已经不怎么使用了!但是仍然可以被使用

  另外,该方法的返回值是一个Object类,前面的学习我们知道,Object类是所有类的父类,因此使用Student类去接收时,涉及向下转型问题!需要强制类型转换

   这就完了吗?NONONO,我们此时还发现调用该方法时还会报错,也就是newInstance方法下面的【红色波浪线】,这是一个异常,我们鼠标移动到【波浪线】处,添加try……catch……语句即可!

  

  此时,就会自动添加try……catch……语句! 

    3、接下来让我们看看有没有实例化成功!由于之前的Student类以及重写了toString方法!那么我们可以通过它来验证!

程序运行!



3、使用反射【实例化一个对象】(二)

   前面我们使用反射实例化Student类的时候不知道你有没有注意到:实例化对象时会调用构造方法,我们前面调用newInstance时调用的是无参的构造方法!并且,这个方法是公有的!

   那么,如果我们要调用【私有的】【含参数】的构造方法实例化对象,该怎么做呢?

   这个表中的方法可以帮助我们解决上述的问题!分析下表可以看出,我们要调用【私有带参数】的构造方法时,需要借助【第三、第四】个方法!

方法用途
getConstructor(Class…<?>parameterTypes)获得该类中参数类型匹配的【公有】的构造方法
getConstructors()获得该类中的所有【公有】的构造方法
getDeclaredConstructor(Class…<?>parameterTypes)获得该类中参数类型匹配的构造方法
getDeclaredConstructors()获得该类中的所有的构造方法

  1、首先,因为我们要借助反射实例化一个Student类的对象,所以我们需要获得Student类的Class对象,这里我们使用上述三种方式中的【方法1】

    2、调用getDeclaredConstructor方法传入构造方法的参数类型

   getDeclaredConstructor方法返回值的类型是Constructor<T>,因此我们实例化一个Constructor类constructor来接收该方法的返回值,泛型T传入Student

  另外,调用该方法时会出现【红色波浪线】,这个异常解决方法与前面的情况一致,添加try……catch语句即可! 

  最后,再强制类型转换一下该方法返回值即可! 

  3、通过constructor调用newInstance方法实例化对象

  接下来实例化对象的时候,我们不再借助Class类中的newInstance方法,而是Constructor类中的newInstance方法!

  (红色波浪线也是添加try……catch……语句即可解决)

 4、验证是否实例化对象成功!

 程序运行:

  发现报错了!其中这是因为【权限问题】,我们调用的该构造方法是【private】修饰的,被该修饰符修饰的方法和成员方法无法在【类外】使用!

   此时,只要使用一条语句即可!

   即通过constructor调用setAccessible方法,传入参数true!



 4、使用反射修改private修饰的成员变量

   现在,我们来学习一下如何使用反射修改【private修饰】的成员变量neme!其实思路与前面的大同小异!

   

  1、首先,因为我们要借助反射实例化一个Student类的对象,所以我们需要获得Student类的Class对象,这里我们使用上述三种方式中的【方法1】

   2、使用与  【实例化一个对象】(二)   相同的方式调用无参构造方法实例化一个Student类

   3、修改【private修饰】的成员变量name

   下面介绍Class中一个获得成员变量的方法!

方法用途

getDeclaredField(")

获得该类中所有的成员变量

  4、验证成员变量name是否被修改! 

   在main方法中调用修改成员变量name的方法! 

    程序运行: 



5、使用反射调用【private修饰】的普通方法

  现在,我们学习一下如何使用反射调【private修饰】的成员方法!

   用该方法打印:我爱学习!

    代码演示:(思路与前面一样,在这里不详细重复!)

 public static void reflectPrivateMethod(){
        //1、获得Student类的【Class对象】
        Class<?> c1=null;
        try {
            c1=Class.forName("reflectdemo.Student");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        //2、调用无参构造方法实例化一个Student对象
        try {
            Constructor<Student> constructor=(Constructor<Student>) c1.getDeclaredConstructor();
            Student student=(Student) constructor.newInstance();
            //获得成员方法:getMethod(方法名,参数类型.class)
            Method method=c1.getDeclaredMethod("function", String.class);
            method.setAccessible(true);//开放权限,使【private修饰】的方法可以被调用
            method.invoke(student,"我爱学习!");//调用方法!


        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

   程序运行:



四、【枚举】的定义

   枚举的主要用途:是将一组【常量】组织起来,以下是一种枚举方式【常量枚举】 :
 

public static final int RED=1;
public static final int GREEN=2;
public static final int BLACK=3;

   但是,常量枚举有一个缺点,例如:可能碰巧有个数字1,但是它可能会误以为是RED,现在我们可以直接用【枚举】来组织,这样一来,就有了【枚举类型】,而不是普通的整型!

public enum TestEnum{
RED,GREEN,BLACK;
}

五、【枚举】的使用

   使用枚举之前,首先我们要创建一个【枚举类】

   1、先在枚举类中创建好四个枚举对象 

1、【switch语句】与【枚举】 

  我们可以结合switch语句使用枚举!

程序运行:



2、以数组形式返回枚举类中的枚举对象

方法用途
values()以数组形式返回枚举类中的枚举对象

 



3、获取【枚举成员】的索引位置

方法用途

ordinal()

获取枚举成员的索引位置

 



4、比较两个【枚举成员】在定义时的顺序

方法用途
compareTo()比较两个枚举成员在定义时的顺序



  讲到这里,我们有一个疑问,上面介绍的三个方法都不是我们实现的,为什么可以调用?

其实,当我们创建好一个枚举类型,这个枚举类【默认继承】Enum这个类,这些方法是Enum类中实现的! (values方法除外!)



 5、枚举的【构造方法】

  假设我们需要给枚举对象赋予有意义的内容,例如:RED,代表:1,颜色:红色,那么我们可以自己实现一个【构造方法】 !

  在这里需要注意的是,枚举类的构造方法只能使用【private】修饰!!!

   自定义构造方法后,每个枚举对象都需要在创建的同时传入参数【color】和【num】的值!



六、Lambda表达式

  Lambda表达式允许你通过【表达式】来代替功能接口!是实现接口中【抽象方法】的另外一种写法!

  基本语法:(parameters)->{statements;}

  Lambda表达式由三部分组成:

  1、parameters:类似方法中的形参列表

  2、->:可以理解为“被用于”的意思

  3、statements:类似方法中的表达式

1、函数式接口

  现在,我们来理解一个东西:函数式接口!

  它与普通接口有上面区别呢?

1、我们都知道,接口中的方法不用具体实现,这种方法称之为【抽象方法】!

   二者区别就是:普通接口可以有多个【抽象方法】,而函数式接口只能有一个【抽象方法】!

   函数式接口:

2、【无返回值无参数】的Lambda表达式:

  为了更好的表示出Lambda表达式的作用,首先,我先来介绍一下,与Lambda表达式有相同作用的一个操作!而这个操作不借助Lambda表达式,而是借助我们前面所学的【匿名内部类】!

  如图:我们使用【匿名内部类】实现了【函数式】接口中的test()方法!使其可以被使用!

程序运行:


  那么,使用Lambda表达式时该如何做呢?

  也就是说,Lambda表达式的作用就是【重写】函数式接口中的【抽象方法】!

  在()中传入参数列表,{ }写入方法主体的表达式!



3、【无返回值一个参数】的Lambda表达式

  如图:这里我们的抽象方法中有一个整型参数时,我们就需要在()中输入参数列表,这里的参数列表不需要像正常的方法中书写为【数据类型  变量名】的格式,只需要输入【任意字符】来代替即可!(可以是a,b,c……)

  因此该Lambda表达式可以理解为:test方法的参数是g,方法作用是打印g的值!



4、【有返回值无参数】的Lambda表达式

 



5、【有返回值有参数】的Lambda表达式

 



 七、变量捕获

     Lambda表达式中存在【变量捕获】,了解变量捕获后,我们才能更好地理解Lambda表达式的【作用域】。另外,我们需要知道:java中的【匿名类】,会存在【变量捕获】!

    那为什么要再次提到【匿名类】?其实,当年再仔细研究【标题六(2)】时,你就会发现:Lambda表达式的作用其实和【匿名类】是相似的!

  那么,我们由【匿名类】存在【变量捕获】可以退测:【Lambda表达式】也存在【变量捕获】!


     好了,大家看到这里可能还是不知所云,什么是【变量捕获】?让我们来看看吧!

     观察下图!首先,我们在Main方法中定义变量a,赋值为10;接着,我们在【匿名内部类】的重写方法中打印a的值,可以看到,并没有什么问题!

   那么让我们再观察下图!

  与前面不同的是,这里的在【匿名内部类】中修改了a的值,另外,可以看到,变量a报错!

在匿名内部类中,如果我们要使用一个变量,那么这个变量要么是常量(final修饰),要么在使用之前和之后没有被修改过!从某种意义来说,匿名内部类要访问的变量是一个【常量】!

   匿名内部类访问的量要么是常量,要么没有被修改过,这个过程就是【变量捕获】!


   由于Lambda表达式的作用与匿名内部类相似,因此它也存在【变量捕获】!

 



  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值