20个高级Java开发面试题及答案!

1、java 中都有哪些引用类型?

(1)强引用

Java中默认声明的就是强引用,比如:

  1. Object obj = new Object();
  2. obj = null;

只要强引用存在,垃圾回收器将永远不会回收被引用的对象。如果想被回收,可以将对象置为null;

(2)软引用(SoftReference)

在内存足够的时候,软引用不会被回收,只有在内存不足时,系统才会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会跑出内存溢出异常。

byte\[\] buff = new byte1024 \* 1024;

SoftReference<byte\[\]> sr = new SoftReference<>(buff);

(3)弱引用(WeakReference)

进行垃圾回收时,弱引用就会被回收。

(4)虚引用(PhantomReference)

(5)引用队列(ReferenceQueue)

引用队列可以与软引用、弱引用、虚引用一起配合使用。

当垃圾回收器准备回收一个对象时,如果发现它还有引用,就会在回收对象之前,把这个引用加入到引用队列中。

程序可以通过判断引用队列中是否加入了引用,来判断被引用的对象是否将要被垃圾回收,这样可以在对象被回收之前采取一些必要的措施。

2、在 Java 中,为什么不允许从静态方法中访问非静态变量?

  1. 静态变量属于类本身,在类加载的时候就会分配内存,可以通过类名直接访问;
  2. 非静态变量属于类的对象,只有在类的对象产生时,才会分配内存,通过类的实例去访问;
  3. 静态方法也属于类本身,但是此时没有类的实例,内存中没有非静态变量,所以无法调用。

3、说说Java Bean的命名规范

  1. JavaBean 类必须是一个公共类,并将其访问属性设置为 public
  2. JavaBean 类必须有一个空的构造函数:类中必须有一个不带参数的公用构造器,此构造器也应该通过调用各个特性的设置方法来设置特性的缺省值。
  3. 一个javaBean类不应有公共实例变量,类变量都为private
  4. 持有值应该通过一组存取方法(getXxx 和 setXxx)来访问:对于每个特性,应该有一个带匹配公用 getter 和 setter 方法的专用实例变量。

属性为布尔类型,可以使用 isXxx() 方法代替 getXxx() 方法。

通常属性名是要和 包名、类名、方法名、字段名、常量名作出区别的:

首先:必须用英文,不要用汉语拼音

(1)包(package)

用于将完成不同功能的类分门别类,放在不同的目录(包)下,包的命名规则:将公司域名反转作为包名。比如www.sohu.com 对于包名:每个字母都需要小写。比如:com.sohu.test;该包下的Test类的全名是:com.sohu.Test.Java 。

如果定义类的时候没有使用package,那么java就认为我们所定义的类位于默认包里面(default package)。

(2)类

首字母大写,如果一个类由多个单词构成,那么每个单词的首字母都大写,而且中间不使用任何的连接符。尽量使用英文。如ConnectionFactory

(3)方法

首单词全部小写,如果一个方法由多个单词构成,那么从第二个单词开始首字母大写,不使用连接符。addPerson

(4)字段

与方法相同。如ageOfPerson

(5)常量

所有单词的字母都是大写,如果有多个单词,那么使用下划线链接即可。

如:public static final int AGE_OF_PERSON = 20; //通常加上static

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记及答案【扫一扫】 即可免费获取**

4、Java Bean 属性命名规范问题分析

  1. public class User {

  2. private String busName;

  3. private String pCount;

  4. private Boolean isRunning;

  5. //正确的命名方式,驼峰式的

  6. public String getBusName() {

  7. return busName;

  8. }

  9. public void setBusName(String busName) {

  10. this.busName = busName;

  11. }

  12. //这是什么?

  13. public String getpCount() {

  14. return pCount;

  15. }

  16. public void setpCount(String pCount) {

  17. this.pCount = pCount;

  18. }

  19. //这个也是不允许的

  20. public Boolean getIsRunning() {

  21. return isRunning;

  22. }

  23. public void setIsRunning(Boolean isRunning) {

  24. this.isRunning = isRunning;

  25. }

  26. }

  27. javabean属性命名尽量使用常规的驼峰式命名规则

  28. 属性名第一个单词尽量避免使用一个字母:如eBook, eMail。

  29. boolean属性名避免使用 "is" 开头的名称

  30. 随着jdk, eclipse, spring 等软件版本的不断提高, 底版本的出现的问题可能在高版本中解决了, 低版本原来正常的代码可能在高版本环境下不再支持。

5、什么是 Java 的内存模型?

在了解什么是 Java 内存模型之前,先了解一下为什么要提出 Java 内存模型。

之前提到过并发编程有三大问题

CPU 缓存,在多核 CPU 的情况下,带来了可见性问题

操作系统对当前执行线程的切换,带来了原子性问题

译器指令重排优化,带来了有序性问题

为了解决并发编程的三大问题,提出了 JSR-133,新的 Java 内存模型,JDK 5 开始使用。

简单总结下

Java 内存模型是 JVM 的一种规范

定义了共享内存在多线程程序中读写操作行为的规范

屏蔽了各种硬件和操作系统的访问差异,保证了 Java 程序在各种平台下对内存的访问效果一致

解决并发问题采用的方式:限制处理器优化和使用内存屏障

增强了三个同步原语(synchronized、volatile、final)的内存语义

定义了 happens-before 规则

6、在 Java 中,什么时候用重载,什么时候用重写?

(1)重载是多态的集中体现,在类中,要以统一的方式处理不同类型数据的时候,可以用重载。

(2)重写的使用是建立在继承关系上的,子类在继承父类的基础上,增加新的功能,可以用重写。

(3)简单总结:

重载是多样性,重写是增强剂;

目的是提高程序的多样性和健壮性,以适配不同场景使用时,使用重载进行扩展;

目的是在不修改原方法及源代码的基础上对方法进行扩展或增强时,使用重写;

生活例子:

你想吃一碗面,我给你提供了拉面,炒面,刀削面,担担面供你选择,这是重载;

你想吃一碗面,我不但给你端来了面,还给你加了青菜,加了鸡蛋,这个是重写;

设计模式:

cglib实现动态代理,核心原理用的就是方法的重写;

详细解答:

Java的重载(overload) 最重要的应用场景就是构造器的重载,构造器重载后,提供多种形参形式的构造器,可以应对不同的业务需求,加强程序的健壮性和可扩展性,比如我们最近学习的Spring源码中的ClassPathXmlApplicationContext,它的构造函数使用重载一共提供了10个构造函数,这样就为业务的选择提供了多选择性。在应用到方法中时,主要是为了增强方法的健壮性和可扩展性,比如我们在开发中常用的各种工具类,比如我目前工作中的短信工具类SMSUtil, 发短信的方法就会使用重载,针对不同业务场景下的不同形参,提供短信发送方法,这样提高了工具类的扩展性和健壮性。

总结:重载必须要修改方法(构造器)的形参列表,可以修改方法的返回值类型,也可以修改方法的异常信息即访问权限;使用范围是在同一个类中,目的是对方法(构造器)进行功能扩展,以应对多业务场景的不同使用需求。提高程序的健壮性和扩展性。

java的重写(override) 只要用于子类对父类方法的扩展或修改,但是在我们开发中,为了避免程序混乱,重写一般都是为了方法的扩展,比如在cglib方式实现的动态代理中,代理类就是继承了目标类,对目标类的方法进行重写,同时在方法前后进行切面织入。

总结:方法重写时,参数列表,返回值得类型是一定不能修改的,异常可以减少或者删除,但是不能抛出新的异常或者更广的异常,方法的访问权限可以降低限制,但是不能做更严格的限制。

(4)在里氏替换原则中,子类对父类的方法尽量不要重写和重载。(我们可以采用final的手段强制来遵循)

7、举例说明什么情况下会更倾向于使用抽象类而不是接口?

接口和抽象类都遵循"面向接口而不是实现编码"设计原则,它可以增加代码的灵活性,可以适应不断变化的需求。下面有几个点可以帮助你回答这个问题:在 Java 中,你只能继承一个类,但可以实现多个接口。所以一旦你继承了一个类,你就失去了继承其他类的机会了。

接口通常被用来表示附属描述或行为如: Runnable 、 Clonable 、 Serializable 等等,因此当你使用抽象类来表示行为时,你的类就不能同时是 Runnable 和 Clonable( 注:这里的意思是指如果把 Runnable 等实现为抽象类的情况 ) ,因为在 Java 中你不能继承两个类,但当你使用接口时,你的类就可以同时拥有多个不同的行为。

在一些对时间要求比较高的应用中,倾向于使用抽象类,它会比接口稍快一点。如果希望把一系列行为都规范在类继承层次内,并且可以更好地在同一个地方进行编码,那么抽象类是一个更好的选择。有时,接口和抽象类可以一起使用,接口中定义函数,而在抽象类中定义默认的实现。

8、实例化对象有哪几种方式

  • new
  • clone()
  • 通过反射机制创建
复制代码
  1. //用 Class.forName方法获取类,在调用类的newinstance()方法
  2. Class<?> cls = Class.forName("com.dao.User");
  3. User u = (User)cls.newInstance();
  • 序列化反序列化
复制代码
  1. //将一个对象实例化后,进行序列化,再反序列化,也可以获得一个对象(远程通信的场景下使用)
  2. ObjectOutputStream out = new ObjectOutputStream (new FileOutputStream("D:/data.txt"));
  3. //序列化对象
  4. out.writeObject(user1);
  5. out.close();
  6. //反序列化对象
  7. ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:/data.txt"));
  8. User user2 = (User) in.readObject();
  9. System.out.println("反序列化user:" + user2);
  10. in.close();

9、byte类型127+1等于多少

byte的范围是-128~127。

字节长度为8位,最左边的是符号位,而127的二进制为01111111,所以执行+1操作时,01111111变为10000000。

大家知道,计算机中存储负数,存的是补码的兴衰。左边第一位为符号位。

那么负数的补码转换成十进制如下:

一个数如果为正,则它的原码、反码、补码相同;一个正数的补码,将其转化为十进制,可以直接转换。

已知一个负数的补码,将其转换为十进制数,步骤如下:

  1. 先对各位取反;
  2. 将其转换为十进制数;
  3. 加上负号,再减去1;

例如10000000,最高位是1,是负数,①对各位取反得01111111,转换为十进制就是127,加上负号得-127,再减去1得-128;


10、Java 容器都有哪些?

(1)Collection

① set

HashSet、TreeSet

② list

ArrayList、LinkedList、Vector

(2)Map

HashMap、HashTable、TreeMap

11、Collection 和 Collections 有什么区别?

(1)Collection是最基本的集合接口,Collection派生了两个子接口list和set,分别定义了两种不同的存储方式。

(2)Collections是一个包装类,它包含各种有关集合操作的静态方法(对集合的搜索、排序、线程安全化等)。

此类不能实例化,就像一个工具类,服务于Collection框架。

12、list与Set区别

(1)List简介

实际上有两种List:一种是基本的ArrayList,其优点在于随机访问元素,另一种是LinkedList,它并不是为快速随机访问设计的,而是快速的插入或删除。

ArrayList:由数组实现的List。允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。

LinkedList :对顺序访问进行了优化,向List中间插入与删除的开销并不大。随机访问则相对较慢。

还具有下列方 法:addFirst(), addLast(), getFirst(), getLast(), removeFirst() 和 removeLast(), 这些方法 (没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。

(2)Set简介

Set具有与Collection完全一样的接口,因此没有任何额外的功能。实际上Set就是Collection,只是行为不同。这是继承与多态思想的典型应用:表现不同的行为。Set不保存重复的元素(至于如何判断元素相同则较为负责)

Set : 存入Set的每个元素都必须是唯一的,因为Set不保存重复元素。加入Set的元素必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。

HashSet:为快速查找设计的Set。存入HashSet的对象必须定义hashCode()。

TreeSet: 保存次序的Set, 底层为树结构。使用它可以从Set中提取有序的序列。

(3)list与Set区别

① List,Set都是继承自Collection接口

② List特点:元素有放入顺序,元素可重复 ,Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉,(元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的,加入Set 的Object必须定义equals()方法 ,另外list支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。)

③ Set和List对比:

Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。

List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记及答案【扫一扫】 即可免费获取**

13、HashMap 和 Hashtable 有什么区别?

  1. HashMap是线程不安全的,HashTable是线程安全的;
  2. HashMap中允许键和值为null,HashTable不允许;
  3. HashMap的默认容器是16,为2倍扩容,HashTable默认是11,为2倍+1扩容;

14、说一下 HashMap 的实现原理?

(1)简介

HashMap基于map接口,元素以键值对方式存储,允许有null值,HashMap是线程不安全的。

(2)基本属性

初始化大小,默认16,2倍扩容;

负载因子0.75;

初始化的默认数组;

size

threshold。判断是否需要调整hashmap容量

(3)HashMap的存储结构

JDK1.7中采用数组+链表的存储形式。

HashMap采取Entry数组来存储key-value,每一个键值对组成了一个Entry实体,Entry类时机上是一个单向的链表结构,它具有next指针,指向下一个Entry实体,以此来解决Hash冲突的问题。

HashMap实现一个内部类Entry,重要的属性有hash、key、value、next。

JDK1.8中采用数据+链表+红黑树的存储形式。当链表长度超过阈值(8)时,将链表转换为红黑树。在性能上进一步得到提升。

15、set有哪些实现类?

(1)HashSet

HashSet是set接口的实现类,set下面最主要的实现类就是HashSet(也就是用的最多的),此外还有LinkedHashSet和TreeSet。

HashSet是无序的、不可重复的。通过对象的hashCode和equals方法保证对象的唯一性。

HashSet内部的存储结构是哈希表,是线程不安全的。

(2)TreeSet

TreeSet对元素进行排序的方式:

元素自身具备比较功能,需要实现Comparable接口,并覆盖compareTo方法。

元素自身不具备比较功能,需要实现Comparator接口,并覆盖compare方法。

(3)LinkedHashSet

LinkedHashSet是一种有序的Set集合,即其元素的存入和输出的顺序是相同的。

16、说一下 HashSet 的实现原理?

HashSet实际上是一个HashMap实例,数据存储结构都是数组+链表。

HashSet是基于HashMap实现的,HashSet中的元素都存放在HashMap的key上面,而value都是一个统一的对象PRESENT。

java 复制代码
private static final Object PRESENT = new Object();
AI生成项目

HashSet中add方法调用的是底层HashMap中的put方法,put方法要判断插入值是否存在,而HashSet的add方法,首先判断元素是否存在,如果存在则插入,如果不存在则不插入,这样就保证了HashSet中不存在重复值。

通过对象的hashCode和equals方法保证对象的唯一性。

17、ArrayList 和 LinkedList 的区别是什么?

ArrayList是动态数组的数据结构实现,查找和遍历的效率较高;

LinkedList 是双向链表的数据结构,增加和删除的效率较高;

18、如何实现数组和 List 之间的转换?

复制代码
  1. String[] arr = {"zs","ls","ww"};
  2. List<String> list = Arrays.asList(arr);
  3. System.out.println(list);
  4. ArrayList<String> list1 = new ArrayList<String>();
  5. list1.add("张三");
  6. list1.add("李四");
  7. list1.add("王五");
  8. String[] arr1 = list1.toArray(new String[list1.size()]);
  9. System.out.println(arr1);
  10. for(int i = 0; i < arr1.length; i++){
  11. System.out.println(arr1[i]);
  12. }

19、在 Queue 中 poll()和 remove()有什么区别?

(1)offer()和add()区别:

增加新项时,如果队列满了,add会抛出异常,offer返回false。

(2)poll()和remove()区别:

poll()和remove()都是从队列中删除第一个元素,remove抛出异常,poll返回null。

(3)peek()和element()区别:

peek()和element()用于查询队列头部元素,为空时element抛出异常,peek返回null。

20、哪些集合类是线程安全的

Vector:就比Arraylist多了个同步化机制(线程安全)。

Stack:栈,也是线程安全的,继承于Vector。

Hashtable:就比Hashmap多了个线程安全。

ConcurrentHashMap:是一种高效但是线程安全的集合

相关推荐
颜酱几秒前
02 | 搭骨架:用 LangGraph 编排 12 步工作流
前端·人工智能·后端
嘉伟桑1 小时前
Python调用电价API返回JSON示例:分时电价、现货电价和字段解析
后端
武子康1 小时前
Shippy:确定性工具、会话级 Sandbox 与 Live-Data Eval(4 类收敛 + 7 步实现方案 + 6 类评测指标)
前端·人工智能·后端
我叫黑大帅2 小时前
我为什么单一消费者的场景下,要用 Redis List 当消息队列?
redis·后端·面试
吃饱了得干活2 小时前
亿级订单表分库分表设计,从0到1全流程
java·数据库·面试
AskHarries2 小时前
文件上传系统
后端
止语Lab3 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活3 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困3 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn3 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端