A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© minaduki2333 初级黑马   /  2019-5-19 10:52  /  976 人查看  /  0 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

反射、注解、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。这些方法可以被分成两种:
  • 延迟方法:返回值类型仍然是Stream接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法。)
  • 终结方法:返回值类型不再是Stream接口自身类型的方法,因此不再支持类似StringBuilder那样的链式调用。本小节中,终结方法包括count和forEach方法。

备注:本小节之外的更多方法,请自行参考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    }}取用前几个:limit
limit方法可以对流进行截取,只取用前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

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马