前言
其实之前就该把这一个篇章写了的,但是一直拖了这么久,也已经在找实习了。所以这篇博客补上,就当做复习啦。
一丶反射
1.什么是反射?
首先对于什么是反射我们需要一些问题来反衬出来,这样来进行问题的场景设置。当我们进行Web项目开发的时候,我们使用了Servlet接口来处理用户请求,但是程序员自己没有创建Servlet对象,那么Servlet对象谁创建的?再如果说有一个类为私有类,也就是说其中的方法和属性全都是私有的,那么我们想要访问或者说使用这个类当中的方法和属性要怎么办呢?再或者说如果有一个类总是变动,难道在我们使用它的时候,只要他改一次我们就要new一次嘛?一切的一切,都关乎到这个问题,也就是反射。
那么回到这个问题的开始,什么反射?
反射指的是在运行状态中,对于任意一个类能够知道它的任意属性和方法。对于任意一个对象都能调用的它的任意属性和方法。这种动态获取信息以及动态调用对象方法的功能称为反射。
2.反射的使用
<1>反射的基石
首先要知道,反射的基石是字节码文件。JAVA程序是不能直接运行的,源文件要在编译之后形成.class的字节码文件,然后使用双亲委派模型被类加载器加载之后形成字节码文件对象,才可以在JVM中运行。
一以下的这些行为会触发类的加载
- new一个对象的时候
- 访问一个类静态成员的时候
- 调用一个类静态方法的时候
- 通过反射的方式创建一个类的字节码对象的时候
- 创建一个子类对象的时候
- java命令执行一个字节码文件的时候
**JAVA中,一切皆是对象。当字节码文件被加载到JVM之后,会形成一个Class对象。**即就是:该类在JVM中形成了一个对象(与new T()创建的对象是不同的)。
字节码对象包含了三部分内容
构造方法—>Constructor对象
成员方法—>Method对象
成员变量—>Filed对象
是不是有点懵?那看下面这个表格
这里注意了:Class对象(字节码文件对象)存放在方法区,不在堆里面。反射就是通过配置文件触发类加载,然后拿到字节码文件对象,然后通过字节码文件对象操作类中的成员。
<2>反射第一步–获取字节码文件
反射第一个就是获取Class对象,然后通过通过Class对象的核心方法达到反射的目的,即就是:在运行状态中,对于任意一个类,都能知道它的属性和方法。对于任意一个对象,都能调用它的属性和方法。
所以第一步就是获取我们字节码文件,也就是Class对象。获取对象的方式有三种,接下来我们一一演示一下。首先我们构造一个私有类(自己可以随意构造,我这里仅供参考)
public class Student {
private String name;
private String gender;
private int age;
private Student(String name,String gender,int age){
this.name = name;
this.gender = gender;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return name + " " + gender + " " + age;
}
}
一般情况下如果说我们想要使用这个类就直接new它了,对不对?但是这里它的构造方法和属性都是私有的,如果直接new就会直接报错
所以这里我们先获取它的字节码对象。
使用Class.forName(“类的全路径名”),该方法是Class中的静态方法,前提时必须知道类的全路径名,具体形式如下:
//1.通过class中的forName方法来进行获取
Class<?> stuClassObj1 = Class.forName("Year22_Month05.day29_2.Student");
使用这种方法有着一定的局限性,仅适合在编译器就已经明确要操作的Class,具体形式如下:
//2. 使用.class方法,仅适合在编译器就已经明确要操作的Class
Class<?> stuClassObj2 = Student.class;
这种方法的问题是在哪里呢?是通过对象来调用的,但是当当前类是个私有类的时候对象是没办法创建的,所以这种方法在这里是没有办法使用的。但是还是演示一下
//3. 使用对象的getClass()方法,要先创建对象,但是对象是私有的,所以这里就有点矛盾,所以可以先把对象设置为公有的进行演示
Class<?> stuClassObj3 = s.getClass();
<3>反射第二步–获取该对象的构造器
在获取到反射类的Class对象后,就需要获取该对象的构造器来创建实例。这里看一下这个
总结一下:
1.带不带S取决于你是否要获取所有的构造器对象,带S就是要,不带就是不要
2.带不带Declared取决于你的构造器是不是私有的,如果带了那就是私有的,如果不带就不是私有的
所以这里我们的获取构造器语句如下:
//2.获取Student类的构造方法
Constructor<?> stuConstruct = stuClassObj.getDeclaredConstructor(String.class,String.class,int.class);
还有一点要说下:如果说要获取所有的构造器,那么就要用数组对象来接收
<4>反射第三步–修改方法的权限
这里一步就是把私有的方法权限修改为可以访问的,如果不是私有的,是当前使用者可以访问的,那就不用写。
//3.修改方法的访问权限
stuConstruct.setAccessible(true);
<5>反射第四步–调用该方法创建对象实例
现在这个构造方法可以被访问了,所以我们直接调用Class类提供的newInstrance方法进行对象的创建就好了。当然了,再多嘴一句,如果说构造方法是私有的,第三步的修改方法的权限是一定要有的
//4.调用该方法
Student s = (Student) stuConstruct.newInstance("Peter","男",18);
所以最后给出完成的代码
//1.获取Student类对象
Class<?> stuClassObj = Class.forName("Year22_Month05.day29_2.Student");
//2.获取Student类的构造方法
Constructor<?> stuConstruct = stuClassObj.getDeclaredConstructor(String.class,String.class,int.class);
//3.修改方法的访问权限
stuConstruct.setAccessible(true);
//4.调用该方法
Student s = (Student) stuConstruct.newInstance("Peter","男",18);
System.out.println(s);
3.反射的延展
如果说我们想要单独的对某些属性修改或者说调用某些方法怎么办呢?这就是我写的这部分延展的内容了,当然了,一切都是在之前的基础上进行延展。
<1>反射属性
这部分单独针对属性讲,看下表
总结一下:
1.带不带s取决于你要的是所有的属性还是单独的属性,带s就是所有的,不带就是单独的
2.带不带Declared取决于你要的属性是不是私有的,如果是私有的那就带,不是私有的就不用带。
这里就演示一下获取私有的成员变量
Class<?> stuClassObj = Class.forName("Year22_Month05.day29_2.Student");
//反射私有的成员变量
Field stuAge = stuClassObj.getDeclaredField("age");
stuAge.setAccessible(true);
stuAge.set(s,19);//因为有一个隐藏的this参数,所以这里前面要加一个this也就是s
System.out.println(s);
针对以上代码,有几点需要特别讲解一下:
1.获取该系列函数的返回值必须要用Filed或者Filed[]接收
2.这里set方法设置值的时候,有两个参数,第一个是s,也就是你要传入你要设置值的对象。第
二个是你要设置的值
PS:这里的S是[2.反射的使用]中创建的S
这里打印一下吧,更直观一点。代码图如下:
运行图如下:
修改成功。
<2>反射方法
这部分单独针对方法,看下面:
和上面的总结一样,带不带S取决于你要所有方法还是单个方法,带不带Declared取决你要操作的方法是公有的还是私有的。
具体代码如下:
//获取Student类对象
Class<?> stuClassObj = Class.forName("Year22_Month05.day29_2.Student");
//反射私有的成员函数
Method stuSetAge = stuClassObj.getDeclaredMethod("setAge", int.class);
stuSetAge.setAccessible(true);
stuSetAge.invoke(s,20);//调用setage这个方法
System.out.println(s);
这里还是有几点需要说一下:
1.调用方法的时候除了给出方法名称外还要给出方法参数类型.class
2.反射方法的返回值要用Method或者Method[]接收
3.拿到了方法之后进行调用要用invoke方法
4.反射的优点和缺点
<1>优点
1.对于任意一个类,可以知道该类的所有属性和方法;对于任意一个对象,都能调用它的任意一个方法
2.可以提高程序的灵活性,可以降低耦合性等等
3.反射已经应用在了很多框架比如Spring上,被广泛的使用
<2>缺点
1.会导致效率问题,也就是效率降低
2.会有维护问题,因为绕过了源代码,反射代码比直接的代码更复杂。
二丶枚举
关于枚举这里稍微说一下
1.什么是枚举?
在说这个问题之前我们要知道为啥要有枚举:采用枚举类型,可以将一些状态,消息类型,错误状态码等类型组织起来统一管理。那么什么是枚举类?
所谓枚举类,其实本质也是一个类,该类默认继承自java.lang.Enum,就像用户写的类一样,默认继承Object
2.枚举类常用方法
Enum类常用方法如下:
是不是有点懵?还是用代码演示一下吧
public enum EnumColor {
RED, BLACK, GREEN;
public static void main(String[] args) {
// 以数组的方式返回所有的枚举成员
EnumColor[] colors = EnumColor.values();
// 打印每个枚举成员的索引
for(int i = 0; i < colors.length; ++i){
System.out.println(colors[i] + ":" + colors[i].ordinal());
}
// 将字符串GREEN转化为枚举类型
EnumColor color1 = EnumColor.valueOf("GREEN");
System.out.println(color1);
// 在进行转换时,如果有对应的枚举类型则转换,否则抛出IllegalArgumentException
// EnumColor color2 = EnumColor.valueOf("YELLOW");
// System.out.println(color2);
System.out.println("-------------------------------------");
System.out.println("枚举实例的比较");
// 注意此处的比较是使用枚举成员的索引来比较了
EnumColor black = EnumColor.BLACK;
EnumColor red = EnumColor.RED;
System.out.println(black.compareTo(red));
System.out.println(BLACK.compareTo(RED));
System.out.println(RED.compareTo(BLACK));
}
}
运行结果如下:
3.关于枚举类的构造方法
上述的枚举类型有一个不太好的地方,就是枚举中只有枚举常量,拿到一个枚举常量最后还是不知道它的颜色,所以可以给当前的枚举增加构造函数。但是这里构造方法有一个需要注意的地方
枚举类型的构造方法,只能是private类型。而且枚举类不能在类外被实例化,也不能被继承,它具有final属性。
- 如果枚举有参数可以添加对应的构造函数,但要注意:枚举的构造函数默认是私有的
- 枚举就是定义了一些状态或者常见集合,一般不需要单独实例化,用的时候到枚举类中找合适的即可
- 枚举的构造函数不能使用public和protected修饰。(这里不写默认是private)
4.枚举类的反射
这个时候就有聪明的同学想到啦,那既然枚举类构造方法只能是private,我能不能通过反射的方式来试试构造一个枚举对象呢?
import java.lang.reflect.Constructor;
public class TestEnum {
public static void main(String[] args) {
// ColorEnum color = new ColorEnum("RED",1);
try {
//获取class对象
Class<?> enumObj = Class.forName("Year22_Month05.day29_2.ColorEnum");
//获取构造器
Constructor<?> enumConstruct = enumObj.getDeclaredConstructor(String.class,int.class);
//修改权限
enumConstruct.setAccessible(true);
//创建对象实例
ColorEnum color = (ColorEnum) enumConstruct.newInstance("YELLOW",4);
//打印一下
System.out.println(color);
} catch (Exception e) {
e.printStackTrace();
}
}
}
最后结果如下:
这个时候小伙伴就好奇了,这是咋回事?我流程没有错呀!没有对应的构造函数?在EnumColor枚举类中不是已经定义了单个String类型的构造器了吗,为什么此处说找到不?前面说过,枚举是继承自 java.lang.Enum 包中的枚举,说到继承,继承了什么?继承了父类除构造函数外的所有东西,并且子类要帮助父类进行构造!而我们写的类,并没有帮助父类构造!那意思是,我们要在自己的枚举类里面,提供super吗?不是的,枚举比较特殊,虽然我们写的是两个,但是默认他还添加了两个参数,哪两个参数呢?我们看一下Enum类的源码:
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
所以我们要加上两个参数
import java.lang.reflect.Constructor;
public class TestEnum {
public static void main(String[] args) {
// ColorEnum color = new ColorEnum("RED",1);
try {
//获取class对象
Class<?> enumObj = Class.forName("Year22_Month05.day29_2.ColorEnum");
//获取构造器
Constructor<?> enumConstruct = enumObj.getDeclaredConstructor(String.class,int.class,String.class,int.class);
//修改权限
enumConstruct.setAccessible(true);
//创建对象实例
ColorEnum color = (ColorEnum) enumConstruct.newInstance("YELLOW",4);
//打印一下
System.out.println(color);
} catch (Exception e) {
e.printStackTrace();
}
}
}
然后运行一下:
又错了?啥意思?什么叫做:枚举类型不能反射?这里笔者源码没太读懂,所以就不贴源码进行讲解了,直接给出结论。枚举类型不能反射。
6.枚举总结
1. 枚举类默认继承自Enum
2. 用户定义的枚举类不能在被继承,默认具有final属性
3. 枚举类中罗列的常量,编译器编译完成后会生成枚举类静态实例对象
4. 枚举类也是类,具有构造函数,构造函数的访问限定只能是private的
5. 枚举类不能单独实例化对象
6. 枚举类不能被反射
7. 优点:枚举常量简单安全,枚举具有内置方法,代码更简单优雅;缺点:不可继承,也无法扩展