实习

linycat / 2023-05-29 / 原文

目录
  • 专业技能
    • 熟悉 Java
      • 集合
        • 谈谈集合
      • 泛型
        • 为什么使用泛型?
        • 谈谈 <?> 与 Object
        • 谈谈 extends 与 super
        • 可以将 List<String> 传递给 List<Object> 吗?
        • 数组可以使用泛型吗?
        • 谈谈泛型类
        • 谈谈泛型接口
        • 谈谈泛型方法
        • 谈谈泛型擦除
        • 谈谈泛型的常用命名
      • IO
      • Exception
        • 谈谈异常的处理机制
        • 如何实现全局异常处理?
        • Throwable 类的常用方法有哪些?
        • 为什么把异常定义为静态变量会导致堆栈跟踪信息错乱?
        • 如何使用 try-with-resources 代替 try-catch-finally?
      • Lambda
        • 什么是 Lambda?
        • 谈谈使用格式
        • 什么是语法糖?
        • Java 有哪些常见的语法糖?
      • Stream
      • LocalDate
        • 为什么不使用 java.util.Date?
        • 为什么不使用 java.util.Calendar?
        • 谈谈 java.time 包
        • 谈谈 java.time 包的常用 API
      • ThreadPoolExecutor
      • 其它
    • 熟悉 MySQL
      • 行表锁
      • 索引
      • 事务隔离
      • 存储引擎
      • 联表查询
      • MVCC
      • explain
      • 其它
    • 熟悉 Spring
      • 生命周期
        • 谈谈循环依赖
      • 事务传播
      • IOC
      • AOP
      • BeanFactory
      • BeanPostProcessor
    • 熟悉 SpringMVC
    • 熟悉 SpringBoot
    • 熟悉 MyBatis
    • 熟悉 MyBatisPlus
    • 熟悉 Redis
    • 熟悉 RabbitMQ
    • 熟悉 设计模式
    • 熟悉 数据结构
    • 熟悉 网络基础
    • 熟悉 Git
    • 了解 SpringCloud
    • 了解 Nginx
      • 能够配置负载均衡
    • 了解 Docker
      • 能够搭建开发环境
    • 了解 Vue2
      • 能够进行简单的请求回显
  • 项目经验

专业技能

熟悉 Java

集合

谈谈集合
Java 的集合有 List、Set、Queue、Map 四个接口,其中 List、Set、Queue 三个子接口是由 Collection 派生出来的。
另外,Collection 是一个接口,Collections 是一个工具类。

泛型

为什么使用泛型?
可以在编译时进行类型检查,避免在运行时出现 ClassCastException 异常。
List list = new ArrayList(); // 形参化类 'ArrayList' 的原始使用
list.add("1");
list.add(2);
String name = (String) list.get(0);
String number = (String) list.get(1); // ClassCastException
谈谈 <?> 与 Object
<?> 表示未知类型,Object 表示对象基类。
// Example with '?'
List<?> unknownList = new ArrayList<>();
unknownList.add(null);
unknownList.add("Hello"); // 编译时异常

// Example with 'Object'
List<Object> objectList = new ArrayList<>();
objectList.add(null);
objectList.add("Hello");
谈谈 extends 与 super
<? extends T> 表示包括 T 在内的任何 T 的子类
<? super T> 表示包括 T 在内的任何 T 的父类
可以将 List<String> 传递给 List<Object> 吗?
不可以。
因为存在泛型擦除,所以在编译时无法确定 List<Object> 实际存储的元素类型,导致子类对象不能直接赋值给父类引用。
List<String> stringList = new ArrayList<>();
List<Object> objectList = new ArrayList<>();
objectList = stringList; // 编译错误
/* 四种解决方法:
 * List<? extends Object> objectList = new ArrayList<>();
 * List<? super String> objectList = new ArrayList<>();
 * List<?> objectList = new ArrayList<>();
 * objectList = Collections.singletonList(stringList);
 */
数组可以使用泛型吗?
不可以。
所以 《Effective Java》 一书中建议使用 List 代替数组,因为 List 可以提供编译时期的类型安全保证。
谈谈泛型类
class 类名<T>
public class Pair<K, V> {
    private K key;
    private V value;
    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }
    public K getKey() {
        return key;
    }
    public V getValue() {
        return value;
    }
}

Pair<String, Integer> pair = new Pair<>("apple", 10);
String key = pair.getKey();
Integer value = pair.getValue();
谈谈泛型接口
interface 接口名<T>
public interface Pair<K, V> {
    K getKey();
    V getValue();
}

public class StringIntegerPair implements Pair<String, Integer> {
    private String key;
    private Integer value;
    public StringIntegerPair(String key, Integer value) {
        this.key = key;
        this.value = value;
    }
    @Override
    public String getKey() {
        return key;
    }
    @Override
    public Integer getValue() {
        return value;
    }
}
谈谈泛型方法
如果方法的返回值类型使用了泛型,那么可以在方法的返回值类型前面使用 <T> 来声明该方法是一个泛型方法。
public class GenericExample {
    public static void main(String[] args) {
        List<String> stringList = Arrays.asList("Apple", "Banana", "Orange");
        String firstStringElement = getFirstElement(stringList);
        System.out.println(firstStringElement); // 输出:Apple
    }

    public static <T> T getFirstElement(List<T> list) {
        if (!list.isEmpty()) {
            return list.get(0);
        }
        return null;
    }
}
谈谈泛型擦除
泛型是在 Java 5 引入的,为了确保兼容旧的非泛型代码,所以需要在运行时进行类型擦除,比如 List<String> 在类型擦除后会变成 List。
public class GenericExample {
    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();
        List<Integer> integerList = new ArrayList<>();
        System.out.println(stringList.getClass() == integerList.getClass()); // true
    }
}
谈谈泛型的常用命名
<T> 表示类型 Type
<E> 表示元素 Element
<K> 表示键 Key
<V> 表示值 Value
<R> 表示返回值 Return
<N> 表示数字 Number
<C> 表示集合 Collection
<S> 表示源 Source
<D> 表示目标 Destination

IO

Exception

谈谈异常的处理机制
Exception 异常,Error 错误,它们都继承 Throwable 类。
Exception 分为受检异常和非受检异常:
    受检异常即编译时异常,继承 Exception 类,常见的有 IOException,必须使用 try-catch-finally 或者 throws 处理。
    非受检异常即运行时异常,继承 RuntimeException 类,常见的有 NullPointerException,可以使用 try-catch-finally 或者 throws 处理,抑或不处理。
Error 通常由 JVM 抛出,常见的有 StackOverflowError,因为无法通过程序修复,所以一般不会进行处理。

try 业务代码,catch 异常处理,finally 资源回收(无论是否发生异常均会执行)。
throws 用于在方法签名抛出可能的异常类型,throw 用于在方法内部抛出具体的异常实例。
public class ThrowReturnDemo {
    public static void main(String[] args) {
        try {
            System.out.println(run());
        } catch (Exception e) {
            e.printStackTrace();
        }
        /**
         * 业务代码
         * 异常处理
         * 资源回收
         * return
         */
    }

    public static String run() throws Exception {
        try {
            System.out.println("业务代码");
            throw new RuntimeException();
        } catch (Exception e) {
            System.out.println("异常处理");
            return "return";
        } finally {
            System.out.println("资源回收");
            // 不要在 finally 中使用 throw、return,避免 try、catch 失效
        }
    }
}
如何实现全局异常处理?
使用 @ControllerAdvice 定义一个全局异常处理器
使用 @ExceptionHandler 捕获整个应用程序抛出的异常
使用 @ResponseBody 返回自定义的响应结构
import lombok.Getter;

@Getter
public class BusinessException extends RuntimeException {
    private final String code;
    private final String msg;

    public BusinessException(String code, String msg) {
        super(msg);
        this.code = code;
        this.msg = msg;
    }

    public BusinessException(String code, String msg, Throwable cause) {
        super(msg, cause);
        this.code = code;
        this.msg = msg;
    }
}
import com.github.pagehelper.PageInfo;
import com.linycat.mall4java.common.enums.JsonResultEnum;

import java.util.HashMap;

/**
 * 自定义响应数据结构
 *
 * @author linycat
 */
public class JsonResult<T> extends HashMap<String, Object> {
    private static final long serialVersionUID = 1L;
    /**
     * 响应状态码
     */
    private static final String CODE = "code";
    /**
     * 响应信息
     */
    private static final String MSG = "msg";
    /**
     * 响应数据
     */
    private static final String DATA = "data";
    /**
     * 分页数据
     */
    private static final String LIST_DATA = "listData";
    /**
     * 页码编号
     */
    private static final String PAGE_NUM = "pageNum";
    /**
     * 页码大小
     */
    private static final String PAGE_SIZE = "pageSize";
    /**
     * 总页码数
     */
    private static final String PAGE_COUNT = "pageCount";
    /**
     * 总记录数
     */
    private static final String TOTAL_COUNT = "totalCount";

    /**
     * 工具类不应具有公共构造函数
     */
    private JsonResult() {
    }

    /**
     * 分页数据
     */
    public static <T> JsonResult<PageInfo<T>> ok(PageInfo<T> pageInfo) {
        // PageInfo(List<? extends T> list)
        JsonResult<PageInfo<T>> result = new JsonResult<>();
        result.put(CODE, JsonResultEnum.SUCCESS.getCode());
        result.put(MSG, JsonResultEnum.SUCCESS.getMsg());
        HashMap<String, Object> pageMap = new HashMap<>(5);
        pageMap.put(LIST_DATA, pageInfo.getList());
        pageMap.put(PAGE_NUM, pageInfo.getPageNum());
        pageMap.put(PAGE_SIZE, pageInfo.getPageSize());
        pageMap.put(PAGE_COUNT, pageInfo.getPages());
        pageMap.put(TOTAL_COUNT, pageInfo.getTotal());
        result.put(DATA, pageMap);
        return result;
    }

    /**
     * 常规数据
     */
    public static <T> JsonResult<T> ok(T data) {
        JsonResult<T> result = new JsonResult<>();
        result.put(CODE, JsonResultEnum.SUCCESS.getCode());
        result.put(MSG, JsonResultEnum.SUCCESS.getMsg());
        result.put(DATA, data);
        return result;
    }

    /**
     * 错误数据
     */
    public static <T> JsonResult<T> error(String code, String msg, T data) {
        JsonResult<T> result = new JsonResult<>();
        result.put(CODE, code);
        result.put(MSG, msg);
        result.put(DATA, data);
        return result;
    }

    /**
     * 自定义键值对
     */
    @Override
    public JsonResult<T> put(String key, Object value) {
        super.put(key, value);
        return this;
    }

    /**
     * 获取响应状态码
     */
    public Object getCode() {
        return super.get(CODE);
    }

    /**
     * 获取响应信息
     */
    public Object getMsg() {
        return super.get(MSG);
    }

    /**
     * 获取响应数据
     */
    public Object getData() {
        return super.get(DATA);
    }
}
public void save(UserEntity userEntity) {
    if (userEntity == null) {
        throw new BusinessException("10001", "用户信息不能为空");
    }
    // 保存用户信息
}
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(BusinessException.class)
    @ResponseBody
    public JsonResult<String> handleBusinessException(BusinessException e) {
        return JsonResult.error(e.getCode(), e.getMsg(), e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public JsonResult<String> handleException(Exception e) {
        return JsonResult.error("500", "系统异常", e.getMessage());
    }
}
Throwable 类的常用方法有哪些?
String getMessage() 返回详细描述信息
void printStackTrace() 打印堆栈跟踪信息
为什么把异常定义为静态变量会导致堆栈跟踪信息错乱?
如果这个异常是一个静态变量,那么它在堆内存中就只有一份实例。
在多线程的情况下就会导致其中一个线程的堆栈跟踪信息被错误地记录到了另外一个线程中。
所以,应该在每个需要使用异常的地方单独创建一个新的异常对象,确保每个线程抛出的异常对象都是相互独立的。
如何使用 try-with-resources 代替 try-catch-finally?
try {
    业务代码
} catch (Exception e) {
    异常处理
} finally {
    资源回收
}

try (创建资源) {
    业务代码
} catch (Exception e) {
    异常处理
} finally {
    资源回收后的操作(可选)
}

Lambda

什么是 Lambda?
一种匿名函数,通常用于函数式接口的实现,即只有一个抽象方法的接口,但是可以有多个非抽象方法。
平时开发时并不需要特意使用 Lambda,一般都是当 IDEA 提示时再根据实际情况进行权衡和选择。
public interface Calculator {
    int calculate(int x, int y);
}

public class Adder implements Calculator {
    @Override
    public int calculate(int x, int y) {
        return x + y;
    }
}

Calculator adder = new Adder();
int result1 = adder.calculate(1, 2);

Calculator lambdaAdder = (x, y) -> x + y;
int result2 = lambdaAdder.calculate(1, 2);
谈谈使用格式
(int x, int y) -> {
    int sum = x + y;
    return sum;
}

(参数列表) -> {
    方法主体
}
什么是语法糖?
一种简化语法。
Java 有哪些常见的语法糖?
自动装箱和自动拆箱、增强for循环、Lambda表达式、可变参数、泛型、枚举类型、try-with-resources

Stream

LocalDate

为什么不使用 java.util.Date?
设计缺陷,线程不安全,比如 getYear 方法返回的是从 1900 年起经过的年数,而不是实际的年份。
System.out.println(new Date().getYear()); // 2023 - 1900 = 123
为什么不使用 java.util.Calendar?
使用繁琐,线程不安全,比如 Calendar.MONTH 是从 0 开始计算的,而不是 1。
System.out.println(Calendar.getInstance().get(Calendar.MONTH) + 1);
谈谈 java.time 包
设计合理,使用简单,线程安全,支持纳秒级别的时间精度。
LocalDate 表示日期,LocalTime 表示时间
LocalDateTime 表示日期和时间,ZonedDateTime 表示时区的日期和时间
Instant 表示时间戳,Duration 表示时间间隔,Period 表示日期间隔
DateTimeFormatter 用于日期时间的格式化
谈谈 java.time 包的常用 API
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Date;

public class Main {
    public static void main(String[] args) {
    }

    /**
     * Date 转 LocalDateTime
     */
    public static void dateToLocalDateTime() {
        Date date = new Date();
        System.out.println("Date: " + date);
        LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
        System.out.println("LocalDateTime: " + localDateTime);
    }

    /**
     * LocalDateTime 转 Date
     */
    public static void localDateTimeToDate() {
        LocalDateTime localDateTime = LocalDateTime.now();
        System.out.println("LocalDateTime: " + localDateTime);
        Date date = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
        System.out.println("Date: " + date);
    }

    /**
     * String 转 LocalDateTime
     */
    public static void stringToLocalDateTime() {
        String string = "2020-05-20 21:30:01";
        System.out.println("String: " + string);
        LocalDateTime localDateTime = LocalDateTime.parse(string, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        System.out.println("LocalDateTime: " + localDateTime);
    }

    /**
     * LocalDateTime 转 String
     */
    public static void localDateTimeToString() {
        LocalDateTime localDateTime = LocalDateTime.now();
        System.out.println("LocalDateTime: " + localDateTime);
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        System.out.println("String: " + localDateTime.format(dateTimeFormatter));
    }

    /**
     * 日期相隔
     */
    public static void localDateBetween() {
        LocalDate startLocalDate = LocalDate.of(2020, 5, 20);
        LocalDate endLocalDate = LocalDate.now();
        System.out.println("比较年月日 Days: " + startLocalDate.until(endLocalDate, ChronoUnit.DAYS));
        System.out.println("仅仅比较日 Days: " + Period.between(startLocalDate, endLocalDate).getDays());
    }

    /**
     * 时间相隔
     */
    public static void localDateTimeBetween() {
        LocalDateTime startLocalDateTime = LocalDateTime.of(2020, 5, 20, 21, 30, 1);
        LocalDateTime endLocalDateTime = LocalDateTime.now();
        Duration duration = Duration.between(startLocalDateTime, endLocalDateTime);
        System.out.println("Days: " + duration.toDays() + ", Hours: " + duration.toHours() + ", Minutes: " + duration.toMinutes());
    }

    /**
     * 时间戳
     */
    public static void timeStamp() {
        Instant instant = LocalDateTime.of(2020, 5, 20, 21, 30, 1).toInstant(ZoneOffset.ofHours(8));
        System.out.println("EpochMilli: " + instant.toEpochMilli());
        System.out.println("CurrentTimeMillis: " + System.currentTimeMillis());
        LocalDateTime localDateTime = LocalDateTime.ofEpochSecond(instant.getEpochSecond(), 0, ZoneOffset.ofHours(8));
        System.out.println("LocalDateTime: " + localDateTime);
    }

    public static void otherApi() {
        // 创建 LocalDateTime 对象
        LocalDateTime dateTime = LocalDateTime.now();
        LocalDateTime specificDateTime = LocalDateTime.of(2020, 5, 20, 21, 30, 1);
        LocalDateTime parsedDateTime = LocalDateTime.parse("2020-05-20T21:30:01");
        // 获取日期和时间
        int year = dateTime.getYear();
        int month = dateTime.getMonthValue();
        int day = dateTime.getDayOfMonth();
        int hour = dateTime.getHour();
        int minute = dateTime.getMinute();
        int second = dateTime.getSecond();
        int nano = dateTime.getNano();
        DayOfWeek dayOfWeek = dateTime.getDayOfWeek();
        // 修改日期和时间
        LocalDateTime modifiedDateTimeWithYear = dateTime.withYear(2024);
        LocalDateTime modifiedDateTimeWithMonth = dateTime.withMonth(6);
        LocalDateTime modifiedDateTimeWithDayOfMonth = dateTime.withDayOfMonth(25);
        LocalDateTime modifiedDateTimeWithHour = dateTime.withHour(12);
        LocalDateTime modifiedDateTimeWithMinute = dateTime.withMinute(15);
        LocalDateTime modifiedDateTimeWithSecond = dateTime.withSecond(30);
        LocalDateTime modifiedDateTimeWithNano = dateTime.withNano(0);
        // 加减日期和时间
        LocalDateTime modifiedDateTimePlusYears = dateTime.plusYears(1);
        LocalDateTime modifiedDateTimeMinusMonths = dateTime.minusMonths(2);
        LocalDateTime modifiedDateTimePlusDays = dateTime.plusDays(7);
        LocalDateTime modifiedDateTimeMinusHours = dateTime.minusHours(3);
        LocalDateTime modifiedDateTimePlusMinutes = dateTime.plusMinutes(15);
        LocalDateTime modifiedDateTimeMinusSeconds = dateTime.minusSeconds(30);
        LocalDateTime modifiedDateTimePlusNanos = dateTime.plusNanos(500000000);
        // 比较日期和时间
        LocalDateTime dateTime1 = LocalDateTime.of(2020, 5, 20, 21, 30, 1);
        LocalDateTime dateTime2 = LocalDateTime.of(2025, 5, 20, 21, 30, 1);
        boolean isBefore = dateTime1.isBefore(dateTime2);
        boolean isAfter = dateTime1.isAfter(dateTime2);
        boolean isEqual = dateTime1.isEqual(dateTime2);
    }
}

ThreadPoolExecutor

其它

熟悉 MySQL

行表锁

索引

事务隔离

存储引擎

联表查询

MVCC

explain

其它

熟悉 Spring

生命周期

谈谈循环依赖

事务传播

IOC

AOP

BeanFactory

BeanPostProcessor

熟悉 SpringMVC

熟悉 SpringBoot

熟悉 MyBatis

熟悉 MyBatisPlus

熟悉 Redis

熟悉 RabbitMQ

熟悉 设计模式

熟悉 数据结构

熟悉 网络基础

熟悉 Git

了解 SpringCloud

了解 Nginx

能够配置负载均衡

了解 Docker

能够搭建开发环境

了解 Vue2

能够进行简单的请求回显

项目经验