Java基础八股文

https://zhuanlan.zhihu.com/p/516271873

1.java八大基本数据类型

  • 6 种数字类型:
    • 4 种整数型:byte、short、int、long
    • 2 种浮点型:float、double
  • 1 种字符类型:char
  • 1 种布尔型:boolean。

2.基本类型和包装类型的区别

  • 成员变量包装类型不赋值就是 null ,而基本类型有默认值且不是 null。
  • 包装类型可用于泛型,而基本类型不可以。
  • 基本数据类型的局部变量存放在 Java 虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被 static 修饰 )存放在 Java 虚拟机的堆中。包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中。
  • 相比于对象类型, 基本数据类型占用的空间非常小。

3.hashmap和treemap有什么区别

TreeMap 和HashMap 都继承自AbstractMap ,但是需要注意的是TreeMap它还实现了NavigableMap接口和SortedMap 接口。
实现 NavigableMap 接口让 TreeMap 有了对集合内元素的搜索的能力。
实现SortedMap接口让 TreeMap 有了对集合中的元素根据键排序的能力。默认是按 key 的升序排序,不过我们也可以指定排序的比较器。
综上,相比于HashMap来说 TreeMap 主要多了对集合中的元素根据键排序的能力以及对集合内元素的搜索的能力。

4.treemap底层数据结构是什么

treemap 底层的实现是基于红黑树的,它通过红黑树的自平衡性来保证 treemap 中的元素有序,并且支持高效的查找、插入和删除操作。
红黑树是一种自平衡二叉查找树,它具有以下性质:

  1. 每个节点要么是黑色,要么是红色。
  2. 根节点是黑色。
  3. 每个叶子节点都是黑色的空节点(NIL)。
  4. 如果一个节点是红色的,则它的子节点必须是黑色的。
  5. 对于每个节点,从该节点到其子孙节点的所有路径上包含相同数目的黑色节点。

红黑树通过维护这些性质来保证查找、插入和删除操作的时间复杂度都是 O(log n)。

5.ArrayList遍历的几种方式

  • 方法一:for (int i = 0; i < j; i++)
  • 方法二:Iterator
for (Iterator iterator = list.iterator(); iterator.hasNext(); ) {
            iterator.next();
        }
  • 方法三:增强for循环
  • 方法四:forEach

image.png

  • 方法五:stream().forEach

image.png

6.把list转换为树状结构

import java.util.ArrayList;
import java.util.List;

public class TreeNode {
    private String id;
    private String parentId;
    private String name;
    private List<TreeNode> children = new ArrayList<>();

    public TreeNode(String id, String parentId, String name) {
        this.id = id;
        this.parentId = parentId;
        this.name = name;
    }

    public void addChild(TreeNode child) {
        children.add(child);
    }

    public String getId() {
        return id;
    }

    public String getParentId() {
        return parentId;
    }

    public String getName() {
        return name;
    }

    public List<TreeNode> getChildren() {
        return children;
    }

    // 将List转换为树状结构的方法
    public static List<TreeNode> buildTree(List<TreeNode> nodeList, String parentId) {
        List<TreeNode> treeList = new ArrayList<>();
        for (TreeNode node : nodeList) {
            if (node.getParentId().equals(parentId)) {
                // 递归构建子树
                node.setChildren(buildTree(nodeList, node.getId()));
                treeList.add(node);
            }
        }
        return treeList;
    }
}

7.接口和抽象类区别?

  1. 实现方式

抽象类是一种特殊的类,它不能被实例化,只能被继承。它可以包含抽象方法和非抽象方法。抽象方法没有具体实现,而是由子类实现。非抽象方法有具体实现,并且可以在子类中直接使用。
接口是一种完全抽象的类,它不能被实例化,只能被实现。它只包含抽象方法和常量,抽象方法没有具体实现,而是由实现接口的类实现。

  1. 继承方式

一个类只能继承一个抽象类,而一个类可以实现多个接口。

  1. 访问修饰符

抽象类中的方法可以有public、protected和default等访问修饰符,而接口中的方法都是public的。

  1. 成员变量

抽象类中可以有成员变量,而接口中只能有常量(即被final修饰的变量)。

  1. 默认实现

抽象类中的抽象方法可以有默认实现,而接口中的方法没有默认实现。

  1. 目的

抽象类的目的是为了让子类重写其中的抽象方法,并且提供一些通用的实现。接口的目的是为了让不同的类实现相同的接口,并且能够进行互操作。

8.什么情况必须用抽象类

  1. 有一些方法需要被所有的子类实现,但是这些方法具体的实现在每个子类中可能是不同的。
  2. 子类中存在公共的成员变量,可以将这些公共的成员变量放到抽象类中,避免在每个子类中重复声明。
  3. 需要限制子类的继承层次,可以通过将某些方法定义为final或者private,然后将其它方法定义为抽象方法来实现这个限制。
  4. 需要定义模板方法,即定义一个算法的骨架,并让子类实现其中的某些步骤,可以使用抽象类来实现这个模板方法。

需要注意的是,如果一个类中的所有方法都是抽象方法,或者该类中只有成员变量而没有方法,那么这个类应该被定义为接口而不是抽象类。因为抽象类的作用是为了提供一些具体实现,而接口的作用是为了定义一些规范或者约束,不包含具体实现。

9.Java的特点

  • 简单易学;
  • 面向对象(封装,继承,多态);
  • 平台无关性( Java 虚拟机实现平台无关性);
  • 支持多线程( C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持);
  • 可靠性;
  • 安全性;
  • 支持网络编程并且很方便( Java 语言诞生本身就是为简化网络编程设计的,因此 Java 语言不仅支持网络编程而且很方便);
  • 编译与解释并存;

Java 强大的生态才是。

10.Java 与 C++ 的区别

  • Java 不提供指针来直接访问内存,程序内存更加安全
  • Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。
  • Java 有自动内存管理垃圾回收机制(GC),不需要程序员手动释放无用内存。
  • C ++同时支持方法重载和操作符重载,但是 Java 只支持方法重载(操作符重载增加了复杂性,这与 Java 最初的设计思想不符)。

11.面向对象和面向过程的区别?

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

12.JDK和JRE的区别?

JDK 是 Java Development Kit 缩写,它是功能齐全的 Java SDK。它拥有 JRE 所拥有的一切,还有编译器(javac)和工具(如 javadoc 和 jdb)。它能够创建和编译程序
JRE 是 Java 运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的一些基础构件。但是,它不能用于创建新程序

13.面向对象有哪些特性?

封装、继承、多态(父类的引用指向子类的实例)

14.什么是值传递和引用传递?

值传递: 是指在调用方法时,将实际参数拷贝一份传递给方法,这样在方法中修改形式参数时,不会影响到实际参数。
引用传递: 也叫地址传递,是指在调用方法时,将实际参数的地址传递给方法,这样在方法中对形式参数的修改,将影响到实际参数。
也就是说值传递,传递的是副本。引用传递,传递的是实际内存地址。
在 Java 语言中本质上只有值传递,也就说 Java 的传参只会传递它的副本,并不会传递参数本身。

15.自动装箱和拆箱

  • 装箱:将基本类型用它们对应的引用类型包装起来;
  • 拆箱:将包装类型转换为基本数据类型;
L1

LINENUMBER 8 L1

ALOAD 0

BIPUSH 10

INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;

PUTFIELD AutoBoxTest.i : Ljava/lang/Integer;

L2

LINENUMBER 9 L2

ALOAD 0

ALOAD 0

GETFIELD AutoBoxTest.i : Ljava/lang/Integer;

INVOKEVIRTUAL java/lang/Integer.intValue ()I

PUTFIELD AutoBoxTest.n : I

RETURN

从字节码中,我们发现装箱其实就是调用了 包装类的valueOf()方法,拆箱其实就是调用了 intValue()方法。

Integer i = 10 等价于 Integer i = Integer.valueOf(10)
int n = i 等价于 int n = i.intValue();

16.String 为什么不可变

  • 保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。
  • String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。

通过反射可以破坏 String 的不可变性

17.String, StringBuffer 和 StringBuilder区别

都是final类,都不允许被继承,线程安全的;
String类长度是不可变的,StringBuffer和StringBuilder类长度是可以改变的;
StringBuffer类是线程安全的,StringBuilder不是线程安全的;
对于三者使用的总结:

  1. 操作少量的数据: 适用 String
  2. 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
  3. 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer

18.String 类的常用方法有哪些

  • length() 字符串的长度
  • 2、charAt() 截取一个字符
  • 3、toCharArray() …
  • 4、equals()

19.new String(“dabin")会创建几个对象

使用这种方式会创建两个字符串对象(前提是字符串常量池中没有 “dabin” 这个字符串对象)。

  • “dabin” 属于字符串字面量,因此编译时期会在字符串常量池中创建一个字符串对象,指向这个 “dabin” 字符串字面量;
  • 使用 new 的方式会在堆中创建一个字符串对象。

20.什么是字符串常量池

https://juejin.cn/post/6995715596092440584#heading-0
字符串常量池(String Pool)保存着所有字符串字面量,这些字面量在编译时期就确定。

  • Java6及以前,字符串常量池存放在永久代。
  • Java7中Oracle的工程师对字符串池的逻辑做了很大的改变,即将字符串常量池的位置调整到Java堆内。
  • 所有的字符串都保存在堆(Heap)中,和其他普通对象一 样,这样可以让你在进行调优应用时仅需要调整堆大小就可以了。
  • 字符串常量池概念原本使用得比较多,但是这个改动使得我们有足够的理由让我们重新考虑在Java7中使用String.intern()。
  • Java8元空间,字符串常量在堆。
  • String在jdk8及以前内部定义了final char [] value用于存储字符串数据。jdk9时改为byte[]。

21.静态常量池、运行时常量池与字符串常量池的区别

像这些静态的、未加载的.class文件的数据被称为静态常量池,但经过jvm把.class文件装入内存、加载到方法区后,常量池就会变为运行时常量池!对应的符号引用在程序加载或运行时会被转变为被加载到方法区的代码的直接引用,在jvm调用这个方法时,就可以根据这个直接引用找到这个方法在方法区的位置,然后去执行。
而字符串常量池又是运行时常量池中的一小部分,字符串常量池的位置在jdk不同版本下,有一定区别!

22.Object常用方法有哪些?

getClass()、finalize()、toString()、equals()、hashcode()、wait()、notify()、notifyAll()和clone()。

23.浅拷贝和深拷贝

  • 浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
  • 深拷贝 :深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。

24.两个对象的hashCode相同,则 equals是否也一定相同吗

1、相等(相同)的对象必须具有相等的哈希码(或者散列码)。
2、如果两个对象的hashCode相同,它们并不一定相同。
equals与hashcode的关系:

  1. 如果两个对象调用equals比较返回true,那么它们的hashCode值一定要相同;
  2. 如果两个对象的hashCode相同,它们并不一定相同。

hashcode方法主要是用来提升对象比较的效率,先进行hashcode()的比较,如果不相同,那就不必在进行equals的比较,这样就大大减少了equals比较的次数,当比较对象的数量很大的时候能提升效率。
之所以重写equals()要重写hashcode(),是为了保证equals()方法返回true的情况下hashcode值也要一致,如果重写了equals()没有重写hashcode(),就会出现两个对象相等但hashcode()不相等的情况。这样,当用其中的一个对象作为键保存到hashMap、hashTable或hashSet中,再以另一个对象作为键值去查找他们的时候,则会查找不到。

25.Java创建对象有几种方式

  1. new关键字
  2. Class.newInstance

这是我们运用反射创建对象时最常用的方法。Class类的newInstance使用的是类的public的无参构造器。因此也就是说使用此方法创建对象的前提是必须有public的无参构造器才行,

  1. Constructor.newInstance

本方法和Class类的newInstance方法很像,但是比它强大很多。 java.lang.relect.Constructor类里也有一个newInstance方法可以创建对象。我们可以通过这个newInstance方法调用有参数(不再必须是无参)的和私有的构造函数(不再必须是public)

  1. Clone方法

无论何时我们调用一个对象的clone方法,JVM就会创建一个新的对象,将前面的对象的内容全部拷贝进去,用clone方法创建对象并不会调用任何构造函数。 要使用clone方法,我们必须先实现Cloneable接口并复写Object的clone方法(因为Object的这个方法是protected的,你若不复写,外部也调用不了呀)。

  1. 反序列化

当我们序列化和反序列化一个对象,JVM会给我们创建一个单独的对象,在反序列化时,JVM创建对象并不会调用任何构造函数。为了反序列化一个对象,我们需要让我们的类实现Serializable接口。

26.说说类实例化的顺序

初始化顺序
父类静态变量
父类静态代码块
子类静态变量
子类静态代码块
父类非静态变量
父类非静态代码块
父类构造函数
子类非静态变量
子类非静态代码块
子类构造函数

27.equals和==有什么区别

对于字符串变量来说,使用"“和"equals"比较字符串时,其比较方法不同。”"比较两个变量本身的值,即两个对象在内存中的首地址,"equals"比较字符串包含内容是否相同。
对于非字符串变量来说,如果没有对equals()进行重写的话,“==” 和 "equals"方法的作用是相同的,都是用来比较对象在堆内存中的首地址,即用来比较两个引用变量是否指向同一个对象。

28.final, finally, finalize 的区别

  • final 用于修饰属性、方法和类, 分别表示属性不能被重新赋值,方法不可被覆盖,类不可被继承。
  • finally 是异常处理语句结构的一部分,一般以try-catch-finally出现,finally代码块表示总是被执行。
  • finalize 是Object类的一个方法,该方法一般由垃圾回收器来调用,当我们调用System.gc()方法的时候,由垃圾回收器调用finalize()方法,回收垃圾,JVM并不保证此方法总被调用。

29.方法重载和重写的区别

(1)重写(Overriding)是”外壳不变,核心重写”,即在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法。
(2)重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
(3)方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形。

30.接口与抽象类区别?

共同点

  • 都不能被实例化。
  • 都可以包含抽象方法。
  • 都可以有默认实现的方法(Java 8 可以用 default 关键字在接口中定义默认方法)。

区别

  • 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系。
  • 一个类只能继承一个类,但是可以实现多个接口。
  • 接口中的成员变量只能是 public static final 类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。

31.常见的Exception有哪些?

NullPointerException:空指针异常
IndexOutOfBoundsException:下标越界
IOException:IO异常

32.Error和Exception的区别?

在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类有两个重要的子类:

  • Exception :程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。
  • Error :Error 属于程序无法处理的错误 ,我们没办法通过 catch 来进行捕获不建议通过catch捕获 。例如 Java 虚拟机运行错误(Virtual MachineError)、虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。

33.运行时异常和非运行时异常 (checked) 的区别?

受检异常(checked exception)和非受检异常(unchecked exception)
两者的区别主要在:受检的异常是由编译器强制执行的,必须捕获,用于指示不受程序控制的异常情况(例如,I/O 错误),而非受检的异常在运行时发生,用于指示编程错误(例如,空指针。正因为如此,受检异常在使用的时候需要比非受检异常更多的代码来避免编译错误。

34.守护线程是什么?

守护线程是区别于用户线程,用户线程即我们手动创建的线程,而守护线程是程序运行的时候在后台提供一种通用服务的线程垃圾回收线程就是典型的守护线程。
守护线程,专门用于服务其他的线程,如果其他的线程(即用户自定义线程)都执行完毕,连main线程也执行完毕,那么jvm就会退出(即停止运行)——此时,连jvm都停止运行了,守护线程当然也就停止执行了。

35.如何实现对象克隆

(1)在要实现克隆的对象类中实现Cloneable接口。
Cloneable接口为标记接口(标记接口为用户标记实现该接口的类具有该接口标记的功能,常见的标记接口有Serializable、Cloneable、RandomAccess),如果没有实现该接口,在调用clone方法时就会抛出CloneNotSupportException异常。
(2)在类中重写Object的clone方法。
重写是为了扩大访问权限,如果不重写,因Object的clone方法的修饰符是protected,除了与Object同包(java.lang)和直接子类能访问,其他类无权访问。并且默认Object的clone表现出来的是浅拷贝,如果要实现深拷贝,也是需要重写该方法的。

  • 浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
  • 深拷贝 :深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。

36.同步和异步的区别

同步是阻塞模式,异步是非阻塞模式。
同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;
异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其它进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。

37.阻塞和非阻塞的区别

阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

38.BIO/NIO/AIO区别的区别?

https://javaguide.cn/java/io/io-model.html#bio-blocking-i-o
BIO 属于同步阻塞 IO 模型
同步阻塞 IO 模型中,应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间。
NIO 属于同步非阻塞 IO 模型
Java 中的 NIO 可以看作是 I/O 多路复用模型
AIO 也就是 NIO 2。Java 7 中引入了 NIO 的改进版 NIO 2,它是异步 IO 模型。
异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

猜你喜欢

转载自blog.csdn.net/weixin_56640241/article/details/129797549