Java开发笔记
鉴于现实原因,笔记仅局限于Java 8版本。
Stream 流
Java 8 引入了 Stream API,它提供了一种高效且表达性强的方式来处理数据集合。Stream API 支持将数据集合转换为流,然后对其进行各种操作,如筛选、排序、聚合等。
创建 Stream
可以通过多种方式创建 Stream:
从集合或数组创建:
List<String> words = Arrays.asList("apple", "banana", "cherry", "date");
Stream<String> stream = words.stream();
从值创建:
Stream<String> stream = Stream.of("apple", "banana", "cherry", "date");
使用 Stream
工厂方法:
Stream<Integer> evenNumbers = Stream.iterate(0, n -> n + 2).limit(10);
IntStream intStream = IntStream.range(0, 10);
中间操作
中间操作可以对 Stream 进行处理,但不会立即产生结果:
filter
:筛选元素words.stream() .filter(s -> s.startsWith("a")) .forEach(System.out::println); // apple, cherry
map
:转换元素words.stream() .map(String::toUpperCase) .forEach(System.out::println); // APPLE, BANANA, CHERRY, DATE
sorted
:排序元素words.stream() .sorted() .forEach(System.out::println); // apple, banana, cherry, date
终止操作(Terminal Operations)
终止操作会消耗 Stream,并产生最终的结果:
forEach
:对每个元素执行操作words.stream() .forEach(System.out::println);
collect
:将元素收集到一个新的集合中List<String> filteredWords = words.stream() .filter(s -> s.startsWith("a")) .collect(Collectors.toList());
reduce
:聚合元素String concatenated = words.stream() .reduce("", (s1, s2) -> s1 + " " + s2);
count
:返回 Stream 中元素的数量long count = words.stream().count();
并行 Stream
可以通过将顺序 Stream 转换为并行 Stream 来并行处理数据:
List<Integer> numbers = IntStream.rangeClosed(1, 1000).boxed().collect(Collectors.toList());
long sum = numbers.parallelStream().mapToInt(Integer::intValue).sum();
组合操作
可以将多个操作组合在一起,形成一个流水线:
List<String> filteredAndSorted = words.stream()
.filter(s -> s.startsWith("a"))
.sorted()
.collect(Collectors.toList());
注意事项
- Stream 操作是惰性的,只有当执行终止操作时才会执行。
- Stream 只能被消费一次,一旦终止操作被执行,Stream 就会被关闭。
- Stream API 是对集合操作的补充,而不是替代。
Stream API 提供了一种声明式的处理方式,可以写出更简洁、更易于理解的代码。
处理复杂对象(Stream API 案例)
在Java中,Stream API同样可以用来处理对象集合。处理对象时,你可以使用Stream的各种操作来执行复杂的查询、过滤、映射、聚合等操作。以下是一些处理对象的常见操作:
筛选(Filtering)
假设你有一个Person
类,你想从Person
对象的列表中筛选出所有年龄大于30岁的对象:
public class Person {
private String name;
private int age;
// 构造函数、getter和setter
}
List<Person> people = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 35),
new Person("Charlie", 30)
);
List<Person> filteredPeople = people.stream()
.filter(person -> person.getAge() > 30)
.collect(Collectors.toList());
映射(Mapping)
如果你想将Person
对象的列表转换为名字的列表:
List<String> names = people.stream()
.map(Person::getName)
.collect(Collectors.toList());
排序(Sorting)
对Person
对象的列表按照年龄进行排序:
List<Person> sortedPeople = people.stream()
.sorted(Comparator.comparingInt(Person::getAge))
.collect(Collectors.toList());
聚合(Aggregating)
计算Person
对象列表中所有人的年龄总和:
int totalAge = people.stream()
.mapToInt(Person::getAge)
.sum();
或者使用reduce
方法:
int totalAge = people.stream()
.mapToInt(Person::getAge)
.reduce(0, (sum, age) -> sum + age);
查找(Finding)
查找第一个年龄大于30岁的Person
对象:
Optional<Person> firstOldPerson = people.stream()
.filter(person -> person.getAge() > 30)
.findFirst();
收集(Collecting)
将Person
对象的列表收集到一个新的ArrayList
中:
List<Person> personList = people.stream()
.collect(Collectors.toCollection(ArrayList::new));
或者直接收集到列表:
List<Person> personList = people.stream()
.collect(Collectors.toList());
分组(Grouping)
按照年龄分组Person
对象:
Map<Integer, List<Person>> peopleByAge = people.stream()
.collect(Collectors.groupingBy(Person::getAge));
部分聚合(Partial Aggregating)
计算Person
对象列表中的最大年龄:
OptionalInt maxAge = people.stream()
.mapToInt(Person::getAge)
.max();
计数(Counting)
计算Person
对象列表中满足条件的对象数量:
long count = people.stream()
.filter(person -> person.getAge() > 30)
.count();
去重(Distincting)
获取不重复的Person
对象列表:
List<Person> distinctPeople = people.stream()
.distinct()
.collect(Collectors.toList());
这些操作可以组合使用,以实现更复杂的数据处理逻辑。使用Stream API处理对象时,你可以轻松地将操作链式调用,编写出更简洁、更易读的代码。
Optional
Optional
类是 Java 8 引入的一个容器类,它用于包含或不包含非空值。Optional
类的设计目的是减少程序中 null
引用的使用,从而减少 NullPointerException
的风险。以下是 Optional
类的一些核心用法:
创建 Optional
对象
Optional.of(T value)
: 创建一个非空的Optional
对象。如果传递的参数为null
,会抛出NullPointerException
。Optional.ofNullable(T value)
: 创建一个可能为空的Optional
对象。如果传递的参数为null
,会创建一个空的Optional
对象。Optional.empty()
: 创建一个空的Optional
对象。
使用 Optional
对象
isPresent()
: 返回一个布尔值,指示Optional
是否包含非空值。get()
: 如果Optional
包含非空值,则返回该值;否则抛出NoSuchElementException
。ifPresent(Consumer<T> consumer)
: 如果Optional
包含非空值,则将该值传递给consumer
进行消费。orElse(T other)
: 如果Optional
包含非空值,则返回该值;否则返回指定的其它值。orElseGet(Supplier<T> other)
: 如果Optional
包含非空值,则返回该值;否则返回other
供应器提供的值。orElseThrow(Supplier<? extends X> exceptionSupplier)
: 如果Optional
包含非空值,则返回该值;否则抛出exceptionSupplier
提供的异常。
转换 Optional
对象
map(Function<T, U> mapper)
: 如果Optional
包含非空值,则将其映射到另一个对象;否则返回一个空的Optional
。flatMap(Function<T, Optional<U>> mapper)
: 如果Optional
包含非空值,则将其映射到另一个Optional
;否则返回一个空的Optional
。
示例代码
public class OptionalExample {
public static void main(String[] args) {
Optional<String> optionalString = Optional.ofNullable("Hello, World!");
// 使用 ifPresent 消费 Optional 中的值
optionalString.ifPresent(System.out::println); // 输出 "Hello, World!"
// 使用 get 获取 Optional 中的值
String value = optionalString.get(); // 如果 optionalString 为空,这里会抛出异常
// 使用 orElse 提供默认值
String orElseValue = optionalString.orElse("Default Value");
// 使用 orElseGet 获取 Supplier 提供的默认值
String orElseGetValue = optionalString.orElseGet(() -> "Default Value from Supplier");
// 使用 orElseThrow 抛出异常
try {
String orElseThrowValue = Optional.<String>empty()
.orElseThrow(IllegalStateException::new);
} catch (IllegalStateException e) {
e.printStackTrace();
}
// 使用 map 转换 Optional 中的值
Optional<Integer> optionalLength = optionalString.map(String::length);
int length = optionalLength.orElse(0); // 如果 optionalString 为空,返回 0
// 使用 flatMap 处理嵌套的 Optional
Optional<Optional<String>> nestedOptional = Optional.of(Optional.of("Nested"));
Optional<String> flatOptional = nestedOptional.flatMap(outer -> outer);
}
}
Optional
类的引入使得处理空值变得更加安全和方便,它鼓励开发者提前考虑和处理空值的情况,从而提高代码质量。
DTO转换
DTO(Data Transfer Object)转换是软件开发中的一个常见需求,特别是在分层架构中。DTO用于在应用程序的不同层之间传输数据,通常从数据库实体转换而来。以下是DTO转换的最佳案例,展示了如何在Java中使用DTO进行转换。
使用手动映射
在没有使用任何映射工具的情况下,手动映射是最基础的方法。
使用BeanUtils
Apache Commons BeanUtils 提供了一个快速复制属性的方法。
import org.apache.commons.beanutils.BeanUtils;
public class UserService {
public UserDTO convertToUserDTO(User user) {
UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(userDTO, user);
return userDTO;
}
}
使用ModelMapper
ModelMapper 是一个强大的映射库,可以轻松地在对象之间映射属性。
import com.rcz.webexample.domain.User;
import com.rcz.webexample.utils.DTO.UserDTO;
import org.modelmapper.ModelMapper;
/**
* modelmapper 转换器
*/
public class UserModelMapperConvert {
private final ModelMapper modelMapper = new ModelMapper();
public UserDTO convert(User user) {
return modelMapper.map(user, UserDTO.class);
}
}
使用MapStruct
MapStruct 是一个代码生成器,它基于约定优于配置的原则,自动生成源对象和目标对象之间的映射代码。
import com.rcz.webexample.domain.User;
import com.rcz.webexample.utils.DTO.UserDTO;
import com.rcz.webexample.utils.VO.UserVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper(componentModel = "spring")
public interface UserConvert {
UserConvert INSTANCE = Mappers.getMapper(UserConvert.class);
User convert(UserDTO userDTO);
List<UserVO> convert(List<User> user);
}
在 UserMapper
接口中定义了转换方法,MapStruct 会在编译时自动生成实现类。
这个库的使用和lombok有时会有冲突,需要指定注解处理的顺序才可以,并且jdk8和更高版本的引入方式不同,需要格外注意。
简单来说就是,虽然好用,因为他的映射是包含了类型安全和高性能的代码生成的,但是需要谨慎使用。
使用Spring Boot的转换服务
Spring Boot 提供了一个转换服务,可以很方便地在不同对象之间进行转换。
import com.rcz.webexample.domain.User;
import com.rcz.webexample.utils.DTO.UserDTO;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
/**
* Spring Boot的转换服务
*/
@Component
public class UserToUserDTOConverter implements Converter<User, UserDTO> {
@Override
public UserDTO convert(User user) {
UserDTO userDTO = new UserDTO();
userDTO.setUserNo(user.getUserNo());
userDTO.setUsername(user.getUsername());
userDTO.setNickname(user.getNickname());
userDTO.setAddress(user.getAddress());
userDTO.setAvatar(user.getAvatar());
userDTO.setFamilyNo(user.getFamilyNo());
userDTO.setCommunityNo(user.getCommunityNo());
userDTO.setOccupation(user.getOccupation());
userDTO.setPoliticalOutlook(user.getPoliticalOutlook());
userDTO.setMaritalStatus(user.getMaritalStatus());
userDTO.setGender(user.getGender());
userDTO.setAge(user.getAge());
userDTO.setPhone(user.getPhone());
userDTO.setEmail(user.getEmail());
userDTO.setRemark(user.getRemark());
return userDTO;
}
}
springboot本身的转换服务有一个很尴尬的点,那就是一个类只提供一组对象的单向转换,这就不是很方便了。但是好处是不需要引入额外的库,能在某种程度上简便一些。
API文档
这里主要讨论Swagger 3 在springboot 2.x与3.x版本下的差别。
一般的集成swagger是使用springfox的包,但是他已经不再维护,因为一般的推荐使用 SpringDoc,这是目前推荐的方式来集成 OpenAPI 3(即 Swagger 3)。
springboot 2.x模式下,还可以继续使用springfox的包.
<!-- swagger3-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
但是3.x模式无法使用,必须使用springdoc提供的包,并且其中关于字段注解、方法注解,也有了很多改变。
<!-- swagger -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.5.0</version>
</dependency>
定时任务
定时任务需要考虑的内容包括是否只是简单的周期性任务,也就是业务具体内容,以及是否需要持久化,一些定时任务设计时考虑的因素,任务执行频率和到期时间检查的逻辑,特别是对于分布式的任务,尽量考虑使用分布式的任务调度系统。
场景:根据任务列表中的任务ID创建定时任务,以检查任务的到期时间并执行相应的操作。
java.util.Timer
类
使用 Timer
类,你可以为每个任务创建一个 TimerTask
,并根据每个任务的到期时间安排任务。
List<Task> tasks = getTaskList(); // 假设这是你从数据库或其他地方获取的任务列表
tasks.forEach(task -> {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
checkAndCompleteTask(task.getId());
}
}, task.getDueTime().getTime());
});
ScheduledExecutorService
接口
使用 ScheduledExecutorService
,你可以为每个任务安排一个单次执行的任务。
List<Task> tasks = getTaskList();
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(tasks.size());
tasks.forEach(task -> {
scheduler.schedule(() -> checkAndCompleteTask(task.getId()), task.getDueTime().getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
});
Spring 框架的 @Scheduled
注解
在 Spring 中,你可以创建一个方法,遍历任务列表,为每个任务安排一个定时任务。
@Component
public class TaskScheduler {
@Scheduled(fixedDelay = 10000) // 每10秒检查一次
public void checkTasks() {
List<Task> tasks = getTaskList();
tasks.forEach(task -> {
if (isDue(task.getId())) {
completeTask(task.getId());
}
});
}
}
Quartz
使用 Quartz,你可以为每个任务创建一个 Job,并根据到期时间触发。
List<Task> tasks = getTaskList();
Scheduler scheduler = getScheduler(); // 获取 Quartz 的调度器
tasks.forEach(task -> {
JobDetail job = buildJobDetail(task.getId());
Trigger trigger = buildTrigger(task.getDueTime());
scheduler.scheduleJob(job, trigger);
});
Spring Boot 的 @Scheduled
注解
在 Spring Boot 中,你可以使用 @Scheduled
注解来创建一个定时任务,该任务定期检查所有任务的到期时间。
@Component
public class TaskScheduler {
@Scheduled(cron = "0 * * * * *") // 每分钟检查一次
public void checkAndCompleteTasks() {
List<Task> tasks = getTaskList();
tasks.forEach(task -> {
if (isDue(task.getId())) {
completeTask(task.getId());
}
});
}
}
Timer
类 和ScheduledExecutorService
都适用于简单的周期性任务,但它们不支持持久化,如果应用重启,所有未执行的任务都会丢失。- Spring 的
@Scheduled
注解 提供了更简洁的代码和易于管理的配置,但它同样不支持任务持久化。 - Quartz 是一个功能强大的调度库,支持任务持久化和复杂的调度策略,但它的配置相对复杂。