Android中高级进阶开发面试题冲刺合集(一)

以下主要针对往期收录的面试题进行一个分类归纳整理,方便大家统一回顾和参考。今天是第一集~

强调一下:【因篇幅问题:文中只放部分内容,全部文档需要的可找作者获取。

Java 基础部分

1.抽象类与接口的区别?

参考答案:大体区别如下:

  • 抽象类可以提供成员方法的实现细节,而接口中只能存在 public 抽象方法;
  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的;
  • 接口中不能含有构造器、静态代码块以及静态方法,而抽象类可以有构造器、静态代码块和静态方法;
  • 一个类只能继承一个抽象类,而一个类却可以实现多个接口;
  • 抽象类访问速度比接口速度要快,因为接口需要时间去寻找在类中具体实现的方法;
  • 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。如果你往接口中添加方法,那么你必须改变实现该接口的类。
  • 接口更多的为了约束类的行为,可用于解耦,而抽象类更加侧重于代码复用。

2.分别讲讲 final、static 和 synchronized 可以修饰什么,以及修饰后的作用?

参考答案:final :可以修饰 类,方法,字段。

修饰类:该类不会被继承。
​
修饰方法:该方法不能被重写。
​
修饰字段:被修饰的 字段必须 赋初始值,并且不能被改变。如果字段是引用类型的。那么他将不能引用别的对象,但是当前的对象内的属性值是可以改变的。

static :可以修饰内部类,方法,字段。

修饰内部类:被static修饰 的内部类可以直接作为一个 普通的类来使用,而不需先实例一个外部列。
​
修饰方法:调用该方法的时候只需要类名 . 方法就可以直接调用,不需要创建对象。
​
修饰字段:通过类名 . 的方式可以直接 获取 或者 赋值。

synchronized 可以修饰 方法,代码块

修饰方法:被 synchronized 修饰方法方法在同一时刻只能被一个线程访问。其他线程将会被阻塞,直到当前线程释放锁。
​
修饰代码块:其实和修饰方法差不多,只不过 修饰代码块可以 使用 类锁。方法如果要使用类锁,只能设置为静态的方法。

3.请简述一下 String、StringBuffer 和 StringBuilder 三者的区别?

参考答案:

String 为字符串常量,一旦创建不可以被修改,是线程安全的;String 类使用 final 修饰符,不可以被继承;String 的长度是不变的。适用于少量操作的字符串。 StringBuffer 为字符串变量,长度是可变的,线程安全。适用于多线程下在字符缓冲区进行大量字符串操作 StringBuilder 为字符串变量,长度是可变的,线程不安全。适用于单线程下在字符缓冲区进行大量字符串操作。 字符串操作在执行速度:StringBuilder > StringBuffer > String

  1. “equals” 与 “==”、“hashCode” 的区别和使用场景?

参考答案:

  • : == 用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据(注意是基本类型)或两个 引用变量是否相等,只能用操作符。 假如一个变量指向的数据是对象类型的,例如Objet obj 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() 相等的两个对象他们的 hashCode() 肯定相等,也就是用 equals() 对比是绝对可靠的。 hashCode() 相等的两个对象他们的 equals() 不一定相等,也就是 hashCode() 不是绝对可靠的。 所有对于需要大量并且快速的对比的话如果都用 equals() 去做显然效率太低,所以解决方式是,每当需要对比的时候,首先用 hashCode() 去对比,如果 hashCode() 不一样,则表示这两个对象肯定不相等(也就是不必再用 equals() 去再对比了),如果 hashCode() 相同,此时再对比他们的 equals(),如果 equals() 也相同,则表示这两个对象是真的相同了

5.Java 中深拷贝与浅拷贝的区别?

参考答案:

首先需要明白,浅拷贝和深拷贝都是针对一个已有对象的操作。那先来看看浅拷贝和深拷贝的概念。在 Java 中,除了基本数据类型(元类型)之外,还存在 类的实例对象 这个引用数据类型。而一般使用 『 = 』号做赋值操作的时候。对于基本数据类型,实际上是拷贝的它的值,但是对于对象而言,其实赋值的只是这个对象的引用,将原对象的引用传递过去,他们实际上还是指向的同一个对象。

而浅拷贝和深拷贝就是在这个基础之上做的区分,如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有真实的创建一个新的对象,则认为是浅拷贝。反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝。所以到现在,就应该了解了,所谓的浅拷贝和深拷贝,只是在拷贝对象的时候,对 类的实例对象 这种引用数据类型的不同操作而已。

总结来说:

1、浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。

2、深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。

6.谈谈 Error 和 Exception 的区别?

参考答案:

Error 是系统中的错误,不可预料的,这种异常发生后,会导致程序立即崩溃。只能通过修改代码,使错误不在出现,这种错误 无法被捕获。

Exception 则是可以预料的。在程序中如果过感觉某段代码会出现 异常,则可以使用 try catch 进行捕获 ,或者直接抛出异常。Exception 可分为 编译时异常(CheckedException) 和 运行时异常(RuntimeException)。运行时异常可以 忽略捕获操作(RuntimeException),编译时异常必须使用 try catch 进行捕获。

7.什么是反射机制?反射机制的应用场景有哪些?

参考答案:

Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。 应用场景:

  1. 逆向代码,例如反编译
  2. 与注解相结合的框架,如 Retrofit
  3. 单纯的反射机制应用框架,例如 EventBus(事件总线)
  4. 动态生成类框架 例如Gson

8.谈谈如何重写 equals() 方法?为什么还要重写 hashCode()?

参考答案:

equals:比较两个对象的地址是否相等

hashCode :一般用在集合里面,比如在hashMap 里面,存入元素的时候 会首先算出 哈希值,然后根据哈希值来确定元素的位置,对于在任何一个对象上调用hashCode 时,返回的 哈希值一定相等的。

为什么 需要重写 hashCode

给集合中存元素时,首先会获取 hashCode 的值,如果没有重写 hashCode ,他会直接将元素的地址转换成一个整数返回。如果我们创建了两个对象,两个对象的所有属性值都一样,在存入HashSet 时,第一个元素会直接存进去,第二个获取的 哈希值 和 第一个不同,所以第二个元素也会存进去,因为 jdk 默认不同的 hashCode 值,equals 一定返回false。所以 这两个值都会被存进去。但是这两个对象的属性值都是一样的,所以这样会造成数据的不唯一性。所以一般重写了 equals 后必须要重写 hashCode。

内存泄露的问题

想象一下,一个类 创建了两个对象,属性值不同,同时重写了 equals 和 hashCode 。然后将他们都存进了 HashSet 中。然后修改第二个 元素的值。最后将第二个元素充 set 集合中删除。 删除之后 则迭代进行打印,会发现第二个元素没有被删除掉,为什么呢? 因为在删除 某个元素时,会获取 hashCode 值,但是由于修改了属性值,导致获取的 哈希值和 存入时获取的不同,所以查找为空,jdk 认为该对象不在集合中,所以不会进行删除操作,但是用户任务 对象已经被删除,导致该对象长时间不能被释放,造成内存泄露。解决的办法是不要在执行的期间 修改与 HashCode 值相关的对象信息,如果非要修改,则必须先从集合中删除,更新数据后在添加到集合。

总结:

1.hashCode是为了提高在散列结构存储中查找的效率,在线性表中没有作用 
​
2.equals和hashCode需要同时覆盖。 
​
3.若两个对象equals返回true,则hashCode一定返回相同的int数。 
​
4.若两个对象equals返回false,则hashCode不一定返回不同的int数,但为不相等的对象生成不同hashCode值可以提高 哈希表的性能 
​
5.若两个对象hashCode返回相同int数,则equals不一定返回true。 
​
6.若两个对象hashCode返回不同int数,则equals一定返回false。 
​
7.同一对象在执行期间若已经存储在集合中,则不能修改影响hashCode值的相关信息,否则会导致内存泄露问题。 

9.Java 中 IO 流分为几种?它们之间有什么区别?

参考答案:

IO 流分为几种

Java中的流分为两种,一种是字节流,另一种是字符流,分别由四个抽象类来表示(每种流包括输入和输出两种所以一共四个):InputStream,OutputStream,Reader,Writer。Java中其他多种多样变化的流均是由它们派生出来的.

字符流和字节流是根据处理数据的不同来区分的。字节流按照8位传输,字节流是最基本的,所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。

  • 1.字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串;
  • 2.节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以。

读文本的时候用字符流,例如txt文件。读非文本文件的时候用字节流,例如mp3。理论上任何文件都能够用字节流读取,但当读取的是文本数据时,为了能还原成文本你必须再经过一个转换的工序,相对来说字符流就省了这个麻烦,可以有方法直接读取。

字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为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开始支持。

10.谈谈你对 Java 泛型中类型擦除的理解,并说说其局限性?

参考答案:

1:类型擦除,发生在编译过程。指的是,所有的泛型信息都会被擦除(默认继承Object)

2:已解决的局限: 1 ArrayList(String> 不能够添加Integer类型的数据;通过 编译器先检查代码中的泛型的类型 解决 2 编辑器从ArrayList(String>中泛型中获取值,都有Object 强转 String 3 父类定义泛型,子类实现;实现的 重载,实际上,编译器会变成重写。通过 编译器的桥方法解决

3:未解决的局限: 1)泛型类型变量,不能是,基本数据类型 2)运行时,无法检测类型;例如:object instanceof ArrayList(String> 这个逻辑无法实现 3)泛型类型,无法在静态方法和静态变量中使用;如下:

public class TestClass {
public static T getSome() {
return null;
}
}

这一段逻辑,添加 static字段,编译器报错

11.String 为什么要设计成不可变的?

参考答案:

1:字符串常量池的需要

当创建一个 String 对象时,如果此字符串已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象

如果允许改变,那么将导致各种逻辑错误,比如改变一个对象将会影响另一个独立对象,严格来说,这种常量池的思想是一种优化手段

2:允许String对象缓存 HashCode

java 中 String 对象的哈希码会被频繁的使用,比如在 hashMap中。字符串的不变形保证了hash码的唯一性,因此可以放放心的进行缓存。这也是一种优化手段,意味着不必没说都计算新的哈希码。在 String 类中有 private int hash 来缓存hashcode

3:安全性

String 被许多的类来当做参数,如 网络url,文件路径path 等等,如果String 不是固定的,将会引起各种安全隐患

12.说说你对 Java 注解的理解?

参考答案:

本质:做一个标识,通过这个标识对代码规范、变量值做一些修饰。主要划分为三类

Source 仅仅存在在.java文件,编译成.class文件就消失了。作用为:让开发者按照注解的规范编写代码。例如:@OverRide
Class 在前期编译期的流程中,会被处理成.class内容,与原生代码效率几乎相同。作用为:自动生成.class文件,做一些辅助性工作。例如:ButterKnife、GreenDao、ARouter 效率和原生代码相当
Runtime 编译成.class文件之后,依旧以注解的方式存在。而是在运行期生效。作用为:在运行期,通过反射做一些辅助性工作。例如:xUtils 由于集中使用遍历+反射,因此效率较低。而且在9.0禁用反射

13.谈一谈 Java 成员变量、局部变量和静态变量的创建和回收时机?

参考答案:

成员变量:生命周期伴随类对象,类对象回收时回收 存在堆里 静态变量:不回收 在方法区 随着类的加载而加载,随着类的消失而消失,由于类需要非常长时间的不使用,不利用,不关联,才有可能会被回收机制回收, 所以静态成员变量的生命周期特别长,除非是共享数据,否则不建议使用静态; 局部变量:方法调用时创建 方法结束时被标记为可回收 存在栈里。

猜你喜欢

转载自blog.csdn.net/m0_64420071/article/details/127215833