【面试题】Java 基础篇

Java 基础面试题

1、为什么Java代码可以实现一次编写、到处运行?

Java 代码之所以能够实现一次编写,到处运行,这需要借助于 JVM Java虚拟机。Java 源代码(.java)在运行之前需要先编译为字节码(.class)文件,之后再借助 JVM 将字节码文件翻译为特定平台下的机器码运行。
:.java 源代码编译后生成 .class 字节码文件,字节码不能直接运行,需要通过 JVM 翻译成机器码才能运行。
示例
.java 源文件 — ( javac.exe 编译) —> .class字节码文件 — ( java.exe 运行) —> 结果
1)javac HelloWorld.java 生成一个字节码文件 HelloWorld.class (字节码文件的名称和类名保持一致)
2)java HelloWorld

2、一个Java文件里可以有多个类吗(不含内部类)?

一个 Java 文件里可以有多个类,但是只能有一个类被声明为 public 的,且该 Java 源文件的名称需要与声明为 public 的类的类名保持一致。

3、说一说你对 Java 访问权限的了解?(其实就是权限修饰符问题)

四种访问权限(范围):private < default 缺省 < protected < public
在这里插入图片描述
修饰类:当修饰类时,只能使用 public 和 default 两种权限。
修饰成员变量 / 成员方法:四种均可以使用。

4、Java 的数据类型?

Java 的数据类型包含两种:基本数据类型和引用数据类型。
基本数据类型
8 个基本数据类型,整型(byte / short / int / long)、浮点型(float / double)、字符型(char)、布尔类型(boolean)。
byte:1 字节(8 位),默认值 0
short:2 字节(16 位),默认值 0
int:4 字节(32 位),默认值 0
long:8 字节(64 位),默认值 0L 或 0l
float:4 字节(32 位),默认值 0.0F 或 0.0f
double:8 字节(64 位),默认值 0.0
char:2 字节(16 位),默认值 ‘\u0000’
boolean:默认值 false
引用数据类型
3 类,分别是数组、类、接口,引用类型就是对一个对象的引用。

5、成员变量 VS 局部变量?

在这里插入图片描述
成员变量补充
未被 static 修饰的变量称为实例变量,生命周期与对象相同。
被 static 修饰的变量称为类变量,生命周期与当前类相同。

6、Java 包装类?

Java 作为面向对象编程语言,其设计理念为“一切皆对象”,而 8 种基本数据类型却是一个例外,为了保持一致也为了方便开发,为这 8 种基本数据类型提供了包装类。
byte — Byte
short — Short
int — Integer
long — Long
float — Float
double — Double
boolean — Boolean
char — Character

7、自动装箱与自动拆箱?

自动装箱:基本数据类型 —> 包装类
自动拆箱:包装类 —> 基本数据类型

8、Integer 和 Double 两种类型的数据判相等?

只有是同种类型的数据才能够进行判等操作,如果是不同数据类型,推荐先转换成为同一种数据类型,然后再进行判等操作!
可以先将 Integer 类型的数据转换成 Double 类型,然后再进行判等操作。
不推荐将 Double 转换成 Integer 类型,这样可能会造成精度损失。

9、int 和 Integer 有什么区别,二者在做 == 运算时会得到什么结果?

首先,int 是基本数据类型,而 Integer 是它的包装类,是引用数据类型。
两者在进行 == 运算时,Integer 会先自动拆箱为 int 类型,然后再对具体数值进行比较。

10、谈谈你对面向对象的理解?

(对于这个问题,每个人的理解不同,以下仅代表个人浅显的理解)
在我看来,面向对象和面向过程是两种不同的解决问题的角度,面向过程更注重解决问题的每一个步骤以及顺序;而面向对象更注重解决问题需要有哪些对象以及每个对象都需要有哪些属性和方法。

11、面向对象的三大特征?

三大特征分别为:封装、继承和多态。
封装可以在不影响使用的情况下,将类的实现内部细节隐藏起来,暴露给外界的只是它的访问方法。封装之后使用者就不必关注内部的实现细节,只需要关注如何使用即可,方便使用,方便修改,方便维护。
继承就是我们常说的子父类关系,子类可以继承父类的属性和方法,子类也可以拥有父类没有的属性和方法,当然,子类也可以称为派生类。Java 中继承使用 extends 关键字来实现,需要值得注意的是,父类中使用 private 修饰的属性和方法不会被子类继承,在子类中也不能直接操作父类中定义为 private 的属性和方法。
多态指的是类与类之间的关系,封装和继承最后归结于多态,在调用时有父类的引用指向子类对象是最常见的多态形式。多态需要必备的三个条件:继承、重写,父类引用指向子类对象。

12、重写 VS 重载?

重写:子类继承父类之后,对父类中同名同参数的方法进行覆盖操作

  • 方法名和形参列表必须相同
  • 子类重写的方法的权限修饰符不小于父类被重写的方法
    父类中声明为 private 的方法不能被重写
  • 返回值类型
    父类 void — 子类 void
    父类基本数据类型 — 子类必须是相同的基本数据类型
    父类引用数据类型 — 子类可以是相同的数据类型,也可以是其子类
  • 子类的异常类型不大于父类的异常类型

重载

  • 两同:同一个类、同一个方法名
  • 一不同:不同的参数列表(参数类型不同,参数个数不同,参数间的顺序不同)
    示例
    public void getSum(int i,int j){}
    public int getSum(int i,iny j){}
    以上两个方法并不是方法重载,方法重载与返回值类型无关,只需看两个位置(方法名和参数列表)。
    方法重载与权限修饰符、返回值类型,形参变量名,方法体均没有关系。

13、构造方法能重写吗?

首先需要明确,重写是子类对父类同名同参数的方法进行覆盖操作。
构造方法不能重写,因为构造方法名是与类名保持一致的,每个类都是有专属于自己的构造方法。
但是构造方法可以重载,可以声明多个同名不同参数列表的构造方法。

14、介绍一下 Object 类?

Object 类是整个类层次的根类,每个类都直接或间接的继承了 Object 类。
在这里插入图片描述
像 equals() 、toString() 、getClass() 、hashCode() 方法都是我们常用的方法。
对于 notify() 、wait() 等方法在多线程部分会使用到。

15、== VS equals() ?

基本数据类型判相等只能使用 == ,比较的是两个变量的具体数值是否相等。
引用数据类型判相等可以使用 == ,但是比较的是两个对象的地址值是否相同,即判断这两个对象是否为同一个对象。
equals() 方法默认使用的是 == 来判等,通常我们都会对其进行重写,重写之后的 equals() 方法会按照重写的内容来判等,一般我们都会比较具体的对象内容是否相等。

16、hashCode() 和 equals() ?

hashCode() 用于获取哈希值,而 equals() 方法用于比较两个对象是否相等。
值得注意的是,如果两个对象相等,则它们一定有相同的哈希值;但反过来,如果两个对象的哈希值相等,并不能保证两个对象相等。

17、String 类的常用方法?

String 类提供了非常多方法供我们使用,如:
char charAt(int index) :返回指定索引处的字符
boolean contains(CharSequence s) :是否包含指定字符序列
boolean endsWith(String suffix) :是否以指定字符串结尾
int indexOf(int ch) :返回指定字符第一次出现的索引
int length() :返回字符串长度
String[] split(String regex) :以指定 regex 分割字符串,返回数组

除此之外,还有许多常用的方法,感兴趣的可以去 API 文档中查看。

18、String 可以被继承吗?

这里是引用
可以清楚的看到,String 类是被 final 关键字修饰的,所以不能被继承。

19、final 关键字?

final 修饰类:该类不能被继承
final 修饰方法:该方法不能被重写
final 修饰变量:该变量不能被重复赋值,实际上是常量
final static 修饰全局常量

20、String VS StringBuffer VS StringBuilder ?

String : 不可变的字符序列;底层使用 char[]
StringBuffer : 可变的字符序列;底层使用 char[];线程安全的,效率较低
StringBuilder : 可变的字符序列;底层使用 char[];线程不安全的,效率较高
效率对比
StringBuilder > StringBuffer > String

21、String 字符串创建的两种方式?

创建字符串时,可以使用 String str = new String(“hello”) 的方式,也可以使用 String str = “hello” 的方式。
使用 new 的方式创建时,JVM 会先使用常量池来管理 hello 直接量,再调用构造器创建一个新的 String 对象存放在堆空间中;而使用第二种方式 JVM 会使用常量池来管理这个字符串,无需再多创建一个新的对象,更推荐使用第二种方式。

22、接口 VS 抽象类?

是否能够定义构造器?抽象类中可以定义构造器,定义的构造器并不是为了实例化使用,而是供子类调用;而接口中不能定义构造器。
能够定义的方法类型?抽象类中既能够定义抽象方法,也可以定义具体方法;而接口中只能定义抽象方法。
是否可以包含静态方法?抽象类中可以包含静态方法,而接口在 JDK 7 之前不能有静态方法,JDK 8 之后可以有静态方法了。
变量类型?抽象类中可以定义成员变量,接口中定义的成员变量实际上都是常量。
一个类只能继承一个抽象类,即 Java 的单继承,但可以实现多个接口。
有抽象方法的类一定是抽象类,但是抽象类中可以没有抽象方法,抽象方法是非必要的。

23、static 关键字?

这里是引用

24、异常体系?

顶级父类:Throwable
两个子类:Exception 和 Error
Exception 分为编译时异常和运行时异常,编译时异常发生在运行前,运行时异常发生在运行过程中。
Error 是程序无法处理的错误,一旦出现,程序会被迫终止。

25、try … catch … finally ?

通常使用 try … catch … finally 结构来处理异常,将可能出现异常的代码存放在 try 块中,catch 中定义异常类型,finally 部分无论是否发生异常,该部分内容都会执行。

26、Java 的四种引用方式?

强引用:最常见。程序创建对象,并将这个对象赋给一个引用变量,即 A a = new A()。如果一个对象具有强引用,垃圾回收器不会对其进行随意的回收。
软引用:如果一个对象只有软引用,当内存空间足够时,垃圾回收器不会回收它;当内存空间不足时,就会回收这些对象的内存。
弱引用:只具有弱引用的对象拥有更短暂的生命周期,当垃圾回收器线程扫描它所管辖的内存区域时,如果发现了具有弱引用的对象,不管当前内存是否足够,都会回收弱引用对象的内存。但垃圾回收器是一个优先级很低的线程,不一定能很快的发现那些只具有弱引用的对象。
虚引用:如果一个对象只具有虚引用,那么它就跟没有任何引用一样,任何时候都可能被垃圾回收期回收。虚引用和软引用和弱引用的区别在于:虚引用必须和引用队列联合使用。

27、Java 中的集合类?

Collection 接口派生出的 Set 、List 和 Queue 与 Map 接口将集合分为了四大类。
Set 集合:无序的,不可重复的
List 集合:有序的,可重复的
Queue 队列:先进先出的队列
Map 集合:存储 key - value 键值对,双列集合,其中 key 是一个 Set 集合,满足无序不可重复的特性
每个接口都提供了多个实现类,其中最常用的有 ArrayList、HashSet、TreeSet、HashMap、TreeMap 等。

28、Java 中有哪些线程安全的或线程不安全的容器?

线程不安全的
java.util 包下的集合类大多都是线程不安全的,如我们常用的 ArrayList、HashMap、HashSet 等,都是线程不安全的集合类,但是它们的性能较好。同时 Collections 工具类也为其提供了 synchronizedXxx() 方法将这些集合类包装成线程安全的集合类。
线程安全的
像 Vector 、Hashtable 这些集合是线程安全的,但是是比较古老的 API,性能方面会差一些。
Java 5 之后,java.util.concurrent 包下提供了大量线程安全的集合类,分为了两大特征,一类以 Concurrent 开头的集合类,如 ConcurrentHashMap 等,另一类以 CopyOnWrite 开头的集合类,如 CopyOnWriteArrayList 等。

29、Map 集合的实现类?

常见的实现类有:HashMap、LinkedHashMap、TreeMap、ConcurrentHashMap。
对于不需要排序的场景,优先考虑 HashMap;如果要求线程安全,则考虑 ConcurrentHashMap。
对于需要排序的场景,如果按插入顺序进行排序,则使用 LinkedHashMap;如果按照 key 值大小排序则使用 TreeMap。

30、Map 集合的 put 添加过程?

1.首次扩容:先判断数组是否为空,如果为空进行第一次扩容。
2.计算下标:使用 hashCode() 方法计算出键值对在数组中的下标。
3.添加数据

  • 如果当前位置为空,则直接插入数据。
  • 如果当前位置不为空且存在相同的 key 值,则进行 value 值的覆盖。
  • 如果当前位置不为空但不存在相同的 key 值,则将数据插入到链表中。
  • 当链表长度达到 8 时,将链表转换成红黑树,将数据插入到书中。

4.扩容:如果数组中的元素个数超过了指定的 threshold ,则进行扩容操作。

31、HashMap VS Hashtable?

HashMap:主要实现类;线程不安全的,效率高;可以存储 null 的 key 和 value。
Hashtable:古老的实现类;线程安全的,效率低;不能存储 null 的 key 和 value。

32、HashMap 在 JDK 7 和 JDK 8 中 的区别?

JDK 7
基于数组 + 链表实现,底层为一个 Entry 数组,根据 hashCode() 方法计算得到键值对的哈希值,从而获得存储的下标,插入到数组链表中。
JDK 8
基于数组 + 链表 + 红黑树实现,底层为一个 Node 数组,当链表存储的元素个数达到 8 时,将链表转换成红黑树结构,优化了 JDK 7 链表过长的问题,提高查找效率。

33、HashMap 的扩容机制?

默认容量:16 ,以 2 的次方扩充。
负载因子:默认为 0.75 ,负载因子用于判断数组是否需要扩容,当元素个数为数组容量的 0.75 时,会扩充数组。
在 JDK 8 时,对 HashMap 进行了优化,采用数组 + 链表 + 红黑树的结构,当链表长度达到阙值 8 时,会将链表转换为红黑树;而当链表长度缩小为 6 时,会将红黑树转换为链表以提高性能。

34、HashMap 为什么是线程不安全的?

HashMap 在并发执行 put 操作时,可能会形成循环链表,引起死循环。
在多线程的条件下调整 HashMap 容量大小时,如果两个线程同时尝试调整,链表中元素的位置会进行重新计算进行调整,在调整的过程中,线程间会存在竞争关系,也就可能导致死循环。

35、ConcurrentHashMap 集合类?

JDK 7
基于 Segment + HashEntry 实现,采用分段锁来保证安全性。
一个 ConcurrentHashMap 里包含一个 Segment 数组,一个 Segment 包含一个 HashEntry 数组,每一个 HashEntry 是一个链表结构,在对 HashEntry 数组进行操作时,需要先获得对应的 Segment 锁。
Segment 扮演锁的角色,HashEntry 存储键值对数据。
在这里插入图片描述

JDK 8
基于Node 数组 + 链表 + 红黑树实现,并发控制使用 Synchronized 和 CAS 处理,synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要 hashCode 不冲突,就不会产生并发,效率又提升很多。

36、ConcurrentHashMap VS Hashtable?

首先,两者都是线程安全的集合类,两者的区别主要体现在实现线程安全的方式不同。
ConcurrentHashMap
JDK 7 :使用分段锁,将整个数组分割成多个 Segment,每个 Segment 会分配一把锁,每把锁只锁容器中的一部分数据,当多线程访问容器里不同数据段里的数据时,就不会存在锁竞争问题,提供效率。
JDK 8 :摒弃了 Segment ,而是使用 synchronized 和 CAS 来操作。
Hashtable
只有一把锁,使用 synchronized 保证线程安全,效率低。

37、LinkedHashMap 集合类?

当需要考虑的元素的插入顺序时,可以使用 LinkedHashMap,它使用双向链表来维护键值对 key - value 的顺序。
LinkedHashMap 继承于 HashMap,所以它的很多结构、方法和 HashMap 都是相似甚至一致的,主要的区别就是它需要维护一个双向链表来保证遍历顺序和插入顺序的一致问题。

38、TreeMap 集合类?

当需要对存储的元素进行排序时,可以考虑使用 TreeMap,TreeMap 基于红黑树实现,根据键值 key 进行自然排序,也可以使用 Comparator 自定义排序方式,但是不可以使用 value 值进行排序。

39、ArrayList VS LinkedList ?

ArrayList
基于数组实现。
默认第一次创建大小为 10 的数值,之后扩容每次会增加 50% 的容量。
更适合进行随机访问操作,时间复杂度 O(1)。
对于插入和删除操作,可能会涉及到元素的大量移动。
LinkedList
基于双向链表实现。
更适合进行插入和删除操作,因为只涉及到了指针的改变。
但是对于查找操作,需要从头开始一个一个的往后遍历,时间复杂度 O(n)。
除了存储元素的具体数值外,还需要存储指针,更占用内存。

首先,这两个集合都实现了 List 接口,这是两者的相同点。更重要的还是两者的不同点。
底层数据结构不同:ArrayList 是基于数组实现的,而 LinkedList 是基于链表实现的。
使用场景不同:ArrayList 更适用于多查询的场景,因为 ArrayList 可直接计算索引地址访问到数据,时间复杂度为 O(1),而 LinkedList 需要从第一个结点开始往后遍历,边遍历边计数,时间复杂度为O(n);LinkedList 更适合于多插入和删除,少查询的场景,这是因为LinkedList 在插入和删除元素时只需要修改结点间的指针指向关系,不存在元素间的大量移动,而 ArrayList 如果想要在中间插入一个或删除一个元素的话,存在元素间的移动问题,且在插入操作之前还需要判断空间是否足够。
内存占用不同:相较于 ArrayList,LinkedList 会占用更多的内存,因为 LinkedList 除了存储数据元素本身外,还需要额外存储指向前一个元素和后一个元素的指针。

40、CopyOnWriteArrayList 集合类?

CopyOnWriteArrayList 是 Java 1.5 在 java.util.concurrent 包下新增的类,采用复制底层数组的方式来实现写操作。
当线程对此类集合执行读取操作时,线程将会直接读取集合本身,无须加锁与阻塞。
当线程对此类集合执行写入操作时,集合会在底层复制一份新的数组,接下来对新的数组执行写入操作。
由于对集合的写入操作都是对数组的副本执行操作,因此它是线程安全的。
在所有线程安全的 List 集合中,它是性能最优的方案。
优点
读操作性能很高,因为无需任何同步措施,比较适用于读多写少的并发场景。
CopyOnWriteArrayList 采用"读写分离"的思想,遍历和修改操作分别作用在不同的 List 容器,所以在使用迭代器进行遍历时候,也就不会抛出 ConcurrentModificationException 异常了。
缺点
一是内存占用问题,每次执行写操作都要将原容器拷贝一份,数据量大时,对内存压力较大,可能会引起频繁GC。
二是无法保证实时性,CopyOnWriteArrayList 的实现策略导致写和读分别作用在新老不同容器上,在写操作执行过程中,读不会阻塞但读取到的却是老容器的数据。

41、TreeSet VS HashSet ?

相同点
均是 Set 派生出来的实现类,拥有和 Set 几乎相同的特性,都是线程不安全的,元素是不能重复的。
不同点
HashSet 中的元素值可以为 null ,而 TreeSet 不能。
HashSet 不能保证元素的排列顺序,而 TreeSet 支持自然排序、定制排序。
HashSet 底层采用哈希表实现,而 TreeSet 底层采用红黑树实现。

42、HashSet 集合类?

HashSet 是基于 HashMap 实现的。
默认构造函数是创建一个初始容量为 16 ,负载因子为 0.75 的 HashMap 。
使用 HashMap 对象来存储集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象。

43、Java 中的 IO 流?

I — Input ,O — Output
IO 用于实现对数据的输入和输出操作。

  • 按照数据流向
    - 输入流:只能读取数据,不能写入数据
    - 输出流:只能写入数据,不能读写数据
  • 按照数据类型
    - 字节流:操作的数据单元是 8 位的字节
    - 字符流:操作的数据单元是 16 位的字符
  • 按照处理功能
    - 节点流:可以直接向一个特定的 IO 设备读 / 写数据,也称为低级流
    - 处理流:对节点流的连接或封装,用于简化数据读 / 写功能,提高效率,也称为高级流。
    在这里插入图片描述

44、Java 的序列化与反序列化?

序列化机制可以将对象转换成字节序列,这些字节序列可以保存到磁盘上,也能够在网络中传输,同时也可以将这些字节序列再次恢复成原来的对象。
对象的序列化,是指将一个 Java 对象写入 IO 流中,对象的反序列化,则是指将 IO 流恢复为原来的 Java 对象。
若想实现序列化,则需要实现 Serializable 接口。

45、Serializable 接口为什么需要定义 serialVersionUID 变量?

serialVersionUID 代表序列化的版本,通过定义类的序列化版本,在反序列化时,只要对象中所存的版本和当前类的版本一致,就允许做恢复数据的操作,否则将会抛出序列化版本不一致的错误。

46、创建线程的方式?

三种方式
继承 Thread 类
步骤 1:创建一个类继承 Thread 类
步骤 2:重写 run() 方法
步骤 3:创建子类对象,相当于创建了线程对象
步骤 4:通过子类对象调用start()方法

// 1.创建一个类继承Thread类
class MyThread extends Thread{
    // 2.重写run()方法
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0){
                System.out.println(i);
            }
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        // 3.创建子类对象
        MyThread t1 = new MyThread();
        // 4.通过子类对象调用start()方法
        t1.start();
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0){
                System.out.println(i + "***************");
            }
        }
    }
}

实现 Runnable 接口
步骤 1:创建一个类实现 Runnable 接口
步骤 2:实现Runnable接口中抽象的 run() 方法
步骤 3:创建实现类的对象
步骤 4:创建 Thread 对象
步骤 5:调用 start() 方法

// 1.创建一个类实现Runnable接口
class MyThread2 implements Runnable{
    // 2. 实现Runnable接口中抽象的run()方法
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}
public class ThreadTest3 {
    public static void main(String[] args) {
        // 3.创建实现类的对象
        MyThread2 t1 = new MyThread2();
        // 4.创建Thread对象
        Thread thread = new Thread(t1);
        thread.setName("线程1");
        // 5.调用start()方法
        thread.start();
        // 创建多个
        Thread thread2 = new Thread(t1);
        thread2.setName("线程2");
        thread2.start();
    }
}

实现 Callable 接口
步骤 1:创建 Callable 接口的实现类
步骤 2:重写 call 方法
步骤 3:创建实现类的对象
步骤 4:创建 FutureTask 对象,将 myCall 传入作为参数
步骤 5:创建 Thread 对象,将 futureTask 传入作为参数,并调用 start() 方法
步骤 6:get 方法可以拿到 call 的返回值

// 1.创建Callable接口的实现类
class MyCall implements Callable {
    // 2.重写Call方法
    @Override
    public Object call() throws Exception {
        int num = 0;
        for (int i = 1; i <= 100; i++) {
            if (i % 2 == 0){
                System.out.println(i);
                num += i;
            }
        }
        return num;
    }
}
public class ThreadTest4 {
    public static void main(String[] args) {
        // 3.创建实现类的对象
        MyCall myCall = new MyCall();
        // 4.创建FutureTask对象,将myCall传入作为参数
        FutureTask futureTask = new FutureTask(myCall);
        // 5.创建Thread对象,将futureTask传入作为参数,并调用start()方法
        new Thread(futureTask).start();
        try {
            // 6.get方法可以拿到call的返回值
            Object sum = futureTask.get();
            System.out.println(sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

47、run() VS start() ?

run() 被称为线程执行体,它的方法体代表了线程需要完成的任务。
start() 方法用来启动线程。调用 start() 方法启用线程时,系统会把 run() 方法当作线程执行体来处理。如果直接调用 run() 方法,则 run() 会立即执行,且在返回之前其他线程无法并发执行。

48、线程的生命周期?

这里是引用

49、wait()、notify()、notifyAll() ?

使用:必须在同步代码块或同步方法中声明。
wait():使当前线程进入阻塞状态,同时会释放同步监视器。
notify():唤醒一个被 wait 的进程。
notifyAll():唤醒所有被 wait 的进程。

50、wait() VS sleep() ?

相同点
都会使进程进入阻塞状态。
不同点
wait 是 Object 类中的方法,而 sleep 是 Thread 类中的方法。
sleep 使当前线程进入阻塞状态,但是不会释放同步监视器。wait 使当前线程进入阻塞状态,同时会释放同步监视器。
sleep 在任何地方都可以调用。wait 只能在同步代码块或同步方法中使用。

51、synchronized VS Lock ?

synchronized 是 Java 关键字,在 JVM 层面实现加锁和解锁;Lock 是一个接口,在代码层面实现加锁和解锁。
synchronized 可以用在代码块上、方法上;Lock 只能写在代码里。
synchronized 在代码执行完或出现异常时自动释放锁;Lock 不会自动释放锁,需要在 finally 中显示释放锁。
synchronized 会导致线程拿不到锁一直等待;Lock 可以设置获取锁失败的超时时间。
synchronized 无法得知是否获取锁成功;Lock 则可以通过 tryLock 得知加锁是否成功。
synchronized 锁可重入、不可中断、非公平;Lock 锁可重入、可中断、可公平 / 不公平,并可以细分读写锁以提高效率。

52、乐观锁 VS 悲观锁?

乐观锁
每次拿数据时都认为别人不会修改,所以不上锁,但在更新的时候会判断当前是否有人更新数据。
悲观锁
总是假设最坏的情况,每次拿数据时都认为别人会修改,所以每次拿数据时都会上锁,如果此时别人再想拿这个数据就会阻塞直到拿到锁,Java 中的悲观锁通过 synchronized 关键字或 Lock 接口来实现的。

53、线程池?

线程池在系统启动时即创建大量空闲的线程,程序将一个 Runnable 对象或 Callable 对象传给线程池,线程池就会启动一个空闲的线程来执行它们的 run() 或 call() 方法,当 run() 或 call() 方法执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个 Runnable 对象或 Callable 对象的 run() 或call() 方法。

class NumberPool implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0){
                System.out.println(i);
            }
        }
    }
}
public class ThreadTest5 {
    public static void main(String[] args) {
        // 1.提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        // 设置属性
//        service1.setCorePoolSize();
        // 2.执行指定的线程操作,需要提供实现Runnable的对象
        service.execute(new NumberPool());
//        service.submit(new NumberPool());
        // 3.关闭连接池
        service.shutdown();
    }
}

工作流程
1.判断核心线程池是否已满,没满则创建一个新的工作线程来执行任务。若满了则执行步骤 2。
2.判断任务队列是否已满,没满则将新提交的任务添加在工作队列。若满了则执行步骤 3。
3.判断整个线程池是否已满,没满则创建一个新的工作线程来执行任务,已满则执行饱和(拒绝)策略。
核心参数
corePoolSize:核心线程数。在没有设置 allowCoreThreadTimeOut 为 true 的情况下,核心线程会在线程池中一直存活,即使处于闲置状态。
maximumPoolSize:线程池能容纳的最大线程数。当活动线程(核心线程 + 非核心线程)达到这个数值后,后续任务将会根据 RejectedExecutionHandler 来进行拒绝策略处理。
keepAliveTime:非核心线程闲置时的超时时长。超过该时长,非核心线程就会被回收。若线程池通设置核心线程也允许 timeOut,即 allowCoreThreadTimeOut 为 true,则该时长同样会作用于核心线程,在超时时,核心线程也会被回收。
workQueue:任务队列,通过线程池的 execute() 方法提交的 Runnable 对象会存储在该队列中。
ThreadFactory:线程工厂,功能很简单,就是为线程池提供创建新线程的功能。

54、Java 反射?

Java 的反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为
java语言的反射机制。
优点
运行期类型的判断,动态加载类,提高代码灵活度。
缺点
性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的 Java 代码要慢很多。
Java 获取反射的三种方法
1.通过 new 对象实现反射机制
2.通过路径实现反射机制
3.通过类名实现反射机制

//方式一
Student stu = new Student(); 
Class c1 = stu.getClass(); 
System.out.println(c1.getName()); 
//方式二
Class c2 = Class.forName("fanshe.Student"); 
System.out.println(c2.getName()); 
//方式三
Class c3= Student.class;
System.out.println(c3.getName());

55、JDK、JRE、JVM ?

JDK Java开发工具
JRE Java运行环境
JVM Java虚拟机
JDK = JRE + Java开发工具
JRE = JVM + Java核心类库

56、深拷贝 VS 浅拷贝?

浅拷贝只会拷贝基本数据类型的值,和引用数据类型的地址值,并不会复制一份对象,通过浅拷贝出来的对象,内部的属性指向的是同一个对象。
深拷贝既会拷贝基本数据类型的值,也会针对引用数据类型对所指向对象的复制,深拷贝出来的对象,内部的属性指向的不是同一个对象。

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
回答: Java基础面试题可以包括很多方面的知识,以下是一些常见的问题和答案: 1. 什么是JNI? JNI是Java Native Interface的缩写,它提供了一组API,用于实现Java和其他语言(主要是C和C++)之间的通信。JNI允许Java代码与本地已编译的代码进行交互,尽管这可能会降低平台的可移植性。\[2\] 2. JNI的步骤是什么? JNI的步骤包括以下几个部分: - 在Java类中编写带有native声明的方法。 - 使用javac命令编译Java类。 - 使用javah命令生成头文件。 - 使用C/C++实现本地方法。 - 生成动态连接库。 - 执行Java代码。\[1\] 3. 请解释一下super.getClass()方法的作用。 super.getClass()方法是用于获取当前对象的父类的Class对象。在给定的示例中,Test类继承自Date类,当调用super.getClass().getName()时,会返回Test类的名称。因此,输出的结果是"Test"。\[3\] 希望以上回答能够帮助你理解Java基础面试题。如果你有其他问题,请随时提问。 #### 引用[.reference_title] - *1* *2* [Java基础常见面试题及详细答案(总结40个)](https://blog.csdn.net/ayouki123456/article/details/124983188)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Java基础面试题50题](https://blog.csdn.net/weixin_38337769/article/details/100560220)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

准Java全栈开发工程师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值