Java基础

1. 语法基础

1.1 面向对象三大特性

  • 封装

    • 将对象的属性和操作隐藏在内部,不允许外部对象直接访问对象内部的信息,对象提供可以被外部访问的方法来操作属性

    • 减少耦合,提高可重用性,减少维护的负担,更容易被程序员理解

  • 继承

    • 继承实现了is-a的关系,可以快速创建新的类,提高代码的重用性,程序的可维护性,提高开发效率

  • 多态

    • 表示一个对象具有多种的状态,具体表现为父类的引用指向子类的实例

    • 多态分为编译时多态和运行时多态:

      • 编译时多态主要指方法的重载

      • 运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定

    • 运行时多态有三个条件:

      • 继承

      • 覆盖(重写)

      • 向上转型

1.2 == 和 equals 的区别是什么

  • 对于基本类型,==比较的是值;equals不能用于基本类型的比较;

  • 对于引用类型,==比较的是地址,equals用于比较对象的内容;

  • 不能更改 == 运算符的行为,但可以覆盖 equals()方法并定义对象相等的标准

1.3 深拷贝和浅拷贝区别是什么?

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

    • 实现 Cloneable 接口并重写 Object 类中的 clone()方法;递归调用clone方法

  • 深拷贝 :深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。

    • 实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆

1.4 简述下重写和重载的特点

重写

  • 1.参数列表必须完全与被重写的方法相同,否则不能称其为重写而是重载.

  • 2.返回的类型必须一直与被重写的方法的返回类型相同,否则不能称其为重写而是重载。

  • 3.访问修饰符的限制一定要大于被重写方法的访问修饰符

  • 4.重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常

重载

  • 1.必须具有不同的参数列表

  • 2.可以有不同的返回类型,只要参数列表不同就可以了;

  • 3.可以有不同的访问修饰符

  • 4.可以抛出不同的异常

1.5 创建线程有哪几种方式?线程有哪些状态?

  • 创建线程有三种方式:

    • 继承 Thread 重写 run 方法;

    • 实现 Runnable 接口;

    • 实现 Callable 接口。

  • 线程的状态:

    • NEW 尚未启动

    • RUNNABLE 正在执行中

    • BLOCKED 阻塞的(被同步锁或者IO锁阻塞)

    • WAITING 永久等待状态

    • TIMED_WAITING 等待指定的时间重新被唤醒的状态

    • TERMINATED 执行完成

1.6 try() 里面有⼀个return语句, 那么后⾯的finally{}⾥⾯的code会不会被执行, 什么时候执行, 是在return前还是return后?

  • 如果try中有return语句, 那么finally中的代码还是会执⾏。因为return表⽰的是要整个⽅法体返回, 所以,finally中的语句会在return之前执⾏。

1.7 循环体中break,continue,return的区别及作用?

  • break跳出当前的循环,不再执行循环(结束当前的循环体)

  • continue跳出本次循环,继续执行下次循环(结束正在执行的循环进入下一个循环条件)

  • return程序返回,不再执行下面的代码(结束当前的方法直接返回)

2. 泛型

2.1 什么是泛型?有什么作用?

  • Java 泛型(Generics) 是 JDK 5 中引入的一个新特性。使用泛型参数,可以增强代码的可读性以及稳定性。

  • 编译器可以对泛型参数进行检测,并且通过泛型参数可以指定传入的对象类型。

3. 异常

Throwable 是 Java 语言中所有错误与异常的超类。

  • Error 类及其子类:程序中无法处理的错误,表示运行应用程序中出现了严重的错误

  • Exception 程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类:运行时异常和编译时异常

    • 运行时异常

    都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。

    运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。

    • 非运行时异常 (编译异常)

    是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

4. 反射

4.1 什么是反射?

反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一 个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对像的方 法的功能称为Java语言的反射机制。

3.1 throw和throws的区别

  • throws 关键字用于异常申明,它的作用和 try-catch 相似;而 throw 关键字用于显式的抛出异常。

  • throws 关键字后面跟的是异常的名字;而 throw 关键字后面跟的是异常的对象。

  • throws 关键字出现在方法签名上,而 throw 关键字出现在方法体里。

  • throws 关键字在声明异常的时候可以跟多个,用逗号隔开;而 throw 关键字每次只能抛出一个异常。

Java集合

2.1 ArrayList 和 LinkedList 的区别是什么?

  • 数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现(JDK1.6 之前为循环链表,JDK1.7 取消了循环)。

  • 随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。而 ArrayList(实现了RandomAccess接口) 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)

  • 增和删效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高

    • ArrayList 要在指定位置 i 插入和删除元素的话,要通过System.arraycopy对指定元素及之后的(n-i)个元素都要执行向后位/向前移一位的操作。

    • LinkedList ,要在指定位置 i 插入和删除元素的话(add(int index, E element)remove(Object o)), 时间复杂度为 O(n) ,因为需要先移动到指定位置再插入。

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

    • 注:我们在项目中一般是不会使用到 LinkedList 的,需要用到 LinkedList 的场景几乎都可以使用 ArrayList 来代替,并且,性能通常会更好!就连 LinkedList 的作者约书亚 · 布洛克(Josh Bloch)自己都说从来不会使用 LinkedList

2.2 ArrayList自动扩容

当增加元素时,增加后的元素个数大于当前数组长度时,按当前数组长度扩容1.5倍,调用Arrays.copyOf进行数组的扩容复制

2.3 ArrayList的Fail-Fast机制?

ArrayList也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会快速失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险

2.3 简述下HashMap 的底层数据结构和扩容机制

  • HashMap 由“数组+链表+红黑树”组成。链表过长,会严重影响 HashMap 的性能,而红黑树搜索的时间复杂度是 O(logn),而链表是 O(n)。因此,JDK 8 对数据结构做了进一步的优化,引入了红黑树,链表和红黑树在达到一定条件会进行转换:

    • 当链表超过 8 且数据总量超过 64 时会转红黑树。

    • 将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树,以减少搜索时间。

2.4 能讲一下HashMap在哪些情况下会触发resize()进行扩容吗?

  1. 初始化数组为空,则进行首次扩容,有初始容量initialCapacity按初始容量,没有按默认初始容量(16)。

  2. 将元素接入链表后,如果链表长度达到8,并且数组长度小于64,则扩容。

  3. 添加后,如果数组中元素超过阈值,即比例超出限制(默认为0.75),则扩容。

2.5 JDK1.8为例,简单讲一下HashMap 的put方法流程?

  1. 首先根据 key 的值计算 hash 值,找到该元素在数组中存储的下标;

  2. 如果数组是空的,则调用 resize 进行初始化;

  3. 如果没有哈希冲突直接放在对应的数组下标里;

  4. 如果冲突了,且 key 已经存在,就覆盖掉 value;

  5. 如果冲突后,发现该节点是红黑树,就将这个节点挂在树上;

  6. 如果冲突后是链表,判断该链表是否大于 8 ,如果大于 8 并且数组容量小于 64,就进行扩容;如果链表节点大于 8 并且数组的容量大于 64,则将这个结构转换为红黑树;否则,链表插入键值对,若 key 存在,就覆盖掉 value。