反射,Java代码中,让一个对象,认识到自己,也叫做"自省"自己清楚的认识自己
谁是最认识对象的??程序员
程序员是非常清楚,某个对象是属于哪个类的
这个对象里面有哪些属性(属性的名字,类型,private/public,其他的修饰符注解之类的)
这个对象里有哪些方法(方法的名字,参数列表,private/public)
这个类的父类是谁
这个类实现了接口有哪些
这些东西程序员只需要看看代码,就知道这些事情了
程序运行的过程中,某个对象也能获取到上述的信息
Java / JVM 提供了一组 API 拿到指定对象的上述信息
反射有什么用呢?
反射这个东西在世界开发中是要"慎用"的,双刃剑 / 非常规编程手段(不是很推荐使用的),正常的进门,拿钥匙进门,非正常情况,通过开锁师傅
反射,理解即可
Class stringClass = Class.forName("java.lang.String");
Class stringClass
这个类对象就代表了"String"这个类,进一步的就能拿到这个类的各种信息,甚至还能创建这个类的实例
String s = "";
这种写法下,你创建的对象一定是 String 类型的,这件事是在程序员代码写完了就确定了("静态")
//通过反射,获取到 String 这个类的 Class 对象
Class stringclass = Class.forName("java.lang.String");
Object obj = stringClass.newInstance();
String str = (String) obj;
System.out.println(str);
完全可以让用户输入,这个东西,可能来自于代码事先写好的,也可能 来自用户在控制台输入,还可能从文件读取,还可能通过一系列计数得出来的,还可能从网络读取的,程序跑起来之后/过程中获取到的"动态"
为了获取"动态"付出的代价很明显的,代码更复杂了,动态的收益是啥呢? => 代码的"通用性",写一份代码,支持各种不同的情况
class Student {
private int id;
private String name;
}
Student s = new Student(1,"小李");
class Animal {
private int animalld;
private String animalName;
private int age;
}
Animal cat = new Animal(2,"大黄",4);
反射可以知道这个类中有哪些属性(名字),此时,就可以比较容易的通过一套代码,就能够兼容多种不同类型的类,只要告诉我这个类的 Class 对象是什么,按照 Class 对象里面该类的属性名,就可以知道这个字符串如何解析,如何构造还原出一个一样的对象出来
由于两个类不同,序列化得到的字符串的结构不同,后续进行反序列化,规则也是不同的,有了反射之后,就可以大幅度降低上述序列化实现的成本
Class.forName(className);
拿到类对象
stringClass.newInstance();
根据类对象创建实例
通过反射,随意的访问某个对象中的任意属性,无论是 private
枚举,本身表示的含义"可以列举的概念"
这个部分也是可以通过"整数"或者其他的方案,来代替枚举的
表示性别
约定
1 => 表示男性 0 => 表示女性
但是用整数表示 性别 ,只是权宜之计,性别概念,本身就和 整数 没有任何关系的
当不小心把枚举误用了,进行一些不应该的计算的时候,编译器直接报销
enum 可以认为是特殊的 class 其实在枚举中,也是可以定义属性
虽然枚举和类很相似,但是还是有很明显的差别的
类可以随意的在外面创建实例,枚举不行,枚举项的创建必须在 枚举内部进行(构造方法一定是私有的)
枚举能支持额外的属性和方法
反射可以拿到类的私有的属性/方法
枚举的构造方法也是私有的
能否通过反射,拿到枚举的私有的构造方法,然后在再外面进行枚举,创建出新的枚举项??
这个是不能的(JVM)源代码
层次上做出了限制,这种做法本身就是故意找茬,没有实际价值的做法,java的反射 api 实现中,针对枚举这个特殊情况特殊处理
lambad 表达式
很多编程语言中,支持一个语法,匿名函数(没有名字的函数),这样的函数用一次就可以丢了,开发中非常常用的,通常作为"回调函数"的时候使用.
Java 看见别人有匿名函数,Java自己也想有,但是 Java 有一个问题 Java 的方法不能脱离实际类
Java 采取了曲线救国的方式,引入概念"函数式接口" functional interface, 如果这个接口,里面只有一个抽象方法,就可以称为函数式接口
基于函数式接口 => 引申出"匿名函数" => lambad 表达式
PriorityQueue queue = new PriorityQueue(o1, o2) -> (o1.id - o2.id);
圆括号表示形参列表.形参的参数类型是可以省略的
"->" 这个是 lambda 的关键标志,看到这个箭头,就知道这是 lambda, 省略
"(o1.id - o2.id)"是 lambda的函数的函数体{}
如果 lambda 里面只有一句 return 代码,此时{}可以省略,return 也可以省略
函数式接口 (lambda 表达式的本体)
@FunctionalInterface
interface MyFuncInterface {
void print(String s);
}
加上注解,进一步强调这个 interface 是函数式接口,万一后续代码写错了,不小心多写了一个方法,编译器可以报错
实际开发中,及时需要使用 lambda 也很少去写函数式接口,如果写了一个方法,产生需要接受一个 lambda 的时候,使用函数式接口
public static void main(String[] args) {
MypriorityQueue queue = new MyPriorityQueue((s1, s2) -> s1.compareTO(s2));
}
class MypriorityQueue {
public MyPriorityQueue(MyPriorityQueueComparator comparator) {
}
}
想要把 lambda 作为另一个方法的实参,就要求该方法的形参是一个函数式接口
MyFuncInterface myFuncInterface = (s) -> System.out.println(s);
把函数赋值给另一个变量,把函数作为另一个函数的参数,把函数作为另一个函数的返回值,函数是"一等公民",这样的特点,"函数式编程语言"非常核心的语法特性
函数式编程语言,广泛使用函数递归,实现逻辑
比如 erlang
1.没有变量 => 常量(原子)
2.没有条件 => 模式匹配(进阶的 Switch case)
3.没有循环 => 递归
大数据开发,用函数式编程更多一点,其他场景见到的都比较少
lambda 表达式的语法精简
1.参数类型可以省略,如果需要,每个参数的类型都要省略
2.参数的小括号里面只有一个参数,那么小括号可以省略
3.如果方法体中只有一个句代码,那么大括号可以省略
4.如果方法体中只有一条语句,且是 return 语句,那么大括号可以省略,且去掉 return 关键字
lambda 的变量捕获
lambda 表达式能够捕获外部的变量,在内部使用
int num = 10000;
MyFuncInterface2 myFuncInterface2 = (String s, int i) -> {
System.out.println(num);
};
lambda 的执行时机可能是未来的某个时候,执行 lambda 的时候可能这个变量 num 已经销毁了
MyFuncInterface2 myFuncInterface2;
{
int num = 10000;
myFuncInterface2 = (String s, int i) -> {
System.out.println(num);
};
}
myFuncInterface2.print("Hello","world",100);
上面的两个 num 值是相同的,地址是不同的, lambda 捕获的 num 其实是外部 num 的一份拷贝,还有就是这个打印语句不是立即执行的
lambda 表达式能够使用捕获的变量,奥秘其实是,在定义拉满bda的时候就把这样的被捕获的变量,在lambda 内部偷偷的拷贝了一份.
既然是拷贝,万一这个变量在捕获之后,做出修改呢?
int num = 10000;
myFuncInterface2 = (String s, int i) -> {
System.out.println(num);
};
num = 20000;
一旦允许修改,意味着捕获到的值和外部的值,就可能存在差异,就会导致代码的含义出现歧义 ,Java为了避免上述的问题,是不允许修改的
变量捕获只能针对 final 修饰的/虽然不是 final 修饰没有进行任何实际修改的