1. Java基础面试题

112 阅读20分钟

一. JavaOOP面试题

1. Java的几种数据类型?

  1. 整型 :

byte(字节型):内存占1个字节(8bit)

short(短整型):内存占用2个字节(16bit)

int(整型):内存占4个字节(32bit)

long(长整型):内存占8个字节(64bit)

  1. 浮点型:

float(单精度浮点型):内存中占4个字节(32bit)

double(双精度浮点型):内存中占8个字节(64bit)

  1. 字符型:

char(字符型):内存占2个字节(16bit)

  1. 布尔类型

boolean(布尔型):内存占1个字节,只有两个值 true和false(8bit)

2.你知道哪些数据结构?

img

在这里插入图片描述

2.1 数组

优点:

  • 查询速度快,时间复杂度为O(1)(因为有索引);

缺点:

  • 数组的大小在创建后就确定了,无法扩容;
  • 添加、删除元素的操作很耗时间,因为要移动其他元素。

2.2 链表

优点:

  • 不需要知道数据的大小,可以充分利用计算机内存空间,实现灵活的内存动态管理

  • 插入元素和删除快,时间复杂度为O(1)(因为只需要改变引用即可,不需要移动元素);

缺点:

  • 不能像数组一样随机读取

  • 含有大量的引用,占用的内存空间大;

  • 查找元素需要遍历整个链表,耗时。

2.3 栈

  • 结构:

    img
  • 特点:先进后出

2.4 队列

  • 结构:

    img
  • 特点:先进先出

2.5 树

  • 二叉树
  • AVL树
  • 红黑树
  • B树
  • B+树

2.6 堆

  • 堆可以被看做是一棵树的数组对象,具有以下特点:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;

  • 堆总是一棵完全二叉树。

  • 将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

2.7 图

图(Graph)结构是一种非线性的数据结构,图在实际生活中有很多例子,比如交通运输网,地铁网络,社交网络,计算机中的状态执行(自动机)等等都可以抽象成图结构。图结构比树结构复杂的非线性结构。 img

2.8 哈希表

哈希表,也叫散列表,是根据关键码和值 (key和value) 直接进行访问的数据结构,通过key和value来映射到集合中的一个位置,这样就可以很快找到集合中的对应元素;

利用哈希函数快速访问到数组的目标数据。如果发生哈希冲突,就使用链表进行存储。

3.Java中的数据结构有哪些?

3.1 线性表(数组):ArrayList

3.2 链表:LinkedList

3.3 栈

3.4 队列:Quene

3.5 图(Map)

3.6 树(Tree):TreeSet 和 TreeMap

4.equals与==的区别?

  • ==

    1. == 通常用于基本类型的比较,比较的是值
    2. == 在比较引用类型时,比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象
  • equals

    equals()方法定义在Object类里面,用于检查两个对象的相等性

    1. 如果比较的类没有重写equals方法,调用的是Object类的equals方法,Object类的equals方法默认是使用==比较地址值
    2. 如果比较的类重写了equals方法,则是判断对象的等价性,比如String,比较的是值。即如果两个对象指向内存地址相同或者两个对象各个字段值相同,那么就是同一个对象。

5. 重写equals() 为什么一定要重写hashcode()?

  • 因为必须保证重写后的equals方法认定为相同的两个对象具有相同的hash值;

  • 拿HashMap的put() 方法来说,在存放String类型的key时,如果String只重写了equals没有重写hashcode,

    当我们往HashMap里放String类型的 k1和k2,k1和k2的值相同,首先会调用Key这个类的hashCode方法计

    算它的hash值,这是由于没有重写hashcode,调用的还是Object的hashcode方法,而Object类的hashCode方

    法返回的hash值其实是对象的内存地址,此时k1和k2的hash值不同,两个相同的key都插进去了,这明显不对。

6. 重载和重写的区别

  • 重写:

    1. 发生在父类与子类之间
    2. 方法名,参数列表,返回类型(除过子类中方法的返回类型是父类中返回类型的子类)必须相同
    3. 访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)
    4. 重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常
  • 重载

    在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同)则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同

    1. 重载Overload是一个类中多态性的一种表现
    2. 重载要求同名方法的参数列表不同(参数类型,参数个数甚至是参数顺序)
    3. 重载的时候,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准
  • 重载(Overload)和重写(Override)的区别?

    • 方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。

    • 重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;

    • 重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求,不能根据返回类型进行区分。

7. ++i 和 i++的区别?

++i:先计算,后赋值

i++:先赋值,后计算

8. Java中各种类型的默认值

  • Byte,short,int,long 默认是都是0
  • Boolean默认值是false
  • Char类型的默认值是 ''
  • Float与double类型的默认是0.0
  • 对象类型的默认值是 null

9.Object类常用方法有那些?

  • Equals
  • Hashcode
  • toString
  • wait
  • notify
  • clone
  • getClass

10. 构造方法能不能重载和重写

可以重载,但不能重写。

11. Static关键字有什么作用?

  • Static可以修饰内部类、方法、变量、代码块
  • Static修饰的类是静态内部类
  • Static修饰的方法是静态方法,表示该方法属于当前类的,而不属于某个对象的,静态方法也不能被重写,可以直接使用类名来调用。在static方法中不能使用this或者super关键字。
  • Static修饰变量是静态变量或者叫类变量,静态变量被所有实例所共享,不会依赖于对象。静态变量在内存中只有一份拷贝,在JVM加载类的时候,只为静态分配一次内存。
  • Static修饰的代码块叫静态代码块,通常用来做程序优化的。静态代码块中的代码在整个类加载的时候只会执行一次。静态代码块可以有多个,如果有多个,按照先后顺序依次执行。

12. final在java中的作用

  1. 被final修饰的方法不可以被重写

  2. 被final修饰的变量不可以被改变.如果修饰引用,那么表示引用不可变,引用指向的内容可变.

  3. 被final修饰的方法,JVM会尝试将其内联,以提高运行效率

  4. 被final修饰的常量,在编译阶段会存入常量池中.

  5. 除此之外,编译器对final域要遵守的两个重排序规则更好: 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序

13. StringString ,StringBuffffer 和 StringBuilder 的区别是什么?

  • String是只读字符串,它并不是基本数据类型,而是一个对象。从底层源码来看是一个fifinal类型的字符 数组,所引用的字符串不能被改变,一经定义,无法再增删改。每次对String的操作都会生成新的 String对象
  • StringBuffer / StringBuilder 表示的字符串对象可以直接进行修改
  • StringBuffer / StringBuilder 的方法完全相同,
  • StringBuffer 是线程安全的,方法被synchronized修饰
  • StringBuilder 是非线程安全的;所以Stringbuilder比stringbuffer效率更高。

14. Java中的继承是单继承还是多继承

Java中既有单继承,又有多继承。

  • 对于java类来说只能有一个父类
  • 对于接口来说可以同时继承多个接口

15.Super与this表示什么?

  • Super表示当前类的父类对象
  • This表示当前类的对象

16.普通类与抽象类有什么区别?

  • 普通类不能包含抽象方法,抽象类可以包含抽象方法
  • 抽象类不能直接实例化,普通类可以直接实例化

17. 什么是接口?为什么需要接口?

  • 简单的来说,接口就是一种规范。定义约束,提供一个统一的实现标准
  • 是一种特殊的java类,接口弥补了java单继承的缺点

18. 接口有什么特点?

  • 接口中声明全是public static final修饰的常量
  • 接口中所有方法都是抽象方法
  • 接口是没有构造方法的
  • 接口也不能直接实例化
  • 接口可以多继承

19. 抽象类和接口的区别?

区别:

  1. 抽象类中有构造方法,接口中无构造方法。
  2. 接口和接口之间支持多继承,类和类之间只能单继承。
  3. 一个类可以实现多个接口,但只能继承一个抽象类。

从编程角度看:

  • 抽象类适合用来定义某个领域的固有属性,也就是本质,接口适合用来定义某个领域的扩展功能。
  • 当需要为一些类提供公共的实现代码时,应优先考虑抽象类;当注重代码的扩展性跟可维护性时,应当优先采用接口。

20. Hashcode有什么作用?

  • java的集合有两类,一类是List,还有一类是Set。前者有序可重复,后者无序不重复。当我们在set中插入的时候怎么判断是否已经存在该元素呢,可以通过equals方法。但是如果元素太多,用这样的方法就会比较慢。

  • hashCode方法可以这样理解:

它返回的就是根据对象的内存地址换算出的一个值。这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。

21. a=a+b与a+=b有什么区别吗?

  • += 操作符会进行隐式自动类型转换,此处a+=b隐式的将加操作的结果类型强制转换为持有结果的类型。

  • 而a=a+b则不会自动进行类型转换。

如:

byte a = 127;
byte b = 127;
b = a + b; // 报编译错误:cannot convert from int to byte
b += a;

22.final、finalize()、finally的区别是什么?

性质不同:

  1. final为关键字;
  2. finalize()为方法;
  3. finally为区块标志,用于try语句中;

作用不同:

  1. final为用于标识常量的关键字,final标识的关键字存储在常量池中;
  2. finalize()方法在Object中进行了定义,用于在对象“消失”时,由JVM进行调用用于对对象进行垃圾回收,类似于C++中的析构函数;用户自定义时,用于释放对象占用的资源(比如进行I/0操作);
  3. finally{}用于标识代码块,与try{}进行配合,不论try中的代码执行完或没有执行完(这里指有异常),该代码块之中的程序必定会进行;

23. a.hashCode() 有什么用?与 a.equals(b) 有什么关系?

  • HashCode有什么用?

  1. 假设内存中有0 1 2 3 4 5 6 7 8这9个位置,如果我有个字段叫做ID,那么我要把这个字段存放在以上9个位置之一,如果不用HashCode而任意存放,那么当查找时就需要到8个位置中去挨个查找

  2. 使用HashCode则效率会快很多,把ID的HashCode % 9,然后把ID存放在取得余数的那个位置,然后每次查找该类的时候都可以通过ID的HashCode % 9求余数直接找到存放的位置了

  3. 如果ID的HashCode % 9算出来的位置上本身已经有数据了怎么办?这就取决于算法的实现了,比如ThreadLocal中的做法就是从算出来的位置向后查找第一个为空的位置,放置数据;HashMap的做法就是通过链式结构连起来。反正,只要保证放的时候和取的时候的算法一致就行了。

  4. 如果ID的HashCode % 9相等怎么办(这种对应的是第三点说的链式结构的场景)?这时候就需要定义equals了。先通过HashCode%8来判断类在哪一个位置,再通过equals来在这个位置上寻找需要的类。对比两个类的时候也差不多,先通过HashCode比较,假如HashCode相等再判断equals。如果两个类的HashCode都不相同,那么这两个类必定是不同的

  • 举个实际的例子Set:

我们知道Set里面的元素是不可以重复的,那么如何做到?Set是根据equals()方法来判断两个元素是否相等的。比方说Set里面已经有1000个元素了,那么第1001个元素进来的时候,最多可能调用1000次equals方法,如果equals方法写得复杂,对比的东西特别多,那么效率会大大降低。使用HashCode就不一样了,比方说HashSet,底层是基于HashMap实现的,先通过HashCode取一个模,这样一下子就固定到某个位置了,如果这个位置上没有元素,那么就可以肯定HashSet中必定没有和新添加的元素equals的元素,就可以直接存放了,都不需要比较;如果这个位置上有元素了,逐一比较,比较的时候先比较HashCode,HashCode都不同接下去都不用比了,肯定不一样,HashCode相等,再equals比较,没有相同的元素就存,有相同的元素就不存。如果原来的Set里面有相同的元素,只要HashCode的生成方式定义得好(不重复),不管Set里面原来有多少元素,只需要执行一次的equals就可以了。这样一来,实际调用equals方法的次数大大降低,提高了效率。

hashcode常用于基于 hash 的集合类,如 Hashtable、HashMap、LinkedHashMap 等等。它与equals() 方法关系特别紧密。根据 Java 规范,两个使用 equal() 方法来判断相等的对象,必须具有相同的 hashcode。

24. 动态代理的实现方式有哪些?

两种动态代理的实现方式:

  1. 基于接口的动态代理

  • 提供者:JDK

  • 使用JDK官方的Proxy类创建代理对象

  • 注意:JDK动态代理,代理的目标对象必须实现接口

  1. 基于类的动态代理(需要引入CGLIB相关Jar包)

  • 提供者:第三方 CGLib

  • 使用CGLib的Enhancer类创建代理对象

  • 注意:如果报 asmxxxx 异常,需要导入 asm.jar包

spring中的动态代理:

  • 最新版的 Spring 中,依然是如上策略不变。即能用 JDK 做动态代理就用 JDK,不能用 JDK 做动态代理就用 Cglib,即首选 JDK 做动态代理。
  • springboot 2.0之前:默认使用jdk动态代理;
  • springboot 2.0之后:默认使用cglib动态代理;

二.Java异常面试题

1. Java中异常分为哪两种?

  1. 编译时异常:RuntimeException
  • SQLException ;
  • IOexception ;
  • FileNotFoundException;
  • ClassNotFoundException
  1. 运行时异常:RuntimeException
  • NullPointerException;
  • ArrayIndexOutOfBoundsException;
  • ClassCastException;
  • ArithmeticException(数学计算异常);
  • ArithmeticException;

2. 处理异常的方式有几种

  1. 捕获异常
  • try 、catch、finally
  1. 抛异常
  • throws

3. 什么时候捕获异常,什么时候抛出异常

功能内部如果出现异常,如果内部可以处理,就用try;

如果功能内部处理不了,就必须声明出来,让调用者处理。

4. 如何自定义一个异常

继承一个异常类,通常是RumtimeException或者Exception

5. Error与Exception区别?

Error和Exception都是java错误处理机制的一部分,都继承了Throwable类。 Exception表示的异常,异常可以通过程序来捕捉,或者优化程序来避免。 Error表示的是系统错误,不能通过程序来进行错误处理。

三.Java中的IO与NIO面试题

1. Java中的IO流分为哪几类

  1. InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
  2. OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

2. 字节流和字符流的区别和使用场景?

  • 区别:
  1. 字节流操作的基本单元是字节;字符流操作的基本单元是字符
  2. 字节流默认不使用缓冲区;字符流使用缓冲区
  • 使用场景:
  1. 字节流:一般用于读取文件,如:音频文件、图片
  2. 字符流:一般用于文本数据的处理

3. NIO 和传统的 IO 有什么区别?

  1. 传统 IO 一般是一个线程等待连接,连接过来之后分配给 processor 线程,processor 线程与通道连接后如果通道没有数据过来就会阻塞(线程被动挂起)不能做别的事情。NIO 则不同,首先,在 selector 线程轮询的过程中就已经过滤掉了不感兴趣的事件,其次,在 processor处理感兴趣事件的 read 和 write 都是非阻塞操作即直接返回的,线程没有被挂起。
  2. 传统 io 的管道是单向的,nio 的管道是双向的。
  3. 两者都是同步的,也就是 java 程序亲力亲为的去读写数据,不管传统 io 还是 nio 都需要read 和 write 方法,这些都是 java 程序调用的而不是系统帮我们调用的,nio2.0 里这点得到了改观,即使用异步非阻塞 AsynchronousXXX 四个类来处理。

4. BIO 和 NIO 和 AIO 的区别以及应用场景?

  • 同步:java 自己去处理 io。

  • 异步:java 将 io 交给操作系统去处理,告诉缓存区大小,处理完成回调。

  • 阻塞:使用阻塞 IO 时,Java 调用会一直阻塞到读写完成才返回。

  • 非阻塞:使用非阻塞 IO 时,如果不能立马读写,Java 调用会马上返回,当 IO 事件分发器通知可读写时在进行读写,不断循环直到读写完成。

  • BIO:同步并阻塞,服务器的实现模式是一个连接一个线程,这样的模式很明显的一个缺陷是:由于客户端连接数与服务器线程数成正比关系,可能造成不必要的线程开销,严重的还将导致服务器内存溢出。当然,这种情况可以通过线程池机制改善,但并不能从本质上消除这个弊端。

  • NIO:在 JDK1.4 以前,Java 的 IO 模型一直是 BIO,但从 JDK1.4 开始,JDK 引入的新的 IO 模型 NIO,它是同步非阻塞的。而服务器的实现模式是多个请求一个线程,即请求会注册到多路复用器 Selector 上,多路复用器轮询到连接有 IO 请求时才启动一个线程处理。

  • AIO:JDK1.7 发布了 NIO2.0,这就是真正意义上的异步非阻塞,服务器的实现模式为多个有效请求一个线程,客户端的 IO 请求都是由 OS 先完成再通知服务器应用去启动线程处理(回调)。 应用场景:并发连接数不多时采用 BIO,因为它编程和调试都非常简单,但如果涉及到高并发的情况,应选择 NIO 或 AIO,更好的建议是采用成熟的网络通信框架 Netty。

  • BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。

NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。

AIO方式适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

5. Java如何实现序列化

  1. 将需要被序列化的类实现Serializable接口
  2. 实现Serializable接口只是为了标注该对象是可被序列化的,然后使用一个输出流来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

四. Java反射

1. java反射的作用

反射机制是在运行时,

  • 对于任意一个类,都能够知道这个类的所有属性和方法;
  • 对于任意个对象,都能够调用它的任意一个方法。
  • 在java中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。

2. 获取Class对象的4种方法?

  1. Class.forName(“类的路径”);
  2. 类名.class
  3. 对象名.getClass()
  4. 基本类型的包装类,可以调用包装类的Type属性来获得该包装类的Class对象

3.Java反射API有几类?

  1. Class 类:反射的核心类,可以获取类的属性,方法等信息。
  2. Field 类:Java.lang.reflec 包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。
  3. Method 类:Java.lang.reflec 包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。
  4. Constructor 类:Java.lang.reflec 包中的类,表示类的构造方法。

4. 反射机制的优缺点?

  • 优点:
  1. 能够运行时动态获取类的实例,提高灵活性;
  • 缺点:
  1. 使用反射性能较低,需要解析字节码,将内存中的对象进行解析
  2. 相对不安全,破坏了封装性(因为通过反射可以获得私有方法和属性)