1.1 Java程序从源代码到运行的过程

  • JVM可以理解的代码就是字节码(.class),面向Java虚拟机,Java语言通过字节码的方式,在一定程度上解决传统解释性语言执行效率低的问题,同时又保留了解释性语言可移植的特性。
  • .class文件到机器码这一步,JVM加载器会首先加载字节码文件,然后通过解释器逐行执行,这种执行速度比较慢,并且有些代码和方法会被经常调用,所以引进了JIT(Just in Time Compilation),属于运行时便意,当JIT完成第一次遍以后,会将对应的机器码保存下来,下次直接使用。所以说Java是编译与解释共存的语言。

1.2 AOT v.s. JIT

AOT(Ahead of Time Compilation),在程序执行前就进行编译,属于静态便意,可以提高Java程序启动速度,避免预热时间过长,减少内存占用,增加程序安全性(AOT遍以后的代码不容易被反编译和修改),适合云原生场景。

但是AOT不支持反射、动态代理、动态加载和JNI(Java Native Interface),所以很多框架和库都无法使用。

1.3 Java v.s. C++

  • Java不提供指针直接访问内存
  • Java类是单继承,C++可以多继承,但是Java的接口可以多继承
  • Java有自动内存管理垃圾回收机制,不需要程序员手动释放无用内存。
  • C++同时支持方法重载和操作符重载,但是Java只支持方法重载。

1.4 基本数据类型

  • byte(8位)、short(16位)、int(32位)、long(64位):默认值0
  • float(32位)、double(64位):默认值0.0
  • char(16位):默认值u0000
  • boolean(1位):默认值false

**包装类型:**包装类型变量不赋值则为null,对于包装类型,==比较的是内存地址,而不是值,所以需要用equals方法比较。占用空间包装类型更大一些,除了定义一些常量和局部变量之外,我们在其他地方比如方法参数、对象属性中很少会使用基本类型来定义变量。并且,包装类型可用于泛型,而基本类型不可以。

包装类型的缓存: 非浮点数缓存范围是[-128, 127],Character缓存范围位[0, 127],Boolean则是true和false。

包装类型的自动拆箱与装箱:

  • 装箱:将基本类型用对应的引用类型包装起来,本质调用包装类的ValutOf方法;
  • 拆箱:将包装类型转换为基本数据类型,本质调用xxxValue方法;

为什么说是几乎所有对象实例都存在于堆中呢? 这是因为 HotSpot 虚拟机引入了 JIT 优化之后,会对对象进行逃逸分析,如果发现某一个对象并没有逃逸到方法外部,那么就可能通过标量替换来实现栈上分配,而避免堆上分配内存。

**浮点数运算精度丢失:**使用BigDecimal进行浮点运算。

**超过Long整数类型的数据如何表示:**使用BigInteger进行表示,内部使用int[]数组进行存储任意大小的整形数据。

1.5 深拷贝和浅拷贝

  • 浅拷贝:在堆上创建一个新的对象,但是原对象内部属性如果是引用类型,则浅拷贝会直接复制原对象内部的引用地址。
  • 深拷贝:完全复制整个对象。
  • 引用拷贝:复制一个引用,指向原对象。

1.5 Objects

1.5.1 ==和equals方法

  • ==:如果是对象,则比较地址是否相同,基本类型则比较值。
  • object.equals():没有重写的情况下,效果与==相同,String类型中的equals方法被重写过,会比较内部值是否相同。

1.5.2 hashCode()有什么用

获取int类型哈希码,hashCode相同时,两个对象不一定相等(哈希冲突),如果两个对象hashCode相等,并且equals返回true,才认为两个对象相等,如果两个对象hashCode不相等,则直接认为两个对象不相等。

**为什么重写equals()时必须重写hashCode()?**因为两个相等的对象的 hashCode 值必须是相等。也就是说如果 equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。

1.6 String

1.6.1 String、StringBuffer、StringBuilder区别

  • String是不可变的,线程安全,但是效率低。
  • StringBuffer是线程安全的,内部加了同步锁。
  • StringBuilder不是线程安全的,内部没有使用同步锁。

String内部使用final关键字修饰数组保存字符串,所以是不可变的,String使用+运算符连接字符串,底层会生成一个StringBuilder进行连接,但是不会进行复用,会导致创建过多的StringBuilder。

1.6.2 字符串常量池

String s1 = new String(“abc”); 会创建几个字符串对象?

  • 2个:字符串常量池不存在这个字符串对象“abc”的引用,那么会在堆上创建两个字符串对象,其中一个字符串对象的引用会被保存在字符串常量池中。
  • 1个:如果字符串常量池存在这个字符串对象的引用,那么只在堆中创建一个字符串对象,并引用到常量池中的字符串对象。

1.6.3 String#intern方法有什么用

String.intern()是一个native方法,用于将指定的字符串对象保存在字符串常量池中:

  • 如果字符串常量池已经保存了对应的字符串对象的引用,就直接返回该引用;
  • 否则,创建一个指向该字符串对象的引用,并返回。

1.7 异常

Java中所有的异常都来自一个共同的祖先java.lang包中的Throwable类。

不要在 finally 语句块中使用 return! 当 try 语句和 finally 语句中都有 return 语句时,try 语句块中的 return 语句会被忽略。这是因为 try 语句中的 return 返回值会先被暂存在一个本地变量中,当执行到 finally 语句中的 return 之后,这个本地变量的值就变为了 finally 语句中的 return 返回值。

1.7.1 Exception和Error有什么区别?

  • Excpetion:程序本身可以处理的异常,可以通过catch捕获,又分为Checked Excpetion和Unchecked Exception。
  • Error:程序无法处理的异常。包括JVM运行错误,OOM,类定义错误等。

1.7.2 Checked Excpetion和Unchecked Exception有什么区别?

Checked Exception,受检异常,Java代码在编译过程中,如果发现该异常没有被catch或者throw,则无法通过编译。

除了RuntimeException及其子类外,其他Exception都属于受检异常:如IO相关异常,ClassNotFoundException,SQLException等

Unchecked Exception,不受检异常,Java代码在编译过程中,即使不处理也可以正常通过编译的异常:如NPE,IllegalArgumentException、NumberFormatException、ArrayIndexOutOfBoundsException等。均为RuntimeException的子类。

1.7.3 finally中的代码一定会被执行吗?

不一定,线程死亡,JVM终止,电脑宕机都不会执行。

1.7.4 使用try-with-resources代替try-catch-finally来使用需要关闭的资源类。