Android-面试官:这些Java知识点我必问
前言
很早之前就打算写这个“Android-面试官”系列文章,由于各种事拖着忙着(其实就是懒)就拖到了现在。现在刚好到国庆了,我有空写,大伙也有空学,作为一名好(lao)博(you)主(tiao)怎么可以一直拖更,不更点干货给大家呢?哈,干货我有一大把,就看你咽不咽的下了。废话就不多说了,直接开梭。
前排温馨提示:阅读本文前,请自备豆浆!
Java作为移动应用开发中不可以或缺的部分,是我们每个Android开发者都必须具备的技能,掌握的越多,对我们的成长进阶就越有利。今天我们就来系统盘点一下,Android开发者成长中必须掌握的Java核心知识点。
Java基础
一、Java 面向对象思想
1、面向对象都有哪些特性以及你对这些特性的理解
继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。
封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程
接口。
多态:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的服务,那么运行时的多态性可以解释为:当A 系统访问B 系统提供的服务时,B 系统有多种提供服务的方式,但一切对A 系统来说都是透明的。方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事:1. 方法重写(子类继承父类并重写父类中已有的或抽象的方法);2. 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。
抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
二、Java中的多态
1、Java 中实现多态的机制是什么?
答:靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,而程序调用的方法在运行期才动态绑定,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。
三、Java 的异常处理
1、Java 中异常分为哪些种类
1)按照异常需要处理的时机分为编译时异常也叫CheckedException 和运行时异常也叫
RuntimeException。只有java 语言提供了Checked 异常,Java 认为Checked 异常都是可以被处理的异常,所以Java 程序必须显式处理Checked 异常。如果程序没有处理Checked 异常,该程序在编译时就会发生错误无法编译。这体现了Java 的设计哲学:没有完善错误处理的代码根本没有机会被执行。对Checked 异常处理方法有两种:
1 当前方法知道如何处理该异常,则用try...catch 块来处理该异常。
2 当前方法不知道如何处理,则在定义该方法是声明抛出该异常。
运行时异常只有当代码在运行时才发行的异常,编译时不需要try catch。Runtime 如除数是0 和数组下标越
界等,其产生频繁,处理麻烦,若显示申明或者捕获将会对程序的可读性和运行效率影响很大。所以由系统自动
检测并将它们交给缺省的异常处理程序。当然如果你有处理要求也可以显示捕获它们。
2 、java 异常处理机制
Java 对异常进行了分类,不同类型的异常分别用不同的Java 类表示,所有异常的根类为java.lang.Throwable,Throwable 下面又派生了两个子类:Error 和Exception,Error 表示应用程序本身无法克服和恢复的一种严重问题。Exception 表示程序还能够克服和恢复的问题,其中又分为系统异常和普通异常,系统异常是软件本身缺陷所导致的问题,也就是软件开发人员考虑不周所导致的问题,软件使用者无法克服和恢复这种问题,但在这种问题下还可以让软件系统继续运行或者让软件死掉,例如,数组脚本越界(ArrayIndexOutOfBoundsException),空指针异常(NullPointerException)、类转换异常(ClassCastException);普通异常是运行环境的变化或异常所导致的问题,是用户能够克服的问题,例如,网络断线,硬盘空间不够,发生这样的异常后,程序不应该死掉。
java 为系统异常和普通异常提供了不同的解决方案,编译器强制普通异常必须try..catch 处理或用throws 声明继续抛给上层调用方法处理,所以普通异常也称为checked 异常,而系统异常可以处理也可以不处理,所以,编译器不强制用try..catch 处理或用throws 声明,所以系统异常也称为unchecked 异常。
四、Collection容器
List
1、用过 ArrayList 吗 ?说一下它的底层实现 ?
有使用,它的底层是基于数组的数据结构, 默认第一次初始化长度为 10 ,由于 add ,put , size 没有处理线程安全,所以它是非线程安全的。
要不我手动画一下它的整体结构吧。如下图所示。
图解:
- Index: ArrayList 的索引下标
- elementData: ArrayList 的索引下标对应的数据
- size: ArrayList 的大小
2、用过 LinkedList 吗?说一下它的底层实现 ?
有用过,它的底层数据结构是双向链表组成, 我还是画一下它的结构图吧。如下所示:
图解:
- 链表每个节点我们叫做 Node,Node 有 prev 属性,代表前一个节点的位置,next 属性,代表后一个节点的位置;
- first 是双向链表的头节点,它的前一个节点是 null。
- last 是双向链表的尾节点,它的后一个节点是 null;
- 当链表中没有数据时,first 和 last 是同一个节点,前后指向都是 null;
3、你在工作中对 ArrayList 和 LinkedList 是怎么选型的?
如果项目中有需要快速的查找匹配,但是新增删除不频繁我一般使用的是 ArrayList 数组结构,但是如果查询比较少,新增和删除比较多我一般用的是 LinkedList 链表结构。(ps:结合它们的原理回答为什么)
4、 ArrayList 在多线程使用应该注意什么?
在多线程使用 List 要注意线程安全问题,解决的办法通常有两种来解决。第一种也是最简单的一种直接使用 Collections.synchronizedList(list) ,但是其性能不好,因为它的实现原理相当于委托模式,交于另一个类来处理,而且内部将每个函数都加了 synchronized , 另一种实现是 java.util.concurrent##CopyOnWriteArrayList 。
延伸:用过 CopyOnWriteArrayList 吗?它是怎么实现线程安全的 ?
答:有用过,它的基本原理和 ArrayList 是一致的,底层也是基于数组实现。它的基本特性总结有以下几点:
1、线程安全的,多线程环境下可以直接使用,无需加锁;
2、通过锁 + 数组拷贝 + volatile 关键字保证了线程安全;
3、每次数组操作,都会把数组拷贝一份出来,在新数组上进行操作,操作成功之后再赋值回去。
Map
1、描述一下HashMap中 put(K key, V value) 这个 API 的存储过程 。
1、根据 key 通过该公式 (h = key.hashCode()) ^ (h >>> 16) 计算 hash 值
2、判断 HashMap table 数组是否已经初始化,如果没有初始化,那么就按照默认 16 的大小进行初始化,扩容阀值也将按照 size * 0.75 来定义
3、通过该公式 (n - 1) & hash 拿到存入 table 的 index 索引,判断当前索引下是否有值,如果没有值就进行直接赋值 tab[index] , 如果有值,那么就会发生 hash 碰撞 💥 ,也就是俗称 hash冲突 , 在 JDK中的解决是的办法有 2 个,其一是链表,其二是 红黑树。
4、当发送 hash 冲突 首先判断数组中已存入的 key 是否与当前存入的 key 相同,并且内存地址也一样,那么就直接默认直接覆盖 values
5、如果 key 不相等,那么先拿到 tab[index] 中的 Node是否是红黑树,如果是红黑树,那么就加入红黑树的节点;如果 Node 节点不是红黑树,那么就直接放入 node 的 next 下,形成单链表结构。
6、如果链表结构的长度 >= 8 就转为红黑树的结构。
7、最后检查扩容机制。
图解如下:
2、说说HashMap的工作原理
简单来说:HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存Entry对象。当两个对象的hashcode相同时,它们的bucket位置相同,‘碰撞’会发生。因为HashMap使用链表存储对象,这个Entry会存储在链表中,当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。
网上文章很多,可以去我的B站看视频讲解:HashMap原理解析
延伸:如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?
答:默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。
3、说一下你对 ArrayMap 的了解
ArrayMap 底层通过两个数组来建立映射关系,其中 int[] mHashes 按大小顺序保存 Key 对象 hashCode 值,Object[] mArray 按 mHashes 的顺序用相邻位置保存 Key 对象和 Value 对象。mArray 长度 是 mHashes 长度的 2 倍。
存储数据是根据 key 的 hashcode() 方法得到 hash 值,计算出在 mArrays 的 index 值,然后利用二分查找找到对应的位置进行插入,当出现哈希冲突时,会在 inde 的相邻位置插入。
取数据是根据 key 的 hashcode() 方法得到 hash 值,然后通过 hash 值根据二分查找拿到 mHashes 的 index 索引,最后在根据 index + 1 索引拿到 mArrays 对应的 values 值。
4、你在工作中对 HashMap 和 ArrayMap 还有 SparseArray 是怎么选型的 ?
好的,我总结了一套性能对比,每次需求我都是参考如下的总结。
序号 | 需求 | 性能选择 |
---|---|---|
01 | 有 1K 数据需要装入容器 | key 是 int 选择 SparseArray 节省 30% 内存,反之选择 ArrayMap 节省 10% |
02 | 有 1W 数据需要装入容器 | HashMap |
Android进阶必备Java高阶知识点
文章篇幅原因,中高级的部分内容过多,文章篇幅原因,所以我整理了241页PDF。虽然短小,但是精悍!看完这份电子书,保你可以随意应付Android面试中的Java问题,如果不行,请私信我给我寄刀片。文档领取方式:点赞+关注,然后私信关键词 【666】即可获得免费领取方式!
一、深入理解Java泛型
1、泛型的作用与定义
2、通配符与嵌套
3、泛型的上下边界
4、RxJava中深入理解泛型
二、注解
- 注解(ANNOTATIONS)概念、什么是注解
- 元注解;什么是元注解、元注解的使用
- 自定义注解
- 默认参数值(DEFAULT PARAMETER VALUES)
- 什么是APT,如何在Android Studio中构建一个APT项目?
- 插桩
- 反射
- Retrofit中的注解
三、并发编程
1、基础概念
1)CPU核心数、线程数
2)CPU时间片轮转机制
2、线程之间的共享
1)synchronized内置锁
2)volatile 关键字
3)线程私有变量 ThreadLocal
3、线程间的协作
1)什么是CAS?
2)CAS(Compare And Swap)导致的ABA问题
4、线程池的使用
1)Java中的ThreadPoolExecutor类
2)深入剖析线程池实现原理
3)如何合理配置线程池的大小
四、数据传输与序列化
1、Serializable原理
1.概念
- 序列化:把Java对象转换为字节序列的过程。
- 反序列化:把字节序列恢复为Java对象的过程。
2.用途 对象的序列化主要有两种用途:
1)把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中。
2)在网络上传送对象的字节序列。
2、Parcelable的原理和使用方法
- Android源码中的Parcelable
3、Json
一种数据传输格式,与开发语言无关,轻量级。
一开始是javaScript的,但是后面比较流传,几乎所有语言都有相应的使用API。
五、Java虚拟机原理
1、垃圾回收
1)如何判断对象已“死”
2)回收方法区
3)垃圾回收算法
2、内存分配策略
1)对象优先在Eden分配
2)大对象直接进入老年代
3)长期存活的对象将进入老年代
4)动态对象年龄判定
5)空间分配担保
3、Dalvik虚拟机
1)Dalvik指令
2)Dalvik启动过程
3)Dalvik上的垃圾回收
六、反射与类加载
1、反射
1)反射基本概念与三种获取Class对象的方式
2)获取构造器实例化对象与属性信息
3)Android 配置打包签名信息的两种方法
4)Hook动态注入代码
2、类加载
1)动态代理模式
2)Android 中的Dalvik和ART
3)ClassLoader 的构造函数
4)双亲委派机制
七、高效IO
1、基于字节的IO操作
2、基于字符的IO操作
3、字节流和字符流的区别
4、常用类
1)文件流:FileInputStream/FileOutputStream, FileReader/FileWriter
2)包装流:PrintStream/PrintWriter/Scanner
3)字符串流:StringReader/StringWriter
4)转换流:InputStreamReader/OutputStreamReader
5)缓冲流:BufferedReader/BufferedWriter , BufferedInputStream/BufferedOutputStream
【Android架构师筑基必备Java知识点】已上传在我的GitHub免费开放下载
文末
我一直觉得技术面试不是考试,考前背背题,发给你一张考卷,答完交卷等通知。
首先,技术面试是一个 认识自己 的过程,知道自己和外面世界的差距。
更重要的是,技术面试是一个双向了解的过程,要让对方发现你的闪光点,同时也要 试图去找到对方的闪光点,因为他以后可能就是你的同事或者领导,所以,面试官问你有什么问题的时候,不要说没有了,要去试图了解他的工作内容、了解这个团队的氛围。
找工作无非就是看三点:和什么人、做什么事、给多少钱,要给这三者在自己的心里划分一个比例。
最后,祝愿大家在这并不友好的环境下都能找到自己心仪的归宿!
欢迎关注我的简书,分享Android干货,交流Android技术。
对文章有何见解,或者有何技术问题,都可以在评论区一起留言讨论,我会虔诚为你解答。
也欢迎大家来我的B站找我玩,有各类Android架构师进阶技术难点的视频讲解,只为助你早日升职加薪。
B站直通车:https://space.bilibili.com/544650554