时间
时间处理重点是格式统一,老项目常见 Date + SimpleDateFormat,新项目优先 java.time。
1 2 3
| SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date start = sdf.parse(startTime); String text = sdf.format(new Date());
|
高频操作:
1、计算两个日期之间的天数
2、生成指定日期时间
3、取当前时间、前一天、前一月、前一年
4、在旧 Date 体系和 LocalDateTime 之间切换
常见模板:
1 2 3 4 5 6 7 8 9 10
| long days = (date.getTime() - nowDate.getTime()) / (24 * 60 * 60 * 1000);
LocalDateTime now = LocalDateTime.now(); LocalDateTime yesterday = now.minusDays(1); LocalDateTime lastMonth = now.minusMonths(1); LocalDateTime lastYear = now.minusYears(1);
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String text = LocalDateTime.now().format(dtf); LocalDateTime time = LocalDateTime.parse("2020-11-04 00:00:00", dtf);
|
老 Date 和新时间 API 互转也很常见:
1 2 3
| Date date = new Date(); LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); Date newDate = Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant());
|
字符串、时间戳、LocalDate 互转也经常写:
1 2 3 4 5 6 7 8 9 10
| DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String strTime = "2020-11-04 00:00:00"; LocalDateTime parsed = LocalDateTime.parse(strTime, dtf); Date parsedDate = Date.from(parsed.atZone(ZoneId.systemDefault()).toInstant());
long milli = 1604458818000L; LocalDateTime milliTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(milli), ZoneId.systemDefault());
LocalDate localDate = LocalDate.now(); Date localDateToDate = Date.from(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
|
老系统如果还在用 Calendar,至少要能看懂这类代码:
1 2 3 4
| Calendar calendar = Calendar.getInstance(); calendar.setTime(new Date()); calendar.add(Calendar.DATE, -1); Date yesterdayDate = calendar.getTime();
|
队列
按场景选数据结构,不要只背接口。
常见:
1、基于数组的普通队列
2、基于链表的链式队列
3、循环队列
4、双端队列
5、优先队列
核心点:
1、数组队列实现简单,但可能发生数据迁移
2、链式队列入队出队简单,但有额外节点开销
3、循环队列用取模解决尾指针回绕问题
4、双端队列两端都能入队出队
5、优先队列默认不是 FIFO,而是按优先级出队
JDK 里常用实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Queue<Integer> queue = new ArrayDeque<>(); queue.offer(1); queue.offer(2); Integer first = queue.poll();
Deque<Integer> deque = new ArrayDeque<>(); deque.addFirst(1); deque.addLast(2);
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(); priorityQueue.offer(3); priorityQueue.offer(1); priorityQueue.offer(2); Integer min = priorityQueue.poll();
|
Filter、Listener、Interceptor 的区别
三者分层不同:
1、Filter 更偏 Servlet 规范层,适合做统一编码、鉴权、日志等横切逻辑。
2、Listener 更偏事件监听,适合感知应用、Session 等生命周期事件。
3、Interceptor 更偏 Spring MVC 层,适合做接口级拦截与上下文处理。
生命周期和顺序也要记住:
1、web.xml 加载顺序通常是 context-param -> listener -> filter -> servlet
2、Filter 需要实现 init、doFilter、destroy
3、Interceptor 常看 preHandle、postHandle、afterCompletion
常用 Filter 写法:
1 2 3 4 5 6 7
| public class LoginFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { chain.doFilter(request, response); } }
|
如果还是老 Servlet 项目,web.xml 里通常这样配:
1 2 3 4 5 6 7 8
| <filter> <filter-name>loginFilter</filter-name> <filter-class>com.demo.LoginFilter</filter-class> </filter> <filter-mapping> <filter-name>loginFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
|
Interceptor 代码一般至少能写出这个骨架:
1 2 3 4 5 6 7
| public class UserRoleAuthorizationInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } }
|
Listener 也常见于启动初始化和 Session 生命周期统计:
1 2 3 4 5 6 7 8 9
| public class DemoServletContextListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { }
@Override public void contextDestroyed(ServletContextEvent sce) { } }
|
对象传参与引用语义
Java 只有值传递。对象参数传的是引用副本,不是引用本身。
高频误区:
1、值传递和引用传递的边界
2、集合 clear 后底层内存是否会马上释放
3、List 拷贝到底是浅拷贝还是深拷贝
4、交集、并集、差集这类常见集合操作怎么写更稳妥
Web
1、请求转发和重定向的区别
2、Cookie 和 Session 的配合方式
3、数组和集合打印结果的差异
4、finally 中 return 对返回值的影响
1、转发还是一次请求,适合服务端内部跳转和带上原始 request
2、重定向会让浏览器重新发请求,适合登录后跳首页、避免表单重复提交
1 2
| request.getRequestDispatcher("/index.jsp").forward(request, response); response.sendRedirect("/login");
|
Cookie 常见写法:
1 2 3 4
| Cookie cookie = new Cookie("token", token); cookie.setPath("/"); cookie.setMaxAge(7 * 24 * 60 * 60); response.addCookie(cookie);
|
Cookie 读取通常也会顺手封个工具方法:
1 2 3 4 5 6 7 8 9 10 11 12
| public static String getCookie(HttpServletRequest request, String cookieName) { Cookie[] cookies = request.getCookies(); if (cookies == null) { return null; } for (Cookie cookie : cookies) { if (cookieName.equals(cookie.getName())) { return cookie.getValue(); } } return null; }
|
登录态一般是 Session 存用户信息,Cookie 存 token 或 session id。
1 2 3
| session.setAttribute("loginUser", user); Object loginUser = session.getAttribute("loginUser"); session.invalidate();
|
Interceptor 一般这样注册:
1 2 3 4 5 6 7 8 9
| @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/login", "/static/**"); } }
|
Session 负责服务端状态,Cookie 负责浏览器侧标识和持久化;拦截器负责把未登录请求拦在 Controller 之前。
零散
日期
1 2 3
| SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date start = sdf.parse(startTime); String nowText = sdf.format(new Date());
|
数组打印
数组不要直接 println。
1 2
| System.out.println(Arrays.toString(arr)); System.out.println(Arrays.deepToString(arr2));
|
DTO 转换
字段接近时可以临时这么转:
1
| List<UserDTO> userDTOs = JSON.parseArray(JSON.toJSONString(users), UserDTO.class);
|
finally
try 或 catch 里只要准备 return,finally 一定会先执行;如果 finally 里也写了 return,最终返回的就是 finally 里的值。