前言:
本章我们就来了解Java中的反射和枚举类。枚举类和反射其实有些关系,接下来我们就来学习他们的使用。
反射:
反射的作用:
反射:反射允许对成员变量,成员方法和构造方法的信息进行编程访问。
Java中有一个很有趣的知识——反射,它就类似于一个照妖镜,可以让一个类中所有的元素都无所遁形。
Java的反射(reflection)机制是在运行状态中,对于任何一个类,都能够知道这个类的所有属性和方法。
我们在使用IDEA的时候,总是输入什么以后会提示东西,其实这就是反射的应用。
说白了就是从类中拿东西,比如成员变量,成员方法,构造方法等。
Java文件被编译后,生成了.class文件,JVM此时就要去解读.class文件,被编译后的Java文件.class也被JVM解析为一个对象,这个对象就是java.lang.Class,这样当程序在运行时,每个java文件就最终变成了Class类对象的一个实例。我们通过Java的反射机制应用到这个实例,就可以去获得甚至去添加改变这个类的属性和动作,使得这个类成为一个动态的类。
获取class对象:
通过上面的说明,当我们使用反射时,必须先拿到一个类。
此时我们就介绍拿到类的3种方法:
1.在Java中已经定一个了一个类叫做Class,就是用来描述字节码文件的。其中有一个静态方法叫做forName,参数为全类名。
2.类名.class
3.对象.class
我们一般使用Class类中forName方法获取类,上面说了要获取全类名,那么什么是全类名呢?
全类名:全类名是指package + (.)类的名字
我们其实很容易获取全类名,我们找到要获取的类,之后右击类名,复制参数即可:
一定在双引号中复制,否则就是类的名,不是全类名。
当我们只知道方法不知道该使用什么类时,可以先写方法并传入参数,之后使用快捷键生成左边:
Ctrl + Alt + V : 自动生成左边
我们说有三种获取类的方法,如果获取的是同一个类,其实获取的都是一样的。
public class MyReflect {public static void main(String[] args) throws ClassNotFoundException {/** 获取class的三种方式:* 1.Class.forName("全类名");* 2.类名.class* 3.对象.getClass();* *///1.Class.forName("全类名");//全类名 : 包名 + 类名Class clazz1 = Class.forName("dome1.Student");//全类名//因为里面有受查异常,所以在主方法中加上System.out.println(clazz1);//2.类名.classClass clazz2 = Student.class;System.out.println(clazz2);System.out.println(clazz1 == clazz2);//获取的都是字节码问价,所以相同//3.对象.getClass()Student s = new Student();Class clazz3 = s.getClass();System.out.println(clazz1 == clazz2);System.out.println(clazz1 == clazz3);}
}
package dome1;public class Student {private String name;private int age;public Student(String name, int age) {this.name = name;this.age = age;}public Student() {}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}
其中最常用的就是第一种方式,第三种方式局限性最大,必须有当前对象才能使用。
获取构造方法:
此时就获取了字节码文件了,之后我们就要从中获取构造方法等内容了。
想一想,我们既然获取了构造方法,是不是就能通过该构造方法创建对象了呢?我们先来看相关方法:
package dome1;public class Student {private String name;private int age;protected Student(String name, int age) {this.name = name;this.age = age;}public Student() {}private Student(String name) {this.name = name;}public Student(int age) {this.age = age;}public String getName() {return name;}private void setName(String name) {this.name = name;}protected int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}
package dome1;import java.lang.reflect.Constructor;public class MyReflectDemo {public static void main(String[] args) throws ClassNotFoundException {//1.获取class字节码文件对象Class clazz = Class.forName("dome1.Student");//2.获取构造方法Constructor[] cons = clazz.getConstructors();for (Constructor con : cons) {System.out.println(con);}}
}
可以看到,我们已经获取了所有的public修饰的构造方法。为了不冗余,我们直接给出图片,我们更改getConstructors方法,并观察区别:
单独获取可以指定获取构造方法,通过参数来指定。
获取修饰权限付:
当我们获取万构造方法以后,我们可以获取这个构造方法的权限修饰符,通过getModifiers方法,返回的是一个整形。
至于每个权限修饰符,都有对应的整数。由于博主在叙利亚,下面给出战损版:
Constructor con2 = clazz.getDeclaredConstructor(String.class);
//我们单独获取构造方法,往里面添加参数,就可以指定获取那个构造方法
//System.out.println(con2);int modifiers = con2.getModifiers();
//获取构造方法的权限修饰符,以整数形式体现
System.out.println(modifiers);
IDEA中可以提示参数,也就是通过反射原理来实现的。
当我们不知道一个方法中有哪些参数是,可以使用:Ctrl + P 来进行提示。
获取方法参数:
使用getParameter方法来获取参数:
Parameter[] parameters = con2.getParameters();
//获取构造方法所有参数
for (Parameter x : parameters) {System.out.println(x);
}
获取私有方法并实例化对象:
此时我们已经获取了构造方法,那么我们就可以通过获取的构造方法去实例化一个对象,使用newInstance方法并传入参数去实例化对象。
Constructor con2 = clazz.getDeclaredConstructor(String.class);
//con2.setAccessible(true);//相当于临时取消修饰权限校验//此时我们已经拿到了构造方法,那么我们就可以通过此构造方法去创建对象
Student stu = (Student) con2.newInstance("张三");
System.out.println(stu);
因为报错是private,我们此时也就是看到了该构造方法,但不能直接去构造。我们要加上setAccessible去临时修改权限。
这就是暴力反射,因为是私有方法,但是通过反射使用了。
获取类中的成员:
package dome2;public class Student {private String name;private int age;public String gender;public Student() {}public Student(String name, int age, String gender) {this.name = name;this.age = age;this.gender = gender;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", gender='" + gender + '\'' +'}';}
}
public class MyReflect {public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {//1.获取字节码文件Class clazz = Class.forName("dome2.Student");//2.获取公共成员变量Field[] fields = clazz.getFields();for (Field x : fields) {System.out.println(x);}//获取单个成员变量Field name = clazz.getDeclaredField("name");System.out.println(name);//获取成员变量名字String n = name.getName();//获取成员变量名System.out.println(n);//获取成员变量的数据类型Class<?> type = name.getType();System.out.println(type);//获取成员变量记录的值 和对象有关,所以先初始化一个Student s = new Student("张三",23,"男");name.setAccessible(true);//临时取消访问权限String value = (String) name.get(s);//获取该对象name记录的值System.out.println(value);//修改对象里面记录的值name.set(s, "lisi");System.out.println(s);}
}
获取类中的方法和抛出的异常:
我们再来看利用反射获取成员方法。
package demo3;import java.io.IOException;public class Student {private String name;private int age;public Student() {}public Student(String name, int age, String gender) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public void sleep() {System.out.println("睡觉");}private void eat(String st) throws IOException,NullPointerException,ClassCastException {System.out.println("在吃" + st);}private void eat(String st, int a) {System.out.println("在吃" + st);}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}
public class MyReflect {public static void main(String[] args) throws ClassNotFoundException {//1.获取class字节码文件Class clazz = Class.forName("demo3.Student");//2.获取里面所有的方法对象(包括父类的公共方法)Method[] methods = clazz.getMethods();for (Method method : methods) {System.out.println(method);}}
}
我们使用getDeclareMethods方法,获取的是该类中的所有方法(没有父类中的方法)。
public static void main(String[] args) throws ClassNotFoundException {//1.获取class字节码文件Class clazz = Class.forName("demo3.Student");/*//2.获取里面所有的方法对象(包括父类的公共方法)Method[] methods = clazz.getMethods();for (Method method : methods) {System.out.println(method);}*///3.获取里面所有的方法对象(不获取父类,但可以获取本类中所有方法Method[] methods = clazz.getDeclaredMethods();for (Method method : methods) {System.out.println(method);}}
注意这里的getMethods和getDeclaredMethods是不一样的,getMethods是获取所有公共方法(包括父类公共方法);getDeclaredMethods是获取本类所有方法。
调用获取的方法(invoke方法):
一样的,我们获取了方法,也就可以使用该方法,但是因为Java中有方法重载,所以必须传入参数,这样才可以调用指定参数。
获取方法后,我们也可以使用Class类中的getExceptionTypes方法获取该方法抛出的异常。
获取方法后, Method中有invoke方法,是调用指定方法,第一个参数是对象,也就是说,我们使用之前要现获取构造方法,之后实例化一个对象,之后将对象传入invoke方法的第一个参数。
public class MyReflect {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {//1.获取class字节码文件Class clazz = Class.forName("demo3.Student");/*//2.获取里面所有的方法对象(包括父类的公共方法)Method[] methods = clazz.getMethods();for (Method method : methods) {System.out.println(method);}*//*//3.获取里面所有的方法对象(不获取父类,但可以获取本类中所有方法Method[] methods = clazz.getDeclaredMethods();for (Method method : methods) {System.out.println(method);}*///获取指定单一方法Method m = clazz.getDeclaredMethod("eat", String.class);System.out.println(m);//因为有方法重载,所以要指定参数//获取方法修饰符int modifiers = m.getModifiers();System.out.println(modifiers);//获取方法名字String name = m.getName();System.out.println(name);//获取方法形参Parameter[] parameters = m.getParameters();for (Parameter parameter : parameters) {System.out.println(parameter);}//获取方法抛出的异常Class[] exceptionTypes = m.getExceptionTypes();for (Class exceptionType : exceptionTypes) {System.out.println(exceptionType);}//方法运行/*Method 类中用于创建对象* Object invoke(Object obj, Object... args): 运行方法* 参数一:用obj对象调用的方法* 参数二:调用方法传递的参数(如果是void就不写)* 返回值:方法的返回值(如果没有就不写)* */Student s = new Student();//参数一s:方法调用者//参数二"羊肉":表示调用方法的时候传递的实际参数m.setAccessible(true);//记住这个方法是私有的m.invoke(s,"羊肉");//如果有返回值就接受,可以强制转换}
}
注意,这里面的Student类还是上面demo3里面的Student类。
枚举:
枚举的作用:
我们有时候使用的值是不会改变的,像一年四季,这些都是常量,不会发生改变,所以便有了枚举类。枚举是JDK1.5以后引入的。主要是将一组常量组织起来。
自定义枚举类:
看枚举之前,我们先来看自定义枚举类,此时我们自己定义一个枚举类。
/*
* 1.构造器私有化
* 2.本类内部创建一组对象 四个 春夏秋冬
* 3.对外暴露对象 -> 通过为对象前加 public final static 修饰符
* 4.可以提供 get 方法 但是只读不写(没有set方法)
* *///自定义枚举类
public class TestEnum {private String name;private String character;//特点//对外暴露public static final TestEnum SPRING = new TestEnum("春天","花香");public static final TestEnum SUMMER = new TestEnum("夏天","烈日");public static final TestEnum AUTUMN = new TestEnum("秋天","气爽");public static final TestEnum WINNER = new TestEnum("冬天","大雪");//构造器私有化 -> 这样外界就不能直接创建对象 -> 防止直接 newprivate TestEnum() {}private TestEnum(String name, String character) {this.name = name;this.character = character;}//只提供 get 方法 只读不写public String getName() {return name;}public String getCharacter() {return character;}@Overridepublic String toString() {return "TestEnum{" +"name='" + name + '\'' +", character='" + character + '\'' +'}';}
}
public class Test {public static void main(String[] args) {System.out.println(TestEnum.SPRING);System.out.println(TestEnum.SUMMER);System.out.println(TestEnum.AUTUMN);System.out.println(TestEnum.WINNER);}
}
定义为静态就是说明不需要实例化对象,而且为了防止其修改使用final。
枚举类构造方法默认被 private 修饰,写成其他则报错。
使用枚举类(values方法和枚举类的方法):
我们每次使用IDEA去创建类时,总会有一些选项,其中就有一个enum选项,我们去创建一个。
public enum SEnum {SPRING("春天","花香"),SUMMER("夏天","烈日"),AUTUMN("秋天","气爽"),WINTER;//这里WINTER调用无参构造器private String name;private String character;SEnum() { //注意,这里没有 private 修饰,因为被默认修饰了}SEnum(String name, String character) {this.name = name;this.character = character;}public String getName() {return name;}public String getCharacter() {return character;}@Overridepublic String toString() {return "SEnum{" +"name='" + name + '\'' +", character='" + character + '\'' +'}';}
}
我们使用枚举类发现能省去很多代码。
我们不能将枚举类进行继承:
可以发现其报错,因为Java不能多继承,所以我们猜测可能是其可能已经继承了一个类。
我们进行反编译(在IDEA左边右击该类,找到Open In -> Explorer,之后回退到Out目录,进入以后找到这个类生成的.class文件,并在目录中输入 cmd ,利用 javap 字节码文件名进行反编译):
当然这里面有些是父类的方法。
发现其默认继承Enum并且是被final修饰的。
values()方法可以将枚举类转变为一个枚举类型的数组,因为枚举中没有下标,我们没有办法通过下标来快速找到需要的枚举类,这时候,转变为数组之后,我们就可以通过数组的下标,来找到我们需要的枚举类。
其实枚举在定义的时候就已经创建了对象,当然枚举里面也可以定义成员并且定义方法。
public enum A {//注意:枚举类第一行必须罗列的是枚举对象的名字X, Y, Z;private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}
}
public class Test {public static void main(String[] args) {//认识枚举A a1 = A.X;System.out.println(a1);//1.枚举类的构造方法是私有的,不能对外创建对象//A a = new A();//2.枚举类的第一行都是常量,记住的是枚举类的对象A a2 = A.Y;//3.枚举类提供一些额外的APIA[] as = A.values();for (A x : as) {System.out.print(x + " ");}System.out.println();A a3 = A.valueOf("Y");System.out.println(a3.name());//拿名字System.out.println(a3.ordinal());//拿索引}
}
注意事项:
常量定义要放到最开始,构造方法放在其下面,否则报错。
主方法直接使用枚举类名调用其中成员,因为是静态变量,并且不能初始化其中成员,而且也不能添加。
枚举类(Enum)中只有一个构造方法:
此时父类中有构造方法,但是我们写的子类没有构造方法,没有调用super。这也就证明了枚举类是一个特殊的类。
枚举类无法被继承,无法被扩展,也就证明了它很安全。
我们比较获取的索引:
public static void main(String[] args) {System.out.println(SEnum.SPRING);SEnum[] sEnum = SEnum.values();for (SEnum anEnum : sEnum) {System.out.println(anEnum.ordinal());//获取枚举成员的索引位置}System.out.println("====");SEnum v1 = SEnum.valueOf("SPRING");System.out.println(v1);/*SEnum v2 = SEnum.valueOf("eheh");//前提是枚举类型中必须包含该常量System.out.println(v2);*///因为 Enum 类实现了 Comparable 接口System.out.println(SEnum.SPRING.compareTo(SEnum.SUMMER));//比较}
枚举类无法反射:
上面我们讲过,反射可以让一个类中的任何东西都无所遁形,那么我们利用反射区获取枚举类中的属性。
public static void main(String[] args) throws ClassNotFoundException,NoSuchMethodException, InvocationTargetException,InstantiationException, IllegalAccessException {Class clazz = Class.forName("demo1.SEnum");Constructor con = clazz.getDeclaredConstructor(String.class, String.class);con.setAccessible(true);SEnum o = (SEnum) con.newInstance("晴天", "开心");//此时通过反射新建了一个枚举对象System.out.println(o);
}
因为enum继承于Enum,Enum中的构造方法又有两个形参,所以我们在传入4个参数。所以对以上代码进行以下局部修改:
Constructor con = clazz.getDeclaredConstructor(String.class,int.class,String.class, String.class);
con.setAccessible(true);
SEnum o = (SEnum) con.newInstance("黑夜",9,"晴天", "开心");
此时我们进入源码观察。
所以证明了枚举对象是非常安全的,是不可以通过反射来创建一个枚举对象的。
抽象枚举:
其实就是在枚举类中定义了抽象方法,因为枚举对象就定义在枚举类中,所以定义的对象必须实现该抽象方法。
//抽象枚举
public enum B {X() {@Overridepublic void go() {}}, Y("张三") {//调用有参构造器@Overridepublic void go() {System.out.println((getName() + "吃饭"));//调用方法}};public abstract void go();//这里面定义了一个抽象方法//因为枚举类中就定义了对象//所以定义时必须实现该抽象方法B(String name) {this.name = name;}//注意:这里面构造方法都是默认私有的B() {}private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}
}
public class Test {public static void main(String[] args) {B a = B.Y;a.go();}
}
lambda表达式:
lambda表达式基本语法:
(parameters)-> expression 或 (Parameters)-> { statements;}
Lambda表达式由三部分组成:
- paramaters:类似方法中的参数形参列表,这里的参数是函数式接口里的参数。这里的参数类型可以明确的声明也可以不声明而由JVM隐含的推断。另外,当只有一个推断类型时可以省略掉括号。
- ->:可以理解为“被用于”的意思
- 方法体:可以是表达式也可以是代码块,是函数是接口里方法的实现。代码块可返回一个值或者什么都不返回,这里代码块等同于方法的方法体。
我们举一个相关例子:
//1.不需要参数,返回值为2
() -> 2;//2.接收一个参数(数字类型),返回其2倍的值
x -> 2 * x;//3.接收2个参数(数字),并返回他们的和
(x, y) -> x + y;//4.接收2个int型参数,返回他们的乘积
(int x, int y) -> x * y;//5.接收一个String对象,并在控制台打印,无返回值
(String s) -> System.out.println(s);
函数式接口:
要想学好lambda表达式,我们首先要来了解什么是函数式接口。
函数式接口定义:一个接口有且只有一个抽象方法(在Java1.8中,新增了default修饰的抽象方法也可以定义在函数式接口。)。
//函数式接口
@FunctionalInterface
interface NPNR {//只能有有一个方法void test();default void test2() {System.out.println("JDK1.8新特性,default默认方法可以有具体的实现");}
}
lambda表达式和匿名内部类的关系:
暂停,接下来有涉及到一个知识,匿名内部类,我们只有了解它之后才能更好的使用lambda表达式。
//函数式接口
@FunctionalInterface
interface NPNR {//只能有有一个方法void test();
}
public static void main(String[] args) {//此时我们先不使用 lambda 表达式NPNR npnr1 = new NPNR() {@Overridepublic void test() {System.out.println("这是一个匿名内部类,里面重写了 test() 方法");}//这个类实现了test并重写,因为是匿名内部类,所以这个类没有名字};//我们使用 lambda 表达式来进行修改NPNR npnr2 = () -> System.out.println("使用 lambda 表达式重写了 test 方法");NPNR npnr3 = () -> {System.out.println("因为这里有多条语句,所以用大括号");System.out.println("前面小括号不能省略");};npnr1.test();npnr2.test();npnr3.test();
}
关于匿名内部类更加详细的知识可以去看我的这篇文章Java中的内部类-CSDN博客。当然,以下还有详细对比其和lambda表达式的区别,我们也可以继续阅读。
lambda表达式的使用:
得知了匿名内部类之后,lambda表达式其实就是简化了匿名内部类的写法。接下来我们举一些实际的例子:
//函数式接口
@FunctionalInterface
interface NPNR {//只能有有一个方法void test();
}//有一个参数无返回值
interface OPNR {void test(int a);
}//有一个参数一个返回值
interface OPOR {int test(int a);
}interface MPNR {void test(int a, int b);
}interface NPR {int test();
}interface OPR {int test(int a);
}interface MPR {int test(int a, int b);
}public class Test {public static void main(String[] args) {/*OPNR opnr = a -> {System.out.println("这是有一个参数,无返回值的 lambda表达式");};*///因为只有一个参数,所以可以把前面的括号省略OPNR opnr = (a) -> {System.out.println("这是有一个参数,无返回值的 lambda表达式");};opnr.test(1);OPOR opor = a -> {System.out.println("有一个参数,一个返回值");return 5;};opor.test(5);MPNR mpnr = (a, b) -> {System.out.println("两个参数,没有返回值");System.out.println(a + b);};mpnr.test(1, 2);NPR npr = () -> {System.out.println("没有参数,一个返回值");return 10;};npr.test();OPR opr = (a) -> {System.out.println("一个参数,一个返回值");return 10;};opr.test(10);MPR mpr = (a, b) -> {System.out.println("有多个参数,一个返回值");return 20;};mpr.test(10, 20);}
}
看到这里,我们其实就可以把lambda表达式当做一个匿名内部类。
变量捕获:
注意这句话叫做变量捕获,也就是说它是捕获常量的。
@FunctionalInterface
interface Opp {void test();
}public class TestLast {public static void main(String[] args) {int sz = 100;sz = 99;Opp opp = () -> {System.out.println("调用" + sz);};}
}
匿名内部类或者lambda表达式中如果想使用外部的变量,这个变量不能修改,这叫做变量捕获。而且更不能在里面改变外部变量。
lambda表达式和匿名内部类的使用:
为了方便复习:
总体来说,这两者相辅相成。还是否记得我们之前使用PriorityQueue,构建的默认是小根堆,构建大根堆需要我们传入比较器,每次都要去先建一个类,之后传入,有三种实现方法:
创建比较器实现:
class Eg implements Comparator<Integer> {@Overridepublic int compare(Integer o1, Integer o2) {//构建大堆return o2.compareTo(o1);}
}public class TestLast {public static void main(String[] args) {PriorityQueue<Integer> queue = new PriorityQueue<>(new Eg());//此时我们自己构建比较器queue.offer(1);queue.offer(2);queue.offer(3);System.out.println(queue.peek());}
}
使用匿名内部类实现:
public static void main(String[] args) {PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {//匿名内部类实现大堆@Overridepublic int compare(Integer o1, Integer o2) {return o2.compareTo(o1);}});queue.offer(1);queue.offer(2);queue.offer(3);System.out.println(queue.peek());
}
使用lambda表达式实现:
这里有一个大前提,就是接口必须实现匿名内部类:
public static void main(String[] args) {PriorityQueue<Integer> queue = new PriorityQueue<>((o1,o2) -> {return o2.compareTo(o1);});queue.offer(1);queue.offer(2);queue.offer(3);System.out.println(queue.peek());
}
forEach方法:
forEach方法可以理解为遍历,我们观察其源码。
public static void main(String[] args) {ArrayList<String> list = new ArrayList<>();list.add("hello");list.add("bit");list.add("lambda");list.forEach(new Consumer<String>() {//还是匿名内部类@Overridepublic void accept(String s) {System.out.println(s);}});
}
使用lambda表达式会更加简洁:
在HashMap中也可以利用forEach方法去获取map中的所有元素:
public static void main(String[] args) {HashMap<Integer, String> map = new HashMap<>();map.put(1, "hello");map.put(2, "bit");map.put(3, "lambda");map.forEach(new BiConsumer<Integer, String>() {@Overridepublic void accept(Integer integer, String s) {System.out.println("key: " + integer + " val: " + s);}});
}
使用lambda表达式改写以后为:
public static void main(String[] args) {HashMap<Integer, String> map = new HashMap<>();map.put(1, "hello");map.put(2, "bit");map.put(3, "lambda");/*map.forEach(new BiConsumer<Integer, String>() {@Overridepublic void accept(Integer integer, String s) {System.out.println("key: " + integer + " val: " + s);}});*/map.forEach((a, s) -> System.out.println("key: " + a + " val: " + s));
}
总结:
Java的反射(reflection)机制是在运行状态中,对于任何一个类,都能够知道这个类的所有属性和方法。
优点:对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法。增加程序的灵活性和扩展性,降低耦合性,提升自适应能力。反射已经运用了很多流行框架:Spring等。
缺点:使用反射会有效率问题,会导致程序效率降低。反射技术绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂。
合理利用反射,一定在安全环境下使用。我们应该熟练的使用以上的几个类。
枚举是一种特殊的类,第一行必须罗列枚举的枚举对象的名名字,之后可以在里面使用构造方法。
枚举对象无法新建,构造方法是必须而且默认是私有的。
抽象枚举其实就是在里面定义了抽象方法。
lambda表达式其实就是匿名内部类的简写。
这些东西多去使用我们就可以掌握,有更好的见解评论区见。