6. Java 注解
理解注解(Annotation)概念和作用
-
概念: 注解是 Java 中的一种元数据机制,用于在代码中嵌入描述信息。
-
作用: 注解用于文档化、编译时检查和运行时处理。
掌握元注解(@Target、@Retention、@Repeatable、@Inherited、@Report、@Documented)
-
@Target
作用: 指定注解可以应用的 Java 元素类型(如类、方法、字段等)。
用法:
@Target
注解用于标注其他注解,定义该注解的使用范围。import java.lang.annotation.ElementType; import java.lang.annotation.Target; @Target(ElementType.METHOD) public @interface MyMethodAnnotation { String description() default ""; }
ElementType.TYPE
: 类、接口或枚举ElementType.FIELD
: 字段ElementType.METHOD
: 方法ElementType.PARAMETER
: 参数ElementType.CONSTRUCTOR
: 构造函数ElementType.LOCAL_VARIABLE
: 局部变量ElementType.ANNOTATION_TYPE
: 注解类型ElementType.PACKAGE
: 包 -
@Retention
作用: 指定注解的生命周期,决定了注解在何时可用(即在源代码中、编译时、运行时)。
用法:
@Retention
注解用于标注其他注解,定义注解的保留策略。import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface MyRuntimeAnnotation { String value() default ""; }
RetentionPolicy.SOURCE
: 注解仅保留在源代码中,编译后丢弃。RetentionPolicy.CLASS
: 注解在编译时保留在.class
文件中,但运行时不可用。RetentionPolicy.RUNTIME
: 注解在运行时可用,可以通过反射读取。 -
@Repeatable
作用: 允许同一个注解在同一目标上多次出现。
用法:
@Repeatable
注解用于标记允许重复的注解,配合容器注解使用。import java.lang.annotation.Repeatable; @Repeatable(MyAnnotations.class) public @interface MyAnnotation { String value() default ""; } import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotations { MyAnnotation[] value(); }
@MyAnnotation("first") @MyAnnotation("second") public class MyClass { }
-
@Inherited
作用: 指定注解是否可以被子类继承。
用法:
@Inherited
注解用于标记其他注解,指示子类是否继承父类的注解。import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface MyInheritedAnnotation { String value() default ""; } @MyInheritedAnnotation("parent") public class ParentClass { } public class ChildClass extends ParentClass { } // ChildClass 会继承 ParentClass 上的 MyInheritedAnnotation 注解。
-
@Documented
作用: 指定注解是否包含在 Javadoc 文档中。
用法:
@Documented
注解用于标记其他注解,指示该注解是否应该被包含在 Javadoc 中。import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyDocumentedAnnotation { String description() default ""; }
-
@Report
注意:
@Report
不是 Java 标准库中的元注解。你可能会见到这种注解在特定的框架或工具中,但它不是 Java 语言本身的一部分。常见的元注解如上所述。
掌握 Java 内置常见注解的参数、用途、应用场景,包括:@0verride、@Deprecated、@SuppressWarnings等
@Override
: 确保方法正确重写父类方法。@Deprecated
: 标记不再推荐使用的类或方法。@SuppressWarnings
: 抑制特定的编译警告。@FunctionalInterface
: 标记函数式接口,支持 Lambda 表达式。
掌握自定义注解,包括注解声明和元素定义。
定义无默认值的元素
public @interface MyAnnotation {
String value(); // 需要提供值的元素
}
使用
@MyAnnotation("example")
public class MyClass {
}
使用 Java 反射机制在运行时读取自定义注解的信息。
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
public class AnnotationProcessor {
public static void main(String[] args) throws Exception {
Method method = MyClass.class.getMethod("myMethod");
if (method.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
System.out.println("Value: " + annotation.value());
}
}
}
掌握注解在 AOP 中的应用,比如配置切面日志。
使用 Spring AOP 配置切面日志
-
添加 Spring AOP 依赖
<dependencies> <!-- Spring AOP --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <!-- AspectJ --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>${aspectj.version}</version> </dependency> </dependencies>
-
定义切面
首先定义一个切面类,使用
@Aspect
注解标记该类,并定义日志记录的通知(Advice)import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class LoggingAspect { @Before("execution(* com.example.service.*.*(..))") public void logBefore(JoinPoint joinPoint) { System.out.println("Before method: " + joinPoint.getSignature().getName()); } @After("execution(* com.example.service.*.*(..))") public void logAfter(JoinPoint joinPoint) { System.out.println("After method: " + joinPoint.getSignature().getName()); } }
-
启用 AOP
在 Spring 配置中启用 AOP 支持。可以使用 Java 配置或 XML 配置。
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration @EnableAspectJAutoProxy public class AppConfig { @Bean public LoggingAspect loggingAspect() { return new LoggingAspect(); } // 其他 Bean 配置 }
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <aop:aspectj-autoproxy/> <bean id="loggingAspect" class="com.example.aspect.LoggingAspect"/> <!-- 其他 Bean 配置 --> </beans>
-
测试切面
创建一个服务类,并在方法中添加一些业务逻辑:
package com.example.service; import org.springframework.stereotype.Service; @Service public class MyService { public void performOperation() { System.out.println("Performing operation..."); } }
-
自定义注解
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { }
使用自定义注解的切面
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component public class CustomLoggingAspect { @Pointcut("@annotation(com.example.annotation.Loggable)") public void loggableMethods() { } @Before("loggableMethods()") public void logBefore(JoinPoint joinPoint) { System.out.println("Before method: " + joinPoint.getSignature().getName()); } }
使用自定义注解
package com.example.service; import org.springframework.stereotype.Service; import com.example.annotation.Loggable; @Service public class MyService { @Loggable public void performOperation() { System.out.println("Performing operation..."); } }
7. Java 数据库编程和 I/0 编程
理解 JDBC 概述和作用
-
JDBC 概述
JDBC 是一个 Java 标准接口,用于独立于数据库的操作。JDBC API 定义了一组接口和类,数据库厂商可以提供这些接口和类的具体实现,从而使 Java 应用程序能够与数据库进行交互。
-
JDBC 的作用
- 数据库连接: 通过 JDBC,Java 程序可以与各种数据库(如 MySQL、Oracle、PostgreSQL、SQL Server 等)建立连接。
- 执行 SQL 语句: 可以通过 JDBC 执行 SQL 查询、插入、更新和删除操作。
- 处理结果集: 通过 JDBC,Java 程序可以处理从数据库查询中返回的结果集。
- 事务管理: JDBC 支持事务管理,可以通过编程控制事务的提交和回滚。
- 数据库元数据: JDBC 提供了获取数据库结构和表信息的能力,比如获取数据库的表、列、数据类型等。
掌握 Connection 接口及其方法
-
Connection
接口定义了与数据库连接相关的操作方法,包括创建 SQL 语句对象、管理事务、设置连接属性等。 -
Connection
接口中一些常用的方法-
创建语句对象
-
Statement createStatement()
创建一个Statement
对象,用于执行不带参数的 SQL 语句。Statement stmt = connection.createStatement();
-
PreparedStatement prepareStatement(String sql)
创建一个PreparedStatement
对象,用于执行带参数的 SQL 语句。PreparedStatement pstmt = connection.prepareStatement("SELECT * FROM users WHERE id = ?");
-
CallableStatement prepareCall(String sql)
创建一个CallableStatement
对象,用于执行存储过程。CallableStatement cstmt = connection.prepareCall("{call myStoredProcedure(?, ?)}");
-
-
事务管理
-
void setAutoCommit(boolean autoCommit)
设置是否自动提交事务。true
表示自动提交,false
表示手动提交。connection.setAutoCommit(false);
-
void commit()
提交当前事务。connection.commit();
-
void rollback()
回滚当前事务。connection.rollback();
-
void rollback(Savepoint savepoint)
回滚到指定的保存点。Savepoint savepoint = connection.setSavepoint(); connection.rollback(savepoint);
-
Savepoint setSavepoint()
创建一个未命名的保存点。Savepoint savepoint = connection.setSavepoint();
-
Savepoint setSavepoint(String name)
创建一个带名称的保存点。Savepoint savepoint = connection.setSavepoint("Savepoint1");
-
-
连接状态
-
boolean isClosed()
检查连接是否已关闭。if (connection.isClosed()) { System.out.println("Connection is closed"); }
-
boolean isValid(int timeout)
检查连接是否在指定的超时时间内有效。if (connection.isValid(10)) { System.out.println("Connection is valid"); }
-
-
设置和获取属性
-
void setReadOnly(boolean readOnly)
设置连接是否为只读模式。connection.setReadOnly(true);
-
boolean isReadOnly()
检查连接是否为只读模式。boolean readOnly = connection.isReadOnly();
-
void setCatalog(String catalog)
设置当前的目录名称。connection.setCatalog("myCatalog");
-
String getCatalog()
获取当前的目录名称。String catalog = connection.getCatalog();
-
void setTransactionIsolation(int level)
设置事务隔离级别。connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
-
int getTransactionIsolation()
获取当前的事务隔离级别。int isolationLevel = connection.getTransactionIsolation();
-
void setSchema(String schema)
设置当前的模式名称。connection.setSchema("mySchema");
-
String getSchema()
获取当前的模式名称。String schema = connection.getSchema();
-
-
关闭连接
-
void close()
关闭数据库连接并释放资源。connection.close();
-
-
其他方法
-
DatabaseMetaData getMetaData()
获取数据库的元数据。DatabaseMetaData metaData = connection.getMetaData();
-
void setHoldability(int holdability)
设置结果集的可保持性。connection.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
-
int getHoldability()
获取结果集的可保持性。int holdability = connection.getHoldability();
-
Map<String, Class<?>> getTypeMap()
获取用户定义的类型映射。Map<String, Class<?>> typeMap = connection.getTypeMap();
-
void setTypeMap(Map<String, Class<?>> map)
设置用户定义的类型映射。connection.setTypeMap(myTypeMap);
-
-
掌握 Statement 接口及其方法
-
Statement
接口提供了一组方法,用于执行 SQL 语句并处理查询结果。常见的 SQL 操作包括查询、插入、更新和删除数据。 -
常用方法
-
执行 SQL 语句
-
boolean execute(String sql)
执行给定的 SQL 语句,返回一个布尔值,表示执行的结果是一个ResultSet
对象(true)还是一个更新计数或没有结果(false)。boolean hasResultSet = statement.execute("SELECT * FROM users");
-
ResultSet executeQuery(String sql)
执行给定的 SQL 查询,并返回一个ResultSet
对象,该对象包含查询结果。ResultSet rs = statement.executeQuery("SELECT * FROM users");
-
int executeUpdate(String sql)
执行给定的 SQL DML 语句(例如,INSERT、UPDATE 或 DELETE 语句),或 DDL 语句(例如,CREATE TABLE 和 DROP TABLE 语句),并返回受影响的行数。int rowsAffected = statement.executeUpdate("UPDATE users SET name='John' WHERE id=1");
-
int[] executeBatch()
执行一批命令,并返回一个包含批处理中每个命令结果的数组。statement.addBatch("INSERT INTO users (name) VALUES ('Alice')"); statement.addBatch("INSERT INTO users (name) VALUES ('Bob')"); int[] batchResults = statement.executeBatch();
-
void addBatch(String sql)
将给定的 SQL 命令添加到当前批处理中。statement.addBatch("INSERT INTO users (name) VALUES ('Alice')");
-
void clearBatch()
清除当前批处理中的所有命令。statement.clearBatch();
-
-
结果集处理
-
ResultSet getResultSet()
获取上一个执行的 SQL 语句生成的ResultSet
对象(如果存在)。ResultSet rs = statement.getResultSet();
-
int getUpdateCount()
获取上一个执行的 SQL 语句所影响的行数(如果存在)。int updateCount = statement.getUpdateCount();
-
boolean getMoreResults()
将Statement
对象移动到下一个结果,返回一个布尔值,表示结果是一个ResultSet
对象(true)还是一个更新计数或没有结果(false)。boolean moreResults = statement.getMoreResults();
-
关闭和取消
-
void close()
关闭Statement
对象,并释放其持有的所有资源。statement.close();
-
void cancel()
取消正在执行的 SQL 语句。statement.cancel();
-
-
其他方法
-
void setFetchSize(int rows)
设置从数据库获取行的建议行数。statement.setFetchSize(50);
-
int getFetchSize()
获取从数据库获取行的建议行数。int fetchSize = statement.getFetchSize();
-
void setMaxRows(int max)
设置此Statement
对象生成的ResultSet
对象可以包含的最大行数。statement.setMaxRows(100);
-
int getMaxRows()
获取此Statement
对象生成的ResultSet
对象可以包含的最大行数。int maxRows = statement.getMaxRows();
-
void setQueryTimeout(int seconds)
设置驱动程序等待Statement
对象执行的秒数。statement.setQueryTimeout(30);
-
int getQueryTimeout()
获取驱动程序等待Statement
对象执行的秒数。int queryTimeout = statement.getQueryTimeout();
-
示例
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class StatementDemo { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/mydatabase"; String user = "root"; String password = "password"; try (Connection connection = DriverManager.getConnection(url, user, password); Statement statement = connection.createStatement()) { // 执行查询 String query = "SELECT id, name, email FROM users"; ResultSet resultSet = statement.executeQuery(query); while (resultSet.next()) { int id = resultSet.getInt("id"); String name = resultSet.getString("name"); String email = resultSet.getString("email"); System.out.println("ID: " + id + ", Name: " + name + ", Email: " + email); } // 执行更新 String update = "UPDATE users SET name='John' WHERE id=1"; int rowsAffected = statement.executeUpdate(update); System.out.println("Rows affected: " + rowsAffected); } catch (SQLException e) { e.printStackTrace(); } } }
-
掌握 PreparedStatement 接口及其方法
-
PreparedStatement
接口是Statement
接口的子接口,SQL 语句在创建PreparedStatement
对象时就已经预编译,这使得它比普通的Statement
更高效。使用PreparedStatement
,可以避免 SQL 注入攻击,因为参数是通过安全的方式传递的。 -
常用方法
-
设置参数
-
void setInt(int parameterIndex, int x)
设置指定参数的int
值。parameterIndex
是参数的索引(从 1 开始),x
是要设置的值。preparedStatement.setInt(1, 123);
-
void setString(int parameterIndex, String x)
设置指定参数的String
值。preparedStatement.setString(2, "John Doe");
-
void setDouble(int parameterIndex, double x)
设置指定参数的double
值。preparedStatement.setDouble(3, 45.67);
-
void setBoolean(int parameterIndex, boolean x)
设置指定参数的boolean
值。preparedStatement.setBoolean(4, true);
-
void setDate(int parameterIndex, java.sql.Date x)
设置指定参数的Date
值。preparedStatement.setDate(5, java.sql.Date.valueOf("2023-07-29"));
-
void setTimestamp(int parameterIndex, java.sql.Timestamp x)
设置指定参数的Timestamp
值。preparedStatement.setTimestamp(6, java.sql.Timestamp.valueOf("2023-07-29 10:10:10"));
-
-
执行 SQL 语句
-
boolean execute()
执行预编译的 SQL 语句,返回一个布尔值,表示执行的结果是一个ResultSet
对象(true)还是一个更新计数或没有结果(false)。boolean hasResultSet = preparedStatement.execute();
-
ResultSet executeQuery()
执行预编译的 SQL 查询,返回一个ResultSet
对象,该对象包含查询结果。ResultSet rs = preparedStatement.executeQuery();
-
int executeUpdate()
执行预编译的 SQL DML 语句(例如,INSERT、UPDATE 或 DELETE 语句),返回受影响的行数。int rowsAffected = preparedStatement.executeUpdate();
-
int[] executeBatch()
执行一批命令,并返回一个包含批处理中每个命令结果的数组。int[] batchResults = preparedStatement.executeBatch();
-
-
管理批处理
-
void addBatch()
将当前的参数设置添加到此PreparedStatement
对象的批处理中。preparedStatement.addBatch();
-
void clearBatch()
清除当前PreparedStatement
对象的所有批处理命令。preparedStatement.clearBatch();
-
-
清除参数
-
void clearParameters()
清除当前参数设置。preparedStatement.clearParameters();
-
-
-
示例
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class PreparedStatementDemo { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/mydatabase"; String user = "root"; String password = "password"; try (Connection connection = DriverManager.getConnection(url, user, password)) { // 创建预编译的 SQL 语句 String selectSQL = "SELECT id, name, email FROM users WHERE id = ?"; try (PreparedStatement preparedStatement = connection.prepareStatement(selectSQL)) { preparedStatement.setInt(1, 1); // 执行查询 ResultSet resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { int id = resultSet.getInt("id"); String name = resultSet.getString("name"); String email = resultSet.getString("email"); System.out.println("ID: " + id + ", Name: " + name + ", Email: " + email); } } // 创建预编译的 SQL 更新语句 String updateSQL = "UPDATE users SET name = ? WHERE id = ?"; try (PreparedStatement preparedStatement = connection.prepareStatement(updateSQL)) { preparedStatement.setString(1, "John Doe"); preparedStatement.setInt(2, 1); // 执行更新 int rowsAffected = preparedStatement.executeUpdate(); System.out.println("Rows affected: " + rowsAffected); } } catch (SQLException e) { e.printStackTrace(); } } }
掌握如何执行 SQL 查询更新操作
-
使用
Statement
执行- 加载了 JDBC
- 使用
DriverManager
获取与数据库的连接。 - 通过
Connection
对象创建一个Statement
对象。 - 使用
executeQuery
方法执行查询操作,使用executeUpdate
方法执行更新操作。 - 如果是查询操作,处理返回的
ResultSet
。 - 关闭
ResultSet
、Statement
和Connection
以释放资源。
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class StatementExample { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/mydatabase"; String user = "root"; String password = "password"; Connection connection = null; Statement statement = null; ResultSet resultSet = null; try { // 1. 建立连接 connection = DriverManager.getConnection(url, user, password); // 2. 创建 Statement 对象 statement = connection.createStatement(); // 3. 执行查询操作 String query = "SELECT id, name, email FROM users"; resultSet = statement.executeQuery(query); // 4. 处理结果集 while (resultSet.next()) { int id = resultSet.getInt("id"); String name = resultSet.getString("name"); String email = resultSet.getString("email"); System.out.println("ID: " + id + ", Name: " + name + ", Email: " + email); } // 5. 执行更新操作 String update = "UPDATE users SET name='John Doe' WHERE id=1"; int rowsAffected = statement.executeUpdate(update); System.out.println("Rows affected: " + rowsAffected); } catch (SQLException e) { e.printStackTrace(); } finally { // 6. 关闭资源 try { if (resultSet != null) resultSet.close(); if (statement != null) statement.close(); if (connection != null) connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
-
使用
PreparedStatement
执行- 加载了 JDBC
- 使用
DriverManager
获取与数据库的连接。 - 通过
Connection
对象创建一个PreparedStatement
对象,并传递预编译的 SQL 语句。 - 使用
setXxx
方法为 SQL 语句中的参数设置值。 - 使用
executeQuery
方法执行查询操作,使用executeUpdate
方法执行更新操作。 - 如果是查询操作,处理返回的
ResultSet
。 - 关闭
ResultSet
、PreparedStatement
和Connection
以释放资源。
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class PreparedStatementExample { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/mydatabase"; String user = "root"; String password = "password"; Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { // 1. 建立连接 connection = DriverManager.getConnection(url, user, password); // 2. 创建 PreparedStatement 对象 String query = "SELECT id, name, email FROM users WHERE id = ?"; preparedStatement = connection.prepareStatement(query); // 3. 设置参数 preparedStatement.setInt(1, 1); // 4. 执行查询操作 resultSet = preparedStatement.executeQuery(); // 5. 处理结果集 while (resultSet.next()) { int id = resultSet.getInt("id"); String name = resultSet.getString("name"); String email = resultSet.getString("email"); System.out.println("ID: " + id + ", Name: " + name + ", Email: " + email); } // 6. 创建 PreparedStatement 对象用于更新操作 String update = "UPDATE users SET name = ? WHERE id = ?"; preparedStatement = connection.prepareStatement(update); // 7. 设置参数 preparedStatement.setString(1, "Jane Doe"); preparedStatement.setInt(2, 1); // 8. 执行更新操作 int rowsAffected = preparedStatement.executeUpdate(); System.out.println("Rows affected: " + rowsAffected); } catch (SQLException e) { e.printStackTrace(); } finally { // 9. 关闭资源 try { if (resultSet != null) resultSet.close(); if (preparedStatement != null) preparedStatement.close(); if (connection != null) connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
掌握 ResultSet 接口及其方法
-
ResultSet
接口是 JDBC 中用于处理 SQL 查询结果集的接口,可以遍历和操作查询返回的数据。它通常是通过Statement
或PreparedStatement
对象的executeQuery
方法生成的。 -
常用方法:
-
遍历结果集
-
boolean next()
将光标移动到结果集的下一行。如果有下一行,返回true
;如果没有更多行,返回false
。while (resultSet.next()) { int id = resultSet.getInt("id"); String name = resultSet.getString("name"); }
-
boolean previous()
将光标移动到结果集的上一行。如果有上一行,返回true
;如果没有更多行,返回false
。while (resultSet.previous()) { int id = resultSet.getInt("id"); String name = resultSet.getString("name"); }
-
boolean first()
将光标移动到结果集的第一行。如果成功,返回true
;否则,返回false
。if (resultSet.first()) { int id = resultSet.getInt("id"); String name = resultSet.getString("name"); }
-
boolean last()
将光标移动到结果集的最后一行。如果成功,返回true
;否则,返回false
。if (resultSet.last()) { int id = resultSet.getInt("id"); String name = resultSet.getString("name"); }
-
boolean absolute(int row)
将光标移动到指定的行。如果成功,返回true
;否则,返回false
。if (resultSet.absolute(5)) { int id = resultSet.getInt("id"); String name = resultSet.getString("name"); }
-
boolean relative(int rows)
将光标相对于当前位置向前或向后移动指定的行数。如果成功,返回true
;否则,返回false
。if (resultSet.relative(2)) { int id = resultSet.getInt("id"); String name = resultSet.getString("name"); }
-
void beforeFirst()
将光标移动到结果集的开头,前面没有数据行。resultSet.beforeFirst();
-
void afterLast()
将光标移动到结果集的末尾,后面没有数据行。resultSet.afterLast();
-
-
获取数据
-
int getInt(String columnLabel)
获取当前行指定列的int
值。int id = resultSet.getInt("id");
-
int getInt(int columnIndex)
获取当前行指定列的int
值。int id = resultSet.getInt(1);
-
String getString(String columnLabel)
获取当前行指定列的String
值。String name = resultSet.getString("name");
-
String getString(int columnIndex)
获取当前行指定列的String
值。String name = resultSet.getString(2);
-
boolean getBoolean(String columnLabel)
获取当前行指定列的boolean
值。boolean isActive = resultSet.getBoolean("active");
-
boolean getBoolean(int columnIndex)
获取当前行指定列的boolean
值。boolean isActive = resultSet.getBoolean(3);
-
double getDouble(String columnLabel)
获取当前行指定列的double
值。double salary = resultSet.getDouble("salary");
-
double getDouble(int columnIndex)
获取当前行指定列的double
值。double salary = resultSet.getDouble(4);
-
-
更新数据
-
void updateInt(String columnLabel, int x)
更新当前行指定列的int
值。resultSet.updateInt("age", 30);
-
void updateInt(int columnIndex, int x)
更新当前行指定列的int
值。resultSet.updateInt(5, 30);
-
void updateString(String columnLabel, String x)
更新当前行指定列的String
值。resultSet.updateString("name", "Jane Doe");
-
void updateString(int columnIndex, String x)
更新当前行指定列的String
值。resultSet.updateString(6, "Jane Doe");
-
void insertRow()
插入新行到结果集中。一般和moveToInsertRow
方法一起使用。resultSet.moveToInsertRow(); resultSet.updateInt("id", 7); resultSet.updateString("name", "New User"); resultSet.insertRow(); resultSet.moveToCurrentRow();
-
void updateRow()
更新当前行到数据库。resultSet.updateRow();
-
void deleteRow()
删除当前行。resultSet.deleteRow();
-
void refreshRow()
刷新当前行的数据。resultSet.refreshRow();
-
void moveToInsertRow()
移动到可以插入新行的特殊行。resultSet.moveToInsertRow();
-
void moveToCurrentRow()
移动回当前行。resultSet.moveToCurrentRow();
-
-
其他方法
-
int findColumn(String columnLabel)
获取指定列的索引。int columnIndex = resultSet.findColumn("name");
-
boolean isBeforeFirst()
判断光标是否位于第一行之前。boolean isBeforeFirst = resultSet.isBeforeFirst();
-
boolean isAfterLast()
判断光标是否位于最后一行之后。boolean isAfterLast = resultSet.isAfterLast();
-
boolean isFirst()
判断光标是否位于第一行。boolean isFirst = resultSet.isFirst();
-
boolean isLast()
判断光标是否位于最后一行。boolean isLast = resultSet.isLast();
-
int getRow()
获取当前行的行号。int row = resultSet.getRow();
-
-
-
实例
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class ResultSetExample { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/mydatabase"; String user = "root"; String password = "password"; Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { // 建立连接 connection = DriverManager.getConnection(url, user, password); // 创建 PreparedStatement 对象 String query = "SELECT id, name, email FROM users"; preparedStatement = connection.prepareStatement(query); // 执行查询操作 resultSet = preparedStatement.executeQuery(); // 遍历结果集 while (resultSet.next()) { int id = resultSet.getInt("id"); String name = resultSet.getString("name"); String email = resultSet.getString("email"); System.out.println("ID: " + id + ", Name: " + name + ", Email: " + email); } // 更新结果集 if (resultSet.last()) { resultSet.updateString("name", "Updated Name"); resultSet.updateRow(); } // 插入新行 resultSet.moveToInsertRow(); resultSet.updateInt("id", 4); resultSet.updateString("name", "New User"); resultSet.updateString("email", "newuser@example.com"); resultSet.insertRow(); resultSet.moveToCurrentRow(); } catch (SQLException e) { e.printStackTrace(); } finally { // 关闭资源 try { if (resultSet != null) resultSet.close(); if (preparedStatement != null) preparedStatement.close(); if (connection != null) connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
掌握批处理和事务管理
-
批处理:通过
Statement
或PreparedStatement
对象的addBatch
和executeBatch
方法执行批量操作。import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; public class PreparedStatementBatchExample { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/mydatabase"; String user = "root"; String password = "password"; Connection connection = null; PreparedStatement preparedStatement = null; try { // 建立连接 connection = DriverManager.getConnection(url, user, password); // 关闭自动提交 connection.setAutoCommit(false); // 创建 PreparedStatement 对象 String sql = "INSERT INTO users (name, email) VALUES (?, ?)"; preparedStatement = connection.prepareStatement(sql); // 添加批处理语句 preparedStatement.setString(1, "User1"); preparedStatement.setString(2, "user1@example.com"); preparedStatement.addBatch(); preparedStatement.setString(1, "User2"); preparedStatement.setString(2, "user2@example.com"); preparedStatement.addBatch(); preparedStatement.setString(1, "User3"); preparedStatement.setString(2, "user3@example.com"); preparedStatement.addBatch(); // 执行批处理 int[] updateCounts = preparedStatement.executeBatch(); // 提交事务 connection.commit(); System.out.println("Batch executed successfully!"); } catch (SQLException e) { e.printStackTrace(); try { if (connection != null) { // 回滚事务 connection.rollback(); } } catch (SQLException ex) { ex.printStackTrace(); } } finally { try { if (preparedStatement != null) preparedStatement.close(); if (connection != null) connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
-
事务管理:通过关闭自动提交模式、显式提交事务和在异常情况下回滚事务,确保一组操作的原子性。
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; public class TransactionManagementExample { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/mydatabase"; String user = "root"; String password = "password"; Connection connection = null; PreparedStatement preparedStatement = null; try { // 建立连接 connection = DriverManager.getConnection(url, user, password); // 关闭自动提交 connection.setAutoCommit(false); // 创建 PreparedStatement 对象 String sql = "UPDATE accounts SET balance = balance - ? WHERE account_id = ?"; preparedStatement = connection.prepareStatement(sql); // 执行第一个更新操作 preparedStatement.setDouble(1, 100.00); preparedStatement.setInt(2, 1); preparedStatement.executeUpdate(); // 执行第二个更新操作 preparedStatement.setDouble(1, 100.00); preparedStatement.setInt(2, 2); preparedStatement.executeUpdate(); // 提交事务 connection.commit(); System.out.println("Transaction committed successfully!"); } catch (SQLException e) { e.printStackTrace(); try { if (connection != null) { // 回滚事务 connection.rollback(); System.out.println("Transaction rolled back!"); } } catch (SQLException ex) { ex.printStackTrace(); } } finally { try { if (preparedStatement != null) preparedStatement.close(); if (connection != null) connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
掌握异常处理和资源关闭
-
异常处理
在 JDBC 编程中,常见的异常包括
SQLException
和SQLTimeoutException
。这些异常需要进行适当的捕获和处理。import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; public class ExceptionHandlingExample { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/mydatabase"; String user = "root"; String password = "password"; Connection connection = null; PreparedStatement preparedStatement = null; try { // 建立连接 connection = DriverManager.getConnection(url, user, password); // 创建 PreparedStatement 对象 String sql = "INSERT INTO users (name, email) VALUES (?, ?)"; preparedStatement = connection.prepareStatement(sql); // 设置参数并执行更新操作 preparedStatement.setString(1, "User1"); preparedStatement.setString(2, "user1@example.com"); preparedStatement.executeUpdate(); } catch (SQLException e) { // 捕获并处理 SQL 异常 System.err.println("SQL error occurred: " + e.getMessage()); e.printStackTrace(); } catch (Exception e) { // 捕获并处理其他异常 System.err.println("Unexpected error occurred: " + e.getMessage()); e.printStackTrace(); } finally { // 确保资源在 finally 块中关闭 try { if (preparedStatement != null) preparedStatement.close(); if (connection != null) connection.close(); } catch (SQLException e) { System.err.println("Error closing resources: " + e.getMessage()); e.printStackTrace(); } } } }
-
资源关闭
JDBC 操作涉及的资源包括
Connection
、Statement
、PreparedStatement
和ResultSet
对象。这些资源在使用完毕后需要关闭,以释放数据库连接和其他资源。使用
try-with-resources
语句import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class TryWithResourcesExample { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/mydatabase"; String user = "root"; String password = "password"; // 使用 try-with-resources 语句自动关闭资源 try (Connection connection = DriverManager.getConnection(url, user, password); PreparedStatement preparedStatement = connection.prepareStatement("SELECT id, name, email FROM users"); ResultSet resultSet = preparedStatement.executeQuery()) { // 遍历结果集 while (resultSet.next()) { int id = resultSet.getInt("id"); String name = resultSet.getString("name"); String email = resultSet.getString("email"); System.out.println("ID: " + id + ", Name: " + name + ", Email: " + email); } } catch (SQLException e) { // 捕获并处理 SQL 异常 System.err.println("SQL error occurred: " + e.getMessage()); e.printStackTrace(); } } }
-
事务管理中的资源关闭
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; public class TransactionManagementExample { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/mydatabase"; String user = "root"; String password = "password"; Connection connection = null; PreparedStatement preparedStatement = null; try { // 建立连接 connection = DriverManager.getConnection(url, user, password); // 关闭自动提交 connection.setAutoCommit(false); // 创建 PreparedStatement 对象 String sql = "UPDATE accounts SET balance = balance - ? WHERE account_id = ?"; preparedStatement = connection.prepareStatement(sql); // 执行第一个更新操作 preparedStatement.setDouble(1, 100.00); preparedStatement.setInt(2, 1); preparedStatement.executeUpdate(); // 执行第二个更新操作 preparedStatement.setDouble(1, 100.00); preparedStatement.setInt(2, 2); preparedStatement.executeUpdate(); // 提交事务 connection.commit(); System.out.println("Transaction committed successfully!"); } catch (SQLException e) { // 捕获并处理 SQL 异常 System.err.println("SQL error occurred: " + e.getMessage()); e.printStackTrace(); try { if (connection != null) { // 回滚事务 connection.rollback(); System.out.println("Transaction rolled back!"); } } catch (SQLException ex) { ex.printStackTrace(); } } finally { // 确保资源在 finally 块中关闭 try { if (preparedStatement != null) preparedStatement.close(); if (connection != null) connection.close(); } catch (SQLException e) { System.err.println("Error closing resources: " + e.getMessage()); e.printStackTrace(); } } } }
理解 BLOB 和 CLOB 数据类型
-
BLOB(Binary Large Object)
- 用途:存储二进制数据,如图像、视频、音频、PDF 文件等。
- 类型:包括 TINYBLOB、BLOB、MEDIUMBLOB 和 LONGBLOB,分别用于存储不同大小的数据。
- 最大容量:
- TINYBLOB: 255 bytes
- BLOB: 65,535 bytes (64 KB)
- MEDIUMBLOB: 16,777,215 bytes (16 MB)
- LONGBLOB: 4,294,967,295 bytes (4 GB)
-
CLOB(Character Large Object)
- 用途:存储大量文本数据,如 HTML、XML 文档、大段文字等。
- 类型:包括 TINYTEXT、TEXT、MEDIUMTEXT 和 LONGTEXT,分别用于存储不同大小的数据。
- 最大容量:
- TINYTEXT: 255 bytes
- TEXT: 65,535 bytes (64 KB)
- MEDIUMTEXT: 16,777,215 bytes (16 MB)
- LONGTEXT: 4,294,967,295 bytes (4 GB)
-
存储和读取 BLOB 数据
建表
CREATE TABLE files ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, data LONGBLOB NOT NULL );
存储
import java.io.FileInputStream; import java.io.IOException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; public class BlobExample { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/mydatabase"; String user = "root"; String password = "password"; String filePath = "path/to/your/file"; try (Connection connection = DriverManager.getConnection(url, user, password); PreparedStatement statement = connection.prepareStatement("INSERT INTO files (name, data) VALUES (?, ?)")) { statement.setString(1, "example.pdf"); try (FileInputStream inputStream = new FileInputStream(filePath)) { statement.setBinaryStream(2, inputStream, (int) filePath.length()); statement.executeUpdate(); System.out.println("File has been uploaded successfully."); } catch (IOException e) { e.printStackTrace(); } } catch (SQLException e) { e.printStackTrace(); } } }
读取
import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class BlobReadExample { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/mydatabase"; String user = "root"; String password = "password"; String outputPath = "path/to/your/output/file"; try (Connection connection = DriverManager.getConnection(url, user, password); PreparedStatement statement = connection.prepareStatement("SELECT name, data FROM files WHERE id = ?")) { statement.setInt(1, 1); try (ResultSet resultSet = statement.executeQuery()) { if (resultSet.next()) { String fileName = resultSet.getString("name"); InputStream inputStream = resultSet.getBinaryStream("data"); try (FileOutputStream outputStream = new FileOutputStream(outputPath + fileName)) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } System.out.println("File has been downloaded successfully."); } catch (IOException e) { e.printStackTrace(); } } } } catch (SQLException e) { e.printStackTrace(); } } }
-
存储和读取 CLOB 数据
建表
CREATE TABLE documents ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, content LONGTEXT NOT NULL );
存储
import java.io.FileReader; import java.io.IOException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; public class ClobExample { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/mydatabase"; String user = "root"; String password = "password"; String filePath = "path/to/your/textfile.txt"; try (Connection connection = DriverManager.getConnection(url, user, password); PreparedStatement statement = connection.prepareStatement("INSERT INTO documents (name, content) VALUES (?, ?)")) { statement.setString(1, "example.txt"); try (FileReader reader = new FileReader(filePath)) { statement.setCharacterStream(2, reader); statement.executeUpdate(); System.out.println("Document has been uploaded successfully."); } catch (IOException e) { e.printStackTrace(); } } catch (SQLException e) { e.printStackTrace(); } } }
-
读取
import java.io.FileWriter; import java.io.IOException; import java.io.Reader; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class ClobReadExample { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/mydatabase"; String user = "root"; String password = "password"; String outputPath = "path/to/your/output/textfile.txt"; try (Connection connection = DriverManager.getConnection(url, user, password); PreparedStatement statement = connection.prepareStatement("SELECT name, content FROM documents WHERE id = ?")) { statement.setInt(1, 1); try (ResultSet resultSet = statement.executeQuery()) { if (resultSet.next()) { String fileName = resultSet.getString("name"); Reader reader = resultSet.getCharacterStream("content"); try (FileWriter writer = new FileWriter(outputPath + fileName)) { char[] buffer = new char[1024]; int charsRead; while ((charsRead = reader.read(buffer)) != -1) { writer.write(buffer, 0, charsRead); } System.out.println("Document has been downloaded successfully."); } catch (IOException e) { e.printStackTrace(); } } } } catch (SQLException e) { e.printStackTrace(); } } }
理解连接池作用
连接池通过重复利用数据库连接、限制连接数量和自动管理连接的生命周期,显著提高了数据库操作的效率和应用程序的性能。使用连接池可以减少连接创建和销毁的开销,提高资源的利用率,并提升应用程序的响应能力。
理解流的概念和作用
- 流 是处理集合数据的高级抽象,提供了简洁和高效的数据处理方式。
- 作用 包括简化数据处理、提高代码可读性、支持函数式编程、性能优化和并行处理。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
List<String> result = names.stream()
.filter(name -> name.startsWith("A")) // 过滤以 "A" 开头的名字
.map(String::toUpperCase) // 转换为大写
.sorted() // 排序
.collect(Collectors.toList()); // 收集到列表
System.out.println(result); // 输出: [ALICE]
}
}
理解字节流和字符流的区别
- 数据处理:
- 字节流:处理原始字节,适用于所有类型的数据(包括二进制数据)。
- 字符流:处理字符数据,专为文本数据设计。
- 基本类:
- 字节流:
InputStream
和OutputStream
。 - 字符流:
Reader
和Writer
。
- 字节流:
- 数据编码:
- 字节流:不涉及字符编码和解码。
- 字符流:自动处理字符编码和解码(通常使用 UTF-8 或其他编码)。
- 适用场景:
- 字节流:适用于二进制数据的读写。
- 字符流:适用于文本数据的读写。
- 性能:
- 字节流:由于直接操作字节,通常适用于性能要求较高的场景。
- 字符流:因为处理的是字符,适用于文本处理,但字符流可以在内部使用缓冲来提高性能。
理解输入流和输出流的概念
-
输入流(Input Stream)
处理从数据源读取数据的操作,适用于需要从文件或其他数据源获取数据的情况。
-
输出流(Output Stream)
处理将数据写入目标的操作,适用于需要将数据保存到文件或发送到其他目标的情况。
-
共同点和区别
- 共同点:
- 都是 Java I/O(输入/输出)库的一部分。
- 都继承自
java.io.InputStream
或java.io.OutputStream
。 - 都需要在操作完成后关闭,以释放资源。
- 区别:
- 功能:
- 输入流:用于从数据源读取数据。
- 输出流:用于将数据写入目标。
- 方法:
- 输入流:如
read()
、read(byte[] b)
。 - 输出流:如
write(int b)
、write(byte[] b)
。
- 输入流:如
- 使用场景:
- 输入流:用于读取文件内容、接收网络数据等。
- 输出流:用于写入文件内容、发送网络数据等。
- 功能:
- 共同点:
掌握文件读写的基本操作
-
字节流进行文件读写
读取
import java.io.FileInputStream; import java.io.IOException; public class ByteStreamReadExample { public static void main(String[] args) { try (FileInputStream fis = new FileInputStream("input.bin")) { int byteData; while ((byteData = fis.read()) != -1) { System.out.print((char) byteData); // 输出读取的字节 } } catch (IOException e) { e.printStackTrace(); } } }
写入
import java.io.FileOutputStream; import java.io.IOException; public class ByteStreamWriteExample { public static void main(String[] args) { try (FileOutputStream fos = new FileOutputStream("output.bin")) { String data = "Hello, ByteStream!"; fos.write(data.getBytes()); // 将字符串转换为字节并写入文件 } catch (IOException e) { e.printStackTrace(); } } }
-
字符流进行文件读写
读取
import java.io.FileReader; import java.io.IOException; public class CharacterStreamReadExample { public static void main(String[] args) { try (FileReader fr = new FileReader("input.txt")) { int charData; while ((charData = fr.read()) != -1) { System.out.print((char) charData); // 输出读取的字符 } } catch (IOException e) { e.printStackTrace(); } } }
写入
import java.io.FileWriter; import java.io.IOException; public class CharacterStreamWriteExample { public static void main(String[] args) { try (FileWriter fw = new FileWriter("output.txt")) { String data = "Hello, CharacterStream!"; fw.write(data); // 将字符串写入文件 } catch (IOException e) { e.printStackTrace(); } } }
-
缓冲流
缓冲流读取
import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; public class BufferedInputStreamExample { public static void main(String[] args) { try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("input.bin"))) { int byteData; while ((byteData = bis.read()) != -1) { System.out.print((char) byteData); } } catch (IOException e) { e.printStackTrace(); } } }
缓冲流写入
import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; public class BufferedOutputStreamExample { public static void main(String[] args) { try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("output.bin"))) { String data = "Hello, BufferedOutputStream!"; bos.write(data.getBytes()); } catch (IOException e) { e.printStackTrace(); } } }
-
对象流
对象流写入对象
import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; public class ObjectOutputStreamExample { public static void main(String[] args) { try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.dat"))) { Person person = new Person("John", 30); oos.writeObject(person); } catch (IOException e) { e.printStackTrace(); } } static class Person implements java.io.Serializable { String name; int age; Person(String name, int age) { this.name = name; this.age = age; } } }
对象流读取对象
import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; public class ObjectInputStreamExample { public static void main(String[] args) { try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.dat"))) { Person person = (Person) ois.readObject(); System.out.println("Name: " + person.name + ", Age: " + person.age); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } static class Person implements java.io.Serializable { String name; int age; Person(String name, int age) { this.name = name; this.age = age; } } }
掌握字节流的常用类和方法
-
FileInputStream
- 作用:从文件读取字节数据。
- 构造函数:
FileInputStream(String name)
:创建一个从指定文件读取数据的流。FileInputStream(File file)
:创建一个从指定File
对象读取数据的流。
- 常用方法:
int read()
:读取下一个字节的数据。如果已到达文件末尾,则返回 -1。int read(byte[] b)
:读取一定数量的字节数据到字节数组中。void close()
:关闭流并释放系统资源。
import java.io.FileInputStream; import java.io.IOException; public class FileInputStreamExample { public static void main(String[] args) { try (FileInputStream fis = new FileInputStream("input.bin")) { int byteData; while ((byteData = fis.read()) != -1) { System.out.print((char) byteData); // 输出读取的字节 } } catch (IOException e) { e.printStackTrace(); } } }
-
FileOutputStream
-
作用:向文件写入字节数据。
-
构造函数:
FileOutputStream(String name)
:创建一个将数据写入指定文件的流。FileOutputStream(File file)
:创建一个将数据写入指定File
对象的流。
-
常用方法:
void write(int b)
:写入一个字节的数据。void write(byte[] b)
:写入字节数组中的数据。void close()
:关闭流并释放系统资源。
import java.io.FileOutputStream; import java.io.IOException; public class FileOutputStreamExample { public static void main(String[] args) { try (FileOutputStream fos = new FileOutputStream("output.bin")) { String data = "Hello, ByteStream!"; fos.write(data.getBytes()); // 将字符串转换为字节并写入文件 } catch (IOException e) { e.printStackTrace(); } } }
-
-
BufferedInputStream
- 作用:提供缓冲功能,以提高读取效率。
- 构造函数:
BufferedInputStream(InputStream in)
:创建一个使用默认缓冲区大小的缓冲输入流。
- 常用方法:
int read()
:从缓冲区读取下一个字节的数据。int read(byte[] b)
:从缓冲区读取一定数量的字节数据到字节数组中。void close()
:关闭流并释放系统资源。
import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; public class BufferedInputStreamExample { public static void main(String[] args) { try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("input.bin"))) { int byteData; while ((byteData = bis.read()) != -1) { System.out.print((char) byteData); } } catch (IOException e) { e.printStackTrace(); } } }
-
BufferedOutputStream
- 作用:提供缓冲功能,以提高写入效率。
- 构造函数:
BufferedOutputStream(OutputStream out)
:创建一个使用默认缓冲区大小的缓冲输出流。
- 常用方法:
void write(int b)
:将一个字节的数据写入缓冲区。void write(byte[] b)
:将字节数组中的数据写入缓冲区。void flush()
:强制将缓冲区中的数据写入目标流。void close()
:关闭流并释放系统资源。
import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; public class BufferedOutputStreamExample { public static void main(String[] args) { try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("output.bin"))) { String data = "Hello, BufferedOutputStream!"; bos.write(data.getBytes()); bos.flush(); // 确保所有数据都被写入 } catch (IOException e) { e.printStackTrace(); } } }
-
DataInputStream
- 作用:可以读取原始数据类型(如
int
、float
)并提供方便的读写方法。 - 构造函数:
DataInputStream(InputStream in)
:创建一个从指定输入流中读取原始数据的流。
- 常用方法:
int readInt()
:读取一个int
数据。float readFloat()
:读取一个float
数据。String readUTF()
:读取一个 UTF-8 编码的字符串。
import java.io.DataInputStream; import java.io.FileInputStream; import java.io.IOException; public class DataInputStreamExample { public static void main(String[] args) { try (DataInputStream dis = new DataInputStream(new FileInputStream("data.bin"))) { int number = dis.readInt(); float decimal = dis.readFloat(); String text = dis.readUTF(); System.out.println("Integer: " + number); System.out.println("Float: " + decimal); System.out.println("String: " + text); } catch (IOException e) { e.printStackTrace(); } } }
- 作用:可以读取原始数据类型(如
-
DataOutputStream
- 作用:可以写入原始数据类型,并提供方便的写入方法。
- 构造函数:
DataOutputStream(OutputStream out)
:创建一个将原始数据写入指定输出流的流。
- 常用方法:
void writeInt(int v)
:写入一个int
数据。void writeFloat(float v)
:写入一个float
数据。void writeUTF(String str)
:写入一个 UTF-8 编码的字符串。
import java.io.DataOutputStream; import java.io.FileOutputStream; import java.io.IOException; public class DataOutputStreamExample { public static void main(String[] args) { try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.bin"))) { dos.writeInt(42); dos.writeFloat(3.14f); dos.writeUTF("Hello, DataOutputStream!"); } catch (IOException e) { e.printStackTrace(); } } }
掌握字符流的常用类和方法
-
FileReader
- 作用:从文件读取字符数据。
- 构造函数:
FileReader(String fileName)
:创建一个从指定文件读取字符数据的流。FileReader(File file)
:创建一个从指定File
对象读取字符数据的流。
- 常用方法:
int read()
:读取下一个字符的数据。如果已到达文件末尾,则返回 -1。int read(char[] cbuf)
:读取一定数量的字符数据到字符数组中。void close()
:关闭流并释放系统资源。
import java.io.FileReader; import java.io.IOException; public class FileReaderExample { public static void main(String[] args) { try (FileReader fr = new FileReader("input.txt")) { int charData; while ((charData = fr.read()) != -1) { System.out.print((char) charData); // 输出读取的字符 } } catch (IOException e) { e.printStackTrace(); } } }
-
FileWriter
- 作用:向文件写入字符数据。
- 构造函数:
FileWriter(String fileName)
:创建一个将字符数据写入指定文件的流。FileWriter(File file)
:创建一个将字符数据写入指定File
对象的流。
- 常用方法:
void write(int c)
:写入一个字符的数据。void write(char[] cbuf)
:写入字符数组中的数据。void write(String str)
:写入字符串的数据。void close()
:关闭流并释放系统资源。
import java.io.FileWriter; import java.io.IOException; public class FileWriterExample { public static void main(String[] args) { try (FileWriter fw = new FileWriter("output.txt")) { String data = "Hello, FileWriter!"; fw.write(data); // 将字符串写入文件 } catch (IOException e) { e.printStackTrace(); } } }
-
BufferedReader
- 作用:提供缓冲功能,以提高读取字符数据的效率。
- 构造函数:
BufferedReader(Reader in)
:创建一个使用默认缓冲区大小的缓冲读取器。
- 常用方法:
String readLine()
:读取一行文本数据。int read()
:读取下一个字符的数据。int read(char[] cbuf)
:读取一定数量的字符数据到字符数组中。void close()
:关闭流并释放系统资源。
import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class BufferedReaderExample { public static void main(String[] args) { try (BufferedReader br = new BufferedReader(new FileReader("input.txt"))) { String line; while ((line = br.readLine()) != null) { System.out.println(line); // 输出读取的行 } } catch (IOException e) { e.printStackTrace(); } } }
-
BufferedWriter
- 作用:提供缓冲功能,以提高写入字符数据的效率。
- 构造函数:
BufferedWriter(Writer out)
:创建一个使用默认缓冲区大小的缓冲写入器。
- 常用方法:
void write(int c)
:将一个字符的数据写入缓冲区。void write(char[] cbuf)
:将字符数组中的数据写入缓冲区。void write(String str)
:将字符串的数据写入缓冲区。void newLine()
:写入一个行分隔符。void flush()
:强制将缓冲区中的数据写入目标流。void close()
:关闭流并释放系统资源。
import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; public class BufferedWriterExample { public static void main(String[] args) { try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) { String data = "Hello, BufferedWriter!"; bw.write(data); // 将字符串写入文件 bw.newLine(); // 写入换行符 bw.write("This is a new line."); // 写入另一行数据 bw.flush(); // 确保所有数据都被写入 } catch (IOException e) { e.printStackTrace(); } } }
-
PrintWriter
- 作用:提供了一个便捷的方法来写入格式化文本。
- 构造函数:
PrintWriter(Writer out)
:创建一个将格式化文本写入指定输出流的打印写入器。PrintWriter(String fileName)
:创建一个将格式化文本写入指定文件的打印写入器。
- 常用方法:
void print(String s)
:打印字符串。void println(String s)
:打印字符串并换行。void printf(String format, Object... args)
:打印格式化字符串。void close()
:关闭流并释放系统资源。
import java.io.PrintWriter; import java.io.FileWriter; import java.io.IOException; public class PrintWriterExample { public static void main(String[] args) { try (PrintWriter pw = new PrintWriter(new FileWriter("output.txt"))) { pw.println("Hello, PrintWriter!"); // 打印并换行 pw.printf("Formatted number: %.2f%n", 3.14159); // 打印格式化文本 } catch (IOException e) { e.printStackTrace(); } } }
理解缓存流的作用和原理
缓存流通过在内存中维护一个缓冲区,减少实际的磁盘或网络 I/O 操作的次数,从而提升了性能。
缓存流 通过在内存中维护缓冲区,减少对底层流的实际 I/O 操作次数,从而提高了读写效率。
字节缓存流(如 BufferedInputStream
和 BufferedOutputStream
)用于处理字节数据,字符缓存流(如 BufferedReader
和 BufferedWriter
)用于处理字符数据。
缓存流通过批量读取和写入数据,减少了 I/O 操作的开销,提升了性能。
掌握缓存流的常用类和方法
-
BufferedInputStream
- 作用:为字节输入流提供缓冲功能,提高读取效率。
- 构造函数:
BufferedInputStream(InputStream in)
:创建一个使用默认缓冲区大小的缓冲输入流。BufferedInputStream(InputStream in, int size)
:创建一个指定缓冲区大小的缓冲输入流。
- 常用方法:
int read()
:从缓冲区读取下一个字节的数据。如果缓冲区已空,则从底层流中读取数据。int read(byte[] b)
:从缓冲区读取一定数量的字节数据到字节数组中。如果缓冲区已空,则从底层流中读取数据。int read(byte[] b, int off, int len)
:从缓冲区读取指定长度的字节数据到字节数组的指定位置中。如果缓冲区已空,则从底层流中读取数据。void close()
:关闭流并释放系统资源。
import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; public class BufferedInputStreamExample { public static void main(String[] args) { try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("input.bin"))) { int byteData; while ((byteData = bis.read()) != -1) { System.out.print((char) byteData); } } catch (IOException e) { e.printStackTrace(); } } }
-
BufferedOutputStream
- 作用:为字节输出流提供缓冲功能,提高写入效率。
- 构造函数:
BufferedOutputStream(OutputStream out)
:创建一个使用默认缓冲区大小的缓冲输出流。BufferedOutputStream(OutputStream out, int size)
:创建一个指定缓冲区大小的缓冲输出流。
- 常用方法:
void write(int b)
:将一个字节的数据写入缓冲区。void write(byte[] b)
:将字节数组中的数据写入缓冲区。void write(byte[] b, int off, int len)
:将字节数组的指定部分写入缓冲区。void flush()
:强制将缓冲区中的数据写入底层流。void close()
:关闭流并释放系统资源。
import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; public class BufferedOutputStreamExample { public static void main(String[] args) { try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("output.bin"))) { String data = "Hello, BufferedOutputStream!"; bos.write(data.getBytes()); bos.flush(); // 确保所有数据都被写入 } catch (IOException e) { e.printStackTrace(); } } }
-
BufferedReader
- 作用:为字符输入流提供缓冲功能,提高读取效率。
- 构造函数:
BufferedReader(Reader in)
:创建一个使用默认缓冲区大小的缓冲读取器。BufferedReader(Reader in, int sz)
:创建一个指定缓冲区大小的缓冲读取器。
- 常用方法:
String readLine()
:读取一行文本数据。int read()
:读取下一个字符的数据。int read(char[] cbuf)
:从缓冲区读取一定数量的字符数据到字符数组中。void close()
:关闭流并释放系统资源。
import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class BufferedReaderExample { public static void main(String[] args) { try (BufferedReader br = new BufferedReader(new FileReader("input.txt"))) { String line; while ((line = br.readLine()) != null) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } } }
-
BufferedWriter
- 作用:为字符输出流提供缓冲功能,提高写入效率。
- 构造函数:
BufferedWriter(Writer out)
:创建一个使用默认缓冲区大小的缓冲写入器。BufferedWriter(Writer out, int sz)
:创建一个指定缓冲区大小的缓冲写入器。
- 常用方法:
void write(int c)
:将一个字符的数据写入缓冲区。void write(char[] cbuf)
:将字符数组中的数据写入缓冲区。void write(String str)
:将字符串的数据写入缓冲区。void newLine()
:写入一个行分隔符。void flush()
:强制将缓冲区中的数据写入底层流。void close()
:关闭流并释放系统资源。
import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; public class BufferedWriterExample { public static void main(String[] args) { try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) { String data = "Hello, BufferedWriter!"; bw.write(data); bw.newLine(); bw.write("This is a new line."); bw.flush(); // 确保所有数据都被写入 } catch (IOException e) { e.printStackTrace(); } } }
理解字符编码和字符集的概念
- 字符集 是一个字符的集合,定义了可以使用哪些字符及其标识符。
- 字符编码 是将字符集中的字符转换为字节序列的规则,决定了如何在计算机中表示这些字符。
掌握字符串和字节数组的转换
-
字符串转换为字节数组:使用
String.getBytes()
方法,可以指定字符编码。import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; public class StringToByteArrayExample { public static void main(String[] args) { String str = "Hello, world!"; // 使用平台默认字符集 byte[] byteArray1 = str.getBytes(); // 使用指定字符集(如 UTF-8) try { byte[] byteArray2 = str.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } // 使用 Java 7+ 的标准字符集 byte[] byteArray3 = str.getBytes(StandardCharsets.UTF_8); // 打印结果 System.out.println("Byte Array 1: " + java.util.Arrays.toString(byteArray1)); System.out.println("Byte Array 2: " + java.util.Arrays.toString(byteArray2)); System.out.println("Byte Array 3: " + java.util.Arrays.toString(byteArray3)); } }
-
字节数组转换为字符串:使用
new String(byte[] bytes, String charsetName)
或new String(byte[] bytes, Charset charset)
,也可以指定字符编码。import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; public class ByteArrayToStringExample { public static void main(String[] args) { byte[] byteArray = {72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33}; // 代表 "Hello, world!" // 使用指定字符集(如 UTF-8) try { String str1 = new String(byteArray, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } // 使用 Java 7+ 的标准字符集 String str2 = new String(byteArray, StandardCharsets.UTF_8); // 打印结果 System.out.println("String 1: " + str1); System.out.println("String 2: " + str2); } }
-
确保字符编码一致,以避免数据丢失或乱码,并注意处理可能的编码异常。
掌握文件处理异常的处理方式
-
异常捕获和处理:使用
try-catch
语句来捕获和处理文件处理过程中可能发生的异常。通常,IOException
是最常见的异常类型,表示在输入或输出操作中发生了问题。 -
异常分类和处理策略:根据不同的异常类型,可以采取不同的处理策略:
FileNotFoundException
:文件未找到异常,通常发生在尝试打开一个不存在的文件时。可以提示用户检查文件路径或创建所需文件。EOFException
:文件结束异常,通常发生在读取数据流时,预期还有更多数据但到达文件结尾。可能需要检查数据源是否完整。IOException
:输入输出异常,表示一般的 I/O 错误。可以记录错误信息并进行适当的处理,例如重试操作或恢复操作。
import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; public class FileExceptionHandlingExample { public static void main(String[] args) { try (FileInputStream fis = new FileInputStream("nonexistent.txt")) { int data; while ((data = fis.read()) != -1) { System.out.print((char) data); } } catch (FileNotFoundException e) { System.err.println("File not found: " + e.getMessage()); } catch (IOException e) { System.err.println("I/O error occurred: " + e.getMessage()); } } }
-
自定义异常
在某些情况下,可能需要创建自定义异常类以提供更具体的错误信息或进行更复杂的错误处理。
public class CustomFileException extends Exception { public CustomFileException(String message) { super(message); } public CustomFileException(String message, Throwable cause) { super(message, cause); } } // 使用自定义异常 import java.io.FileReader; import java.io.IOException; public class CustomExceptionExample { public static void main(String[] args) { try { readFile("example.txt"); } catch (CustomFileException e) { System.err.println("Custom exception occurred: " + e.getMessage()); } } public static void readFile(String filename) throws CustomFileException { try (FileReader fr = new FileReader(filename)) { // 读取文件 } catch (IOException e) { throw new CustomFileException("Failed to read file: " + filename, e); } } }
8. Java 多线程编程
创建线程:掌握如何创建线程,掌握创建线程的各种方式
-
继承
Thread
类class MyThread extends Thread { @Override public void run() { System.out.println("Thread is running"); } } public class Main { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); // 启动线程 } }
-
实现
Runnable
接口class MyRunnable implements Runnable { @Override public void run() { System.out.println("Runnable is running"); } } public class Main { public static void main(String[] args) { Thread thread = new Thread(new MyRunnable()); thread.start(); // 启动线程 } }
-
使用
Callable
和Future
:Callable
是一个功能增强的Runnable
,它可以返回结果并且可以抛出异常。它与Future
一起使用。import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; class MyCallable implements Callable<Integer> { @Override public Integer call() { return 123; } } public class Main { public static void main(String[] args) { Callable<Integer> callable = new MyCallable(); FutureTask<Integer> futureTask = new FutureTask<>(callable); Thread thread = new Thread(futureTask); thread.start(); try { Integer result = futureTask.get(); // 获取线程结果 System.out.println("Callable result: " + result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } }
-
使用线程池:线程池可以更有效地管理线程,避免频繁创建和销毁线程的开销。
ExecutorService
是一个常用的线程池实现。import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Main { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(2); Runnable task = () -> System.out.println("Thread pool task running"); executor.execute(task); // 提交任务 executor.execute(task); // 提交任务 executor.shutdown(); // 关闭线程池 } }
-
使用
ForkJoinPool
:适合处理大规模的并行任务,特别是递归分解的问题。import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveTask; class MyTask extends RecursiveTask<Integer> { private final int workLoad; MyTask(int workLoad) { this.workLoad = workLoad; } @Override protected Integer compute() { if (workLoad > 1) { MyTask task1 = new MyTask(workLoad / 2); MyTask task2 = new MyTask(workLoad / 2); task1.fork(); task2.fork(); return task1.join() + task2.join(); } else { return 1; // Base case } } } public class Main { public static void main(String[] args) { ForkJoinPool forkJoinPool = new ForkJoinPool(); MyTask task = new MyTask(100); Integer result = forkJoinPool.invoke(task); System.out.println("ForkJoinPool result: " + result); } }
启动线程:掌握如何启动线程,理解使用 start()和 run()方法有何区别
start()
方法:用于启动一个新线程,异步执行线程中的run()
方法,线程在后台运行。run()
方法:在当前线程中执行,不会创建新线程。- 正确的方式是使用
start()
方法来启动线程,而不是直接调用run()
方法。如果直接调用run()
方法,线程不会真正地并发执行,而是同步地执行
线程同步:掌握什么是线程同步,理解为什么要进行线程同步。
线程同步 是为了避免在多线程环境中对共享资源的并发访问导致数据不一致和错误。
- 数据一致性:
- 当多个线程同时对共享数据进行读写操作时,如果没有同步控制,可能会导致数据不一致。例如,两个线程同时对一个计数器进行增操作,最终的结果可能不正确。
- 避免竞争条件:
- 竞争条件发生在两个或更多线程对共享资源进行操作时,结果取决于线程执行的顺序。如果没有适当的同步,线程的执行顺序可能导致不可预期的行为。
- 保持程序的正确性:
- 通过同步,可以确保线程的操作是按预期进行的,避免出现由于并发操作引起的程序逻辑错误。
synchronized 关键字:掌握如何使用 sychronized 关键字实现线程同步,掌握其使用方式。
-
同步方法
- 方法的定义上使用
synchronized
关键字,表示该方法是同步的,同一时间只能有一个线程执行这个方法。 - 特点:
- 当一个线程正在执行同步方法时,其他线程无法同时执行该方法。
- 同步方法的锁是当前实例对象(
this
)。
public class Counter { private int count = 0; // 同步方法 public synchronized void increment() { count++; } // 同步方法 public synchronized int getCount() { return count; } }
- 方法的定义上使用
-
同步代码块
- 方法内部使用
synchronized
关键字来同步代码块,控制对特定代码段的访问。可以对特定对象进行加锁,从而提高程序的并发性能。 - 特点:
synchronized
关键字可以作用于代码块,锁定特定对象。- 可以提高程序的并发性能,因为只对需要同步的部分加锁。
public class Counter { private int count = 0; private final Object lock = new Object(); // 自定义锁对象 public void increment() { synchronized (lock) { // 同步代码块 count++; } } public int getCount() { synchronized (lock) { // 同步代码块 return count; } } }
- 方法内部使用
锁对象:掌握什么是锁对象,掌握如何使用锁对象进行线程同步:
-
锁对象
锁对象是一个用来控制线程访问共享资源的对象。在
synchronized
关键字中,锁对象可以是当前实例对象 (this
)、类对象 (Class
对象) 或自定义对象。锁对象用于保证同一时间只有一个线程能够访问被锁定的代码块或方法,从而避免数据竞争和保持线程安全。 -
使用锁对象进行线程同步
-
实例对象锁
实例方法上使用
synchronized
关键字时,锁定的是当前实例对象 (this
)。这意味着只有一个线程可以执行同一个对象的同步方法。public class Counter { private int count = 0; // 同步实例方法 public synchronized void increment() { count++; } // 同步实例方法 public synchronized int getCount() { return count; } }
-
类对象锁
类对象作为锁,通常是通过同步静态方法或静态代码块来实现。这确保了所有实例都共享相同的锁,从而同步对类级别的资源的访问。
public class Counter { private static int count = 0; // 同步静态方法 public static synchronized void increment() { count++; } // 同步静态方法 public static synchronized int getCount() { return count; } }
或者使用同步静态代码块:
public class Counter { private static int count = 0; public static void increment() { synchronized (Counter.class) { // 锁定类对象 count++; } } public static int getCount() { synchronized (Counter.class) { // 锁定类对象 return count; } } }
-
自定义锁对象
自定义对象作为锁可以提供更细粒度的锁控制,避免不必要的同步。例如,锁定某个特定资源或功能的访问。
public class Counter { private int count = 0; private final Object lock = new Object(); // 自定义锁对象 public void increment() { synchronized (lock) { // 使用自定义锁对象 count++; } } public int getCount() { synchronized (lock) { // 使用自定义锁对象 return count; } } }
-
显式锁(
ReentrantLock
)ReentrantLock
是java.util.concurrent.locks
包中的一个类,提供了比synchronized
更灵活的锁控制。import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Counter { private int count = 0; private final Lock lock = new ReentrantLock(); // 显式锁 public void increment() { lock.lock(); // 获取锁 try { count++; } finally { lock.unlock(); // 释放锁 } } public int getCount() { lock.lock(); // 获取锁 try { return count; } finally { lock.unlock(); // 释放锁 } } }
-
volatile 关键字:理解 volatile 关键字的作用,理解什么时候使用 volatile 关键字。
volatile
关键字用于声明一个变量,使其具有“易失性”,即变量的值在多个线程中是可见的,并且禁止线程对该变量的操作进行缓存。这可以帮助解决多线程中的某些同步问题,但与 synchronized
关键字和其他锁机制相比,volatile
的功能是有限的。
-
作用:
- 保证可见性:
volatile
关键字确保当一个线程修改了某个变量的值后,其他线程可以立即看到这个修改。这是因为volatile
变量的值不会被线程本地缓存,而是直接从主内存中读取。
- 禁止指令重排:
- 使用
volatile
关键字可以防止编译器和处理器对变量读写的指令进行重排优化。即,volatile
变量的读写操作在程序中是有序的。
- 使用
- 保证可见性:
-
何时使用
-
简单状态标志:
当一个变量用于线程间的简单状态标志(例如,停止线程的标志),并且不涉及复杂的同步操作,可以使用
volatile
。例如:public class StopFlag { private volatile boolean stopRequested = false; public void requestStop() { stopRequested = true; } public boolean isStopRequested() { return stopRequested; } }
在这个例子中,
stopRequested
用于表示是否请求停止,volatile
确保不同线程对这个标志的读写操作是可见的。 -
单例模式中的双重检查锁定:
在实现单例模式时,
volatile
可以用于确保单例对象的创建过程是线程安全的。特别是在双重检查锁定(Double-Check Locking)模式中,volatile
确保了对象的正确初始化。public class Singleton { private static volatile Singleton instance; private Singleton() { } public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
在这个例子中,
volatile
确保了在instance
被初始化后,其他线程能够看到正确的实例
-
-
局限性:在需要原子性或涉及多个变量的复合操作时,
volatile
不能替代synchronized
或其他锁机制。
线程安全:理解什么是线程安全,掌握如何保证线程安全。
-
线程安全:多个线程同时访问和操作共享资源时,不会导致数据不一致或程序错误。一个线程安全的程序能够正确地处理多个线程并发执行的情况,确保在并发访问时数据的一致性和正确性。
-
保证线程安全:
-
使用
synchronized
关键字 -
使用
volatile
关键字 -
使用显式锁
-
使用并发集合
java.util.concurrent
包中的集合类,如ConcurrentHashMap
和CopyOnWriteArrayList
,已经实现了线程安全机制,适用于多线程环境中的共享数据结构。import java.util.concurrent.ConcurrentHashMap; public class Example { private ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); public void put(String key, Integer value) { map.put(key, value); } public Integer get(String key) { return map.get(key); } }
-
使用
Atomic
类java.util.concurrent.atomic
包中的原子类,如AtomicInteger
和AtomicBoolean
,提供了原子操作的方法,适用于简单的线程安全需求。import java.util.concurrent.atomic.AtomicInteger; public class Counter { private AtomicInteger count = new AtomicInteger(0); public void increment() { count.incrementAndGet(); } public int getCount() { return count.get(); } }
-
使用不可变对象
不可变对象在创建后其状态不能被修改,这样可以确保线程安全。例如,
String
和Integer
是不可变对象。public final class ImmutableClass { private final int value; public ImmutableClass(int value) { this.value = value; } public int getValue() { return value; } }
-
线程池概述:理解什么是线程池,掌握为什么要使用线程池。
- 线程池:是一种管理和复用线程的机制,用于控制并发任务的执行。线程池的核心思想是预先创建一定数量的线程,并将它们放入池中,这样可以重复利用这些线程来处理多个任务,从而提高资源的使用效率和程序的性能。
- 为什么使用:
- 提高性能:
- 减少线程创建和销毁的开销:每次创建和销毁线程都有一定的开销。线程池通过复用线程,减少了线程创建和销毁的开销,提高了性能。
- 提高响应速度:线程池中的线程可以被快速分配,减少了创建线程的延迟,提升了程序的响应速度。
- 资源管理:
- 控制并发数量:通过设置线程池的最大线程数,能够有效控制系统的并发度,防止因过多线程导致系统资源的过度消耗。
- 避免线程泄漏:线程池会管理线程的生命周期,避免了线程泄漏的问题。
- 任务调度:
- 任务排队:线程池使用任务队列来缓存待执行的任务,这使得任务可以按照提交的顺序进行执行。
- 支持异步处理:线程池能够支持异步任务的处理,提高了程序的吞吐量和效率。
- 简化代码:
- 简化线程管理:使用线程池可以简化多线程编程中的线程管理工作,使得开发者只需要关注任务的实现,不需要管理线程的生命周期。
- 提高性能:
线程池的优势:能够列举使用线程池的优势和好处。
- 提高性能:通过减少线程创建和销毁的开销,提升响应速度。
- 有效管理系统资源:控制并发数量,避免线程泄漏。
- 任务调度与处理:任务排队、支持异步处理。
- 提高代码可读性和简化线程管理:减少代码冗余,简化线程管理。
- 提供更多灵活性:自定义线程池配置,支持不同类型的任务。
- 增强任务执行的可靠性:避免任务丢失,提供错误处理机制。
- 提高资源利用率:线程复用,动态调整线程数量。
Executor 框架:理解什么是 Executor 框架,掌握它的主要组件
Executor
框架:是 Java 5 引入的用于简化多线程编程的工具,它提供了一种管理和执行线程的高级机制,允许开发者以更高效和可控的方式处理并发任务。Executor
框架封装了线程的创建、调度和执行逻辑,从而简化了线程管理并提高了并发程序的可维护性。- Executor 框架的主要组件
Executor
接口:Executor
是java.util.concurrent
包中的一个接口,定义了任务执行的基本接口。ExecutorService
接口:ExecutorService
是Executor
接口的子接口,提供了更多的功能,包括任务的调度、终止和结果的获取。ExecutorService
扩展了Executor
的功能,允许提交具有返回值的任务,并提供了优雅地关闭线程池的方法。ScheduledExecutorService
接口:ScheduledExecutorService
是ExecutorService
的子接口,提供了定时任务和周期任务的调度功能。ScheduledExecutorService
允许在指定的延迟后执行任务,或按固定间隔或延迟执行任务,适用于周期性任务和定时任务。ThreadPoolExecutor
类:ThreadPoolExecutor
是ExecutorService
的实现类,提供了丰富的线程池配置选项,可以用来创建和管理线程池。ThreadPoolExecutor
提供了灵活的配置选项,可以满足不同的线程池需求,包括核心线程数、最大线程数、空闲时间和任务队列等。Executors
工厂类:提供了静态方法来创建常见类型的线程池。Executors
工厂类简化了线程池的创建过程,提供了多种常用的线程池实现方式,适用于不同的并发需求。
线程池的创建:掌握如何创建线程池,掌握常见的线程池实现类
-
使用
Executors
工厂类创建线程池-
固定大小的线程池
适用于执行数量固定的任务,例如并发执行数据库查询。
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class FixedThreadPoolExample { public static void main(String[] args) { // 创建一个固定大小的线程池 ExecutorService executor = Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { final int taskId = i; executor.submit(() -> { System.out.println("Task " + taskId + " is running on thread " + Thread.currentThread().getName()); }); } executor.shutdown(); // 关闭线程池 } }
-
可缓存的线程池 (
newCachedThreadPool
)适用于执行短期异步任务,例如处理客户端请求。
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class CachedThreadPoolExample { public static void main(String[] args) { // 创建一个可缓存的线程池 ExecutorService executor = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { final int taskId = i; executor.submit(() -> { System.out.println("Task " + taskId + " is running on thread " + Thread.currentThread().getName()); }); } executor.shutdown(); // 关闭线程池 } }
-
单线程池 (
newSingleThreadExecutor
)适用于需要顺序执行任务的场景,例如日志记录任务。
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class SingleThreadExecutorExample { public static void main(String[] args) { // 创建一个单线程的线程池 ExecutorService executor = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { final int taskId = i; executor.submit(() -> { System.out.println("Task " + taskId + " is running on thread " + Thread.currentThread().getName()); }); } executor.shutdown(); // 关闭线程池 } }
-
定时任务线程池 (
newScheduledThreadPool
)适用于需要定时执行任务或周期性任务的场景,例如定时清理任务。
import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class ScheduledThreadPoolExample { public static void main(String[] args) { // 创建一个定时任务线程池 ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); // 延迟 5 秒执行任务 executor.schedule(() -> System.out.println("Task executed after 5 seconds"), 5, TimeUnit.SECONDS); // 每隔 3 秒执行一次任务 executor.scheduleAtFixedRate(() -> System.out.println("Periodic task executed"), 0, 3, TimeUnit.SECONDS); // 关闭线程池 executor.shutdown(); } }
-
-
使用
ThreadPoolExecutor
类创建线程池import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class CustomThreadPoolExample { public static void main(String[] args) { // 创建一个自定义线程池 ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, // corePoolSize 4, // maximumPoolSize 60, // keepAliveTime TimeUnit.SECONDS, // time unit new LinkedBlockingQueue<>(), // task queue new ThreadPoolExecutor.AbortPolicy() // rejection policy ); for (int i = 0; i < 10; i++) { final int taskId = i; executor.submit(() -> { System.out.println("Task " + taskId + " is running on thread " + Thread.currentThread().getName()); }); } executor.shutdown(); // 关闭线程池 } }
- 主要参数说明:
corePoolSize
:核心线程池的线程数量。maximumPoolSize
:线程池中允许的最大线程数量。keepAliveTime
:非核心线程的空闲时间,当线程池中的线程数量超过核心线程数量时,线程在空闲时间超过此值后将被终止。unit
:keepAliveTime
的时间单位。workQueue
:任务队列,用于存储待执行的任务。handler
:当任务无法添加到队列时的处理策略(例如:AbortPolicy
、CallerRunsPolicy
、DiscardOldestPolicy
、DiscardPolicy
)。
- 主要参数说明:
Callable 和 Future:理解什么是 Callable和Future,掌握如何使用它们获取线程执行结果
-
Callable
接口-
Callable
是一个函数式接口,与Runnable
类似,用于定义可以被线程池执行的任务,但与Runnable
不同的是,Callable
可以返回一个结果并且可以抛出异常。 -
使用场景:当需要执行一个任务并获取结果时,使用
Callable
更为合适。例如,计算任务的结果或者从任务中获取信息。 -
V call() throws Exception
:执行任务并返回一个结果,可能会抛出异常。
import java.util.concurrent.Callable; public interface Callable<V> { V call() throws Exception; }
-
-
Future
接口Future
接口用于表示异步计算的结果,允许任务的执行结果在未来某个时间点可用。通过Future
可以获取任务的执行结果、检查任务是否完成以及取消任务。Future
接口具有几个主要方法:boolean cancel(boolean mayInterruptIfRunning)
:尝试取消任务的执行。如果任务已经完成或被取消,则返回false
。boolean isCancelled()
:检查任务是否已被取消。boolean isDone()
:检查任务是否已完成。V get()
:获取任务的结果,如果任务尚未完成,则等待任务完成。V get(long timeout, TimeUnit unit)
:获取任务的结果,最多等待指定的时间。如果超时,则抛出TimeoutException
。
import java.util.concurrent.Future; public interface Future<V> { boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }
-
实例
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class CallableFutureExample { public static void main(String[] args) { // 创建一个固定大小的线程池 ExecutorService executor = Executors.newFixedThreadPool(2); // 创建一个 Callable 任务 Callable<Integer> task = () -> { Thread.sleep(2000); // 模拟耗时操作 return 123; }; // 提交任务并获取 Future 对象 Future<Integer> future = executor.submit(task); try { // 获取任务的结果,如果任务尚未完成,则会阻塞直到任务完成 Integer result = future.get(); System.out.println("Task result: " + result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } finally { executor.shutdown(); // 关闭线程池 } } }
线程池的关闭:掌握如何正确关闭线程池,以及有哪些关闭方式
-
shutdown()
:优雅关闭线程池,停止接受新任务并等待已提交任务完成。 -
shutdownNow()
:强制关闭线程池,停止所有正在执行的任务并取消等待任务。 -
awaitTermination(long timeout, TimeUnit unit)
:等待线程池完成关闭过程的可选方法,通常在shutdown()
后使用。
并发集合:理解 Java 中常用的并发集合类及其使用场景
ConcurrentHashMap
:适用于高并发环境中的键值对存储和操作。CopyOnWriteArrayList
:适用于读操作频繁、写操作少的场景。CopyOnWriteArraySet
:适用于读操作频繁、写操作少的去重集合需求。ConcurrentSkipListMap
:适用于高并发环境中需要有序存储和操作的场景。BlockingQueue
:适用于生产者-消费者问题、任务队列等需要阻塞操作的场景。
同步器:理解CountDownLatch和CyclicBarrier
CountDownLatch
和 CyclicBarrier
是 Java java.util.concurrent
包中的两个常用同步工具类,它们用于协调多个线程之间的执行顺序和等待状态。
-
CountDownLatch
:CountDownLatch
是一个计数器,可以用来协调多个线程的执行。其基本思想是允许一个或多个线程等待直到某个条件被满足。- 适用于当线程需要等待其他线程完成任务之后再继续执行。
- 一次性使用,计数器用尽后不能重用。
- 适合等待一组线程完成其任务后再执行其他操作。
-
CyclicBarrier
:CyclicBarrier
是一个同步工具类,允许一组线程相互等待,直到所有线程都到达某个公共屏障点。在所有线程到达屏障点后,它们将被释放,继续执行后续的操作。CyclicBarrier
可以被重用,即在所有线程到达屏障点后,屏障会被重置,允许下一轮等待。- 适用于线程需要在多个阶段之间同步进度。
- 可重用,适合在多轮的同步场景中使用。
- 支持回调函数,在所有线程到达屏障点后执行特定操作。
-
实例
import java.util.concurrent.CountDownLatch; public class CountDownLatchExample { public static void main(String[] args) { int numThreads = 3; CountDownLatch latch = new CountDownLatch(numThreads); Runnable task = () -> { System.out.println(Thread.currentThread().getName() + " is working"); try { Thread.sleep(1000); // 模拟工作 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { latch.countDown(); // 任务完成,计数器减 1 } }; // 启动 3 个线程 for (int i = 0; i < numThreads; i++) { new Thread(task).start(); } try { latch.await(); // 主线程等待计数器变为 0 System.out.println("All threads have completed"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }
import java.util.concurrent.CyclicBarrier; import java.util.concurrent.BrokenBarrierException; public class CyclicBarrierExample { public static void main(String[] args) { int numThreads = 3; CyclicBarrier barrier = new CyclicBarrier(numThreads, () -> { System.out.println("All threads have reached the barrier"); }); Runnable task = () -> { System.out.println(Thread.currentThread().getName() + " is working"); try { Thread.sleep(1000); // 模拟工作 barrier.await(); // 等待其他线程到达屏障点 System.out.println(Thread.currentThread().getName() + " passed the barrier"); } catch (InterruptedException | BrokenBarrierException e) { Thread.currentThread().interrupt(); } }; // 启动 3 个线程 for (int i = 0; i < numThreads; i++) { new Thread(task).start(); } } }
9. Java 反射机制
反射机制概述:理解什么是 Java 反射机制,以及为什么要使用反射
-
Java反射机制是指在运行时可以动态地获取类的信息并操作类的属性、方法和构造器。通过反射,Java程序可以在运行时检查和使用类的结构和行为,而不是在编译时确定。
-
为什么要使用反射:
- 框架开发:
- 许多Java框架(如Spring、Hibernate)都广泛使用反射机制来实现依赖注入、对象关系映射等功能。反射允许这些框架在运行时动态创建和管理对象,而不需要提前知道对象的类型。
- 动态代理:
- 反射是Java动态代理机制的基础。通过反射,可以在运行时创建代理类,并将方法调用委托给实际的实现类。这在AOP(面向切面编程)中非常有用。
- 运行时动态性:
- 在某些情况下,程序需要在运行时加载和使用类,而这些类在编译时是未知的。反射允许程序动态加载类并调用其方法,例如通过读取配置文件或用户输入决定使用哪个类。
- 工具和IDE开发:
- 反射广泛应用于开发工具和集成开发环境(IDE)中,用于动态分析和操作代码结构。例如,IDE可以使用反射来实现代码补全、自动生成文档等功能。
- 框架开发:
Class 类:理解描述 Class 类的作用和常用方法。
-
Class
类的作用- 获取类的元数据:通过
Class
对象可以获取类的名称、修饰符、包信息、父类、实现的接口等。 - 创建新实例:可以使用反射机制创建类的实例。
- 获取类的成员:可以获取类的构造器、方法、字段等信息,并进行相应的操作。
- 动态调用方法:可以在运行时动态调用类的方法。
- 动态访问和修改字段:可以在运行时动态访问和修改类的字段值。
- 获取类的元数据:通过
-
常用方法
-
获取类的名称
String className = clazz.getName(); String simpleName = clazz.getSimpleName();
-
获取类的修饰符
int modifiers = clazz.getModifiers();
-
获取包信息
Package pkg = clazz.getPackage();
-
获取父类
Class<?> superClass = clazz.getSuperclass();
-
获取实现的接口
Class<?>[] interfaces = clazz.getInterfaces();
-
获取构造器
Constructor<?>[] constructors = clazz.getConstructors(); Constructor<?> constructor = clazz.getConstructor(parameterTypes);
-
获取方法
Method[] methods = clazz.getMethods(); Method method = clazz.getMethod("methodName", parameterTypes);
-
获取字段
Field[] fields = clazz.getFields(); Field field = clazz.getField("fieldName");
-
创建实例
Field[] fields = clazz.getFields(); Field field = clazz.getField("fieldName");
-
调用方法
Method method = clazz.getMethod("methodName", parameterTypes); Object result = method.invoke(instance, args);
-
访问和修改字段
Field field = clazz.getField("fieldName"); Object value = field.get(instance); field.set(instance, newValue);
-
-
示例:
import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; public class ReflectionExample { public static void main(String[] args) { try { // 获取Class对象 Class<?> clazz = Class.forName("com.example.MyClass"); // 获取类名 String className = clazz.getName(); System.out.println("Class Name: " + className); // 获取类的修饰符 int modifiers = clazz.getModifiers(); System.out.println("Modifiers: " + Modifier.toString(modifiers)); // 获取包信息 Package pkg = clazz.getPackage(); System.out.println("Package: " + pkg.getName()); // 获取父类 Class<?> superClass = clazz.getSuperclass(); System.out.println("Superclass: " + superClass.getName()); // 获取实现的接口 Class<?>[] interfaces = clazz.getInterfaces(); for (Class<?> iface : interfaces) { System.out.println("Interface: " + iface.getName()); } // 获取构造器 Constructor<?> constructor = clazz.getConstructor(); Object instance = constructor.newInstance(); // 获取方法并调用 Method method = clazz.getMethod("myMethod", String.class); method.invoke(instance, "Hello, World!"); // 获取字段并访问 Field field = clazz.getField("myField"); Object fieldValue = field.get(instance); System.out.println("Field Value: " + fieldValue); field.set(instance, "New Value"); System.out.println("Updated Field Value: " + field.get(instance)); } catch (Exception e) { e.printStackTrace(); } } }
获取 Class 对象:理解获取 Class 对象的三种方式。
-
通过类名: 使用类名直接获取
Class
对象。这种方式在编译时就已经确定了类的信息。Class<?> clazz = MyClass.class;
-
通过对象: 使用已经创建的对象获取其对应的
Class
对象。这种方式在运行时获取对象的类信息。MyClass obj = new MyClass(); Class<?> clazz = obj.getClass();
-
通过类的全限定名: 使用类的全限定名字符串获取
Class
对象。这种方式在运行时通过字符串加载类,适用于动态加载类的场景。try { Class<?> clazz = Class.forName("com.example.MyClass"); } catch (ClassNotFoundException e) { e.printStackTrace(); }
public class ReflectionExample {
public static void main(String[] args) {
// 方式1:通过类名
Class<?> clazz1 = MyClass.class;
System.out.println("Class Name (方式1): " + clazz1.getName());
// 方式2:通过对象
MyClass obj = new MyClass();
Class<?> clazz2 = obj.getClass();
System.out.println("Class Name (方式2): " + clazz2.getName());
// 方式3:通过类的全限定名
try {
Class<?> clazz3 = Class.forName("com.example.MyClass");
System.out.println("Class Name (方式3): " + clazz3.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class MyClass {
// 示例类的定义
}
##### 动态创建对象:掌握如何使用反射动态创建对象
1. 通过无参构造器创建对象:使用`Class`对象的`newInstance()`方法(已过时)或者`Constructor`对象的`newInstance()`方法来创建实例。
```java
public class ReflectionExample {
public static void main(String[] args) {
try {
// 获取Class对象
Class<?> clazz = Class.forName("com.example.MyClass");
// 使用无参构造器创建对象
Object instance = clazz.getDeclaredConstructor().newInstance();
System.out.println("Created instance: " + instance);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class MyClass {
public MyClass() {
// 无参构造器
}
@Override
public String toString() {
return "MyClass instance";
}
}
-
通过有参构造器创建对象:通过
Class
对象获取相应的Constructor
对象,然后使用newInstance
方法创建实例。public class ReflectionExample { public static void main(String[] args) { try { // 获取Class对象 Class<?> clazz = Class.forName("com.example.MyClass"); // 获取带参数的构造器 Constructor<?> constructor = clazz.getConstructor(String.class); // 使用构造器创建对象 Object instance = constructor.newInstance("Hello, Reflection!"); System.out.println("Created instance: " + instance); } catch (Exception e) { e.printStackTrace(); } } } class MyClass { private String message; public MyClass(String message) { this.message = message; } @Override public String toString() { return "MyClass instance with message: " + message; } }
-
通过反序列化创建对象
反射访问属性:掌握如何使用反射访问对象的属性。
import java.lang.reflect.Field;
public class ReflectionExample {
public static void main(String[] args) {
try {
// 创建对象
Person person = new Person("John Doe", 30);
// 获取Class对象
Class<?> clazz = person.getClass();
// 获取私有字段name
Field nameField = clazz.getDeclaredField("name");
// 解除私有访问控制
nameField.setAccessible(true);
// 获取字段值
String nameValue = (String) nameField.get(person);
System.out.println("Name (before): " + nameValue);
// 修改字段值
nameField.set(person, "Jane Doe");
System.out.println("Name (after): " + person.getName());
// 获取私有字段age
Field ageField = clazz.getDeclaredField("age");
// 解除私有访问控制
ageField.setAccessible(true);
// 获取字段值
int ageValue = ageField.getInt(person);
System.out.println("Age (before): " + ageValue);
// 修改字段值
ageField.set(person, 25);
System.out.println("Age (after): " + person.getAge());
} catch (Exception e) {
e.printStackTrace();
}
}
}
反射访问方法:掌握如何使用反射调用对象的方法。
import java.lang.reflect.Method;
public class ReflectionExample {
public static void main(String[] args) {
try {
// 创建对象
Person person = new Person("John Doe", 30);
// 获取Class对象
Class<?> clazz = person.getClass();
// 调用公有方法getName
Method getNameMethod = clazz.getMethod("getName");
String name = (String) getNameMethod.invoke(person);
System.out.println("Name: " + name);
// 调用公有方法setName
Method setNameMethod = clazz.getMethod("setName", String.class);
setNameMethod.invoke(person, "Jane Doe");
System.out.println("Updated Name: " + person.getName());
// 调用私有方法printInfo
Method printInfoMethod = clazz.getDeclaredMethod("printInfo");
printInfoMethod.setAccessible(true);
printInfoMethod.invoke(person);
} catch (Exception e) {
e.printStackTrace();
}
}
}
获取构造方法:掌握如何使用反射获取对象的构造方法
import java.lang.reflect.Constructor;
public class ReflectionExample {
public static void main(String[] args) {
try {
// 获取Class对象
Class<?> clazz = Class.forName("Person");
// 获取所有的构造方法
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("Constructor: " + constructor);
}
// 获取无参构造方法并创建实例
Constructor<?> noArgsConstructor = clazz.getConstructor();
Object noArgsInstance = noArgsConstructor.newInstance();
System.out.println("No-Args Constructor Instance: " + noArgsInstance);
// 获取带参数的构造方法并创建实例
Constructor<?> paramsConstructor = clazz.getConstructor(String.class, int.class);
Object paramsInstance = paramsConstructor.newInstance("John Doe", 30);
System.out.println("Params Constructor Instance: " + paramsInstance);
} catch (Exception e) {
e.printStackTrace();
}
}
}
获取字段信息:掌握如何使用反射获取对象的字段信息
import java.lang.reflect.Field;
public class ReflectionExample {
public static void main(String[] args) {
try {
// 创建对象
Person person = new Person("John Doe", 30);
// 获取Class对象
Class<?> clazz = person.getClass();
// 获取私有字段name
Field nameField = clazz.getDeclaredField("name");
// 解除私有访问控制
nameField.setAccessible(true);
// 获取字段值
String nameValue = (String) nameField.get(person);
System.out.println("Name (before): " + nameValue);
// 修改字段值
nameField.set(person, "Jane Doe");
System.out.println("Name (after): " + person.getName());
// 获取私有字段age
Field ageField = clazz.getDeclaredField("age");
// 解除私有访问控制
ageField.setAccessible(true);
// 获取字段值
int ageValue = ageField.getInt(person);
System.out.println("Age (before): " + ageValue);
// 修改字段值
ageField.set(person, 25);
System.out.println("Age (after): " + person.getAge());
} catch (Exception e) {
e.printStackTrace();
}
}
}
修改字段值:掌握如何使用反射修改对象的字段值。
import java.lang.reflect.Field;
public class ReflectionExample {
public static void main(String[] args) {
try {
// 创建对象
Person person = new Person("John Doe", 30);
// 获取Class对象
Class<?> clazz = person.getClass();
// 获取私有字段name
Field nameField = clazz.getDeclaredField("name");
// 解除私有访问控制
nameField.setAccessible(true);
// 获取字段值
String nameValue = (String) nameField.get(person);
System.out.println("Name (before): " + nameValue);
// 修改字段值
nameField.set(person, "Jane Doe");
System.out.println("Name (after): " + person.getName());
// 获取私有字段age
Field ageField = clazz.getDeclaredField("age");
// 解除私有访问控制
ageField.setAccessible(true);
// 获取字段值
int ageValue = ageField.getInt(person);
System.out.println("Age (before): " + ageValue);
// 修改字段值
ageField.set(person, 25);
System.out.println("Age (after): " + person.getAge());
} catch (Exception e) {
e.printStackTrace();
}
}
}
反射应用:理解反射在实际开发中的典型应用场景。
反射在实际开发中提供了灵活性和动态性,使得代码可以在运行时做出决策和改变。
这些特性在框架开发、动态代理、插件系统、序列化、动态加载、测试调试等场景中非常有用。然而,使用反射时应注意其性能开销和安全性问题。
10. Lambda 表达式
理解 Lambda 表达式的语法和结构
(parameters) -> { statements }
或者
(parameters) -> expression
-
参数列表:括号内列出 Lambda 表达式的参数。可以省略参数类型,如果只有一个参数,可以省略括号。
-
箭头操作符 (
->
):将参数列表和 Lambda 表达式的主体分开。 -
表达式主体:Lambda 表达式的实现体,可以是一个表达式或一个代码块。如果是一个表达式,结果会自动返回。如果是一个代码块,需要使用
return
关键字显式地返回结果。x -> x * x
(x, y) -> { int result = x + y; return result; }
掌握 Lambda 表达式的使用场景和好处
-
使用Lambda 表达式的
Stream
API 操作集合List<String> names = Arrays.asList("John", "Jane", "Jack", "Jill"); // 过滤出名字长度大于4的名字 List<String> longNames = names.stream() .filter(name -> name.length() > 4) .collect(Collectors.toList()); System.out.println(longNames); // 输出 [Jane, Jack]
-
回调函数
ExecutorService executor = Executors.newSingleThreadExecutor(); executor.submit(() -> { // 执行一些后台任务 System.out.println("Task is running in background"); }); executor.shutdown();
-
函数式接口的实现
@FunctionalInterface interface Converter<F, T> { T convert(F from); } Converter<String, Integer> stringToInteger = Integer::parseInt; System.out.println(stringToInteger.convert("123")); // 输出 123
-
排序和比较
List<String> names = Arrays.asList("John", "Jane", "Jack", "Jill"); // 按名字长度排序 names.sort((s1, s2) -> Integer.compare(s1.length(), s2.length())); System.out.println(names); // 输出 [John, Jack, Jane, Jill]
熟练编写简单的 Lambda 表达式
理解函数式接口的定义和特点
-
函数式接口:是一个只包含一个抽象方法的接口。这种接口可以有多个默认方法和静态方法,但必须只有一个抽象方法。
@FunctionalInterface public interface MyFunctionalInterface { void singleAbstractMethod(); // 这是唯一的抽象方法 // 可以有默认方法 default void defaultMethod() { System.out.println("Default method"); } // 可以有静态方法 static void staticMethod() { System.out.println("Static method"); } }
-
特点
- 单一抽象方法:函数式接口只能有一个抽象方法。这个方法可以在实现时通过 Lambda 表达式或方法引用来提供实现。
- 默认方法:函数式接口可以包含多个默认方法。
- 静态方法:函数式接口可以包含静态方法。静态方法属于接口本身,不属于接口的实例。
- 方法引用:函数式接口可以通过方法引用来实现,这种方法引用可以是静态方法、实例方法或构造函数。
@FunctionalInterface
注解:@FunctionalInterface
注解是可选的,但推荐使用。它用于标识函数式接口并帮助编译器进行检查,确保接口只有一个抽象方法。如果接口有多个抽象方法,编译器会报错。
掌握使用@Fumctiona1Interface 注解声明函数式接口掌握常见的函数式接口,如 Consumer、Predicate、Function 等熟悉函数式接口在 Lambda 表达式中的应用
@FunctionalInterface
public interface MyFunctionalInterface {
void singleAbstractMethod(); // 这是唯一的抽象方法
// 可以有默认方法
default void defaultMethod() {
System.out.println("Default method");
}
// 可以有静态方法
static void staticMethod() {
System.out.println("Static method");
}
}
List<String> names = Arrays.asList("John", "Jane", "Jack", "Jill");
// 使用 Predicate 过滤
List<String> longNames = names.stream()
.filter(name -> name.length() > 4)
.collect(Collectors.toList());
// 使用 Function 映射
List<String> upperCaseNames = names.stream()
.map(name -> name.toUpperCase())
.collect(Collectors.toList());
// 使用 Consumer 遍历
names.forEach(name -> System.out.println(name));
理解 Lambda 表达式和方法引用的关系能够比较 Lambda 表达式和匿名内部类的异同
Lambda 表达式与方法引用的关系
-
等效性:
- 方法引用可以视为 Lambda 表达式的一种简化形式。当 Lambda 表达式只是调用现有的方法时,可以用方法引用来代替。
- Lambda 表达式和方法引用都可以用来实现函数式接口。
-
简洁性:
- 方法引用提供了更简洁的语法,特别是当 Lambda 表达式的主体只是简单地调用现有的方法时。例如,
s -> s.toUpperCase()
可以简化为String::toUpperCase
。 - 方法引用提高了代码的可读性,使意图更加明确。
- 方法引用提供了更简洁的语法,特别是当 Lambda 表达式的主体只是简单地调用现有的方法时。例如,
-
适用场景:
- Lambda 表达式:适用于需要自定义行为的情况,尤其是当 Lambda 表达式中包含逻辑或多个操作时。
- 方法引用:适用于直接引用已有方法的情况,尤其是当 Lambda 表达式仅仅是对方法的调用时。
-
示例
Lambda 表达式:
List<String> names = Arrays.asList("John", "Jane", "Jack"); // 使用 Lambda 表达式进行排序 names.sort((s1, s2) -> s1.compareToIgnoreCase(s2)); System.out.println(names); // 输出 [Jack, Jane, John]
方法引用:
List<String> names = Arrays.asList("John", "Jane", "Jack"); // 使用方法引用进行排序 names.sort(String::compareToIgnoreCase); System.out.println(names); // 输出 [Jack, Jane, John]
理解方法引用的概念和用法
-
方法引用:是一种简化 Lambda 表达式的方式,当 Lambda 表达式的内容仅仅是调用某个方法时,我们可以使用方法引用来代替。方法引用使代码更加简洁、可读,尤其是在函数式编程的场景下。
-
方法引用的类型
-
静态方法引用
ClassName::staticMethodName
// 定义一个函数式接口 @FunctionalInterface public interface StringFormatter { String format(String input); } // 定义一个包含静态方法的类 public class StringUtils { public static String toUpperCase(String input) { return input.toUpperCase(); } } public class MethodReferenceExample { public static void main(String[] args) { // 使用方法引用 StringFormatter formatter = StringUtils::toUpperCase; System.out.println(formatter.format("hello")); // 输出 "HELLO" } }
-
实例方法引用(特定对象的实例方法)
instance::instanceMethodName
// 定义一个函数式接口 @FunctionalInterface public interface StringPrinter { void print(String input); } public class Printer { public void printUpperCase(String input) { System.out.println(input.toUpperCase()); } } public class MethodReferenceExample { public static void main(String[] args) { Printer printer = new Printer(); // 使用方法引用 StringPrinter printerRef = printer::printUpperCase; printerRef.print("hello"); // 输出 "HELLO" } }
-
实例方法引用(对象的实例方法,来自于某个参数)
ClassName::instanceMethodName
// 定义一个函数式接口 @FunctionalInterface public interface StringComparator { int compare(String s1, String s2); } public class MethodReferenceExample { public static void main(String[] args) { List<String> names = Arrays.asList("John", "Jane", "Jack"); // 使用方法引用(引用 String 类的 compareToIgnoreCase 方法) names.sort(String::compareToIgnoreCase); System.out.println(names); // 输出 [Jack, Jane, John] } }
-
构造函数引用
ClassName::new
// 定义一个函数式接口 @FunctionalInterface public interface Supplier<T> { T get(); } public class Person { private String name; public Person(String name) { this.name = name; } public String getName() { return name; } } public class MethodReferenceExample { public static void main(String[] args) { // 使用构造函数引用 Supplier<Person> personSupplier = () -> new Person("John"); // 使用方法引用(引用构造函数) Supplier<Person> personSupplierRef = Person::new; Person person = personSupplierRef.get(); System.out.println(person.getName()); // 输出 "John" } }
掌握四种方法引用的类型:静态方法引用、实例方法引用、类方法引用、构造方法引用
-
见上
掌握使用 Lambda 表达式和方法引用简化代码
了解 Stream API 的作用和优势
主要用于处理集合数据。它提供了一种高效、声明性的方式来进行集合操作,包括过滤、映射、排序、聚合等。Stream API 的核心在于通过流的方式处理数据,这种方式能够提高代码的可读性和可维护性,同时提供了一些性能上的优化。
理解如何创建 Stream 对象
-
从集合(
List
、Set
、Queue
)中调用stream()
或parallelStream()
方法。List<String> names = Arrays.asList("John", "Jane", "Jack", "Jill"); // 创建顺序流 Stream<String> sequentialStream = names.stream(); // 创建并行流 Stream<String> parallelStream = names.parallelStream();
-
从数组使用
Arrays.stream()
方法。String[] namesArray = {"John", "Jane", "Jack", "Jill"}; // 从对象数组创建流 Stream<String> streamFromArray = Arrays.stream(namesArray); // 从原始类型数组创建流 int[] numbers = {1, 2, 3, 4, 5}; IntStream intStreamFromArray = Arrays.stream(numbers);
-
使用
Stream.of()
方法创建包含固定元素的流。Stream<String> streamOfElements = Stream.of("John", "Jane", "Jack", "Jill");
-
使用
Stream.iterate()
或Stream.generate()
创建无限流。// 使用 iterate 创建流 Stream<Integer> infiniteStreamIterate = Stream.iterate(0, n -> n + 2); // 使用 generate 创建流 Stream<Double> infiniteStreamGenerate = Stream.generate(Math::random);
-
使用
Files.lines()
从文件中创建流。try (Stream<String> linesStream = Files.lines(Paths.get("example.txt"))) { linesStream.forEach(System.out::println); // 输出文件中的每一行 } catch (IOException e) { e.printStackTrace(); }
掌握 Stream API 中常用的中间操作和终端操作
-
中间操作:如
-
filter
:filter(Predicate<T> predicate)
过滤流中的元素,只保留符合条件的元素。List<String> names = Arrays.asList("John", "Jane", "Jack", "Jill"); List<String> longNames = names.stream() .filter(name -> name.length() > 4) .collect(Collectors.toList()); // longNames: ["Jane", "Jill"]
-
map
:map(Function<T, R> mapper)
将流中的每个元素应用一个函数,生成一个新的流。List<String> names = Arrays.asList("John", "Jane", "Jack", "Jill"); List<String> upperCaseNames = names.stream() .map(String::toUpperCase) .collect(Collectors.toList()); // upperCaseNames: ["JOHN", "JANE", "JACK", "JILL"]
-
flatMap
:flatMap(Function<T, Stream<R>> mapper)
将流中的每个元素映射成一个流,然后将所有流合并成一个流。List<List<String>> listOfLists = Arrays.asList( Arrays.asList("John", "Jane"), Arrays.asList("Jack", "Jill") ); List<String> allNames = listOfLists.stream() .flatMap(List::stream) .collect(Collectors.toList()); // allNames: ["John", "Jane", "Jack", "Jill"]
-
distinct
:distinct()
去除流中的重复元素,返回一个包含唯一元素的流。List<String> names = Arrays.asList("John", "Jane", "John", "Jack"); List<String> distinctNames = names.stream() .distinct() .collect(Collectors.toList()); // distinctNames: ["John", "Jane", "Jack"]
-
sorted
:sorted()
/sorted(Comparator<T> comparator)
对流中的元素进行排序,默认排序或使用自定义比较器。List<String> names = Arrays.asList("John", "Jane", "Jack", "Jill"); List<String> sortedNames = names.stream() .sorted() .collect(Collectors.toList()); // sortedNames: ["Jack", "Jane", "Jill", "John"]
-
limit
:limit(long maxSize)
截取流中的前maxSize
个元素,返回一个包含这些元素的新流。List<String> names = Arrays.asList("John", "Jane", "Jack", "Jill"); List<String> firstTwoNames = names.stream() .limit(2) .collect(Collectors.toList()); // firstTwoNames: ["John", "Jane"]
-
skip
:skip(long n)
跳过流中的前n
个元素,返回一个包含剩余元素的新流。List<String> names = Arrays.asList("John", "Jane", "Jack", "Jill"); List<String> skippedNames = names.stream() .skip(2) .collect(Collectors.toList()); // skippedNames: ["Jack", "Jill"]
等,用于对流进行转换和处理。
-
-
终端操作:如
-
collect
:collect(Collector<? super T, A, R> collector)
将流中的元素收集到一个集合、数组或其他类型的结果中。List<String> names = Arrays.asList("John", "Jane", "Jack", "Jill"); Set<String> namesSet = names.stream() .collect(Collectors.toSet()); // namesSet: ["John", "Jane", "Jack", "Jill"]
-
forEach
:forEach(Consumer<? super T> action)
对流中的每个元素执行指定的操作。List<String> names = Arrays.asList("John", "Jane", "Jack", "Jill"); names.stream() .forEach(System.out::println); // 输出: // John // Jane // Jack // Jill
-
reduce
:reduce(T identity, BinaryOperator<T> accumulator)
/reduce(BinaryOperator<T> accumulator)
对流中的元素进行归约操作,将其合并为一个单一的结果。List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); int sum = numbers.stream() .reduce(0, Integer::sum); // sum: 15
-
count
:count()
计算流中元素的数量。List<String> names = Arrays.asList("John", "Jane", "Jack", "Jill"); long count = names.stream().count(); // count: 4
-
findFirst
:findFirst()
/findAny()
查找流中的第一个元素或任意一个元素(如果流中存在元素)。List<String> names = Arrays.asList("John", "Jane", "Jack", "Jill"); Optional<String> firstName = names.stream().findFirst(); // firstName: Optional[John] Optional<String> anyName = names.stream().findAny(); // anyName: Optional[John] (或其他任意一个元素)
-
anyMatch
:anyMatch(Predicate<? super T> predicate)
判断流中是否有任意一个元素符合指定的条件。List<String> names = Arrays.asList("John", "Jane", "Jack", "Jill"); boolean hasJack = names.stream() .anyMatch(name -> name.equals("Jack")); // hasJack: true
-
allMatch
:allMatch(Predicate<? super T> predicate)
判断流中的所有元素是否都符合指定的条件。List<String> names = Arrays.asList("John", "Jane", "Jack", "Jill"); boolean allStartWithJ = names.stream() .allMatch(name -> name.startsWith("J")); // allStartWithJ: true
-
noneMatch
:noneMatch(Predicate<? super T> predicate)
判断流中是否没有任何元素符合指定的条件。List<String> names = Arrays.asList("John", "Jane", "Jack", "Jill"); boolean noneStartWithA = names.stream() .noneMatch(name -> name.startsWith("A")); // noneStartWithA: true
等,用于触发流的计算并生成结果。
熱悉 Stream API 在集合数据处理中的应用场景
-
-
过滤数据:使用
filter
方法从集合中筛选出符合特定条件的数据import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamExample { public static void main(String[] args) { List<String> names = Arrays.asList("John", "Jane", "Jack", "Jill"); // 过滤出名字长度大于4的名字 List<String> longNames = names.stream() .filter(name -> name.length() > 4) .collect(Collectors.toList()); System.out.println(longNames); // 输出: [Jane, Jill] } }
-
转换数据:通过
map
方法将集合中的每个元素转换成另一种形式。例如,将字符串转换为大写字母或将对象转换为其某个属性的值。import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamExample { public static void main(String[] args) { List<String> names = Arrays.asList("John", "Jane", "Jack", "Jill"); // 将所有名字转换为大写 List<String> upperCaseNames = names.stream() .map(String::toUpperCase) .collect(Collectors.toList()); System.out.println(upperCaseNames); // 输出: [JOHN, JANE, JACK, JILL] } }
-
扁平化数据:使用
flatMap
可以将流中的每个元素映射为一个流,并将所有流合并成一个流。这对于处理嵌套集合特别有用。import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamExample { public static void main(String[] args) { List<List<String>> listOfLists = Arrays.asList( Arrays.asList("John", "Jane"), Arrays.asList("Jack", "Jill") ); // 扁平化嵌套集合 List<String> allNames = listOfLists.stream() .flatMap(List::stream) .collect(Collectors.toList()); System.out.println(allNames); // 输出: [John, Jane, Jack, Jill] } }
-
排序数据:使用
sorted
方法对流中的元素进行排序。可以进行自然排序或使用自定义比较器进行排序。import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamExample { public static void main(String[] args) { List<String> names = Arrays.asList("John", "Jane", "Jack", "Jill"); // 按字母排序 List<String> sortedNames = names.stream() .sorted() .collect(Collectors.toList()); System.out.println(sortedNames); // 输出: [Jack, Jane, Jill, John] } }
-
聚合数据:使用
reduce
方法对流中的元素进行归约操作,例如计算总和、求最大值、最小值等。import java.util.Arrays; import java.util.List; public class StreamExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); // 计算总和 int sum = numbers.stream() .reduce(0, Integer::sum); System.out.println(sum); // 输出: 15 // 计算最大值 int max = numbers.stream() .reduce(Integer.MIN_VALUE, Integer::max); System.out.println(max); // 输出: 5 } }
-
分组数据:使用
Collectors.groupingBy
方法将流中的元素按指定条件分组。这对于统计和分类数据非常有用。import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class StreamExample { public static void main(String[] args) { List<String> names = Arrays.asList("John", "Jane", "Jack", "Jill"); // 按名字的首字母分组 Map<Character, List<String>> groupedByFirstLetter = names.stream() .collect(Collectors.groupingBy(name -> name.charAt(0))); System.out.println(groupedByFirstLetter); // 输出: {J=[John, Jack, Jill], J=[Jane]} } }
-
统计数据:使用
Collectors.counting
、Collectors.summingInt
、Collectors.averagingInt
等方法进行统计计算。import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamExample { public static void main(String[] args) { List<String> names = Arrays.asList("John", "Jane", "Jack", "Jill"); // 计算名字的总长度 int totalLength = names.stream() .collect(Collectors.summingInt(String::length)); System.out.println(totalLength); // 输出: 16 // 计算名字的平均长度 double averageLength = names.stream() .collect(Collectors.averagingInt(String::length)); System.out.println(averageLength); // 输出: 4.0 } }
-
查找和匹配:使用
findFirst
、findAny
、anyMatch
、allMatch
、noneMatch
等方法查找符合条件的元素或进行条件匹配。import java.util.Arrays; import java.util.List; import java.util.Optional; public class StreamExample { public static void main(String[] args) { List<String> names = Arrays.asList("John", "Jane", "Jack", "Jill"); // 查找第一个名字以 J 开头的元素 Optional<String> firstJName = names.stream() .filter(name -> name.startsWith("J")) .findFirst(); firstJName.ifPresent(System.out::println); // 输出: John // 检查是否有任何名字以 J 开头 boolean anyStartWithJ = names.stream() .anyMatch(name -> name.startsWith("J")); System.out.println(anyStartWithJ); // 输出: true } }
11. Java web 开发基础
理解 Servlet 的生命周期和工作原理
-
生命周期:Servlet 的生命周期包括加载和实例化、初始化、请求处理和销毁四个阶段。
-
工作原理:Servlet 容器接收客户端请求,通过线程池处理请求,调用相应的 Servlet 实例的
service()
方法处理请求,并返回响应数据。Servlet 容器负责管理 Servlet 的生命周期和资源。理解 Servlet 的请求和响应处理机制
Servlet 的请求和响应处理机制包括接收请求、创建请求和响应对象、调用处理方法、处理请求、生成响应和发送响应的过程。HttpServletRequest
对象封装了客户端请求的数据,而 HttpServletResponse
对象用于设置响应数据并返回给客户端。
理解 JSP 和 Servlet 的关系
-
JSP 和 Servlet 是 Java Web 开发的两个核心技术,JSP 主要用于生成动态 Web 内容(视图),而 Servlet 主要用于处理请求和业务逻辑(控制器)。
-
JSP 页面在运行时被转换为 Servlet 类,Servlet 负责生成最终的 HTML 响应。
-
它们通常结合使用以实现 MVC 模式,将业务逻辑与视图分离,提供更清晰的代码结构和维护性。
了解 MVC模式的基本思想
MVC(Model-View-Controller)模式是一种设计模式,广泛应用于 Web 开发、桌面应用和移动应用中,用于组织代码结构和实现应用程序的分层设计。MVC 模式的基本思想是将应用程序的不同部分分离,以实现关注点分离、提高代码的可维护性和可扩展性。MVC 模式将应用程序划分为三个主要组成部分:
-
模型(Model):模型是应用程序的核心部分,负责处理与数据相关的逻辑。它通常表示应用程序的业务数据、业务规则和数据操作。模型与视图和控制器没有直接的依赖关系。
-
视图(View):视图负责将模型中的数据展示给用户。视图的主要任务是呈现用户界面,显示模型的内容,但不处理业务逻辑。视图从模型获取数据,并将其以适当的格式展示给用户。
-
控制器(Controller):控制器处理用户输入,调用模型进行业务处理,并选择合适的视图来展示处理结果。它充当模型和视图之间的中介,协调用户的请求和响应。
理解 MVC 模式的三个核心组件以及他们的关系:模型、视图和控制器
-
模型负责应用程序的数据和业务逻辑。
-
视图负责显示数据和用户界面。
-
控制器负责处理用户输入,协调模型和视图。
+-------------------+
| User Interface | (View)
| |
| +-------------+ |
| | View | |
| +------+------| |
| | |
| | |
+---------v-------+
|
v
+---------+--------+
| Controller | (Controller)
| |
| +-------------+ |
| | Control | |
| +------+------| |
| | |
| | |
+---------v-------+
|
v
+---------+--------+
| Model | (Model)
| |
| +-------------+ |
| | Business | |
| | Logic | |
| +-------------+ |
+------------------+
掌握如何将业务逻辑、数据和界面分离,实现代码的可维护性和可扩展性
-
使用 MVC 模式:模型(Model)、视图(View)和控制器(Controller),实现代码的分离
-
分层架构
- 表示层(Presentation Layer):
- 处理用户交互,负责显示数据给用户和接收用户输入。
- 业务逻辑层(Business Logic Layer):
- 处理应用程序的业务逻辑。通常包含服务、业务规则和数据处理逻辑。
- 数据访问层(Data Access Layer):
- 负责与数据存储系统(如数据库)进行交互,处理数据的读取和写入。
- 持久化层(Persistence Layer)(可选):
- 专注于数据的持久化操作,如 ORM(对象关系映射)框架。
- 表示层(Presentation Layer):
-
使用设计模式
- DAO(Data Access Object)模式:
- 用于封装与数据源的交互,将数据访问逻辑与业务逻辑分离。
- 服务层(Service Layer)模式:
- 将业务逻辑集中在服务类中,提供统一的业务接口。
- 工厂模式(Factory Pattern):
- 用于创建对象,解耦对象的创建和使用。
- 策略模式(Strategy Pattern):
- 允许在运行时选择算法或行为,将算法的选择与使用分离。
- DAO(Data Access Object)模式:
-
遵循软件设计原则
- SOLID原则。
-
代码组织和模块化
-
封装:
- 将相关的功能封装到独立的模块或类中,确保模块之间的低耦合性。
-
接口设计:
- 设计清晰、简洁的接口,定义模块间的交互方式。
-
分组和命名:
- 合理分组相关的功能和类,并使用清晰的命名来反映其功能和职责。
-
-
使用框架和工具
- 现代开发框架(如 Spring、Django、Angular、React)和工具(如构建工具、自动化测试工具)
理解 MVVM 模式在前端开发中的作用和优势
MVVM 模式的核心组件
- Model(模型)
- 职责:模型负责应用程序的数据和业务逻辑。它表示应用程序的状态,并提供数据操作的接口(如从服务器获取数据、进行数据验证等)。
- 功能:
- 处理和管理应用程序的数据。
- 进行业务逻辑计算。
- 可能与后端服务或数据库交互。
- View(视图)
- 职责:视图负责将模型的数据展示给用户,并提供用户界面(UI)。视图通常由 HTML、CSS 和模板引擎构建。
- 功能:
- 渲染数据:展示模型中的数据。
- 处理用户输入:如按钮点击、表单提交。
- 更新用户界面:响应数据变化和用户交互。
MVVM 模式的优势
- 分离关注点:
- MVVM 模式将用户界面(视图)、数据(模型)和数据与视图之间的交互(视图模型)分离,使得每个组件的职责更加明确,有助于提高代码的可维护性和可读性。
- 双向数据绑定:
- 在 MVVM 模式中,双向数据绑定是一种常见的特性,它简化了视图和模型之间的数据同步,减少了手动更新视图的代码。
- 提高测试性:
- 由于视图模型和模型之间的解耦,测试视图模型和模型变得更加容易。可以独立测试业务逻辑而无需依赖视图。
- 提高重用性:
- 视图模型可以被多个视图重用,从而实现视图逻辑的重用和减少代码重复。
- 响应性和交互性:
- MVVM 模式通过数据绑定机制使得用户界面能够快速响应数据的变化,提供了良好的用户体验。
- 清晰的架构:
- MVVM 模式提供了一种结构化的方式来组织代码,使得应用程序的架构更加清晰,有助于团队协作和长期维护。
掌握 ViewModel 的概念和作用,Vewode 如何实现业务逻辑与视图逻辑分离
-
ViewModel 在 MVVM 模式中充当了视图与模型之间的中介,负责数据绑定、业务逻辑处理和数据转换。通过将业务逻辑和视图逻辑分离,ViewModel 提高了代码的可维护性和可测试性,使得开发、测试和维护更加高效。实现业务逻辑与视图逻辑分离的关键在于定义清晰的职责,使用数据绑定和命令模式,确保视图、视图模型和模型之间的解耦。
-
示例
假设我们有一个待办事项应用,用户可以添加和删除任务:
- Model:
class TaskModel { constructor() { this.tasks = []; } addTask(task) { this.tasks.push(task); } removeTask(task) { this.tasks = this.tasks.filter(t => t !== task); } }
- ViewModel:
class TaskViewModel { constructor(model) { this.model = model; this.tasks = this.model.tasks; this.newTask = ''; } addTask() { if (this.newTask) { this.model.addTask(this.newTask); this.newTask = ''; } } removeTask(task) { this.model.removeTask(task); } }
- View(HTML模板):
<div id="taskList"> <ul> <li v-for="task in tasks">{{ task }} <button @click="removeTask(task)">Remove</button></li> </ul> <input v-model="newTask" placeholder="Add a task"> <button @click="addTask">Add</button> </div>
熟悉数据绑定、事件处理等 MVVM 模式的关键特性
-
数据绑定:是一种将视图(UI)和视图模型(ViewModel)之间的数据进行自动同步的机制。它确保视图中的数据和视图模型中的数据保持一致,并在数据变化时自动更新。
-
类型:
- 单向数据绑定:视图模型的数据更新视图,但视图对数据的更改不会影响视图模型。这种方式适用于从模型到视图的数据传递。
- 双向数据绑定:视图和视图模型之间的数据双向同步,视图中的数据变化会更新视图模型,视图模型中的数据变化会更新视图。这种方式简化了表单数据的管理和实时更新。
-
实现:
- 框架支持:如 Angular 的
ng-model
、Vue.js 的v-model
、React 的useState
等。 - 手动绑定:通过编程方式将视图和视图模型的数据关联起来。
- 框架支持:如 Angular 的
-
示例: 在 Vue.js 中,双向数据绑定可以通过
v-model
实现:<input v-model="message" placeholder="Type something"> <p>The message is: {{ message }}</p>
在这个例子中,
message
是视图模型中的数据,v-model
将输入框与message
进行双向绑定。
-
-
事件处理:是指响应用户在视图中的操作(如点击按钮、提交表单)并将这些操作传递给视图模型进行处理。事件处理机制可以将用户的行为映射到视图模型的方法或逻辑。
-
实现:
- 框架支持:如 Angular 的
ng-click
、Vue.js 的@click
、React 的onClick
等。 - 手动绑定:在纯 JavaScript 中,可以使用
addEventListener
绑定事件处理器。
示例: 在 Vue.js 中,事件处理可以通过
v-on
或@
实现:<button @click="addTask">Add Task</button>
在这个例子中,
@click="addTask"
将按钮的点击事件绑定到视图模型中的addTask
方法。 - 框架支持:如 Angular 的
-
-
命令绑定:是将视图中的操作(如按钮点击)与视图模型中的逻辑或方法绑定起来的机制。通过命令绑定,用户的操作可以直接触发视图模型中的命令,从而简化视图与逻辑的交互。
-
实现:
- 框架支持:如 Angular 的
ng-click
、Vue.js 的事件绑定、Knockout.js 的click
绑定等。
示例: 在 Angular 中,命令绑定可以通过
ng-click
实现:<button ng-click="vm.saveData()">Save</button>
在这个例子中,按钮的点击事件会触发视图模型
vm
中的saveData
方法。 - 框架支持:如 Angular 的
-
-
双向数据:绑定是指视图和视图模型之间的数据能够双向同步,即视图的变化会反映到视图模型,视图模型的变化会反映到视图。这种机制简化了数据管理,减少了代码的复杂性。
-
实现:
- 框架支持:如 Angular 的
ng-model
、Vue.js 的v-model
等。 - 手动实现:通过事件监听和数据更新逻辑手动实现双向绑定。
示例: 在 Vue.js 中,双向数据绑定可以通过
v-model
实现:<input v-model="message" placeholder="Type something"> <p>The message is: {{ message }}</p>
在这个例子中,
v-model
实现了输入框和message
之间的双向绑定,用户输入会实时更新message
,而message
的变化也会实时更新视图。 - 框架支持:如 Angular 的
-
-
依赖注入:是一种设计模式,用于管理对象之间的依赖关系。在 MVVM 模式中,依赖注入可以用于将视图模型注入到视图中,简化对象的创建和管理。
-
实现:
- 框架支持:如 Angular 的依赖注入机制、Vue.js 的插件系统等。
示例: 在 Angular 中,视图模型可以通过依赖注入传递到组件中:
import { Component } from '@angular/core'; import { TaskService } from './task.service'; @Component({ selector: 'app-task', templateUrl: './task.component.html', styleUrls: ['./task.component.css'] }) export class TaskComponent { constructor(private taskService: TaskService) {} }
在这个例子中,
TaskService
被注入到TaskComponent
中,以便于进行数据操作。
了解 Spring 框架的起源和发展
理解 Spring 的核心概念和作用:IOC、AOP
-
-
IoC(控制反转)
控制反转(IoC)是一种设计原则,它通过将对象的创建和依赖管理转移到外部容器来减少对象之间的耦合度。在 Spring 中,IoC 主要通过依赖注入(DI,Dependency Injection)来实现。
作用:
- 解耦:IoC 将对象的创建和管理交给 Spring 容器,从而将应用程序的组件解耦。对象不再直接创建其依赖的对象,而是通过容器注入依赖,使得组件之间的依赖关系更加清晰和灵活。
- 增强测试性:由于依赖关系由容器管理,测试组件时可以方便地模拟或替换依赖,使得单元测试变得更加容易。
- 简化配置:通过使用 XML 配置文件、注解或 Java 配置类,Spring 容器可以自动配置和管理对象的生命周期和依赖关系,简化了配置管理。
实现方式:
- 依赖注入(DI):
- 构造函数注入:通过构造函数将依赖对象注入到目标对象中。
- Setter 方法注入:通过 setter 方法将依赖对象注入到目标对象中。
- 字段注入:通过直接将依赖对象注入到目标对象的字段中(通常使用注解)。
示例: 假设有一个服务类 UserService
需要依赖 UserRepository
:
-
XML 配置:
<bean id="userRepository" class="com.example.UserRepository"/> <bean id="userService" class="com.example.UserService"> <property name="userRepository" ref="userRepository"/> </bean>
-
Java 配置:
@Configuration public class AppConfig { @Bean public UserRepository userRepository() { return new UserRepository(); } @Bean public UserService userService() { return new UserService(userRepository()); } }
-
注解配置:
@Service public class UserService { @Autowired private UserRepository userRepository; } @Component public class UserRepository { }
-
AOP(面向切面编程)
面向切面编程(AOP)是一种编程范式,用于将横切关注点(如日志、安全、事务)从核心业务逻辑中分离出来。在 Spring 中,AOP 提供了通过切面(Aspect)定义和管理这些横切关注点的能力。
作用:
- 模块化横切关注点:AOP 使得可以将日志记录、安全控制、事务管理等横切关注点从业务逻辑中分离出来,从而提高代码的模块化和可维护性。
- 代码复用:通过切面可以将横切关注点的逻辑集中管理,避免了在多个业务方法中重复编写相同的代码。
- 简化事务管理:AOP 可以声明式地管理事务,而不需要在业务逻辑中显式处理事务的开始、提交和回滚。
核心概念:
- 切面(Aspect):
- 切面是一个关注点的模块,它包含了横切逻辑的实现代码。切面由切点(Pointcut)和通知(Advice)组成。
- 切点(Pointcut):
- 切点定义了在哪些连接点(Joinpoint)上应用切面逻辑。连接点通常是方法调用、对象创建等。
- 通知(Advice):
- 通知定义了在切点匹配到的连接点上执行的具体逻辑。通知有不同类型,如前置通知(Before)、后置通知(After)、环绕通知(Around)等。
- 连接点(Joinpoint):
- 连接点是程序执行的一个点,如方法调用、异常处理等。
- 织入(Weaving):
- 织入是将切面应用到目标对象的过程。Spring AOP 在运行时进行织入,即在应用程序运行时将切面逻辑动态地添加到目标对象中。
示例: 假设我们要为所有的
UserService
方法添加日志记录功能:-
切面定义:
@Aspect @Component public class LoggingAspect { @Before("execution(* com.example.UserService.*(..))") public void logBefore(JoinPoint joinPoint) { System.out.println("Method " + joinPoint.getSignature().getName() + " is called"); } }
-
配置 AOP(使用 Java 配置):
@Configuration @EnableAspectJAutoProxy public class AppConfig { @Bean public LoggingAspect loggingAspect() { return new LoggingAspect(); } }
12. 理解 SpringBoot 和 Spring 的关系
自动化单元测试
- 单元测试:
- 单元测试是一种软件测试方法,用于验证软件中最小的测试单元(如函数、方法或类)的正确性。单元测试通常由开发人员在编写代码时编写,并在每次代码更改后执行。
- 自动化测试:
- 自动化测试是使用测试工具和框架编写测试脚本,自动执行测试用例并验证测试结果的过程。自动化测试可以减少手动测试的工作量,提高测试的准确性和重复性。
掌握 JUnit 注解:@Test、@Before,@After 等
-
@Test
:-
作用:标记一个方法为测试方法。JUnit 会执行这个方法以验证其正确性。
-
用法:通常与断言一起使用,来检查方法的行为是否符合预期。
-
示例:
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; public class CalculatorTest { @Test public void testAdd() { Calculator calculator = new Calculator(); int result = calculator.add(2, 3); assertEquals(5, result); } }
-
-
@BeforeEach
(JUnit 5)或@Before
(JUnit 4):-
作用:在每个测试方法执行之前运行的方法。通常用于设置测试环境,如初始化测试数据或创建测试对象。
-
用法:在测试类中定义一个方法,并使用
@BeforeEach
注解(JUnit 5)或@Before
注解(JUnit 4)。 -
示例:
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; public class CalculatorTest { private Calculator calculator; @BeforeEach public void setUp() { calculator = new Calculator(); } @Test public void testAdd() { int result = calculator.add(2, 3); assertEquals(5, result); } }
-
-
@AfterEach
(JUnit 5)或@After
(JUnit 4):-
作用:在每个测试方法执行之后运行的方法。通常用于清理测试环境,如关闭数据库连接或释放资源。
-
用法:在测试类中定义一个方法,并使用
@AfterEach
注解(JUnit 5)或@After
注解(JUnit 4)。 -
示例:
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; public class CalculatorTest { private Calculator calculator; @BeforeEach public void setUp() { calculator = new Calculator(); } @Test public void testAdd() { int result = calculator.add(2, 3); assertEquals(5, result); } @AfterEach public void tearDown() { calculator = null; } }
-
-
@BeforeAll
(JUnit 5)或@BeforeClass
(JUnit 4):-
作用:在所有测试方法执行之前运行一次的方法。通常用于设置共享的测试环境,如初始化静态资源或配置。
-
用法:在测试类中定义一个静态方法,并使用
@BeforeAll
注解(JUnit 5)或@BeforeClass
注解(JUnit 4)。 -
示例:
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; public class DatabaseTest { @BeforeAll public static void setUpDatabase() { // 初始化数据库连接 } @Test public void testDatabaseQuery() { // 测试数据库查询 } }
-
-
@AfterAll
(JUnit 5)或@AfterClass
(JUnit 4):-
作用:在所有测试方法执行之后运行一次的方法。通常用于清理共享的测试环境,如关闭数据库连接或释放资源。
-
用法:在测试类中定义一个静态方法,并使用
@AfterAll
注解(JUnit 5)或@AfterClass
注解(JUnit 4)。 -
示例:
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; public class DatabaseTest { @BeforeAll public static void setUpDatabase() { // 初始化数据库连接 } @Test public void testDatabaseQuery() { // 测试数据库查询 } @AfterAll public static void tearDownDatabase() { // 关闭数据库连接 } }
-
-
@Disabled
(JUnit 5)或@Ignore
(JUnit 4):-
作用:暂时禁用某个测试方法或测试类。常用于跳过不必要或有问题的测试。
-
用法:在测试方法或测试类上使用
@Disabled
注解(JUnit 5)或@Ignore
注解(JUnit 4)。 -
示例:
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; public class CalculatorTest { @Test @Disabled("Test is disabled for now") public void testSubtraction() { // 这个测试方法被禁用 } }
-
-
@ParameterizedTest
(JUnit 5):-
作用:用于参数化测试,即用不同的输入数据执行同一个测试方法。可以提高测试的覆盖率,减少重复代码。
-
用法:结合
@ValueSource
、@CsvSource
或其他参数源注解使用。 -
示例:
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import static org.junit.jupiter.api.Assertions.assertTrue; public class StringTest { @ParameterizedTest @ValueSource(strings = {"hello", "world", "JUnit"}) public void testStringLength(String input) { assertTrue(input.length() > 0); } }
-
理解 DBUnit 工作原理
- 测试数据管理: DBUnit 通过提供一个简单的接口来管理测试数据,允许用户在测试开始之前将数据加载到数据库中,并在测试结束后将数据库恢复到初始状态。它可以与 JUnit 和 TestNG 等测试框架集成,以便在测试过程中使用数据库。
- 数据源配置: DBUnit 通过数据源(如 JDBC 数据源)连接到数据库。测试数据可以从不同的数据源加载,如 XML、CSV、Excel 等格式的文件,DBUnit 会将这些数据源转换成数据库中的数据表。
- 测试数据加载: 在测试开始之前,DBUnit 可以从数据源文件(如 XML 文件)加载测试数据到数据库中。这些数据用于模拟实际数据库中的状态,以便测试应用程序的行为。
- 测试执行: 测试执行过程中,DBUnit 将使用已经加载的测试数据进行操作和验证。测试方法可以对数据库进行各种操作(如插入、更新、删除等),并验证数据库中的状态是否符合预期。
- 数据恢复: 测试执行后,DBUnit 可以将数据库恢复到初始状态,以确保每个测试方法都在干净的环境中执行。这样可以避免测试之间的相互干扰,并确保测试的独立性和可靠性。
掌握 JUnit 中如何使用断言
-
JUnit 5 的断言
-
assertEquals(expected, actual)
:-
作用:验证实际值是否等于预期值。
-
示例:
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; public class CalculatorTest { @Test public void testAddition() { Calculator calculator = new Calculator(); int result = calculator.add(2, 3); assertEquals(5, result, "Addition result should be 5"); } }
-
-
assertNotEquals(unexpected, actual)
:-
作用:验证实际值是否不等于预期值。
-
示例:
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertNotEquals; public class CalculatorTest { @Test public void testSubtraction() { Calculator calculator = new Calculator(); int result = calculator.subtract(5, 3); assertNotEquals(1, result, "Subtraction result should not be 1"); } }
-
-
assertTrue(condition)
:-
作用:验证条件是否为 true。
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertTrue; public class StringTest { @Test public void testStringIsEmpty() { String str = ""; assertTrue(str.isEmpty(), "String should be empty"); } }
-
-
assertFalse(condition)
:-
作用:验证条件是否为 false。
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertFalse; public class StringTest { @Test public void testStringIsNotEmpty() { String str = "Hello"; assertFalse(str.isEmpty(), "String should not be empty"); } }
-
-
assertNull(actual)
:-
作用:验证实际值是否为 null。
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertNull; public class ObjectTest { @Test public void testObjectIsNull() { Object obj = null; assertNull(obj, "Object should be null"); } }
-
-
assertNotNull(actual)
:-
作用:验证实际值是否不为 null。
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertNotNull; public class ObjectTest { @Test public void testObjectIsNotNull() { Object obj = new Object(); assertNotNull(obj, "Object should not be null"); } }
-
-
assertThrows(expectedType, executable)
:-
作用:验证指定的代码块是否抛出预期类型的异常。
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertThrows; public class CalculatorTest { @Test public void testDivisionByZero() { Calculator calculator = new Calculator(); assertThrows(ArithmeticException.class, () -> calculator.divide(1, 0), "Expected ArithmeticException"); } }
-
-
assertAll(executable...)
:-
作用:组合多个断言并在所有断言执行后检查结果。它提供了对多个断言的聚合报告。
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; public class CalculatorTest { @Test public void testMultipleAssertions() { Calculator calculator = new Calculator(); int result1 = calculator.add(2, 3); int result2 = calculator.subtract(5, 3); assertAll("Calculator", () -> assertEquals(5, result1, "Addition result should be 5"), () -> assertEquals(2, result2, "Subtraction result should be 2") ); } }
-
掌握 Mocking, Stubbing
-
-
Stubbing
Stubbing 是在测试中模拟某个对象的方法,以便返回预定义的结果。它主要用于控制测试的输出,确保测试能够在预期的环境下执行。
特点:
- 预设行为:你可以定义当特定方法被调用时返回什么值。
- 隔离依赖:可以隔离测试对象和其依赖项,确保测试的可预测性。
- 简单实现:适用于返回固定值或预定义行为的场景。
示例(使用 Mockito 进行 Stubbing):
import static org.mockito.Mockito.*; public class ExampleTest { @Test public void testService() { // 创建一个 Mock 对象 Dependency mockDependency = mock(Dependency.class); // 设定当调用 mockDependency.method() 时返回特定值 when(mockDependency.method()).thenReturn("Mocked Value"); // 使用 mockDependency 进行测试 Service service = new Service(mockDependency); String result = service.performAction(); assertEquals("Expected Result", result); } }
-
Mocking
Mocking 是一种创建模拟对象的技术,用于验证对象的方法是否被调用。Mock 对象不仅模拟方法的行为,还可以验证方法的调用次数和顺序。
特点:
- 行为验证:除了设定预期的返回值,还可以验证方法是否被调用、调用的次数以及调用的顺序。
- 复杂验证:适用于需要验证交互的场景,比如确认某个方法是否被调用过。
- 对象行为:可以模拟对象的行为,进行更深入的验证。
示例(使用 Mockito 进行 Mocking):
import static org.mockito.Mockito.*; public class ExampleTest { @Test public void testServiceInteraction() { // 创建一个 Mock 对象 Dependency mockDependency = mock(Dependency.class); // 调用一个方法,不设置返回值 Service service = new Service(mockDependency); service.performAction(); // 验证 mockDependency.method() 方法是否被调用过 verify(mockDependency).method(); } }
-
Mocking 和 Stubbing 的区别
- 目的:
- Stubbing:用于模拟方法的返回值,控制测试的输入数据。
- Mocking:用于验证对象的行为,检查方法是否按预期被调用。
- 实现:
- Stubbing:关注于设置预期的返回值或行为。
- Mocking:关注于验证方法调用的交互和顺序。
- 工具支持:
- Stubbing:常见于测试框架如 Mockito、EasyMock 中,通常用于简单的返回值设置。
- Mocking:Mockito、EasyMock 和其他 Mock 框架提供了丰富的验证功能,适用于更复杂的测试需求。
- 目的: