一、【反射】的定义
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表达式的作用与匿名内部类相似,因此它也存在【变量捕获】!