友情链接站长平台,中国小康建设网 官方网站,为什么 要建设网站,石家庄大型网站设计公司文章目录 匿名内部类“匿名”在哪里 函数式编程lambda表达式的条件Supplier使用示例 ConsumeracceptandThen使用场景 FunctionalBiFunctionalTriFunctional 匿名内部类
匿名内部类的学习和使用是实现lambda表达式和函数式编程的基础。是想一下#xff0c;我们在使用接口中的方… 文章目录 匿名内部类“匿名”在哪里 函数式编程lambda表达式的条件Supplier使用示例 ConsumeracceptandThen使用场景 FunctionalBiFunctionalTriFunctional 匿名内部类
匿名内部类的学习和使用是实现lambda表达式和函数式编程的基础。是想一下我们在使用接口中的方法的时候正常流程都是定义这个接口的实现类然后使用实现类的对象调用接口中的方法。下面展示一个接口的方法使用的常规方法
public interface Test {void forTest();
}如果想要使用这个接口我们需要定义Test的实现类
// 定义一个学生类实现 Swim 游泳接口
public class TestImpl implements Test{// 实现方法Overridepublic void test() {//for testSystem.out.println(测试test方法);}//测试public static void main(String[] args) {// 创建 Student 类的对象 s1TestImpl t new TestImpl();//打印调用实现的方法t.test();}
}“匿名”在哪里
1. 匿名了实现的接口所在的父类 java中可以根据传入的对象类型区分这个对象的类信息所以匿名的第一层就是省略了这个类省去了 implements ClassName中的ClassName。之所以被称为 “匿名”主要是因为它没有显式地定义类名并且在创建对象的同时就直接实现了某个接口或者继承了某个类以下是关于它为何可以实现匿名以及具体匿名了哪些信息的详细解释
2. 匿名了外部独立的类定义结构 匿名内部类将类的定义和使用紧密结合在了一起它直接嵌套在创建对象的代码语句中没有像常规类那样在外部单独的代码块里呈现完整的类结构比如类的修饰符public、private 等、类的继承关系除了在匿名内部类中体现的继承自某个类或者实现某个接口等这些在普通类定义中可能出现的结构信息都被隐去了整个类的定义仿佛是 “匿名” 地融入到了使用它的那一处代码当中使代码结构更加简洁不过也相对牺牲了一些代码的清晰性和可维护性。换而言之匿名内部类中我们的TestImpl也不再需要显示给出这里我们以实现自定义比较器作为示例
ListInteger nums Arrays.asList(1,5,3,7,11,6,2);
nums.sort(new ComparatorInteger(){Overridepublic int compare(Integer i1, Integer i2){return i2- i1;}
});3. 其他信息补充说明 对于内部的方法入参和重写的注解是实现一个接口方法中必须的信息有无返回值需要根据接口方法定义是void还是其他区分以上就是匿名内部类的使用。在日常开发中基于参数是一个接口方法返回值的这种写法较为常见例如Runnable或者Comarator等
函数式编程
当前java 8中提供了很多基于函数式的新特性。其中函数式接口有代表性的非 lambda表达式莫属。相较于匿名内部类lambda表达式更加精简仅保留传入的实参和返回值以及计算逻辑。上文中的自定义比较器在lambda表达式下可以优化为“
ListInteger nums Arrays.asList(1,5,3,7,11,6,2);
nums.sort((i1, i2) - i2- i1);lambda表达式的条件
1. 必须实现的是函数式接口 函数式接口是指只包含一个抽象方法的接口除了从 Object 类继承的公共方法如 equals、hashCode 等这些不算额外的抽象方法。这是最关键的前提条件因为 Lambda 表达式本质上就是为了简洁地实现函数式接口而设计的语法糖。代码示例“下面的Runnable仅包含一个接口因此可以改写为lambda表达式
new Runnable() {Overridepublic void run() {System.out.println(执行任务);}
};如果是含有多个抽象方法的接口无法使用lambda表达式改写因为编译器无法区分你需要覆盖的具体方法所以下面的接口无法适配lambda表达式的改造
MyTestInterface myObj new MyTestInterface() {Overridepublic void method1() {// 具体实现逻辑}Overridepublic int method2(int num) {return num * 2;}
};2. 接口抽象方法的参数和返回值类型需明确可推断 虽然 Java 有类型推断机制但在使用 Lambda 表达式改写匿名内部类时接口抽象方法的参数类型和返回值类型要能够相对清晰地确定以便编译器能正确解析 Lambda 表达式所代表的逻辑。例如比较器的重写编译器可以根据传入的实参判断类型是String
interface StringJoinerFunction {String join(String s1, String s2);
}
public class StringJoinerExampleWithError {public static void main(String[] args) {StringJoinerFunction joiner (s1, s2) - {System.out.println(s1 s2);return s1s2; // 返回值类型与接口定义的String不符无法正确改写};System.out.println(joiner.join(Hello, World));}
}3. 实现逻辑相对简单无复杂的语句块或逻辑分支 这个要求仅仅基于代码本身的可读写例如一些if else逻辑也可以用于lamvbda表达式但是却失去了代码简洁原本的意义。不如直接使用匿名内部类或者实现接口的形式完成抽象方法的调用。
Supplier
Supplier供给者 Supplier是一个不接受任何输入参数但返回一个结果的操作。它主要用于生成数据或对象。Supplier接口定义了一个get方法该方法不接受任何输入参数并返回一个结果。表示从函数式接口返回的对象中获取Supplier内的数据 SupplierString supplier () - {return Hello World;};System.out.println(supplier.get());Supplier在企业级开发中的应用场景大约有以下几个方面
使用示例
1. 动态加载配置 一些数据库配置信息和连接池的加载如果消耗较大的资源并且希望使用时候动态加载的情况下可以使用Supplier预先定义这个连接配置项。避免在应用启动阶段就占用大量内存和初始化时间。 例如创建一个数据库连接池对象代码示例如下 public static SupplierDataSource supplier () - {HikariDataSource dataSource new HikariDataSource();dataSource.setJdbcUrl(xxxx:3306/pdb_19c);dataSource.setUsername(Hikari);dataSource.setPassword(123456);return dataSource;};public static DataSource getDataSource () {return (DataSource) Optional.ofNullable(supplier).get();}public static Connection getConnectionDyn() throws Exception {return getDataSource().getConnection();}2. 生成默认数据
继续上面的思路可以使用static final修饰默认值使用Supplier处理指向默认值的配置用作某属性为空的兜底配置 private static final SupplierInteger portSupplier () - {OptionalInteger configPort readPortFromConfig();return configPort.orElse(DEFAULT_PORT);};private static OptionalInteger readPortFromConfig() {// 模拟从配置文件读取端口号这里假设返回Optional.empty()表示读取失败Integer i1 null;return Optional.ofNullable(i1);}public static void main(String[] args) {System.out.println(portSupplier.get());}3. 实现灵活的策略模式 策略模式在企业级开发中常用于根据不同情况选择不同的业务逻辑执行方式。Supplier 结合 Lambda 表达式可以让策略模式的实现更加简洁和灵活下面是用Supplier实现的策略模式 public static SupplierBigDecimal normalMemberDiscount () - {return BigDecimal.valueOf(0.9); // 9折};// 高级会员折扣策略public static SupplierBigDecimal premiumMemberDiscount () - {return BigDecimal.valueOf(0.8); // 8折};public static BigDecimal calculateDiscount(Order order, SupplierBigDecimal discountSupplier) {return order.getAmount().multiply(discountSupplier.get());}4. 生成运行时的测试数据 Supplier 结合 Lambda 表达式可以方便地实现这一点让测试数据的生成更加灵活和动态。 例如在测试一个用户注册模块时需要生成不同的用户信息作为测试数据
class UserTestDataGenerator {private static SupplierString usernameSupplier () - {Random random new Random();return user_ random.nextInt(1000);};private static SupplierString passwordSupplier () - {Random random new Random();return pass_ random.nextInt(1000);};private static SupplierString emailSupplier () - {Random random new Random();return user_ random.nextInt(1000) example.com;};public static SupplierUser userSupplier () - new User(usernameSupplier.get(), passwordSupplier.get(), emailSupplier.get());
}Consumer
序言顾名思义是消费者的意思。这个函数本身不接收返回值类型一般实现打印、输出、入参的转换处理等操作 1. 代码示例 下面给出Consumer定义的示例可以看到这个对象的定义类似于一个Comparator比较器其作用是接收一个字符串然后执行accept方法中对于字符串的操作。 ConsumerString con new ConsumerString() {Overridepublic void accept(String string) {System.out.println(string values : string);}};con.accept(Hello World); 如果使用新版idea编译器的亲们可以发现编译器提示这个方法的优化写法为使用lambda表达式的形式即下面的格式 ConsumerString con string - System.out.println(string values : string);con.accept(Hello World);accept
accept在笔者看来可以视作一个开关当主线程调用这个方法的时候执行开关内部的逻辑。可以类比线程的submit方法执行内部的代码块 public static void main(String[] args) {// 定义一个ConsumerString类型的变量使用Lambda表达式实现其accept方法ConsumerString stringConsumer (str) - System.out.println(str);// 调用accept方法传入一个字符串参数stringConsumer.accept(Hello, World!);}
}由上面的代码可以了解到我们设置Consumer的定义并且在想要让其执行的地方应用accept()触发Consumer的函数部分输出了Hello World
andThen
如果一个操作之后还有其他操作可以将其Consumer对象放到andThen的参数位置上这个是因为andThen相当于执行accept的accept从源码分析上可以得到这样的结论
default ConsumerT andThen(Consumer? super T after) {Objects.requireNonNull(after);return (T t) - { accept(t); after.accept(t); };}consumer之间可以使用andThen进行串联式的编排例如对姓名进行输出之后转换字符串为小写通过consumer的定义和组装可以轻松实现。同时这两consumer对象也可以放在函数方法的形参位置作为回调方法使用 ListString arr Arrays.asList(Wang, Zi, Meng);ConsumerString con2 out - System.out.print(会员姓名 out ;);ConsumerString con3 str2 - System.out.println(小写版本为: str2.toLowerCase());con2.andThen(con3);arr.stream().forEach(con2.andThen(con3)::accept);输出结果
使用场景
使用函数式的consumer和普通的for循环有什么区别将通过下面的示例进行展示
case 1 代码简洁性与可读性
如果在for循环中需要多重处理并且这段代码整体写在循环体内容易造成多层嵌套或者本身具有一定程度的复用性应该考虑将其抽象出来作为一个Consumer对象例如循环处理某一个属性需要将属性进行字符的转换或者精度的保留ConsumerString con string - System.out.println(string.toUppercase());con.accept(Hello World);2. 更好地支持函数式编程范式
Java 8 引入了函数式编程的一些特性Consumer 作为函数式接口只包含一个抽象方法 accept 的接口符合函数式编程中对行为操作的抽象概念。它可以方便地与其他函数式接口如 Predicate、Function 等以及 Stream API 等配合使用实现更高级、更灵活的编程模式比如对集合进行过滤使用 Predicate后再对满足条件的元素进行消费使用 Consumer等操作能够在代码中更好地体现数据的转换、处理流程让代码更具逻辑性和条理性同时也便于进行代码的单元测试等维护工作。3. 增强代码的可复用性和灵活性
将操作抽象为 Consumer 接口可以方便地在不同的地方复用这些操作逻辑。例如前面提到的将字符串转换为大写的 Consumer 操作可以在多个需要对字符串进行此处理的地方重复使用只需要传递这个 Consumer 实例即可。而且通过将 Consumer 作为方法参数能让方法的功能更加灵活多样根据传入的不同 Consumer 实现不同的业务逻辑提高了代码应对不同需求变化的能力降低了代码的耦合度使得整个代码库更加易于扩展和维护。虽然在很多情况下使用普通逻辑确实也能实现相同的功能但 Consumer 函数式接口凭借其在代码简洁性、函数式编程支持以及复用性和灵活性等方面的优势在 Java 编程中有着广泛且合适的应用场景能够帮助开发人员更高效、优雅地编写代码应对各种复杂的业务需求Functional
通过lambda表达式可以看出匿名内部类的优化写法func定义如下 FunctionInteger,String func new FunctionInteger, String() {Overridepublic String apply(Integer integer) {return String.valueOf(integer);}};list.stream().forEach(li - {System.out.println(func.apply(li));});FunctionInteger,String funcLLambda integer - String.valueOf(integer);BiFunctional
这个接口有四个类型参数T、U、V 分别对应三个输入参数的类型而 R 对应返回结果的类型其唯一的抽象方法 apply 接受三个参数分别为 T、U、V 类型并返回一个 R 类型的结果符合接受三个元素作为入参并返回结果的需求并且由于标注了 FunctionalInterface可以很好地使用 Lambda 表达式来实现它。
代码示例 private BiFunctionDouble,Double,Double biFunction (Double a, Double b) - Math.sqrt(a*a b*b);TriFunctional
这个接口有四个类型参数T、U、V 分别对应三个输入参数的类型而 R 对应返回结果的类型其唯一的抽象方法 apply 接受三个参数分别为 T、U、V 类型并返回一个 R 类型的结果符合接受三个元素作为入参并返回结果的需求并且由于标注了 FunctionalInterface可以很好地使用 Lambda 表达式来实现它
代码示例 public static void main(String[] args) {TriFunctionString, String, String, String formatFunction (str1, str2, str3) - {return String.format(姓名: %s, 年龄: %s, 城市: %s, str1, str2, str3);};String result formatFunction.apply(张三, 25, 北京);System.out.println(result);}