Android -- 面试复习指南之 Java 基础
Posted Kevin-Dev
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android -- 面试复习指南之 Java 基础相关的知识,希望对你有一定的参考价值。
Java 基础中考察频率比较高的是 Object
、String
、面向对象、集合、泛型和反射。
一、抽象类与接口的区别?
- 抽象类可以提供成员方法的实现细节,而接口中只能存在 public 抽象方法;
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的;
- 接口中不能含有构造器、静态代码块以及静态方法,而抽象类可以有构造器、静态代码块和静态方法;
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口;
- 抽象类访问速度比接口速度要快,因为接口需要时间去寻找在类中具体实现的方法;
- 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。如果你往接口中添加方法,那么你必须改变实现该接口的类。
- 接口更多的为了约束类的行为,可用于解耦,而抽象类更加侧重于代码复用。
二、分别讲讲 final,static,synchronized 关键字可以修饰什么,以及修饰后的作用?
- final :可以修饰 类,方法,字段。
修饰类:该类不会被继承。 修饰方法:该方法不能被重写。 修饰字段:被修饰的 字段必须
赋初始值,并且不能被改变。如果字段是引用类型的。那么他将不能引用别 的对象,但是当前的对象内的 属性值是可以改变的。
- static :可以修饰内部类,方法,字段。
修饰内部类:被static修饰 的内部类可以直接作为一个 普通的类来使用,而不需先实例一个外部列。 修饰方法:调用该方法的时候只需要类名 .
方法就可以直接调用,不需要创建对象。 修饰字段:通过类名 . 的方式可以直接 获取 或者 赋值。
- synchronized 可以修饰 方法,代码块
修饰方法:被 synchronized 修饰方法方法在同一时刻只能被一个线程访问。其他线程将会被阻塞,直到当前线程释放锁。
修饰代码块:其实和修饰方法差不多,只不过 修饰代码块可以 使用 类锁。方法如果要使用类锁,只能设置为静态的方法。
三、请简述一下 String、StringBuffer 和 StringBuilder 的区别?
String 为字符串常量,一旦创建不可以被修改,是线程安全的;String 类使用 final 修饰符,不可以被继承;String
的长度是不变的。适用于少量操作的字符串。 StringBuffer
为字符串变量,长度是可变的,线程安全。适用于多线程下在字符缓冲区进行大量字符串操作 StringBuilder
为字符串变量,长度是可变的,线程不安全。适用于单线程下在字符缓冲区进行大量字符串操作。 字符串操作在执行速度:StringBuilder >
StringBuffer > String
四、“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() 也相同,则表示这两个对象是真的相同了
五、Java 中深拷贝与浅拷贝的区别?
首先需要明白,浅拷贝和深拷贝都是针对一个已有对象的操作。那先来看看浅拷贝和深拷贝的概念。在 Java
中,除了基本数据类型(元类型)之外,还存在 类的实例对象 这个引用数据类型。而一般使用 『 =
』号做赋值操作的时候。对于基本数据类型,实际上是拷贝的它的值,但是对于对象而言,其实赋值的只是这个对象的引用,将原对象的引用传递过去,他们实际上还是指向的同一个对象。而浅拷贝和深拷贝就是在这个基础之上做的区分,如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有真实的创建一个新的对象,则认为是浅拷贝。反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝。所以到现在,就应该了解了,所谓的浅拷贝和深拷贝,只是在拷贝对象的时候,对
类的实例对象
这种引用数据类型的不同操作而已。总结来说:1、浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。2、深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
六、谈谈 Error 和 Exception 的区别?
Exception是java程序运行中可预料的异常情况,咱们可以获取到这种异常,并且对这种异常进行业务外的处理。
Error是java程序运行中不可预料的异常情况,这种异常发生以后,会直接导致JVM不可处理或者不可恢复的情况。所以这种异常不可能抓取到,比如OutOfMemoryError、NoClassDefFoundError等。
其中的Exception又分为检查性异常和非检查性异常。两个根本的区别在于,检查性异常 必须在编写代码时,使用try
catch捕获(比如:IOException异常)。非检查性异常
在代码编写使,可以忽略捕获操作(比如:ArrayIndexOutOfBoundsException),这种异常是在代码编写或者使用过程中通过规范可以避免发生的。
切记,Error是Throw不是Exception 。
七、什么是反射机制?反射机制的应用场景有哪些?
Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为
Java 语言的反射机制。
应用场景:
1.逆向代码,例如反编译
2.与注解相结合的框架,如 Retrofit
3.单纯的反射机制应用框架,例如 EventBus(事件总线)
4.动态生成类框架 例如 Gson
八、谈谈如何重写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值的相关信息,否则会导致内存泄露问题。
九、Java 中 IO 流分为几种?BIO,NIO,AIO 有什么区别?
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开始支持。
十、谈谈你对Java泛型中类型擦除的理解,并说说其局限性?
Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。
如在代码中定义的List和List等类型,在编译后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。类型擦除也是Java的泛型实现方法与C++模版机制实现方式之间的重要区别。
十一、String为什么要设计成不可变的?
1,字符串常量池的需要
当创建一个 String 对象时,如果此字符串已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象
如果允许改变,那么将导致各种逻辑错误,比如改变一个对象将会影响另一个独立对象,严格来说,这种常量池的思想是一种优化手段 2,允许String对象缓存 HashCode
java 中 String 对象的哈希码会被频繁的使用,比如在hashMap中。字符串的不变形保证了hash码的唯一性,因此可以放放心的进行缓存。这也是一种优化手段,意味着不必没说都计算新的哈希码。在String 类中有 private int hash 来缓存hashcode 3,安全性 String 被许多的类来当做参数,如 网络url,文件路径path 等等,如果String不是固定的,将会引起各种安全隐患
十二、说说你对Java注解的理解?
注解是一种在源代码中标注的特殊标记,可以标注源代码中的类、属性、方法、参数等。 一、注解的保留级别:
SOURCE:注解仅保留在源码级别,被编译器忽略; CLASS:注解在编译时由编译器保留,但JVM会忽略;
RUNTIME:注解由JVM保留,运行时环境可以使用它。 二、注解的应用场景:
1、Source:APT技术,在编译期能够获取注解与注解声明的类包括类中所有成员信息,一般用于生成额外的辅助类。
2、Class:字节码增强,在编译出Class后,通过修改Class数据来实现修改代码逻辑的目的。可以使用注解来区分是否需要修改,或者修改为不同的逻辑。
3、Runtime:反射,在程序运行期间,通过反射技术动态获取注解与其元素,来完成不同的逻辑判定。
十三、谈一谈Java成员变量,局部变量和静态变量的创建和回收时机?
成员变量:生命周期伴随类对象,类对象回收时回收 存在堆里 静态变量:不回收 在方法区
随着类的加载而加载,随着类的消失而消失,由于类需要非常长时间的不使用,不利用,不关联,才有可能会被回收机制回收,
所以静态成员变量的生命周期特别长,除非是共享数据,否则不建议使用静态; 局部变量:方法调用时创建 方法结束时被标记为可回收 存在栈里
十四、请说说Java中String.length()的运作原理?
以上是关于Android -- 面试复习指南之 Java 基础的主要内容,如果未能解决你的问题,请参考以下文章
2022Android面试题合集最新整理版,移动开发架构师筑基必备