Java最全好用到爆的 Java 技巧,java三大特性面试回答

最后

针对最近很多人都在面试,我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。

最新整理面试题
在这里插入图片描述

上述的面试题答案都整理成文档笔记。也还整理了一些面试资料&最新2021收集的一些大厂的面试真题

最新整理电子书

在这里插入图片描述

最新整理大厂面试文档

在这里插入图片描述

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

}

@Override

protected UserInputDTO doBackward(User user) {

UserInputDTO userInputDTO = new UserInputDTO();

BeanUtils.copyProperties(user,userInputDTO);

return userInputDTO;

}

}

看了这部分代码以后,你可能会问,那逆向转化会有什么用呢?其实我们有很多小的业务需求中,入参和出参是一样的,那么我们变可以轻松的进行转化,我将上边所提到的 UserInputDTO 和 UserOutputDTO 都转成 UserDTO 展示给大家。

DTO:

public class UserDTO {

private String username;

private int age;

public String getUsername() {

return username;

}

public void setUsername(String username) {

this.username = username;

}

public int getAge() {

return age;

}

public void setAge(int age) {

this.age = age;

}

public User convertToUser(){

UserDTOConvert userDTOConvert = new UserDTOConvert();

User convert = userDTOConvert.convert(this);

return convert;

}

public UserDTO convertFor(User user){

UserDTOConvert userDTOConvert = new UserDTOConvert();

UserDTO convert = userDTOConvert.reverse().convert(user);

return convert;

}

private static class UserDTOConvert extends Converter<UserDTO, User> {

@Override

protected User doForward(UserDTO userDTO) {

User user = new User();

BeanUtils.copyProperties(userDTO,user);

return user;

}

@Override

protected UserDTO doBackward(User user) {

UserDTO userDTO = new UserDTO();

BeanUtils.copyProperties(user,userDTO);

return userDTO;

}

}

}

API:

@PostMapping

public UserDTO addUser(UserDTO userDTO){

User user = userDTO.convertToUser();

User saveResultUser = userService.addUser(user);

UserDTO result = userDTO.convertFor(saveResultUser);

return result;

}

当然,上述只是表明了转化方向的正向或逆向,很多业务需求的出参和入参的 DTO 对象是不同的,那么你需要更明显地告诉程序:逆向是无法调用的。

private static class UserDTOConvert extends Converter<UserDTO, User> {

@Override

protected User doForward(UserDTO userDTO) {

User user = new User();

BeanUtils.copyProperties(userDTO,user);

return user;

}

@Override

protected UserDTO doBackward(User user) {

throw new AssertionError(“不支持逆向转化方法!”);

}

}

看一下 doBackward 方法,直接抛出了一个断言异常,而不是业务异常,这段代码告诉代码的调用者,这个方法不是准你调用的,如果你调用,我就 “断言” 你调用错误了。

Bean 的验证

如果你认为我上边写的那个添加用户 API 写的已经非常完美了,那只能说明你还不是一个优秀的程序员。我们应该保证任何数据的入参到方法体内都是合法的。

为什么要验证

很多人会告诉我,如果这些 API 是提供给前端进行调用的,前端都会进行验证啊,你为什还要验证?

其实答案是这样的,我从不相信任何调用我 API 或者方法的人,比如前端验证失败了,或者某些人通过一些特殊的渠道(比如 Charles 进行抓包),直接将数据传入到我的 API,那我仍然进行正常的业务逻辑处理,那么就有可能产生脏数据!

“对于脏数据的产生一定是致命”,这句话希望大家牢记在心,再小的脏数据也有可能让你找几个通宵!

JSR 303 验证

Hibernate 提供的 JSR 303 实现,我觉得目前仍然是很优秀的,具体如何使用,我不想讲,因为谷歌上你可以搜索出很多答案!

再以上班的 API 实例进行说明,我们现在对 DTO 数据进行检查:

public class UserDTO {

@NotNull

private String username;

@NotNull

private int age;

//其他代码略

}

API 验证:

@PostMapping

public UserDTO addUser(@Valid UserDTO userDTO){

User user = userDTO.convertToUser();

User saveResultUser = userService.addUser(user);

UserDTO result = userDTO.convertFor(saveResultUser);

return result;

}

我们需要将验证结果传给前端,这种异常应该转化为一个 API 异常(带有错误码的异常)。

@PostMapping

public UserDTO addUser(@Valid UserDTO userDTO, BindingResult bindingResult){

checkDTOParams(bindingResult);

User user = userDTO.convertToUser();

User saveResultUser = userService.addUser(user);

UserDTO result = userDTO.convertFor(saveResultUser);

return result;

}

private void checkDTOParams(BindingResult bindingResult){

if(bindingResult.hasErrors()){

//throw new 带验证码的验证错误异常

}

}

BindingResult 是 Spring MVC 验证 DTO 后的一个结果集,可以参考 Spring 官方文档(spring.io/)。

检查参数后,可以抛出一个 “带验证码的验证错误异常”。

上边的 DTO 代码,已经让我看的很累了,我相信读者也是一样,看到那么多的 Getter 和 Setter 方法,太烦躁了,那时候有什么方法可以简化这些呢。

请拥抱 Lombok,它会帮助我们解决一些让我们很烦躁的问题。

去掉 Setter 和 Getter

其实这个标题,我不太想说,因为网上太多,但是因为很多人告诉我,他们根本就不知道 Lombok 的存在,所以为了让读者更好的学习,我愿意写这样一个例子:

@Setter

@Getter

public class UserDTO {

@NotNull

private String username;

@NotNull

private int age;

public User convertToUser(){

UserDTOConvert userDTOConvert = new UserDTOConvert();

User convert = userDTOConvert.convert(this);

return convert;

}

public UserDTO convertFor(User user){

UserDTOConvert userDTOConvert = new UserDTOConvert();

UserDTO convert = userDTOConvert.reverse().convert(user);

return convert;

}

private static class UserDTOConvert extends Converter<UserDTO, User> {

@Override

protected User doForward(UserDTO userDTO) {

User user = new User();

BeanUtils.copyProperties(userDTO,user);

return user;

}

@Override

protected UserDTO doBackward(User user) {

throw new AssertionError(“不支持逆向转化方法!”);

}

}

}

看到了吧,烦人的 Getter 和 Setter 方法已经去掉了。

但是上边的例子根本不足以体现 lombok 的强大。我希望写一些网上很难查到,或者很少人进行说明的 lombok 的使用以及在使用时程序语义上的说明。

比如:@Data,@AllArgsConstructor,@NoArgsConstructor… 这些我就不进行一一说明了,请大家自行查询资料。

Bean 中的链式风格

什么是链式风格?我来举个例子,看下面这个 Student 的 Bean:

public class Student {

private String name;

private int age;

public String getName() {

return name;

}

public Student setName(String name) {

this.name = name;

return this;

}

public int getAge() {

return age;

}

public Student setAge(int age) {

return this;

}

}

仔细看一下 set 方法,这样的设置便是 chain 的 style,调用的时候,可以这样使用:

Student student = new Student()

.setAge(24)

.setName(“zs”);

相信合理使用这样的链式代码,会更多的程序带来很好的可读性,那看一下如果使用 Lombok 进行改善呢,请使用 @Accessors(chain = true),看如下代码:

@Accessors(chain = true)

@Setter

@Getter

public class Student {

private String name;

private int age;

}

这样就完成了一个对于 Bean 来讲很友好的链式操作。

静态构造方法

静态构造方法的语义和简化程度真的高于直接去 new 一个对象。比如 new 一个 List 对象,过去的使用是这样的:

List list = new ArrayList<>();

看一下 Guava 中的创建方式:

List list = Lists.newArrayList();

Lists 命名是一种约定 (俗话说:约定优于配置),它是指 Lists 是 List 这个类的一个工具类,那么使用 List 的工具类去产生 List,这样的语义是不是要比直接 new 一个子类来的更直接一些呢,答案是肯定的,再比如如果有一个工具类叫做 Maps,那你是否想到了创建 Map 的方法呢:

HashMap<String, String> objectObjectHashMap = Maps.newHashMap();

好了,如果你理解了我说的语义,那么,你已经向成为 Java 程序员更近了一步了。

再回过头来看刚刚的 Student,很多时候,我们去写 Student 这个 bean 的时候,他会有一些必输字段,比如 Student 中的 name 字段,一般处理的方式是将 name 字段包装成一个构造方法,只有传入 name 这样的构造方法,才能创建一个 Student 对象。

接上上边的静态构造方法和必传参数的构造方法,使用 Lombok 将更改成如下写法(@RequiredArgsConstructor 和 @NonNull):

@Accessors(chain = true)

@Setter

@Getter

@RequiredArgsConstructor(staticName = “ofName”)

public class Student {

@NonNull private String name;

private int age;

}

测试代码:

Student student = Student.ofName(“zs”);

这样构建出的 bean 语义是否要比直接 new 一个含参的构造方法 (包含 name 的构造方法) 要好很多。

当然,看过很多源码以后,我想相信将静态构造方法 ofName 换成 of 会先的更加简洁:

@Accessors(chain = true)

@Setter

@Getter

@RequiredArgsConstructor(staticName = “of”)

public class Student {

@NonNull private String name;

private int age;

}

测试代码:

Student student = Student.of(“zs”);

当然它仍然是支持链式调用的:

Student student = Student.of(“zs”).setAge(24);

这样来写代码,真的很简洁,并且可读性很强。

使用 Builder

Builder 模式我不想再多解释了,读者可以看一下 Head First(《设计模式》)的建造者模式。

今天其实要说的是一种变种的 Builder 模式,那就是构建 Bean 的 Builder 模式,其实主要的思想是带着大家一起看一下 Lombok 给我们带来了什么。

看一下 Student 这个类的原始 Builder 状态:

public class Student {

private String name;

private int age;

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public int getAge() {

return age;

}

public void setAge(int age) {

this.age = age;

}

public static Builder builder(){

return new Builder();

}

public static class Builder{

private String name;

private int age;

public Builder name(String name){

this.name = name;

return this;

}

public Builder age(int age){

this.age = age;

return this;

}

public Student build(){

Student student = new Student();

student.setAge(age);

student.setName(name);

return student;

}

}

}

调用方式:

Student student = Student.builder().name(“zs”).age(24).build();

这样的 Builder 代码,让我是在恶心难受,于是我打算用 Lombok 重构这段代码:

@Builder

public class Student {

private String name;

private int age;

}

调用方式:

Student student = Student.builder().name(“zs”).age(24).build();

代理模式

正如我们所知的,在程序中调用 Rest 接口是一个常见的行为动作,如果你和我一样使用过 Spring 的 RestTemplate,我相信你会我和一样,对它抛出的非 HTTP 状态码异常深恶痛绝。

所以我们考虑将 RestTemplate 最为底层包装器进行包装器模式的设计:

public abstract class FilterRestTemplate implements RestOperations {

protected volatile RestTemplate restTemplate;

protected FilterRestTemplate(RestTemplate restTemplate){

this.restTemplate = restTemplate;

}

//实现RestOperations所有的接口

}

然后再由扩展类对 FilterRestTemplate 进行包装扩展:

public class ExtractRestTemplate extends FilterRestTemplate {

private RestTemplate restTemplate;

public ExtractRestTemplate(RestTemplate restTemplate) {

super(restTemplate);

this.restTemplate = restTemplate;

}

public RestResponseDTO postForEntityWithNoException(String url, Object request, Class responseType, Object… uriVariables)

throws RestClientException {

RestResponseDTO restResponseDTO = new RestResponseDTO();

ResponseEntity tResponseEntity;

try {

tResponseEntity = restTemplate.postForEntity(url, request, responseType, uriVariables);

restResponseDTO.setData(tResponseEntity.getBody());

restResponseDTO.setMessage(tResponseEntity.getStatusCode().name());

restResponseDTO.setStatusCode(tResponseEntity.getStatusCodeValue());

}catch (Exception e){

restResponseDTO.setStatusCode(RestResponseDTO.UNKNOWN_ERROR);

restResponseDTO.setMessage(e.getMessage());

restResponseDTO.setData(null);

}

return restResponseDTO;

}

}

包装器 ExtractRestTemplate 很完美的更改了异常抛出的行为,让程序更具有容错性。在这里我们不考虑 ExtractRestTemplate 完成的功能,让我们把焦点放在 FilterRestTemplate 上,“实现 RestOperations 所有的接口”,这个操作绝对不是一时半会可以写完的,当时在重构之前我几乎写了半个小时,如下:

public abstract class FilterRestTemplate implements RestOperations {

protected volatile RestTemplate restTemplate;

protected FilterRestTemplate(RestTemplate restTemplate) {

this.restTemplate = restTemplate;

}

@Override

public T getForObject(String url, Class responseType, Object… uriVariables) throws RestClientException {

return restTemplate.getForObject(url,responseType,uriVariables);

}

@Override

public T getForObject(String url, Class responseType, Map<String, ?> uriVariables) throws RestClientException {

return restTemplate.getForObject(url,responseType,uriVariables);

}

@Override

public T getForObject(URI url, Class responseType) throws RestClientException {

return restTemplate.getForObject(url,responseType);

}

@Override

public ResponseEntity getForEntity(String url, Class responseType, Object… uriVariables) throws RestClientException {

return restTemplate.getForEntity(url,responseType,uriVariables);

}

//其他实现代码略。。。

}

我相信你看了以上代码,你会和我一样觉得恶心反胃,后来我用 Lombok 提供的代理注解优化了我的代码(@Delegate):

@AllArgsConstructor

public abstract class FilterRestTemplate implements RestOperations {

@Delegate

protected volatile RestTemplate restTemplate;

}

这几行代码完全替代上述那些冗长的代码。

搜索公纵号:MarkerHub,关注回复[ vue ]获取前后端入门教程!

是不是很简洁,做一个拥抱 Lombok 的程序员吧。

重构:需求案例

项目需求

项目开发阶段,有一个关于下单发货的需求:

如果今天下午 3 点前进行下单,那么发货时间是明天,如果今天下午 3 点后进行下单,那么发货时间是后天,如果被确定的时间是周日,那么在此时间上再加 1 天为发货时间。

思考与重构

我相信这个需求看似很简单,无论怎么写都可以完成。

很多人可能看到这个需求,就动手开始写 Calendar 或 Date 进行计算,从而完成需求。

而我给的建议是,仔细考虑如何写代码,然后再去写,不是说所有的时间操作都用 Calendar 或 Date 去解决,一定要看场景。

对于时间的计算我们要考虑 joda-time 这种类似的成熟时间计算框架来写代码,它会让代码更加简洁和易读。

请读者先考虑这个需求如何用 Java 代码完成,或先写一个你觉得完成这个代码的思路,再来看我下边的代码,这样,你的收获会更多一些:

final DateTime DISTRIBUTION_TIME_SPLIT_TIME = new DateTime().withTime(15,0,0,0);

private Date calculateDistributionTimeByOrderCreateTime(Date orderCreateTime){

DateTime orderCreateDateTime = new DateTime(orderCreateTime);

Date tomorrow = orderCreateDateTime.plusDays(1).toDate();

Date theDayAfterTomorrow = orderCreateDateTime.plusDays(2).toDate();

return orderCreateDateTime.isAfter(DISTRIBUTION_TIME_SPLIT_TIME) ? wrapDistributionTime(theDayAfterTomorrow) : wrapDistributionTime(tomorrow);

}

private Date wrapDistributionTime(Date distributionTime){

DateTime currentDistributionDateTime = new DateTime(distributionTime);

DateTime plusOneDay = currentDistributionDateTime.plusDays(1);

boolean isSunday = (DateTimeConstants.SUNDAY == currentDistributionDateTime.getDayOfWeek());

return isSunday ? plusOneDay.toDate() : currentDistributionDateTime.toDate() ;

}

读这段代码的时候,你会发现,我将判断和有可能出现的不同结果都当做一个变量,最终做一个三目运算符的方式进行返回,这样的优雅和可读性显而易见,当然这样的代码不是一蹴而就的,我优化了 3 遍产生的以上代码。读者可根据自己的代码和我写的代码进行对比。

提高方法

如果你做了 3 年 + 的程序员,我相信像如上这样的需求,你很轻松就能完成,但是如果你想做一个会写 Java 的程序员,就好好的思考和重构代码吧。

线程、数据库、算法、JVM、分布式、微服务、框架、Spring相关知识

一线互联网P7面试集锦+各种大厂面试集锦

学习笔记以及面试真题解析

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

(tomorrow);

}

private Date wrapDistributionTime(Date distributionTime){

DateTime currentDistributionDateTime = new DateTime(distributionTime);

DateTime plusOneDay = currentDistributionDateTime.plusDays(1);

boolean isSunday = (DateTimeConstants.SUNDAY == currentDistributionDateTime.getDayOfWeek());

return isSunday ? plusOneDay.toDate() : currentDistributionDateTime.toDate() ;

}

读这段代码的时候,你会发现,我将判断和有可能出现的不同结果都当做一个变量,最终做一个三目运算符的方式进行返回,这样的优雅和可读性显而易见,当然这样的代码不是一蹴而就的,我优化了 3 遍产生的以上代码。读者可根据自己的代码和我写的代码进行对比。

提高方法

如果你做了 3 年 + 的程序员,我相信像如上这样的需求,你很轻松就能完成,但是如果你想做一个会写 Java 的程序员,就好好的思考和重构代码吧。

线程、数据库、算法、JVM、分布式、微服务、框架、Spring相关知识

[外链图片转存中…(img-K5ISYdsa-1715344225520)]

一线互联网P7面试集锦+各种大厂面试集锦

[外链图片转存中…(img-FELct9yP-1715344225521)]

学习笔记以及面试真题解析

[外链图片转存中…(img-WY3YaZil-1715344225521)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值