Java 基础常见知识点&面试题总结,2022 最新版
原文地址:https://mp.weixin.qq.com/s/PQA_sB5J2nK05ilKUDz0mQ侵权联系删除
两者的主要区别在于解决问题的方式不同:
- 面向过程把解决问题的过程拆成一个个方法,通过一个个方法的执行解决问题。
- 面向对象会先抽象出对象,然后用对象执行方法的方式解决问题。
另外,面向对象开发的程序一般更易维护、易复用、易扩展。
相关 issue : 面向过程 :面向过程性能比面向对象高??
- 语法形式 :从语法形式上看,成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。
- 存储方式 :从变量在内存中的存储方式来看,如果成员变量是使用 static 修饰的,那么这个成员变量是属于类的,如果没有使用 static 修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。
- 生存时间 :从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动生成,随着方法的调用结束而消亡。
- 默认值 :从变量是否有默认值来看,成员变量如果没有被赋初始值,则会自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。
new 运算符,new 创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。
一个对象引用可以指向 0 个或 1 个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有 n 个引用指向它(可以用 n 条绳子系住一个气球)。
- 对象的相等一般比较的是内存中存放的内容是否相等。
- 引用相等一般比较的是他们指向的内存地址是否相等。
构造方法是一种特殊的方法,主要作用是完成对象的初始化工作。
如果一个类没有声明构造方法,也可以执行!因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。如果我们自己添加了类的构造方法(无论是否有参),Java 就不会再添加默认的无参数的构造方法了,我们一直在不知不觉地使用构造方法,这也是为什么我们在创建对象的时候后面要加一个括号(因为要调用无参的构造方法)。如果我们重载了有参的构造方法,记得都要把无参的构造方法也写出来(无论是否用到),因为这可以帮助我们在创建对象的时候少踩坑。
构造方法特点如下:
- 名字与类名相同。
- 没有返回值,但不能用 void 声明构造函数。
- 生成类的对象时自动执行,无需调用。
构造方法不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。
封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。就好像我们看不到挂在墙上的空调的内部的零件信息(也就是属性),但是可以通过遥控器(方法)来控制空调。如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。就好像如果没有空调遥控器,那么我们就无法操控空凋制冷,空调本身就没有意义了(当然现在还有很多其他方法 ,这里只是为了举例子)。
不同类型的对象,相互之间经常有一定数量的共同点。例如,小明同学、小红同学、小李同学,都共享学生的特性(班级、学号等)。同时,每一个对象还定义了额外的特性使得他们与众不同。例如小明的数学比较好,小红的性格惹人喜爱;小李的力气比较大。继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。
关于继承如下 3 点请记住:
- 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。
- 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。(以后介绍)。
多态,顾名思义,表示一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。
多态的特点:
- 对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
- 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
- 多态不能调用“只在子类存在但在父类不存在”的方法;
- 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。
共同点 :
- 都不能被实例化。
- 都可以包含抽象方法。
- 都可以有默认实现的方法(Java 8 可以用 default 关键字在接口中定义默认方法)。
区别 :
- 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系(比如说我们抽象了一个发送短信的抽象类,)。
- 一个类只能继承一个类,但是可以实现多个接口。
- 接口中的成员变量只能是 public static final 类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。
关于深拷贝和浅拷贝区别,我这里先给结论:
- 浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
- 深拷贝 :深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。
上面的结论没有完全理解的话也没关系,我们来看一个具体的案例!
浅拷贝
浅拷贝的示例代码如下,我们这里实现了 Cloneable 接口,并重写了 clone() 方法。
clone() 方法的实现很简单,直接调用的是父类 Object 的 clone() 方法。
测试 :
从输出结构就可以看出, person1 的克隆对象和 person1 使用的仍然是同一个 Address 对象。
深拷贝
这里我们简单对 Person 类的 clone() 方法进行修改,连带着要把 Person 对象内部的 Address 对象一起复制。
测试 :
从输出结构就可以看出,虽然 person1 的克隆对象和 person1 包含的 Address 对象已经是不同的了。
那什么是引用拷贝呢? 简单来说,引用拷贝就是两个不同的引用指向同一个对象。
我专门画了一张图来描述浅拷贝、深拷贝、引用拷贝:
Object 类是一个特殊的类,是所有类的父类。它主要提供了以下 11 个方法:
== 对于基本类型和引用类型的作用效果是不同的:
- 对于基本数据类型来说,== 比较的是值。
- 对于引用数据类型来说,== 比较的是对象的内存地址。
因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。
equals() 不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals()方法存在于Object类中,而Object类是所有类的直接或间接父类,因此所有的类都有equals()方法。
Object 类 equals() 方法:
equals() 方法存在两种使用情况:
- 类没有重写 equals()方法 :通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Object类equals()方法。
- 类重写了 equals()方法 :一般我们都重写 equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。
举个例子(这里只是为了举例。实际上,你按照下面这种写法的话,像 IDEA 这种比较智能的 IDE 都会提示你将 == 换成 equals() ):
String 中的 equals 方法是被重写过的,因为 Object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。
当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。
String类equals()方法:
hashCode() 的作用是获取哈希码(int 整数),也称为散列码。这个哈希码的作用是确定该对象在哈希表中的索引位置。
hashCode()定义在 JDK 的 Object 类中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。另外需要注意的是:Object 的 hashCode() 方法是本地方法,也就是用 C 语言或 C++ 实现的,该方法通常用来将对象的内存地址转换为整数之后返回。
散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode?
下面这段内容摘自我的 Java 启蒙书《Head First Java》:
当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashCode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashCode 值作比较,如果没有相符的 hashCode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashCode 值的对象,这时会调用 equals() 方法来检查 hashCode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
其实, hashCode() 和 equals()都是用于比较两个对象是否相等。
那为什么 JDK 还要同时提供这两个方法呢?
这是因为在一些容器(比如 HashMap、HashSet)中,有了 hashCode() 之后,判断元素是否在对应容器中的效率会更高(参考添加元素进HashSet的过程)!
我们在前面也提到了添加元素进HashSet的过程,如果 HashSet 在对比的时候,同样的 hashCode 有多个对象,它会继续使用 equals() 来判断是否真的相同。也就是说hashCode 帮助我们大大缩小了查找成本。
那为什么不只提供 hashCode() 方法呢?
这是因为两个对象的hashCode 值相等并不代表两个对象就相等。
那为什么两个对象有相同的 hashCode 值,它们也不一定是相等的?
因为 hashCode() 所使用的哈希算法也许刚好会让多个对象传回相同的哈希值。越糟糕的哈希算法越容易碰撞,但这也与数据值域分布的特性有关(所谓哈希碰撞也就是指的是不同的对象得到相同的 hashCode )。
总结下来就是 :
- 如果两个对象的hashCode 值相等,那这两个对象不一定相等(哈希碰撞)。
- 如果两个对象的hashCode 值相等并且equals()方法也返回 true,我们才认为这两个对象相等。
- 如果两个对象的hashCode 值不相等,我们就可以直接认为这两个对象不相等。
相信大家看了我前面对 hashCode() 和 equals() 的介绍之后,下面这个问题已经难不倒你们了。
因为两个相等的对象的 hashCode 值必须是相等。也就是说如果 equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。
如果重写 equals() 时没有重写 hashCode() 方法的话就可能会导致 equals 方法判断是相等的两个对象,hashCode 值却不相等。
思考 :重写 equals() 时没有重写 hashCode() 方法的话,使用 HashMap 可能会出现什么问题。
总结 :
- equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。
- 两个对象有相同的 hashCode 值,他们也不一定是相等的(哈希碰撞)。
更多关于 hashCode() 和 equals() 的内容可以查看:Java hashCode() 和 equals()的若干问题解答
可变性
String 是不可变的(后面会详细分析原因)。
StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串,不过没有使用 final 和 private 关键字修饰,最关键的是这个 AbstractStringBuilder 类还提供了很多修改字符串的方法比如 append 方法。
线程安全性
String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
性能
每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
对于三者使用的总结:
- 操作少量的数据: 适用 String
- 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
- 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer
String 类中使用 final 关键字修饰字符数组来保存字符串,所以String 对象是不可变的。
修正 :我们知道被 final 关键字修饰的类不能被继承,修饰的方法不能被重写,修饰的变量是基本数据类型则值不能改变,修饰的变量是引用类型则不能再指向其他对象。因此,final 关键字修饰的数组保存字符串并不是 String 不可变的根本原因,因为这个数组保存的字符串是可变的(final 修饰引用类型变量的情况)。
String 真正不可变有下面几点原因:
保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。
String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。
相关阅读:如何理解 String 类型值的不可变?- 知乎提问
补充(来自issue 675):在 Java 9 之后,String 、StringBuilder 与 StringBuffer 的实现改用 byte 数组存储字符串。
public final class String implements java.io.Serializable,Comparable<String>, CharSequence {// @Stable 注解表示变量最多被修改一次,称为“稳定的”。@Stableprivate final byte[] value;}abstract class AbstractStringBuilder implements Appendable, CharSequence {byte[] value;}
Java 9 为何要将 String 的底层实现由 char[] 改成了 byte[] ?
新版的 String 其实支持两个编码方案:Latin-1 和 UTF-16。如果字符串中包含的汉字没有超过 Latin-1 可表示范围内的字符,那就会使用 Latin-1 作为编码方案。Latin-1 编码方案下,byte 占一个字节(8 位),char 占用 2 个字节(16),byte 相较 char 节省一半的内存空间。
JDK 官方就说了绝大部分字符串对象只包含 Latin-1 可表示的字符。
如果字符串中包含的汉字超过 Latin-1 可表示范围内的字符,byte 和 char 所占用的空间是一样的。
这是官方的介绍:https://openjdk.java.net/jeps/254 。
Java 语言本身并不支持运算符重载,“+”和“+=”是专门为 String 类重载过的运算符,也是 Java 中仅有的两个重载过的元素符。
上面的代码对应的字节码如下:
可以看出,字符串对象通过“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。
不过,在循环内使用“+”进行字符串的拼接的话,存在比较明显的缺陷:编译器不会创建单个 StringBuilder 以复用,会导致创建过多的 StringBuilder 对象。
StringBuilder 对象是在循环内部被创建的,这意味着每循环一次就会创建一个 StringBuilder 对象。
如果直接使用 StringBuilder 对象进行字符串拼接的话,就不会存在这个问题了。
如果你使用 IDEA 的话,IDEA 自带的代码检查机制也会提示你修改代码。
String 中的 equals 方法是被重写过的,比较的是 String 字符串的值是否相等。Object 的 equals 方法是比较的对象的内存地址。
字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
更多关于字符串常量池的介绍可以看一下 Java 内存区域详解 这篇文章。
会创建 1 或 2 个字符串对象。
1、如果字符串常量池中不存在字符串对象“abc”的引用,那么会在堆中创建 2 个字符串对象“abc”。
示例代码(JDK 1.8):
对应的字节码:
ldc 命令用于判断字符串常量池中是否保存了对应的字符串对象的引用,如果保存了的话直接返回,如果没有保存的话,会在堆中创建对应的字符串对象并将该字符串对象的引用保存到字符串常量池中。
2、如果字符串常量池中已存在字符串对象“abc”的引用,则只会在堆中创建 1 个字符串对象“abc”。
示例代码(JDK 1.8):
对应的字节码:
这里就不对上面的字节码进行详细注释了,7 这个位置的 ldc 命令不会在堆中创建新的字符串对象“abc”,这是因为 0 这个位置已经执行了一次 ldc 命令,已经在堆中创建过一次字符串对象“abc”了。7 这个位置执行 ldc 命令会直接返回字符串常量池中字符串对象“abc”对应的引用。
String.intern() 是一个 native(本地)方法,其作用是将指定的字符串对象的引用保存在字符串常量池中,可以简单分为两种情况:
- 如果字符串常量池中保存了对应的字符串对象的引用,就直接返回该引用。
- 如果字符串常量池中没有保存了对应的字符串对象的引用,那就在常量池中创建一个指向该字符串对象的引用并返回。
示例代码(JDK 1.8) :
先来看字符串不加 final 关键字拼接的情况(JDK1.8):
注意 :比较 String 字符串的值是否相等,可以使用 equals() 方法。String 中的 equals 方法是被重写过的。Object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是字符串的值是否相等。如果你使用 == 比较两个字符串是否相等的话,IDEA 还是提示你使用 equals() 方法替换。
对于编译期可以确定值的字符串,也就是常量字符串 ,jvm 会将其存入字符串常量池。并且,字符串常量拼接得到的字符串常量在编译阶段就已经被存放字符串常量池,这个得益于编译器的优化。
在编译过程中,Javac 编译器(下文中统称为编译器)会进行一个叫做 常量折叠(Constant Folding) 的代码优化。《深入理解 Java 虚拟机》中是也有介绍到:
常量折叠会把常量表达式的值求出来作为常量嵌在最终生成的代码中,这是 Javac 编译器会对源代码做的极少量优化措施之一(代码优化几乎都在即时编译器中进行)。
对于 String str3 = \”str\” + \”ing\”; 编译器会给你优化成 String str3 = \”string\”;。
并不是所有的常量都会进行折叠,只有编译器在程序编译期就可以确定值的常量才可以:
- 基本数据类型( byte、boolean、short、char、int、float、long、double)以及字符串常量。
- final 修饰的基本数据类型和字符串变量
- 字符串通过 “+”拼接得到的字符串、基本数据类型之间算数运算(加减乘除)、基本数据类型的位运算(<<、>>、>>> )
引用的值在程序编译期是无法确定的,编译器无法对其进行优化。
对象引用和“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。
我们在平时写代码的时候,尽量避免多个字符串对象拼接,因为这样会重新创建对象。如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer。
不过,字符串使用 final 关键字声明之后,可以让编译器当做常量来处理。
示例代码:
被 final 关键字修改之后的 String 会被编译器当做常量来处理,编译器在程序编译期就可以确定它的值,其效果就相当于访问常量。
如果 ,编译器在运行时才能知道其确切值的话,就无法对其优化。
示例代码(str2 在运行时才能确定其值):
8年经验面试官详解 Java 面试秘诀
作者 | 胡书敏
责编 | 刘静
出品 | CSDN(CSDNnews)
本人目前在一家知名外企担任架构师,而且最近八年来,在多家外企和互联网公司担任Java技术面试官,前后累计面试了有两三百位候选人。在本文里,就将本人的面试经验,针对Java初学者、Java初级开发和Java开发,给出若干准备简历和准备面试的。
Java程序员准备和投递简历的实战技巧
1.1 简历中应包含的要素,一个都别落下
为了让简历更吸引技术面试官或其它相关筛选简历的人,大家在准备简历应当注意“直接”两字:能让筛选人能直接地看出本人的教育背景、工作经历和项目经理,并让他们“直接”感到这份简历能纳入考虑范围。
根据这个原则,大家可以按次序在简历中列出如下表所给出的要素。
1.2 该如何描述公司的工作情况
这部分一般是按时间倒叙描述,比如可以按如下的格式写:
2015年11月到2017年10月,在xx公司,职务是Java高级开发。离职理由是想进一步发展。
2012年2月到2015年11月,在xx公司,职务是Java初级开发。离职理由是想进一步发展。
按此格式写之前的公司情况
这部分的内容应当尽量靠前,在罗列公司情况时,请大家注意如下的四个要点。
第一,工作情况可以和项目经验分开写,一般会在后继的项目经验里写具体用到的技术框架以及所做过项目的细节,在这里的工作情况描述里,可以不用过于复杂,让招聘方看到你之前的公司情况即可。
第二,尽量别出现长时间的“空白期”,比如上份工作是2月份结束的,而下份工作是6月开始的。如果出现持续三个月以上的“不在职状态”,需要在简历中说明情况,比如这段时间你是换城市发展了,或辞职复习考研或复习考公务员,总之得找个能说得过去的理由。
第三,在简历上,尽量别让人感觉你每份工作都做不长,但不能以此作假。比如我见过有候选人会合并公司,比如2016年11月到2017年3月在A公司,2017年4月到10月在B公司,他为了不让招聘方感觉他换工作太频繁,在简历上就写2016年11月到2017年10月在B公司工作,而故意合并了A公司的经历。这样的话,如果遇到背景调查,会露馅,即使有些公司不做调查,在劳动手册等材料上也能反应出真实的工作情况,所以这种做法有一定的风险性。
这里推荐的做法是,不要合并公司,但可以写明理由,比如当时小王是被外派公司A以人力派遣的形式外派到B公司,但没过多久A公司因某种原因不再具备人力派遣的资质了,这时小王就不得不终止与A公司的合同转而和B公司签约,这样虽然看上去小王是换了公司,但实际上没有。通过类似的合理解释,招聘方就不再会质疑小王的工作能力和稳定性了。
第四,可以写上合适的离职理由,尤其当你短时间里换工作比较多,可能引起招聘方的质疑的情况里,更该考虑些合适的理由。
合理的离职理由可以是,想为自己提供一个更大的发展空间,或想通过升级来独当一面,以此进一步提升自己的能力,或公司因资金等方面的原因倒闭了。总之,这不是我主观上不稳定,而是由于客观原因导致我不得不换工作。
而可能会导致没面试机会的离职原因是,待遇问题(虽然大家心知肚明,但不能这样写),或无法承受大压力,或同事领导排挤。这类理由往往会暴露出候选人的缺点,所以不大家采用。从这意义上来讲,“合同期满”也不是一个好的离职原因,因为如果候选人能力强,那么为什么原公司不和你续约呢?
总之,在描述公司情况时,一旦出现会让招聘方感觉你能力不强或不稳定时,一定得醒目地写上足以信服的理由,这样你的简历才会有机会被继续被读下去,进而你才会有技术面试的机会。
1.3 尽量把学习培训项目和毕业设计项目往商业项目上靠
商业项目是指能挣钱的项目,和它对应的就是些不以挣钱为目的的学习项目或毕业设计项目。正因为客户付了钱,所以商业项目的要求要远远高于学习或毕业设计项目,这也是为什么招聘公司会看重商业项目而会主动过滤学习项目的原因。
比如小张在大三时帮计算机系的王老师所在的ABC软件公司干了半年的活,如果小张在简历上写:“在校期间,从x年x月到x年x月完成了xx系统,用到了xx技术”,那么这多半会被当成类似于课程设计的学习经验,但如果再如下关键性的描述:“这个系统是属于xx公司的xx商业项目里的一部分,我和另外三位开发人员做了半年,最终这个系统成功上线并在客户xx公司的环境里投入运营”,那这样小张的商业项目总年限里就能这半年时间了。
又如小李在做毕业设计时,花了7个月的时间参与了导师的一个电商商业项目,他主要的工作是设计一个调度算法,但也参与了一些诸如订单管理模块的工作。如果他就平淡地写一句,毕业设计是xx,毕业论文是xx,那么招聘方看过就算了,也不会认为小李在做毕业设计时还有过商业项目经验,这样小李未免有些吃亏。
但如果这样写:“在x年x月到x年x月的7个月里,在毕业设计中,我参与了xx公司的xx电商项目,客户方是x,我参与了订单管理和xx模块,并设计了其中的调度算法,在我的毕业论文里,详细介绍了这种做法”。文字没修改太多,但足以让小李增加7个月的商业项目经验。
我们发现大多数初级程序员的水平其实也差不多,这时就得看谁的商业项目经验丰富了。比如有次我们无法从两位候选人中权衡,因为他们的综合条件和面试情况都差不多,但其中有一位在大三阶段有段为期6个月的商业项目实习经验,另一位没有(也有可能他也有但没当成商业项目来写),这种情况下我们就录用了有实习经验的候选人了。
1.4 描述项目的技巧
我们可以根据职位需求,从如下几个方面来描述项目经验。
第一,简要描述项目的背景,比如时间范围,客户是谁,项目规模有多大。如下是范例。
从x年x月到现在(这个时间范围至少是最近半年),我参与某外汇交易系统,客户是xx银行,这个项目组的构成是,1位项目经理外加10位开发,总共的规模大概在80个人月左右。
第二,大致描述项目的需求和包含哪些模块,然后简要说下你做了哪些模块,同时说下在这个项目用到的开发工具和主要技术点,这部分的描述如下所述。
这个外汇交易系统包括挂盘撮合成交、实盘成交、反洗钱和数据批处理等模块,我主要负责了挂盘撮合成交模块,其中用到了Spring MVC架构,数据库是Oracle,用Mybatis实现的ORM,该系统是运行发布在Weblogic服务器上,我们还用了Nginx来实现负载均衡,用Redis来缓存数据。在这个项目里,我还用到了JS实现了一些前台页面。
第三,这里可以职位的需求,描述JD里要求的技术在项目里是如何用的。同样这里也应围绕技术,而别多写业务细节
1.5 在简历中描述项目时可以添加的亮点
我们见过不少简历,在描述项目时,也能像上文一样,能根据招聘职位的具体要求展示出自己的匹配点,这种简历属于“达标”,即可以纳入考虑范围。在这个基础上,如果大家在项目里有下表列出的亮点,一定请写上,这就是大家优于别人的地方。
1. 数据库和JVM调优;
2. 你理解的框架底层代码;
3. 项目里用到的设计模式;
4. 项目管理和部署工具;
5. 若干案例,讲述你分析和解决bug的技能;
6. 其它能帮助到你的加分项,比如工期紧,用到新技术等。
1.6 哪些简历可以通过筛选
从面试官角度来看,除了学历等硬件条件外,如果简历满足如下的4点要求,就一般能有面试机会了。
1. 商业项目足量,且其中包含的技能和职位介绍很匹配;
2. 最近用到的技能和职位介绍很匹配;
3. 没有过长职业空白期或不稳定等情况;
4. 一定请记住,公司只能通过简历认识到你,简历上没写清楚等同于你不行。
其实这就是我们写简历的方向,而且,在针对具体公司投递简历时,还可以以此为目标,微调简历。
面试时该如何讲解技术项目赢得面试官好感
2.1 别害怕,因为面试官什么都不知道
面试官是人,不是神,拿到你的简历的时候,是没法核实你的项目细节的(一般公司会到录用后,用背景调查的方式来核实)。更何况,你做的项目是以月为单位算的,而面试官最多用分钟来从你的简历上了解你的项目经验,所以你对项目的熟悉程度要远远超过面试官,所以你一点也不用紧张。如果你的工作经验比面试官还丰富的话,甚至还可以控制整个面试流程(笔者在面试方面成精后也经常干这种事情,大家一定也能行)。
既然面试官无法了解你的底细,那么他们怎么来验证你的项目经验和技术?下面总结了一些常用的提问方式。
2.2 面试时的错误表现
在面试过程中,如果候选人出现如下的表现,那么很有可能过不了面试,请大家注意。
1. 面试时介绍的项目时间等情况简历上写的不一致,这就有简历造假的嫌疑;
2. 介绍项目时只介绍业务,忽略技术。因为面试官只关心技术,不关心业务;
3. 对于提到的技术,连最基本的问题也回答不上,这就说明候选人这项技术没掌握;
4. 说得太流利或太磕磕巴巴,这就说明在背词或者是表达有问题。
2.3 面试中介绍项目的范例
第一步,介绍项目基本情况。
可以这样说,这个项目是xx产品的xx模块的,有xx和xx模块,我做了xx模块,用了半年,我的组里一共有5个人。这里可以谈下业务,但别深入,因为面试官不熟悉,也不想熟悉候选人的业务,这块时间控制在1分钟之内。
第二步,介绍项目里关键技术和管理方式。
可以这样说,这个项目里,我用到了Spring框架,用到nginx等组件,项目管理用Maven,部署用jenkins,静态扫描用Sonar,任务管理和bug管理用jira,平时采用敏捷的项目迭代方式,每天有站会,大约1月一个迭代版本。这块可以根据自己的情况来介绍,时间也别太长,估计用1分钟也就够了。
第三步,业务讲用到的技术,但别展开:
比如有个职位介绍,里面写到需要有数据库优化的经验,那么可以说,项目里xx模块,我用到MyCat作为分库分表,(不展开技术),上线后,数据库能承受住每秒2000个并发请求(说下用好的结果)。
又如一个JD里说要用到微服务技术,那么就可以说,项目里用到了Spring Cloud框架,用到了Ribbon,Eureka等组件,容器是Docker。用好以后,在发布时会发现,各模块之间的调用耦合性降低。
2.4 介绍项目时的要点归纳
从上述介绍项目的范例中,可以归纳出相关要点如下。
1. 面试前,需要阅读职位介绍,挖掘用过的技能要点,然后尽可能地在介绍项目里提到这些技能关键字;
2. 在介绍项目里,业务,提到职位介绍里的技术,因为一旦技术结业业务,就说明你有过相关技术的实践经验,而不是仅仅只会理论;
3. 别过多介绍业务,多抛出职位介绍里的关键字。还是这句话,面试官不关心业务,你提到业务只是以此证明你在实践中用过相关技术而已;
4. 此时还在项目介绍阶段,别过多展开技能,你抛出技能关键字后,面试官自然会问的。而一旦你过多展开技术,那么面试官就有可能感觉到你思路不清晰。
Java面试者该准备哪些加分项技能
3.1框架是重点,但别让人感觉你只会山寨别人的代码
一般工作在3年内的候选人,大多仅仅是能“山寨”别人的代码,也就是说能在现有框架的基础上,照着别人写的流程,扩展出新的功能模块。比如要写个股票挂单的功能模块,是会模仿现有的下单流程,然后从前端到后端再到数据库,依样画葫芦写一遍,最多把功能相关的代码点改掉。
如果单纯使用SSM框架,大多数项目都会有痛点。比如数据库性能差,或者业务模块比较复杂,并发量比较高,用Spring MVC里的Controller无法满足跳转的需求。所以我一般还会主动问:你除了依照现有框架写业务代码时,还做了哪些改动?
我听到的回答有:增加了Redis缓存,以避免频繁调用一些不变的数据。或者,在MyBitas的xml里,select语句w条件有is,即这个值有就增加一个w条件,对此,会对任何一个w增加一个不带is的查询条件,以免该语句当传入参数都是时,做全表扫描。或者,干脆说,后端异步返回的数据量很大,时间很长,我在项目里就调大了异步返回的最大时间,或者对返回信息做了压缩处理,以增加网络传输性能。
对于这个问题,我不在乎听到什么回答,我只关心回答符不符逻辑。一般只要答对,我就会给出“在框架层面有自己的体会,有一定的了解”,否则,我就只会给出“只能在项目经理带领下编写框架代码,对框架本身了解不多”。
其实,在准备面试时,归纳框架里的要点并不难,我就不信所有人在做项目时一点积累也没,只要你说出来,可以说,这方面你就碾压了将近7成的竞争者。
3.2 别单纯看单机版的框架,适当了解些分布式
在描述项目里框架技术时,最好你再带些分布式的技术。下面我列些大家可以准备的分布式技术。
-
反向代理方面,nginx的基本配置,比如如何通过lua语言设置规则,如何设置session粘滞。如果可以,再看些nginx的底层,比如协议,集群设置,失效转移等;
-
远程调用dubbo方面,可以看下dubbo和zookeeper整合的知识点,再深一步,了解下dubbo底层的传输协议和序列化方式;
-
消息队列方面,可以看下kafka或任意一种组件的使用方式,简单点可以看下配置,工作组的设置,再深入点,可以看下Kafka集群,持久化的方式,以及发送消息是用长连接还是短拦截。
以上仅仅是用3个组件举例,大家还可以看下Redis缓存,日志框架,MyCAT分库分表等。准备的方式有两大类,第一是要会说怎么用,这比较简单,能通过配置文件搭建成一个功能模块即可,第二是可以适当读些底层代码,以此了解下协议,集群和失效转移之类的高级知识点。
3.3 数据库方面,别就知道增删改查,得了解性能优化
在实际项目里,大多数程序员用到的可能仅仅是增删改查,当我们用Mybatis时,这个情况更普遍。不过如果你面试时也这样表现,估计你的能力就和其它竞争者差不多了。
这方面,你可以准备如下的技能:
-
SQL高级方面,比如group by, having,左连接,子查询(带in),行转列等高级用法;
-
建表方面,你可以考虑下,你项目是用三范式还是反范式,理由是什么?
-
尤其是优化,你可以准备下如何通过执行计划查看SQL语句改进点的方式,或者其它能改善SQL性能的方式(比如建索引等);
-
如果你感觉有能力,还可以准备些MySQL集群,MyCAT分库分表的技能。比如通过LVS+Keepalived实现MySQL负载均衡,MyCAT的配置方式。同样,如果可以,也看些相关的底层代码。
哪怕你在前三点表现一般,那么至少也能超越将近一般的候选人,尤其当你在SQL优化方面表现非常好,那么你在面试高级开发时,数据库层面一定是达标的,如果你连第四点也回答非常好,那么恭喜你,你在数据库方面的能力甚至达到了初级架构的级别。
3.4 Java核心方面,围绕数据结构和性能优化准备面试题
Java核心这块,网上的面试题很多,不过在此之外,大家还应当着重集合(即数据结构)和多线程并发这两块,在此基础上,大家可以准备些设计模式和虚拟机的说辞。
下面列些我一般会问的部分问题:
-
String a = \”123\”; String b = \”123\”; a==b的结果是什么?这包含了内存,String存储方式等诸多知识点;
-
HashMap里的hashcode方法和equal方法什么时候需要重写?如果不重写会有什么后果?对此大家可以进一步了解HashMap(甚至ConcurrentHashMap)的底层实现;
-
ArrayList和LinkedList底层实现有什么差别?它们各自适用于哪些场合?对此大家也可以了解下相关底层代码;
-
volatile关键字有什么作用?由此展开,大家可以了解下线程内存和堆内存的差别;
-
CompletableFuture,这个是JDK1.8里的新特性,通过它怎么实现多线程并发控制?
-
JVM里,new出来的对象是在哪个区?再深入一下,问下如何查看和优化JVM虚拟机内存;
-
Java的静态代理和动态代理有什么差别?最好底层代码来说。
通过上述的问题点,我其实不仅仅停留在“会用”级别,比如我不会问如何在ArrayList里放元素。大家可以看到,上述问题包含了“多线程并发”,“JVM优化”,“数据结构对象底层代码”等细节,大家也可以举一反三,通过看一些高级知识,多准备些其它类似面试题。
3.5 Linux方面,至少了解如何看日志排查问题
如果候选人能证明自己有“排查问题”和“解决问题”的能力,这绝对是个加分项,但怎么证明?目前大多数的互联网项目,都是部署在Linux上,也就是说,日志都是在Linux,下面归纳些实际的Linux操作。
-
能通过less命令打开文件,通过Shift+G到达文件底部,再通过?+关键字的方式来根据关键来信息;
-
能通过grep的方式查关键字,具体用法是, grep 关键字 文件名,如果要两次在结果里查找的话,就用grep 关键字1 文件名 | 关键字2 –color。最后–color是高亮关键字;
-
能通过vi来编辑文件;
-
能通过chmod来设置文件的权限。
当然,还有更多更实用的Linux命令,但在实际面试过程中,不少候选人连一条linux命令也不知道。还是这句话,你哪怕知道些很基本的,也比一般人强了。
3.6 通读一段底层代码,作为加分项
如何证明自己对一个知识点非常了解?莫过于能通过底层代码来说明。我在和不少工作经验在5年之内的程序员沟通时,不少人认为这很难?确实,如果要通过阅读底层代码了解分布式组件,那难度不小,但如果如下部分的底层代码,并不难懂。
-
ArrayList,LinkedList的底层代码里,包含着基于数组和链表的实现方式,如果大家能以此讲清楚扩容,“通过枚举器遍历“等方式,绝对能证明自己;
-
HashMap直接对应着Hash表这个数据结构,在HashMap的底层代码里,包含着hashcode的put,get等的操作,甚至在ConcurrentHashMap里,还包含着Lock的逻辑。我相信,如果大家在面试中,看看而言ConcurrentHashMap,再在纸上边说边画,那一定能征服面试官;
-
可以看下静态代理和动态代理的实现方式,再深入一下,可以看下Spring AOP里的实现代码;
-
或许Spirng IOC和MVC的底层实现代码比较难看懂,但大家可以说些关键的类,根据关键流程说下它们的实现方式。
其实准备的底层代码未必要多,而且也不限于在哪个方面,比如集合里基于红黑树的TreeSet,基于NIO的开源框架,甚至分布式组件的Dubbo,都可以准备。而且准备时未必要背出所有的底层(事实上很难做到),你只要能一些重要的类和方法,讲清楚思路即可(比如讲清楚HashMap如何通过hashCode快速定位)。
那么在面试时,如何找到个好机会说出你准备好的上述底层代码?在面试时,总会被问到集合,Spring MVC框架等相关知识点,你在回答时,顺便说一句,“我还了解这块的底层实现”,那么面试官一定会追问,那么你就可以说出来了。
预估面试题,准备对应的回答
4.1 哪些问题面试中大概率会被问到
在面试里,不管如何引导面试官,其实如下方面的问题很大可能会被问到,所以在面试前可以提前准备。
-
职位介绍里提到的技能要点,比如职位介绍里有提到Mybatis,那么面试官一定会问相关问题;
-
你在项目介绍时抛出的技术关键字,比如你在面试过程中介绍项目时提到了Redis,那么在介绍完项目后,面试官就会问,“你项目里是如何使用Redis的?”,类似的,简历中你写的技术,也有可能会被问到;
-
Java核心,数据库,Spring框架,项目管理等基础问题,这些就不用说了,不过如果你引导得当的话,面试官会花费很多时间问你提到的技术,这块会问得比较少;
-
必要的算法题,比如排序等,其实面试官感觉你技术可以的话,这块就不怎么会问了,但准备的时候需要看这个,有备无患。
4.2 面试官提问的方式
以上介绍了常见问题的种类,这里介绍下面试官常用的提问方式。
-
问用法,比如直接提问,项目里你netty怎么用的?这块大家可以项目准备说辞;
-
问流程,比如业务,讲下nginx负载均衡的用法?这也可以项目和网上搜到的资料准备说辞;
-
问原因,比如为什么要用netty?这块就要项目说明了;
-
问技术点, 比如netty里零拷贝怎么回事?对此,需要对简历上提到的每个技术点,以及面试过程中将要提到的每个技术点,搜相关面试问题,并业务说明;
-
问基础知识,比如finally从句的用途,这就可以通过刷题来获取了。
4.3 举例说明该如何准备面试问题
下面给出准备问题的技巧。
-
斟酌面试时抛出的技能,逐一准备说辞;
-
针对技术,网上问题,比如搜Spring IOC面试题,网上的参考答案准备说辞;
-
准备技术的实施要点,比如做了哪些配置文件,你在项目里踩过哪些坑?
-
最好底层代码说明。
如下给出两个例子,先以MyCat分库分表为例,给出介绍说辞的技巧。
-
准备业务背景,为什么要用?比如我们项目数据库并发压力大,需要用MyCat作为分库分表;
-
如何使用,无非是设置分库规则,改写SQL语句等;
-
准备下踩到的坑,比如自增长主键在每台机器上都要保证唯一;
-
然后再些底层代码,准备下一条SQL语句是如何分发到对应的分库上的,然后执行好以后又如何返回的;
-
再可以准备些只有做过才知道的细节,比如发布上线和清洗数据的流程;
-
网上找些MyCAT的面试题,准备相关说辞。
一般说到了这里,面试官就不怎么问了,哪怕你后面再被问倒,面试官也会感觉你MyCat很熟悉。
下面以Netty为例,给出相关技巧。
-
业务需求点,说下为什么要用这个技术,怎么用的,以及用了有什么好处? 比如为了优化网络通讯协议,所以用基于TCP协议的Netty,业务模块里的xxx功能是用到netty;
-
准备下踩到的坑,比如在某业务场景里,我遇到了半包粘包问题,我是通过调试底层代码解决的;
-
用了Netty对项目的帮助。比如Netty是基于TCP协议的,它要比协议要轻,所以通讯性能高,且Netty内部的Reactor线程模型对系统的IO帮助很大;
-
基于零拷贝、读写索引和异步处理机制,准备些底层代码,在面试里说明;
-
顺带再准备下Netty的组件,工作流程等问题,这能搜到问题和相关说辞。
在讲的时候,大家甚至可以边画Netty流程图,再底层代码说明,这样面试官一定会对大家刮目相看。
其实这里仅仅是抛砖引玉,或者提到的技术比较高深 ,但可以讲述的技术还可以是线程池,MyBatis组件,Redis,甚至是虚拟机优化等。哪怕是初级开发,也能多少抓住一两个点,按上述思路说明。
面试时如何不被面试官牵着鼻子,自我把控面试的走向?
5.1 在介绍项目时,引导话题的技巧以及案例
在做项目介绍的时候,你可以穿插说出一些你的亮点,但请记得,不论在介绍项目还是在回答问题,你当前的职责不是说明亮点而是介绍项目,一旦你详细说,可能会让面试官感觉你跑题了。
所以这时你可以一笔带过,比如你可以说,“我们的项目对数据要求比较大,忙的时候平均每小时要处理几十万条数据”,这样就可以把面试官引入“大数据”的方向。
你在面试前可以根据职位的需求,准备好这种“一笔带过”的话。比如这个职位的需求点是Spring MVC框架,大数据高并发,要有数据库调优经验,那么介绍以往项目时,你就最好突出这些方面你的实际技能。
再给大家举个例子,比如Java虚拟机内存管理和数据库优化是绝大多数项目都要遇到的两大问题,大家都可以在叙述项目经验时说,在这个项目里,我们需要考虑内存因素,因为我们的代码只允许在2G内存环境中运行,而且对数据库性能要求比较高,所以我们经常要监控优化内存和数据库里的SQL语句。这样当面试官深入提问时,就能抛出自己准备好的虚拟机内存优化和数据库优化方面的说辞。
或者说,在项目介绍时提到,在xx模块里,我们使用了nginx做负载均衡,达到了承受百万级并发的效果,从而引出nginx的话题。
实在不行,你也可以说“我除了做开发,也做了了解需求,测试和部署的工作,因为这个项目人手比较少,压力比较大”,这样你也能展示你有过独挡一面的经历。
5.2 以Netty为例,讲述引出值钱话题的技巧
比如在介绍项目时,我提到了Netty技术,如果面试官没打断,我就问,能否介绍其中的Netty细节?得到允许后再说。
或者把技术关联到面试官可能会问的问题上,比如问及网络通讯时介绍Netty,这个事先整理一个问题列表,遇到此类问题,顺带抛出Netty说辞。
问题列表可以是,项目里你用到哪些组件?用到哪些通讯协议?如何进行模块间的交互等等,然后先回答问题本身,再扩展到Netty。但请记住,别自说自话,因为过犹不及,其它技术照此办理
5.3 以案例说明,在回答问题时引出准备过话题的技巧
比如面试官问你Spring相关问题,假设问到,你对Spring依赖注入了解多少,在说好Spring相关问题后再提一句,我们同时用Spring,以低耦合的方式整合了MyCAT组件,从而达到了分库分表的效果,这样就引出了分库分表的话题 。
或者在介绍Netty流程后,再说一句,在实际项目里,我们还遇到了因Netty底层代码而导致的OOM问题,对此,我们组负责排查和解决问题,这样就自然而然地引出了OOM内存溢出的问题。
或者在介绍完线程相关问题时,再提一句,在项目里,我们用到了线程池来管理线程,这样就引出了高并发的话题。
但在引导的时候,请注意如下的三点。
第一,面试官不接口的,应当立即停止,再说下去就属于自说自话了。
第二,还是要准备必要的基础问题,还是要刷题,还是要准备各种说辞,因为面试前的全面准备,是引导的基础。
第三,应当引导面试官问些“框架”和“性能调优”等值钱方面的技能,这样才能最大程度地展示你的能力,同样,此类问题需要面试前准备。
5.4 你可以引导的加分项
在如下的一些表格里,归纳的加分项甚至初级开发多少也能准备,其中涵盖了诸多方面。
表 Java Core方面可以准备的亮点
表 数据库方面可以准备的亮点
表Java Web框架方面可以准备的亮点
表 分布式组件方面可以准备的亮点
由此大家能看到,其实很多事先可以准备的点,其实是你没有想到,但你项目里一定用过。你据此准备,在通过上述技巧在面试中合理地找机会说出来,你面试成功的可能性一定会增加。
总结,面试准备后,结果可能就大不同
先从面试官的角度看下,哪些人能面试成功?
1. 最近半年的项目经历和JD匹配度很高;
2. 通过面试,JD上的技能候选人大多能掌握;
3. 候选人在Java核心,数据库和框架方面的基础技能达标;
4. 不是刺头,团队合作没问题,没有其它大问题。
但如果大家面试前不准备,或者准备不到位,那么就会面临如下的后果了:
1. 简历未必能过筛选,甚至没有面试机会;
2. 无法证明项目里用到的技术和JD高度契合;
3. 介绍项目经验时没问题,把提问的主动权交给面试官;
4. 不知道将会问哪些问题,所有问题都现场想;
5. 在面试现场,没法让面试官全面了解你的技术亮点。
但如果按照上述方法准备,大家很大程度上能得到如下的收获。
1. 能通过微调简历,得到更多的面试机会;
2. 能通过挖掘项目经验,证明自己的技能和JD契合;
3. 能知道哪些属于值钱技能,并能业务准值钱技能和调优技能的说辞,而且能不露痕迹地展示;
4. 不仅限于coding,更能展示项目管理(sonar等),linux,项目部署(nginx)等方面的技能;
5. 知道面试大致会问哪些问题,并由此能事先准备;
6. 能事先尽可能多地挖掘亮点,并在面试时展示。
这就是大家阅读本文后的收获,最后感谢大家看完本文。
作者简介:胡书敏,知名外企资深架构师,8年内面试过数以百计的Java工程师,5年的Java培训讲师经验,帮助众多初学者成功拿到心仪的Offer。著有《Java核心技术及面试指南》。CSDN博客专家:https://blog.csdn.net/sxeric
声明:本文为作者原创投稿,未经允许请勿转载。
【End】
本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com
文章为作者独立观点不代本网立场,未经允许不得转载。