个人整理 -Java 后端面试题 - 基础篇

本贴最后更新于 1720 天前,其中的信息可能已经时过境迁
  • 标 ★ 号的知识点为重要知识点

★ 为什么重写 equals 还要重写 hashcode?

如果重写了 equals 的话,可以根据自己的规则进行判定两个对象是否相等。(比如学号姓名一样的学生实体即为相等)但是,如果将实体存进与哈希有关的集合当中时,哈希地址是根据 hashcode 进行计算的。如果没有重写 hashcode 的时候,默认返回的是引用值。这将会导致 Hash 表当中存入两个相同的学生。 所以所有有关 hash 的方法都会出问题。

两个对象值相同(x.equals(y) == true),但却可有不同的 hashCode?

如果重写 equals 但未重写 hashCode 时的话会导致这样的问题。按照规范的话,两对象 equals 相同的话,hashCode 也必须相同。

Object 默认的 hashcode 实现?

Object 类对于 hashcode 并没有具体的实现,调用的是 jvm 底层的 c++ 代码进行计算。该方法返回的是一个 int 数字,底层的 cpp 代码中
一共有六种默认的实现方式。

Object 基类有哪些方法?

类中的方法:clone(),但是使用该方法必须实现 Java.lang.Cloneable 接口, equals()方法判断引用是否一致,指向同一对象,即相等于==,只有覆写了 equals()方法之后, 才可以说不同。hashcode(),对象的地址, toString(), finalize()。

==比较的是什么?

在 java 中==符号比较的是两个对象在内存当中的引用值。

若对一个类不重写,它的 equals()方法是如何比较的?

若不重写 equals 的话,将直接调用基类 Object 的 equals 方法,该方法比较的还是内存当中的引用值。

★java 中的四种引用类型:

  • 强引用:java 默认声明的就是强引用,只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM 也会直接抛出 OutOfMemoryError,不会去回收。
  • 软引用:在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。
  • 弱引用:弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。(常用作集合的 key,避免内存泄露)
  • 虚引用:虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收。

java 的基础类型和字节大小。

一个字节是 8 位
整形数: byte 1 字节 short 2 字节 int 4 字节 long 8 字节
浮点数:float 4 字节 double 8 字节
char:2 字节
boolean:1 字节

Java 支持的数据类型有哪些?什么是自动拆装箱?

  • char
  • byte
  • short
  • boolean
  • long
  • float
  • double
  • int
    自动拆装箱是在代码编译之后,自动进行 int i = Integer.valueOf(i) 的转换操作,
    最早的 1.5JDK 之前没有自动拆装箱,所以当时是需要手动写拆箱装箱。

什么是自动拆装箱?

Integer total = 99;这是自动装箱。int totalprim = total;这是自动拆箱。意思是将一个 int 基本类型放到一个需要 Integer 类型的地方会自动装箱,将一个 Integer 类型的包装类型放到需要 int 的地方会自动拆箱。包装类型当中,有一个类似 字符串常量池的常数常量池,数值大小在-127~128 之间。

int 和 Integer 有什么区别?

  • int 是基本类型,Integer 是 int 的包装类型。
  • int 的默认值是 0,Integer 的默认值 null。
  • Integer 缓存了-128~127 之间的数。

java8 的新特性。

  • lambda 表达式支持函数式编程,简化代码。
  • 接口可有默认方法与静态方法。
  • 方法引用的简便写法。
  • 可重复注解。
  • 扩展注解的使用范围:局部变量、泛型变量、异常。
  • 可获取参数的名字。
  • Optional 对于 Null 优雅的处理。
  • java 集合中的 Stream 的增强处理(类似数据库),以及并行处理。
  • Date/Time API 更好的支持。(duration 特别好用)
  • 内置 javasrcipt 引擎
  • Base64 包
  • JVM 中的永久区被移除,换做元数据区

lambda 表达式的优缺点(待讨论)

  • 优点:
    • 简洁,不再需要匿名内部类。
    • 并行计算在大数据量的情况下会比 for 循环更块
  • 缺点:
    • 普通情况下比 for 循环慢
    • 调试不方便
    • 类型转换要特殊处理

一个十进制的数在内存中是怎么存的?

以二进制补码形式存储,最高位是符号位,正数的补码是它的原码,负数的补码是它的反码加 1,在求反码时符号位不变,符号位为 1,其他位取反

浮点数为什么精度会发生丢失?

2 进制的小数无法精确的表达 10 进制小数,计算机在计算 10 进制小数的过程中要先转换为 2 进制进行计算,这个过程中出现了误差。
例如 0.125 二进制表示为 0.001 但 0.4 转换为二进制的话,计算会出现无限小数。所以会导致精度丢失。

★ 浅析 Java 中的 final 关键字?

修饰方法表示不能重写,修饰类型表示不能继承,修饰变量表示需要初始化并赋值,并且不能重新赋值。
(如果这个值是一个对象,对象内的数据可改变,但此对象的引用不能改变)。

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

值传递代表的是将实参的值拷贝传给形参,形参的改变并不会影响到实参。
引用传递指的是将实参的引用值传给行参,方法内基于引用值找到对象并修改对象中的值是会生效的,但地址值依然是引用值的拷贝,无法生效。

Java 中方法参数的传递规则?

java 中是值传递的方式。意味着方法参数这个变量都不是真正要操作的变量,而是变量的拷贝。引用类型的地址值改变也不会影响原来的变量,但修改引用类型中的值,可以生效,因为修改会直接作用到堆中的 java 对象。

★ 当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?

值传递指的是传递进来的对象的引用值是一份拷贝。(比如将传递进方法的参数对象的引用值设为 null 的话,
在方法外部调用该对象的方法的话,是不会报空指针的。因为传递进来的是引用的拷贝值。)

String 和 StringBuffer 的区别?

  • String 不可变,进行更改字符串的话相当于改了字符串的引用。
  • StringBuffer 可变,修改字符串是基于原来的字符串进行修改,不会创建新的字符串。

★StringBuffer 和 StringBuilder 的区别,从源码角度分析?

StringBuffer 源码中方法加了 synchronized 进行修饰,所以是线程同步的。而 StringBuilder 中的方法没有用
synchronized 进行修饰,所以不是线程同步的。

String,Stringbuffer,StringBuilder 的区别?

String 是字符串常量,编译之后其实是调用 StringBuilder。
StringBuilder 是字符串变量,线程非安全,效率高。在循环拼接字符串中,推荐使用 StringBuider.
StringBuffer 是字符串变量,线程安全,效率低。

如何输出一个某种编码的字符串?

new String(str.getBytes("ISO-8859-1"), "GBK"); 通过添加字符编码进行转换。

★ 什么是字符串常量池?

在 Java 堆中,有一块专门放置字符串的池子不同 JDK 版本不一样,在编译期就存在的字符串将会
直接存入这个池中,或者在不同代码地方的相同的字符串将会直接引用同一个字符串,为什么能
这样引用是因为字符串的不可变性,java 常量池的实现其实是享元模式的思想,可以节省创建的时间,并且节省空间。

★String 方法 intern()的作用是?

  1. 当常量池中不存在"abc"这个字符串的引用,将这个对象的引用加入常量池,返回这个对象的引用。
  2. 当常量池中存在"abc"这个字符串的引用,返回这个对象的引用;

★String 为什么是不可变的?

因为常量池中的字符串会被多个地方引用到,如果字符串是可变的话,更改一个字符串将导致原先引用
该字符串的所有地方都被改变了。这个不可变是指堆中常量池的字符串本身不可变,代码中的引用是可变的。
String 类本身设计出不可变是为了防止客户程序员继承 String 类,造成破坏。

为什么 String 不可变,为什么是 final?

  • 因为 String 类中持有的 value 数组使用 final 进行修饰的,所以它的数组引用就变成不可变的了。
  • 这么做的原因之一是因为 String 有常量池这个概念。比如一个 String a 在常量池中被很多变量
  • 引用了,如果 String 是可变的,当改变了 a 当中的字符,会造成所有引用这个字符变量的地方都跟着改变。
  • 但如果 String 是不可变的话,a 原本的字符不变,将一个新的字符串赋予给 a,这样就不会影响到其他引用之前 a 字符串的地方。

String 能继承吗?

  • 不能,因为 String 类是经过 final 修饰的。
  • 这么做的原因可能是因为 String 是底层的类,用 final 修饰的话,自然而然的方法也会被 final 修饰。因此在调用 String 的任何方法的时候,都采用 JVM 的内嵌机制,效率会有较大的提升。
  • 还有可能是出于安全的考虑,防止继承的子类改写 String 的方法,再将子类以 String 父类的形式进行调用。例如使用 + 号操作符重载?

★String s = new String("xyz");究竟产生了几个对象,从 JVM 角度谈谈?

当 JVM 执行到这一行的时候,会先去常量池中查看是否有 xyz 的字符串,没有的话,会新建这个字符串放入
常量池,如果有的话则不创建。new 操作符将会在对象堆中创建一个字符串对象。所以这个操作可能生成 1
个或者 2 个对象。

★String 拼接字符串效率低,你知道原因吗?

如果是“ab”+"cd"的话,并不会效率低,因为经过编译优化之后会自动变成“abcd”,
但如果是代码中动态拼接,或者循环拼接的话,比如a+b+c+d将会产生a,ab,abc这三个临时的字符串,  
创建不必要的字符串是浪费性能和空间的,所以大部分的String拼接字符串效率会低。
其实+号,编译器转换成StringBuilder的append。有点类似于C++当中的操作符重载。

String 的常见 API?

  • indexOf 检索子串的位置
  • substring 获取子串
  • trim 去除字符串前后空白
  • charAt 获取字符的位置
  • startWith\endWith 检测是否是指定字符串开头或者结尾
  • toUpperCase 转换成大写
  • toLowerCase 转换成小写
  • valueOf 将其他类型转为字符串
  • split 分隔字符串

String.valueOf 和 Integer.toString 的区别?

没有区别。String.valueOf(i),方法内部调用的也是 Integer.toString(i)。

★Java 中的 subString()真的会引起内存泄露么?

假设A字符串很大,B=A.substring,在java1.6版本以前B会持有A字符串类型内置的字符数组,  
AB是共享同一个字符数组,就算A被回收了,但是B还是持有A字符串内很大的字符数组,在Java1.7  
之后修复了这个问题,在substring中,会重新复制一份字符数组给B,性能会比1.6版本差一点,  
但就不会导致内存泄露。假设A="123456789",B="9",但是其实B当中还是持有完整的"123456789"数组

&和&&的区别?

  • 一个是按位与,一个是逻辑与。逻辑与会短路。按位与不会短路。

在 Java 中,如何跳出当前的多重嵌套循环?

  • 定义一个标号。然后在内层循环当中 break 标号;
      ok:

      for (int j = 0; j < 1000; j++) {
          for (int k = 0; k < 1000; k++) {
              System.out.println(k);
              break ok;
          }
      }

你能比较一下 Java 和 JavaSciprt 吗?

  • java 是编译型语言,强类型,面向对象。由 JVM 执行。
  • javascript 是解释性语言,弱类型,一般面向过程。由浏览器引擎执行。

简述正则表达式及其用途。

  • 主要用来匹配出特定规则的字符串。(如电话号码)

Java 中是如何支持正则表达式操作的?

  • String 类中的 split\match\replace 等方法都支持正则。
  • Pattern 类支持正则。

浅析 Java 中的 static 关键字?

修饰变量代表这是类的共享变量,修饰方法的话表示类的静态方法,可在不实例化对象的情况下进行方法调用,修饰代码块的话表示类初始化会调用这些代码,还有一个非常少见的用法就是静态导入,调用方法就不用使用类型.方法名的方式调用,而是直接使用方法名进行调用。

  • static 可以修饰内部类,但是不能修饰普通类。静态内部类的话可以直接调用静态构造器(不用对象)
  • static 修饰方法, static 方法就是没有 this 的方法。在 static 方法内部不能调用非静态方法,反过来是可以的。而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用 static 方法。这实际上正是 static 方法的主要用途。方便在没有创建对象的情况下来进行调用(方法/变量)。
  • static 修饰变量,就变成了静态变量,随类加载一次,可以被多个对象共享。
  • static 修饰代码块,形成静态代码块,用来优化程序性能,将需要加载一次的代码设置成随类加载,静态代码块可以有多个。

★ 你对 Java 中的 volatile 关键字了解多少?

volatile 关键字的作用在于内存可见性(更改变量导致各个线程的变量缓存失效,使其需要从主内存获取新的变量值)、 禁止指令重排序。volatile 关键字并不能保证原子性。适合于修饰状态值,相当于一个轻量级的锁

★i++ 是线程安全的吗?如何解决线程安全性?

i++ 操作包括三步,读取 i,对 i+1,写入 i,所以这并不是原子性操作,并不能保证线程安全。可以对 i 用 volatile 关键字进行修饰在对 i 的加操作用 synchronized 或者 lock 或者 CAS 方式(AtomicInteger)进行操作。

★ 请谈谈什么是 CAS?

Compare And Swap 比较并更换
内存值 预期值 想修改的值 如果内存值等于预期的值那么修改为想修改的值。
底层是调用 cpu 指令集的 cmpxchg 指令来实现。
AtomicInteger 的原理就是使用 volatile 保证可见性,利用 CAS 保证原子性。

★ 从字节码角度深度解析 i++ 和 ++i 线程安全性原理?

i++ 操作包括三步,读取 i,对 i+1,写入 i,所以在各自的本地线程中 i+1 操作可能会丢失
i++ 先将本地变量 i 读取到操作数栈,再进行 ++ 写会本地变量,所以操作数栈里的是原值。
++i 先将本地变量进行 ++,再读取到操作数栈,所以操作数栈里的值是 +1 后的值。

Java 有哪些特性,举个多态的例子。

  • 例如 Animal 类型都有 move 方法,Cat 和 Bird 都集成了 Animal 类。但是 Cat 的 move 是用四肢跑。Bird 的 move 使用翅膀飞。
  • 多态是一种动态方法绑定的机制。

类和对象的区别?

  • 好比猫属于生物中的一个物种(类)。而家里养了一只叫 kitty 的猫,kitty 就是猫这个物种中一个具体并真实存在的对象。

Object 当中的主要方法?

- getClass() : 获取对象所属类型
- hashCode() : 获取对象的
- equals()   : 判断对象是否相等
- toString() : 将对象转换为字符串
- clone()    : 克隆一个对象
- wait()...  : 暂停线程让出锁,进入锁池
- notify()   : 唤醒线程
- notifyAll(): 唤醒所有线程
- finalize() : 在对象回收时会执行的方法

重载和重写的区别?相同参数不同返回值能重载吗?

  • 重载是在同一个类中,方法名一样,但参数列表不一样的静态多态。虚拟机无法根据返回值的不同来判断使用哪个重载方法。
  • 重写是子类重写了父类的方法,方法签名一样,但实现不一样。
  • 重载其实是一种静态多态,在编译成字节码的时候已经完成绑定。
  • 重写其实是一种动态多态,在程序运行期间才完成绑定。

Java 中是否可以覆盖(override)一个 private 或者是 static 的方法?

  • Java 中 static 方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而 static 方法是编译时静态绑定的。
  • 还有私有的方法不能被继承,子类就没有访问权限,肯定也是不能被覆盖。

类加载机制的双亲委派模型,好处是什么?

  • 双亲委派模型是每次收到类加载请求时,先将请求委派给父类加载器完成,如果父类加载器无法完成加载,那么子类尝试自己加载。
  • 双亲委派机制可以避免加载子类自定义的 Object 类、String 类等一些跟 jdk 命名相同的类。使得加载的类都是同一个。这样才安全。

当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的 Class),子类加载器才会尝试自己去加载。

静态变量存在哪里?

  • 存在方法区(永久代,在 1.8 之后是元数据区)当中。

什么是泛型?

  • 泛型是参数化类型。避免为不同类型参数的方法进行多次重载,方便开发。并且编译器会进行泛型的检查,在开发上更安全。

  • 但是泛型信息在运行时其实是擦除的。

  • 简单的说就是类型的参数化,就是可以把类型像方法的参数那样传递。

  • 泛型使编译器可以在编译期间对类型进行检查以提高类型安全,减少运行时由于对象类型不匹配引发的异常。

  • 并且所有的强制转换都是自动和隐式的,提高代码的重用率和性能。

★extends 和 super 泛型限定符的区别?

  • extends 表示的是所需类型是本类或其子类。 表示的是限定上界。
  • super 表示的是所需类型是本类或其父类。 表示的是限定下界。

★ 是否可以在 static 环境中访问非 static 变量?

  • 因为静态的成员属于类,随着类的加载而加载到静态方法区内存,当类加载时,此时不一定有实例创建,没有实例,就不可以访问非静态的成员。

通过反射创建对象的方法?

  • 1.通过类对象的.newInstance 方法。
  • 2.通过类对象的构造器对象的.newInstance 方法。

如何通过反射获取和设置对象私有字段的值?

  • 先通过 getDeclaredFields();方法获取属性列表。
  • 再通过 field.setAccessible(true);打开访问权限。
  • 再通过 field.set(obj, value);将值填入。

★ 反射的实现与作用?

在java中实现反射是通过Class对象,得到该对象后可通过该对象调用相应的方法获取该类中的属性或者方法,给属性赋值或
调用该类中的方法。

反射的用途我个人的理解主要是为了弥补Java静态语言不灵活的缺陷。而实现的一种动态编程的方法。
动态决定调用某些类,属性,方法。

作用如下:
1、java集成开发环境,每当我们敲入点号时,IDE便会根据点号前的内容,动态展示可以访问的字段和方法。
2、java调试器,它能够在调试过程中枚举某一对象所有字段的值。
3、web开发中,我们经常接触到各种配置的通用框架。为保证框架的可扩展性,他往往借助java的反射机制。
例如Spring框架的依赖反转(IOC)便是依赖于反射机制。

一般来说反射是用来做框架的,或者说可以做一些抽象度比较高的底层代码。

java 反射机制。

可以在运行时判断一个对象所属的类,构造一个类的对象,判断类具有的成员变量和方法,调用 1 个对象的方法。
4 个关键的类:Class,Constructor,Field,Method。

  • getConstructor 获得构造函数/getDeclardConstructor;
  • getField/getFields/getDeclardFields 获得类所生命的所有字段;
  • getMethod/getMethods/getDeclardMethod 获得类声明的所有方法,
    正常方法是一个类创建对象,而反射是 1 个对象找到 1 个类。

java 支持多继承吗?

  • java 不支持类的多继承,只支持接口的多继承。

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

  • 抽象类可以有构造方法(可让子类调用),接口中不能有构造方法。
  • 抽象类中可以有普通成员变量,接口中没有普通成员变量。
  • 抽象类中可以包含非抽象普通方法(模板方法模式),接口中的所有方法必须都是抽象的。
  • 抽象类中的抽象方法的访问权限可以是 public、protected 和(默认类型子类不能继承),接口中的抽象方法只能是 public 类型的,并且默认即为 public abstract 类型
  • 抽象类中可以包含静态方法,在 JDK1.8 之前接口中不能不包含静态方法,JDK1.8 以后可以包含。
  • 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问权限可以是任意的,但接口中定义的变量只能是 public static final 类型的,并且默认即为 public static final 类型。
  • 一个类可以实现多个接口,用逗号隔开,但只能继承一个抽象类,接口不可以实现接口,但可以继承接口,并且可以继承多个接口,用逗号隔开。

Comparable 和 Comparator 的区别?

  • 简而言之
  • Comparable 是一个接口比较器。类自己实现了排序规则,可直接调用集合类的 sort
  • Comparator 是一个外部比较器。通过定义一个独立的比较器,在集合进行排序的时候传入一个比较器。
  • 个人偏向于 Comparator 的做法,比较低耦合,可以应用不用的 comparator 进行不用的比较。

面向对象的特征有哪些方面?

  • 抽象、继承、封装、多态

final、finally、finalize 的区别?

  • final 是修饰符。修饰变量、方法、类。
  • finally 是异常处理块当中最后执行的语句。
  • finalize 是在垃圾收集器将对象从内存中清除出去之前调用的方法。

静态内部类和普通内部类的区别?

内部类 :

  • 内部类中的变量和方法不能声明为静态的。(非静态内部类只有在实例化外部类的时候才会加载,如果内部类有静态变量的话,导致可以通过 Out.Inner.static 访问到该内部类的静态变量,但此时内部类还未被加载)
  • 内部类实例化:B 是 A 的内部类,实例化 B:A.B b = new A().new B()。
  • 内部类可以引用外部类的静态或者非静态属性及方法。(因为内部类实例化的话,外部类必然已经实例化)

静态内部类 :

  • 静态内部类属性和方法可以声明为静态的或者非静态的。
  • 实例化静态内部类:B 是 A 的静态内部类,A.B b = new A.B()。
  • 静态内部类只能引用外部类的静态的属性及方法。(因为引用外部类中的属性时,外部类未必实例化了)

内部类可以引用外部类的成员吗?

  • 普通内部类的话可以引用。静态内部类的话只能引用外部类的静态变量。

Java 的接口和 C++ 的虚类的相同和不同处。

  • 相当于接口和抽象类的区别。

JAVA 语言如何进行异常处理,关键字:throws,throw,try,catch,finally 分别代表什么意义?在 try 块中可以抛出异常吗?

  • throw 直接抛出一个异常。
  • throws 声明将要抛出的异常。
  • try 包围可能出现异常的范围。
  • catch 在异常出现时,执行的代码
  • finally 不管异常有没有出现,都会执行的一段代码。
    在 try 块中可以抛出异常。

Java 中 throw 和 throws 的区别是什么?

throw 是 new 一个异常,要么自己捕获,要么声明继续传给调用者。
throws 是声明一个异常但是不处理,将异常的处理交给调用者。

finally 语句块你踩过哪些坑?

比较少,因为关于流的关闭操作都有放在 finally 中进行关闭,return 操作很少会放入 finally 中。
程序最终执行到哪个块就在哪个有 return 的块中进行 return。

  • finally 块一定会执行.
  • finally 前有 return,会先执行 return 语句,并保存下来,再执行 finally 块,最后 return。不会覆盖之前的返回值。
  • finally 前有 return、finally 块中也有 return,先执行前面的 return,保存下来,再执行 finally 的 return,
    覆盖之前的结果,并返回

return 在 finally 中则会覆盖。但尽量不要乱写多个 return。

谈一下面向对象的"六原则一法则"。

  • 单一职责原则
  • 开闭原则
  • 依赖倒置原则
  • 里氏替换原则
  • 接口隔离原则
  • 合成聚合复用原则
  • 迪米特法则

请问 JDK 和 JRE 的区别是什么?

JRE是Java运行时环境。它是运行编译好的Java程序所必需的一切包,包括Java虚拟机(JVM)、Java类库、java命令和其他基础设施。
如果您只关心在计算机上运行Java程序,那么您将只安装JRE。
JDK是Java开发工具包,功能齐全的SDKforJava。它是JRE的超集,包含编译器(javac)和一些开发工具(如javadoc和jdb)。

他们两个的明显区别就是一个用来运行程序。一个用来开发程序(开发程序当然需要能运行程序的环境)。

Java 中的 LongAdder 和 AtomicLong 的区别

LongAdder 类与 AtomicLong 类的区别在于高并发时前者将对单一变量的 CAS 操作分散为对数组 cells 中多个元素的 CAS 操作,取值时进行求和;而在并发较低时仅对 base 变量进行 CAS 操作,与 AtomicLong 类原理相同。

error 和 exception 有什么区别?

  • Error 类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。
    对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。

  • Exception 类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,
    使程序恢复运行,而不应该随意终止异常。

什么是 java 序列化,如何实现 java 序列化?

序列化:把 Java 对象转换为字节序列的过程。
反序列化:把字节序列恢复为 Java 对象的过程。
用途:把对象的字节序列持久化到硬盘上或者在网络上传输。
序列化的实现:将需要被序列化的类实现 Serializable 接口,
该接口没有需要实现的方法。Serializable 只是标注该对象是可被序列化的,
然后使用一个输出流(如:FileOutputStream 或者 ByteArrayOutputStream)来构造一个 ObjectOutputStream(对象流)对象,接着,使用 ObjectOutputStream 对象的 writeObject(Object obj)方法就可以将参数为 obj 的对象写出(即保存其状态),要恢复的话则用输入流。

其好处一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里),
二是,利用序列化实现远程通信,即在网络上传送对象的字节序列。
当序列化 ID 不一致时,会导致序列化失败。

★ 对象克隆和实现方式

克隆的对象可能包含一些已经修改过的属性,而 new1 个对象属性都还是初始化时候的值,被复制克隆的类要实现 Clonable 接口,覆盖 clone()方法,访问修饰符为 public,方法中调用 super.clone()得到所需要的复制方法,类中的属性类也需要实现 Clonable 接口,覆写 clone()方法,并在 super 中也调用子属性类的 clone()复制,才可以实现深拷贝。

或者写到流中序列化的方式来实现,不必考虑引用类型中还包含引用类型,直接用序列化来实现对象的深复制拷贝,即将对象写到流,再从流中读出来,需要实现 seriazation 接口。

反射中,Class.forName 和 classloader 的区别?

区别:

  • Class.forName 除了将类的.class 文件加载到 jvm 中之外,还会对类进行解释,执行类中的 static 块。
  • 而 classloader 只干一件事情,就是将.class 文件加载到 jvm 中,不会执行 static 中的内容,只有在 newInstance 才会去执行 static 块。

ClassLoader 就是遵循双亲委派模型最终调用启动类加载器的类加载器,实现的功能是“通过一个类的全限定名来获取描述此类的二进制字节流”, 获取到二进制流后放到 JVM 中。Class.forName()方法实际上也是调用的 CLassLoader 来实现的。

java 判断对象是否是某个类的类型方法?

  • instanceof 运算符是用来在运行时指出对象是否是特定类的一个实例。instanceof 通过返回一个布尔值来指出,这个对象是否是这个特定类或者是它的子类的一个实例。
  • getClass 判断,如 o.getClass().equals(ClassA.class)。(使用 instanceof 来判断一个对象是不是属于某个类,但是有时候这个类是继承于一个父类的,所以,不能严格判断出是不是自己的类,而不是自己的父类。)

★Java 内存泄露的问题调查定位:jmap,jstack 的使用等等

jstack 观测进程状态,可以查看死锁、阻塞、等待资源的异常进程。
先使用 jps 查看当前运行的 java 进程,然后使用 jstack id 是观测进程内的死锁、阻塞、等待资源等异常情况。
jmap 观测虚拟机内的内存使用情况以及检测频繁 gc,jmap -histo:live 进程号 查看哪些数量异常高的实例进行分析。或者 jmap -dump:format=b,file=d:/heap.hprof 分析内存快照。如果是字符串的话可以分析其内的内容。
一般来说都是业务相关的数据。

Arrays.sort 实现原理和 Collections.sort 实现原理

事实上 Collections.sort 方法底层就是调用的 array.sort 方法,其中涉及 legacyMergeSort(a):归并排序,
ComparableTimSort.sort():Timsort 排序。Timsort 排序是结合了合并排序(merge sort)和插入排序(insertion sort)而得出的排序算法。
核心过程:
TimSort 算法为了减少对升序部分的回溯和对降序部分的性能倒退,将输入按其升序和降序特点进行了分区。排序的输入的单位不是一个个单独的数字,而是一个个的块-分区。其中每一个分区叫一个 run。针对这些 run 序列,每次拿一个 run 出来按规则进行合并。每次合并会将两个 run 合并成一个 run。合并的结果保存到栈中。合并直到消耗掉所有的 run,这时将栈上剩余的 run 合并到只剩一个 run 为止。这时这个仅剩的 run 便是排好序的结果。
(0)如何数组长度小于某个值,直接用二分插入排序算法
(1)找到各个 run,并入栈
(2)按规则合并 run

foreach 和 while 的区别(编译之后)

增强 for 循环只是 java 提供的一个语法糖,在编译之后其实是利用 Iterator 和 While 进行遍历。如果 foreach 中进行增加移出操作之后又紧跟 hasNext()操作会触发异常。这其中涉及到 fail-fast(同步修改失败)和 fail-safe(对副本进行修改)机制。
https://www.cnblogs.com/luyu1993/p/7148765.html
cusor 比 size 大,导致认为还有 next 元素

★cloneable 接口实现原理,浅拷贝 or 深拷贝

cloneable 接口实际上只是标记该类是否可以克隆,具体操作需要重写,一般在重写的方法内调用 clone 方法。如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有真实的创建一个新的对象,则认为是浅拷贝。反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝。
深拷贝有三种方式:

  • Cloneable 接口实现深拷贝,实现 clone 方法,并且在 clone 方法内部,把该对象引用的其他对象也要 clone 一份,这就要求这个被引用的对象必须也要实现 Cloneable 接口并且实现 clone 方法。
  • 序列化实现深拷贝。
  • FastJSON 或者利用反射依次复制值。

讲讲类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,当 new 的时候,他们的执行顺序。

父类静态变量、
父类静态代码块、
子类静态变量、
子类静态代码块、
父类非静态变量(父类实例成员变量)、
父类构造函数、
子类非静态变量(子类实例成员变量)、
子类构造函数。

★ 描述动态代理的几种实现方式,分别说出相应的优缺点。

JDK 动态代理:利用反射机制生成一个实现代理接口的 Proxy 类,在调用具体方法前调用 InvokeHandler 来处理。
CGlib 动态代理:利用 ASM(开源的 Java 字节码编辑库,操作字节码)开源包,将代理对象类的 class 文件加载进来,通过修改其字节码生成子类来处理。

区别:JDK 代理只能对实现接口的类生成代理;CGlib 是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理 final 修饰的类。

JDK 动态代理

1、因为利用 JDKProxy 生成的代理类实现了接口,所以目标类中所有的方法在代理类中都有。
2、生成的代理类的所有的方法都拦截了目标类的所有的方法。而拦截器中 invoke 方法的内容正好就是代理类的各个方法的组成体。
3、利用 JDKProxy 方式必须有接口的存在。
4、invoke 方法中的三个参数可以访问目标类的被调用方法的 API、被调用方法的参数、被调用方法的返回类型。

cglib 动态代理

1、 CGlib 是一个强大的,高性能,高质量的 Code 生成类库。它可以在运行期扩展 Java 类与实现 Java 接口。
2、 用 CGlib 生成代理类是目标类的子类。
3、 用 CGlib 生成 代理类不需要接口
4、 用 CGLib 生成的代理类重写了父类的各个方法。
5、 拦截器中的 intercept 方法内容正好就是代理类中的方法体

CheckedException,RuntimeException 的区别。

Throwable 有两个子类,一个 Error,一个 Exception。直接继承 Exception 的是受检异常,继承 Exception 的子类 RuntimeException 的是非受检异常。

受检异常:编译器强制必须捕获。不是程序本身的错误,只是外部因素如文件读写异常、数据库异常,为了保障程序的健壮性,必须要捕获。属于不可控的异常。但是为了对异常进行抛出、捕获和处理异常需要增加较多代码,会降低代码的可读性。

非受检查异常:不必须捕获,抛给虚拟机。一般是由于程序不严谨导致的错误。比如空指针异常或者算数异常等。这种属于一种程序的 bug,按照道理来讲是程序员编程不当导致的,这种 bug 一旦发生应该消除。

请列出 5 个运行时异常。

ClassCastException(类转换异常)
IndexOutOfBoundsException(数组越界)
NullPointerException(空指针)
ArithmeticException (算数异常,除 0)
NumberFormatException (字符串转数字错误)

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

不会,因为类加载是双亲委托机制,String 类会从顶层类加载器加载,轮不到自定义的加载器加载。

tomcat 结构,类加载器流程

tomcat 的 WebClassLoader 先尝试自己加载,加载不到的话,再交给父类进行双亲委托机制。这个加载器不会加载 java 核心类。

什么情况下会发生栈内存溢出。

栈溢出有两种,一种是 stackoverflow,另一种是 outofmemory,前者一般是因为方法递归没终止条件,后者一般是方法中线程启动过多。

转自我的 github

技术讨论群 QQ:1398880
  • 面试

    面试造航母,上班拧螺丝。多面试,少加班。

    325 引用 • 1395 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • aopstudio

    感谢博主,帮大忙了

  • 其他回帖
  • someone
    作者

    互相学习帮忙!^_^