Android一次面试失败的复盘和自我反思

今天下午去了一个线下面试,笔试+技术面,这是我人生中第一次面着面着心态炸了的面试,结果非常糟糕。现在回想起来,不管是在心态上,还是在基础知识上,我都存在着很多问题。为了直面我自己存在的问题,于是有了这篇复盘文章。

笔试题复盘

下面是在笔试中我回答的不好的题目:

  • 重载和重写是什么意思,区别是什么?
  • Java异常机制中,异常Exception与错误Error区别
  • List<String>能否转为List<Object>
  • Synchronized的原理以及与ReentrantLock的区别。
  • Looper如何在子线程中创建?
  • volatile和synchronize有什么区别?
  • 请说一说ArrayList 如何保证线程安全,除了加关键字的方式 ?

1.重载和重写是什么意思,区别是什么?

重写(Override)

重写就是重新写的意思,当父类中的方法对于子类来说不适用或者需要扩展增强时,子类可以对从父类中继承来的方法进行重写。

比如Activity是Android开发中四大组件之一。在Activity中存在各种声明周期方法:onCreate、onStart …等等。而我们应用中需要使用Activity来展示UI,那么我们会需要编写自己的类继承自Activity。

public class MainActivity extends Activity{
    
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

在上述代码中,onCreate就是被我们重写的Activity中定义方法。我们在onCreate增加了setContentView

的调用,完成了对超类Activity中对应方法的修改与扩展。

重载(Overload)

重载则是在同一个类中,允许存在多个同名方法,只要它们的参数列表不同即可。比如在Android开发中,我们会使用LayoutInflater的inflate方法加载布局,inflate方法存在多个定义,其中包括两个参数的,与三个参数的:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    
    
	return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    
    
    //......
}

可以看到在两个参数的inflate方法中,会为我们调起三个参数的inflate方法,而定义第一个inflate方法的目的就是为了提供默认的attachToRoot参数值。因为Java中无法定义方法参数默认值,所以我们经常使用重载达成此目的。

2.Java异常机制中,异常Exception与错误Error区别

在Java中存在一个Throwable可抛出类,Throwable有两个重要的子类,一个是Error,另一个则是Exception。

Error是程序不能处理的错误,表示程序中较严重问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError等等。这些错误发生时,JVM一般会选择线程终止。这些错误是不可查的,它们在程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。

Exception是程序可以处理的异常。而Exception又分为运行时异常(RuntimeException)与非运行时异常。

  • 运行异常

    运行时异常,又称不受检查异常 。所谓的不受检查,指的是Java编译检查时不会告诉我们有这个异常,需要在运行时候才会暴露出来,比如下标越界,空指针异常等。

  • 非运行时异常

    RuntimeException之外的异常我们统称为非运行时异常,比如IOException、SQLException,是必须进行处理的异常(检查异常) ,如果不处理(throw到上层或者try-catch),程序就不能编译通过 。

3.List<String>能否转为List<Object>

Java的泛型是伪泛型,编译时会进行泛型擦除

因此List<Number>和 List<Integer> 最终的类型都被擦除了,无论是List<String> 还是 List<Object> 都是List类型。

既然存在泛型擦除,但是下面的代码无法通过编译检查:

List<String> strs = new ArrayList<Integer>();
List<Object> objects = strs;

编译器会帮我我们检查明显的代码问题,因此上述代码会报错,这是编译器的行为,但是如果我们将代码改为:

List<String> strs = (List)new ArrayList<Integer>();
List<Object> objects = (List)strs;

注意,每条语句我们增加了强转声明。此时编译器能够成功完成编译。因此List<String>其实能够强转为List<Object>。但是存在隐患:

List<String> strs = (List)new ArrayList<Integer>();
List<Object> objects = (List)strs;
objects.add(123);
String str =  strs.get(0);

上述代码使用objects(List<Object> )向集合中增加整型数据:123。然后通过 strs获取数据时,因为其类型为List<String>,但是真实数据类型为整型。此时就会发生运行时异常:

Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String

4.Synchronized的原理以及与ReentrantLock的区别。

ReentrantLock与Synchronized的区别,除了一个是Java类实现,一个是关键字之外,还包括:

在这里插入图片描述
除此之外,ReenTrantLock相对于Synchronized还拥有自己的独有特性:

  • ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。
  • ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
  • ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。

5.Looper如何在子线程中创建?

创建子线程的looper必须要通过Looper.prepare()初始化looper,然后再通过Looper.loop()方法让Loop运行起来。

那么具体的细节请看下面的说明:

Handler消息处理流程前面已经介绍过了,还不清楚的朋友,一定要先看下前面章节《Handler怎么进行线程通信,原理是什么》。
首先我们要知道Looper相关的几个重要方法:

Looper.prepare():Looper 初始化, 同时会初始化MessageQueue, Looper消息机制必须要初始化Looper。

Looper.myLooper(): 获取当前调用线程中ThreadLocal缓存的Looper对象。

Looper.loop():让Loop进入死循环。

getLooper(): Handler中的方法, 获取Handler中缓存的Looper对象。

Looper.quit(): 终止 Looper.looper() 死循环, 执行 quit后Handler机制将失效, 执行时如果MessageQueue中还有Message未执行, 将不会执行未执行Message, 直接退出, 调用quit后将不能发消息给Handler。

Looper.quitSafely(): 作用同 quit(), 但终止Looper之前会先执行完消息队列中所有非延时任务, 调用quit后将不能发消息给Handler。

子线程中使用Looper如下:

Handler mHandler;
new Thread(new Runnable() {
    
    
    @Override
    public void run() {
    
    
        Looper.prepare();//Looper初始化
        //Handler初始化 需要注意, Handler初始化传入Looper对象是子线程中缓存的Looper对象
        mHandler = new Handler(Looper.myLooper());
        Looper.loop();//死循环
        //注意: Looper.loop()之后的位置代码在Looper退出之前不会执行,(并非永远不执行)
    }
}).start();

其中主要需要注意的就是Handler的初始化,需要传入当前线程的 Looper 对象。然而,一般情况下我们并不会这样去给子线程创建looper,因为这样的方式只能创建一个handler对象。我们最正确的创建子线程 Looper的方法一般是调用HandlerThread。

6.volatile和synchronize有什么区别?

  • volatile 只能作用于变量,synchronized 可以作用于变量、方法、对象。

  • volatile 只保证了可见性和有序性,无法保证原子性,synchronized 可以保证线程间的有序性(个人猜测是无法保证线程内的有序性,即线程内的代码可能被 CPU 指令重排序)、原子性和可见性。

  • volatile 线程不阻塞,synchronized 线程阻塞。

  • volatile 本质是告诉 jvm 当前变量在寄存器中的值是不安全的需要从内存中读取;sychronized 则是锁定当前变量,只有当前线程可以访问到该变量其他线程被阻塞。

  • volatile标记的变量不会被编译器优化, synchronized标记的变量可以被编译器优化。

7.请说一说ArrayList 如何保证线程安全,除了加关键字的方式 ?

ArrayList 如何保证线程安全
  • 继承Arraylist,然后重写或按需求编写自己的方法,这些方法要写成synchronized,在这些synchronized的方法中调用ArrayList的方法。
  • 可以使用Collections.synchronizedList()这个函数,使用方法如下
List<Map<String,Object>> data=new ArrayList<Map<String,Object>>();
List<Map<String,Object>> data=Collections.synchronizedList(new ArrayList<Map<String,Object>>());

在这里插入图片描述

  • 使用CopyOnWriteArrayList
List<String> k=new CopyOnWriteArrayList<>();

在面试过程中,由于我是非常抵触直接手写代码的,所以涉及到手写代码的题都没写。当然,在面试中也给面试官教育了,也起了一些口头冲突。回头想想,自己实在是心浮气躁,情商也很低。

面试过程复盘

关于面试,其实这次和面试官的交流谈不上愉快,基本我回答他的一个问题,他的回复都是“你的答案并不是我想听到的,我想听到的是xxx而不是xxx”。作为一个仅仅工作三年多的普通android,我也自知手头项目确实乏善可陈,不过因为之前面试没有遇到过这种场面,所以这次面试我面到一半心态就开始炸了,以至于到后面闹了一些不愉快。

在这个难熬的过程和面试官也聊了一些东西,他的意思是他想招做低代码的(岗位jd压根就没写…),然后我也提了下我的看法,因为我目前遇到的场景确实少,以业务开发为主,自然对这些东西接触有限,之后就给批评了。结合前面笔试写的不好的情况,面试官也直接跟我说面试不通过了。在面试过程中由于我心态炸了,回答问题也是有些不耐烦,最后也被面试官指出了面试态度有问题。至此,一场失败的面试结束了。

面试过后,我觉得自己确实问题很大,也在BOSS上托HR向面试官道了个歉。

总结和教训

离职到现在已经有两个月了,由于行情确实不大行,面试机会非常少。这阵子我的心情都是处于一个很down的状态,个人的性格变得有些烦躁易怒,甚至发展到线下笔试都有不耐烦没有写完题的情况。这次的面试算是一个教训,告诉我必须要调整好自己的心态,至少在应对面试这件事上,一定要抱以敬畏和学习的态度去面对,而不是心浮气躁自以为是。

其次是关于面试八股文的问题,八股文里面很多手写题,有些功能我用api很快实现就不管了,实际上自己也不确定如果不使用这些api自己会不会写。然后还有对笔试的抵触问题,这个也是我要去改变的一个点。现在Android面试的话笔试真不少,还要用纸写代码的,所以自己平时得在纸上多练练。

我这里从某一大厂前辈那里薅来了一份面试题合集解析,分享给大家,希望大家不要想我一样在面试题上出现问题,下面直接领取。

第一章 Java方面

  • 第一节 Java基础
  • 第二节 Java集合
  • 第三节 Java多线程
  • 第四节 Java虚拟机

在这里插入图片描述

第二章 Android 方面

  • 第一节 Android 四大组件相关
  • 第二节 Android 异步任务和消息机制
  • 第三节 Android UI 绘制相关
  • 第四节 Android 性能调优相关
  • 第五节 Android 中的 IPC
  • 第六节 Android 系统 SDK 相关
  • 第七节 第三方框架分析
  • 第八节 综合技术
  • 第九节 数据结构方面
  • 第十节 设计模式
  • 第十一节 计算机网络方面
  • 第十二节 Kotlin方面

在这里插入图片描述

第三章 音视频开发高频面试题

  • 为什么巨大的原始视频可以编码成很小的视频呢?这其中的技术是什么呢?
  • 怎么做到直播秒开优化?
  • 直方图在图像处理里面最重要的作用是什么?
  • 数字图像滤波有哪些方法?
  • 图像可以提取的特征有哪些?

在这里插入图片描述

第四章 Flutter高频面试题

  • 第一节 Dart部分

    • Dart 语言的特性?
    • Dart的一些重要概念?
    • dart是值传递还是引用传递?
    • Dart 多任务如何并行的?
    • 说一下 mixin?
  • 第二节 Flutter 部分

    • Flutter 特性有哪些?
    • Flutter 中的生命周期
    • Widget 和 element 和 RenderObject 之间的关系?
    • mixin extends implement 之间的关系?
    • Flutter 和 Dart的关系是什么?

在这里插入图片描述

第五章 算法高频面试题

  • 如何⾼效寻找素数
  • 如何运⽤⼆分查找算法
  • 如何⾼效解决接⾬⽔问题
  • 如何去除有序数组的重复元素
  • 如何⾼效进⾏模幂运算

在这里插入图片描述

第六章 Android Framework方面

  • 第一节 系统启动流程面试题解析
  • 第二节 Binder面试题解析
  • 第三节 Handler面试题解析
  • 第四节 AMS面试题解析

在这里插入图片描述

第七章 企业常见174道面试题

  • 1.SD卡
  • 2.android的数据存储方式
  • 3.BroadcastReceiver
  • 4.sp频繁操作会有什么后果?sp能存多少数据?
  • 5.dvm与jvm的区别
  • 6.ART
  • 7.Activity的生命周期
  • 8.Application能不能启动Activity
  • 9.Activity的状态都有哪些
  • 10.横竖屏切换时Activity的生命周期

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Code1994/article/details/131289783