背景
最近在准备面试,结合之前的工作经验和近期在网上收集的一些面试资料,准备将Android开发岗位的知识点做一个系统的梳理,整理成一个系列:Android应用开发岗 面试汇总。本系列将分为以下几个大模块:
Java基础篇、Java进阶篇、常见设计模式
Android基础篇、Android进阶篇、性能优化
网络相关、数据结构与算法
常用开源库、Kotlin、Jetpack
注1:以上文章将陆续更新,直到我找到满意的工作为止,有跳转链接的表示已发表的文章。
注2:该系列属于个人的总结和网上东拼西凑的结果,每个知识点的内容并不一定完整,有不正确的地方欢迎批评指正。
注3:部分摘抄较多的段落或有注明出处。如有侵权,请联系本人进行删除。
面向对象
1、面向对象的特征
封装:“隐藏细节”、“数据安全”:将对象不需要让外界访问的成员变量和方法私有化,只提供符合开发者意愿的公有方法来访问这些数据和逻辑,保证了数据的安全和程序的稳定。
继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。
多态:多态性是通过继承父类,得到不同的子类,允许不同子类型的对象对同一消息作出不同的响应;通过重写父类的方法来实现
抽象:对一类事物的高度提炼以得到它们的共性,抽象不需要了解全部细节,而只是一种通用的描述,即将某一类事物封装成类
2、接口和抽象类的区别和相同点
相同:
- 用来声明一个新的类型
- 都可以含有抽象方法
不同:
- 声明的关键字不同,类为abstract,接口为interface
- 接口中只包含抽象方法(方法会被隐式地定义成public abstract方法),抽象类中可以包含非抽象方法
- 接口中的成员变量必须为public static final(隐式被定义),抽象类则无此要求
- 一个类最多有一个父类,但可以同时实现几个接口
作用:
- 充分实现面向对象编程思想(我自己总结的,有点抽象)
- 控制流程,在父类中写明业务逻辑,如规定方法的执行顺序。经典例子有Activity的声明周期回调方法(模板方法模式)
面试题:抽象类是否能够不含有抽象方法,能否被final修饰
①抽象类中不一定包含抽象方法(编译器可以编译通过,亲测),但是包含抽象方法的类一定要被声明为抽象类
②抽象类和抽象方法不能被final修饰,因为被final修饰后的类、方法、变量是不能被修改的,而抽象类/方法是需要被实现的
3、方法重写(Overriding)和方法重载(Overload)的含义
重写:又名方法的覆盖、覆写,对父类的方法重写定义,是多态的实现手段。
重载:同一个方法名,输入参数类型不同,输入参数顺序不同。注:无法以返回参数类型来作为重载函数的区分标准
4、成员变量和局部变量的区别
从语法形式上看:成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public、private、static 等修饰符所修饰,而局部变量不能被这些修饰符所修饰;但是它们都可以被 final 所修饰。
从变量在内存中的存储方式来看:如果成员变量被 static 所修饰,那么这个成员变量属于类,如果没有被 static 修饰,则该成员变量属于对象实例。对象存在于堆内存,局部变量存在于栈内存(具体是Java虚拟机栈)。
从变量在内存中的生存时间来看:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用结束而自动消失。
成员变量如果没有赋初始值,则会自动以类型的默认值而赋值(例外:被 final 修饰的成员变量必须在初始化时赋值),局部变量则不会自动赋值。
5、Object类有哪些方法
1.getClass方法
获取运行时类型,返回值为Class对象
2.hashCode方法
返回该对象的哈希码值,是为了提高哈希表的性能(HashTable)
3.equals方法
判断两个对象是否相等,在Object中equals就是使用==去判断,所以在Object中equals是等价于==的,但是在String及某些类对equals进行了重写
4.clone方法
JAVA里除了8种基本类型传参数是值传递,其他的类对象传参数都是引用传递,我们有时候不希望在方法里将参数改变,这时就需要在类中复写clone方法。
如果在clone方法中调用super.clone()方法需要实现Cloneable接口,否则会抛出CloneNotSupportedException。
此方法只实现了一个浅层拷贝,对于基本类型字段成功拷贝,但是如果是嵌套对象,只做了赋值,也就是只把地址拷贝了,所以没有成功拷贝,需要自己重写clone方法进行深度拷贝。
5.toString方法
返回一个String字符串,用于描述当前对象的信息,可以重写返回对自己有用的信息,默认返回的是当前对象的类名 + hashCode的16进制数字。
6.wait方法
多线程时用到的方法,作用是让当前线程进入等待状态,同时也会让当前线程释放它所持有的锁。直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,当前线程被唤醒
7.notify方法
多线程时用到的方法,唤醒该对象等待的某个线程
8.notifyAll方法
多线程时用到的方法,唤醒该对象等待的所有线程
9.finalize
对象在被GC准备释放之前一定会调用finalize方法,对象被释放前最后的挣扎,因为无法确定该方法什么时候被调用,很少使用。
参考链接
基础知识
1、8种基本数据类型
浮点型:float(4 byte), double(8 byte)
整型: byte(1 byte), short(2 byte), int(4 byte) , long(8 byte)
字符型: char(2 byte)
序号 | 数据类型 | 位数 | 默认值 | 取值范围 | 样例 |
---|---|---|---|---|---|
1 | byte(位) | 8 | 0 | -2^7 - 2^7-1 | byte b = 10; |
2 | short(短整数) | 16 | 0 | -2^15 - 2^15-1 | short s = 10; |
3 | int(整数) | 32 | 0 | -2^31 - 2^31-1 | int i = 10; |
4 | long(长整数) | 64 | 0 | -2^63 - 2^63-1 | long l = 10l; |
5 | float(单精度) | 32 | 0.0 | 2^31 - 2^31-1 | float f = 10.0f; |
6 | double(双精度) | 64 | 0.0 | -2^63 - 2^63-1 | double d = 10.0d; |
7 | char(字符) | 16 | 空 | 0 - 2^16-1 | char c = 'c'; |
8 | boolean(布尔值) | 8 | false | true、false | boolean b = true; |
参考链接
2、包装类型
①8种基本数据类型都对应的有包装类型
②对于包装类说,用途主要包含两种:
a、作为 和基本数据类型对应的类 类型存在,方便涉及到对象的操作。
b、包含每种基本数据类型的相关属性如最大值、最小值等,以及相关的操作方法。
③包装类的常量池:整型(Byte Short Integer Long)的缓存常量池范围是-128~127,Double和Float则没有缓存,不管是什么值都会new一个对象来表达该数值
④装箱:将基本数据类型包装成它们的引用类型的过程称为装箱。如Long aaa = 127L,在编译时会自动装箱变成Long aaa = Long.valueOf(127L),Integer Short Byte同理,同样因为常量缓存池的存在,只要数值在常量池范围内,就会返回已经存在的对象的引用;否则创建新的对象返回;对于Double和Float装箱时每次都是创建新的对象返回
⑤拆箱:将包装类型转换成基本数据类型的过程称为为拆箱。long a = aaa.longValue()。在做比较运算时,两个值有一个不是包装类型时,包装类会自动拆箱退化成基本数据类型进行比较
3、String、StringBuffer、StringBuild
概念:
①String是字符常量,StringBuffer、StringBuild都是字符串变量;前者创建的内容不可变,而后两者创建的内容可变;
②String的每次操作都是在内存中重新new一个对象,而StringBuffer、StringBuild不需要,而且它们提供了一定的缓存能力,默认16个字节大小的数组,超过默认的数组长度后会扩容为原来长度的两倍再加2,所以在使用它俩时可以考虑指定长度避免频繁扩容带来的性能问题
关于线程安全:
①StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的;
②StringBuilder并没有对方法进行加同步锁,所以是非线程安全的;
③String中的对象是不可变的,也就可以理解为常量,显然线程安全。
关于效率:
如果程序不是多线程的,那么使用StringBuilder效率高于StringBuffer,在大部分情况下StringBuilder> StringBuffer > String
面试题: 为什么说String是不可变的呢? 因为String类被final修饰,一个String类型的变量,被重新赋值时,是指向的一个新的String对象
4、“==”和equals的区别
①“==”比较的是两个数据的值是否相等。用作基本数据类型之间的比较时,是直接比较值;用作对象之间的比较时,比较的是内存地址是否为同一个。
②equals比较的是各自对象所存储的内容是否相等,该方法为Object中的方法(Object中的该方法是直接比较的对象的内存地址,其内部实现用的是“==”)
③equals方法不能作用于基本数据类型的变量(Double、Integer等包装类型都重写了equals)
④如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址(面试题:为什么要重写equals方法)
参考链接
5、为什么重写 equals 时必须重写 hashCode 方法
①equals方法的实现逻辑:先判断两个对象的hashCode是否相等,如果相等,再进行值的比较 (先做hashCode判断的目的是为了提升执行效率)
②一个类Person 重写equals时,如果通过name判断是否为同一个人(重写equals:this.name.equals(入参name)),而不重写hashCode方法,既hashCode调用的还是Object中的该方法,则会出现hashCode不相等的情况,导致equals方法返回值也不相等
6、编译时和运行时的概念
编译时: 将Java文件编译成.class文件的过程,不涉及到内存的分配
运行时: 将虚拟机执行.class文件的过程,涉及到内存的分配
7、final finally finalize 区别
单词相似,但概念完全无关联
final 是一个修饰符,用来修饰类(不能被继承)、属性(不能被修改)、方法(不能被重写)
finally 是一个关键字,与 try 和 catch 一起用于异常的处理
finalize 是一个方法名, 是在对象被垃圾回收器删除它之前调用的方法,给对象自己最后一个复活的机会
8、值传递和引用传递
值传递:对基本数据类型进行赋值,如int、float等
引用传递:对对象的内存地址进行赋值,即A a1 = new A();A a2 = a1;
9、深拷贝和浅拷贝
浅拷贝:
①基础数据类型:直接进行赋值
②引用数据类型:把变量指向被复制的对象实例(即内存地址)
深拷贝:
①基础数据类型:直接进行赋值
②引用数据类型:对引用数据类型里的具体数据(基础数据类型)进行赋值,而不是复制内存地址 (可通过实现Cloneable接口并重写clone 方法来实现)
10、Collection和Collections
Collection:是一个集合接口,它提供了对集合对象进行基本操作的通用接口方法
Collections:是一个集合的工具类,它包含有各种有关集合操作的静态方法
11、序列化和反序列化
序列化: 将一个Java对象所描述的内容转换为可以存储和传输的形式的过程,通常是将对象转换为字节序列
反序列化: 跟序列化相反的过程,将字节序列恢复成对象,使其在内存中运行
12、进程和线程的区别
两个完全不同的概念
进程: 指操作系统中独立运行的内存区域,每个进程都有自己独立的一块内存空间。比如在Windows系统中,一个运行的exe就是一个进程。各程序之间数据是不能共享的。
线程: 指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享该进程的内存。这里的线程指操作系统线程(区别于CPU线程的4核8线程)
13、内部类
定义: A类的内部再创建一个类B,该类B就是内部类
作用:
①内部类可直接访问外部类的属性
②内部类可以用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立。
③在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。
④创建内部类对象的时刻并不依赖于外围类对象的创建。
⑤内部类并没有令人迷惑的“is-a”关系,他就是一个独立的实体。
⑥内部类提供了更好的封装,除了该外围类,其他类都不能访问。
参考链接
Java内部类主要分为:
成员内部类:
是最普通的内部类,它是外围类的一个成员,所以他是可以无限制的访问外围类的所有 成员属性和方法,尽管是private的,但是外围类要访问内部类的成员属性和方法则需要通过内部类实例来访问。
局部内部类:
嵌套在方法和作用域内的,对于这个类的使用主要是应用与解决比较复杂的问题,想创建一个类来辅助我们的解决方案,到那时又不希望这个类是公共可用的,所以就产生了局部内部类,局部内部类和成员内部类一样被编译,只是它的作用域发生了改变,它只能在该方法和属性中被使用,出了该方法和属性就会失效。
匿名内部类:
Android中给按钮设置监听事件的常用做法就是创建一个匿名内部类
静态内部类
使用static修饰的内部类我们称之为静态内部类。非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围内,但是静态内部类却没有。没有这个引用就意味着:
1、 它的创建是不需要依赖于外围类的。
2、 它不能使用任何外围类的非static成员变量和方法。
参考链接
14、单例模式
定义: 确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例
作用:
1、控制资源的使用,通过线程同步来控制资源的并发访问
2、控制实例产生的数量,达到节约资源的目的
3、作为通信媒介使用,也就是数据共享,它可以在不建立直接关联的条件下,让多个不相关的两个线程或者进程之间实现通信
实现方式:(5种)
懒汉式
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
饿汉式
public class Singleton{
//类加载时就初始化
private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
静态内部类(最佳方案)
这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
双重检验锁
public class Singleton {
private volatile static Singleton instance; //声明成 volatile
private Singleton (){}
public static Singleton getSingleton() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
枚举 Enum
public enum EasySingleton{
INSTANCE;
}
参考链接
15、Java反射机制(Reflection)
1.定义: Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。
2.作用: Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。
3.实际应用: 动态代理、IDE的代码提醒补全功能、调用系统的hide方法、热修复中用到
4.反射的实现:
1、获取Class类(获取类的字节码)
Class.forName(className)
类名.class
Object.getClass()
2、获取属性
public Field getField(String name) 获取public的属性,包括父类的public属性
public Field getDeclaredField(String name) 可获取所有的属性,不限public,但不能获取父类的属性
getFields()、getDeclaredFields()获取到属性数组
3、设置访问权限
xxx.setAccessible(true);//开启访问权限
4、获取方法(作用跟获取属性类似)
public Method getMethod(String name, Class>... parameterTypes)
public Method[] getMethods() throws SecurityException
public Method getDeclaredMethod(String name, Class>... parameterTypes)
public Method[] getDeclaredMethods() throws SecurityException
5、执行方法
Son s = new Son();
Class> sonClass = s.getClass();
try {
Method method = sonClass.getDeclaredMethod("exposeFatherBlackHistory", String.class);
method.setAccessible(true);
method.invoke(s,"偷藏私房钱");
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
6、获取构造方法
1、 Son son = Son.class.newInstance(); 获取无惨构造方法
2、 Constructor constructor = Son.class.getConstructor(String.class);
参考链接
参考链接
16、注解(Annotation)
①定义:又称Java标注,是JDK5.0 引入的一种注释机制(给开发工具、编译工具、程序和程序员看的)
如:@Override、@Deprecated
②元注解: 有五种分别是:@Retention、@Documented、@Target、@Inherited、@Repeatable
@Retention:(作用到注解类上)
当@Retention应用到一个注解上的时候,它解释说明了这个注解的存活时间。 它的取值如下:
RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视
RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中
RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们
@Target:
@指定了注解运用的地方。你可以这样理解,当一个注解被@Target注解时,这个注解就被限定了运用的场景
ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
ElementType.CONSTRUCTOR 可以给构造方法进行注解
ElementType.FIELD 可以给属性进行注解
ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
ElementType.METHOD 可以给方法进行注解
ElementType.PACKAGE 可以给一个包进行注解
ElementType.PARAMETER 可以给一个方法内的参数进行注解
ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
@Documented:将注解中的元素包含到 Javadoc 中去
@Inherited:Inherited 是继承的意思,子类继承了超类的注解
@Repeatable:是java1.8加进来的,表示的是可重复
③如何获取注解:通过反射来获取被注解的类、方法、成员变量上的运行时注解信息,经典里例子有ButterKnife库
④反射+注解的代码示例:
package com.wang.test01;
import java.lang.annotation.*;
import java.lang.reflect.Field;
//反射和注解
public class test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1= Class.forName("com.wang.test01.User");
//通过反射获得注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
Table Tableannotation = (Table) c1.getAnnotation(Table.class);
String value = Tableannotation.value();
System.out.println(value);
//获得类指定的注解
Field f = c1.getDeclaredField("name");
Fieldwang fAnnotation = f.getAnnotation(Fieldwang.class);
System.out.println(fAnnotation.columnName());
System.out.println(fAnnotation.type());
System.out.println(fAnnotation.length());
}
}
//实体类
@Table("学生")
class User{
@Fieldwang(columnName = "db_name",type = "int",length = 10)
private String name;
@Fieldwang(columnName = "db_id",type = "int",length = 10)
private int id;
@Fieldwang(columnName = "db_age",type = "int",length = 10)
private int age;
public User() {
}
public User(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
'}';
}
}
@Target(ElementType.TYPE) //只允许注解类
@Retention(RetentionPolicy.RUNTIME)
@interface Table {
String value();
}
@Target(ElementType.FIELD) //只允许属性注解
@Retention(RetentionPolicy.RUNTIME)
@interface Fieldwang{
String columnName();
String type();
int length();
}
17、String转换成Integer的方式及原理
方式其一:Integer.parseInt(String s, int radix)
原理:Integer.parseInt(string str)方法调用Integer内部的parseInt(string str,10)方法,默认基数为10,parseInt内部首先判断字符串是否包含符号(-或者+),则对相应的negative和limit进行赋值,然后再循环字符串,对单个char进行数值计算Character.digit(char ch, int radix)在这个方法中,函数肯定进入到0-9字符的判断(相对于string转换到int),否则会抛出异常,数字就是如上面进行拼接然后生成的int类型数值。
原文链接
18、Serializable 和Parcelable 的区别
Serializable: JAVA序列化接口,用于序列化类实现对象的本地存储和网络、进程传输。使用反射,效率相对低,会产生很多临时数据。
Parcelable: Android序列化接口,主要用于将对象分解成Intent可传输数据。效率远高于Serializable。但是使用繁琐,需要手写很多代码