Java基础面试总结

1、谈谈对面向对象的理解

面向对象只关心实现对象是谁,有封装、继承、多态三大特性。

  • 封装:不关心内部实现,内部构造,只需要知道怎么操作它,比如电视、手机将内部封装起来,直接使用
  • 继承:使代码更容易扩展,比如有学生教师类,他们都有一些公用方法与属性,可以将其抽取出来定义为父类,再去继承它,可以复用代码,减少冗余
  • 多态:同一个方法调用,由于对象不同可能会有不同的行为,在具体场景中,不知道具体传进来的对象是student类还是teacher类,那么可以用people类接收它
    多态存在要有3个必要条件:继承、方法重写、父类引用指向子类对象。父类引用指向子类对象后,用父类引用调用子类重写的方法,多态就出现了

2、面向对象与面向过程的区别

面向过程:面向过程性能比面向对象高。因为对象需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、linux等一般采用面向过程开发。
面向对象:面向对象易维护、易复用、易扩展。 因为面向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护

3、JDK、JRE、JVM的关系

JDK:Java开发工具包,包含了JRE
JRE:Java程序的运行环境,包含了JVM和核心类库
JVM:Java虚拟机,Java程序需要运行在虚拟机上
只运行,JRE即可,要编译就要JDK

4、值传递与引用传递

值传递:方法接收的是实参值的拷贝,会创建副本
引用传递:方法接收的是实参所引用的对象在堆中的地址,不会创建副本,对形参的修改将影响到实参。
Java 中的传递方式是值传递。

5、==与equals()区别

对于基本数据类型比较的值,对于引用数据类型比较的是内存地址
equals()属于Object类的方法,没有重写前使用效果与==相同,可以重写例如String类,使其比较内容值是否相等

6、为什么重写 equals 时必须重写 hashCode ⽅法

hashcode可以获取哈希码,确定该对象在哈希表中的索引位置
提高效率:使用hashcode提前检验,定位,不用每一次都使用equlas方法比较,
保证没有重复对象出现,确保hashmap的去重性:假设只重写equals方法,不重写hashcode()方法,相同的对象hashcode不同,从而映射到不同下标下,HashMap无法保证去重性。

7、深拷贝与浅拷贝

clone() 是 Object类 的 protected 方法,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。
浅拷贝:浅拷贝会在堆上创建一个新的对象,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,拷贝对象和原对象共用同一个内部对象。
深拷贝:深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。
引用拷贝:引用拷贝就是两个不同的引用指向同一个对象。

8、java与C++的区别

(1)Java是纯面向对象的语言,所有的对象都继承自java.lang.Object类,C++为了兼容C既支持面向对象也支持面向过程
(2)Java通过虚拟机从而实现跨平台特性,但是C++依赖于特定的平台
(3)Java 没有指针,它的引用可以理解为安全指针,而 C++ 具有和 C 一样的指针。
(4)java支持自动垃圾回收,而C++需要手动释放
(5)Java 不支持多重继承,只能通过实现多个接口来达到相同目的,而 C++ 支持多重继承
(6)Java 不支持操作符重载,虽然可以对两个 String 对象执行加法运算,但是这是语言内置支持的操作,不属于操作符重载,而 C++ 可以。
(7)Java 的 goto 是保留字,但是不可用,C++ 可以使用 goto。

9、重载和重写的区别

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分
重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏代换原则);父类中private修饰的方法子类不能重写。

10、String为什么不可变以及不可变的好处

保存字符串的字符数组被final修饰且为私有的,并且String类没有提供暴露修改这个字符串的方法。
不可变的好处:缓存哈希值(使得哈希值不变,只进行一次计算)、Stringpool需要、线程安全性、避免网络安全问题

11、String,StringBuffer和StringBuilder之间的区别是什么?

StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在 AbstractStringBuilder中也是使用字符数组保存字符串,不过没有使用 final 和 private 关键字修饰,所以这两种对象都是可变的。
可变性:String是不可变对象,任何对String的修改都会创建新的String对象,StringBuffer和StringBuilder可变类。
效率:频繁对字符串进行操作时,使用String会生成一些临时对象,多一些附加操作,效率低些
安全性:String是不可变的,相当于常量,线程安全,StringBuffer方法由synchronized修饰,线程安全

12、8种基本数据类型与占用空间大小

byte(1字节)、short(2)、int(4)、long(8)、float(4)、double(8)、boolean(4-内存对齐)、char(2)
逻辑上boolean型只占用1bit,但是虚拟机底层对boolean值进行操作实际使用的是int型,操作boolean数组则使用byte型;

13、java集合的机制的继承关系

(1)以Collection为父接口
(2)以Map为父接口,存储键值对
在这里插入图继承片描述

14、集合和数组的区别

在这里插入图片描述

15、List、Set、Map 、Queue集合存放元素的特点

List:有序可重复,元素有下标
Set:无序不可重复,元素没有下标
Map:key-value存储,key值无序不可重复
Queue:按特定的排序规则来确定先后顺序,存储的元素是有序的、可重复的

16、集合底层数据结构

List

  • ArrayList:Object数组
  • Vector:Object数组
  • LinkedList:双向链表

Set

  • HashSet:是基于HashMap实现的,底层采用HashMap来保存元素
  • LinkedHashSet:LinkedHashSet是HashSet的子类,是基于LinkedHashMap实现的
  • TreeSet:红黑树(自平衡的排序二叉树)

Queue
PriorityQueue: 数组来实现二叉堆
ArrayQueue:数组+双指针

#Map

  • HashMap:
    JDK1.8之前HashMap是由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的。JDK1.8之后在解决哈希冲突时有了较大的变化,当链表长度大于等于阈值(默认为8)时,考虑将链表转化为红黑树,以减少搜索时间。(将链表转换成红黑树之前,如果当前数组的长度小于64,那么会选择先进行数组扩容,而不是转换为红黑树)

  • LinkedHashMap:LinkedHashMap 继承自 HashMap,数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保存键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。

  • HashTable:数组+链表,数组是HashTable的主体,链表则是为了解决哈希冲突而存在的

  • TreeMap:红黑树

17、Set与List的区别

1、List,Set都是继承自Collection接口
2、特点区分:list有序可重复、Set无序不可重复,另外加入Set的Object必须重写equals(),list支持for循环通过下标来遍历,也可以使用迭代器遍历,但是Set
只能用迭代器遍历
3、 Set和list对比:

  • Set检索元素效率低下,删除和插入效率高
  • List和数组类似,list可以动态增长,查找元素效率高,插入删除元素效率低

18、ArrayList和Vector的区别

线程安全:ArrayList是线程不安全的,Vector是线程安全的
效率:ArrayList效率高,因为不加锁
扩容:ArrayList初始容量为0,第一次添加时容量为10,扩容为原来的1.5倍;Vector初始容量为10,扩容为原来的2倍

19、 ArrayList和LinkedList的区别

底层数据结构:ArrayList底层使用数组,LinkedList底层采用双向链表(注意:JDK1.6之前为循环链表,JDK1.7取消了循环)
查询效率:数组支持随机快速访问,而链表需要依次遍历,更耗时
插入删除:在非首尾的地方增删元素,LinkedList的效率高
内存地址:ArrayList内存地址连续,LinkedList内存地址逻辑上连续,空间上不连续

20、ArrayList的扩容机制

添加元素时使用ensureCapacityInternal()方法来保证容量足够,如果不够时需要使用grow()方法进行扩容
新容量的大小为oldCapacity +(oldCapacity>>1),所以新容量大约是原容量的1.5倍
扩容操作需要调用Array.copyof()把原数组复制到新数组中,操作代价很高,因此最好在创建ArrayList对象时就指定大概的容量大小,减少扩容操作的次数

21、Queue和Deque的区别

Queue是单端队列,只能从一端插入元素,另一端删除元素,实现上一般遵循先进先出(FIFO)原则。Queue扩展了Collection接口,根据操作失败后处理方式的不同可以分为两类方法:一种是在操作失败之后抛出异常,一种则会返回特殊值

Deque是双端队列,在队列的两端都可以插入或删除元素。Deque扩展了Queue的接口,增加了在队首和队尾进行插入和删除的方法,同样根据失败后的处理方式的不同分为两种:一种是操作失败后抛出异常,一种是返回特殊值

22、ArrayDeque与LinkedList的区别

  • ArrayDeque 和 LinkedList 都实现了 Deque 接口,两者都具有队列的功能。
  • ArrayDeque 是基于可变长的数组和双指针来实现,而 LinkedList 则通过链表来实现。
  • ArrayDeque 不支持存储 NULL 数据,但 LinkedList 支持。
  • ArrayDeque 是在 JDK1.6 才被引入的,而LinkedList 早在 JDK1.2时就已经存在。
  • ArrayDeque 插入时可能存在扩容过程, 不过均摊后的插入操作依然为 O(1)。
  • 虽然 LinkedList不需要扩容,但是每次插入数据时都需要申请新的堆空间,均摊性能相比更慢。

23、说一说PriorityQueue(优先队列)

  • PriorityQueue元素出队顺序是与优先级相关的,总是优先级最高的元素先出队。
  • PriorityQueue 利用了二叉堆的数据结构来实现的,底层使用可变长的数组来存储数据
  • PriorityQueue 通过堆元素的上浮和下沉,实现了在 O(logn) 的时间复杂度内插入元素和删除堆顶元素。
  • PriorityQueue 是非线程安全的,且不支持存储 NULL 和 non-comparable 的对象。
  • PriorityQueue 默认是小顶堆,但可以接收一个 Comparator 比较器作为构造参数,从而来自定义元素优先级

24、Hashmap与Hashtable的区别

  • 线程安全:HashMap是非线程安全的,HashTable是线程安全的
  • 效率:由于HashTable方法被sychonized修饰,效率比HashMap低
  • 底层数据结构:HashMap Jdk1.8当链表长度大于等于8且数组长度大于等于64时链表会转为红黑树,HashTable没有这样的机制
  • 容量和扩容:默认初始容量:HashTable为11,HashMap为16,扩容时:HashTable变为原来的2*n倍+1,HashMap变为原来的二倍
  • HashMap中,可以使用null作为键或值,HashTable中不行

25、HashMap和HashSet的区别

HashSet底层就是基于HashMap实现的。(除了个别方法自己实现,其他调用hashmap的),HashMap实现了Map接口,HashSet实现了Set接口
HashMap使用键值(Key)计算hashcode,HashSet使用成员对象来计算hashcode值

26、HashSet如何检查重复

对HashMap封装了一层,很多方法还是直接用的map的,通过计算对象的hash值定位,同时比较与其他对象hashcode值是否相等,若没有相等则没有重复对象,若有相同的则用equals判断

27、HashMap jdk1.8与1.7的区别

  • JDK8中新增了红黑树,JDK8是通过数组+链表+红黑树来实现的
  • JDK1.7中链表的插入是用的头插法,而JDK8中则改为了尾插法
  • 因为jdk1.7是用单链表进行的纵向延伸,当采用头插法时会容易出现逆序且环形链表死循环问题。但是在JDK1.8之后因为加入了红黑树使用尾插法,能够避免出现逆序和链表死循环问题
  • JDK1.7是先扩容再添加新元素,JDK1.8是先添加新元素然后再扩容
  • JDK1.8中数组扩容的条件发生了变化,只会判断当前元素个数是否超过了阈值,而不再判断当前put进来的元素对应的数组下标位置是否有值

28、HashMap实现概述

1、hashmap底层是使用数组+链表+红黑树实现的,
2、put过程:
执行put方法会执行putval方法,执行putval前会计算hash值

  • 调用key对象的hashcode方法计算出来的哈希值,将hash值得高16右移并与原哈希值取异或运算,混合高 16 位和低 16 位的值,得到一个更加散列的哈希值
  • 然后进入putval方法,会判断数组是否为空,或者数组长度是否为0,若是为空进入resize()方法初始化数组长度、扩容阈值
  • 然后通过(n-1)&hash值判断当前元素存放的位置,判断数组在该位置是否为空,如果为空直接新建一个节点插入,
  • 不为空的话,会判断当前下标的key与要插入的key是否相等。如果相等,会把这个节点赋给一个新的节点(注意是新建一个节点来记录,在最后判断那个节点是否为空)
  • 不相等会判断是否是树的节点(instanceof关键字),如果是树的节点进入putTreeval()来处理,返回值也是赋给那个新的节点
  • 不是树的话,会遍历这条链表,如果遍历到链表的最后还没有发现重复的key,那么就直接插入到链表最后,然后如果链表的长度大于扩容阈值的话,就进入treeifyBin()判断是否需要树化,然后退出循环
    判断树化的条件(链表长度>=8进入treeifyBin(tab,hash);进入该方法还需要判断当前数组长度大于等于64才能树化,如果小于64进入resize()方法扩容)
  • 最后检查之前那个新的节点是否为空,不为空的话就说明存在重复的key,然后把要插入的value值直接覆盖到新的那个节点上就行了

3、get过程与put过程基本相似

扩容机制:
(1)元素的个数大于扩容阈值
当容量达到(目前容量*负载因子)的大小时,就要开始扩容,这样做的目的是减少hash的碰撞,每次扩容都会新建一个两倍容量的数组,将原来的数组重新hash进去(注意不是复制进去,因为数组的长度变了,hash的规则也变了)

29、为什么要转换为红黑树?为什么不直接开始就使用红黑树?

(1)因为当长度过长,遍历链表的时间也会越来越长,用红黑树可以减少遍历时间
(2)如果一开始使用红黑树,那么就要进行左旋、右旋、变色等操作,在元素个数小的时候比较消耗时间,并且遍历时间消耗与链表没有什么区别

30、可不可以使用二叉树,不用红黑树?为什么阈值是8?

(1)可以使用二叉树,但是使用二叉树可能会出现只有左子树或者右子树的情况,这样和链表没什么区别
(2)阈值是8是因为泊松分布,单个hash槽中元素个数为8的概率小于百万分之一,所以选择7为分水岭,为7不做操作,为6时红黑树转链表

31、一般使用什么作为key?

一般使用String这种不可变类作为key,因为这样的话在对象创建之后hashcode就是定值, 并且这种类已经很好的实现了hashcode与equals方法的重写

32、hashmap的长度为什么是2的幂次方

因为当容量为2的幂时,hash&(length-1)运算才等价于数组长度取模,而&比%运算具有更高的效率,而且这样能尽量均匀分布减少哈希碰撞。
2的n次方实际就是1后面n个0,2的n次方-1实际就是n个1。这样按位“与”时,哈希值每一位都能 & 1,hash的每一位都参与运算,分布更均匀。

33、为什么要把key的哈希码右移16位呢

因为hashcode的int类型正好32位,为了使计算出的hash值更加的分散,所以选择先将hash无符号右移16位,然后再与原hash异或时,就能达到hash的高16位和低16位都能参与计算,尽最大努力减少哈希冲突

35、TreeMap
TreeMap是基于红黑树的一种提供顺序访问的Map,操作的时间复杂度都是0(log n),默认按键的升序排序,具体顺序可以指定comparator比较器决定。TreeMap的键和值都不能为null

34、反射原理

什么是反射?
动态的获取类的各个属性以及调用它的方法
原理:通过将类对应的字节码文件加载到jvm内存中得到一个Class对象,通过这个Class对象可以获取实例的各个属性以及调用它的方法。
优点:让代码更加灵活,为各种框架提供开箱即用的功能提供了便利
缺点:让我们在运行时有了分析操作类的能力,这同样也增加了安全问题

获取Class对象的方式
反射机制的使用场景:

  1. 通过反射机制运行配置文件内容
    • 加载配置文件,并解析配置文件得到相应信息
    • 根据解析的字符串利用反射机制获取某个类的Class实例
    • 动态配置属性
  2. jdk动态代理
  3. jdbc通过Class.forName()加载数据的驱动程序
  4. Spring解析XML装配Bean

35、Object的方法有哪些?notify与notifyAll的区别?

  • getClass—final方法:用于返回当前运行时对象的Class对象
  • toString方法:--------对象的字符串表达形式(对象所属类的名称+@+转换为16进制的对象的哈希值组成的字符串)
  • equals方法------------没有重写的话,对于基本数据数据来说比较的值是否相等,对于引用类型来说,比较对象的引用地址是否相等
  • Clone方法-------------实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常
  • notify方法-------------唤醒在该对象上等待的某个线程
  • notifyAll方法----------唤醒在该对象上等待的所有线程
  • wait方法---------------让当前线程进入等待状态,同时,wait()方法也会让当前线程释放它所持有的的锁,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法来唤醒
  • finalize方法----------- 可以用于对象的自我拯救
  • hashCode方法:计算哈希值,建议和equals方法一起重写

36、Java中接口和抽象类的区别

方法:接口中只能定义方法,不能有方法的实现,java1.8中可以定义default(可以在接口中实现方法,不会破坏实现类)与static方法体,而抽象类可以有方法定义与实现,方法可在抽象类中实现。
成员变量:接口成员变量只能是public static final的,且必须初始化,抽象类可以和普通类一样任意类型
继承实现:一个类只能继承一个抽象类(extends),可以实现多个接口(implements)
其他:都不能实例化,接口不能有构造函数,抽象类可以有

37、throw与throws的区别

throw 关键字用在方法内部,只能用于抛出一种异常;throws 关键字用在方法声明上,可以抛出多个异常,用来标识该方法可能抛出的异常列表

38、异常体系

Throwable的子类为Error和Exception
Exception的子类为RuntimeException异常和RuntimeException以外的异常(例如IOException)
Error就是一些程序处理不了的错误,代表JVM出现了一些错误,应用程序无法处理。

39、包装类型和基本类型的区别是什么

最主要的区别是包装类型是对象,拥有属性和方法,可以很方便的调用一些基本的方法,默认值是null,
其次是基本数据类型直接存在在栈中,而包装类型是一个对象,对象的引用变量存储在栈中,存储了对象在堆中的地址,对象的数据存在堆中

40、BIO、NIO、AIO、IO多路复用的区别

• BIO (Blocking I/O):同步阻塞 I/O 模式,数据的读写必须阻塞在⼀个线程内等待其完成。
• NIO (Non-blocking/New I/O):NIO 是⼀种同步⾮阻塞的 I/O 模型
• AIO (Asynchronous I/O)它是异步⾮阻塞的 IO 模型。异步 IO 是基于事件和回调机制实现的,也就是应⽤操作之后会直接返回,不会堵塞在那⾥

41、谈谈常量池的理解

Class 文件中的常量池(编译器生成的字面量和符号引用)会在类加载后放入方法区运行时常量池
方法区运行时常量池除了在编译期生成的常量,还允许动态生成,例如 String 类的 intern()。
字符串常量池(jdk6及以前,在方法区,jdk7及以后移到堆)
八种基本类型的包装类的对象池

42、内存泄漏与内存溢出

无用内存得不到释放。程序申请了内存,使用完后又不能归还JVM,造成内存泄漏,内存泄漏多了就造成内存溢出。内存溢出——》OOM(内存满了,没有内存给实例分配空间,且无法扩展)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值