Java面试题 2024年

1、String/StringBuffer/StringBuilder区别

        String是一个不可变的字符序列,每次对String进行修改时都会创建一个新的String对象。因此,在频繁操作字符串的情况下,会产生大量的垃圾对象,影响性能。

        StringBuffer和StringBuilder是可变的字符序列,可以进行修改操作而不创建新的对象。它们都继承自AbstractStringBuilder,底层都是通过char数组实现的,但它们之间还是存在一些区别。

        StringBuffer是线程安全的,使用synchronized关键字实现线程同步,适合在多线程环境下使用。

        StringBuilder是非线程安全的,不进行线程同步,适合在单线程环境下使用。由于不需要线程同步,所以StringBuilder的性能一般比StringBuffer更好。

        因此,如果在单线程环境下需要频繁操作字符串,应该使用StringBuilder;如果在多线程环境下需要频繁操作字符串,应该使用StringBuffer。

2、反射机制

        Java的反射机制是指在运行时动态地获取和操作类、对象、方法等元数据的能力。通过反射,可以在运行时获取类的构造方法、字段、方法,甚至可以通过反射调用对象的方法、修改对象的属性等。

        反射可以帮助我们实现一些动态的功能,比如根据类名动态创建对象、根据方法名动态调用方法、动态修改对象的属性等。它可以让我们在编写代码时不需要提前知道类的具体细节,而是在运行时动态地获取和操作类的元数据。

        Java的反射机制主要涉及到三个类:Class、Constructor和Method。Class类代表类的元数据,可以通过它获取类的构造方法、字段、方法等信息。Constructor类代表构造方法的元数据,可以通过它创建对象。Method类代表方法的元数据,可以通过它调用方法。

3、==与equals区别

        "=="是Java中的运算符,用于比较两个对象的引用是否相等。它比较的是对象的地址,即比较两个对象是否完全相同。

        而equals()是Object类中的方法,用于比较两个对象的内容是否相等。它的默认实现是调用"=="运算符来比较对象的引用是否相等,但是可以根据需要重写equals()方法,自定义比较规则。

        总结起来,"=="比较的是对象的引用是否相同,而equals()比较的是对象的内容是否相同。在使用时,一般情况下应该使用equals()方法来判断两个对象是否相等。

4、接口和抽象类的区别

        接口和抽象类是面向对象编程中的两个概念,它们都可以用来实现多态和封装。它们的主要区别如下:

        1. 实现方式:抽象类是类的一种,可以包含属性和方法的具体实现,而接口是一组方法的集合,只包含方法的声明。

        2. 继承关系:一个类只能继承一个抽象类,而一个类可以实现多个接口。

        3. 实现的灵活性:由于Java中类的单继承限制,如果一个类已经继承了某个抽象类,那么它就无法再继承其他的类。而接口的实现更加灵活,一个类可以实现多个接口。

        4. 程序设计的目的:抽象类的目的是为了实现代码的重用,提供一个公共的基类。接口的目的是为了定义一组方法的规范,使得多个类可以按照这个规范进行设计和实现。

        总的来说,抽象类强调的是“是什么”(用来表示一种类型),而接口强调的是“能做什么”(用来表示一种行为)。在设计程序时,应根据具体需求选择使用抽象类还是接口。

5、重写(override)和重载(overload)的区别

        重写(override)和重载(overload)是面向对象编程中常用的两个概念,它们的区别如下:

        1. 定义:重写是指在子类中重新实现父类的方法,具有相同的名称和参数列表;而重载是指在同一个类中定义多个具有相同名称但参数列表不同的方法。

        2. 关系:重写是父类和子类之间的关系,子类重写父类的方法,意味着子类重新定义了父类的行为;而重载是同一个类中的方法之间的关系,通过参数列表的不同来区分多个方法。

        3. 调用方式:重写是通过子类对象调用重写的方法时,会优先执行子类的方法;而重载是通过方法名和参数列表来调用不同的方法,根据参数列表的不同来区分调用哪个方法。

        4. 需要满足的条件:重写必须满足方法名称、参数列表和返回类型都相同的条件;而重载必须满足方法名称相同,但参数列表必须不同。

        5. 编译器的处理:重写是动态绑定的,具体执行哪个方法在运行时决定;而重载是静态绑定的,在编译时就根据参数列表的不同确定调用哪个方法。

        总结来说,重写是在子类中重新定义父类的方法,具有相同的方法名和参数列表;而重载是在同一个类中定义多个具有相同名称但参数列表不同的方法。重写是针对父类和子类之间的关系,重载是针对同一个类中不同方法之间的关系。

6、sleep和wait的区别

        sleep和wait都是用于控制线程的方法,但是在使用上有一些区别。

        1. 等待条件:sleep方法会暂停线程的执行,并且让出CPU,但是不会释放锁。而wait方法会暂停线程的执行,同时会释放锁,等待其他线程通过notify或notifyAll方法唤醒。

        2. 使用方式:sleep方法是Thread类的静态方法,可以直接使用。而wait方法是Object类的方法,需要使用synchronized关键字加锁后才能调用。

        3. 异常处理:sleep方法会抛出InterruptedException异常,可以进行异常处理。而wait方法会抛出InterruptedException和IllegalMonitorStateException异常,因此需要进行异常处理。

        4. 唤醒方式:sleep方法只能通过时间来唤醒,即等待时间到了之后线程会自动恢复执行。而wait方法可以通过notify或notifyAll方法来唤醒,或者等待时间到了自动唤醒。

        总的来说,sleep方法是线程的静态控制方法,用于暂停线程的执行一段时间,而wait方法是对象的方法,需要在synchronized块内使用,用于等待条件满足时才能继续执行。

7、String类的常用方法

        1. length():返回字符串的长度。
        2. charAt(int index):返回字符串中指定索引位置的字符。
        3. substring(int beginIndex):返回从指定索引位置开始到字符串末尾的子字符串。
        4. substring(int beginIndex, int endIndex):返回从指定索引位置开始到指定索引位置结束的子字符串。
        5. indexOf(String str):返回字符串中第一次出现指定字符串的索引位置。
        6. lastIndexOf(String str):返回字符串中最后一次出现指定字符串的索引位置。
        7. equals(String anotherString):比较两个字符串是否相等。
        8. startsWith(String prefix):判断字符串是否以指定的前缀开头。

8、Java中有了基础类型,为什么还需要包装类型?

        Java中的基础类型(primitive types)是用来表示简单的数据类型,如整数、浮点数、字符等。它们具有固定的大小和内存布局,并且不具备面向对象的特性。

        包装类型(wrapper types)是为了解决基础类型不具备面向对象特性而提供的一种封装机制。包装类型将基础类型封装为对象,使得基础类型可以具备面向对象的特性,例如可以使用方法、参与继承和多态等。

       为什么需要包装类型?主要有以下几个原因:

        1. 泛型支持:在使用泛型时,只能使用对象类型(如Integer)作为类型参数,不能使用基础类型(如int)。因此,如果要在泛型中使用基础类型,就需要使用对应的包装类型。

        2. 集合类支持:Java中的集合类(如List、Set)只能存储对象类型,不能存储基础类型。因此,如果要在集合类中存储基础类型,就需要使用对应的包装类型。

        3. null值支持:基础类型不支持null值,但包装类型可以将null作为有效的值来表示。在某些场景下,需要使用null来表示特殊的情况,例如表示一个值还没有被赋值。

        4. 包装类型提供了一些额外的方法和常量,方便对数据进行操作和处理。

        需要注意的是,包装类型和基础类型之间可以进行自动装箱和拆箱的转换,编译器会自动进行类型的转换,使得使用起来更加方便。但是,由于包装类型是对象,相比于基础类型会占用更多的内存和时间,因此在性能要求较高的场景下,应尽量使用基础类型。

9、遍历LIST有哪些方式

  • 普通遍历:for(int i=0; i< arrays.size(); i++)
  • 增强for遍历:for(String str : arrays)
  • foreach遍历:list.forEach((str) -> xxxxx)
  • 使用Iterator迭代器遍历
  • java8 stream遍历

10、数组 (Array) 和列表 (ArrayList) 区别

        数组 (Array) 和列表 (ArrayList) 是两种常用的数据结构,它们有以下的区别:

        1. 大小限制:数组的大小是固定的,一旦创建就无法改变大小。而列表的大小是可变的,可以根据需要动态添加或删除元素。

        2. 内存管理:数组在内存中是连续存储的,因此对于大型数组来说,要找到特定索引的元素比较快。列表在内存中使用链表或动态分配的数组来存储元素,因此对于插入和删除操作来说更高效。

        3. 类型限制:数组可以存储任何类型的元素,包括基本类型和对象。列表只能存储对象类型,不能存储基本类型,需要使用对应的包装类。

        4. 性能:由于数组的大小是固定的,所以它比列表在创建时更节省内存。数组的访问和搜索速度通常比列表更快。然而,在插入和删除元素方面,列表通常比数组更高效。

        5. 功能:数组没有提供额外的方法和操作,只能通过索引访问和修改元素。列表提供了许多可用于添加、删除和修改元素的方法,以及查询和遍历列表的方法。

        总的来说,数组适用于需要快速访问元素,并且对大小有限制的场景。列表适用于需要频繁插入和删除元素,并且对大小有变化的场景。在实际应用中,需要根据具体的需求选择使用数组还是列表。

 11、什么是集合

集合就是一个放数据的容器,准确的说是放数据对象引用的容器 集合类存放的都是对象的引用,而不是对象的本身 集合类型主要有3种:set(集)、list(列表)和map(映射)。

12、常用的集合类有哪些?

Map接口和Collection接口是所有集合框架的父接口:

1. Collection接口的子接口包括:Set接口和List接口

2. Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及 Properties等

3. Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等

4. List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等

13、List,Set,Map三者的区别?

Java 容器分为 Collection 和 Map 两大类,Collection集合的子接口有Set、List、Queue三种子接 口。我们比较常用的是Set、List,Map接口不是collection的子接口。

Collection集合主要有List和Set两大接口

List:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多 个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。

Set:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一 个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。

Map是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一;value 不要求有序,允许重 复。Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应 的值对象。

Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、 ConcurrentHashMap

14. 集合和数组的区别?

数组是固定长度的;集合可变长度的。

数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。

数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。

15、怎么确保一个集合不能被修改?

可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变 集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。

代码如下:

List<String> list = new ArrayList<>();
 list. add("x");
 Collection<String> clist = Collections. unmodifiableCollection(list);
 clist. add("y"); // 运行时此行报错
System. out. println(list. size());

16、迭代器 Iterator 是什么?

Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来 获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允许调用者在迭代过程中移除元素。 因为所有Collection皆继承了Iterator迭代器

17、Iterator 怎么使用?有什么特点?

Iterator 使用代码如下:

 List<String> list = new ArrayList<>();
 Iterator<String> it = list. iterator();
 while(it. hasNext()){
   String obj = it. next();
   System. out. println(obj);
 }

Iterator 的特点是只能单向遍历,但是更加安全,因为它可以确保,在当前遍历的集合元素被更改 的时候,就会抛出 ConcurrentModificationException 异常。

18、如何边遍历边移除 Collection 中的元素?

使用 Iterator.remove() 方法,如下:

Iterator<Integer> it = list.iterator();
 while(it.hasNext()){
     *// do something*
     it.remove();
 }

19、说一下 ArrayList 的优缺点?

ArrayList的优点如下:

ArrayList 底层以数组实现,是一种随机访问模式。ArrayList 实现了 RandomAccess 接口, 因此查找的时候非常快。

ArrayList 在顺序添加一个元素的时候非常方便。

ArrayList 的缺点如下:

删除元素的时候,需要做一次元素复制操作。如果要复制的元素很多,那么就会比较耗费性能。 插入元素的时候,也需要做一次元素复制操作,缺点同上。

ArrayList 比较适合顺序添加、随机访问的场景。

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

数组转 List:使用 Arrays. asList(array) 进行转换。

List 转数组:使用 List 自带的 toArray() 方法。

代码示例:

// list to array
 List<String> list = new ArrayList<String>();
 list.add("123");
 list.add("456");
 list.toArray();
 // array to list
 String[] array = new String[]{"123","456"};
 Arrays.asList(array);

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

数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实 现。

随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数 据存储方式,所以需要移动指针从前往后依次查找。

增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。

内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储 了两个引用,一个指向前一个元素,一个指向后一个元素。

线程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;

综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多 时,更推荐使用 LinkedList。

LinkedList 的双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向 直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结 点和后继结点。

22、ArrayList 和 Vector 的区别是什么?

这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合

线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。

性能:ArrayList 在性能方面要优于 Vector。

扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。

Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问 Vector的话代码要在同步操作上耗费大量的时间。

Arraylist不是同步的,所以在不需要保证线程安全时时建议使用Arraylist。

23、多线程场景下如何使用 ArrayList?

ArrayList 不是线程安全的,如果遇到多线程场景,可以通过 Collections 的 synchronizedList 方 法将其转换成线程安全的容器后再使用。例如像下面这样:

List<String> synchronizedList = Collections.synchronizedList(list);
 synchronizedList.add("aaa");
 synchronizedList.add("bbb");
 for (int i = 0; i < synchronizedList.size(); i++) {
     System.out.println(synchronizedList.get(i));
 }

24、List 和 Set 的区别?

List , Set 都是继承自Collection 接口

List 特点:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多 个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。

Set 特点:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一 个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。

另外 List 支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无 序,无法用下标来取得想要的值。

Set和List对比

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

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

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

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

26、HashSet如何检查重复?HashSet是如何保证数据不可重复的?

向HashSet 中add ()元素时,判断元素是否存在的依据,不仅要比较hash值,同时还要结合 equles 方法比较。

HashSet 中的add ()方法会使用HashMap 的put()方法。   

 HashMap 的 key 是唯一的,由源码可以看出 HashSet 添加进去的值就是作为HashMap 的key, 并且在HashMap中如果K/V相同时,会用新的V覆盖掉旧的V,然后返回旧的V。所以不会重复( HashMap 比较key是否相等是先比较hashcode 再比较equals )。

27、什么是链表?

链表是可以将物理地址上不连续的数据连接起来,通过指针来对物理地址进行操作,实现增删改查 等功能。

链表大致分为单链表和双向链表

1. 单链表:每个节点包含两部分,一部分存放数据变量的data,另一部分是指向下一节点的next指针

2. 双向链表:除了包含单链表的部分,还增加的pre前一个节点的指针

链表的优点

插入删除速度快(因为有next指针指向其下一个节点,通过改变指针的指向可以方便的增加删除元素)

内存利用率高,不会浪费内存(可以使用内存中细小的不连续空间(大于node节点的大小),并且在需要空间的时候才创建空间)

大小没有固定,拓展很灵活。

链表的缺点

不能随机查找,必须从第一个开始遍历,查找效率低

28、HashMap的扩容操作是怎么实现的?

1. 在jdk1.8中,resize方法是在hashmap中的键值对大于阀值时或者初始化时,就调用resize方法进 行扩容;

2. 每次扩展的时候,都是扩展2倍;

3. 扩展后Node对象的位置要么在原位置,要么移动到原偏移量两倍的位置。

29、为什么HashMap中String、Integer这样的包装类适合作为K?

String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率

都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况 内部已重写了 equals() 、 hashCode() 等方法,遵守了HashMap内部的规范,不容易出现Hash值计算错误的情况;

30、HashMap 与 HashTable 有什么区别?

1. 线程安全: HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本 都经过 synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap );

2. 效率: 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被 淘汰,不要在代码中使用它;(如果你要保证线程安全的话就使用 ConcurrentHashMap );

3. 对Null key 和Null value的支持: HashMap 中,null 可以作为键,这样的键只有一个,可以有 一个或多个键所对应的值为 null。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛 NullPointerException。

4. 初始容量大小和每次扩充容量大小的不同 :

5. 创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来 的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。

6. 创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其 扩充为2的幂次方大小。也就是说 HashMap 总是使用2的幂作为哈希表的大小。

7. 底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈 值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。

8. 推荐使用:在 Hashtable 的类注释可以看到,Hashtable 是保留类不建议使用,推荐在单线程环 境下使用 HashMap 替代,如果需要多线程使用则用 ConcurrentHashMap 替代。

31、Array 和 ArrayList 有何区别?

Array 可以存储基本数据类型和对象,ArrayList 只能存储对象。

Array 是指定固定大小的,而 ArrayList 大小是自动扩展的。

Array 内置方法没有 ArrayList 多,比如 addAll、removeAll、iteration 等方法只有 ArrayList 有。

32、comparable 和 comparator的区别?

comparable接口实际上是出自java.lang包,它有一个 compareTo(Object obj)方法用来排序

comparator接口实际上是出自 java.util 包,它有一个compare(Object obj1, Object obj2)方法用来排序

一般我们需要对一个集合使用自定义排序时,我们就要重写compareTo方法或compare方法,当我们需要对某一个集合实现两种排序方式,比如一个song对象中的歌名和歌手名分别采用一种排 序方法的话,我们可以重写compareTo方法和使用自制的Comparator方法或者以两个 Comparator来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的 Collections.sort().

33、Collection 和 Collections 有什么区别?

java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操 作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为 各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。

Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进 行排序、搜索以及线程安全等各种操作。

  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值