文章目录
反射
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个 类只有一个Class对象),这个对象就包含了完整的类的结构信息。可以通过这个对象看到类的结构。
- 正常方式:引入需要的”包类”名称——通过new实例化——取得实例化对象
- 反射方式:实例化对象——getClass()方法——得到完整的“包类”名称
Java反射机制提供的功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
Class 类
Class实例对应着加载到内存中的一个运行时类,可以利用该实例调用和获取运行时类中的结构。一个 Class 对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。
哪些类型可以有Class对象?
(1)class: 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类 (2)interface:接口 (3)[]:数组 (4)enum:枚举 (5)annotation:注解@interface (6)primitive type:基本数据类型 (7)void
其他说明:
- Class本身也是一个类
- Class对象只能由系统建立对象
- 一个加载的类在 JVM 中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中的一个.class文件
- 每个类的实例都会记得自己是由哪个 Class 实例所生成
- 通过Class可以完整地得到一个类中的所有被加载的结构Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的 Class对象
- Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的 Class对象
- 加载到内存的运行时类,会缓存一段时间,在此时间内,可以通过不同方式来获取此类
相关方法:
方法名 | 功能说明 |
---|---|
static Class forName(String name) | 返回指定类名 name 的 Class 对象 |
Object newInstance() | 调用缺省构造函数,返回该Class对象的一个实例 |
Class getSuperClass() | 返回当前Class对象的父类的Class对象 |
getName() | 返回此Class对象所表示的实体(类、接口、数组类、基本类型 或void)名称 |
Class [] getInterfaces() | 获取当前Class对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Class getSuperclass() | 返回表示此Class所表示的实体的超类的Class |
Constructor[] getConstructors() | 返回一个包含某些Constructor对象的数组 |
Field[] getDeclaredFields() | 返回Field对象的一个数组 |
Method getMethod(String name,Class … paramTypes) | 返回一个Method对象,此对象的形参类型为paramType |
获取Class类的实例(四种方法):
- 前提:若已知具体的类,通过运行时类的class属性获取,该方法最为安全可靠, 程序性能最高
- 实例:Class clazz = String.class;
- 前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象
- 实例:Person p = new Person(); Class clazz = p.getClass();
- 前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException (这种方法用的频率最高)
- 实例:Class clazz = Class.forName(“java.lang.String”);
- 其他方式(不做要求)
- ClassLoader cl = this.getClass().getClassLoader();
- Class clazz4 = cl.loadClass(“类的全类名”);
读取配置文件的两种方法:
方法一,流读取:
Properties p = new Properties();
FileInputStream fi = new FileInputStream(new File("jdbc.properties"));//相对路径在module下
p.load(fi);
String username = p.getProperty("username");
String password = p.getProperty("password");
System.out.println(username+" || "+password);
方法二,类加载器读取:
Properties p1 = new Properties();
ClassLoader cl = Day11.class.getClassLoader();
InputStream is = cl.getResourceAsStream("jdbc.properties");//相对路径在module的src下
p1.load(is);
String username1 = p1.getProperty("username");
String password1 = p1.getProperty("password");
System.out.println(username1+" || "+password1);
创建运行时类的对象
创建类的对象:调用Class对象的newInstance()方法。要求: 1)类必须有一个无参数的构造器。 2)类的构造器的访问权限需要足够。
对于没有无参的构造器,创建对象的方式是:
- 通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类型的构造器
- 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数
- 通过Constructor实例化对象。
//1.根据全类名获取对应的Class对象
String name = “xx.xx.Person";
Class clazz = null;
clazz = Class.forName(name);
//2.调用指定参数结构的构造器,生成Constructor的实例
Constructor con = clazz.getConstructor(String.class,Integer.class);
//3.通过Constructor的实例创建对应类的对象,并初始化类属性
Person p2 = (Person) con.newInstance("Peter",20);
System.out.println(p2);
获取运行时类的完整结构
- 实现的全部接口
- public Class<?>[] getInterfaces() 确定此对象所表示的类或接口实现的接口。
- 所继承的父类
- public Class<? Super T> getSuperclass() 返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的 Class。
- 全部的构造器
- public Constructor[] getConstructors() 返回此 Class 对象所表示的类(无父类)的所有public构造方法。
- public Constructor[] getDeclaredConstructors() 返回此 Class 对象表示的类声明的所有构造方法。
- Constructor类中:
- 取得修饰符: public int getModifiers();
- 取得方法名称: public String getName();
- 取得参数的类型:public Class<?>[] getParameterTypes();
- 全部的方法
- public Method[] getDeclaredMethods() 返回此Class对象所表示的类或接口(不包含父类)的全部方法。
- public Method[] getMethods() 返回此Class对象所表示的类或接口(不包含父类)的public的方法。
- Method类中:
- 取得全部的返回值 :public Class<?> getReturnType();
- 取得全部的参数 : public Class<?>[] getParameterTypes();
- 取得修饰符 : public int getModifiers();
- 取得异常信息 : public Class<?>[] getExceptionTypes();
- 全部的Field(属性)
- public Field[] getFields() 返回此Class对象所表示的类或接口及其父类的public的Field。
- public Field[] getDeclaredFields() 返回此Class对象所表示的类或接口及其父类的全部Field。
- Field方法中:
- 以整数形式返回此Field的修饰符 : public int getModifiers();(用Modifiers.toString()方法可视化)
- 得到Field的属性类型 : public Class<?> getType(); (用getName()方法可视化)
- 返回Field的名称 : public String getName() ;
- Annotation相关
- get Annotation(Class<T> annotationClass)
- getDeclaredAnnotations()
- 泛型相关
- 获取父类泛型类型:Type getGenericSuperclass()
- 泛型类型:ParameterizedType
- 获取实际的泛型类型参数数组:getActualTypeArguments()
- 抛出的异常
- public Class<?>[] getExceptionTypes();
- 类所在的包
- Package getPackage()
调用运行时类的指定结构
调用指定方法
通过反射,调用类中的方法,通过Method类完成。步骤:
- 通过Class类的getMethod(String name,Class…parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型(因为重载的存在,需要指定形参列表)。
- 之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息。
Object invoke(Object obj, Object … args)说明:
- Object 对应原方法的返回值,若原方法无返回值,此时返回null
- 若原方法若为静态方法,此时形参Object obj可为null
- 若原方法形参列表为空,则Object[] args为null
- 若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方法,将可访问private的方法。
调用指定属性
在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和 get()方法就可以完成设置和取得属性内容的操作。
- public Field getField(String name) 返回此Class对象表示的类或接口的指定的public的Field。
- public Field getDeclaredField(String name)返回此Class对象表示的类或接口的指定的Field。
- 在Field中:
- public Object get(Object obj) 取得指定对象obj上此Field的属性内容。
- public void set(Object obj,Object value) 设置指定对象obj上此Field的属性内容。
关于setAccessible方法的使用:
- Method和Field、Constructor对象都有setAccessible()方法。
- setAccessible启动和禁用访问安全检查的开关。
- 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
- 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
- 使得原本无法访问的私有成员也可以访问。
- 参数值为false则指示反射的对象应该实施Java语言访问检查。
public class Person implements Comparable{
public int id;
private String name;
private Person(String name){
this.name = name;
}
private void showNation(String name){
System.out.println("你好,我的名字是"+name);
}
...
}
Class clazz = Person.class;
Constructor cons1 = clazz.getDeclaredConstructor(String.class);
cons1.setAccessible(true);
Person p2 = (Person) cons1.newInstance("刘星星");
Field name2 = clazz.getDeclaredField("name");//调用属性name
name2.setAccessible(true);
name2.set(p2,"李鑫鑫");//设置属性name
Method m2 = clazz.getDeclaredMethod("showNation",String.class);//调用方法showNation,且指定形参列表
m2.setAccessible(true);
m2.invoke(p2,"中国");//相当于p2.showNation("中国")
动态代理
静态代理:
静态代理模式中代理类和被代理类在编译期间就确定下来,缺乏动态性,不利于扩展。
public class Po {
public static void main(String[] args) {
Network server = new Server();
Network client = new Client(server);
client.browse();
}
interface Network{
public void browse();
}
//被代理
class Server implements Network
@Override
public void browse() {
System.out.println("被代理者访问网络");
}
}
//代理
class Client implements Network{
private Network network;
public Client(Network network) {
this.network = network;
}
public void check(){
System.out.println("代理者在访问前的一些准备工作");
}
public void end(){
System.out.println("代理者在访问后的一些收尾工作");
}
@Override
public void browse() {
check();
network.browse();
end();
}
}
动态代理:
Proxy :专门完成代理的操作类,是所有动态代理类的父类。通过此类为一个或多个接口动态地生成实现类。
- 提供用于创建动态代理类和动态代理对象的静态方法
- static Class<?> getProxyClass(ClassLoader loader, Class<?>… interfaces) 创建 一个动态代理类所对应的Class对象
- static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 直接创建一个动态代理对象
其中,InvocationHandler h——得到InvocationHandler接口的实现类实例,ClassLoader loader——类加载器,
Class<?>[] interfaces——得到被代理类实 现的全部接口
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface Human{
void getBelief();
String eat(String food);
}
class SuperMan implements Human{
@Override
public void getBelief() {
System.out.println("我是超人,我相信我可以飞!");
}
@Override
public String eat(String food) {
return "我是超人,我爱吃" + food;
}
}
class BatMan implements Human{
@Override
public void getBelief() {
System.out.println("我是蝙蝠侠,我相信我很有钱!");
}
@Override
public String eat(String food) {
return "我是蝙蝠侠,我爱吃" + food;
}
}
class ProxyFactory{
//调用此方法,返回代理类对象
public static Object getProxyInstance(Object obj){//obj是被代理对象
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(), handler);
}
}
class MyInvocationHandler implements InvocationHandler{
private Object object;//需要使用被代理类对象进行赋值
public void bind(Object object){
this.object = object;
}
//当通过代理类对象调用方法A时,会自动地调用如下invoke()方法:
//将被代理类要执行的方法A的功能声明在invoke()方法中。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//method,即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法。
return method.invoke(object,args);
}
}
public class ProxyTest {
public static void main(String[] args) {
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(new SuperMan());
//当通过代理类调用方法时,会自动地调用被代理类中同名方法。
System.out.println(proxyInstance.eat("麻辣烫"));
proxyInstance.getBelief();
Human proxyInstance2 = (Human) ProxyFactory.getProxyInstance(new BatMan());
System.out.println(proxyInstance2.eat("披萨"));
proxyInstance2.getBelief();
}
}
动态代理与AOP(Aspect Orient Programming)
使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有太大的意义。通常都是为指定的目标对象生成动态代理。
这种动态代理在AOP中被称为AOP代理,AOP代理可代替目标对象,AOP代理包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异: AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理。
Lambda表达式
Lambda 是一个匿名函数,可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。其本质是作为函数式接口的一个实例(只有一个抽象方法)。
Comparator<Integer> c1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1,o2);
}
};
Comparator<Integer> c2 = (o1,o2)-> Integer.compare(o1,o2);//lambda表达式
Comparator<Integer> c3 = Integer::compare;//方法引用
操作符“->” ,该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:
- 左侧:指定了 Lambda 表达式需要的参数列表。(其实就是接口中抽象方法的形参列表)
- 右侧:指定了 Lambda 体,是 Lambda 表达式要执行的功能。(其实就是接口中抽象方法的方法体)
使用:
- 语法格式一:无参,无返回值
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("我爱你!");
}
};
r1.run();
Runnable r2 = () -> System.out.println("我爱你Lambda!");
r2.run();
- 语法格式二:Lambda 需要一个参数,但是没有返回值。
Consumer<String> con = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
con.accept("我爱你!");
Consumer<String> con1 = (String s) -> {
System.out.println(s);
};
con1.accept("我爱你Lambda!");
- 语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
Consumer<String> con1 = (s) -> {
System.out.println(s);
};
- 语法格式四:Lambda 若只需要一个参数时,参数的小括号可以省略
Consumer<String> con1 = s -> {
System.out.println(s);
};
- 语法格式五:Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值
Comparator<Integer> c2 = (o1,o2)-> {
System.out.println("我爱你Lambda!");
return Integer.compare(o1,o2);
}
- 语法格式六:当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略
Comparator<Integer> c2 = (o1,o2)-> Integer.compare(o1,o2);
上述 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的类型推断。
总结:
- Lambda形参列表的参数类型可以省略(类型推断)。
- Lambda形参列表只有一个参数,则括号可以省略。
- Lambda体应该用{}包裹,如果只有一句执行语句(可能是return语句),可以省略{}以及return关键字。
函数式接口
只包含一个抽象方法的接口,称为函数式接口。
可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。
可以在一个接口上使用@FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
方法引用与构造器引用
方法引用
当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用。方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致。
格式:使用操作符“::” 将类(或对象) 与方法名分隔开来。如下三种主要使用情况:
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
Comparator<Integer> c2 = (o1,o2)-> Integer.compare(o1,o2);//lambda表达式
Comparator<Integer> c3 = Integer::compare;//方法引用
Consumer<String> con1 = s -> System.out.println(s);//lambda表达式
Consumer<String> con1 = System.out::println;//方法引用
注意:当函数式接口方法的第一个参数是需要引用方法的调用者,并且第二个参数是需要引用方法的参数(或无参数)时:ClassName::methodName
构造器引用
格式: ClassName::new。与函数式接口相结合,自动与函数式接口中方法兼容。 可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致。且方法的返回值即为构造器对应类的对象。
数组引用
格式: type[] :: new
Stream API
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用 Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。 也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算。Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。 “集合讲的是数据,Stream讲的是计算!”
注意:
- Stream 自己不会存储元素。
- Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
- Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
使用流程:
- Stream实例化
- 一系列中间操作(过滤、映射…)
- 终止操作
流程注意点:
- 一个中间操作链,对数据源的数据进行处理
- 一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用