目录
2.4 枚举和反射(阿里面试:为什么枚举实现单例模式是安全的?)
1、反射
1.1 反射的定义
Java的反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任 意一个对象,都能够调用它的任意方法和属性,也能够修改部分类型信息;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。
简单来说,反射就是在运行的状态下,能够看清类的基本信息。
我们可以这样理解:在安检时,我们把行李箱放在安检的机器上,这时,不管行李箱里面有什么东西,哪怕是私密的东西,也可以被看的一清二楚。
1.2 反射机制的原理
一个.java文件经过编译会生成.class字节码文件,在运行时,这个字节码文件又会被JVM解析成一个Class类的对象,程序在运行时,每个java文件最终就会变成Class类对象的一个实例,我们就可以通过这个Class类来获取或修改类的基本信息。
也就是说,在反射之前,我们需要做的第一步就是先拿到当前需要反射的类的Class对象(见下文),然后通过Class对象的核心方法,达到反射的目的,
1.3 反射相关类
要想实现反射,我们必须了解以下类:
- Class类:代表类的实体,在运行的Java应用程序中表示类和接口
- Field类:代表类的成员变量/类的属性
- Method类:代表类的方法
- Constructor类:代表类的构造方法
1.4 Class类
1.4.1 相关方法
1.4.1.1 常用获得类相关的方法
1.4.1.2 常用获得类中属性相关的方法
1.4.1.3 获得类中构造器相关的方法
1.4.1.4 获得类中方法相关的方法
1.4.2 获取Class对象
-
第一种,使用 Class.forName("类的全路径名"); (静态方法)(常用) 例:Class<?> c1 = Class.forName("reflectdemo.Student");
-
使用 .class 方法。 例:Class c2 = Student.class;
-
使用类对象的 getClass() 方法。 例:Student student = new Student();Class<?> c3 = student.getClass();
1.5 总结
反射优点:
- 对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法。
- 增加程序的灵活性和扩展性,降低耦合性,提高自适应能力。
- 反射已经运用在了很多流行框架如:Struts、Hibernate、Spring 等等。
反射缺点:
- 调用大量方法,导致程序效率降低 。
- 会带来维护问题。
- 反射代码比相应的直接代码更复杂 。
1.6 代码实例
public static void reflectPrivateField() {
try {
Class<?> c1 = Class.forName("reflectdemo.Student");
Field field = c1.getDeclaredField("name");
field.setAccessible(true);//修改private修饰的成员,需要添加这条语句
//获取Student对象
Student student = (Student)c1.newInstance();
field.set(student,"poll");
System.out.println(student);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
2、枚举
2.1 定义
枚举的使用语法:用enum代替class关键字,定义一个枚举类。枚举类实际上就是一个类。
其中的RE、 BLACK、GREEN ,叫做枚举对象。
能够将常量组织起来统一进行管理,不具备整形等值的概念,是枚举类型。
本质:是 java.lang.Enum 的子类,也就是说,自己写的枚举类,就算没有显示的继承 Enum ,但是其默认继承了Enum类。
2.1.1 简单使用
public enum Color {
RED,PICK,BLACK,BLUE;//枚举对象
public static void main(String[] args) {
Color color = RED;
switch (color) {
case RED :
System.out.println(RED);
break;
case BLACK :
System.out.println(BLACK);
break;
case PICK:
System.out.println(PICK);
break;
case BLUE:
System.out.println(BLUE);
break;
default:
System.out.println("null");
break;
}
}
}
2.2 常用方法
因为任何的自定义枚举类都继承自Enum类,故继承了Enum类的方法:
其中 valueOf方法,只能转化成已有的枚举对象。
方法使用:
public enum Color {
RED,PICK,BLACK,BLUE;//枚举对象
public static void main(String[] args) {
Color[] colors = Color.values();//将枚举对象转化为数组
for (Color color : colors) {
System.out.println(color+" "+color.ordinal());//获取索引
}
System.out.println("====");
Color color = Color.valueOf("RED");//将字符串转化为枚举对象(只能转化成已有的枚举对象)
System.out.println(color);
System.out.println("===");
System.out.println(RED.compareTo(PICK));//比较枚举对象的定义顺序(下标差)
}
}
2.3 构造方法
因为枚举本身就是一个类,所以也有普通类所具备的构造方法。
当我们没有写出任何构造方法时,Java会帮我们默认提供不带参数的构造方法,所以我们实例出的枚举对象不用传入参数。但是当我们写出带参构造时,我们就需要自主给枚举对象提供相应参数。
但是这里重点要说的是:枚举的构造方法默认是私有的。
我们发现,当private修饰构造方法时,private显示为灰色(上图),说明构造方法默认是私有的,当我们使用public修饰时,会编译报错:
2.4 枚举和反射(阿里面试:为什么枚举实现单例模式是安全的?)
我们上文讲到了反射可以拿到类中所有的属性和方法,包括private私有的,那么枚举的构造方法也是私有的,我们是否可以拿到呢?
答案是:反射不可以拿到枚举的构造方法。
当我们通过反射(newInstance方法中)去获取枚举对象时,直接抛出了异常,也就是说,在反射中,枚举被过滤掉了,所以我们不能通过反射获取枚举类的实例!!!
总结:枚举是非常安全的,甚至不可以用反射获取枚举的实例。
2.5 总结
- 枚举本身就是一个类,其构造方法默认为私有的,且都是默认继承与 java.lang.Enum
- 枚举的构造方法默认是私有的
- 枚举可以避免反射和序列化问题
- 枚举常量更简单安全
- 枚举不可继承,无法扩展
3、Lambda表达式
3.1 定义
Lambda表达式是JDK8引入的一种语法形式,主要用来简化代码,语法为:
3.2 函数式接口
Lambda表达式主要用来简化匿名内部类的书写,而且只能简化函数式接口的匿名内部类的书写。
函数式接口:
- 有且仅有一个抽象方法的接口
- 我们可以为函数式接口添加@FunctionalInterface 注解,当其不是函数式接口时,编译器会报错。
所以我们可以这样理解Lambda表达式:Lambda就是匿名内部类的简化,实际上是创建了一个匿名类的对象,实现了函数式接口,重写了接口的方法 。
3.3 省略规则
-
省略所实现接口的表示,省略方法名、返回值,只保留重写方法的参数列表和方法体
-
参数类型可省略,若要省略则全部参数的类型都要省略
-
若只有一个参数,那么参数的小括号可省略;若有两个参数,则小括号不可省略。
-
参数和方法体间使用 -> 连接
-
若方法体当中只有一句代码,那么花括号可以省略,分号可以省略
-
如果方法体中只有一条语句,且是return语句,那么大括号可以省略,且去掉return关键字,去掉分号
代码演示:
public static void main(String[] args) {
//原匿名内部类形式
PriorityQueue<String> queue1 = new PriorityQueue<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
});
//Lambda表达式形式
PriorityQueue<String> queue2 = new PriorityQueue<>((o1,o2) -> o2.compareTo(o1));
}
3.4 变量捕获
变量捕获就是在匿名内部类中使用到了一个变量,那这个变量在使用前或者使用后都不能做出修改,否则会编译报错(这个变量就叫做被捕获的变量):
在匿名内部类中存在变量捕获,那么在Lambda表达式中也必然存在:
上述代码中变量aaaa就叫做:被捕获的变量
所以我们得出结论:
- 这个变量要么是被final修饰为常量(常量不可修改)。
- 如果不是被final修饰的,我们要保证在使用之前和使用之后,没有修改。
3.5 Lambda表达式在集合中的使用
3.5.1 forEach循环
forEach循环需要使用匿名内部类重写方法,我们可以使用Lambda表达式来简化代码。
3.5.1.1 Collection集合
public static void main1(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(23);
list.add(29);
list.add(13);
//原形式
list.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.print(integer+" ");
}
});
System.out.println();
//Lambda形式
list.forEach(integer -> System.out.print(integer+" "));
}
3.5.1.2 Map集合
public static void main(String[] args) {
HashMap<String,Integer> map = new HashMap<>();
map.put("hello",10);
map.put("world",13);
map.put("abc",20);
map.forEach(new BiConsumer<String, Integer>() {
@Override
public void accept(String s, Integer integer) {
System.out.println(s+" "+integer+" ");
}
});
//Lambda形式
map.forEach((key,value) -> System.out.println(key+" "+value+" "));
3.5.2 sort排序-forEach循环
3.5.2.1 List集合
List集合的sort方法,也可以使用匿名内部类重写方法来排序,我们可以使用Lambda形式:
public static void main2(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(23);
list.add(29);
list.add(13);
//原匿名内部类排序形式
/*list.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
});*/
//Lambda形式 ->排序
list.sort((o1, o2) -> o1.compareTo(o2));
//Lambda形式 ->forEach循环
list.forEach(integer -> System.out.print(integer+" "));
}
END
标签:反射,Java,System,枚举,Lambda,Class,out From: https://blog.csdn.net/2401_83595513/article/details/140726230