Java面试之基础知识

1.equals()和hashCode()的关系/h2>
  1. 不会创建“类对应的散列表”
    p1和p2相等的情况下,hashCode()也不一定相等。

  2. 会创建“类对应的散列表”

    **如果两个对象相等,那么它们的hashCode()值一定相同。**这里的相等是指,通过equals()比较两个对象时返回true。

    如果两个对象hashCode()相等,它们并不一定相等。因为在散列表中,hashCode()相等,即两个键值对的哈希值相等。然而哈希值相等,并不一定能得出键值对相等。补充说一句:“两个不同的键值对,哈希值相等”,这就是哈希冲突

  3. 原则

    (1)同一个对象(没有发生过修改)无论何时调用hashCode()得到的返回值必须一样。
    (2)hashCode()的返回值相等的对象不一定相等,通过hashCode()和equals()必须能唯一确定一个对象。
    (3)一旦重写了equals()函数(重写equals的时候还要注意要满足自反性、对称性、传递性、一致性),就必须重写hashCode()函数。而且hashCode()的生成哈希值的依据应该是equals()中用来比较是否相等的字段。

  4. String重写hashcode
    string重写hashcode

2.JAVA中try、catch、finally带return的执行顺序

如果理解不了,请看这

如果try中没有异常,则顺序为try→finally,如果try中有异常,则顺序为try→catch→finally。但是当try、catch、finally中加入return之后,就会有几种不同的情况出现

  1. try中有return的时候,先执行return之前的逻辑,保留return需要返回的信息,再去执行finally,最后返回return保留的信息
  2. 例外,上面说的是基本变量,当是一个引用变量的时候,finally会去操作地址,修改里面的值(finally通过地址改变了变量,还是会影响方法返回值的。)
  3. catch中有return,与try中一样,会先执行return前的代码,然后暂时保存需要return的信息,再执行finally中的代码,最后再通过return返回之前保存的信息。
  4. finally中有return的时候,try中的return会失效,在执行完finally的return之后,就不会再执行try中的return。这种写法,编译是可以编译通过的,但是编译器会给予警告,所以不推荐在finally中写return,这会破坏程序的完整性,而且一旦finally里出现异常,会导致catch中的异常被覆盖。

3.Integer包装类型的转换/h2>

不懂来这,小老弟

自动装箱:int–>Integer

自动拆箱:

  1. 两个通过new生成的Integer变量永远是不相等的(因为new生成的是两个对象,其内存地址不同)
  1. Integer变量和int变量比较时,只要两个变量的值是向等的,则结果为true(因为包装类Integer和基本数据类型int比较时,java会自动拆包装为int,然后进行比较,实际上就变为两个int变量的比较)
  1. 非new生成的Integer变量和new Integer()生成的变量比较时,结果为false。(因为非new生成的Integer变量指向的是java常量池中的对象,而new Integer()生成的变量指向堆中新建的对象,两者在内存中的地址不同)
  1. 对于两个非new生成的Integer对象,进行比较时,如果两个变量的值在区间-128到127之间,则比较结果为true,如果两个变量的值不在此区间,则比较结果为false
  1. 对于第4条的原因: java在编译Integer i = 100 ;时,会翻译成为Integer i = Integer.valueOf(100);,而java API中对Integer类型的valueOf的定义如下:

java对于-128到127之间的数,会进行缓存,Integer i = 127时,会将127进行缓存,下次再写Integer j = 127时,就会直接从缓存中取,就不会new了

4.ThreadLocal/h2>

详细看

4.1 作用

  • ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程上下文。

  • 提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度

4.2 应用场景

  • 在Java的多线程编程中,为保证多个线程对共享变量的安全访问,通常会使用synchronized来保证同一时刻只有一个线程对共享变量进行操作。这种情况下可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。最常见的ThreadLocal使用场景为用来解决数据库连接、Session管理等。
  • 在转账案例中:
    传递数据 : 保存每个线程绑定的数据,在需要的地方可以直接获取, 避免参数直接传递带来的代码耦合问题
    线程隔离 : 各线程之间的数据相互隔离却又具备并发性,避免同步方式带来的性能损失

4.3 与sychronized的区别/h3>

要避免内存泄漏有两种方式:

  1. 使用完ThreadLocal,调用其remove方法删除对应的Entry

  2. 使用完ThreadLocal,当前Thread也随之运行结束

    相对第一种方式,第二种方式显然更不好控制,特别是使用线程池的时候,线程结束是不会销毁的。

也就是说,只要记得在使用完ThreadLocal及时的调用remove,无论key是强引用还是弱引用都不会有问题。那么为什么key要用弱引用呢/p>

事实上,在ThreadLocalMap中的set/getEntry方法中,会对key为null(也即是ThreadLocal为null)进行判断,如果为null的话,那么是会对value置为null的。

这就意味着使用完ThreadLocal,CurrentThread依然运行的前提下,就算忘记调用remove方法,弱引用比强引用可以多一层保障:弱引用的ThreadLocal会被回收,对应的value在下一次ThreadLocalMap调用set,get,remove中的任一方法的时候会被清除,从而避免内存泄漏。

4.5.5 hash冲突

这里定义了一个AtomicInteger类型,每次获取当前值并加上HASH_INCREMENT,HASH_INCREMENT = 0x61c88647,这个值跟斐波那契数列(黄金分割数)有关,其主要目的就是为了让哈希码能均匀的分布在2的n次方的数组里, 也就是Entry[] table中,这样做可以尽量避免hash冲突。

码执行流程:

A. 首先还是根据key计算出索引 i,然后查找i位置上的Entry,

B. 若是Entry已经存在并且key等于传入的key,那么这时候直接给这个Entry赋新的value值,

C. 若是Entry存在,但是key为null,则调用replaceStaleEntry来更换这个key为空的Entry,

D. 不断循环检测,直到遇到为null的地方,这时候要是还没在循环过程中return,那么就在这个null的位置新建一个Entry,并且插入,同时size增加1。

最后调用cleanSomeSlots,清理key为null的Entry,最后返回是否清理了Entry,接下来再判断sz 是否>= thresgold达到了rehash的条件,达到的话就会调用rehash函数执行一次全表的扫描清理。

5.四种引用以及它们的应用场景/h2>
  1. 强引用
  • 不可回收的资源,垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠回收具有强引用的对象,来解决内存不足的问题。
  • 如果想中断或者回收强引用对象,可以显式地将引用赋值为null,这样的话JVM就会在合适的时间,进行垃圾回收
  • 使用场景:大部分场景都是强引用,如果想回收强引用最好是设置成NULL,再通知GC回收。
  1. 软引用
  • 可有可无。如果此时内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用,如图片缓存框架中缓存图片就是通过软引用实现。
  • 软引用可用来实现内存敏感的高速缓存
  • 软引用可以和一个引用队列联合使用,如果软件用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。(用于判断哪些软引用对象已经被清理)
  1. 弱引用
  • 弱引用的对象拥有更短的生命周期,只要垃圾回收器扫描到它,不管内存空间充足与否,都会回收它的内存。
  • 弱引用适用于内存敏感的缓存,如ThreadLocal中的key就用到了弱引用。
  1. 虚引用
  • 不用于存储引用对象,而是用于检测回收活动。
  • 它并不影响对象的生命周期。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。
  • 虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

6.深拷贝与浅拷贝/h2>

深入学习,请参考

  1. 浅拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该对象,如果字段类型是值类型(基本类型)的,那么对该字段进行复制;如果字段是引用类型的,则只复制该字段的引用而不复制引用指向的对象。此时新对象里面的引用类型字段相当于是原始对象里面引用类型字段的一个副本,原始对象与新对象里面的引用字段指向的是同一个对象
  2. 实现深拷贝两种方式:第一种是给需要拷贝的引用类型也实现Cloneable接口并覆写clone方法;第二种则是利用序列化
  3. 当一个类里面有很多引用类型时,需要手动调用很多clone,而且如果引用类型内部还有引用类型时,那么代码将会很恶心,量也很大。。。
  4. 深拷贝-序列化方式
    这种方式其实就是将对象转成二进制流,然后再把二进制流反序列成一个java对象,这时候反序列化生成的对象是一个全新的对象,里面的信息与原对象一样,但是所有内容都是一份新的

7.IO流

详细请看

  1. 用到的设计模式
  • 适配器模式
    (1)将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。手机充电器(将220v转换为5v的电压),读卡器等,其实就是使用到了适配器模式。
    (2)主要分为三个部分,目标接口(我们的手机充电器),适配者类(欧美国家的插座),适配器(插座,让我们可以在欧美国家充电),对象适配者类,注入其中一个作为属性,实现另一个接口,完美
  • 装饰器模式
    装饰模式又名包装(Wrapper)模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。装饰模式通过创建一个包装对象,也就是装饰,来包裹真实的对象。装饰模式以对客户端透明的方式动态地给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不创造更多子类的情况下,将对象的功能加以扩展。装饰模式把客户端的调用委派到被装饰类。装饰模式的关键在于这种扩展是完全透明的。
  1. 装饰者模式和静态代理的区别
  • 相同点:

  • 不同点:

    声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2021年5月12日
下一篇 2021年5月12日

相关推荐