Java的反射机制、Lambda表达式和枚举
文章目录
- Java的反射机制、Lambda表达式和枚举
- 1.反射机制
- 反射的概念、用途、优缺点
- 反射相关的类及使用(重要!!)
- 相关类
- Class类:代表类实体,表示类和接口
- Field类:代表类的成员变量/类的属性
- Method类:代表类的方法
- Constructor类:代表类的构造方法
- 获取类中注解相关的方法(了解)
- 使用
- 获取Class对象的三种方式
- 例子
- 通过反射创建对象
- 反射私有构造方法创建对象
- 反射私有属性
- 反射私有方法
- 2.Lambda表达式
- Lambda是什么、语法
- 基本使用
- 用途1:变量捕获
- 匿名内部类变量捕获
- Lambda表达式变量捕获
- 用途2:在集合中的使用
- Collection接口
- List接口
- Map接口
- 3.枚举
- 枚举简介、优缺点
- 枚举的构造方法
- 创建与使用
- 枚举与反射
1.反射机制
反射的概念、用途、优缺点
Java的反射机制就是在运行状态中,对于任意一个类都能知道它所有属性和方法,对于任意一个对象都能调用它的任意方法和属性,并进行部分类型信息的修改,这种动态获取信息以及动态调用对象方法的功能叫做Java的反射机制。
Java程序中很多对象在运行时会出现两种基本类型:运行时类型(RTTI)和编译时类型。
例如Person p=new Student(),这句代码中p在编译时就是Person,运行时就是Student,程序需要再运行时发现对象和类的真实信息,通过反射就可以判断出这个对象和类属于哪些类。
-
在日常的第三方应用开发过程中,可以利用Java的反射机制通过反射来获取所需要的私有成员或者方法(比较常见)
-
最重要的用途就是开发各种框架。
例如,在spring中,我们把所有的类Bean交给spring容器管理,不管是xml配置还是注解配置Bean,当我们从容器中获取Bean来依赖注入时,容器就读取配置,而配置中给的就是类的信息,spring根据这些信息,动态的创建Bean
优点:
- 对于任意类都能知道它的属性和方法,对于任意对象都能调用它的任意方法
- 可以增加程序的灵活性和扩展性
- 已经运用在很多流行的框架如spring中
缺点:
- 会有效率问题,导致程序效率降低
- 代码可能比较复杂
反射相关的类及使用(重要!!)
在Java中,实现反射都要依赖于Class类。
这是因为,Java文件被编译后,生成.class文件,JVM需要去解读.class文件,被编译后的Java文件.class也被JVM解析成一个对象,这个对象就是java.lang.Class。
这样当程序运行时,每个Java文件最终都变成了Class类对象的一个实例。
因此,将反射机制应用到这个实例上时,就可以获取或添加改变这个类的属性和方法时,这个类就会成为一个动态的类。
注意:所有和反射相关的包都在 import java.lang.reflect 包下面
相关类
所有的这些类都是加了declared能够访问的范围是更大的。
Class类:代表类实体,表示类和接口
方法 | 用途 |
---|---|
getClassLoader() | 获取类的加载器 |
getDeclaredClasser() | 返回一个数组,数组中包含这个类中所有的类和接口类的对象 |
forName(String className) | 根据类名返回类的对象 |
newInstance | 创建类的实例 |
getName() | 获取类的完整路径名字 |
Field类:代表类的成员变量/类的属性
方法 | 用途 |
---|---|
getField(String name) | 获取某个公有的属性对象 |
getFields() | 获取所有公有的属性对象 |
getDeclaredField(String name) | 获取某个属性对象 |
getDeclaredFields() | 获取所有属性对象 |
Method类:代表类的方法
方法 | 用途 |
---|---|
getMethod(String name,Class…<?> parameterTypes) | 获取该类某个公有的方法 |
getMethods() | 获取该类所有公有的方法 |
getDeclaredMethod(String name,Class…<?> parameterTypes) | 获取该类某个方法 |
getDeclaredMethods() | 获取该类所有方法 |
Constructor类:代表类的构造方法
方法 | 用途 |
---|---|
getConstructor(Class…<?> parameterTypes) | 获取该类中与参数类型匹配的公有构造方法 |
getConstructors() | 获取该类中所有公有构造方法 |
getDeclaredConstructor(Class…<?> parameterTypes) | 获取该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 获取该类所有构造方法 |
获取类中注解相关的方法(了解)
方法 | 用途 |
---|---|
getAnnotation(Class annotationClass) | 返回该类中与参数类型匹配的公有注解对象 |
getAnnotations() | 返回该类中所有公有注解对象 |
getDeclaredAnnotation(Class annotationClass) | 返回该类中与参数类型匹配的所有注解对象 |
getDeclaredAnnotations() | 返回该类中所有的注解对象 |
使用
在进行反射之前,需要做的第一步就是拿到当前需要反射的类的Class对象,然后根据Class对象的核心方法,达到反射的目的(获取或修改相关信息)。
获取Class对象的三种方式
第一种:使用Class.forName(“类的全路径名”);【使用的比较多的】
第二种:使用.class方法(适合在编译时就已经明确要操作的Class)
第三种:使用类对象的getClass()方法
class Student{
//私有属性name
private String name = "school";
//公有属性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(String,name)");
}
//私有不带参
private void eat(){
System.out.println("i am eating");
}
//公开不带参
public void sleep(){
System.out.println("i am sleeping");
}
//私有带参
private void function(String str) {
System.out.println(str);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Reflect {
public static void main(String[] args) throws ClassNotFoundException {
Student student=new Student();
//获取Class对象的三种方法
//1.通过getClass方法
Class<?> c1=student.getClass();
//2.通过类名.class获取
Class<?> c2=Student.class;
//3.通过Class.forName(全路径)[需要处理异常——自己处理or交给JVM处理]
Class<?> c3=Class.forName("Student");//在当前类
System.out.println(c1.equals(c2));
System.out.println(c1.equals(c3));
System.out.println(c3.equals(c2));
}
}
例子
通过反射创建对象
public static void reflectNewInstance() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class<?> c3 = Class.forName("Student");
Student student = (Student) c3.newInstance();
System.out.println(student);
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
//通过反射创建对象
reflectNewInstance();
}
反射私有构造方法创建对象
//反射私有的构造方法
public static void reflectPrivateConstructor() {
try {
Class<?> c3=Class.forName("Student");
Constructor<?> constructor=c3.getDeclaredConstructor(String.class,int.class);
//设置为true之后可以修改访问权限
constructor.setAccessible(true);
Student student=(Student) constructor.newInstance("zhangsan",17);
System.out.println(student);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
//反射私有属性
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
//通过反射创建对象
// reflectNewInstance();
//通过反射得到私有构造方法创建对象
reflectPrivateConstructor();
}
反射私有属性
//反射私有属性
public static void reflectPrivateField(){
try {
Class<?> c3=Class.forName("Student");
Field field=c3.getDeclaredField("name");
field.setAccessible(true);//修改属性的访问权限
Object objectStudent=c3.newInstance();
Student student=(Student)objectStudent;
field.set(student,"张三");
String name=(String)field.get(student);
System.out.println("反射私有属性修改名字为:"+name);
} 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);
}
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
//通过反射创建对象
// reflectNewInstance();
//通过反射得到私有构造方法创建对象
// reflectPrivateConstructor();
//反射私有属性
reflectPrivateField();
}
反射私有方法
//反射私有方法
public static void reflectPrivateMethod(){
try {
Class<?> c3=Class.forName("Student");
Method method= c3.getDeclaredMethod("function",String.class);
method.setAccessible(true);
Object object=c3.newInstance();
Student student=(Student) object;
method.invoke(student,"给私有function函数传的参数");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
//通过反射创建对象
// reflectNewInstance();
//通过反射得到私有构造方法创建对象
// reflectPrivateConstructor();
//反射私有属性
// reflectPrivateField();
//反射私有方法
reflectPrivateMethod();
}
2.Lambda表达式
相当于只有一个抽象方法的接口的简化。
Lambda是什么、语法
它是JavaSE8中的一个新的特性,Lambda表达式允许我们通过表达式代替功能接口。Lambda 表达式(Lambda expression),基于数学中的λ演算得名,也可称为闭包(Closure)。
基本语法: **(parameters) -> expression**
或 **(parameters) ->{ statements; }**
parameters:就是函数式接口里的形参列表。这里的参数类型可以明确的生命也可以由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 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
函数式接口:一个接口有且只有一个抽象方法
如果我们在某个接口上声明了 @FunctionalInterface 注解,那么编译器就会按照函数式接口的定义来要求该接口,这样如果有两个抽象方法,程序编译就会报错的。
定义方法:
@FunctionalInterface interface NoParameterNoReturn { //注意:只能有一个方法 void test(); }
@FunctionalInterface interface NoParameterNoReturn { void test(); default void test2() { System.out.println("JDK1.8新特性,default默认方法可以有具体的实现"); } }
基本使用
@FunctionalInterface
interface NoParameterNoReturn{
void test();
}
@FunctionalInterface
interface OneParameterNoReturn{
void test(int a);
}
@FunctionalInterface
interface MoreParameterNoReturn{
void test(int a,int b);
}
@FunctionalInterface
interface NoParameterReturn{
int test();
}
@FunctionalInterface
interface OneParameterReturn{
int test(int a);
}
@FunctionalInterface
interface MoreParameterReturn{
int test(int a,int b);
}
public class LambdaDemo {
public static void main(String[] args) {
//匿名内部类,重写了实现接口的方法
NoParameterNoReturn noParameterNoReturn=new NoParameterNoReturn(){
@Override
public void test() {
System.out.println("无参无返回值");
}
};
noParameterNoReturn.test();
NoParameterNoReturn noParameterNoReturn1=()->{
System.out.println("L:无参无返回值");
};
noParameterNoReturn1.test();
OneParameterNoReturn oneParameterNoReturn=new OneParameterNoReturn() {
@Override
public void test(int a) {
System.out.println("一参无返回值");
}
};
oneParameterNoReturn.test(1);
OneParameterNoReturn oneParameterNoReturn1= (x)->{System.out.println("L:一参无返回值");};
oneParameterNoReturn1.test(1);
MoreParameterNoReturn moreParameterNoReturn=new MoreParameterNoReturn() {
@Override
public void test(int a,int b) {
System.out.println("多参无返回值");
}
};
moreParameterNoReturn.test(1,2);
MoreParameterNoReturn moreParameterNoReturn1=(x,y)->{
System.out.println("L:多参无返回值");
};
moreParameterNoReturn1.test(1,2);
//匿名内部类,重写了实现接口的方法
NoParameterReturn noParameterReturn=new NoParameterReturn(){
@Override
public int test() {
System.out.println("无参有返回值");
return 0;
}
};
noParameterReturn.test();
NoParameterReturn noParameterReturn1=()->{
System.out.println("L:无参有返回值");
return 0;
};
noParameterNoReturn1.test();
OneParameterReturn oneParameterReturn=new OneParameterReturn() {
@Override
public int test(int a) {
System.out.println("一参有返回值");
return 0;
}
};
oneParameterNoReturn.test(1);
OneParameterReturn oneParameterReturn1= (x)->{System.out.println("L:一参有返回值");return x;};
oneParameterNoReturn1.test(1);
MoreParameterReturn moreParameterReturn=new MoreParameterReturn() {
@Override
public int test(int a,int b) {
System.out.println("多参有返回值");
return a+b;
}
};
moreParameterReturn.test(1,2);
MoreParameterReturn moreParameterReturn1=(x,y)->{
System.out.println("L:多参有返回值");
return x+y;
};
moreParameterNoReturn1.test(1,2);
}
}
用途1:变量捕获
变量捕获可以使我们更好的理解Lambda表达式的作用域,Java中的匿名内部类和Lambda表达式会存在变量捕获。
匿名内部类变量捕获
@FunctionalInterface
interface Test{
void test();
}
public class LambdaDemo2 {
//匿名内部类变量捕获
public static void test01() {
int a=100;
final int b=10;
// int c=100;
// c=20;
Test t=new Test(){
@Override
public void test() {
System.out.println("匿名内部类的变量捕获:(常量或者没有被改变过值的变量)"+a);
System.out.println("匿名内部类的变量捕获:(常量或者没有被改变过值的变量)"+b);
// System.out.println("匿名内部类的变量捕获:(常量或者没有被改变过值的变量)"+c);
}
};
t.test();
}
public static void main(String[] args) {
test01();
}
}
可以被捕获的只能是常量或者没有被修改过的常量,否则就会直接报错:
Lambda表达式变量捕获
@FunctionalInterface
interface Test{
void test();
}
public class LambdaDemo2 {
//匿名内部类变量捕获
public static void test01() {
int a=100;
final int b=10;
// int c=100;
// c=20;
Test t=new Test(){
@Override
public void test() {
System.out.println("匿名内部类的变量捕获:(常量或者没有被改变过值的变量)"+a);
System.out.println("匿名内部类的变量捕获:(常量或者没有被改变过值的变量)"+b);
// System.out.println("匿名内部类的变量捕获:(常量或者没有被改变过值的变量)"+c);
}
};
t.test();
}
//Lambda表达式的变量捕获
public static void test02() {
int a=100;
final int b=10;
Test t=()->{System.out.println("L的变量捕获:(常量或者没有被改变过值的变量)"+a);System.out.println("L的变量捕获:(常量或者没有被改变过值的变量)"+b);};
t.test();
}
public static void main(String[] args) {
// test01();
test02();
}
}
用途2:在集合中的使用
Collection接口
以实现Collection接口方法(实现了Iterable接口)的forEach方法为例
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;
public class LambdaDemo3 {
public static void main(String[] args) {
ArrayList<Integer> list=new ArrayList<>();
list.add(1);
list.add(19);
list.add(17);
list.add(16);
list.add(12);
list.add(12);
//使用foreach迭代器
System.out.println("迭代器:");
for (int x:list) {
System.out.print(x+" ");
}
System.out.println();
//使用匿名内部类方式
System.out.println("匿名内部类:");
list.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.print(integer+" ");
}
});
System.out.println();
System.out.println("Lambda表达式:");
//使用Lambda表达式方式
list.forEach((i)->{
System.out.print(i+" ");
});
}
}
List接口
以sort方法为例:
//sort方法
list.sort((a,b)->{return b-a;});//修改排序规则为降序排序
System.out.println(list);
Map接口
import java.util.HashMap;
import java.util.function.BiConsumer;
public class LambdaDemo4 {
public static void main(String[] args) {
//Map接口的forEach方法
HashMap<Integer,String> map=new HashMap<>();
map.put(1,"张三");
map.put(2,"李四");
map.put(3,"王五");
//匿名内部类
map.forEach(new BiConsumer<Integer, String>() {
@Override
public void accept(Integer integer, String s) {
System.out.println(integer+":"+s);
}
});
//Lambda表达式
map.forEach((integer,s)->{
System.out.println("L:"+integer+":"+s);
});
}
}
3.枚举
枚举简介、优缺点
它是在JDK1.5之后引入的,主要用来将常量组织起来。通常应用在错误状态码,消息类型,颜色划分和状态机上等等。
定义好的是枚举对象。
枚举类型实际上是 java.lang.Enum 的子类,也就是说,自己写的枚举类,就算没有显示的继承 Enum ,但是其默认继承了这个类。
优点:
- 枚举常量更安全简单
- 枚举具有内置方法,更优雅
缺点:
- 没法继承,不能被实例化【原因就是它构造方法是私有的】,不可扩展
枚举的构造方法
默认是私有的,也只能是私有的,不能被继承,并且只能在定义枚举对象的时候被调用。
public enum TestEnum {
RED,BLACK,WHITE,
BLUE("BLUE",4),PINK;
TestEnum(){
}
private String color;
private int ri;
TestEnum(String color,int ri){
this.color=color;
this.ri=ri;
}
}
创建与使用
public enum TestEnum {
RED,BLACK,WHITE;
public static void main(String[] args) {
test01();
test02();
test03();
}
//测试枚举的switch用法
public static void test01() {
TestEnum testEnum = TestEnum.BLACK;
switch(testEnum){
case RED:
System.out.println("red");
break;
case BLACK:
System.out.println("black");
break;
case WHITE:
System.out.println("white");
break;
default:
System.out.println("没有当前颜色");
break;
}
}
//以数组形式返回枚举类型的所有成员
public static void test02() {
TestEnum[] testEnum=TestEnum.values();
for (int i = 0; i < testEnum.length; i++) {
System.out.println("testEnum[i]:"+testEnum[i]);
}
}
public static void test03(){
//将普通字符串转换成枚举实例
// TestEnum testEnum=TestEnum.valueOf("GREEN");//需要是已经定义的,否则报错 No enum constant TestEnum.GREEN
TestEnum testEnum=TestEnum.valueOf("RED");
System.out.println(testEnum);
//获取枚举成员的索引位置
int index=TestEnum.BLACK.ordinal();
System.out.println("BLACK:"+index);
//比较两个枚举成员在定义时的顺序,默认拿序号比较
System.out.println(BLACK.compareTo(RED));
}
}
枚举与反射
能不能通过反射创建枚举实例?
不能!!看源码分析:
//反射枚举 私有的构造方法
public static void reflectEnumPrivateConstructor() throws Exception {
Class<?> c3=Class.forName("TestEnum");
Constructor<?> constructor=c3.getDeclaredConstructor(String.class,int.class);
//设置为true之后可以修改访问权限
constructor.setAccessible(true);
TestEnum testEnum=(TestEnum) constructor.newInstance("GREY",10);
System.out.println(testEnum);
}
public static void main(String[] args) throws Exception {
//通过反射创建对象
// reflectNewInstance();
//通过反射得到私有构造方法创建对象
// reflectPrivateConstructor();
//反射私有属性
// reflectPrivateField();
//反射私有方法
// reflectPrivateMethod();
//反射枚举类型私有方法
reflectEnumPrivateConstructor();
}
通过查看源码我们发现,枚举在这里被过滤了。
总的来说,因为枚举的构造方法是私有的,所以不能实例化,另外反射拿到的类对象再去new实例的时候会因为枚举类型被过滤掉,所以枚举实现的单例模式是安全的。
所以枚举具有构造方法默认是私有的,并且都是默认继承java.lang.Enum这个类,可以避免反射和序列化问题,枚举常量更安全更简单,代码更加优雅。