H5W3
当前位置:H5W3 > java > 正文

【Java】去年面了多个候选人,看看我挖的坑还有他们应该要补的Java基础(二)

看看我在Object 通用方法埋了什么坑

【Java】去年面了多个候选人,看看我挖的坑还有他们应该要补的Java基础(二)

没什么区别。

错,初学者或者代码打得少的人都会犯这个错。

test1会直接报空指针异常,你想想看,null.equals看不起来不就怪怪的吗?空指针怎么可能有方法呢是吧,

拓展:我们一般在企业开发中都会将已知的字面量放在equals,未知的参数放在equals后面,这样可以避免空指针,编程新手容易犯这个异常,我review过的代码这么实现的,说实话,挺多次的,不止编程新人,两三年工作经验的都会这么做,实在不应该,

equals方法比较的是字符串的内容是否相等,而 == 比较的则是对象地址。

这么回答也是对的,但是,我们更希望听到更专业的回答,比如:

首先Java中的数据类型可以分为两种,一种是基本数据类型,也称原始数据类型,如byte,short,char,int,long,float,double,boolean 他们之间的比较,应用双等号(==),比较的是他们的值。

另一种是复合数据类型,包括类,当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址,所以,除非 是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。

而JAVA当中所有的类都是继承于Object这个基类的,在Object中的基类中定义了一个equals的方法,这个方法的初始行为是比较对象的内存地址,但在一些类库当中这个方法被覆盖掉了,如String,Integer,Date在这些类当中equals有其自身的实现,而不再是比较类在堆内存中的存放地址了。

这么回答显的既专业又牛逼,还大气上档次,你学废了吗?

一般问到这种问题,候选人都可以回答:

如果两个对象equals方法相等,则它们的hashCode一定相同;

如果两个对象的hashCode相同,它们的equals()方法则不一定相等。

而两个对象的hashCode()返回值相等不能判断这两个对象是相等的,但两个对象的hashcode()返回值不相等则可以判定两个对象一定不相等。

基本上初学者或者应届生在这一步就被我卡主了, 支支吾吾的回答不了多少,基本上都在这里掉分,因为上面的规范回答百度一查一大把,但是对hashCode作用有深入了解的却很少,这道题也可以看出一个人的专业水平。

正确的回答是:

hashCode的作用实际上是为了提高在散列结构存储中查找的效率,自然只有每个对象的hashCode尽可能的不同才能保证散列存储性能的提高,这也是为什么Object默认提供hash码都是不同的原因。

举个例子:

以HashSet为例,要想保证元素不重复则需要调用对象的equals方法比较一次,但是如果这个结构放了很多元素,比如5000次,如果没有hashCode的话则需要在每次加元素的时候对比5000次,而如果有hashCode则不一样了,当集合要添加新的元素的时候先调用hashCode方法,这样便可以定位到元素的地址,而无需多次判断。

【Java】去年面了多个候选人,看看我挖的坑还有他们应该要补的Java基础(二)

每次问到这道题,大部分人都是回答2,小部分人是回答1。

都错,正确答案是直接报错

【Java】去年面了多个候选人,看看我挖的坑还有他们应该要补的Java基础(二)

为什么?因为clone方法是Object的protect方法,需要子类显示的去重写clone方法,并且实现Cloneable 接口,这是规定。

那么问题来了?加上Cloneable 接口后呢?到底是2还是1呢?

这又是我挖的坑,主要为了考察候选人对浅拷贝和深拷贝的理解

最终结果其实是2,因为直接用clone拷贝的时候是一种浅拷贝,最终拷贝对象和原始对象的引用类型引用还是同一个对象。

如果想要实现深拷贝,则需要开发人员自己在clone方法内自己进行重写。

拓展:在企业开发中一般我们不允许直接用clone方法来做拷贝,存在抛出异常的风险,还需要进行类型转换。如果是我review到这种代码,都是直接告诉同事:在Effective Java 书上有讲到,最好不要去使用 clone(),我们可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象,比如这样

【Java】去年面了多个候选人,看看我挖的坑还有他们应该要补的Java基础(二)

总结:我们都知道Java是面向对象编程的,我的理解实际上就是面对Object编程,能够合理的使用和了解原理equals和hashCode是一个初级程序员必须具备的素养,而对浅拷贝和深拷贝的理解也基本是基础中的基础,希望看完这系列,大家可以了然于胸,下次遇见这种面试题,直接吹。

看看我在抽象类和接口埋了什么坑

抽象类和抽象方法都使用 abstract 关键字进行声明,如果一个类中包含抽象方法,那么这个类必须声明为抽象类,并且抽象类和普通类最大的区别是,抽象类不能被实例化,只能被继承。

bingo,答案是对的,但是还不够好

上面的是标准答案,随便一本Java书籍都可以看到,但是可以补充下你们对应用的理解,这可以体现你的编码水平到了哪一步,我这边补充我理想的答案:

抽象类可以实现代码的重用,模板方法设计模式是抽象类的一个典型应用,例如我们游戏的所有活动都要进行一样的初始化,但是初始化的时候需要根据不同活动进行不同功能开启判断,那么就可以定义一个活动的基类,将功能开启判断做成一个抽象方法,让所有活动都继承这个基类,然后在子类自行重写功能开启判断。

补充上自己对应用的理解,可以充分体现你的专业,面试官听完,肯定是内心一顿尼玛的牛逼。

接口可以说成是抽象类的一种延伸,接口中的所有方法都必须是抽象的。实际上,接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。

你们懂的,这又是一个标准回答,实际上,不够好,可以补充以下几句话

从Java8开始,接口也可以拥有默认的方法实现了,为啥Java团队要做这个支持呢?

实际上,在Java上深耕多年的老司机其实都被接口没有默认实现搞过,维护成本太高了,你想想,如果你现在有100个类实现了一个接口,而在这个时候,需要给接口新增一个方法,特么Java8之前不允许给接口加默认实现,所以你就得改100个类,这特么不得搞出人命吗?

真心话:能够说出对抽象类和接口应用的理解的候选人不多,基本上说出来的我内心都是往牛逼去吹的,大写的服,因为这充分体现一个开发人员对编码方面的思考,你不知道一个写代码有思考和整洁,而不是只顾实现功能的程序员,主管会多喜欢!!!

  • 抽象类可以有构造方法,接口中不能有构造方法。
  • 抽象类中可以有普通成员变量,接口中没有普通成员变量
  • 抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
  • 抽象类中可以包含静态方法,接口中不能包含静态方法
  • 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。
  • 一个类可以实现多个接口,但只能继承一个抽象类。

这个就是标准答案了,不过我们面试官比较希望可以在回答区别题的时候自己做些拓展回答,将上面接口和抽象类的理解都说一遍,而不是背书一样的回答,切记,回答问题,遇见自己懂的知识点,尽量做拓展,加上自己的理解,一是可以向面试官展示你的专业,二是可以拖时间,尽量避免让面试官想更多难题坑你

总结:看到这里,估计很多人对抽象类和接口都嗤之以鼻,都是随口可以说出的答案,但是,你以为我们只是想要听你背书式的回答吗,错了,我们想知道的是后面你的拓展和理解,这才可以体现你的编码能力和专业。

看看我在重写和重载里埋了什么坑

【Java】去年面了多个候选人,看看我挖的坑还有他们应该要补的Java基础(二)

这道题简直就是了,很多年前我来我司应聘的时候也遇见过这道题,说实话,应届生和初级绕不过这道题,现在直接给大家亮牌:

【Java】去年面了多个候选人,看看我挖的坑还有他们应该要补的Java基础(二)

给大家说说如何理解这种题,理解以下的原则就可以了:

JVM在调用一个方法时,会优先从本类中查找看是否有对应的方法,如果没有再到父类中查看是否从父类继承来。

如果没有怎么办?那JVM就会对参数进行转型,转成父类之后看是否有对应的方法。

总的来说,方法调用的优先级如下:

  • this.func(this)
  • super.func(this)
  • this.func(super)
  • super.func(super)

记住这个过程就可以了。

class TestMain {

public int show(int x) {
return x;
}

public void show(int x) {
System.out.println(x);
}
}

我面试过的大部分初级或者应届都会回答是,不得不说这道题实在太坑了,不开玩笑,你看那堆被这道题埋过的人里边就有我。

千万记住了,重载的定义是:在同一个类中,一个方法与已经存在的方法名称上相同,并且参数类型、个数、顺序至少有一个不同。这句话里边并没有包含着返回值,如果只是返回值不同,其它都相同根本不算是重载。

当然,idea是会直接提示报错的,这也是为什么我没有截idea的源码出来给大家看的原因,一看有爆红你们就知道有问题了。不过还是那句话,这并不意味着idea可以检测出来的东西,你就可以不懂,这是基础。

总结:说实话,在笔试题中出现重载和重写那两道题实在是太常见了,百分之九十的应届生或者初级开发都被坑过,所以看完这篇文章记得收藏啊,并不是你看了就记住了,而是记得在面试前重新看一遍,这可是关系到你offer上那个金光闪闪的数字的上限的啊

看看我在反射方面埋了什么坑

终于到了反射了,我们面试官其实很喜欢这个知识点,可以用它来筛掉一大批的api工程师

在运行时,Java 反射,可以获取任意类的名称、package 信息、所有属性、方法、注解、类型、类加载器、现实接口等,并且可以调用任意方法和实例化任意一个类的独享,通过反射我们可以实现动态装配,降低代码的耦合度、实现动态代理等,不过反射的过度使用会严重消耗系统资源。

例子很多,比如:

  • JDBC中,利用反射动态加载了数据库驱动程序。
  • Web服务器中利用反射调用了Sevlet的服务方法。
  • 还有多框架都用到反射机制,注入属性,调用方法,如Spring等。

基本上回答几个就可以了,这里因为还没到框架模块,所以先不对Spring、JDBC等源码进行深挖,一般都是基础题面完了,才开始挖对框架的使用和源码的理解。

动态代理其实就是是运行时动态生成代理类。 动态代理的应用有 Spring AOP、测试框架的后端 mock、rpc,Java注解对象获取等应用。

目前动态代理可以提供两种,分为JDK 原生动态代理和 CGLIB动态代理;

JDK 原生动态代理是基于接口实现的,而 CGLIB是基于继承当前类的子类实现的,当目标类有接口的时候才会使用JDK动态代理,其实是因为JDK动态代理无法代理一个没有接口的类,因为JDK动态代理是利用反射机制生成一个实现代理接口的匿名类;

而CGLIB是针对类实现代理,主要是对指定的类生成一个子类,并且覆盖其中的方法。

大部分都会回答JDK 原生动态代理,小部分人会回答CGLIB。

但其实两者都是错的,这是我挖好的坑,太多候选人都只知道AOP用了动态代理了,但是能够完整回答出来用了哪种的却寥寥无几,毕竟只有看过源码的人才能回答这个问题,一般能够回答出来的,我这边都会额外加分

实际答案是:在Spring中默认使用的是JDK动态代理,除非目标类没有实现接口,才会转为CGLIB代理,如果想要强行使用CGLIB代理免责需要在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true" />

然而在SpringBoot中,从2.0开始就默认使用CGLIB代理。**

优点:可以动态执行,在运行期间根据业务功能动态执行方法、访问属性,最大限度发挥了java的灵活性。 缺点:对性能有影响,这类操作总是慢于直接执行java代码。

到了这里基本上很多候选人都无法回答了,我见过太多一开始就说性能性能的,可是一说到如何解决,就基本支支吾吾的回答不出来。

记住了,如果聊到性能,记得想好怎么回答优化方面的问题,否则就是自己搬起石头砸自己的脚。

实际上可以从以下几个方面回答:

  • 在系统启动时,将反射得到元数据保存起来,使用时,只需从内存中调用即可。
  • 尽量用高点的JDK,因为高版本的虚拟机会对执行次数较多的方法进行优化,例如使用jit技术,而低版本的那个时候还没实现,
  • 可以考虑使用高性能的反射库,Spring内部也有提供一些。

总结:这里其实建议大家多去玩玩反射,不要只会api调用,对于我们面试官而言,熟悉和理解反射是一个中级程序员必备的条件。

看看我在异常和注解方面埋的坑

目前来说,可以作为异常抛出的类,分为两种: Error 和 Exception。

而其中 Error 用来表示 JVM 无法处理的错误,然后Exception 也分为两种,分别是:

  • 受检异常 :需要用 try…catch… 语句捕获并进行处理,并且可以从异常中恢复;
  • 非受检异常 :是程序运行时错误,例如除 0 会引发 Arithmetic Exception,此时程序崩溃并且无法恢复。

注解提供了一种类似注释的机制,用来将任何的信息或数据与类、方法、或者成员变量等进行关联。

Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。

注解的用处很多,举两个最常见的例子:

  • 生成文档。这是最常见的,也是java 最早提供的注解,系统会在扫描到使用了注解的类后,根据注解信息生成文档。
  • 在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。

JAVA 中有以下几个『元注解』:

  • @Target:注解的作用目标
  • @Retention:注解的生命周期
  • @Documented:注解是否应当被包含在 JavaDoc 文档中
  • @Inherited:是否允许子类继承该注解

没错,这几个元注解太常见了,但是却很少人能够真的用的好。

关于@Target的作用目标有多个目标类型,直接截图如下:

【Java】去年面了多个候选人,看看我挖的坑还有他们应该要补的Java基础(二)

基本上回答几个就可以了,不过记住了,上面的TYPE就是指的是类,也就是说这个注解是作用在类上的。

@Retention注解的生命周期可以分为以下几种:

  • etentionPolicy.SOURCE:当前注解编译期可见,不会写入 class 文件
  • RetentionPolicy.CLASS:类加载阶段丢弃,会写入 class 文件
  • RetentionPolicy.RUNTIME:永久保存,可以反射获取

区别在于:

第一种是只能在编译期可见,编译后会被丢弃;

第二种会被编译器编译进 class 文件中,无论是类或是方法,乃至字段,他们都是有属性表的,而 JAVA 虚拟机也定义了几种注解属性表用于存储注解信息,但是这种可见性不能带到方法区,类加载时会予以丢弃;

第三种则是永久存在的可见性,可以反射获取,基本上用这种居多。

对于注解生命周器的理解可以避免一些坑,还是那句话,基本上注解这块能回答的比较流畅的,说明都是玩过组件或者看过源码的,因为对于开发一个组件来说,注解太常用了。

这道题很少人会回答的上来,毕竟大家对@Inherited这个元注解太陌生了。

在注解上使用元注解@Inherited,表示该注解会被子类所继承,注意注意,仅针对类哟,成员、方法并不受该元注解的影响

因此,如果想要让子类也继承父类的注解,则需要给注解加上元注解@Inherited。

拓展:一般初级开发都基本不会用上注解,因为注解更多的是用来写组件用的,我们项目组这边就很喜欢自定义一些注解来实现组件,因为用起来实在是太舒服了。

总结:关于异常这块,Error 和 Exception是经常会被问到的基础考点,而注解这块是一个能够加分的点,希望大家别错过这个知识点,学透它,不过记住上面几个坑点,其实也差不多了。

最后

上一篇文章:https://mp.weixin.qq.com/s/F73r_f5YcBOayPdsin4gBg 点赞和阅读量都很高,说明文章质量是ok的,感谢大家的支持;

Java基础系列也基本差不多了,说实话,这两篇文章的坑点,只要你记住了,Java基础这块基本上没问题了。

后续安排:

  • 【好好面试】Java集合系列
  • 【好好面试】Java岗位常考算法
  • Caffeine源码解析

有兴趣的关注我一波,Java面试官带你们跨过一个个的面试坑,保证不亏。

为了感谢最近大家的支持,我这边特地跳了一些Java相关的资源,很多专题,比如JAVA+TCP、Java反射机制、Java多线程专题等,都是针对性训练的利器,建议人手一份。

【Java】去年面了多个候选人,看看我挖的坑还有他们应该要补的Java基础(二)

【Java】去年面了多个候选人,看看我挖的坑还有他们应该要补的Java基础(二)

给文章点个再看,关注公众号,回复:Java面试 即可拿到资源。

公众号:饭谈编程 原文链接:https://mp.weixin.qq.com/s/F73r_f5YcBOayPdsin4gBg

谢谢点赞支持👍👍👍!

本文地址:H5W3 » 【Java】去年面了多个候选人,看看我挖的坑还有他们应该要补的Java基础(二)

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址