系列文章目录
提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
例如:第一章 Python 机器学习入门之pandas的使用
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
随着Android的不断发展,Android面试也越来越重要,很多人都开启了2023新的跳槽之旅,本文就介绍了Android面试的真题答案及解析。
一、第一章 Java 方面
第一节 Java基础部分
1.1 抽象类与接口的区别?
大体区别如下:
- 抽象类可以提供成员方法的实现细节,而接口中只能存在public抽象方法;
- 抽象类中的成员变量可以是各种类型的,但是接口中的成员变量只能是public static final类型的;
- 抽象类可以有构造器、静态代码块以及静态方法,但是接口中是不可以含有构造器、静态代码块和静态方法;
- 一个类只能继承一个抽象类,但是一个类可以实现多个接口;
- 抽象类的访问速度要比接口速度快,因为接口需要时间去寻找在类中的具体实现方法;
- 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。如果你往接口中添加新的方法,那么你必须改变实现该接口的类;
- 抽象类是更加侧重鱼代码复用,而接口更多的为了约束类的行为,可用于解耦。
1.2 分别讲讲final,static,synchronized关键字可以修饰什么,以及修饰后的作用?
static
1.static方法
static方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。
public class StaticTest {
public static void a (){}
public staitc void main(String[]args){
StaticTest.a();
}
}
2.static变量
static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
3.static代码块
static关键字还有一个比较关键的作用就是用来形成静态代码快以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。
public class StaticTest {
private static int a;
private static int b;
static {
a = 1;
b = 2;
}
}
final
1.final变量
凡是对成员变量或者本地变量(在方法中的或者代码块中的变量称为本地变量)声明为final的都叫做final变量。final变量经常和static关键字一起使用,作为常量。
private final int aa = 1;
static {
a = 1;
b = 2;
}
private void init(){
aa = 2;//报错编译器会提示不能赋值。
}
2.final方法
final也可以声明方法。方法前面加上final关键字,代表这个方法不可以被子类的方法重写。如果你认为一个方法的功能已经足够完整了,子类中不需要改变的话,你可以声明此方法为final。final方法比非final方法要块,因为在编译的时候已经静态绑定了,不需要在运行时再动态绑定。
public static void main(String []args){
StaticTesgt.a();
}
class StaticTest2 extends StaticTest{
public final void a(){//这边就会编译器提示不能重写}
}
3.final类
其实跟上面同个道理,使用final来修饰的类叫做final类。final类通常功能是完整的,它们不能被继承。Java中有许多类是final的,譬如String,Integer以及其他包装类。
synchronized
synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。synchronized的作用主要有三个:
- 确保线程互斥的访问同步代码
- 保证共享变量的修改能够及时可见
- 有效解决重排序问题
1.synchronized方法
有效避免了类成员变量的访问冲突:
private synchronized void init(){
init(){
aa = 2;
}
}
2.synchronized代码块
这时锁就是对象,谁拿到这个锁谁就可以运行它锁控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁。
public final void a(){
synchronized(lock){
//代码
}
}
@Override
public void run(){
}
1.3 请简述一下String、StringBuffer和StringBuilder的区别?
- String为字符串常量,StringBuffer与StringBuilder字符串变量,从而效率是String<StringBuffer<StringBuilder(一般情况下);
- StringBuffer是线程安全的,而StringBuilder为非线程安全;
- String是不可变的对象,每次对String类型进行改变的等同于生成了一个新的String对象,经常改变内容的字符串不建议使用String;
- 对StringBuffer类改变,每次结果都会对StringBuffer对象本身进行操作,而不是生成新的对象,再改变对象引用,经常改变内容的字符串建议使用StringBuffer;
- StringBuffer上的主要操作为append和insert方法。
1.4 "equals"与"=="、"hashCode"的区别和使用场景?
- ==:
==用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据(注意是基本类型)或两个引用变量是否相等,只能用==操作符。
假如一个变量指向的数据是对象类型的,例如Object 1 = new Object()那么,这时候涉及了两块内存;变量obj1是一个内存,new Object()是另一个内存(堆内存),变量obj1所对应的内存中存储的数值就是对象占用的那块内存(堆内存)的首地址。对于指向对象类型的变量,如果要比较两个变量是否指向同一个对象,即要看这两个变量所对应的内存中的数值是否相等,这时候就需要用==操作符进行比较;
- equals:
equals方法是用于比较两个独立对象的内容是否相同:
String a = new String("a");
String b = new String("a");
两条new语句创建了两个对象,然后用a/b这两个变量分别指向了其中一个对象,这是两个不同的对象,它们的首地址是不同的,即a和b中存储的数值(对应对象的首地址)是不相同的,所以,表达式a==b将返回false;而这两个对象中的内容是相同的,所以,表达式a.equals(b)将返回true。我们看看equal源码:
*Note that it is generally necessary to override the {@code hashCode}
*method whenever this method is overridden,so as to maintain the
*general contract for the {@code hashCode} method,which states
*that equal objects must have equal hash codes.
*
*@param obj the reference object with which to compare.
*@return {@code true}if this object is the same as the obj
* argument;{@code false} otherwise.
* @see #hashCode()
* @see java.util.HashMap
*/
public boolean equals(Object obj){
return (this == obj);
}
如果一个类没有重写equals方法那么就是调用object的equals其实就是==。
- hashCode:
因为重写的equals()里一般比较全面比较复杂,这样效率就比较低,而利用hashCode()进行对比,则只要生成一个hash值进行比较就可以了,效率很高,那么hashCode()既然效率这么高为什么还要equals()呢?
- 使用场景:
因为hashCode()并不是完全可靠,有时候不同的对象他们生成的hashCode也会一样(hash冲突),所以hashCode()只能说是大部分时候可靠,并不是绝对可靠,所以可以得出:equals()相等的两个对象他们的equals()不一定相等,也就是hashCode()不是绝对可靠的。所有对于需要大量并且快速的对比的话如果都用equals()去做显然效率太低,所以解决方式是,每当需要对比的时候,首先用hashCode()去对比,如果hashCode()不一样,则表示这两个对象肯定不相等(也就是不必再用equals()去再对比了),如果hashCode()相同,此时再对比他们的equals(),如果equals()也相同,则表示这两个对象是真的相同了。
1.5 Java中深拷贝与浅拷贝的区别?
首先需要明白,浅拷贝和深拷贝都是针对一个已有对象的操作。那先来看看浅拷贝和深拷贝的概念。在Java中,除了基本数据类型(元类型)之外,还存在类的实例对象这个引用数据类型。而一般使用[=]号做赋值操作的时候。对于基本数据类型,实际上是拷贝的它的值,但是对于对象而言,其实赋值的只是这个对象的引用,将原对象的引用传递过去,他们实际上还是指向的同一个对象。而浅拷贝和深拷贝就是在这个基础之上做的区分,如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有真实的创建一个新的对象,则认为是浅拷贝。反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝。所以到现在,就应该了解了,所谓的浅拷贝和深拷贝,只是在拷贝对象的时候,对类的实例对象这种引用数据类型的不同操作而已。总结来说:1、浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。2、深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
1.6 谈谈Error和Exception的区别?
Exception是java程序运行中可预料的异常情况,咱们可以获取到这种异常,并且对这种异常进行业务外的处理。
Error是java程序运行中不可预料的异常情况,这种异常发生以后,会直接导致JVM不可处理或者不可恢复的情况。所以这种异常不可能抓取到,比如OutOfMemoryError、NoClassDefFoundError等。
其中的Exception又分为检查性异常和非检查性异常。两个根本的区别在于,检查性异常必须在编写代码时,使用try-catch捕获(比如:IOException异常)。非检查性异常在代码编写时,可以忽略捕获操作(比如:ArrayIndexOutOfBoundsException)。切记,Error是Throw不是Exception。
1.7 什么是反射机制?反射机制的应用场景有哪些?
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。应用场景:
- 逆向代码,例如反编译
- 与注解相结合的框架,如Retrofit
- 单纯的反射机制应用框架,例如EventBus(事件总线)
- 动态生成类框架,例如Gson
1.8 谈谈如何重写equals()方法?为什么还要重写hashCode()?
先来说一下hashCode()和equals方法吧。
hashCode()
- hashCode的存在主要用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在三列存储结构中确定对象的存储地址的。
- 如果两个对象相同,就是适用于equals(java.lang.Object)方法,那么这两个对象的hashCode一定相同。
- 如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第二点。
- 两个对象的hashCode相同,并不一定表示这两个对象就相同,也就是不一定适用于equals()方法,只能够说明这两个对象在三列存储结构中,如Hashtable,他们存在同一个篮子里。
以上话以前摘录自一篇博客,讲的非常好
equals(Object obj)
- 如果一个类没有重写equals(Object obj)方法,则等价于通过 == 比较两个对象,即比较的是对象在内存中的空间地址是否相等。
- 如果重写了equals(Object obj)方法,则根据重写的方法内容去比较相等,返回true则相等,false则不相等。
我用一个简单的demo来举个例子吧
public class MyClass {
public static void main(String[] args){
HashSet books = new HashSet();
books.add(new A());
books.add(new A());
books.add(new B());
books.add(new B());
books.add(new C());
books.add(new C());
System.out.println(books);
}
}
class A{
//类A的equals方法总是返回true,但没有重写其hashCode()方法
hashCode()方法
@Override
public boolean equals(Object o){
return true;
}
}
class B{
// 类B的HashCode()方法总是返回1,但没有重写其equals()方法
@Override
public int hashCode(){
return 1;
}
}
class C {
public int hashCode(){
return 2;
}
@Override
public boolean equals(Object o){
return true;
}
}
结果
- 即使两个A对象通过equals()比较返回true,但HashSet依然把他们当成两个对象,即使两个B对象的hashCode()返回值相同,但HashSet依然把他们当成两个对象。
- 即也就是,当把一个对象放入HashSet中时,如果需要重写该对象对应类的equals()方法,则也应该重写其hashCode()方法。规则是:如果两个对象通过equals()方法比较返回true,这两个对象的hashCode值也应该相同。
- 如果两个对象通过equals()方法比较返回true,但这两个对象的hashCode()方法返回的不同的hashCode值时,这将导致HashSet会把这两个对象保存在Hash表的不同位置,从而使两个对象都可以添加成功,这就与Set集合的规则冲突了。
- 如果两个对象的hashCode()方法返回的hashCode值相同,但他们通过equals()方法比较返回false时将更麻烦:因为两个对象的hashCode值相同,HashSet将试图把它们保存在同一个位置,但又不行(否则将只剩下一个对象),所以实际上会在这个位置用链式结构来保存多个对象;而HashSet访问集合元素时也是根据元素的hashCode值来快速定位的,如果hashSet中两个以上的元素具有相同的HashCode值时,将会导致性能下降。
1.9 Java中IO流分为几种?BIO,NIO,AIO有什么区别?
IO流分为几种
Java中的流非为两种,一种是字节流,另一种是字符流,分别由四个抽象类来表示(每种流包括输入和输出两种所以一共四个):InputStream,OutputStream,Reader,Writer。Java中其他多种多样变化的流均是由他们派生出来的。
字符流和字节流是根据处理数据的不同来区分的。字节流按照8位传输,字节流是最基本的,所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。
- 字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串;
- 字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以。
读文本的时候用字符流,例如txt文件。读非文本文件的时候用字节流,例如mp3。理论上任何文件都能够用字节流读取,但当读取的是文本数据时,为了能还原成文本你必须再经过一个转换的工序,相对来说字符流就省了这个麻烦,可以有方法直接读取。
字符流处理的单元为2个字节的Unidcode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节,操作字节和字节数组。所以字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,所以它对多国语言支持性比较好!
BIO、NIO、AIO有什么区别
BIO:Block IO同步阻塞式IO,就是我们平常使用的传统IO,它的特点是模式简单使用方便,并发处理能力低。
NIO:Non IO同步非阻塞IO,是传统IO的升级,客户端和服务器端通过Channel(通道)通讯,实现了多路复用。
AIO:Asynchronous IO是NIO的升级,也叫NIO2,实现了异步非堵塞IO,异步IO的操作基于事件和回调机制。
BIO是一个连接一个线程。
NIO是一个请求一个线程。
AIO是一个有效请求一个线程。
- BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
- NIO同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
- AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
适用场景分析
- BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
- NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限与应用中,编程比较复杂,JDK1.4开始支持。
- AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
1.10 谈谈你对Java泛型中类型擦除的理解,并说说其局限性?
Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。
如在代码中定义的List和List等类型,在编译后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。类型擦除也是对Java的泛型实现方法与C++模板机制实现方式之间的重要区别。
1.11 String为什么要设计成不可变的?
1、字符串常量池的需要
当创建一个String对象时,如果此字符串已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象,如果允许改变,那么将导致各种逻辑错误,比如改变一个对象将会影响到另一个独立对象,严格来说,这种常量池的思想是一种优化手段。
2、允许String对象缓存HashCode
java中String对象的哈希码会被频繁的使用,比如在hashMap中。字符串的不变形保证了hash码的唯一性,因此可以放心的进行缓存。这也是一种优化手段,意味着不必每次都计算新的哈希码。在String类中有private int hash来缓存hashCode
3、安全性
String被许多的类来当作参数,如网络url,文件路径path等等,如果String不是固定的,将会引起各种安全隐患
1.12说说你对Java注解的理解?
三种注解
source
class
runtime
分别在源码,编译时,运行时存活
自定义的source注解,可以用来对一些方法或者参数进行约束,比如说指定线程,指定参数类型
class注解,是配合apt编写注解处理器,对注解的类或者变量进行解析生成.class文件,辅助工作比如说arouter,在注解处理器中生成代码帮你做路径和activity的路由表,butterknife的注解帮你做findviebyid的工作runtime注解则是在runtime时还能存在的通常配合反射机制,把注解标注的对象拿到进行操作,比方说retrofit,通过反射机制拿到注解的,接口和接口中的方法,在通过动态代理生成接口的实现类
1.13 谈一谈Java成员变量,局部变量和静态变量的创建和回收时机?
成员变量:生命周期伴随类对象,类对象回收时回收,存在堆里
静态变量:不回收。在方法区,随着类的加载而加载,随着类的消失而消失,由于类需要非常长时间的不适用,不利用,不关联,才有可能会被回收机制回收,所以静态成员变量的生命周期特别长,除非是共享数据,否则不建议使用静态;
局部变量:方法调用时创建方法结束时被标记为可回收存在栈里
1.14 请说说Java中String.length()的运作原理?
private final charvalue[];
public String(char value[]){
this.value = Arrays.copyOf(value,value.length);
}
public int length(){
return value.length;
}
第二节 Java集合
2.1 谈谈List,Set,Map的区别?
List中存储的数据是有顺序的,并且值允许重复;Map中存储的数据是无序的,它的键是不允许重复的,但是值是允许重复的;Set中存储的数据是无顺序的,并且不允许重复,但元素在集合中的位置是由元素的hashcode决定的,即位置是固定的(Set集合是根据hashcode来进行数据存储的,所以位置是固定的,但是这个位置不是用户可以控制的,所以对于用户来说set中的元素还是无序的)
2.2 谈谈ArrayList和LinkedList的区别?
- ArrayList是基于数组的数据结构,LinkedList是基于链表的数据结构。
- ArrayList适用于查询操作,LinkedList适用于插入和删除操作。