面经-基础篇-三

本文最后更新于:2021年3月30日 晚上

以下内容并非博主亲身经历的面试问题,只是对网络中的面经做一个总结、记录。

1.21 如何在父类中为子类自动完成所有的hashcode和equals实现?这么做有何优劣?

  • 优点:子类中我们不用再写。
  • 缺点:有时候父类中equals和hashcode方法不满足我们的需求,需要重写。

1.22 说一说你对java.lang.Object对象中hashCode和equals方法的理解。在什么场景下需要重新实现这两个方法。

  • hashCode与equals方法都是Java Object对象中的方法,也就是说Java的一切对象都提供这两个方法。
  • 当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。
    如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;
    如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。
    所以这里存在一个冲突解决的问题,这个时候就需要重写。

1.23 有没有可能2个不相等的对象有相同的hashcode。

  • hashCode是所有java对象的固有方法;
  • 如果不重载的话,返回的实际上是 该对象在jvm的堆上的内存地址 ,而不同对象的内存地址肯定不同,所以这个hashCode也就肯定不同了。
  • 如果重载了的话,由于采用的算法的问题,有可能导致两个不同对象的hashCode相同。

1.24 这样的a.hashcode() 有什么用,与a.equals(b)有什么关系。

  • hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。
  • equals() 主要是用于比对两者的对象时候相同,相对于hashCode()是比较简单的。
  • 两者主要在数据的存储方面有联系;
  • equals()的时间复杂度为O(n),而hashCode()判断是否有相同元素的代价,只是一次哈希计算,时间复杂度为O(1),这极大地提高了数据的存储性能。
  • 当hashcode有冲突时,容器就能判断:这个新加入的元素已经存在,需要另作处理:覆盖掉原来的元素(key)或舍弃。
  • 此时就需要再进行一个equals()的比对,只有当equals()也返回true的时候,才会认为元素重复,舍弃存储

1.25 数组和链表数据结构描述,各自的时间复杂度。

1.25.1 各自的特点:

  • 两种数据结构都是线性表,在排序和查找等算法中都有广泛的应用

  • 数组:
    数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。但是如果要在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。同样的道理,如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。如果应用需要快速访问数据,很少或不插入和删除元素,就应该用数组。

  • 链表:
    链表恰好相反,链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起。比如:上一个元素有个指针指到下一个元素,以此类推,直到最后一个元素。如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元素中的指针就可以了。如果应用需要经常插入和删除元素你就需要用链表数据结构了。

1.25.2 数组和链表的区别:

  1. 从逻辑结构角度来看:
    • 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。
    • 链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项)
  2. 数组元素在栈区,链表元素在堆区;
  3. 从内存存储角度来看:
    • (静态)数组从栈中分配空间, 对于程序员方便快速,但自由度小。
    • 链表从堆中分配空间, 自由度大但申请管理比较麻烦。
    • 数组利用下标定位,时间复杂度为O(1),链表定位元素时间复杂度O(n);
    • 数组插入或删除元素的时间复杂度O(n),链表的时间复杂度O(1)。

1.26 error和exception的区别,CheckedException,RuntimeException的区别。

  • 首先Exception和Error都是继承于Throwable 类,在 Java 中只有 Throwable 类型的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型。

  • Exception 是程序正常运行过程中可以预料到的意外情况,并且应该被开发者捕获,进行相应的处理。

  • Error是java程序运行中不可预料的异常情况(正常情况下不大可能出现的情况),这种异常发生以后,会直接导致JVM不可处理或者不可恢复的情况。所以这种异常不可能抓取到,比如OutOfMemoryError、NoClassDefFoundError等。【表示由JVM所侦测到的无法预期的错误,由于这是属于JVM层次的严重错误 ,导致JVM无法继续执行,因此,这是不可捕捉到的,无法采取任何恢复的操作,顶多只能显示错误信息。 Error类体系描述了Java运行系统中的内部错误以及资源耗尽的情形。应用程序不应该抛出这种类型的对象(一般是由虚拟机抛出)。假如出现这种错误,除了尽力使程序安全退出外,在其他方面是无能为力的。】

  • 其中的Exception又分为检查性异常(checked)和非检查性异常(unchecked)(也叫RuntimeException)。

  • 两个根本的区别在于:

    • 检查性异常 必须在编写代码时,使用try catch捕获(比如:IOException异常)。
    • 非检查性异常 在代码编写使,可以忽略捕获操作(比如:ArrayIndexOutOfBoundsException),这种异常是在代码编写或者使用过程中通过规范可以避免发生的,具体根据需要来判断是否需要捕获,并不会在编译器强制要求。

1.27 常见运行时异常 (RuntimeException)

  • NullPointerException (空指针异常)
  • IllegalArgumentException (传递非法参数异常)
  • ClassCastException (类转换异常)
  • IndexOutOfBoundsException (下标越界异常)
  • ArrayIndexOutOfBoundsException (数组越界异常)
  • ArrayStoreException (数据存储异常,操作数组时类型不一致)
  • NumberFormatException (数字格式异常)
  • BufferOverflowException (缓冲区溢出异常)
  • AruthmeticException (算术异常)

1.28 常见非运行时异常 (CheckedException)

  • IOException (IO 操作异常)
  • ClassNotFoundException (找不到指定 class 的异常)
  • FileNotFoundException (文件不存在异常)
  • SQLException (SQL语句异常)
  • InterruptedException (中断异常-调用线程睡眠时候)

1.29 常见错误 (Error)

  • OutOfMemoryError (内存溢出错误)
  • NoClassDefFoundError (找不到 class 定义错误)
  • StackOverflowError (深递归导致栈被耗尽而抛出的错误)

1.30 在自己的代码中,如果创建一个java.lang.String类,这个类是否可以被类加载器加载?为什么?

不能
JVM的四种类加载器
Java使用的是 双亲委托机制 :如果一个类加载器收到了类加载的请求,它首先不会自己尝试去加载这个类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是加此,因此所有的加载请求最终到达顶层的启动类加载器,只有当父类加载器反馈自己无法完成加载请求时(指它的搜索范围没有找到所需的类),子类加载器才会尝试自己去加载。

因此,当你创建一个java.lang.String类时,启动类加载器首先加载的是java.lang.String本类,加载你自己的java.lang.String类时,就会由虚拟机抛出的 java.lang.SecurityException:Prohibited package name:java.lang 异常。

00. 参考链接


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!