Java基础面试题总结2022

Java基础知识是面试中重要的一环,问题的回答要往更深的程度上进行思考回答。希望这里的总结能在面试中帮助到你。
如果小伙伴发现哪里有错误可以评论指出,我这边会及时优化!

1、==和equals比较

==:如果比较的是基本数据类型,比较的是值。如果比较的是引用数据类型,比较的引用对象的地址值是否相等。
equals:用来比较两个对象的内容是否相等
注意:equals不能用于基本数据类型的比较,如果没有对equals方法进行重写,
则比较的是引用类型变量地址值是否相等。

2、hashCode与equals

hashCode()介绍:存储方式是键值对。
	hashCoce()的作用是获取哈希码,也称为散列码,它实际上返回的是一个int整数
	作用:确定这个对象在哈希表中索引的位置,hashCode() 定义在JDK的Object.java中,Java中的任何类都包含有hashCode() 函数。
hashCode()如何检查重复?
	对象加入HashSet时候,HashSet会先计算对象的HashCode()值来判断对象加入的位置,看该位置是否有值,如果没有,添加。如果有值,会调用equals()方法检查两个对象是否相同,如果相同,HashSet不会让其添加。如果不同的是,就会重新散列到其他位置。这样大大减少了equals()方法的次数,大大提高了执行速度。
  • 如果两个对象相等,则hashcode一定也是相同的

  • 两个对象相等,则二者equals()方法返回true

  • 两个对象hashCode()相等,它们也不一定相等

  • 如果equals()被覆盖过,则hashCode()方法一定被覆盖

  • hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个

    对象无论如何都不会相等

3、final finally finalize区别

final可以修饰类、变量、方法:修饰类的时候不能被继承,修饰方法表示方法不能被重写,修饰变量的时候表示该变量不能被重新赋值

finally一般作用在try-catch方法中,在处理异常的时候,通常我们将一定执行的代码方法finally代码块中,一般来存放一些关闭资源的代码(扩展:finally不被执行到的方式)

finalize 是一个属于Object类的方法,该方法一般由垃圾回收器做调用,当我们调用System.gc()的时候,由垃圾回收器调用finalize()方法

4、String、StringBuffer、StringBuilder的区别及应用场景

String是final修饰的,是不可变的,每次操作都会产生新的String对象
StringBuffer是线程安全的,方法都被synchronized 所修饰
StringBuilder线程不安全的
性能:StringBuilder > StringBuffer > String

对于三者使用的总结:

  • 如果要操作少量的数据用String
  • 单线程:操作字符串缓冲区操作大量数据 选择 StringBuilder
  • 多线程:操作字符串缓冲区操作大量数据 选择 StringBuffer

5、重载和重写的区别

构造器不能被继承,因此不能被重写,但是可以被重载

重载发生在同一个类中:
	方法名一致,参数类型不一致,与其他无关
	重载与方法的返回值类型无关
	可以抛出不同的异常,可以有不同的修饰符
重写发生在父子类之间
	参数列表,返回值类型必须一致
	构造方法不能被重写,生命final的方法不能被重写,
	访问权限不能比父类被重写方法权限大
	重写的方法能够抛出任何非强制异常(UncheckedException,也叫非运行时异常),无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以

6、接口和抽象类的区别(问的很多)

不同点

  • 抽象类可以定义构造器 接口不行
    • 抽象类可以有抽象方法和具体方法 接口全部是抽象方法
  • 抽象类中的成员可以是 private、默认、protected、public 接口中的成员全都是 public 的
  • 抽象类中可以定义成员变量 接口中定义的成员变量实际上都是常量
  • 有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法
  • 抽象类中可以包含静态方法 接口中不能有静态方法
  • 一个类只能继承一个抽象类 一个类可以实现多个接口

相同点:

1.不能够实例化

2.可以将抽象类和接口类型作为引用类型

3.一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类

6、Java反射

反射机制是在运行状态中,对于任何一个类都能知道这个类的属性和方法,对于任何一个对象都能调用其中任何一个方法和属性,这种动态获取信息以及动态调用对象方法的功能称为Java的反射机制

优点:运行期类型的判断,动态类加载,提高代码的灵活度。
缺点:性能瓶颈,反射相当于一系列的解释操作,通知JVM要做的事情,性能比直接的Java代码要慢很多
反射应用场景

平时开发过程中很少使用到,但是很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。

  • JDBC链接数据库的时候Class.forName()通过反射加载数据库驱动
  • Spring框架XML配置装配Bean的过程
Java获取反射的三种方法
  1. 通过new对象的方式实现反射机制
  2. 通过路径实现反射机制
  3. 通过类名实现反射机制
public class Student {
    private int id;
    String name;
    protected boolean sex;
    public float score;
}
public class Get {
    //获取反射机制三种方式
    public static void main(String[] args) throws ClassNotFoundException {
        //方式一(通过建立对象)
        Student stu = new Student();
        Class classobj1 = stu.getClass();
        System.out.println(classobj1.getName());
        //方式二(所在通过路径-相对路径)
        Class classobj2 = Class.forName("fanshe.Student");
        System.out.println(classobj2.getName());
        //方式三(通过类名)
        Class classobj3 = Student.class;
        System.out.println(classobj3.getName());
    }
}

7、JAVA 异常分类及处理

Java中所有异常都来自顶级父类Throwable:其下两个子类Error和Exception

7.1 Error:

程序无法处理的错误,一旦出现这个错误,程序将被迫停止运行

7.2 Exception:

分为两个部分,运行时异常RunTimeException和检查异常CheckedException

CheckedException:发生在编译过程中,会导致编译不通过(编译器自动检测)

RunTimeException:RuntimeException类极其子类表示JVM在运行期间可能出现的错误。比如说试图使用空值对象的引用(NullPointerException)、数组下标越界(ArrayIndexOutBoundException)。此类异常属于不可查异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。

7.3 捕获异常

try-catch

try-catch-finally

7.4 抛出异常

throws:声明异常 作用在方法上抛出异常,为异常列表一旦该方法中某行代码抛出异常,则该异常将由调用该方法的上层方法处理。如果上层方法无法处理,可以继续将该异常向上层抛。

throw:抛出异常 在方法内,用throw来抛出一个Throwable类型的异常。一旦遇到到throw语句,后面的代码将不被执行。

8、集合类(重点)

集合和数组的区别
  • 数组是固定长度的;集合可变长度的。
  • 数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。
  • 数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。
Collection
List接口:有序可重复

ArrayList:底层以数组实现,查询速度快,添加的时候如果牵扯到数组元素的变动效率较慢(顺序添加的话速度也快)

删除的时候,需要做一次元素复制操作,如果要复制的元素很多,那么会比较耗费性能
插入元素的时候,也需要做一次元素复制操作,缺点同上。(顺序插入切数组空间足够速度也快)

LinkedList:数据结构基础之双向链表,按序号索引数据需要进行前向或后向遍历,查询数据较慢,但插入数据时只需要记录当前项的前后项即可,所以 LinkedList 插入速度较快

Vector:由于加了 synchronized 修饰,因此 Vector 是线程安全容器,但性能上较ArrayList差

Set接口:无序不可重复

HashSet:HashSet 是基于 HashMap 实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为PRESENT,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。

TreeSet:(有序,唯一): 红黑树(自平衡的排序二叉树。)

TreeSet 要求存放的对象所属的类必须实现 Comparable 接口,该接口提供了比较元素的 compareTo()方法,当插入元素时会回调该方法比较元素的大小。

LinkedHashSet:

LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。
Map
说一下 HashMap 的实现原理?

HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。

HashMap 基于 Hash 算法实现的

当我们往Hashmap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标
存储时,如果出现hash值相同的key,此时有两种情况。(1)如果key相同,则覆盖原始值;(2)如果key不同(出现冲突),则将当前的key-value放入链表中
获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。
理解了以上过程就不难明白HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。

需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)

HashMap在JDK1.7和JDK1.8中有哪些不同?HashMap的底层实现

数组和链表。数组的特点是:寻址容易,插入和删除困难;链表的特点是:寻址困难,但插入和删除容易;所以我们将数组和链表结合在一起,发挥两者各自的优势,使用一种叫做拉链法的方式可以解决哈希冲突。
JDK1.8之前采用的是拉链法。拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。

数据结构:
在这里插入图片描述
相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。
在这里插入图片描述

HashMap在JDK1.7 VS JDK1.8 比较

在JDK1.8之后主要解决或优化了一些问题

  • resize扩容优化
  • 引入了红黑树,目的是避免单挑链表过长而影响查询的效率
  • 解决了多线程死循环的问题,但仍是非线程安全的,多线程时可能会造成数据丢失的问题。
不同JDK 1.7JDK 1.8
存储结构数组 + 链表数组 + 链表 + 红黑树
初始化方式单独函数:inflateTable()直接集成到了扩容函数resize()
hash值计算方式扰动处理 = 9次扰动 = 4次位运算 + 5次异或运算扰动处理 = 2次扰动 = 1次位运算 + 1次异或运算
存放数据的规则无冲突时,存放数组;冲突时,存放链表无冲突时,存放数组;冲突 & 链表长度 < 8:存放单链表;冲突 & 链表长度 > 8:树化并存放红黑树
插入数据方式头插法(先讲原位置的数据移到后1位,再插入数据到该位置)尾插法(直接插入到链表尾部/红黑树)
扩容后存储位置的计算方式全部按照原来方法进行计算(即hashCode ->> 扰动函数 ->> (h&length-1))按照扩容后的规律计算(即扩容后的位置=原位置 or 原位置 + 旧容量)

**总结:**在1.7中数据结构采用数组和链表,1.8中采用数组+链表+红黑树的结构。初始化方式由1.7的单独函数到1.8的直接 集成到扩容函数resize()中,且在1.8之后hash值的计算方式做了优化。在1.7中数据存放时如果经过hashCode之后没有冲突则存放数组中,如果有冲突,则存放在链表中,在1.8之后,如果存储的数据没有冲突,存放在数组中,如果有冲突那么存放在链表中,当链表的长度 < 8时,存放在单链表中。如果链表长度 > 8且数组长度 > 64时链表转化为红黑树;如果链表数据 < 6那么将会由红黑树转化为链表结构

HashMap默认初始化长度是 16 扩容机制:2倍扩容(当达到数组长度的阈值时) 阈值 = 0.75 也就是大于原长度*0.75时数组自动扩容

ConcurrentHashMap 底层具体实现知道吗?实现原理是?

JDK1.7

首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式进行实现,结构如下:
在这里插入图片描述

一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。

JDK1.8

JDK1.8中,放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。
在这里插入图片描述
附加Java基础知识技能树学习链接
https://bbs.csdn.net/skill/java/java-af0bf70f564b4ac68a7fadda69d55086?category=468

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值