Java高频面试题——基础篇(持续更新)


如有错误,欢迎在下方评论区指出

1、谈谈对Java平台的理解?

a、 Java是一种面向对象的语言,具有跨平台运行的能力,能够 write once,run anywhere

b、 Java的GC机制,Java通过垃圾回收器去回收分配的内存,程序员不需要关系内存回收问题

c、 JRE和JDK:JRE是java的运行环境,包含JVM和Java类库。JDK则是在JRE的基础上提供了一些工具,比如编译器和一些诊断工具

2、Java是解释执行语言吗?

  • 这句话不是非常准确 Java代码在执行时,先通过Javac编译为字节码,在运行时,通过JVM内的解释器转换为目标机器的可执行机器码。

  • 但是常见JVM、比如Hotspot JVM都提供JIT(just in time) 动态编译器 ,能够在运行时将热点代码直接翻译成机器码,这部分属于编译执行

3、Exception和Error有什么区别?

  • 首先:
    ExceptionError都是继承于Throwable 类,在Java中只有Throwable类型的实例才能被程序抛出(throw)或者捕获(catch),它是异常处理机制的基本类型

  • 其次:

    Exception 和Error它是体现java平台针对不同异常情况的分类。

    Exception 是程序正常运行过程中可以预料到的意外情况,应该被捕获并进行处理。

    Error正常情况下不大可能发生的情况,绝大部分Error都会导致程序状态的不正常,且不可恢复,既然非正常情况,所以我们不便也不需要进行处理,例如OutOFMemoryError之类的都是Error的子类。

  • 再次
    Exception 分为检查型异常非检查型异常。检查型异常必须在源码处进行捕获处理,这是编译检查的一部分。除了RuntimeException以外全部都是检查型异常。

    ​ 非检查型异常就是所谓的RuntimeException、类似NullPointerException和ArrayIndexOfBoundException就是我们的非检查型异常,通常可以编码避免的逻辑错误,具体可以根据需要进行捕获,编译时不检查,如果抛出非检查型异常就是代码逻辑问题,需要解决

4、异常处理的原则?

1、尽量不要捕获类似Exception这样的通用异常

2、不要去生吞异常

3、Throw early,Catch late

4、定义自定义异常时,考虑定义检查型异常还是非检查型异常

5、选择抛出检查型异常还是非检查型异常
a、对于可恢复的情况使用检查型异常
b、避免不必要的检查型异常
c、优先使用标准异常
d、抛出与抽象相对的异常
e、应尽量保障失败的原子性

5、谈谈 final、finally、finalize有什么不同?

final:

final可以用来修饰类、方法和变量(成员变量和局部变量)

a、修饰类
表明类不能被其他类所继承(final类所有成员方法都会隐式定义为final方法)

b、修饰方法
锁定方法,阻止继承类对其进行更改(重写)

c、修饰变量
final成员变量表示常量,只能赋值一次,赋值后其值不能更改

​ final修饰一个基本数据类型时,表明该基本数据类型的值一旦在初始化后便不能发生改变;

​ final修饰一个成员变量,必须要显式的初始化,初始化的方式有两种:
​ 1、直接在声明变量时初始化;

​ 2、在声明变量时不赋值,但是需要在变量所在类的所有构造函数中对其进行赋值

finally:
作为异常处理的一部分,它只能用在try/catch语句中,并附带一个语句块,如果try代码块被执行,无论是否抛出异常该代码
都会被执行,经常被用在需要释放资源的情况下
finalize:
finalize()是java.lang.Object里定义的一个方法,这个方法在gc启动,该对象被回收前调用。

6、强引用、软引用、弱引用、幻象引用的区别?

不同引用类型,主要体现的是对象的不同可达性状态和对垃圾回收器的影响

强引用:

​ 常见不同对象引用。只要还用强调引用去指向一个对象的话,就表明这个对象还活着,垃圾回收器不会回收这种对象

​ 平常的编码Object obj=new Object()中的obj就是强引用,通过关键字new创建出来的对象所关联的引用就是强引用。

​ 当JVM内存不足时,JVM宁愿去抛出OOM异常,使程序异常终止,也不会去靠释放强引用对象来解决内存不足的问题。

​ 对于一个普通的对象,如果没有其他引用关系,只要超过了引用的作用域或者显示的将强引用对象赋值为null,
就可以被垃圾回收器回收了,具体回收时机还是看垃圾回收器的回收策略。

软引用:

​ 是一种相对于强引用弱化了的引用,只有JVM认为内存不足时,才会试图去回收软引用指向的对象。JVM会确保程序发送OOM之前去回收软引用指向的对象。软引用通常用来实现内存敏感的缓存,如果还有空闲内存,就可以暂时保留缓存,如果JVM认为内存不足时就可以清理掉缓存,这样既能保证缓存的正常使用,又不至于缓存的过度使用导致内存耗尽。

​ 软引用使用SoftReference类实现,软引用可以和一个引用队列ReferenceQueue联合使用,如果软引用所引用的对象被垃圾回收器回收,JVM会把这个软引用加入到与之关联的引用队列,后续,我们可以调用ReferenceQueue的poll()方法来检查是否有它关联对象被回收,如果队列为空 返回null,否则该方法返回队列前面的一个 Reference对象。

弱引用:

​ 通过WeekReference类实现,比软引用的生命周期更短,在垃圾回收器扫描所管辖的内存区域时,一旦发现具有弱引用的对象,不管当前内存空间是否足够,都会去回收它的内存,由于垃圾回收器的线程优先级很低,不一定能很快的发现这些弱引用。它同样可以关联ReferenceQueue
幻象引用(虚引用)
​ 通过PhantomReference来实现。无法通过虚引用来访问对象的任何属性或者函数。虚引用仅仅提供一种保证对象被finalize之后,做某事机制。也就是关联ReferenceQueue

可达性分析:
强可达: 就是一个对象可以有一个或多个线程不通过各种引用就能访问到的情况。比如 new创建一个新的对象,那么创建它的线程就是强可达的状态
软可达: 就是只能通过软引用才能访问的对象
弱可达: 只能通过弱引用访问对象,这个状态是最接近finalize状态
幻象可达: 就是没用强、软、弱引用关系,并且finalize之后,只有幻象引用指向这个对象
不可达: 就认为这个对象可以被清除

7、String StringBuffer StringBuilder有什么区别?

String:
​ 1、String是不可变字符,它的底层是用一个final关键字修饰的字符数组

​ 2、String 对象在赋值之后就会在字符串常量池中缓存下来,如果下次创建会先判定常量池中是否已经有了缓存对象,如果有的话,直接返回引用给创建者

String str= "you"
str=str+"win"

​ str刚开始执行常量池中"you",拼接字符串"win"的时候,先开辟一块内存空间保存"win",然后再开辟一块内存空间来保存拼接之后的字符串"you win",并且 str指向拼接之后的字符串,整个过程一共占用三块内存空间,所以效率非常低,所以JDK中有了StringBuffer和StringBuilder。

​ StringBuilder 和 StringBuffer都是继承于AbstractStringBuiler 它们底层都是没有用final关键字修饰的char[]
StringBuilder 是线程不安全的,它的执行效率要比StringBuffer要高
StringBuffer 是线程安全的。

8、Int和Integer有什么区别?

​ 1、Integer是int的包装类,int则是基本类型的一种

​ 2、Integer必须实例化之后才能使用,而int变量则不需要

​ 3、Integer实际上是对象的引用,在new一个Integer时,实际上是生成了一共指向此对象的指针,而int则是直接存储的值。

​ 4、Integer 默认值null,int则是0

​ a、由于Integer变量实际上是对一个Integer对象的引用,所以两个通过new生成的Integer变量永远都是不相等的
(因为new生成的是两个不同的对象,其地址不一样)

​ b、Integer变量和int变量比较时,只要两个变量的值相等,则结果为true
(因为包装类型Integer和基本类型int比较时,java会自动将Integer拆箱为int,然后进行比较,所以实际上是两个int在进行比较)

​ c、非new生成的Integer和new生成的Integer变量进行比较时,结果都为false
(因为非new生成的Integer变量指向的是java常量池中的对象,而new生成的是指向堆中的对象的引用)

​ d、对于两个非new生成的Integer对象,进行比较时,如果变量值的范围在 -127到127之间,则结果为true,如果不在,结果为false

9、对比Vector、ArrayList、LinkedList有何区别

​ 这三者都实现了集合框架中的List接口(有序集合)

​ Vector 是java早期版本提供的线程安全的动态数组,如果不是在多线程中使用,不建议采用,因为同步的话有一定开销,

​ Vector内部是采用对象数组来保存数据的,然后提供扩容,当数组已满时,默认会创建一个是当前容量2倍的新数组,然后将旧数组元素拷贝到新数组中

​ ArrayList 是使用比较多的动态数组,本身不是线程安全的容器,ArrayList也是数组实现,也需要扩容,与Vector不同的是,Vector扩容到一倍,ArrayList只增加50%;

​ LinkedList 是双向链表,所以不需要调整容量,也不是线程安全的容器

Vector、ArrayList底层都是数组,LinkedList底层是双向链表

读写机制:
ArrayList   扩容时需要调用底层的System.arraycopy()方法进行大量的数组元素复制。
			删除元素时并不会减少数组容量(如果需要缩小数组的容量需要调用trimTosize())
			查找元素时遍历数组通过equals()比对
			
LinkedList  插入元素时,需要新建一个Entity对象,然后更新相应元素的前后引用。
			查找元素需要遍历链表
			删除元素时,只需删除元素,然后变更前后元素的指向

Vector 		默认创建一个大小为10对象数组,capacityIncrement默认为0;当扩容时 元素长度增加现有大小
			size+capacityIncrement,当capacityIncrement设置为复制,则扩容还是两倍

读写效率:
			ArrayList 对元素的增加和删除都会引起数组的移动,因此,对于删除和增加速度较慢,但检索支持随机访问速度很快
			LinkedList 基于链表实现,增加和删除的速度很快,但是检索需要遍历很慢
线程安全:
			ArrayList、LinkedList 是非线程安全的。
			Vector 是基于Synchrinized实现的线程安全

10、说说TreeSet、HashSet、LinkedHashSet

TreeSet: 支持自然顺序访问,但是 添加、删除、包含效率比较低,无序集合

HashSet: 利用哈希算实现,可以提供常数时间的插入、删除和包含操作,但是不保证有序

LinkedHashSet: 内部比HashSet多一个双向链表来记录元素的一个插入顺序,可以提供常数时间的插入、删除和包含操作但是需要维护顺序链表,有额外的开销

11、HashTable、HashMap、TreeMap有什么不同?

HashMap和HashTable 底层实现相同,不同的是HashTable 使用了Synchronized实现的线程安全,HashMap则是非线程安全

HashMap支持null作为键和值,只允许一个键为null HashTable不支持null

HashMap 的迭代器(Iterator)是有fail-fast机制,而HashTable的Enumerator迭代器不是fail-fast,

fail -fast:

​ 当有其他线程在迭代过程中修改了HashMap的结构(增加和删除),迭代器会抛出ConcurrentModificationException,
​ 但是迭代器本身的修改不会抛出异常。这也是Enumeration和Iterator的区别
Java5之后提供了更加完善的ConcurrentHashMap来实现线程安全的Map

12、如何保证线程安全的集合操作?

​ 各种并发安全的容器可以选择:Vector、HashTable、ConcurrentHashMap、CopyOnWriteArrayList

​ Collections提供工具类来获取一个同步的包装器:

Collections.synchronizendList()

Collections.synchrinizedMap()

Collections.synchrinizedSet()

13、重载和重写的区别?

重载:
发生在同一个类中: 要求方法名必须一致,方法的参数类型不同、或者个数不同、顺序不同、方法的修饰符和返回值也可以不同
重写:
发生在子类对父类方法的实现过程进行重新编写,发生在子类中。
方法名、方法参数列表需要一致,返回值的范围小于等于父类,抛出异常的范围也小于等于父类。访问修饰符的范围大于等于父类,
如果父类方法修饰符为private则子类就不能重写了。

14、构造器Constructor是否可被override?

Constructor 不能 override(重写) ,但是可以overload(重载)

15、面向对象编程的三大特性:封装、继承和多态

封装:
封装是可以把一个对象的属性私有化,同时又可以提供一些可以被外部访问属性的方法。

继承:
是使用已存在的类作为基础来创建一个新的类,这个新的类可以增加新的数据和方法,也可以使用父类的功能,但是不能选择性的
继承,通过继承可以方便实现代码的复用

多态:
一个引用变量到底指向哪个实例类,该引用变量发出的方法调用到底是哪个类实现的。必须在程序运行时才能确定。
实现多态的方式就是继承和接口

16、接口和抽象类的区别?

​ 1、接口的方法修饰符默认都是public,所有方法在接口中不能有实现(jdk7及以前,jdk8默认方法实现)

​ 2、接口中除了 static、final变量,不能有其他变量,而抽象类中则不一定。

​ 3、一个类可以实现多个接口,但只能继承一个抽象类。接口可以通过extends拓展多个接口

​ 4、接口方法默认修饰符public 抽象方法可以public protected、default

​ 5、抽象是对类的抽象,是一种模板设计;接口是对行为进行抽象

17、成员变量和局部变量的区别?

​ 1、从语法形式:
​ 成员变量属于类,而局部变量是在方法中定义的变量或者是方法的参数;成员变量可以被 public、private、static修饰,而局部变量不能被访问修饰符和static修饰。但是他们都可以被final修饰。

​ 2、变量内存储方式:
​ 如果成员变量使用static修饰,那么这个成员变量是属于类的,如果没有使用static修饰,
​ 成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。

​ 3、变量的生命周期:
​ 成员变量是属于对象的一部分,它随着对象的生命周期,局部变量是随着方法的调用而自动消失。

​ 4、成员变量如果没有显式的赋值,会自动以默认。值(final修饰的必须要显式赋值),局部变量是不会自动赋值的。

18、创建一个对象用什么运算符?对象的实体于对象的引用有什么不同?

​ 创建一个对象使用new运算符,new创建的对象(存在于堆内存),对象的引用是指向这个对象的实例的(存在于栈内存),
一个对象可以有多个引用或者0。

19、一个类的构造方法的作用是什么?如果一个类没有声明构造方法,该类能被正常使用吗?

构造器是完成对类的初始化工作。

​ 1、没有声明构造器,可以被正常实例化。因为类都有一个默认的无参构造器。

​ 2、如果显式的将无参构造器编程private,这样外部就没法使用了,但是我们可以提供一个方法在内部初始化然后返回(单例模式)

20、静态方法和实例方法有何不同?

​ 1、在调用静态方法时,可以使用 类名.方法名 的方式,也可以使用 对象名.方法名 的方式。静态方法无须实例化对象就可以使用。
​ 2、静态方法类只能访问静态成员(静态变量和静态方法),而不允许访问实例对象和实例方法。实例方法无限制。

21、== 与equals的区别?

== : 判断对象的地址是不是相等。(基本类型 == 比较的是值,引用类型比较的是内存地址)

equals(): 分两种情况
a、如果类没有重写equals方法,则通过equals()比较相当于==

​ b、如果重写equals()方法,一般都会去比较对象字段的内容

一般重写equals方法时,还要去重写hashcode方法

​ String中的equals方法是被JDK重写过的,String的equals是比较值的内容。
当我们创建String类型的对象时,虚拟机会在常量池中差值是否已存在相同的字符串。如果有直接返回,没有的话会在常量池中创建一个

22、hashCode()与equals()?

​ 1、如果两个对象相等,则hashCode()返回的hash值也相同

​ 2、如果两个对象相等,则调用equals()返回true

​ 3、两个对象有相同的hashCode值,则两个对象不一定相等

​ 4、equals()方法被重写,hashCode()也一定需要重写

​ 5、如果不重写 hashCode()默认是堆上对象产生的特殊值。如果不重写两个对象永远不会有相同的hashcode

23、Java是值传递还是引用传递?

Java只有值传递
​ 方法得到的所有参数值都是一个拷贝,也就是方法不能修改传递给它的任何参数变量的内容。

24、线程和进程的区别?

​ 线程是比进程更小的执行单位。一个进程在执行过程中可以产生多个线程

​ 线程与进程不同的是可以共享系统和内存空间,但是进程是独立占用cpu时间,系统的资源的。

​ 线程的资源开销比进程小的多

25、BIO、NIO、AIO?

​ BIO:同步阻塞I/O模式:

​ NIO:同步非阻塞I/O模式:抽象出Channel、Selector、Buffer对象实现

​ AIO:异步非阻塞I/O模式:基于事件和回调机制实现。

26、深拷贝和浅拷贝的区别?

浅拷贝: 对于基本类型进行值传递,对引用类型进行引用传递拷贝
深拷贝: 对于基本类型进行值传递,对引用类型会创建一个新的对象,并复制其中内容。

27、List、Set、Map三者的区别?

​ List:用于存储不唯一,有序的对象

​ Set:不允许重复的集合,不会有多个元素引用相同的对象

​ Map:使用键值对存储,Map会维护有关Key关联的Value。两个Key可以引用相同的对象,但是Key不能重复。

28、HashSet如何检查重复?

​ 当将对象加入HashSet时,HashSet会先调用对象的HshCode()方法获取hash值来判断对象加入位置,同时也会和其他对象的hash值做比较,如果没有相同的hash值,则没有重复,如果相同,这时会调用equals方法和hash值相同的对象比较,如果返回true则相同,插入失败,返回false则不同,插入成功。所有我们在使用HashSet时一定要重写HashCode()和equals()方法

29、HashMap的底层实现?(必问)

JDK1.8之前的实现:

​ HashMap底层是数组+链表组合在一起实现。HashMap通过Key的hashCode经过扰动函数得到一个hash值,然后(n-1)&hash判断当前元素在数组的位置,如果当前位置已经存在,判断该元素的hash和key是否相同,如果相同则覆盖,不同则通过链表解决hash冲突。
JDK1.8以及之后
​ 在解决hash冲突时做了优化,当链表长度大于阈值(默认是8)会将链表转化为红黑树,提高搜索效率。

30、HashMap的长度为什么是2的幂次方

​ 在定位元素位置时 需要做 hash % length 来得到元素的位置但是%的运算效率不高,所以可以通过 hash%length=hash&(length-1) 将取余运算转为位运算来提高效率,但是这个有个前提-length必须是2的幂次方。

31、HashMap多线程操作时会导致死循环的原因?

​ 在1.8之前的版本在扩容进行Rehash的过程中由于hash冲突是采用头插法将元素放在链表中的所以会出现死循环
​ 1.8时改为尾插法避免了该问题。

32、ConcurrentHashMap 底层实现?

JDK1.7:
分段锁
​ ConcurrentHashMap是由Segment数组和HashEntry数组组成。
​ Segment实现了ReentrantLock,是一种可重入锁,扮演锁的角色。
​ 就是将hashMap分成一段一段,只有当在一个段中发生冲突才需要并发操作。
JDK1.8:
​ 抛弃了Segment使用CAS和Synchronized保证并发安全。
​ synchronizen只锁定当前冲突后的链表和红黑树的根节点,只要hash不冲突,就不会出现并发操作,大大提高了效率。

持续更新中

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值