IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Lambda表达式(Java8系列2) -> 正文阅读

[移动开发]Lambda表达式(Java8系列2)

Lambda

  • 你甚至不能将一个lambda赋值给一个Object

  • 为了好地支持Lambda。最终采取的方法是:增加函数式接口的概念

    • @FunctionalInterface

      • 非必要

  • 方法引用(::)

    • 对象::实例方法

    • 类::静态方法

    • 类::实例方法

  • 构造器引用

    • 类名::new

  • Lambda作用域

    • 含有自由变量的代码称为闭包(Closure)

    • 可以直接在 lambda 表达式中访问外部的局部变量

      • final int num = 1;
        Converter<Integer, String> stringConverter =
         ? ? ?  (from) -> String.valueOf(from + num);
        ?
        stringConverter.convert(2); ? ? // 3
    • 访问成员变量和静态变量

    • class Lambda4 {
       ? ?static int outerStaticNum;
       ? ?int outerNum;
      ?
       ? ?void testScopes() {
       ? ? ? ?Converter<Integer, String> stringConverter1 = (from) -> {
       ? ? ? ? ? ?outerNum = 23;
       ? ? ? ? ? ?return String.valueOf(from);
       ? ? ?  };
      ?
       ? ? ? ?Converter<Integer, String> stringConverter2 = (from) -> {
       ? ? ? ? ? ?outerStaticNum = 72;
       ? ? ? ? ? ?return String.valueOf(from);
       ? ? ?  };
       ?  }
      }

  • 默认方法

    • 函数式接口的支持

      • 一个接口如果需要多个方法,也要是函数式接口,那可将其他接口都声明为default

    • Collection中的forEach

      • 不能接受所有集合子类都实现一些新的方法

  • 类型推断

    • 改善心情

    • java7

      • Map<String, String> myMap = new HashMap<>();
    • java8

      • JEP101

      1.支持通过方法上下文推断泛型目标类型

      2.支持在方法调用链路当中,泛型类型推断传递到最后一个方法

class List<E> {
 ? static <Z> List<Z> nil() { ... };
 ? static <Z> List<Z> cons(Z head, List<Z> tail) { ... };
 ? E head() { ... }
}
//通过方法赋值的目标参数来自动推断泛型的类型
List<String> l = List.nil();
//而不是显示的指定类型
//List<String> l = List.<String>nil();
//通过前面方法参数类型推断泛型的类型
List.cons(42, List.nil());
//而不是显示的指定类型
//List.cons(42, List.<Integer>nil());

一、forEach

本文由 JavaGuide 翻译,原文地址:https://www.baeldung.com/foreach-java

1 概述

在Java 8中引入的forEach循环为程序员提供了一种新的,简洁而有趣的迭代集合的方式。

在本文中,我们将看到如何将forEach与集合一起使用,它采用何种参数以及此循环与增强的for循环的不同之处。

2 基础知识

public interface Collection<E> extends Iterable<E>

Collection 接口实现了 Iterable 接口,而 Iterable 接口在 Java 8开始具有一个新的 API:

void forEach(Consumer<? super T> action)//对 Iterable的每个元素执行给定的操作,直到所有元素都被处理或动作引发异常。

使用forEach,我们可以迭代一个集合并对每个元素执行给定的操作,就像任何其他迭代器一样。

例如,迭代和打印字符串集合for循环版本:

for (String name : names) {
 ? ?System.out.println(name);
}

我们可以使用forEach写这个 :

names.forEach(name -> {
 ? ?System.out.println(name);
});

3.使用forEach方法

3.1 匿名类

我们使用 forEach迭代集合并对每个元素执行特定操作。要执行的操作包含在实现Consumer接口的类中,并作为参数传递给forEach 。

所述消费者接口是一个功能接口(具有单个抽象方法的接口)。它接受输入并且不返回任何结果。

Consumer 接口定义如下:

@FunctionalInterface
public interface Consumer {
 ? ?void accept(T t);
}

任何实现,例如,只是打印字符串的消费者:

Consumer<String> printConsumer = new Consumer<String>() {
 ? ?public void accept(String name) {
 ? ? ? ?System.out.println(name);
 ?  };
};

可以作为参数传递给forEach

names.forEach(printConsumer);

但这不是通过消费者和使用forEach API 创建操作的唯一方法。让我们看看我们将使用forEach方法的另外2种最流行的方式:

3.2 Lambda表达式

Java 8功能接口的主要优点是我们可以使用Lambda表达式来实例化它们,并避免使用庞大的匿名类实现。

由于 Consumer 接口属于函数式接口,我们可以通过以下形式在Lambda中表达它:

(argument) -> { body }
name -> System.out.println(name)
names.forEach(name -> System.out.println(name));

3.3 方法参考

我们可以使用方法引用语法而不是普通的Lambda语法,其中已存在一个方法来对类执行操作:

names.forEach(System.out::println);

4.forEach在集合中的使用

4.1.迭代集合

任何类型Collection的可迭代 - 列表,集合,队列 等都具有使用forEach的相同语法。

因此,正如我们已经看到的,迭代列表的元素:

List<String> names = Arrays.asList("Larry", "Steve", "James");
 
names.forEach(System.out::println);

同样对于一组:

Set<String> uniqueNames = new HashSet<>(Arrays.asList("Larry", "Steve", "James"));
 
uniqueNames.forEach(System.out::println);

或者让我们说一个队列也是一个集合

Queue<String> namesQueue = new ArrayDeque<>(Arrays.asList("Larry", "Steve", "James"));
 
namesQueue.forEach(System.out::println);

4.2.迭代Map - 使用Map的forEach

Map没有实现Iterable接口,但它提供了自己的forEach 变体,它接受BiConsumer。*

Map<Integer, String> namesMap = new HashMap<>();
namesMap.put(1, "Larry");
namesMap.put(2, "Steve");
namesMap.put(3, "James");
namesMap.forEach((key, value) -> System.out.println(key + " " + value));

4.3.迭代一个Map - 通过迭代entrySet

namesMap.entrySet().forEach(entry -> System.out.println(entry.getKey() + " " + entry.getValue()));

二、 内置函数式接口(Built-in Functional Interfaces)

函数式接口函数描述符
Runnable()->void
Predicate<T>T->boolean
Consumer<T>T->void
Function<T,R>T->R
Supplier<T>() -> T
UnaryOperator<T>T -> T
BinaryOperator<T>(T,T)->T
BiPredicate<L,R>(L,R)->boolean
BiConsumer<T,U>(T,U)->void
BiFunction<T,U,R>(T,U)->R

JDK 1.8 API包含许多内置函数式接口。 其中一些借口在老版本的 Java 中是比较常见的比如: ComparatorRunnable,这些接口都增加了@FunctionalInterface注解以便能用在 lambda 表达式上。

但是 Java 8 API 同样还提供了很多全新的函数式接口来让你的编程工作更加方便,有一些接口是来自 Google Guava 库里的,即便你对这些很熟悉了,还是有必要看看这些是如何扩展到lambda上使用的。

Predicates

Predicate 接口是只有一个参数的返回布尔类型值的 断言型 接口。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非):

译者注: Predicate 接口源码如下

package java.util.function;
import java.util.Objects;
?
@FunctionalInterface
public interface Predicate<T> {
 ? ?
 ? ?// 该方法是接受一个传入类型,返回一个布尔值.此方法应用于判断.
 ? ?boolean test(T t);
?
 ? ?//and方法与关系型运算符"&&"相似,两边都成立才返回true
 ? ?default Predicate<T> and(Predicate<? super T> other) {
 ? ? ? ?Objects.requireNonNull(other);
 ? ? ? ?return (t) -> test(t) && other.test(t);
 ?  }
 ? ?// 与关系运算符"!"相似,对判断进行取反
 ? ?default Predicate<T> negate() {
 ? ? ? ?return (t) -> !test(t);
 ?  }
 ? ?//or方法与关系型运算符"||"相似,两边只要有一个成立就返回true
 ? ?default Predicate<T> or(Predicate<? super T> other) {
 ? ? ? ?Objects.requireNonNull(other);
 ? ? ? ?return (t) -> test(t) || other.test(t);
 ?  }
 ? // 该方法接收一个Object对象,返回一个Predicate类型.此方法用于判断第一个test的方法与第二个test方法相同(equal).
 ? ?static <T> Predicate<T> isEqual(Object targetRef) {
 ? ? ? ?return (null == targetRef)
 ? ? ? ? ? ? ? ?? Objects::isNull
 ? ? ? ? ? ? ?  : object -> targetRef.equals(object);
 ?  }

示例:

Predicate<String> predicate = (s) -> s.length() > 0;
?
predicate.test("foo"); ? ? ? ? ? ? ?// true
predicate.negate().test("foo"); ? ? // false
?
Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;
?
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();

Functions

Function 接口接受一个参数并生成结果。默认方法可用于将多个函数链接在一起(compose, andThen):

译者注: Function 接口源码如下

package java.util.function;
 
import java.util.Objects;
 
@FunctionalInterface
public interface Function<T, R> {
 ? ?
 ? ?//将Function对象应用到输入的参数上,然后返回计算结果。
 ? ?R apply(T t);
 ? ?//将两个Function整合,并返回一个能够执行两个Function对象功能的Function对象。
 ? ?default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
 ? ? ? ?Objects.requireNonNull(before);
 ? ? ? ?return (V v) -> apply(before.apply(v));
 ?  }
 ? ?// 
 ? ?default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
 ? ? ? ?Objects.requireNonNull(after);
 ? ? ? ?return (T t) -> after.apply(apply(t));
 ?  }
 
 ? ?static <T> Function<T, T> identity() {
 ? ? ? ?return t -> t;
 ?  }
}

Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);
backToString.apply("123"); ? ? // "123"

Suppliers

Supplier 接口产生给定泛型类型的结果。 与 Function 接口不同,Supplier 接口不接受参数。

Supplier<Person> personSupplier = Person::new;
personSupplier.get(); ? // new Person

Consumers

Consumer 接口表示要对单个输入参数执行的操作。

Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));

Comparators

Comparator 是老Java中的经典接口, Java 8在此之上添加了多种默认方法:

Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
?
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
?
comparator.compare(p1, p2); ? ? ? ? ? ? // > 0
comparator.reversed().compare(p1, p2); ?// < 0

三、Optionals

Optionals不是函数式接口,而是用于防止 NullPointerException 的漂亮工具。这是下一节的一个重要概念,让我们快速了解一下Optionals的工作原理。

Optional 是一个简单的容器,其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是有时却什么也没有返回,而在Java 8中,你应该返回 Optional 而不是 null。

译者注:示例中每个方法的作用已经添加。

//of():为非null的值创建一个Optional
Optional<String> optional = Optional.of("bam");
// isPresent(): 如果值存在返回true,否则返回false
optional.isPresent(); ? ? ? ? ? // true
//get():如果Optional有值则将其返回,否则抛出NoSuchElementException
optional.get(); ? ? ? ? ? ? ? ? // "bam"
//orElse():如果有值则将其返回,否则返回指定的其它值
optional.orElse("fallback"); ? ?// "bam"
//ifPresent():如果Optional实例有值则为其调用consumer,否则不做处理
optional.ifPresent((s) -> System.out.println(s.charAt(0))); ? ? // "b"

推荐阅读:[[Java8]如何正确使用Optional]

四、方法引用

ambda表达式方法引用
(args) -> ClassName.staticMethod(args)ClassName::staticMethod静态方法方法引用
(arg0, params) -> arg0.instanceMethod(params)ClassName::instanceMethod内部实例方法引用
arg0 (params) -> arg0.instanceMethod(params)arg0.instanceMethod外部实例方法引用

五、 lambda表达式的写法

六、 重构重复代码
?

Java8?由Oracle在2014年发布,是继Java5之后最具革命性的版本。 Java8吸收其他语言的精髓带来了函数式编程,lambda表达式,Stream流等一系列新特性,学会了这些新特性,可以让你实现高效编码优雅编码。

烂代码登场

首先引入一个实际的例子,我们常常会写一个dao类来操作数据库,比如查询记录,插入记录等。

下面的代码中实现了查询和插入功能(引入Mybatis三方件):

public class StudentDao {
 ? ?/**
 ? ? * 根据学生id查询记录
 ? ? * @param id 学生id
 ? ? * @return 返回学生对象
 ? ? */
 ? ?public Student queryOne(int id) {
 ? ? ? ?SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
 ? ? ? ?SqlSession session = null;
 ? ? ? ?try {
 ? ? ? ? ? ?session = sqlSessionFactory.openSession();
 ? ? ? ? ? ?// 根据id查询指定的student对象
 ? ? ? ? ? ?return session.selectOne("com.coderspace.mapper.student.queryOne", id);
 ? ? ?  } finally {
 ? ? ? ? ? ?if (session != null) {
 ? ? ? ? ? ? ? ?session.close();
 ? ? ? ? ?  }
 ? ? ?  }
 ?  }
 ? ?/**
 ? ? * 插入一条学生记录
 ? ? * @param student 待插入对象
 ? ? * @return true if success, else return false
 ? ? */
 ? ?public boolean insert(Student student) {
 ? ? ? ?SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
 ? ? ? ?SqlSession session = null;
 ? ? ? ?try {
 ? ? ? ? ? ?session = sqlSessionFactory.openSession();
 ? ? ? ? ? ?// 向数据库插入student对象
 ? ? ? ? ? ?int rows = session.insert("com.coderspace.mapper.student.insert", student);
 ? ? ? ? ? ?return rows > 0;
 ? ? ?  } finally {
 ? ? ? ? ? ?if (session != null) {
 ? ? ? ? ? ? ? ?session.close();
 ? ? ? ? ?  }
 ? ? ?  }
 ?  }
  }

睁大眼睛观察上面的代码可以发现,这两个方法有很多重复的代码。

除了下面这两行,其他的代码都是一样的,都是先获取session,然后执行核心操作,最后关闭session。

// 方法1中核心代码
return session.selectOne("com.coderspace.mapper.student.queryOne", id);
// 方法2中核心代码
int rows = session.insert("com.coderspace.mapper.student.insert", student);

作为一个有追求的程序员,不,应该叫代码艺术家,是不是应该考虑重构一下。

获取session和关闭session这段代码围绕着具体的核心操作代码,我们可以称这段代码为模板代码。

假如又来了一个需求,需要实现删除student方法,那么你肯定会copy上面的获取session和关闭session代码,这样做有太多重复的代码,作为一名优秀的工程师肯定不会容忍这种事情的发生。

开始重构烂代码

怎么重构呢?现在请出我们的主角登场:环绕执行模式使行为参数化

名字是不是很高大上,啥叫行为参数化?上面例子中我们已经观察到了,除了核心操作代码其他代码都是一模一样,那我们是不是可以将核心操作代码作为入参传入模板方法中,根据不同的行为分别执行。

变量对象很容易作为参数传入,行为可以作为参数传入吗?

答案是:当然可以,可以采用lambda表达式传入。

下面开始重构之前的例子,主要可以分为三步:

(1)定义函数式接口;

(2)定义模板方法;

(3)传递lambda表达式

所有的环绕执行模式都可以套用上面这三步公式。

第一步:定义函数式接口

@FunctionalInterface
public interface DbOperation<R> {
 ? ?/**
 ? ? * 通用操作数据库接口
 ? ? * @param session 数据库连接session
 ? ? * @param mapperId 关联mapper文件id操作
 ? ? * @param params 操作参数
 ? ? * @return 返回值,R泛型
 ? ? */
 ? ?R operate(SqlSession session, String mapperId, Object params);
}

定义了一个operate抽象方法,接收三个参数,返回泛型R。

第二步:定义模板方法

DbOperation是一个函数式接口,作为入参传入:

public class CommonDao<R> {
 ? ?
 ? ?public R proccess(DbOperation<R> dbOperation, String mappperId, Object params) {
 ? ? ? ?SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
 ? ? ? ?SqlSession session = null;
 ? ? ? ?try {
 ? ? ? ? ? ?session = sqlSessionFactory.openSession();
 ? ? ? ? ? ?// 核心操作
 ? ? ? ? ? ?return dbOperation.operate(session, mappperId, params);
 ? ? ?  } finally {
 ? ? ? ? ? ?if (session != null) {
 ? ? ? ? ? ? ? ?session.close();
 ? ? ? ? ?  }
 ? ? ?  }
 ?  }
  }

第三步:传递lambda表达式

// 根据id查询学生
String mapperId = "com.coderspace.mapper.student.queryOne";
int studentNo = 123;
CommonDao<Student> commonDao = new CommonDao<>();
// 使用lambda传递具体的行为
Student studentObj = commonDao.proccess(
 ? ? ?  (session, mappperId, params) -> session.selectOne(mappperId, params),
 ? ? ? ?mapperId, studentNo);
// 插入学生记录
String mapperId2 = "com.coderspace.mapper.student.insert";
Student student = new Student("coderspace", 1, 100);
CommonDao<Boolean> commonDao2 = new CommonDao<>();
// 使用lambda传递具体的行为
Boolean successInsert = commonDao2.proccess(
 ? ? ?  (session, mappperId, params) -> session.selectOne(mappperId, params),
 ? ? ? ?mapperId2, student);

实现了上面三步,假如要实现删除方法,CommonDao里面一行代码都不用改,只用在调用方传入不同的参数即可实现。

总结

环绕执行模式在项目实战中大有用途,如果你发现几行易变的代码外面围绕着一堆固定的代码,这个时候你应该考虑使用lambda环绕执行模式了。

环绕执行模式固有套路请跟我一起大声读三遍:

第一步:定义函数式接口

第二步:定义模板方法

第三步:传递lambda表达式

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-04-22 18:47:45  更:2022-04-22 18:51:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 22:32:54-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码