知识零碎
Java是值传递还是引用传递
结论: Java只有值传递没有引用传递。
值传递与引用传递的区别:对形参的修改不会影响到实参被称为值传递。引用传递则相反。
public static void change(Integer i) {
i = 3;
}
public static void change(Person person) {
person.name = "李四";
}
public static void main(String.. args) {
Integer x = 1;
change(x);
System.out.println(x); // x = 1;
Person person = new Person("张三");
change(person);
System.out.println(person.getName()); // "李四"
}
- 如果是基本数据类型,是将数据复制一份传递给方法,自然不会影响。
- 如果是对象做参数时,将堆中对象的引用复制一份传递给方法,
引用的地址不会被修改(也是被称为值传递的根本原因
),但是地址的内容会被函数修改。
Integer中的缓存池
Integer x = 123;
Integer y = 123;
System.out.println(x == y); // true
Integer a = 128;
Integer b = 128;
System.out.println(a == b); // false
System.out.println(a.equals(b)); // true
Integer z = Integer.valueOf(123);
Integer w = new Integer(123);
System.out.println(x == z); // true
System.out.println(z == w); // false
- Integer类型的缓存池的范围是
[-128, 127]
,只要是这个范围的值自动装箱就会返回相同的对象。- Integer类型中的equals()方法是对
包装的值
进行了比较,而不是比较对象。- valueOf()方法利用了缓存,符合第一条的规则。
- 如果通过new关键字创建对象,是没用利用缓存,并不符合第一条规则。
多层嵌套循环跳出问题
c中可以通过goto语句跳出多层嵌套循环,java保留了goto关键字,但没有任何作用。
public static void main(String[] args) {
int[] a = new int[]{1, 2};
int[] b = new int[]{4, 5};
loop_a:
for (int j : a) {
for (int k : b) {
if (k == 5) {
break loop_a; // 跳出最外层循环
}
System.out.println(j + " -> " + k); // 1 -> 4
}
}
System.out.println("------------------------");
loop_b:
for (int j : a) {
for (int k : b) {
if (k == 5) {
continue loop_b; // 跳过内层循环值为5的,继续从外层的下一个数开始执行
}
System.out.println(j + " -> " + k); // 1 -> 4; 2 -> 4
}
}
}
}
String对象的创建
字符串常量池(String Common Pool):因为字符串的大量创建会影响程序的性能,JVM引入了
字符串常量池
来减少字符串性能的开销(也基于字符串的不可变性才能实现)。
String a = "abc";
String b = new String("abc");
String c = "a" + "b" + "c";
String d = a.intern();
String e = b.intern();
System.out.println(a == b); // false
System.out.println(a == c); // true
System.out.println(a == d); // true
System.out.println(d == e); // true
字面量创建对象
new关键字创建对象
String.intern()
当调用 intern 方法时,如果池中已经包含一个等于该String对象的字符串,
由equals(Object)方法确定
,则返回池中的字符串。否则,将此String对象添加到池中并返回对该String对象的引用。
最近在阅读Spring AOP的源码(基于Spring 5.2.8.RELEASE)中,发现
@EnableAspectJAutoProxy
注解中的proxyTargetClass
参数并不如注释(是否创建基于子类的CGLIB代理
)中说所的哪样生效。无论我设置成true/false都会使用CGLIB代理。
自定义配置代码
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("org.springframework.chapter13")
public class AopConfiguration {
}
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
AopConfiguration.class);
// ....
}
}
HashMap的几点注意事项
数组的创建时机
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 省略代码
}
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4
final Node<K,V>[] resize() {
// 省略代码
else {
// 初始容量16
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
// 省略代码
return newTab;
}
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // 扩容因子赋值
}
当我们通过
new HashMap<>()
创建HashMap对象时,它只是对扩容因子进行赋值,并没有创建Node<K,V>[]
,只有在第一次执行putVal()
才会创建。
节点转换为红黑树的时机
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class BaseTest {
@Test
public void tree() {
HashMap<Key, Integer> map = new HashMap<>();
map.put(new Key(), 1);
map.put(new Key(), 2);
map.put(new Key(), 3);
map.put(new Key(), 4);
map.put(new Key(), 5);
map.put(new Key(), 6);
map.put(new Key(), 7);
map.put(new Key(), 8); // ①
map.put(new Key(), 9); // ②
System.out.println("map 转换为红黑树了吗?");
}
static class Key {
@Override
public int hashCode() {
return 1;
}
}
}
根据以上代码问:第几步时会执行方法treeifyBin
将Node节点转换为红黑树?
先说答案:执行第二步
的时候会转换为红黑树。
static final int TREEIFY_THRESHOLD = 8;
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
// 代码省略
// 执行到此说明:tab已经初始化,对应的tab位置不为null
// 且新增的key和原有的key,hash相同,equals不同
for (int binCount = 0; ; ++binCount) { // 遍历该tab[i]下的所有node依次进行比较
// 如果遍历到最后一个节点且不和之前的节点value相同。
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// binCount初始为0,只要binCount=7时就触发转换为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
// 如果hash,equals都相同需要替换
break;
// 否则继续下一个节点
p = e;
}
}
}
// 代码省略
return null;
}
static final int MIN_TREEIFY_CAPACITY = 64;
// 转换为红黑树
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
// 核心:只有当当前容量大于64时才会转换为红黑树,否则只是扩容
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize(); // 扩容
else if ((e = tab[index = (n - 1) & hash]) != null) {
// 省略转换为红黑树代码
}
}
核心在于
binCount初始值为0
,与TREEIFY_THRESHOLD - 1
进行比较,并且只有当指定位置的tab[i]已经有值之后才会进入上述方法,所以当插入第九个值
的时候且就会触发红黑树转换方法。但是!!!它只有在
HashMap的容量大于MIN_TREEIFY_CAPACITY(64)
的时候才会转换为红黑树,否则只是扩容!
数组的扩容时机
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class BaseTest {
static class Key2 {
}
@Test
public void map() {
HashMap<Key2, Integer> map = new HashMap<>();
map.put(new Key2(), 1);
map.put(new Key2(), 2);
map.put(new Key2(), 3);
map.put(new Key2(), 4);
map.put(new Key2(), 5);
map.put(new Key2(), 6);
map.put(new Key2(), 7);
map.put(new Key2(), 8);
map.put(new Key2(), 9);
map.put(new Key2(), 10);
map.put(new Key2(), 11);
map.put(new Key2(), 12); // ①
System.out.println("map 扩容了吗?");
map.put(new Key2(), 13); // ②
}
}
根据上述代码问:第几步进行了HashMap扩容?
先说答案:第二步
。
我们知道扩容因子是0.75
,即当HashMap当前的容量为16,它的扩容阈值是16 * 0.75 = 12
。
transient int size;
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
// 默认情况下是capcity * 0.75
// size初始值为0
if (++size > threshold)
resize(); // 扩容操作
}
size的初始值是0,但是进行比较的时候是
++size
先加1再比较。所以当容量是16时,扩容阈值是12,那么插入第13个值
就会触发resize()
进行扩容。
SpringMVC相关问题
拦截器中@value注解不生效
原因在于:当我们继承WebMvcConfigurationSupport
中的addInterceptors
方法,并添加自定义的拦截器时,如果我们使用new的方式创建,那么该拦截器不会被IOC容器管理,所以无法给通过@value
注解注入配置,推荐@Bean
注解注入。
public class LoginInterceptor implements HandlerInterceptor {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("request is coming in ...");
return false;
}
}
public class SpringCloudEurekaServerApplication implements WebMvcConfigurer {
@Bean
public LoginInterceptor loginInterceptor() {
return new LoginInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor()).addPathPatterns("/**");
}
}
WebMvcConfigurer 和 WebMvcConfigurationSupport
- 存在
WebMvcConfigurationSupport
则WebMvcConfigurer
不生效
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
public class WebMvcAutoConfiguration {
}
- 实现方式不同
// 实现方式
public class SpringCloudEurekaServerApplication implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
}
}
// 继承方式
public class SpringCloudEurekaServerApplication extends WebMvcConfigurationSupport{
@Override
public void addInterceptors(InterceptorRegistry registry) {
}
}
Spring中的事件
监听事件的三种方式
- 实现ApplicationListener
<T>
+ @Component
@Component
public class MyListener implements ApplicationListener<MyEvent> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void onApplicationEvent(MyEvent event) {
logger.info("MyListener get event: {}", event.getSource());
}
}
- @EventListener + @Component
@Component
public class MyListener2 {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@EventListener
public void onApplicationEvent(MyEvent event) {
logger.info("MyListener2 get event: {}", event.getSource());
}
}
- 实现ApplicationListener
<T>
+ spring.factories注入
public class MyListener3 implements ApplicationListener<MyEvent> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void onApplicationEvent(MyEvent event) {
logger.info("MyListener3 get event: {}", event.getSource());
}
}
org.springframework.context.ApplicationListener=\
io.spring.server.event.MyListener3
位于resources/META-INF/spring.factories文件中
同步/异步发送事件
默认情况下是同步发送事件,在容器的refresh()中存在initApplicationEventMulticaster()
方法,用于初始化事件发送器。
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
// 如果IOC容器中存在名为applicationEventMulticaster的bean则使用该bean作为事件发送器
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
} else {
// 不存在则使用内置的SimpleApplicationEventMulticaster作为事件发送器,并注入到容器中
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(
APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
}
}
- SimpleApplicationEventMulticaster(同步)
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
内置的
SimpleApplicationEventMulticaster
中的multicastEvent
方法会判断是否存在Executor
,如果存在则用线程池发送。
- 自定义ApplicationEventMulticaster(异步)
@Component(value = "applicationEventMulticaster")
public class AsyncApplicationEventMulticaster extends SimpleApplicationEventMulticaster {
public AsyncApplicationEventMulticaster() {
super.setTaskExecutor(Executors.newFixedThreadPool(2));
}
}
通过传入线程池实现异步invokeListeners,需要注入注入名必须是
applicationEventMulticaster
。
事件发送两次
在使用spring.factories
注入ApplicationListener
时,发现监听器会被调用两次,通过debug发现:因为是web项目,所以上下文中存在父子容器的问题(AnnotationConfigServletWebServerApplicationContext
和AnnotationConfigApplicationContext
),所以在子容器发布事件后,父容器也会发送一次。
@Override
public void onApplicationEvent(MyEvent event) {
if (applicationContext.getParent() != null) {
logger.info("MyListener3 get event: {}", event.getSource());
}
}
只让子容器发送事件,父容器不需要发送。