type
status
date
slug
summary
tags
category
icon
password
😀
这里写文章的前言: Java基础巩固
 

📝 为什么重写 hashCode 方法也要重写 equals

在基于哈希实现的容器里,如果想使用某个类,不仅需要定义它的hashCode()方法,还需要定义equals()方法。这两个方法一起使用,才能实现对哈希容器的正确查找。
官方文档讲的很清楚,防止依赖hashcode的api出现错误,比如经典的set和map, 一般情况下是不需要的。
代码如下:
只要将 Person 对象中 hashCode 或者 equals 注释掉一个的话,set集合中的值就会是2
 
 

📔 String

为什么String是final修饰

notion image
  1. 安全,如果是可变的,会被恶意去修改
  1. hashCode也不会改变
  1. 这种设计可以去做字符串常量池
  • 因为字符串常量池是共享的,多个地方可能会引用同一个字符串,如果string是可变的,一个地方把字符串改了,其他地方的引用就变成了被改之后的字符串了,这就出错了。

String的创建方法

string str = "abc"

在字符串常量池里面存在的对象,直接拿来用就可以了,可能要创建多个的对象,变成只创建一个对象,更高效的使用内存,此时堆空间是没有对象的
notion image

String str = new ("abc");

这种创建方式会在堆中间会多一个对象,比较不划算
  • 如果没有abc 会创建两个对象,一个String 一个abc
  • 如果有abc,只会创建一个对象
notion image
 

象里面带String属性

直接放在对象里面,不会再常量池里面了
notion image

拼接式生成String

String是不可变的,不能拼接,只会生成多一个拼接后的数据存在常量池 相当于 str2 = “abcdef" ;
notion image
 

for循环拼接

notion image
 
编译器会优化成如下图, 用StringBuilder.append();拼接
notion image

用Intern方法

会检查这个字符串在常量池是否存在,如果存在的话直接返回 可以有效减少字符串的创建
 

📔代理

Java静态代理

代理对象和目标对象实现了相同的接口,目标对象作为代理对象的一个属性,具体接口实现中,代理对象可以在调用目标对象相应方法前后加上其他业务处理逻辑。 缺点:一个代理类只能代理一个业务类。如果业务类增加方法时,相应的代理类也要增加方法。
 

Java动态代理

Java动态代理是写一个类实现InvocationHandler接口,重写Invoke方法,在Invoke方法可以进行增强处理的逻辑的编写,这个公共代理类在运行的时候才能明确自己要代理的对象,同时可以实现该被代理类的方法的实现,然后在实现类方法的时候可以进行增强处理。 实际上:代理对象的方法 = 增强处理 + 被代理对象的方法
 

JDK和CGLIB生成动态代理类的区别:

JDK动态代理只能针对实现了接口的类生成代理(实例化一个类)。此时代理对象和目标对象实现了相同的接口,目标对象作为代理对象的一个属性,具体接口实现中,可以在调用目标对象相应方法前后加上其他业务处理逻辑 CGLIB是针对类实现代理,主要是对指定的类生成一个子类(没有实例化一个类),覆盖其中的方法
 

Spring AOP应用场景

性能检测,访问控制,日志管理,事务等。 默认的策略是如果目标类实现接口,则使用JDK动态代理技术,如果目标对象没有实现接口,则默认会采用CGLIB代理

静态代码块/非静态代码块

静态代码

执行优先级高于非静态的初始化块,它会在类初始化的时候执行一次,它仅能初始化类变量,即static修饰的数据成员, (随着类的加载而执行,而且只执行一次)

非静态代码

执行的时候如果有静态初始化块,先执行静态初始化块再执行非静态初始化块,
在每个对象生成时都会被执行一次,它可以初始化类的实例变量。非静态初始化块会在构造函数执行时,在构造函数主体代码执行之前被运行
 

执行顺序

静态代码块 -----> 非静态代码块 --------> 构造函数

📔抽象和接口的区别

抽象类

  1. 可以有构造器
  1. 可以有抽象方法,和具体的方法
  1. 成员变量都是public的
  1. 抽象类可以定义成员变量
  1. 抽象类可以有静态方法
  1. 一个类只能继承一个抽象类

接口

  1. 不能有构造器
  1. 只能有抽象方法, 不能有实现方法
  1. 不能有静态方法
  1. 一个类可以实现多个接口
  1. 接口的成员变量都是常量

什么时候用抽象类, 什么时候用接口

  1. 抽象类是一个类,它的存在旨在被继承。它可以包含抽象方法、具体方法、实例变量等内容。
  1. 接口,共通事务之间的共通的特征/功能,比如 fly,很多东西都会飞,能做出这些功能。接口是一组方法的集合,但是这些方法都没有具体的实现。接口通常用于定义一组事物需要什么,但是不关心它们需要用何种方式来完成。
 

多态

基本概念

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。那么在继承中要构成多态的还有两个条件:
1. 必须通过基类的指针或者引用调用虚函数 2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
notion image
 

虚函数

在函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了

多态分类

  • 静态多态:函数重载和运算符重载属于静态多态,复用函数名
  • 动态多态:派生类和虚函数实现运行时多态
调用虚函数时,根据对象的实际类型,在虚方法表中查找函数的地址,并进行调用。

多态底层实现

父类中写了一个虚函数后,内部便有个一个虚函数指针vfptr,虚函数指针会指向一个虚函数表vftable,表的内部记录的是虚函数
当发生继承时(此处默认为public继承),子类将继承父类的所有元素。
因此,子类也就继承了父类中的虚函数指针,此时子类和父类完全相同,子类和父类中的虚函数指针指向的都是父类中虚函数地址。但是当子类重写了父类中的虚函数后,子类中的虚函数表内部会替换成子类的虚函数地址,父类中的虚函数指针不发生变化。当父类的指针或引用指向子类对象时,将发生多态。因此,当上述函数(即参数为父类指针或引用)的参数指向子类时,将从子类的虚函数表中找到虚函数的地址,此时读取的是子类中的虚函数。由此形成动态的运行中确定函数地址
 
 

同步/异步

同步

应用程序开发非常简单;在阻塞等待数据期间,用户线程挂起,基本不会占用CPU资源
同步调用虽然让系统间只耦合于接口,而且实时性也会比异步调用要高
但是我们也需要知道同步调用会带来如下几个问题:
  1. 同步调用需要被调用方的吞吐不低于调用方的吞吐。否则会导致被调用方因为性能不足而拖死调用方。换句话说,整个同步调用链的性能会由最慢的那个服务所决定。
  1. 同步调用会导致调用方一直在等待被调用方完成如果一层接一层地同步调用下去,所有的参与方会有相同的等待时间。这会非常消耗调用方的资源。因为调用方需要保存现场(Context)等待远端返回,所以对于并发比较高的场景来说,这样的等待可能会极度消耗资源。
  1. 同步调用只能是一对一的,很难做到一对多
  1. 同步调用最不好的是,如果被调用方有问题,那么其调用方就会跟着出问题,于是会出现多米诺骨牌效应,故障一下就蔓延开来

异步

请求响应式

在这种情况下,发送方(sender)会直接请求接收方(receiver),被请求方接收到请求后,直接返回——收到请求,正在处理。
  • 对于返回结果,有两种方法,一种是发送方时不时地去轮询一下,问一下干没干完。
  • 另一种方式是发送方注册一个回调方法,也就是接收方处理完后回调请求方
 

通过订阅的方式

这种情况下,接收方(receiver)会来订阅发送方(sender)的消息,发送方会把相关的消息或数据放到接收方所订阅的队列中,而接收方会从队列中获取数据。这种方式下,发送方并不关心订阅方的处理结果,它只是告诉订阅方有事要干,收完消息后给个 ACK 就好了,你干成啥样我不关心

Broker的方式

谓 Broker,就是一个中间人,发送方(sender)和接收方(receiver)都互相看不到对方,它们看得到的是一个 Broker,发送方向 Broker 发送消息,接收方向 Broker 订阅消息。如下图所示
notion image
在 Broker 这种模式下,发送方的服务和接收方的服务最大程度地解耦。但是所有人都依赖于一个总线,所以这个总线就需要有如下的特性:
  1. 必须是高可用的,因为它成了整个系统的关键;
  1. 必须是高性能而且是可以水平扩展的;
  1. 必须是可以持久化不丢数据的。
 

阻塞和非阻塞的区别

1. 阻塞是指用户进程(或者线程)一直在等待,而不能做别的事情; 2. 非阻塞是指用户进程(或者线程)获得内核返回的状态值就返回自己的空间,可以去做别的事情。在Java中,非阻塞IO的socket被设置为NONBLOCK模式
 

Cookie和Session区别

Cookie

Cookie可以让服务端跟踪每个客户端的访问,但是每次客户端的访问都必须传回这些Cookie,如果Cookie很多,则无形的增加了客户端与服务端的数据传输量

Session

Session则很好地解决了这个问题,同一个客户端每次和服务端交互时,将数据存储通过Session到服务端,不需要每次都传回所有的Cookie值,而是传回一个ID,每个客户端第一次访问服务器生成的唯一的ID,客户端只要传回这个ID就行了,这个ID通常为NAME为JSESSIONID的一个Cookie。这样服务端就可以通过这个ID,来将存储到服务端的KV值取出了
 

过滤器和拦截器区别

过滤器

用于在请求进入应用程序之前或响应离开应用程序之后对请求和响应进行处理的组件.过滤器通常用于实现对请求和响应进行预处理和后处理的逻辑,例如身份验证、日志记录、请求参数处理等。过滤器在请求的前后都可以进行操作,对整个请求进行处理
应用场景:
  1. 身份验证和授权:验证用户身份,并根据权限授予访问权限。
  1. 请求日志记录:记录请求和响应的详细信息,以进行故障排查和监控。
  1. 请求参数处理:解析和处理请求参数,例如解码、转换等。
  1. 跨域资源共享(CORS):实施跨域请求的安全策略。

拦截器

用于拦截并处理请求和响应的组件
拦截器通常与特定框架或应用程序的执行流程相关联,用于在请求到达控制器(或处理程序)之前或离开控制器之后执行某些操作
应用场景:
  1. 日志记录:记录请求和响应的关键信息,用于审计和故障排查。
  1. 事务管理:管理数据库事务的开始、提交和回滚。
  1. 缓存管理:在控制器执行前检查缓存,如果缓存中有相应的数据,则直接返回,提高性能。
  1. 性能监控:记录请求的执行时间和资源消耗,用于性能分析和优化。
 

🤗 总结归纳

📎 参考文章

 
💡
有关文章的问题,欢迎您在底部评论区留言,一起交流~