反射、Lambda表达式(Java学习笔记十二)

反射

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实例化
  • 一系列中间操作(过滤、映射…)
  • 终止操作

流程注意点:

  • 一个中间操作链,对数据源的数据进行处理
  • 一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值