Java基础-牛客面经八股
1. 介绍一下Java基本数据类型和引用类型。
1.提供8种基本数据类型:byte(8), short(16), int(32), long(64), float(32), double(64), char(16), boolean,这些基本数据类型有对应的封装类;这基本数据类型在声明之后就会立刻在栈上被分配内存空间。
2.其他类型都是引用类型:类,接口,数组,String等,这些变量在声明时不会被分配内存空间,只是存储了一个内存地址,真实值在堆空间中。
2. 请你说一下抽象类和接口的区别
相同点:
- 1、两者都不能实例化;
- 2、可以拥有抽象方法。
区别:
- 1、抽象类定义的关键字是abstract class,接口定义的关键字是interface;
- 2、属性上,抽象类可以有静态变量、常量和成员变量,接口只能有常量;
- 3、抽象类可以有普通方法,而接口jdk1.8之前只能有抽像方法(1.8之后,增加了静态方法和默认方法);
- 4、抽象方法可以有构造方法,接口不可以有构造方法。
- 5、一个类只能单继承一个父类,而一个接口可以继承多个父接口,同时,一个类可以实现多个接口却没有实现多个父类这一说法;
- 6、抽象方法在业务编程上更像一个模板,有自己的功能,同时也可以有优化补充的多种形式,而接口更像是一种规范和要求,实现就要按照要求来进行。
3. 请你说一下final关键字。
- final可以修饰类,方法,变量。
- final修饰类,该类不可被继承。
- final修饰方法,该方法不能被重写。
- final修饰变量,如果是基本变量则值不能再改变,如果是引用变量则引用地址不能改变,但值可以改变。
4. 介绍下Java中的static关键字。静态方法能不能调用非静态成员?
- 1.静态的含义在于是类内部的一个成员变成属于类自己的,而不属于实例化对象。
- 2.static可修饰类,方法,代码块,变量。不可以修饰构造器方法。
- 3.修饰:
- static修饰变量:属于静态变量也叫类变量,直属于类对象而不是实例,可以通过类名访问,它一般会在类加载过程中被初始化。生命周期贯穿整个程序。存储在方法区中。静态成员变量随着静态类的加载而创建。
- static修饰方法:static方法中不可以使用this,super关键字,因为this是随着对象创建而存在。不能调用非静态方法,只能访问静态变量和静态方法
- static修饰类: 被static修饰的类为静态类,可以在不创建实例的情况下访问它的静态方法或静态成员变量。它是他也只能访问到外部的的静态成员。
- static修饰的代码块会在类加载的时候执行,且只执行一次。用于对该类中的静态变量进行操作。
5. String、StringBuffer、Stringbuilder有什么区别?
关于String、StringBuffer、Stringbuilder区别从四个方面来理解
- 1.值可变性:
- String是不可变的,如果尝试修改String的值,都会产生一个新的字符串对象。
- StringBuffer和StringBuilder是可变的,修改不会产生新的对象。
- 2.线程安全:
- String是线安全的,因为它不可变。
- StringBuffer是线程安全的,因为它里面的每个操作方法都加了synchronized关键字。
- StringBuilder是线程不安全的,单线程下使用StringBuilder效率最高。
- 3.性能:
- String效率最低,其次是StringBuffer,StringBuilder性能最好
- 因为String不可变,所以每次做字符串拼接的时候一直创建新的对象和分配内存空间
- StringBuffer加了同步锁,StringBuilder是无阻塞的。
- 4.数据存储:
- String存储在字符串常量池中。(new string的话,string对象也在堆内存中)
- StringBuffer和StringBuilder存储在堆内存中。
6. 说一下Java中==与equals()的区别。
- ==比较对象内存地址是否相同,适用于基本数据类型和对象引用比较。
- equals()用于对象内容比较,默认实现等同于==,但常被重写(如String类)。
- String等包装类重写equals()后比较实际值而非地址。
- 基本类型只能用==比较数值,对象类型需根据需求选择比较方式。
- 正确使用时应确保equals()方法被目标类正确实现。
7. 请你说说hashCode()和equals()的区别,为什么重写equals()就要重写hashCode()
- 1、hashCode():获取哈希码,equals():比较两个对象是否相等。
- 2、二者两个约定:如果两个对象相等,它们必须有相同的哈希码;若两个对象的哈希码相同,他们却不一定相等。也就是说,equals()比较两个对象相等时hashCode()一定相等,hashCode()相等的两个对象equqls()不一定相等。
- 3、加分回答:由于hashCode()与equals()具有联动关系,equals()重写时,hashCode()也需要进行重写,使得这两个方法始终满足相关的约定。
8. 介绍一下包装类的自动拆箱与自动装箱。
- 1、自动装箱、自动拆箱是JDK1.5提供的功能。
- 2、自动装箱:把一个基本类型的数据直接赋值给对应的包装类型;
- 3、自动拆箱是指把一个包装类型的对象直接赋值给对应的基本类型;
- 4、通过自动装箱、自动拆箱功能,简化基本类型变量和包装类对象之间的转换过程
9. 请你说说Java中重载和重写的区别。
- 1.重载发生在同一类中,而重写发生在子类中。
- 2.重载要求方法名相同,参数列表,返回值,访问修饰符都可以不同。重写要求方法名相同,参数列表相同,返回值类型要小于等于父类的方法,抛出的异常要小于等于父类方法抛出的异常,访问修饰符权限大于等于父类方法的访问修饰符权限。
- 3.final,private修饰的方法不能重写,构造方法也不能重写。
10. 介绍一下Java的泛型。
- 1.泛型:Java在jdk1.5引入了泛型,在没有泛型之前,每次从集合中读取的对象都必须进行类型转换,如果在插入对象时,类型出错,那么在运行时转换处理的阶段就出错;在提出泛型之后就可以明确的指定集合接受哪些对象类型,编译器就能知晓并且自动为插入的代码进行泛化,在编译阶段告知是否插入类型错误的对象,程序会变得更加安全清晰。
- 2.泛型擦除:Java泛型是伪泛型,因为Java代码在编译阶段,所有的泛型信息会被擦除,Java的泛型基本上都是在编辑器这个层次上实现的,在生成的字节码文件中是不包含泛型信息的,使用泛型的时候加上的类型,在编译阶段会被擦除掉,这个过程称为泛型擦除。
11. 请说说你对反射的了解。
- 1.反射就是在程序运行期间动态的获取对象的属性和方法的功能叫做反射。它能够在程序运行期间,对于任意一个类,都能知道它所有的方法和属性,对于任意一个对象,都能知道他的属性和方法。
- 2.获取Class对象的三种方式:getClass();xx.class;Class.forName(“xxx”);
- 3.反射的优缺点:
- 优点:运行期间能够动态的获取类,提高代码的灵活性。
- 缺点:性能比直接的Java代码要慢很多。
- 4.应用场景:spring的xml配置模式,以及动态代理模式都用到了反射。
12. 你知道哪些线程安全的集合?举例你是怎么使用的?
- java.uti包中的集合类大部分都是非线程安全的,例如:ArrayList/LinkedList/HashMap等等,
- 但也有少部分是线程安全的,像是Vector和Hashtable,它们属于很古老的API了,是基于Synchronized实现的,性能很差,在实际的开发中不常用。
- 一般可以使用collections工具类中的syncheronizedXxx()方法将非线程安全的集合包装成线程安全的类。
- 在java5之后可以使用concurrent包提供的大量的支持并发访问的集合类,例如ConcurrentHashMap/CopyOnWriteArrayList等
13. 请你说说HashMap底层原理和扩容机制。
在JDK8中,HashMap底层是采用“数组+链表+红黑树”来实现的。
HashMap是基于哈希算法来确定元素的位置(槽)的,当我们向集合中存入数据时,它会计算传入的Key的哈希值,并利用哈希值取余来确定槽的位置。
如果元素发生碰撞,也就是这个槽已经存在其他的元素了,则HashMap会通过链表将这些元素组织起来。如果碰撞进一步加剧,某个链表的长度达到了8同时数组长度大于64,则HashMap会创建红黑树来代替这个链表,从而提高对这个槽中数据的查找的速度。
HashMap中,数组的默认初始容量为16,这个容量会以2的指数进行扩容。具体来说,当数组中的元素达到一定比例的时候HashMap就会扩容,这个比例叫做负载因子,默认为0.75。
Put()方法的执行过程中,主要包含四个步骤:
- 判断数组,若发现数组为空,则进行首次扩容。
- 判断头节点,若发现头节点为空,则新建链表节点,存入数组。
- 判断头节点,若发现头节点非空,则将元素插入槽内。
- 插入元素后,判断元素的个数,若发现超过阈值则再次扩容。
- 第3步又可以细分为如下三个小步骤:
- 若元素的key与头节点一致,则直接覆盖头节点。
- 若元素为树型节点,则将元素追加到树中。
- 若元素为链表节点,则将元素追加到链表中。追加后,需要判断链表长度以决定是否转为红黑树。若链表长度达到8、数组容量未达到64,则扩容。若链表长度达到8、数组容量达到64,则转为红黑树。
扩容机制 向HashMap中添加数据时,有三个条件会触发它的扩容行为:
- 如果数组为空,则进行首次扩容。
- 将元素接入链表后,如果链表长度达到8,并且数组长度小于64,则扩容。
- 添加后,如果数组中元素超过阈值,即比例超出限制(默认为0.75),则扩容。
- 并且,每次扩容时都是将容量翻倍,即创建一个2倍大的新数组,然后再将旧数组中的数组迁移到新数组里。由于HashMap中数组的容量为2^N
14. ConcurrentHashMap 线程安全的具体实现方式。
- 一、ConcurrentHashMap的底层数据结构与HashMap一样,也是采用“数组+链表+红黑树
- 二、采用锁定头节点的方式降低了锁粒度,以较低的性能代价实现了线程安全。
- 三、实现机制:
- 初始化数组或头节点时,ConcurrentHashMap并没有加锁,而是CAS的方式进行原子替换
- 插入数据时会进行加锁处理,但锁定的不是整个数组,而是槽中的头节点。所以,ConcurrentHashMap中锁的粒度是槽,而不是整个数组,并发的性能很好。
- 扩容时会进行加锁处理,锁定的仍然是头节点。并且,支持多个线程同时对数组扩容,提高并发能力。
- 在扩容的过程中,依然可以支持查找操作。
15. 请你说说ArrayList和LinkedList的区别。
- ArrayList底层是数组实现的,数组是一组连续的内存单元,读取快(使用索引),插入删除慢(需要重新计算大小或是更新索引)
- LinkedList底层基于双向链表,读取慢,插入删除快;链表的每一个节点保存了数据值,和指向前一个节点的指针和指向后一个节点的指针。占内存
16. 介绍一下基本类型和包装类型的区别?
- 基本数据类型:直接存储数据值,占用固定内存,默认值不为null;适合高频计算场景,不需要对象开销
- 包装类型:是对象,默认值为null,存储在堆内存中,支持泛型和方法调用;适合需要适用对象的场景,如泛型,数据库映射,反射获取属性等
17. Java的Object类有哪些方法?
在 Java 中,Object 类是所有类的根父类,所有对象(包括数组)都隐式继承自 Object 类。它定义了 11 个方法(不同 JDK 版本可能有差异)。以下是这些方法的作用及常见使用场景:
- toString():返回对象的字符串表示,便于调试和日志输出;通常建议重写以展示关键信息。
- equals(Object obj):判断两个对象是否“逻辑相等”;集合类如 HashSet、ArrayList 中常依赖此方法。
- hashCode():返回对象的哈希值,配合 equals() 使用,在哈希类集合中(如 HashMap)尤为重要。
- getClass():返回对象的运行时类型,常用于反射。
- clone():创建当前对象的浅拷贝,需实现 Cloneable 接口。
- finalize()(已废弃):垃圾回收前的回调方法,用于释放资源,不推荐使用。
- wait() / wait(long) / wait(long, int):使线程等待并释放锁,用于线程通信。
- notify() / notifyAll():唤醒一个或多个等待该对象锁的线程,用于线程通信。
这些方法为 Java 提供了最基本的对象行为支持。开发中,我们通常会根据业务需求重写 equals()、hashCode() 和 toString(),以实现正确的对象比较、哈希存储和输出描述。
18.说一下Java中的深拷贝、浅拷贝、引用拷贝。
Java 中的拷贝分为三种:引用拷贝、浅拷贝和深拷贝。
- 引用拷贝(Reference Copy)
- 定义:只是复制对象的引用地址,新旧变量指向同一个对象。
- 特点:修改任意一个对象,另一个也会受影响。
- 使用场景:无需复制对象内容,只需共享引用。
- 浅拷贝(Shallow Copy)
- 定义:创建一个新对象,复制基本类型字段的值,复制引用类型字段的引用地址。
- 特点:对象本身是新的,但其内部引用对象仍指向原始对象,存在共享。
- 实现方式:实现 Cloneable 接口,重写 clone() 方法,调用 super.clone() 实现。
- 深拷贝(Deep Copy)
- 定义:不仅复制对象本身,还递归复制其所有引用类型字段所指向的对象,每一层都是新对象。
- 特点:新旧对象完全独立,互不影响。
- 实现方式:手动实现递归 clone(),或通过对象序列化反序列化完成。
19. JDK动态代理是什么?和cglib区别?
- JDK动态代理是Java基于接口实现的代理技术,通过反射机制在运行时生成代理类。
- 与CGLib的区别:
- JDK需实现接口,CGLib通过继承直接代理普通类;
- JDK无需第三方库,CGLib需引入ASM包;
- CGLib无法代理final类/方法,JDK无法代理无接口类;
- CGLib生成代理类性能通常更高。
20. 什么是序列化?什么是反序列化?
- 序列化是将对象转换为可存储或传输的格式(如字节流、JSON),便于保存或网络传输。
- 反序列化是逆向过程,将序列化后的数据恢复为原始对象结构,实现数据的重构和使用。
- 两者常用于数据持久化、缓存或跨平台通信场景。
21. 常见序列化协议有哪些?
常见序列化协议包括JSON(轻量级文本格式)、XML(可扩展标记语言)、Protocol Buffers(高效二进制协议)、MessagePack(紧凑二进制格式)、Apache Avro(支持动态模式)。其他如Thrift、BSON等也广泛应用。
22. 什么是 BlockingQueue?都有哪些实现?
BlockingQueue是Java中支持线程安全阻塞操作的队列接口,用于协调生产者和消费者线程。
常见实现包括:ArrayBlockingQueue(固定容量数组)、LinkedBlockingQueue(可选容量链表)、SynchronousQueue(直接传递队列)、PriorityBlockingQueue(优先级排序)和DelayQueue(延迟元素处理)。
23. 讲一下PriorityQueue。
PriorityQueue是Java中基于优先级堆实现的无界队列,元素按自然顺序或自定义Comparator排序,队首元素总是优先级最高(最小或最大)。
默认使用小顶堆结构,插入和删除操作时间复杂度为O(log n)。非线程安全,常用方法包括offer()、poll()、peek()。







