前言:
本章我们就来了解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.类名.class
Class 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;
}
@Override
public 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;
}
@Override
public 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;
}
@Override
public 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);
}
@Override
public 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("冬天","大雪");
//构造器私有化 -> 这样外界就不能直接创建对象 -> 防止直接 new
private TestEnum() {
}
private TestEnum(String name, String character) {
this.name = name;
this.character = character;
}
//只提供 get 方法 只读不写
public String getName() {
return name;
}
public String getCharacter() {
return character;
}
@Override
public 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;
}
@Override
public 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.枚举类提供一些额外的API
A[] 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() {
@Override
public void go() {
}
}, Y("张三") {
//调用有参构造器
@Override
public 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() {
@Override
public 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> {
@Override
public 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>() {
//匿名内部类实现大堆
@Override
public 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>() {
//还是匿名内部类
@Override
public 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>() {
@Override
public 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>() {
@Override
public 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表达式其实就是匿名内部类的简写。
这些东西多去使用我们就可以掌握,有更好的见解评论区见。