微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

【面试准备】Java基础知识

前言

技术面试这样东西就像是考试,它不一定就真的是你在项目开发中一直会用到的东西,但它绝对会从侧面反映出你的技术水平,面试准备是不可轻视的,准备与不准备之间会差很多。

但是它不是把不会的包装成会的,而是把那些在运用的知识更加系统化地表达出来,这是面试准备最重要的目的。

本篇内容主要整理自极客时间-杨晓峰-Java核心技术面试精讲

目录

1 谈谈你对Java平台的理解

典型回答

Java是面向对象的语言,它有两个非常重要的特性,一是 Write Once Run AnyWhere,即编写一次随处运行的跨平台特性;二是垃圾回收机制,大部分情况下,程序员不需要自己操心内存的分配和回收。

我们日常会接触到 JRE(Java Runtime Environment)或者 JDK(Java Development Kit)。 JRE,也就是 Java 运行环境,包含了 JVM 和 Java 类库,以及一些模块等。而 JDK 可以看作是 JRE 的一个超集,提供了更多工具,比如编译器、各种诊断工具等。

Java还有非常丰富的生态,比如JavaEE,Spring,Hadoop,Spark等。

问题分析

一般这类问题属于开放型的问题,通常面试官都是作为暖场问题来提问。对于这个问题重要的就是把下面这张图用自己的语言表述出来:

在这里插入图片描述

需要知道下面几点:

  • Java的生态
  • Java语言的基本特性
  • Java的类库
  • Java虚拟机的核心机制
  • JDK所带的和其他一些Java工具

2 Java是解释执行的吗

典型回答

这个说法不太准确。我们开发的 Java 的源代码,首先通过 Javac 编译成为字节码(bytecode),然后,在运行时,通过 Java 虚拟机(JVM)内嵌的解释器将字节码转换成为最终的机器码。

但是常见的 JVM,比如我们大多数情况使用的 Oracle JDK 提供的 Hotspot JVM,都提供了 JIT(Just-In-Time)编译器,也就是通常所说的动态编译器,JIT 能够在运行时将热点代码编译成机器码,这种情况下部分热点代码就属于编译执行,而不是解释执行了。

问题分析

我们通常把 Java 分为编译期和运行时。这里说的 Java 的编译和 C/C++ 是有着不同的意义的,Javac 的编译,编译 Java 源码生成 .class 文件里面实际是字节码,而不是可以直接执行的机器码。Java 通过字节码和 Java 虚拟机(JVM)这种跨平台的抽象,屏蔽了操作系统和硬件的细节,这也是实现 “一次编译,到处执行” 的基础。

JVM 会通过类加载器(Class-Loader)加载字节码,解释或者编译执行。就像前面提到的,主流 Java 版本中,如 JDK 8 实际是解释和编译混合的一种模式,即所谓的混合模式 -Xmixed

Java 虚拟机启动时,可以指定不同的参数对运行模式进行选择。 比如,指定 -Xint,就是告诉 JVM 只进行解释执行,不对代码进行编译,这种模式抛弃了 JIT 可能带来的性能优势。毕竟解释器(interpreter)是逐条读入,逐条解释运行的。与其相对应的,还有一个 -Xcomp 参数,这是告诉 JVM 关闭解释器,不要进行解释执行,或者叫作最大优化级别。但是 -Xcomp 会导致 JVM 启动变慢非常多,同时有些 JIT 编译器优化方式,比如分支预测,如果不进行 profiling,往往并不能进行有效优化。

更多的是JVM的内容,这里点到为止了,感觉把这个问题说清楚就可以了,如果你比较了解JVM可以答得更多,也可以引导面试官往这方面问,因为JVM绝对是面试里面的加分项,如果你不怎么了解JVM那么到这里我觉得也足够了。

3 Exception和Error有什么区别,运行时异常与一般异常有什么区别

典型回答

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

Exception 和 Error 体现了 Java 平台设计者对不同异常情况的分类。Exception 是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。

Error 是指在正常情况下,不大可能出现的情况,绝大部分的 Error 都会导致程序(比如 JVM 自身)处于非正常的、不可恢复状态。既然是非正常情况,所以不便于也不需要捕获,常见的比如 OutOfMemoryError 之类,都是 Error 的子类。

Exception 又分为可检查(checked)异常和不检查(unchecked)异常,可检查异常在源代码里必须显式地进行捕获处理,这是编译期检查的一部分。

不检查异常就是所谓的运行时异常,类似 NullPointerException、Arrayindexoutofboundsexception 之类,通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译期强制要求。

问题分析

上面的回答还都是概念层面的,但是异常处理到编码上还是比较考验功底的。

一、理解 Throwable、Exception、Error 的设计和分类,下图的一些典型类要做到心里有数:

在这里插入图片描述

LinkageError为Java编译期Error,与VirtualMachineError的运行期Error相对应

二、理解 Java 语言中操作 Throwable 的元素和实践

掌握最基本的语法是必须的,如 try-catch-finally 块,throw、throws,异常处理这块我觉得到web部分的全局异常处理怎么实践更有实战意义一点。

三、异常处理的原则

  1. 尽量不要捕获 Exception 这样的通用异常,而是捕获特定的异常。这样做主要有两点,一是代码要具有可读性,它是一项协作的工作;二是RuntimeException更应该被扩散出来,而不是捕获。
  2. 不要生吞异常

try {
   // 业务代码
} catch (IOException e) {
    e.printstacktrace();
}

如果它作为一段实验性质的代码查查错什么的没什么问题,但是在生产环境中错误信息应该被输出到指定的日志中,标准输出(一般是打印到控制台)不是一个好地方。

  1. Throw early, catch late

public void readPreferences(String fileName){
   //...perform operations... 
  InputStream in = new FileInputStream(fileName);
   //...read the preferences file...
}


public void readPreferences(String filename) {
  Objects. requireNonNull(filename);
  //...perform other operations... 
  InputStream in = new FileInputStream(filename);
   //...read the preferences file...
}

如果 fileName 是 null,那么程序就会抛出 NullPointerException,但是由于没有第一时间暴露出问题,堆栈信息可能非常令人费解,往往需要相对复杂的定位。在发现问题的时候,第一时间抛出,能够更加清晰地反映问题,就像第二段代码那样操作。这就是 Throw early

关于这里的处理方式我一直都有疑惑,对于fileName是否会是Null我们是否可以断言呢?拿一个web项目举例,我们肯定都是分层的,如果每个调用环节都这样操作是不是会非常繁琐,如果Controller处做了Validation,那么在Service还需不需要去做Null的判断呢?

至于 catch late,捕获异常之后如何处理,最差的就是生吞,如果实在不知道如何处理,可以选择保留原有异常的 cause 信息,直接再抛出或者构建新的异常抛出去。

如果我们有清晰的业务逻辑,通常我们会自定义异常,让它与全局的架构相整合,除了保证提供充足的信息,还有两点需要考虑:

  1. 是否定义成 Checked Exception,这种类型设计的初衷更是为了从异常情况恢复,作为异常设计者,我们往往有充足信息进行分类
  2. 考虑避免敏感信息,避免潜在的安全问题,比如不会打印用户的隐私信息

四、Java对于异常处理的开销

  • try-catch 代码段会产生额外的性能开销,或者换个角度说,它往往会影响 JVM 对代码进行优化。所以包裹段应尽量少,不要用异常去进行流程控制
  • Java 每实例化一个 Exception,都会对当时的栈进行快照,这是一个相对比较重的操作。如果发生的非常频繁,这个开销可就不能被忽略了。当我们的服务出现反应变慢、吞吐量下降的时候,检查发生最频繁的 Exception 也是一种思路。

其他内容

关于 Checked Exception的争论:

http://literatejava.com/exceptions/checked-exceptions-javas-biggest-mistake/

https://v.qq.com/x/page/d0635rf5x0o.html

3.5 NoClassDefFoundError 和 ClassNotFoundException 有什么区别

承接上文的 Error与Exception的话题,这个问题属于一个入门问题。

首先它们当然是 Error与Exception的区别,更关键的是它们发生的原因:

ClassNotFoundException发生的原因

Java支持使用反射方式在运行时动态加载类,例如使用Class.forName方法来动态地加载类时,可以将类名作为参数传递给上述方法从而将指定类加载到JVM内存中,如果这个类在类路径中没有被找到,那么此时就会在运行时抛出ClassNotFoundException异常。

解决该问题需要确保所需的类连同它依赖的包存在于类路径中,常见问题在于类名书写错误

另外还有一个导致ClassNotFoundException的原因就是:当一个类已经被某个类加载器加载到内存中了,此时另一个类加载器又尝试着动态地从同一个包中加载这个类。

通过控制动态类加载过程,可以避免上述情况发生。

NoClassDefFoundError发生的原因

如果JVM或者ClassLoader实例尝试加载(可以通过正常的方法调用,也可能是使用new来创建新的对象)类的时候却找不到类的定义。要查找的类在编译的时候是存在的,运行的时候却找不到了。这个时候就会导致NoClassDefFoundError.

造成该问题的原因可能是打包过程漏掉了部分类,或者jar包出现损坏或者篡改。解决这个问题的办法是查找那些在开发期间存在于类路径下但在运行期间却不在类路径下的类。

如果你用过Maven来开发项目,那么因为依赖冲突导致的 NoClassDefFoundError肯定不会陌生。

4 谈谈 final、finally、 finalize 有什么不同?

持续更新中…

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐