Skip to content

知识零碎

Java是值传递还是引用传递

结论: 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()); // "李四"
}
  1. 如果是基本数据类型,是将数据复制一份传递给方法,自然不会影响。
  2. 如果是对象做参数时,将堆中对象的引用复制一份传递给方法,引用的地址不会被修改(也是被称为值传递的根本原因),但是地址的内容会被函数修改。

Integer中的缓存池

java
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
  1. Integer类型的缓存池的范围是[-128, 127],只要是这个范围的值自动装箱就会返回相同的对象。
  2. Integer类型中的equals()方法是对包装的值进行了比较,而不是比较对象。
  3. valueOf()方法利用了缓存,符合第一条的规则。
  4. 如果通过new关键字创建对象,是没用利用缓存,并不符合第一条规则。

多层嵌套循环跳出问题

c中可以通过goto语句跳出多层嵌套循环,java保留了goto关键字,但没有任何作用。

java
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引入了字符串常量池来减少字符串性能的开销(也基于字符串的不可变性才能实现)。

java
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

字面量创建对象

String字面量

new关键字创建对象

image-20220512175258152

String.intern()

当调用 intern 方法时,如果池中已经包含一个等于该String对象的字符串,由equals(Object)方法确定,则返回池中的字符串。否则,将此String对象添加到池中并返回对该String对象的引用。

微信截图_20220512175811

最近在阅读Spring AOP的源码(基于Spring 5.2.8.RELEASE)中,发现@EnableAspectJAutoProxy注解中的proxyTargetClass参数并不如注释(是否创建基于子类的CGLIB代理)中说所的哪样生效。无论我设置成true/false都会使用CGLIB代理。

自定义配置代码

java
@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的几点注意事项

数组的创建时机

java
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()才会创建。

节点转换为红黑树的时机

java
@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节点转换为红黑树?

先说答案:执行第二步的时候会转换为红黑树。

java
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)的时候才会转换为红黑树,否则只是扩容!

数组的扩容时机

java
@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

java
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注解注入。

java
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

  • 存在WebMvcConfigurationSupportWebMvcConfigurer不生效
java
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
public class WebMvcAutoConfiguration {
}
  • 实现方式不同
java
// 实现方式
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
java
@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
java
@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注入
java
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());
    }
}
java
org.springframework.context.ApplicationListener=\
io.spring.server.event.MyListener3

位于resources/META-INF/spring.factories文件中

同步/异步发送事件

默认情况下是同步发送事件,在容器的refresh()中存在initApplicationEventMulticaster()方法,用于初始化事件发送器。

java
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(同步)
java
@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(异步)
java
@Component(value = "applicationEventMulticaster")
public class AsyncApplicationEventMulticaster extends SimpleApplicationEventMulticaster {

    public AsyncApplicationEventMulticaster() {
        super.setTaskExecutor(Executors.newFixedThreadPool(2));
    }
}

通过传入线程池实现异步invokeListeners,需要注入注入名必须是applicationEventMulticaster

事件发送两次

在使用spring.factories注入ApplicationListener时,发现监听器会被调用两次,通过debug发现:因为是web项目,所以上下文中存在父子容器的问题(AnnotationConfigServletWebServerApplicationContextAnnotationConfigApplicationContext),所以在子容器发布事件后,父容器也会发送一次。

java
@Override
public void onApplicationEvent(MyEvent event) {
    if (applicationContext.getParent() != null) {
        logger.info("MyListener3 get event: {}", event.getSource());
    }
}

只让子容器发送事件,父容器不需要发送。