JAVA面试问题

一、java基础知识

1.1重载和重写的区别

重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。

重写:发生在父子类中,方法名、参数列表必须相同,返回值反胃小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法。

1.2String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变得?

可变性

简单来说:string类中使用final关键字字符数组保存字符串,private final chat value[ ] ,所以string对象是不可变得。而StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstraststringBuider中也是使用字符数组保存字符串char[ ]value但是没有final关键字修饰,所以这两种对象都是可变的。

StringBuilder与StringBuffer的构造方法都是调用父类构造方法也就是AbstractStringBuilder实现的,大家可以自行查阅源码。

AbstractStringBuilder.java

abstract class AbstractStringBuilder implements Appendable, CharSequence {
     char[] value; 
     int count; 
     AbstractStringBuilder() {
     }
     AbstractStringBuilder(int capacity) {
         value = new char[capacity]; 
}

线程安全性

String中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder是StringBuider与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity。append、insert、indexOf等公共方法。StringBuilder对方法加了同步锁或者对调用的方法加了同步锁,所以线程是安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。

性能

每次对string类型进行改变的时候,都会成为一个新的String对象,然后将指针执行新的String对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StringBuilder相比使用StringBuffer仅能获得10%~15%左右的性能提升,但却要冒多线程不安全的风险。

对于三者使用的总结:

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

1.3.自动装箱与拆箱

装箱:

        将基本类型用他们对应的引用类型包装起来;

拆箱:

        将包装类型转换为基本数据类型;

1.4==与equals的区别

==:

         他的作用是判断两个对象的地址是不是相等。即,判断两个队形是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)

equals():

        它的作用是判断两个对象的地址是不是相等。但一般有两种使用情况:

  • 情况1:类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两的对象。
  • 情况2:类覆盖了equals()方法。一般,我们都覆盖equals()方法来两个对象的内容相等;若它们的内容相等,则范湖true(即,认为这两个对象相等)。

举个栗子:

public class test1 {
     public static void main(String[] args) { 
         String a = new String("ab"); // a 为一个引用 
         String b = new String("ab"); // b为另一个引用,对象的内容一样
         String aa = "ab"; // 放在常量池中 
         String bb = "ab"; // 从常量池中查找 
         if (aa == bb) // true 
             System.out.println("aa==bb");
         if (a == b) // false,非同一对象 
             System.out.println("a==b"); 
         if (a.equals(b)) // true 
             System.out.println("aEQb");
         if (42 == 42.0) { // true                 
             System.out.println("true");
         }
    } 
}

说明:

  • String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。
  • 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把他赋给当前引用。如果没有就在常量池中重新创建一个String对象。

1.5关于final关键字的一些总结

final关键字主要用在三个地方:变量、方法、类。

  1. 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后变不能在让其指出另一个对象。
  2. 当用final修饰的类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式的指定final方法.
  3. 使用 fifinal 方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。
    在早期的 Java 实现版本中,会将 fifinal 方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的
    任何性能提升(现在的 Java 版本已经不需要使用 fifinal 方法进行这些优化了)。类中所有的 private 方法都隐式地
    指定为 fifianl

1.6Object类的常见方法总结

Object类是一个特殊类,是所有父类的子类。他主要提供了一下11个方法:

 1.7java 中的异常处理

java异常类层次构造图

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

Exception(异常)Error(错误),二者都是java异常处理的重要子类,各自都包含大量子类。

Error(错误):是程序无法处理的错误,表示运行应用程序中较严重的问题。打多数错误与代码编写者执行编写者执行的操作无关,而表示代码运行时JVM(java虚拟机)出现的问题。例如:java虚拟机运行错误(Virtual MachineError),当JVM不再有继续执行操作所需的内存资源时,将出现OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。

这些错误表示故障发生于虚拟机自身、或者发生在虚拟机视图执行应用时,如java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时你不允许出现的状况。对于设计合理的应用程序来说,几十发生了错误,本质上也不应该试图去处理它所引起的异常状况。在java中,错误通过Error的子类描述。

Exception(异常):吃程序本身可以处理的异常。Exception类有一个重要的子类RuntimeException。RuntimeException异常由java虚拟机抛出。NullPointerException(要访问的变量没有引用任何对象时,抛出该异常)、ArithmeticException(算数运算异常,一个整数除以0时,抛出该异常)和ArraylndexOutOfBoundsExveption(下标越界异常)。

注意:异常和错误的区别:异常能被程序本身处理,错误是无法处理的。

Throwabl类常用方法 

  • public string getMessage():返回异常发生时的详细信息
  • public string toString():返回异常发生时的简要描述
  • public string getLocalizedMessage():返回异常对象的本地化信息。使用Throwable的子类覆盖这个方法,可以声称本地化信息。如果子类没有覆盖该方法,这该方法返回的信息与getMessage()返回的结果相同。
  • public void printStackTrace():在控制台上打印Throwable对象封装的异常信息

异常处理总结

  • try块:用于捕获异常。其后可接零个或多个catch块,如果没有catch块,如果没有catch块,则必须跟一个finally块。
  • catch块:用于处理try捕获到的异常
  • finally块:无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行

在以下四种特殊情况下,finally块不会被执行:

  1. 在finally语句块中发生了异常
  2. 在前面的的代码中用了System.exit()退出程序
  3. 程序所在的线程死亡
  4. 关闭CPU

1.8获取用键盘输入常用的两种方法

方法一:通过Scanner

Scanner input =new Scanner(System.in);
String s= input.nextLine();
input.close();

方法二:通过BufferedReader

BufferedReader input =new BufferedReader(new InputStreamReader(System.in));
String s=input.readLine();

1.9接口和抽象类的区别是什么?

  1. 接口的方法默认是public,所有方法在接口中不能有实现(java8开始接口方法可已有默认实现),抽象类可以有非抽象的方法。
  2. 接口中的实例变量默认是final类型的,而抽象类中则不一定
  3. 一个类可以实现多个接口,但最多只能实现一个抽象类
  4. 一个类实现接口库的话要实现接口的所有方法,而抽象类不一定
  5. 接口不能用new实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。

2.java集合框架

2.1ArrayList与LinkedLIst异同

  • 是否保证线程安全:ArrayList和LinkedList都是不同步的,也就是不保证线程安全
  • 底层数据结构:ArrayList底层使用的是Object数组;LinkedList 底层使用的是双向链表数据结构(JDK1.6之 前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别);
  • 插入和删除是否受元素位置的影响: ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素 位置的影响。 比如:执行 add(E e) 方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种 情况时间复杂度就是O(1) 。但是如果要在指定位置 i 插入和删除元素的话( add(int index, E element) )时 间复杂度就为 O(n-i) 。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的 (n-i) 个元素都要执行向后位 / 向前移一位的操作。 ② LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是
    近似 O 1 )而数组为近似 O n )。
  • 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速机访问就是通 过元素的序号快速获取元素对象( 对应于 get(int index) 方法 )
    内存空间占用: ArrayList 的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)

2.2ArrayList与vector区别

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

2.3HashMap的底层实现

Jdk1.8之前

JDK1.8 之前 HashMap 底层是 数组和链表 结合在一起使用也就是 链表散列 HashMap 通过 key hashCode 过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的 长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的 话,直接覆盖,不相同就通过拉链法解决冲突。
所谓扰动函数指的就是 HashMap hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。

JDK1.8之后

相比于之前的版本, JDK1.8 之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8 )时,将链表转化为红黑树,以减少搜索时间。

2.4HashMap和Hashtable的区别

  1. 线程是否安全: HashMap 是非线程安全的, HashTable 是线程安全的; HashTable 内部的方法基本都经过 synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!
  2. 效率: 因为线程安全的问题, HashMap 要比 HashTable 效率高一点。另外, HashTable 基本被淘汰,不要在代码中使用它;
  3. Null key Null value 的支持: HashMap 中, null 可以作为键,这样的键只有一个,可以有一个或多个键 所对应的值为 null 。但是在 HashTable put 进的键值只要有一个 null ,直接抛出 NullPointerException
  4. 初始容量大小和每次扩充容量大小的不同 : ①创建时如果不指定容量初始值, Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的 2n+1 HashMap 默认的初始化大小为 16 。之后每次扩充,容量变为原来的2 倍。②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2 的幂次方大小( HashMap 中的 tableSizeFor() 方法保证,下面给出了源代码)。也就是说 HashMap 总是使用2 的幂作为哈希表的大小 , 后面会介绍到为什么是 2 的幂次方。
  5. 底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。 Hashtable 没有这样的机制。

2.5HashMap的长度为什么是2的幂次方

为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀。我们上面也讲到了过了, Hash 值的范围值-2147483648 2147483647 ,前后加起来大概 40 亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个40 亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ (n - 1) & hash 。(n代表数组长度)。这也就解释了 HashMap 的长度为什么是 2 的幂次方。
这个算法应该如何设计呢?
我们首先可能会想到采用 % 取余的操作来实现。但是,重点来了: 取余 (%) 操作中如果除数是 2 的幂次则等价于与其
除数减一的与 (&) 操作(也就是说 hash%length==hash&(length-1) 的前提是 length 2 n 次方;)。 并且 用二进制位操作 & ,相对于 % 能够提高运算效率,这就解释了 HashMap 的长度为什么是 2 的幂次方。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值