47、Java 中的流、Lambda 表达式与 Spring 框架入门

Java 中的流、Lambda 表达式与 Spring 框架入门

1. Java 中的流与 Lambda 表达式

在 Java 编程中,流(Streams)、聚合操作和 Lambda 表达式是强大的特性,它们让原本复杂的任务变得简单。下面我们来详细了解如何使用流和 Lambda 表达式处理数据。

1.1 Lambda 表达式与流过滤

在流处理中,Lambda 表达式是一个关键元素。例如,我们有一个 Person 对象的列表,要筛选出年龄大于等于 18 岁的人,可以这样写代码:

people.stream()
    .filter((p) -> p.getAge() >= 18);

这里的 p 是 Lambda 表达式的参数,它代表流中的每个 Person 对象。箭头操作符 -> 用于分隔参数和 Lambda 表达式的主体。主体部分 p.getAge() >= 18 是一个判断条件,返回 true 的对象会留在流中。

如果要筛选出名字以 J 开头的人,代码如下:

people.stream()
    .filter((p) -> p.getName().startsWith("J"));

若要同时满足年龄大于等于 18 岁且名字以 J 开头的条件,可以将两个 filter 方法链式调用:

people.stream()
    .filter((p) -> p.getAge() >= 18)
    .filter((p) -> p.getName().startsWith("J"));

通常建议使用多个独立的 filter 方法,这样可以使代码更具可读性。

1.2 流的 map 方法

有时候我们只关心数据的某一部分,或者需要将数据转换为其他类型。 map 方法可以帮助我们实现这一点。例如,要从 Person 对象列表中获取所有人的名字,可以这样做:

people.stream()
    .map((p) -> p.getName());

这里通过 map 方法将 Person 对象流转换为 String 对象流。

还有一些特殊的 map 方法,如 mapToInt() mapToDouble() mapToLong() ,可以将流转换为特定的基本类型流。例如,要计算所有人的平均年龄:

int averageAge = people.stream()
                    .mapToInt((p) -> p.getAge())
                    .average();

map 方法还可以与 filter 方法或其他流方法结合使用。例如,要获取年龄大于 18 岁的人的名字:

people.stream()
    .filter((p) -> p.getAge() >= 18)
    .map((p) -> p.getName());
1.3 流的 collect 方法

filter map 方法返回的都是流,我们通常需要将流转换为列表或其他数据结构以便在代码中使用。 collect 方法可以实现这一功能。

将流收集到列表中:

List<Person> overEighteen = people.stream()
                            .filter((p) -> p.getAge() >= 18)
                            .collect(Collectors.toList());

将流收集到 Map 中,按年龄分组:

Map<Integer, List<Person>> peopleAges = people.stream()
        .collect(Collectors.groupingBy((p) -> p.getAge()));

需要注意的是,将过滤后的流赋值给新的列表时,原始列表不会改变。

以下是一个完整的使用 collect 方法的示例:

package com.tsg.lambdafun;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class App {

    public static void main(String[] args) {

        List<Person> people = new ArrayList<>();

        people.add(new Person("Alfred", 17));
        people.add(new Person("Henrey", 18));
        people.add(new Person("George", 19));
        people.add(new Person("Joe", 27));
        people.add(new Person("Zelda", 7));
        people.add(new Person("Zoe", 27));

        List<Person> oldPeople = people.stream()
                .filter((p) -> p.getName().startsWith("Z"))
                .collect(Collectors.toList());

        oldPeople.stream()
                .forEach((currentPerson) -> {
                    System.out.print(currentPerson.getName());
                    System.out.print(" - ");
                    System.out.println(currentPerson.getAge());
                });
    }
}

运行这个程序,输出结果为:

Zelda - 7
Zoe - 27

流处理的主要特点总结如下:
| 特点 | 描述 |
| ---- | ---- |
| 与集合关联 | 流与对象集合相关联,但它不是数据结构,而是一系列的项。 |
| 管道处理 | 流对象通过管道进行处理,管道由零个或多个中间操作和一个终端操作组成。 |
| 内部迭代 | 流的迭代是内部的,调用代码无法控制迭代过程。 |
| 常用方法 | 主要使用 filter() map() collect() forEach() 方法处理数据。 |

流处理的基本流程可以用以下 mermaid 流程图表示:

graph LR
    A[集合] --> B[创建流]
    B --> C{中间操作?}
    C -- 是 --> D[中间操作]
    D --> C
    C -- 否 --> E[终端操作]
    E --> F[结果]
1.4 练习

为了巩固所学知识,我们可以做以下练习:
- 练习 1:筛选年轻人
- 修改代码,只打印出年龄小于 18 岁的人。
- 进一步修改,只打印出年龄小于 18 岁且名字以 A 开头的人。
- 使用 collect() 方法创建一个新列表,其中只包含年龄小于 17 岁的人,并打印结果进行确认。
- 练习 2:DVD 库更新
- 对之前的 DVD 库程序进行修改,利用 Lambda 表达式、流和聚合特性。
- 添加以下功能:
- 查找最近 N 年发行的所有电影。
- 查找具有给定 MPAA 评级的所有电影。
- 查找指定导演的所有电影,并按 MPAA 评级将电影分类到不同的数据结构中。
- 查找特定制片厂发行的所有电影。
- 计算电影集合中所有电影的平均年龄。
- 找出集合中最新的电影。
- 找出集合中最旧的电影。
- 计算电影集合中所有电影的平均注释数量。
- 在实现过程中,需要包含一个 DAO 接口,其中包含上述所有方法以及之前版本 DAO 中的方法。同时,实现一个类来实现 DVDLibrary 接口,使用 Lambda 表达式、流和聚合操作,并对控制器、视图和应用类进行必要的更改。

2. Spring 框架入门

Spring 是一个为基于 JVM 的企业应用提供支持的库集合。在这部分内容中,我们将重点介绍 Spring 框架的依赖注入以及如何使用 XML 配置文件或基于注解的配置来实现它。

2.1 学习目标

通过学习,我们要达到以下目标:
- 解释依赖注入(DI)和面向接口编程如何协同工作。
- 说明为什么使用 Spring 框架。
- 概述 Spring 的四个主要策略。
- 定义普通 Java 对象(POJOs)。
- 使用 XML 配置实现依赖注入。
- 使用基于注解的配置实现依赖注入。

2.2 依赖注入与面向接口编程

依赖注入是一种实现控制反转的设计模式,这两个术语通常可以互换使用。在依赖注入中,客户端对象不再负责实例化它们所依赖的对象(即服务),而是由其他实体将依赖项注入到客户端对象中。依赖项可以通过构造函数或 setter 方法传递给客户端。

我们之前已经在一定程度上使用了这种模式,将组件的依赖项通过构造函数传递。但这种配置是硬编码在 App 类中的,如果需要更改配置,就必须修改 App 类并重新编译。Spring 框架允许我们将配置外部化到 XML 文件中,或者使用注解来定义依赖注入。

使用依赖注入有以下优点:
- 实现客户端与服务的具体实现之间的松散耦合。
- 将系统的配置信息外部化到配置文件中,这样在进行配置更改时无需重新编译应用程序。
- 支持更灵活的并行开发,开发人员可以针对接口进行编程,并在组件的实际实现构建过程中使用存根或模拟实现。

2.3 为什么使用 Spring

Spring 最初是为了应对当时 Java 框架日益复杂的问题而创建的。它的主要目的是简化 Java 应用程序的编程和创建。早期,企业 Java 应用主要使用 Enterprise Java Bean(EJB)规范,该规范需要复杂的部署描述符和大量额外的代码。随着时间的推移,Java 开发者希望有更简单、更干净的方式来构建复杂应用,Spring 应运而生。

虽然 Spring 框架从一开始就大大简化了企业 Java 编程,但它也并非没有受到批评。早期版本严重依赖基于 XML 的配置,有时感觉就像在用 XML 编写程序。后来的版本增加了基于注解和 Java 的配置选项,为开发者提供了更多选择。

最近的 Spring 版本引入了 Spring Boot,它基于应用程序所包含或可用的库实现自动配置。Spring Boot 采用“约定优于配置”的方法,在标准情况下几乎不需要配置。当然,开发者仍然可以使用 XML、Java 或注解来覆盖应用程序中的任何配置设置。

2.4 理解 Spring 方法

Spring 除了通过依赖注入实现松散耦合外,还提供了一种轻量级的开发方法,使用普通 Java 对象和模板来替代重复的代码。

普通 Java 对象(POJOs)
许多框架要求开发者继承它们的类以利用其特性,这往往导致代码与框架紧密绑定,限制了代码的可移植性。Spring 则允许使用普通 Java 对象,这些对象可以基本不包含特定于框架的代码,因此更易于测试,并且在需要时可以轻松迁移到其他框架。

Spring 框架包含一个称为控制反转(IoC)容器的容器,它负责创建应用程序所需的对象,并通过对象的生命周期进行配置和管理。Spring 提供了两个支持依赖注入的容器:
- BeanFactory 容器 :最简单的容器,提供基本的依赖注入支持。
- ApplicationContext 容器 :基于 BeanFactory 构建,提供更多企业级功能。由于它包含了 BeanFactory 的所有功能,因此建议使用它。

Spring 容器管理的所有对象称为 Bean。容器使用配置元数据来确定要实例化、组装和配置哪些对象。我们可以使用 XML 来表示配置元数据,也可以通过注解或 Java 代码来实现。

模板
Spring 使用模板来减少应用程序中重复的资源管理和错误处理代码。以下是一个不使用 Spring JDBC 模板与数据库交互的示例:

public Employee getEmployeeById(long id) {
    Connection conn = null;
    PreparedStatement stmt = null;
    ResultSet rs = null;
    try {
        conn = dataSource.getConnection();
        stmt = conn.prepareStatement(
                "select id, firstname, lastname, salary from "
                + "employee where id=?");
        stmt.setLong(1, id);
        rs = stmt.executeQuery();
        Employee employee = null;
        if (rs.next()) {
            employee = new Employee();
            employee.setId(rs.getLong("id"));
            employee.setFirstName(rs.getString("firstname"));
            employee.setLastName(rs.getString("lastname"));
            employee.setSalary(rs.getBigDecimal("salary"));
        }
        return employee;
    } catch (SQLException e) {
        // 这里应该做什么?
    } finally {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {}
        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {}
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {}
        }
    }
    return null;
}

而使用 Spring JDBC 模板的示例如下:

public Employee getEmployeeById(long id) {
    return jdbcTemplate.queryForObject (
        "select id, firstname, lastname, salary " +
        "from employee where id=?",
        new RowMapper<Employee>() {
            public Employee mapRow(ResultSet rs, int rowNum) throws SQLException {
                Employee employee = new Employee();
                employee.setId(rs.getLong("id"));
                employee.setFirstName(rs.getString("firstname"));
                employee.setLastName(rs.getString("lastname"));
                employee.setSalary(rs.getBigDecimal("salary"));
                return employee;
            }
        },
        id);
}

可以明显看出,使用 Spring 模板的代码更加简洁,开发者可以将注意力集中在业务逻辑上,而不是重复的资源管理和错误处理代码上。

Spring 容器的工作流程可以用以下 mermaid 流程图表示:

graph LR
    A[POJO 类] --> B[Spring 容器]
    C[配置元数据] --> B
    B --> D[创建和管理 Bean]
    D --> E[应用程序]
2.5 Spring 依赖注入实战

为了演示如何实现 Spring 依赖注入,我们可以使用一个预先编写好的 MVC 项目。可以通过以下 URL 下载 Book Tracker 文件: www.wiley.com/go/jobreadyjava ,在“下载”链接下找到该文件。

具体操作步骤如下:
1. 将文件保存到计算机上并解压其内容。
2. 在 IDE 中打开解压后的项目。
3. 运行程序,会看到一个简单的菜单结构,允许我们输入和查看书籍详细信息。

Main Menu
1. View Books

通过以上内容,我们对 Java 中的流、Lambda 表达式以及 Spring 框架的依赖注入有了初步的了解。在实际开发中,可以根据具体需求灵活运用这些技术,提高开发效率和代码质量。

2.6 Spring 依赖注入配置方式对比

Spring 提供了 XML 配置和注解配置两种方式来实现依赖注入,下面我们对这两种方式进行详细对比。

配置方式 优点 缺点 适用场景
XML 配置 配置信息与代码分离,便于维护和修改;可以清晰地看到 Bean 之间的依赖关系 配置文件可能会变得冗长复杂,增加开发和维护成本;需要手动编写大量 XML 代码 大型项目,需要对 Bean 配置进行集中管理和维护;团队成员对 XML 配置比较熟悉
注解配置 代码简洁,减少了配置文件的编写;开发效率高,能够快速实现依赖注入 注解与代码耦合度高,修改配置可能需要修改代码;对于复杂的配置,注解可能不够灵活 小型项目,追求开发效率;团队成员对注解编程比较熟悉

以下是使用 XML 配置和注解配置实现依赖注入的简单示例:

XML 配置示例

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userService" class="com.example.service.UserService">
        <property name="userDao" ref="userDao"/>
    </bean>

    <bean id="userDao" class="com.example.dao.UserDaoImpl"/>
</beans>

注解配置示例

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    // 业务方法
}
import org.springframework.stereotype.Repository;

@Repository
public class UserDaoImpl implements UserDao {

    // 数据访问方法
}
2.7 Spring 框架的其他特性

除了依赖注入,Spring 框架还提供了许多其他强大的特性,如 MVC 架构、RESTful 服务、安全认证等。

MVC 架构
Spring MVC 是一个基于 Servlet 的 MVC 框架,用于构建 Web 应用程序。它遵循 MVC 设计模式,将业务逻辑、数据和视图分离,提高了代码的可维护性和可测试性。

Spring MVC 的基本工作流程如下:

graph LR
    A[客户端请求] --> B[DispatcherServlet]
    B --> C{HandlerMapping}
    C --> D[Controller]
    D --> E[ModelAndView]
    E --> F{ViewResolver}
    F --> G[View]
    G --> H[响应客户端]

RESTful 服务
Spring 框架支持构建 RESTful Web 服务,通过使用 @RestController @RequestMapping 等注解,可以轻松地创建 RESTful 接口。

以下是一个简单的 RESTful 服务示例:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class RestfulController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello, RESTful!";
    }
}

安全认证
Spring Security 是 Spring 框架提供的安全认证和授权框架,用于保护 Web 应用程序的安全性。它可以实现用户认证、授权、防止 CSRF 攻击等功能。

使用 Spring Security 的基本步骤如下:
1. 添加 Spring Security 依赖。
2. 创建配置类,继承 WebSecurityConfigurerAdapter 类,并重写相关方法进行安全配置。
3. 在配置类中定义用户认证和授权规则。

以下是一个简单的 Spring Security 配置示例:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
           .authorizeRequests()
               .antMatchers("/public/**").permitAll()
               .anyRequest().authenticated()
               .and()
           .formLogin()
               .loginPage("/login")
               .permitAll()
               .and()
           .logout()
               .permitAll();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
2.8 总结与展望

通过本文的学习,我们深入了解了 Java 中的流、Lambda 表达式以及 Spring 框架的相关知识。流和 Lambda 表达式为我们处理数据提供了强大而简洁的方式,能够大大提高代码的可读性和开发效率。Spring 框架则为企业级应用开发提供了全面的解决方案,包括依赖注入、MVC 架构、RESTful 服务和安全认证等功能。

在未来的开发中,我们可以根据项目的需求和特点,灵活运用这些技术。例如,在处理大量数据时,可以使用流和 Lambda 表达式进行高效的数据处理;在构建企业级 Web 应用时,可以使用 Spring 框架来实现系统的架构设计和功能开发。

同时,随着 Java 技术的不断发展,这些特性也会不断完善和扩展。我们需要持续学习和关注新技术的发展,不断提升自己的技术水平,以适应不断变化的开发需求。

希望本文能够帮助读者更好地理解和应用 Java 中的流、Lambda 表达式和 Spring 框架,在实际开发中取得更好的效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值