文章目录
反射
1 、反射的概念
Java程序中,所有的对象都有两种类型:编译时类型和运行时类型,而很多时候对象的编译时类型和运行时类型不一致。
平时我们照镜子的时候,在镜子后面会有自己的影子,其实java中的反射也是类似的,一个类或者对象通过反射可以获得自身的对象,该对象是一个java.lang.Class 的对象(就像一个镜像文件)。
因为加载完类之后,就产生了一个Class类型的对象,并将引用存储到方法区,那么每一个类在方法区内存都可以找到唯一Class对象与之对应,这个对象包含了完整的类的结构信息,我们可以通过这个对象获取类的结构。这种机制就像一面镜子,Class对象像是类在镜子中的镜像,通过观察这个镜像就可以知道类的结构,所以,把这种机制形象地称为反射机制。
非反射:类(原物)–>类信息
反射:Class对象(镜像)–>类(原物)
2 、java.lang.Class类
要想解剖一个类,必须先要获取到该类的Class对象。而剖析一个类或用反射解决具体的问题就是使用相关API(1)java.lang.Class(2)java.lang.reflect.*。所以,Class对象是反射的根源。
获取Class对象的四种方式
(1)类型名.class
要求编译期间已知类型
(2)对象.getClass()
获取对象的运行时类型
(3)Class.forName(类型全名称)
可以获取编译期间未知的类型
(4)ClassLoader的类加载器对象.loadClass(类型全名称)
可以用系统类加载对象或自定义加载器对象加载指定路径下的类型
import org.junit.Test;
public class GetClassObject {
@Test
public void test01() throws ClassNotFoundException{
Class c1 = GetClassObject.class;
GetClassObject obj = new GetClassObject();
Class c2 = obj.getClass();
Class c3 = Class.forName("com.classtype.GetClassObject");
Class c4 = ClassLoader.getSystemClassLoader().loadClass("com.classtype.GetClassObject");
System.out.println("c1 = " + c1);
System.out.println("c2 = " + c2);
System.out.println("c3 = " + c3);
System.out.println("c4 = " + c4);
System.out.println(c1 == c2);
System.out.println(c1 == c3);
System.out.println(c1 == c4);
}
@Test
public void test02(){
int[] arr1 = {1,2,3,4,5};
int[] arr2 = {10,34,5,66,34,22};
int[][] arr3 = {{1,2,3,4},{4,5,6,7}};
String[] arr4 = {"hello","world"};
Class c1 = arr1.getClass();
Class c2 = arr2.getClass();
Class c3 = arr3.getClass();
Class c4 = arr4.getClass();
System.out.println("c1 = " + c1);
System.out.println("c2 = " + c2);
System.out.println("c3 = " + c3);
System.out.println("c4 = " + c4);
System.out.println(c1 == c2);
System.out.println(c1 == c3);
System.out.println(c1 == c4);
System.out.println(c3 == c4);
}
}
3 反射的基本应用
1 获取类型的详细信息
可以获取:包、修饰符、类型名、父类(包括泛型父类)、父接口(包括泛型父接口)、成员(属性、构造器、方法)、注解(类上的、方法上的、属性上的)
示例代码获取常规信息:
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class TestClassInfo {
public static void main(String[] args) throws Exception {
//1、先得到某个类型的Class对象
Class clazz = String.class;
//比喻clazz好比是镜子中的影子
//2、获取类信息
//(1)获取包对象,即所有java的包,都是Package的对象
Package pkg = clazz.getPackage();
System.out.println("包名:" + pkg.getName());
//(2)获取修饰符
//其实修饰符是Modifier,里面有很多常量值
/*
* 0x是十六进制
* PUBLIC = 0x00000001; 1 1
* PRIVATE = 0x00000002; 2 10
* PROTECTED = 0x00000004; 4 100
* STATIC = 0x00000008; 8 1000
* FINAL = 0x00000010; 16 10000
* ...
*
* 设计的理念,就是用二进制的某一位是1,来代表一种修饰符,整个二进制中只有一位是1,其余都是0
*
* mod = 17 0x00000011
* if ((mod & PUBLIC) != 0) 说明修饰符中有public
* if ((mod & FINAL) != 0) 说明修饰符中有final
*/
int mod = clazz.getModifiers();
System.out.println("类的修饰符有:" + Modifier.toString(mod));
//(3)类型名
String name = clazz.getName();
System.out.println("类名:" + name);
//(4)父类,父类也有父类对应的Class对象
Class superclass = clazz.getSuperclass();
System.out.println("父类:" + superclass);
//(5)父接口们
System.out.println("父接口们:");
Class[] interfaces = clazz.getInterfaces();
for (Class iter : interfaces) {
System.out.println(iter);
}
//(6)类的属性, 你声明的一个属性,它是Field的对象
/* Field clazz.getField(name) 根据属性名获取一个属性对象,但是只能得到公共的
Field[] clazz.getFields(); 获取所有公共的属性
Field clazz.getDeclaredField(name) 根据属性名获取一个属性对象,可以获取已声明的
Field[] clazz.getDeclaredFields() 获取所有已声明的属性
*/
// Field valueField = clazz.getDeclaredField("value");
// System.out.println("valueField = " +valueField);
System.out.println("------------------------------");
System.out.println("成员如下:");
System.out.println("属性有:");
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
//修饰符、数据类型、属性名
int modifiers = field.getModifiers();
System.out.println("属性的修饰符:" + Modifier.toString(modifiers));
String name2 = field.getName();
System.out.println("属性名:" + name2);
Class<?> type = field.getType();
System.out.println("属性的数据类型:" + type);
}
System.out.println("-------------------------");
//(7)构造器们
System.out.println("构造器列表:");
Constructor[] constructors = clazz.getDeclaredConstructors();
for (int i=0; i<constructors.length; i++) {
Constructor constructor = constructors[i];
System.out.println("第" + (i+1) +"个构造器:");
//修饰符、构造器名称、构造器形参列表 、抛出异常列表
int modifiers = constructor.getModifiers();
System.out.println("构造器的修饰符:" + Modifier.toString(modifiers));
String name2 = constructor.getName();
System.out.println("构造器名:" + name2);
//形参列表
System.out.println("形参列表:");
Class[] parameterTypes = constructor.getParameterTypes();
for (Class parameterType : parameterTypes) {
System.out.println(parameterType);
}
//异常列表
System.out.println("异常列表:");
Class<?>[] exceptionTypes = constructor.getExceptionTypes();
for (Class<?> exceptionType : exceptionTypes) {
System.out.println(exceptionType);
}
System.out.println();
}
System.out.println("---------------------------------");
//(8)方法们
System.out.println("方法列表:");
Method[] declaredMethods = clazz.getDeclaredMethods();
for (int i=0; i<declaredMethods.length; i++) {
Method method = declaredMethods[i];
System.out.println("第" + (i+1) +"个方法:");
//修饰符、返回值类型、方法名、形参列表 、异常列表
int modifiers = method.getModifiers();
System.out.println("方法的修饰符:" + Modifier.toString(modifiers));
Class<?> returnType = method.getReturnType();
System.out.println("返回值类型:" + returnType);
String name2 = method.getName();
System.out.println("方法名:" + name2);
//形参列表
System.out.println("形参列表:");
Class[] parameterTypes = method.getParameterTypes();
for (Class parameterType : parameterTypes) {
System.out.println(parameterType);
}
//异常列表
System.out.println("异常列表:");
Class<?>[] exceptionTypes = method.getExceptionTypes();
for (Class<?> exceptionType : exceptionTypes) {
System.out.println(exceptionType);
}
System.out.println();
}
}
}
2 创建任意引用类型的对象
两种方式:
1、直接通过Class对象来实例化(要求必须有公共的无参构造)
2、通过获取构造器对象来进行实例化
方式一的步骤:
(1)获取该类型的Class对象(2)创建对象
方式二的步骤:
(1)获取该类型的Class对象(2)获取构造器对象(3)创建对象
如果构造器的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)
示例代码:
import org.junit.Test;
import java.lang.reflect.Constructor;
public class TestCreateObject {
@Test
public void test1() throws Exception{
// ClassOne obj = new ClassOne();//编译期间无法创建
Class<?> classOne = Class.forName("com.ext.demo.ClassOne");
//classOne.ext.demo.ClassOne
//classOne.newInstance()创建的就是ClassOne的对象
Object obj = classOne.newInstance();
System.out.println(obj);
}
@Test
public void test2()throws Exception{
Class<?> classOne = Class.forName("com.ext.demo.ClassOne");
//java.lang.InstantiationException: com.ext.demo.ClassOne
//Caused by: java.lang.NoSuchMethodException: com.ext.demo.ClassOne.<init>()
//即说明ClassOne没有无参构造,就没有无参实例初始化方法<init>
Object stu = classOne.newInstance();
System.out.println(stu);
}
@Test
public void test3()throws Exception{
//(1)获取Class对象
Class<?> classOne = Class.forName("com.ext.demo.ClassOne");
/*
* 获取ClassOne类型中的有参构造
* 如果构造器有多个,我们通常是根据形参【类型】列表来获取指定的一个构造器的
* 例如:public ClassOne(String title, int num)
*/
//(2)获取构造器对象
Constructor<?> constructor = classOne.getDeclaredConstructor(String.class,int.class);
//(3)创建实例对象
// T newInstance(Object... initargs) 这个Object...是在创建对象时,给有参构造的实参列表
Object obj = constructor.newInstance("火柴人",2022);
System.out.println(obj);
}
}
3 操作任意类型的属性
(1)获取该类型的Class对象
Class classOne = Class.forName(“包.类名”);
(2)获取属性对象
Field field = classOne.getDeclaredField(“属性名”);
(3)如果属性的权限修饰符不是public,那么需要设置属性可访问
field.setAccessible(true);
(4)创建实例对象:如果操作的是非静态属性,需要创建实例对象
Object obj = classOne.newInstance(); //有公共的无参构造
Object obj = 构造器对象.newInstance(实参…);//通过特定构造器对象创建实例对象
(4)设置属性值
field.set(obj,“属性值”);
如果操作静态变量,那么实例对象可以省略,用null表示
(5)获取属性值
Object value = field.get(obj);
如果操作静态变量,那么实例对象可以省略,用null表示
示例代码:
public class Student {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
import java.lang.reflect.Field;
public class TestField {
public static void main(String[] args)throws Exception {
//1、获取Student的Class对象
Class classOne = Class.forName("com.reflect.Student");
//2、获取属性对象,例如:id属性
Field idField = classOne.getDeclaredField("id");
//3、如果id是私有的等在当前类中不可访问access的,我们需要做如下操作
idField.setAccessible(true);
//4、创建实例对象,即,创建Student对象
Object stu = classOne.newInstance();
//5、获取属性值
/*
* 以前:int 变量= 学生对象.getId()
* 现在:Object id属性对象.get(学生对象)
*/
Object value = idField.get(stu);
System.out.println("id = "+ value);
//6、设置属性值
/*
* 以前:学生对象.setId(值)
* 现在:id属性对象.set(学生对象,值)
*/
idField.set(stu, 2);
value = idField.get(stu);
System.out.println("id = "+ value);
}
}
4 调用任意类型的方法
(1)获取该类型的Class对象
Class clazz = Class.forName(“包.类名”);
(2)获取方法对象
Method method = clazz.getDeclaredMethod(“方法名”,方法的形参类型列表);
(3)创建实例对象
Object obj = classOne.newInstance();
(4)调用方法
Object result = method.invoke(obj, 方法的实参值列表);
如果方法的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)
如果方法是静态方法,实例对象也可以省略,用null代替
示例代码:
import org.junit.Test;
import java.lang.reflect.Method;
public class TestMethod {
@Test
public void test()throws Exception {
// 1、获取Student的Class对象
Class<?> classOne = Class.forName("com.reflect.Student");
//2、获取方法对象
/*
* 在一个类中,唯一定位到一个方法,需要:(1)方法名(2)形参列表,因为方法可能重载
*
* 例如:void setName(String name)
*/
Method setNameMethod = classOne.getDeclaredMethod("setName", String.class);
//3、创建实例对象
Object stu = classOne.newInstance();
//4、调用方法
/*
* 以前:学生对象.setName(值)
* 现在:方法对象.invoke(学生对象,值)
*/
Object setNameMethodReturnValue = setNameMethod.invoke(stu, "张三");
System.out.println("stu = " + stu);
//setName方法返回值类型void,没有返回值,所以setNameMethodReturnValue为null
System.out.println("setNameMethodReturnValue = " + setNameMethodReturnValue);
Method getNameMethod = clazz.getDeclaredMethod("getName");
Object getNameMethodReturnValue = getNameMethod.invoke(stu);
//getName方法返回值类型String,有返回值,getNameMethod.invoke的返回值就是getName方法的返回值
System.out.println("getNameMethodReturnValue = " + getNameMethodReturnValue);//张三
}
@Test
public void test02()throws Exception{
Class<?> classOne = Class.forName("com.ext.demo.ClassOne");
Method printInfoMethod = classOne.getMethod("printInfo", String.class);
//printInfo方法是静态方法
printInfoMethod.invoke(null,"火柴人");
}
}
小结:反射的使用,可以让我们在原本获取不到私有类的情况下,开启后门让我们获取,因为是镜像类所以类有的属性方法……镜像类都拥有,且我们获取使用时值得注意的就是需要我们进行反向使用,因为反射是建立在镜像上的。此外我们需要掌握获取类的四种方法,以及获取后的常用方法比如如何获取属性,方法。
Lambda表达式
1 Lambda表达式语法
Lambda表达式是用来给【函数式接口】的变量或形参赋值用的。其实本质上,Lambda表达式是用于实现【函数式接口】的“抽象方法”,或者是给函数式接口的变量传递一段实现抽象方法的方法体代码。
Lambda表达式语法格式
(形参列表) -> {Lambda体}
语法格式说明:
- (形参列表)它就是你要赋值的函数式接口的抽象方法的(形参列表),照抄
- {Lambda体}就是实现这个抽象方法的方法体
- ->称为Lambda操作符(减号和大于号中间不能有空格,而且必须是英文状态下半角输入方式)
2 使用Lamda表达式标准格式
import com.atguigu.fi.Calculator;
public class LambdaGrammar {
public static void main(String[] args) {
Calculator<Integer,Integer> c1 = (Integer a, Integer b) -> {return a+b;};
Calculator<Integer,Integer> c2 = (Integer a, Integer b) -> {return a-b;};
Calculator<Integer,Integer> c3 = (Integer a, Integer b) -> {return a*b;};
Calculator<Integer,Integer> c4 = (Integer a, Integer b) -> {return a/b;};
System.out.println(c1.calculate(5, 2));
System.out.println(c2.calculate(5, 2));
System.out.println(c3.calculate(5, 2));
System.out.println(c4.calculate(5, 2));
}
}
3 Lambda表达式的简化
某些情况下Lambda表达式可以精简:
- 当{Lambda体}中只有一句语句时,可以省略{}和{;}
- 当{Lambda体}中只有一句语句时,并且这个语句还是一个return语句,那么{、return、;}三者可以省略。它们三要么一起省略,要么都不省略。
- 当Lambda表达式(形参列表)的类型已知,获取根据泛型规则可以自动推断,那么(形参列表)的数据类型可以省略。
- 当Lambda表达式(形参列表)的形参个数只有一个,并且类型已知或可以自动推断,则形参的数据类型和()可以一起省略,但是形参名不能省略。
- 当Lambda表达式(形参列表)是空参时,()不能省略
示例代码:
import com.atguigu.fi.Calculator;
import com.atguigu.fi.Convertor;
import org.junit.Test;
public class LambdaGrammarSimple {
@Test
public void test01() {
//使用Lambda表达式实现Calculator接口,求两个整数的和的功能
Calculator<Integer,Integer> c1 = (Integer a, Integer b) -> {return a+b;};
System.out.println(c1.calculate(5, 2));
Calculator<Integer,Integer> c2 = (Integer a, Integer b) -> a+b;
System.out.println(c2.calculate(5, 2));
Calculator<Integer,Integer> c3 = (a, b) -> a+b;
System.out.println(c3.calculate(5, 2));
//上面三种写法完全等价
}
@Test
public void test02() {
//使用Lambda表达式实现Convertor接口,实现取字符串的首字母的功能
Convertor<String,Character> c1 = (String str)-> {return str.charAt(0);};
System.out.println(c1.change("hello"));
Convertor<String,Character> c2 = (String str)-> str.charAt(0);
System.out.println(c2.change("world"));
Convertor<String,Character> c3 = (str)-> str.charAt(0);
System.out.println(c3.change("atguigu"));
Convertor<String,Character> c4 = str-> str.charAt(0);
System.out.println(c4.change("chai"));
//上面四种写法完全一致
}
}
小结:Lambda表达式类似于箭头是函数编程思想的常客,用法简单,可以减少冗余的代码。
总结:反射可以使我们获取到私有属性等等,是开启后门的一种方法。因为是类的镜像所以指向的方法区是同一个,所以是可以获取该类的所有方法属性……Lambda表达式则是函数编程里的重要代码,他可以让我们使用简洁的代码,表达完整的信息。
每日金句:
甘谷与共,是浮生茶,也是人生路。