Skip to content

Latest commit

 

History

History
286 lines (181 loc) · 19.2 KB

File metadata and controls

286 lines (181 loc) · 19.2 KB

Java Question List

1. 面向对象的三个基本元素和五个原则

  1. 三个基本元素:
  • 封装:从字面上理解就是包装的意思,是指利用抽象数据类型,将数据和关于数据的操作封装起来,使其成为一个不可分割的独立实体。数据将会被保护在抽象数据类型的内部,仅能够通过暴露在表面的操作(public方法,比如setter和getter)来与这个对象进行交流和交互。用户不知道对象的内部细节,但是通过该对象提供的接口来访问对象。其好处是:减少耦合,方便地在未来修改调整自己,更加有把握地(精确地)控制成员,隐藏信息,实现细节。

  • 继承: 继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。

  • 多态: 多态性是指允许不同类的对象对同一消息作出 响应。使用相同的消息,使得类作出不同的反应(继承为我们使用多态打下了基础)。Java实现多态有三个必要条件:继承、重写、向上转型。

2. 五个基本原则(SOLID):

  • 单一职责原则(Single-Resposibility Principle):一个类,最好只做一件事,只有一个引起它的变化。单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。
  • 开放封闭原则(Open-Closed principle):软件实体,对扩展开放,对修改封闭的。
  • Liskov替换原则(Liskov-Substituion Principle):子类必须能够替换其基类。这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础。
  • 依赖倒置原则(Dependecy-Inversion Principle):依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。
  • 接口隔离原则(Interface-Segregation Principle):使用多个小的专门的接口,而不要使用一个大的总接口。

3. 抽象类和接口的区别?Link

  • 从Java语言角度讲,interface只允许有一个默认方法(Java8),抽象类可以有实现方法。
  • 在设计层级理解他们的不同:
  1. 抽象的层次不同:抽象类对类的整体(包括属性,行为)都可以进行抽象,接口对类的局部进行抽象,具体来说接口仅仅是对类的行为进行抽象。
  2. 跨域不同:抽象类是 从各种子类中提取相似的部分,然后泛化成抽象类,子类可以继承这样的抽象类。 实现接口是 不存在is-a的关系的类们,你不可以称同样可以飞行的飞机和鸟为同一个抽象类,但是他们可以有同样的接口fly-able。抽象类的父类和派生类在概念上一致,接口的原生类和派生类在仅仅在局部行为上一致。
  3. 设计层次不同:抽象类是从一堆在底层的子类们来进行抽象提取,从下往上,从而产生抽象类;接口是在直接定义的高度来声明的,然后从这个高度上往下实现此接口。抽象类是自底向上抽象而来的,接口是自顶向下设计出来的。

4.序列化是什么?如何实现它?

序列化是一种将对象转换为字节流的过程,目的是为了将该对象存储到内存中,等后面再次构建该对象时可以获取到该对象先前的状态及数据信息。Java中,有两种方式可以实现序列化,既可以实现Serializable接口,也可以实现Parcelable接口。然而,在Android中,我们不应该使用Serializable接口。因为Serializable接口使用了反射机制,这个过程相对缓慢,而且往往会产生出很多临时对象,这样可能会触发垃圾回收器频繁地进行垃圾回收。相比而言,Parcelable接口比Serializable接口效率更高,性能方面要高出10x多倍。

5.对字符串进行 ==equals() 操作时有什么区别?

== 比较两个字符串的地址,初学者很经常拿来比较其内容,将会导致出现不等的情况。 equals()是String这个类重写的一个方法,平常的类的equals()也仅仅是比较两个变量的地址,而String类的equals()重写后,将依次比较其串中的字符。

6.hashCode()equals() 何时使用?

一般是在想要人性化地(而不是计算机式地,比较地址那样)比较两个对象的时候,我们需要使用这两个方法,或者说我们要重写这两个方法,而且有如下的原则:

  1. hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;
  2. 如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode一定要相同;
  3. 如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点;
  4. 两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”。

7.什么是内存泄露,Java 是如何处理它的?

总的来说是:保留下来却永远不再使用的对象引用 它包括: 1. 静态集合类引起内存泄漏, 2. 当集合里面的对象属性被修改后,再调用remove()方法时不起作用, 3. 监听器, 4. 各种连接 5. 内部类和外部模块的引用 6. 单例模式 详情可见:http://note.youdao.com/noteshare?id=376631d4640128dc55646c8e577cc3ab

8.比较 HashSetTreeSet

TreeSet是基于二叉树实现的,其中的数据是自动排序好的。不允许放入null值。

HashSet是基于Hash表实现的,其中的数据是无序的,允许放入null值。

详细的笔记在:http://note.youdao.com/noteshare?id=4a3e44e90105d9906eb308317bc816bb

9.本地变量、实例变量以及类变量之间的区别?

本地变量就是局部变量,它在方法或者代码块里被声明并使用,其内存中的位置是栈里,没有默认初始化值,生命周期很短。 实例变量是没有被static修饰的成员变量,它属于一个类的一个实例。每次new一个实例,这样的变量也同时new一遍,其位置在堆区中,有默认初始化的值,生命周期和它所在的实例一样长。 类变量,又称静态变量,它是被static修饰的成员变量,它属于一个类,被所有实例共享。每次new一个实例,这样的变量并不会被new一遍,其内存位置在方法区内。可以通过类名直接访问。有默认的初始化值,生命周期很长。

10.修饰符transient的作用是什么?

  • transient 是一个类型修饰符,仅仅能用来修饰变量。在此字段所在的对象进行序列化的时候,这个字段不会被序列化。

11.JVM内存管理/内存区域

注意和JMM内存模型区分

JVM运行时数据区的划分:

  • VM Stack 虚拟机栈
  • Native Method Stack 本地方法栈
  • Heap 堆
  • Method Area 方法区 存储已经被虚拟机加载的类信息/常量/静态变量
  • Program Counter Register 程序计数器

Runtime Data

12.GC垃圾回收

  • 判断对象是否存活?

    • 引用计数法,缺点是很难解决对象之间循环引用的问题
    • 根节点搜索(可达性分析 Reachability Analysis),通过一系列的“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots之间没有任何引用链相连时,证明此对象是不可用的。
  • Java语言中,可以作为GC Roots的对象包括下面几种:

    • 虚拟机栈(栈帧中的本地变量(即局部变量)表)中引用的对象
    • 本地方法栈中Native方法引用的对象
    • 方法区中静态属性引用的对象
    • 方法区中常量引用的对象
  • 垃圾收集算法

    • 标记-清除算法,存在效率和空间问题,产生大量不连续的内存碎片
    • 复制算法,将内存的一部分用作回收,但是实现简单,运行高效,一般用来回收新生代
    • 标记-整理算法,一般用于回收老年代
    • 分代收集
      • 新生代中,每次垃圾收集时都有大批对象死去,只有少量存活,所以使用复制算法
      • 老年代中,对象存活率高,没有额外空间对他进行分配担保,所以使用“标记-清除/整理算法”来进行回收
  • 无用的类?类要同时满足一下三个条件才能算是无用的类

    • 该类所有的实例都已经被回收,Java堆中不存在该类的任何实例
    • 加载该类的ClassLoader已经被回收
    • 该类对应的java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过发射访问该类的方法

13.JVM虚拟机的类加载机制

类的生命周期:

  • Loading 加载
  • Linking 连接 :
    • Verification 验证
    • Preparation 准备
    • Resolution 解析
  • Initialization 初始化 以下5种情况必须对类进行“初始化”:
    • 遇到new/getstatic/putstatic/invokestatic这4条字节码指令时
    • 使用java.lang.reflect包的方法对类进行反射调用时
    • 初始化一个类时,如果其父类尚未初始化,那么要先触发其父类初始化
    • 虚拟机启动时,用户需要指定一个要执行的主类(包含main方法),虚拟机会先初始化这个主类
    • JDK1.7动态语言支持时。。。
  • Using 使用
  • Unloading 卸载

双亲委派模型:

  • 启动类加载器(Bootstrap ClassLoader)
  • 扩展类加载器(Extension ClassLoader)
  • 应用程序类加载器(Application ClassLoader)

如果一个类加载器收到了类加载的请求,他首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,只有父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。

14.Java中CAS详解

关联概念:可重入锁|不可重入锁

不可重入锁,一个线程持有锁时,其他线程不能进入同步代码块,是一种悲观锁。

CAS,compare and swap的缩写,中文翻译成比较并交换。

独占锁是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。而另一个更加有效的锁就是乐观锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁用到的机制就是CAS,Compare and Swap。

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前 值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。

通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新 值 B,然后使用 CAS 将 V 的值从 A 改为 B。如果 V 处的值尚未同时更改,则 CAS 操作成功。

类似于 CAS 的指令允许算法执行读-修改-写操作,而无需害怕其他线程同时 修改变量,因为如果其他线程修改变量,那么 CAS 会检测它(并失败),算法 可以对该操作重新计算。

15.JMM(Java Memory Model)Java内存模型

img

Java内存模型规定了所有的变量都存储在主内存(Main Memory)中 每条线程都有自己的工作内存(Working Memory),线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取/赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。线程间变量值的传递,均需要通过主内存来完成。

虚拟机实现时必须保证下面的每一种操作都是原子的,不可再分的

  • lock 锁定
  • unlock 解锁
  • read 读取
  • load 载入
  • use 使用
  • assign 赋值
  • store 存储
  • write 写入

16.同步机制 以及 原子性/可见性/有序性

原子性/可见性/有序性

Java的内存模型是围绕着在并发过程中,如何处理原子性/可见性/有序性这三个特征来建立的

  • 原子性:原子的/不可再分的
  • 可见性:一条线程修改了共享变量的值,其他线程能够立即得知这个修改
    • volatile
    • final
    • synchronized
  • 有序性:如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序*的。即“线程内表现为串行”。
    • volatile
    • synchronized

同步机制以及volatile

  • volatile是啥?最轻量级的同步机制

    • 保证变量对所有线程的可见性,新值立即同步到主内存,每次使用前立即从主内存刷新
    • 禁止指令重排优化

    volatile修饰的变量,赋值后多了一个“lock addl $0x0,(%esp)”,相当于一个内存屏障,指令重排时不能把后面的指令重排序到内存屏障之前的位置

  • i++用volatile是否能线程安全?

    i++并非原子操作,在getstatic指令获取i的值的时候,获取的是正确的,最新的值,但是在add的时候,有可能其他线程已经把i的值加大了,再写回主内存的时候就不对了

    Java中的运算并非原子操作,导致volatile变量的运算在并发下一样是不安全的

    所以在运算中,仍要通过加锁来保证原子性

17.HashMap的原理

了解HashMap以及LinkedHashMap的源码

概括的说,HashMap 是一个关联数组、哈希表,它是线程不安全的,允许key为null,value为null。遍历时无序。  其底层数据结构是数组称之为哈希桶,每个桶里面放的是链表,链表中的每个节点,就是哈希表中的每个元素。  在JDK8中,当链表长度达到8,会转化成红黑树,以提升它的查询、插入效率,它实现了Map<K,V>, Cloneable, Serializable接口。!

hashmap_table

hashmap_put

https://blog.csdn.net/zxt0601/article/details/77413921

LinkedHashMap结合Android中的LruCache学习

LinkedHashMap继承HashMap并实现了Map接口,同时具有可预测的迭代顺序(按照插入顺序排序)。它与HashMap的不同之处在于,维护了一条贯穿其全部Entry的双向链表(因为额外维护了链表的关系,性能上要略差于HashMap,不过集合视图的遍历时间与元素数量成正比,而HashMap是与buckets数组的长度成正比的),可以认为它是散列表与链表的结合。

 		/**
     * HashMap.Node subclass for normal LinkedHashMap entries.
     */
    static class LinkedHashMapEntry<K,V> extends HashMap.Node<K,V> {
        LinkedHashMapEntry<K,V> before, after;
        LinkedHashMapEntry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }

18.ThreadLocal

很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

19.线程池

Executors 类里的工厂方法可以创建很多类型的线程池:

  • newSingleThreadExecutor():包含单个线程和无界队列的线程池,同一时间只能执行一个任务
  • newFixedThreadPool():包含固定数量线程并共享无界队列的线程池;当所有线程处于工作状态,有新任务提交时,任务在队列中等待,直到一个线程变为可用状态
  • newCachedThreadPool():只有需要时创建新线程的线程池
  • newWorkStealingThreadPool():基于工作窃取(work-stealing)算法的线程池,后面章节

20.设计模式

  • 单例模式(延迟加载线程安全的静态内部类实现)
  • 观察者模式
  • 责任链模式
  • 工厂模式
  • builder模式
  • 命令模式

Android 面试之查漏补缺

要准备第二次杭州之行了,目的是拿到一个网易|有赞的offer

  • ConcurrentHashMap原理
  • CAP原则又称CAP定理,指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得 [1] 。

ConcurrentHashMap的设计与实现非常精巧,大量的利用了volatile,final,CAS等lock-free技术来减少锁竞争对于性能的影响

ConcurrentHashMap采用了分段锁的设计,允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的Hashtable,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。

HashTable虽然性能上不如ConcurrentHashMap,但并不能完全被取代,两者的迭代器的一致性不同的,HashTable的迭代器是强一致性的,而ConcurrentHashMap是弱一致的。 ConcurrentHashMap的get,clear,iterator 都是弱一致性的。 Doug Lea 也将这个判断留给用户自己决定是否使用ConcurrentHashMap。

顺便罗列一下目前我能想到的问题

  • ARouter路由框架的原理
  • Retrofit的原理
  • 组件之间跨进程通信/跳转问题解决方案 aidl/binder直接把多个Map合并共同调度

组件化之跨进程一

组件化之跨进程二

  • JDK1.6之后对synchronized锁机制的优化

synchronized

锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。

  • 死锁的四个必要条件?

  • 怎么避免死锁?锁的申请就没有发生交叉

    在涉及到要同时申请两个锁的方法中,总是以相同的顺序来申请锁,比如总是先申请 id 大的账户上的锁 ,然后再申请 id 小的账户上的锁,这样就无法形成导致死锁的那个闭环。