反射、注解、Junit
反射反射: 概述 *反射:框架设计的灵魂
* 框架:半成品软件。可以在框架的基础上进行软件开发,简化编码
* 反射:将类的各个组成部分封装为其他对象,这就是反射机制
* 好处: 1. 可以在程序运行过程中,操作这些对象。 2. 可以解耦,提高程序的可扩展性。
Java代码在计算机中的3个阶段:
SOURCE:源代码阶段
CLASS:类对象阶段
RUNTIME:运行时阶段
获取字节码对象的3种方式
获取一个类的字节码对象的3种方式: 1. Class.forName("全类名") 将字节码文件加载进内存,返回Class对象 适用场景: 多用于配置文件,将类名定义在配置文件中. 读取文件, 加载类 2. 类名.class . 通过类名的属性class获取 适用场景: 多用于参数的传递 getConstructor(String.class, int.class) 3. 对象.getClass() getClass()方法在Object类中定义 适用场景: 多用于对象的获取字节码的方式 p.getClass() 同一个类的字节码对象, 只有"唯一的一个"
Class的方法概述
java.lang.Class<T>类: 表示一个类的字节码对象, 其中包含该类中定义的内容// 成员方法// 1. 获取成员变量们 Field[] getFields(): 获取所有 public 的成员变量 Field getField(String name): 获取指定名称的 public 的成员变量 Field[] getDeclaredFields(): 获取所有的成员变量, 不考虑权限修饰符 Field getDeclaredField(String name): 获取指定名称的成员变量, 不考虑权限修饰符// 2. 获取构造方法们 Constructor<?>[] getConstructors(): 获取所有 public 的构造方法 Constructor<T> getConstructor(Class<?>... parameterTypes): 获取指定的 public 构造方法 Constructor<?>[] getDeclaredConstructors(): 获取所有的构造方法, 不考虑权限修饰符 Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes): 获取指定的构造方法, 不考虑权限修饰符// 3. 获取成员方法们: Method[] getMethods(): 获取所有 public 的成员方法 Method getMethod(String name, Class<?>... parameterTypes) : 获取指定的 public 成员方法 Method[] getDeclaredMethods(): 获取所有的成员方法, 不考虑权限修饰符 Method getDeclaredMethod(String name, Class<?>... parameterTypes): 获取指定的成员方法, 不考虑权限修饰符// 4. 获取Class对象代表的类的全类名 String getName(): 获取当前Class对象代表的类的全类名// 5. 创建对象 T newInstance(): 使用当前类的空参构造, 创建一个对象
反射: 获取成员变量Field
java.lang.Class<T>类: 表示一个类的字节码对象, 其中包含该类中定义的内容 // 获取功能 1. 获取成员变量们 Field[] getFields(): 获取所有 public 的成员变量 Field getField(String name): 获取指定名称的 public 的成员变量 Field[] getDeclaredFields(): 获取所有的成员变量, 不考虑权限修饰符 Field getDeclaredField(String name): 获取指定名称的成员变量, 不考虑权限修饰符java.lang.reflect.Field: 表示一个成员变量 // 成员方法 String name; Person p = new Person(); p2.name = "abc"; void set(Object obj, Object value): 设置指定对象的成员变量的值 field.set(p1, "abc") Object get(Object obj): 获取指定对象的成员变量的值 field.get(p1) void setAccessible(boolean flag): 传true时忽略访问权限修饰符的安全检查. 暴力反射 field.set
反射: 获取构造方法Constructor
java.lang.Class<T>类: 表示一个类的字节码对象, 其中包含该类中定义的内容 // 获取构造方法们 Constructor<?>[] getConstructors(): 获取所有 public 的构造方法 Constructor<T> getConstructor(Class<?>... parameterTypes): 获取指定的 public 构造方法 Constructor<?>[] getDeclaredConstructors(): 获取所有的构造方法, 不考虑权限修饰符 Constructor<T> getDeclaredConstructor(Class... parameterTypes): 获取指定的构造方法, 不考虑权限修饰符 T newInstance(): 使用当前类的空参构造创建一个对象 Constructor con = c.getConstructor(String.class, int.class); con.newInstance("zhangsan", 18); new Person("zhangsan", 18); java.lang.reflect.Constructor<T>: 表示一个构造方法 // 成员方法 T newInstance(Object... initargs): 使用当前构造方法传入参数, 创建对象 void setAccessible(boolean flag): 注意: 构造方法不能利用此方法忽略权限, 会抛异常
反射: 获取成员方法Method
java.lang.Class<T>类: 表示一个类的字节码对象, 其中包含该类中定义的内容 // 获取成员方法们: Method[] getMethods(): 获取所有 public 的成员方法 Method getMethod(String name, Class<?>... parameterTypes) : 获取指定的 public 成员方法 Method[] getDeclaredMethods(): 获取所有的成员方法, 不考虑权限修饰符 Method getDeclaredMethod(String name, Class<?>... parameterTypes): 获取指定的成员方法, 不考虑权限修饰符 java.lang.reflect.Method类: 表示一个成员方法 // 成员方法 Person p = new Person(); p.eat("adf", 123); Object invoke(Object obj, Object... args): 使用指定对象和指定参数值调用此方法 String getName(): 获取方法名 void setAccessible(boolean flag): 传true时忽略访问权限修饰符的安全检查. 暴力反射
反射: 利用反射实现可以运行任意类的任意方法的框架案例
java.lang.Class<T> // 成员方法 ClassLoader getClassLoader(): 返回该类的类加载器 java.lang.ClassLoader: 类加载器 加载.class文件到内存的方法区中, 其他类型文件.properties // 成员方法 InputStream getResourceAsStream(String name): 读取相对于 out/production/模块名 目录中的文件, 返回一个字节流 使用类加载器加载配置文件 // 随便获取一个类的字节码对象 Class clazz = 类名.class; // 用字节码对象获取类加载器, 可加载bin目录中编译的文件 ClassLoader classLoader = clazz.getClassLoader(); // 使用类加载器加载一个文件, 返回一个字节流 InputStream is = classLoader.getResourceAsStream("相对于src目录的相对路径"); // 有了字节流, 就可以使用Properties的load(InputStream in)方法读取配置 Properties p = new Properties(); p.load(is); String value = p.getProperty("key");
注解注解: 概念
注解: Annotation JDK 1.5 引入. 也叫元数据, 是一种代码级别的说明 它可以声明在包, 类, 字段(成员变量), 方法, 局部变量, 方法参数等的前面, 用来对这些元素进行说明注解: 说明程序的。给计算机看的注释: 用文字描述程序的。给程序员看的使用注解: @注解名称作用分类: 1. 编写文档: 通过代码里标识的注解生成文档 (生成API文档 @author @version @since @param @return) 2. 代码分析: 通过代码里标识的注解对代码进行分析 (使用反射) (JUnit提供的 @Test @Before @After) 3. 编译检查: 通过代码里标识的注解让编译器能够实现基本的编译检查 (@Override @FunctionalInterface)
注解: JDK内置注解
JDK中预定义的一些注解: @Override: 检测被该注解标注的方法是否是"重写"父类(接口)的 @Deprecated: 该注解标注的内容,表示已过时 @SuppressWarnings: 压制警告. 一般传递参数all @SuppressWarnings("all")
自定义注解: 格式和本质
public interface 接口名 {}自定义注解格式:关键字 @interface 元注解 public @interface 注解名称 { 属性; (接口中的抽象方法) 属性; 属性; ... }@注解名称 注解的本质: 注解本质上就是一个接口,该接口默认继承Annotation接口 public interface MyAnno extends java.lang.annotation.Annotation {}
自定义注解: 属性定义
属性: 接口中的"抽象方法" 属性的要求: 1. 属性的"返回值类型"可以是以下类型: 基本数据类型(8种) String 枚举 注解 以上类型的数组 2. 定义了属性,在使用注解时, 需要"给属性赋值" (其实是抽象方法的返回值) 1. 属性使用 default 关键字指定默认值, 则可以不赋值 2. 如果只有一个名为"value"的属性需要赋值, 则 value 可以省略, 直接写值即可 3. 给数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
自定义注解: 元注解
元注解: 用于描述注解的注解 常用元注解: @Target: 描述注解能够作用的位置 ElementType枚举的常用取值: TYPE:可以作用于类上 METHOD:可以作用于方法上 FIELD:可以作用于成员变量上 示例: @Target(value = {ElementType.TYPE, ElementType.METHOD}) @Retention: 描述注解被保留的阶段 RetentionPolicy枚举的取值: SOURCE: 保留到源代码阶段 CLASS: 保留到类对象阶段 RUNTIME: 保留到运行时阶段 示例: @Retention(RetentionPolicy.RUNTIME):保留注解到class字节码文件中并被JVM读取到 @Documented: 加上后, 当前注解会被抽取到api文档中 @Inherited: 加上后, 当前注解会被子类继承
自定义注解: 解析注解
获取注解属性值的步骤: 1. 获取注解定义位置的对象 (Class对象(类注解), Field对象(成员变量注解), Method对象(方法注解)) 2. 调用 ProAnno a = cls.getAnnotation(ProAnno.class) 方法获取注解对象 3. 通过注解对象调用抽象方法获取属性值 // 比如获取一个类上的注解 注解类型 注解变量名 = 被注解的类.class.getAnnotation(注解名.class); 数据类型 变量名 = 注解变量名.抽象方法(); ProAnno proAnno = Test.class.getAnnotation(ProAnno.class); String className = proAnno.className(); String methodName = proAnno.methodName();
注解案例: 自己实现测试框架
java.lang.reflect.Method类: // 继承的方法 boolean isAnnotationPresent(注解名.class): 判断当前方法上是否出现了某个注解java.lang.Class类: String getName(): 获取类的全名 com.itheima.Person String getSimpleName(): 获取类名不含包名 Personjava.lang.Throwable String getMessage(): 获取异常的详细原因 // 成员方法 Throwable getCause(): 获取真正导致异常的根本异常 try { // 调用方法 method.invoke(calculator); } catch (Exception e) { // 计数器增加 number++; // 获取方法名 method.getName(); // 获取异常的类型 e.getCause().getClass().getSimpleName();// 可以获取最根本异常NullPointerException e.getClass().getSimpleName(); // 不能获取最根本异常 InvocationTargetException // 获取异常的详细原因 e.getMessage(); }
JUnit单元测试JUnit: 使用步骤
JUnit使用步骤: 1. 定义一个测试类(也叫测试用例) 包名:xxx.xxx.xx.test 被测试的类名: Calculator 对应的测试类名: CalculatorTest 2. 定义测试方法:可以独立运行 被测试的方法名: add() 对应的测试方法名: testAdd() 建议测试方法的返回值是void, 参数列表是空参 3. 在方法上加 @Test 注解 4. 在 @Test 注解上按 Alt+Enter, 选择 "Add 'JUnit4' to Classpath" 导入JUnit依赖环境 5. 在方法名上右键, 选择 "Run '方法名()'" 判定结果: 红色:失败 绿色:成功.(测试通过) 断言: Assert 使用断言操作来判断结果是否符合预期: Assert.assertEquals(期望的结果, 运算的结果); 如果 期望的结果 和 运算的结果 相等, 则认为测试通过, 否则测试失败 测试失败的原因提示: java.lang.AssertionError: Expected :1 (表示我期望得到的是1) Actual :-1 (但是实际得到的是-1)
JUnit: @Before, @After
@Before: 修饰的方法会"在每个测试方法执行 之前"被执行@After: 修饰的方法会"在每个测试方法执行 之后"被执行注意: @Before, @After 修饰的方法可以有多个, 但是谁先执行是由JUnit内部来决定的, 没有明显的规律 所以不要写多个@Before, @After 修饰的方法
Stream流、方法引用
Stream流流式思想概述Stream(流)是一个来自数据源的元素队列 - 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
- 数据源 流的来源。 可以是集合,数组 等。
和以前的Collection操作不同, Stream操作还有两个基础的特征: - Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者增强for的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法。
当使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。 获取流java.util.stream.Stream<T>是Java 8新加入的最常用的流接口。(这并不是一个函数式接口。) 获取一个流非常简单,有以下几种常用的方式: - 所有的Collection集合都可以通过stream默认方法获取流;
- Stream接口的静态方法of可以获取数组对应的流。
根据Collection获取流首先,java.util.Collection接口中加入了default方法stream用来获取流,所以其所有实现类均可获取流。 import java.util.*;import java.util.stream.Stream;public class Demo04GetStream { public static void main(String[] args) { List<String> list = new ArrayList<>(); // ... Stream<String> stream1 = list.stream(); Set<String> set = new HashSet<>(); // ... Stream<String> stream2 = set.stream(); Vector<String> vector = new Vector<>(); // ... Stream<String> stream3 = vector.stream(); }}根据Map获取流java.util.Map接口不是Collection的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流需要分key、value或entry等情况: import java.util.HashMap;import java.util.Map;import java.util.stream.Stream;public class Demo05GetStream { public static void main(String[] args) { Map<String, String> map = new HashMap<>(); // ... Stream<String> keyStream = map.keySet().stream(); Stream<String> valueStream = map.values().stream(); Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream(); }}根据数组获取流如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以Stream接口中提供了静态方法of,使用很简单: import java.util.stream.Stream;public class Demo06GetStream { public static void main(String[] args) { String[] array = { "张无忌", "张翠山", "张三丰", "张一元" }; Stream<String> stream = Stream.of(array); }}备注:of方法的参数其实是一个可变参数,所以支持数组。 1.4 常用方法流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种: 备注:本小节之外的更多方法,请自行参考API文档。 逐一处理:forEach虽然方法名字叫forEach,但是与for循环中的“for-each”昵称不同。 void forEach(Consumer<? super T> action);该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理。 复习Consumer接口java.util.function.Consumer<T>接口是一个消费型接口。Consumer接口中包含抽象方法void accept(T t),意为消费一个指定泛型的数据。基本使用:import java.util.stream.Stream;public class Demo12StreamForEach { public static void main(String[] args) { Stream<String> stream = Stream.of("张无忌", "张三丰", "周芷若"); stream.forEach(name-> System.out.println(name)); }}过滤:filter可以通过filter方法将一个流转换成另一个子集流。方法签名: Stream<T> filter(Predicate<? super T> predicate);该接口接收一个Predicate函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。 复习Predicate接口此前我们已经学习过java.util.stream.Predicate函数式接口,其中唯一的抽象方法为: boolean test(T t);该方法将会产生一个boolean值结果,代表指定的条件是否满足。如果结果为true,那么Stream流的filter方法将会留用元素;如果结果为false,那么filter方法将会舍弃元素。 基本使用Stream流中的filter方法基本使用的代码如: import java.util.stream.Stream;public class Demo07StreamFilter { public static void main(String[] args) { Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若"); Stream<String> result = original.filter(s -> s.startsWith("张")); }}在这里通过Lambda表达式来指定了筛选的条件:必须姓张。 映射:map如果需要将流中的元素映射到另一个流中,可以使用map方法。方法签名: <R> Stream<R> map(Function<? super T, ? extends R> mapper);该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。 复习Function接口此前我们已经学习过java.util.stream.Function函数式接口,其中唯一的抽象方法为: R apply(T t);这可以将一种T类型转换成为R类型,而这种转换的动作,就称为“映射”。 基本使用Stream流中的map方法基本使用的代码如: import java.util.stream.Stream;public class Demo08StreamMap { public static void main(String[] args) { Stream<String> original = Stream.of("10", "12", "18"); Stream<Integer> result = original.map(str->Integer.parseInt(str)); }}这段代码中,map方法的参数通过方法引用,将字符串类型转换成为了int类型(并自动装箱为Integer类对象)。 统计个数:count正如旧集合Collection当中的size方法一样,流提供count方法来数一数其中的元素个数: long count();该方法返回一个long值代表元素个数(不再像旧集合那样是int值)。基本使用: import java.util.stream.Stream;public class Demo09StreamCount { public static void main(String[] args) { Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若"); Stream<String> result = original.filter(s -> s.startsWith("张")); System.out.println(result.count()); // 2 }}取用前几个:limitlimit方法可以对流进行截取,只取用前n个。方法签名: Stream<T> limit(long maxSize);参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。基本使用: import java.util.stream.Stream;public class Demo10StreamLimit { public static void main(String[] args) { Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若"); Stream<String> result = original.limit(2); System.out.println(result.count()); // 2 }}跳过前几个:skip如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流: Stream<T> skip(long n);如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。基本使用: import java.util.stream.Stream;public class Demo11StreamSkip { public static void main(String[] args) { Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若"); Stream<String> result = original.skip(2); System.out.println(result.count()); // 1 }}组合:concat如果有两个流,希望合并成为一个流,那么可以使用Stream接口的静态方法concat: static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)备注:这是一个静态方法,与java.lang.String当中的concat方法是不同的。
该方法的基本使用代码如: import java.util.stream.Stream;public class Demo12StreamConcat { public static void main(String[] args) { Stream<String> streamA = Stream.of("张无忌"); Stream<String> streamB = Stream.of("张翠山"); Stream<String> result = Stream.concat(streamA, streamB); }}集合元素处理(Stream方式)题目将上一题当中的传统for循环写法更换为Stream流式处理方式。两个集合的初始内容不变,Person类的定义也不变。 解答等效的Stream流式处理代码为: import java.util.ArrayList;import java.util.List;import java.util.stream.Stream;public class DemoStreamNames { public static void main(String[] args) { List<String> one = new ArrayList<>(); // ... List<String> two = new ArrayList<>(); // ... // 第一个队伍只要名字为3个字的成员姓名; // 第一个队伍筛选之后只要前3个人; Stream<String> streamOne = one.stream().filter(s -> s.length() == 3).limit(3); // 第二个队伍只要姓张的成员姓名; // 第二个队伍筛选之后不要前2个人; Stream<String> streamTwo = two.stream().filter(s -> s.startsWith("张")).skip(2); // 将两个队伍合并为一个队伍; // 根据姓名创建Person对象; // 打印整个队伍的Person对象信息。 Stream.concat(streamOne, streamTwo).map(Person::new).forEach(System.out::println); }}运行效果完全一样: Person{name='宋远桥'}Person{name='苏星河'}Person{name='石破天'}Person{name='张天爱'}Person{name='张二狗'}方法引用在使用Lambda表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿什么参数做什么操作。那么考虑一种情况:如果我们在Lambda中所指定的操作方案,已经有地方存在相同方案,那是否还有必要再写重复逻辑? 2.1 冗余的Lambda场景来看一个简单的函数式接口以应用Lambda表达式: @FunctionalInterfacepublic interface Printable { void print(String str);}在Printable接口当中唯一的抽象方法print接收一个字符串参数,目的就是为了打印显示它。那么通过Lambda来使用它的代码很简单: public class Demo01PrintSimple { private static void printString(Printable data) { data.print("Hello, World!"); } public static void main(String[] args) { printString(s -> System.out.println(s)); }}其中printString方法只管调用Printable接口的print方法,而并不管print方法的具体实现逻辑会将字符串打印到什么地方去。而main方法通过Lambda表达式指定了函数式接口Printable的具体操作方案为:拿到String(类型可推导,所以可省略)数据后,在控制台中输出它。 2.2 问题分析这段代码的问题在于,对字符串进行控制台打印输出的操作方案,明明已经有了现成的实现,那就是System.out对象中的println(String)方法。既然Lambda希望做的事情就是调用println(String)方法,那何必自己手动调用呢? 2.3 用方法引用改进代码能否省去Lambda的语法格式(尽管它已经相当简洁)呢?只要“引用”过去就好了: public class Demo02PrintRef { private static void printString(Printable data) { data.print("Hello, World!"); } public static void main(String[] args) { printString(System.out::println); }}请注意其中的双冒号::写法,这被称为“方法引用”,而双冒号是一种新的语法。 2.4 方法引用符双冒号::为引用运算符,而它所在的表达式被称为方法引用。如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。 语义分析例如上例中,System.out对象中有一个重载的println(String)方法恰好就是我们所需要的。那么对于printString方法的函数式接口参数,对比下面两种写法,完全等效: - Lambda表达式写法:s -> System.out.println(s);
- 方法引用写法:System.out::println
第一种语义是指:拿到参数之后经Lambda之手,继而传递给System.out.println方法去处理。 第二种等效写法的语义是指:直接让System.out中的println方法来取代Lambda。两种写法的执行效果完全一样,而第二种方法引用的写法复用了已有方案,更加简洁。 注:Lambda 中 传递的参数 一定是方法引用中 的那个方法可以接收的类型,否则会抛出异常 推导与省略如果使用Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式——它们都将被自动推导。而如果使用方法引用,也是同样可以根据上下文进行推导。 函数式接口是Lambda的基础,而方法引用是Lambda的孪生兄弟。 下面这段代码将会调用println方法的不同重载形式,将函数式接口改为int类型的参数: @FunctionalInterfacepublic interface PrintableInteger { void print(int str);}由于上下文变了之后可以自动推导出唯一对应的匹配重载,所以方法引用没有任何变化: public class Demo03PrintOverload { private static void printInteger(PrintableInteger data) { data.print(1024); } public static void main(String[] args) { printInteger(System.out::println); }}这次方法引用将会自动匹配到println(int)的重载形式。 2.5 通过对象名引用成员方法这是最常见的一种用法,与上例相同。如果一个类中已经存在了一个成员方法: public class MethodRefObject { public void printUpperCase(String str) { System.out.println(str.toUpperCase()); }}函数式接口仍然定义为: @FunctionalInterfacepublic interface Printable { void print(String str);}那么当需要使用这个printUpperCase成员方法来替代Printable接口的Lambda的时候,已经具有了MethodRefObject类的对象实例,则可以通过对象名引用成员方法,代码为: public class Demo04MethodRef { private static void printString(Printable lambda) { lambda.print("Hello"); } public static void main(String[] args) { MethodRefObject obj = new MethodRefObject(); printString(obj::printUpperCase); }}2.6 通过类名称引用静态方法由于在java.lang.Math类中已经存在了静态方法abs,所以当我们需要通过Lambda来调用该方法时,有两种写法。首先是函数式接口: @FunctionalInterfacepublic interface Calcable { int calc(int num);}第一种写法是使用Lambda表达式: public class Demo05Lambda { private static void method(int num, Calcable lambda) { System.out.println(lambda.calc(num)); } public static void main(String[] args) { method(-10, n -> Math.abs(n)); }}但是使用方法引用的更好写法是: public class Demo06MethodRef { private static void method(int num, Calcable lambda) { System.out.println(lambda.calc(num)); } public static void main(String[] args) { method(-10, Math::abs); }}在这个例子中,下面两种写法是等效的: - Lambda表达式:n -> Math.abs(n)
- 方法引用:Math::abs
2.7 通过super引用成员方法如果存在继承关系,当Lambda中需要出现super调用时,也可以使用方法引用进行替代。首先是函数式接口: @FunctionalInterfacepublic interface Greetable { void greet();}然后是父类Human的内容: public class Human { public void sayHello() { System.out.println("Hello!"); }}最后是子类Man的内容,其中使用了Lambda的写法: public class Man extends Human { @Override public void sayHello() { System.out.println("大家好,我是Man!"); } //定义方法method,参数传递Greetable接口 public void method(Greetable g){ g.greet(); } public void show(){ //调用method方法,使用Lambda表达式 method(()->{ //创建Human对象,调用sayHello方法 new Human().sayHello(); }); //简化Lambda method(()->new Human().sayHello()); //使用super关键字代替父类对象 method(()->super.sayHello()); }}但是如果使用方法引用来调用父类中的sayHello方法会更好,例如另一个子类Woman: public class Man extends Human { @Override public void sayHello() { System.out.println("大家好,我是Man!"); } //定义方法method,参数传递Greetable接口 public void method(Greetable g){ g.greet(); } public void show(){ method(super::sayHello); }}在这个例子中,下面两种写法是等效的: - Lambda表达式:() -> super.sayHello()
- 方法引用:super::sayHello
|