Android面试之J2SE基础

1. 九种基本数据类型的大小以及他们的封装类

Java中九中基本数据类型为:boolean、byte、short、char、int、float、double、long以及void。对应的封装类为Boolean、Byte、Short、Character、Integer、Float、Double、Long、Void。

  • boolean的取值范围:true/false。
  • byte:一个字节(8位),有正负(拿一位作为符号位),-2的-7次方~2的7次方-1。
  • short:2个字节,有正负,-2的15次方~2的15次方-1。
  • char:在Java中是Unicode编码,2个字节,无正负,0~2的16次方-1。
  • int:4个字节,有正负,-2的31次方~2的31次方-1。
  • float:4个字节,有正负。
  • long:8个字节,有正负,-2的63次方~2的63次方-1。
  • double:8个字节,Java中默认的小数位double类型。

2. switch能否用String做参数

  • 在Java7之前 switch只允许int类型(向下兼容)以及enum做参数,在Java7中,String被加上了,所以java7中允许作为switch的参数的有:byte、char、short、int、String、枚举类型。
  • 在switch语句中,String的比较用的是String.equals,因此可以放心的使用。需要注意的是,传给switch的String变量不能为null,同时switch的case子句中使用的字符串也不能为null

代码如下:

public class Test {  
    public void test(String str) {
        switch(str) {
            case "abc":
                System.out.println("abc");
                break;
            case "def":
                System.out.println("def");
                break;
            default:
                System.out.println("default");
            }
     }
}

其内部实现如下:

public class Test {
    public void test(String str) {
        int i = -1;
        switch(str.hashCode()) {
        case 96354: // "abc".hashCode()
            if (str.equals("abc")) {
              i = 0;
            }
            break;
        case 99333: // "def".hashCode()
            if (str.equals("def")) {
                i = 1;
            }
            break;
        default:
            break;
        } 
        switch(i) {
        case 0:
            System.out.println("abc");
            break;
        case 1:
            System.out.println("def");
            break;
        default:
            System.out.println("default");
        }
    }
}
  • 如果switch传入的null,那么在运行时对一个null对象调用hashCode方法会出现NullPointerException。
  • 如果switch的case写的是null,那么在编译时无法求出hashCode,因此在编译时就会报错了
  • switch支持String只是一个语法糖,由javac来负责生成相应的代码。底层的JVM在switch上并没有进行修改。

3. equals与==的区别

java中的数据类型可分为两大类:

  1. 基本数据类型,也称原始数据类型。

    • 他们之间的比较,应用双等号(==),比较的是他们的值。
  2. 复合数据类型(对象)

    • 当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址,所以,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。
    • JAVA当中所有的类都是继承于Object这个基类的,在Object中的基类中定义了一个equals的方法,这个方法的初始行为是比较对象的内存地 址,但在一些类库当中这个方法被覆盖掉了,如String,Integer,Date在这些类当中equals有其自身的实现,而不再是比较类在堆内存中的存放地址了。
    • 对于复合数据类型之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是基于他们在内存中的存放位置的地址值的,因为Object的equals方法也是用双等号(==)进行比较的,所以比较后的结果跟双等号(==)的结果相同。

4. Object有哪些公有方法?

  • protected Object clone();创建并返回此对象的一个副本(默认浅拷贝),只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常 。
  • public boolean equals(Object obj)指示其他某个对象是否与此对象相等(默认比较两个对象的内存地址) 。
  • protected void finalize():当垃圾回收器确定不存在该对象时,由该对象的垃圾回收器调用此方法。
  • public Class getClass():返回此Object的运行时类 。
  • public int hashCode():返回该对象的哈希值(默认为内存地址)。
  • public void notify():唤醒在此对象监视器上等待的单个线程 。
  • public void notifyAll():唤醒在此对象监视器上等待的所有线程。
  • public String toString():返回该对象的字符串表示 。
  • public void wait():在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待 。
  • public void wait(long timeout):在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待 。
  • public void wait(long timeout, int nanos) :在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。

5. Java中的四种引用以及用到的场景

Java中引用分为强引用,软引用,弱引用以及虚引用。

  • 强引用(StrongReference):是使用最普遍的引用。如果一个对象还存在引用,当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。像Object object = new Object()就是典型的强引用。
  • 软引用(SoftReference):如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
  • 弱引用(WeakReference):弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。Java中提供了WeakHashMap来满足这一常见需求。
  • 虚引用(PhantomReference):“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
    虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。

6. Hashcode的作用

  • hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合有HashMap,HashTable,HashSet等。
  • 根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称为散列值。

7. ArrayList、LinkedList、Vector的区别

  • 都实现了List接口
  • ArrayList内部实现是个数组,其大小将会动态地增长,内部的元素可以直接通过get与set方法进行访问。
  • LinkedList 是一个双链表,在添加和删除元素时具有比ArrayList更好的性能,但在get与set方面弱于ArrayList。
  • Vector 和ArrayList类似,但属于强同步类。如果你的程序本身是线程安全的(没有在多个线程之间共享同一个集合/对象),那么使用ArrayList是更好的选择。
  • Vector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间,而ArrayList每次对size增长50%。 LinkedList 实现了 Queue 接口,该接口比List提供了更多的方法,包括 offer(),peek(),poll()等。
  • 注意: 默认情况下ArrayList的初始容量为11,所以如果可以预估数据量的话,分配一个较大的初始值属于最佳实践,这样可以减少调整大小的开销。

8. String、StringBuffer以及StringBuilder的区别

  • String:字符串常量,字符串长度不可变
  • StringBuffer:字符串变量,线程安全。如果要频繁的对字符串内容进行修改,出于效率考虑最好使用StringBuffer,如果想转换成String类型,可以调用StringBuffer的toString()方法。
  • StringBuilder:字符串变量,非线程安全。在内部,StringBuilder被当做是一个包含字符序列的数组。
  • String类型与StringBuffer的主要性能区别:String是不可变的对象, 因此在每次对String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,性能就会降低。使用 StringBuffer 类时,每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。所以多数情况下推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。
  • 基本原则:如果要操作少量的数据,用String ;单线程操作大量数据,用StringBuilder ;多线程操作大量数据,用StringBuffer。

9. Map、Set、List、Queue、Stack的特点与用法

  • Map

    • Map是键值对,键Key是唯一不能重复的,一个键对应一个值,值可以重复。
    • TreeMap可以保证顺序,HashMap不保证顺序,即为无序。
    • Map中可以将Key和Value单独取出来,其中的KeySet()方法可以将所有的key抽取成一个Set集合。而Values()可以将map中所有的value抽取成一个集合。

      Android面试之J2SE基础_第1张图片

  • Set

    • 不包含重复元素的集合,set中最多一个null元素。
    • 只能用Iterator实现单项遍历,Set中没有同步的方法。

  • List

    • 有序可以重复的集合。
    • 可以在任意位置增加删除元素。
    • 用Iterator实现单项遍历,也可以用ListIterator实现双向遍历。

      Android面试之J2SE基础_第2张图片

  • Queue
    • 遵循先进先出的原则。
    • 使用时尽量避免使用add()和remove()方法,而是使用offer()来添加元素,使用poll()来移除元素,他的优点时可以通过返回值来判断是否成功。
    • LinkedList实现了Queue接口。
    • Queue通常不允许插入null元素。
  • Stack
    • 遵循先进后出的原则。
    • 继承Vector。
    • 通过五个操作对Vector进行扩展,允许将向量视为堆栈,提供push和pop操作,以及取堆顶点的peep()方法,测试堆栈是否为空的empty()方法。

10. HashMap和ConcurrentHashMap的区别,HashMap的底层源码

  • HashMap不是线程安全的,ConcurrentHashMap是线程安全的。
  • ConcurrentHashMap具体是怎么实现线程安全的呢,肯定不可能是每个方法加synchronized,那样就变成了HashTable。
  • 从ConcurrentHashMap代码中可以看出,它引入了一个“分段锁”的概念,具体可以理解为把一个大的Map拆分成N个小的HashTable,根据key.hashCode()来决定把key放到哪个HashTable中。
  • 在ConcurrentHashMap中,就是把Map分成了N个Segment,put和get的时候,都是现根据key.hashCode()算出放到哪个Segment中。
  • 在hashMap的基础上,ConcurrentHashMap通过把整个Map分为N个Segment(类似HashTable),可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。
  • hashMap源码:HashMap源码分析(JDK1.8)

11. HashMap和HashTable的区别

集合 线程安全 键和值 效率 同步 方法
HashMap 不安全 允许有null的键和值 效率较高 方法不是Synchronize的要提供外同步 有containsValue和containsKey方法
HashTable 安全 不允许有null的键和值 效率较低 方法是Synchronize的 有contains方法

12. TreeMap、HashMap、LinkedHashMap的区别

  • Map主要用于存储健值对,根据键得到值,因此不允许键重复(重复了覆盖了),但允许值重复。
  • Hashmap 是一个最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。HashMap最多只允许一条记录的键为Null,允许多条记录的值为 Null,HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,或者使用ConcurrentHashMap。
  • Hashtable与 HashMap类似,它继承自Dictionary类,不同的是:它不允许记录的键或者值为空;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了 Hashtable在写入时会比较慢。
  • LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的。也可以在构造时用带参数,按照应用次数排序。在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。
  • TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。

13. Collection包结构,与Collections的区别

  • Collection是集合类的上级接口,子接口主要有Set 和List、Map。
  • Collections是针对集合类的一个帮助类,提供了操作集合的工具方法:一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。

    Android面试之J2SE基础_第3张图片

14. try catch finally,try里面有return,finally还执行么?

  • 在try中没有异常的情况下try、catch、finally的执行顺序 try — finally
  • 如果try中有异常,执行顺序是try — catch — finally
  • 如果try中没有异常并且try中有return这时候正常执行顺序是try —- finally — return
  • 如果try中有异常并且try中有return这时候正常执行顺序是try—-catch—finally— return
  • 总之 finally 永远执行!
  • try-catch-finally里都没有return ,finally 之后有个return ,如果try中有异常,finally执行完后,还能执行return吗?那是不可能执行的了,try中有异常以后,根据java的异常机制先执行catch后执行finally,此时错误异常已经抛出,程序因异常而终止,所以你的return是不会执行的。
  • finally中的return会覆盖掉其它位置的return

15. Exception与Error包结构,OOM你遇到过哪些情况,SOF你遇到过哪些情况?

java异常结构图:
Android面试之J2SE基础_第4张图片

  • Throwable

    • Throwable是Java语言中所有错误或异常的超类。
    • Throwable包含了两个子类:Error和Exception。他们通常用于指示发生了异常情况。
    • Throwable包含了其线程创建时线程执行堆栈的快照,它提供了printStackTrace()等方法用于获取堆栈跟踪数据等信息。
  • Exception

    • Exception及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件。
  • RuntimeException
    • RuntimeException是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。
    • 编译器不会检查RuntimeException异常。 例如,除数为零时,抛出ArithmeticException异常。RuntimeException是ArithmeticException的超类。当代码发生除数为零的情况时,倘若既”没有通过throws声明抛出ArithmeticException异常”,也”没有通过try…catch…处理该异常”,也能通过编译。这就是我们所说的”编译器不会检查RuntimeException异常”!
    • 如果代码会产生RuntimeException异常,则需要通过修改代码进行避免。 例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生!
  • Error
    • 和Exception一样, Error也是Throwable的子类。 它用于指示合理的应用程序不应该试图捕获的严重问题,大多数这样的错误都是异常条件。
  • Java将可抛出(Throwable)的结构分为三种类型: 被检查的异常(Checked Exception),运行时异常(RuntimeException)和错误(Error)。
    • 运行时异常
      • 定义:RuntimeException及其子类都被称为运行时异常。
      • 特点:Java编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既”没有通过throws声明抛出它”,也”没有用try-catch语句捕获它”,还是会编译通过。例如,除数为零时产生的ArithmeticException异常,数组越界时产生的IndexOutOfBoundsException异常,fail-fail机制产生的ConcurrentModificationException异常等,都属于运行时异常。
      • 虽然Java编译器不会检查运行时异常,但是我们也可以通过throws进行声明抛出,也可以通过try-catch对它进行捕获处理。
      • 如果产生运行时异常,则需要通过修改代码来进行避免。 例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生!
    • 被检查的异常
      • 定义 : Exception类本身,以及Exception的子类中除了”运行时异常”之外的其它子类都属于被检查异常。
      • 特点 : Java编译器会检查它。 此类异常,要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。例如,CloneNotSupportedException就属于被检查异常。当通过clone()接口去克隆一个对象,而该对象对应的类没有实现Cloneable接口,就会抛出CloneNotSupportedException异常。
      • 被检查异常通常都是可以恢复的。
    • Error
      • 定义 : Error类及其子类。
      • 特点 : 和运行时异常一样,编译器也不会对错误进行检查。
      • 当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误。程序本身无法修复这些错误的。例如,VirtualMachineError就属于错误。
  • OOM
    • OutOfMemoryError异常
      • 除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(OOM)异常的可能
      • Java Heap 溢出
      • 一般的异常信息:java.lang.OutOfMemoryError:Java heap spaces
      • java堆用于存储对象实例,我们只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,就会在对象数量达到最大堆容量限制后产生内存溢出异常。
      • 出现这种异常,一般手段是先通过内存映像分析工具(如Eclipse Memory Analyzer)对dump出来的堆转存快照进行分析,重点是确认内存中的对象是否是必要的,先分清是因为内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。
      • 如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链。于是就能找到泄漏对象时通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收。
    • 虚拟机栈和本地方法栈溢出
      • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
      • 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。
      • 这里需要注意当栈的大小越大可分配的线程数就越少。
    • 运行时常量池溢出
      • 异常信息:java.lang.OutOfMemoryError:PermGen space
      • 如果要向运行时常量池中添加内容,最简单的做法就是使用String.intern()这个Native方法。该方法的作用是:如果池中已经包含一个等于此String的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。由于常量池分配在方法区内,我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容量。
    • 方法区溢出
      • 方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。
      • 异常信息:java.lang.OutOfMemoryError:PermGen space
      • 方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收,判定条件是很苛刻的。在经常动态生成大量Class的应用中,要特别注意这点。

16. Java面向对象的三个特征以及含义

  • 继承
    • 继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。
    • 对象的一个新类可以从现有的类中派生,这个过程称为继承,新类继承了原始类的特性,新类称为原始类的派生类,而原始类称为新类的基类。
    • 派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。
  • 封装
    • 封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。
  • 多态
    • 多态性是指允许不同类的对象对同一消息作出响应。
    • 多态性包括参数化多态性和包含多态性。
    • 多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。

17. override和overload的含义

  • OverLoad:顾名思义,就是重新加载,即重载。同一个类中,多个方法同名,参数列表不同(参数个数不同,参数类型不同),与方法的返回值无关,与权限修饰符无关,final方法能被重载。
  • Override:重写,在子类继承父类的时候子类中可以定义某方法与其父类有相同的名称和参数,当子类在调用这一函数时自动调用子类的方法,而父类相当于被覆盖(重写)了。父类和子类之间
  • 方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被“屏蔽”了。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。Overloaded的方法是可以改变返回值的类型。

18. Interface与Abstract类的区别

  • abstract class 在Java中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。
  • 在abstract class 中可以有自己的数据成员,也可以有非abstarct的方法,而在interface中,只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在 interface中一般不定义数据成员),所有的方法都是public abstract的。
  • 抽象类中的变量默认是 friendly 型,其值可以在子类中重新定义,也可以重新赋值。接口中定义的变量默认是public static final 型,且必须给其赋初值,所以实现类中不能重新定义,也不能改变其值。
  • abstract class和interface所反映出的设计理念不同。其实abstract class表示的是”is-a”关系,interface表示的是”like-a”关系。
  • 实现抽象类和接口的类必须实现其中的所有方法。抽象类中可以有非抽象方法。接口中则不能有实现方法。

19. static class(嵌套类) 与非静态类(内部类)的区别

  • 内部静态类不需要有指向外部类的引用。但非静态内部类需要持有对外部类的引用。
  • 非静态内部类能够访问外部类的静态和非静态成员。静态类不能访问外部类的非静态成员。他只能访问外部类的静态成员。
  • 一个非静态内部类不能脱离外部类实体被创建,一个非静态内部类可以访问外部类的数据和方法,因为他就在外部类里面。
  • 内部类里面不能有静态方法。
  • 嵌套就是我跟你没关系,自己可以完全独立存在,但是我就想借你的壳用一下,来隐藏一下我自己。内部类就是我是你的一部分,我了解你,我知道你的全部,没有你就没有我(所以内部类对象是以外部类对象存在为前提的)。
  • 简单理解就是:如果把类比喻成鸡蛋,内部类为蛋黄,外部类是蛋壳。那么静态类相当于熟鸡蛋,就算蛋壳破碎(外部类没有实例化),蛋黄依然完好(内部类可以实例化);而非静态类相当于生鸡蛋,蛋壳破碎(无实例化),蛋黄也会跟着破碎(不能实例化)。

staic和not static的区别

  • static与non-static是对立的。static应当(注意是应当)使用类名来引用。而non-static必须(是必须)使用对象实例名来引用。
  • static与non-static在引用数据成员方面的差别:因为static、non-static的数据相关性,static只能引用类的static数据成员;而non-static既可以引用类的static数据成员,也可以引用对象自身的数据。
  • static与non-static method在overload(重载)方面是一样的。
  • 而static与non-static method在override(重写)方面则完全不同。static方法是与类相关的,不是通过this引用的,所以它不能被override。其引用在编译期就得确定。而non-static方法才有可能被override。
  • static与abstract,它们不能同时用于修饰一个方法。因为abstract的语义就是说这个方法是多态方法,需要subclass的实现。而static方法则是在本类中实现的,编译期绑定,不具有多态行为。
  • static与interface,interface中的method也不能是static的。理由同上。但其数据成员 are all static, no matter you mark it static or not 。
  • 多态只限于方法,所以,无论static还是non-static的成员变量,引用的是哪个在编译期就已经确定。

static 与final

  • static修饰方法或者变量时,将变量或者方法(注意这里可以修饰方法)与类绑定,指定与类相关。
  • final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。。

20. java多态的实现原理

  • 父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,而程序调用的方法在运行期才动态绑定,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。
  • 设计时多态:方法重载实现额多态。
  • 运行时多态:方法重写实现额多态。

21. 实现多线程的两种方法

  1. 实现Runnable接口:实际工作中,几乎所有的多线程应用都用实现Runnable这种方式。
    • Runnable适合多个相同程序代码的线程去处理统一资源的情况。把虚拟CPU通程序的代码、数据有效的分离,较好的体现了面向对象的设计思想。
    • 避免由于Java的单继承特性带来的局限性。也就是如果新建的类要继承其他类的话,因为JAVA中不支持多继承,就只能实现java.lang.Runnable接口。
    • 有利用程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。
  2. 继承Thread类:
    • 不能再继承其他类
    • 编写简单,可直接操纵线程,无需使用Thread.currentThread().

22. 线程同步的方法

  1. synchronized关键字和Lock:当使用线程来同时运行多个任务时,可以通过使用锁(互斥)来同步两个任务的行为,从而使得一个任务不会干涉另一个任务的资源。也就是说,如果两个任务在交替着步入某项共享资源,你可以使用互斥来使得任何时刻只有一个任务可以访问这项资源。

    • synchronized:在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize,另外可读性非常好,不管用没用过5.0多线程包的程序员都能理解。
    • ReentrantLock:ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候。ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。
      • ReentrantLock获取锁定与三种方式:
        • lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁。
        • tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false。
        • tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false。
        • lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断。
    • Atomic:和上面的类似,不激烈情况下,性能比synchronized略逊,而激烈的时候,也能维持常态。激烈的时候,Atomic的性能会优于ReentrantLock一倍左右。但是其有一个缺点,就是只能同步一个值,一段代码中只能出现一个Atomic的变量,多于一个同步无效。因为他不能在多个Atomic之间同步。
    • 线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,如果使用synchronized ,如果A不释放,B将一直等下去,不能被中断。如果使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情。
    • synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中
  2. Object的方法wait()和notify()或者Condition的await()和signal()方法:任务彼此之间协作一起去解决某个问题,在解决这个问题中,由于某些部分必须在其他部分被解决之前解决。

    • wait():使一个线程处于等待状态,并且释放所持有的对象的lock。会在等待外部世界产生变化时将任务挂起,并且只有在notify或notifyAll发生时,这个任务才会被唤醒病区检查所产生的变化。因此,wait()提供了一种在任务之间对活动同步的方式。
    • sleep():使一个正在运行的线程处于睡眠状态,是Thread的一个静态方法,调用此方法需要捕捉InterruptedException异常,没有释放锁。
    • notify():在众多等待同一个锁的任务中只有一个会被唤醒。注意的是在调用此方法时,并不能确切的唤醒某一个等待的线程,而是有JVM确定唤醒哪个线程,而不是按优先级。
    • notifyAll():将唤醒所有正在等待的任务。当notifyAll()因某个特定所而被调用时,只有等待这个锁的任务才会被唤醒,注意不是给所有唤醒线程一个对象的锁,而是让它们竞争。

23. 锁的等级

  1. 方法锁:synchronized标记的方法。
  2. 对象锁:在方法上加了synchronized的锁,或者synchronized(syncObject){}的代码段。
  3. 类锁:在代码中的方法上加了static和synchronized的锁,因为在静态方法中加同步锁会锁住整个类。
  4. 方法锁:对象锁(也叫方法锁)。同步静态方法/静态变量互斥体,由于一个class不论被实例化多少次,其中的静态方法和静态变量在内存中都只由一份。所以,一旦一个静态的方法被申明为synchronized。此类所有的实例化对象在调用此方法,共用同一把锁,我们称之为类锁。一旦一个静态变量被作为synchronized block的mutex。进入此同步区域时,都要先获得此静态变量的对象锁。
  5. 对象锁:是针对一个对象的,它只在该对象的某个内存位置声明一个标志位标识该对象是否拥有锁,所以它只会锁住当前的对象。一般一个对象锁是对一个非静态成员变量进行syncronized修饰,或者对一个非静态方法进行syncronized修饰。对于对象锁,不同对象访问同一个被syncronized修饰的方法的时候不会阻塞住。
  6. 类锁:是锁住整个类的,当有多个线程来声明这个类的对象的时候将会被阻塞,直到拥有这个类锁的对象被销毁或者主动释放了类锁。这个时候在被阻塞住的线程被挑选出一个占有该类锁,声明该类的对象。其他线程继续被阻塞住。
  7. 对象锁是用来控制实例方法之间的同步,类锁是用来控制静态方法(或静态变量互斥体)之间的同步。所谓的类锁,不过是Class对象的锁而已。获取类的Class对象有好几种,最简单的就是MyClass.class的方式。

24. 写出生产者消费者模式

  1. wait()和notify()实现:
package com.xqq.生产者与消费者之wait和notify实现;

import java.util.LinkedList;
import java.util.Queue;

/** * 用于存放生产数据的缓冲区队列 * @author xqq */
public class FlowQueue<T> {
    private Queue<T> queue = new LinkedList<T>();
    private int maxSize;
    public FlowQueue(int maxSize) {
        this.maxSize = maxSize;
    }
    public synchronized void put(T v) throws InterruptedException{
        while(queue.size() >= maxSize){
            wait();
        }
        queue.offer(v);
        notifyAll();
    }

    public synchronized T get() throws InterruptedException{
        while(queue.isEmpty()){
            wait();
        }
        T returnVal = queue.poll();
        notifyAll();
        return returnVal;
    }
}

package com.xqq.生产者与消费者之wait和notify实现;

import java.util.concurrent.TimeUnit;

public class Consumer implements Runnable{
    private int delay;
    private FlowQueue<Item> input;

    public Consumer(int delay, FlowQueue<Item> input) {
        this.delay = delay;
        this.input = input;
    }
    public void run() {
        for(;;){
            try {
                System.out.println("Consumer " + input.get());
                TimeUnit.MILLISECONDS.sleep(delay);
            } catch (Exception e) {
                return;
            }
        }
    }
}

package com.xqq.生产者与消费者之wait和notify实现;

import java.util.concurrent.TimeUnit;

public class Producer implements Runnable{
    private int delay;
    private FlowQueue<Item> output;

    public Producer(int delay, FlowQueue<Item> output) {
        this.delay = delay;
        this.output = output;
    }
    public void run() {
        for(;;){
            try {
                Item product = new Item();
                System.out.println("Product " + product);
                output.put(product);
                TimeUnit.MILLISECONDS.sleep(delay);
            } catch (Exception e) {
                return;
            }
        }
    }
}

package com.xqq.生产者与消费者之wait和notify实现;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ProducerConsumer {
    public static void main(String[] args) throws InterruptedException {
        int producerSleep = 1;
        int consumerSleep = 200;
        FlowQueue<Item> fq = new FlowQueue<Item>(10);
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(new Producer(producerSleep, fq));
        exec.execute(new Consumer(consumerSleep, fq));
        TimeUnit.SECONDS.sleep(2);
        exec.shutdownNow();
    }
}

运行结果:
Product Item 0
Consumer Item 0
Product Item 1
Product Item 2
Product Item 3
Product Item 4
Product Item 5
Product Item 6
Product Item 7
Product Item 8
Product Item 9
Product Item 10
Product Item 11
Consumer Item 1
Product Item 12
Consumer Item 2
Product Item 13
Consumer Item 3
Product Item 14
Consumer Item 4
Product Item 15
Consumer Item 5
Product Item 16
Consumer Item 6
Product Item 17
Consumer Item 7
Product Item 18
Consumer Item 8
Product Item 19
Consumer Item 9
Product Item 20
  1. BlockingQueue实现:
package com.xqq.生产者与消费者BlockingQueue实现;

import java.util.concurrent.BlockingQueue;

public class Consumer implements Runnable {
    private BlockingQueue<Integer> sharedQueue;
    public Consumer(BlockingQueue<Integer> sharedQueue) {
        this.sharedQueue = sharedQueue;
    }
    public void run() {
        for (;;) {
            try {
                System.out.println("Consumer " + sharedQueue.take());
            } catch (Exception e) {
                return;
            }
        }
    }
}

package com.xqq.生产者与消费者BlockingQueue实现;

import java.util.concurrent.BlockingQueue;

public class Producer implements Runnable{
    private BlockingQueue<Integer> sharedQueue;

    public Producer(BlockingQueue<Integer> sharedQueue) {
        this.sharedQueue = sharedQueue;
    }
    public void run() {
        for(int i = 0; i<10; i++){
            try {
                System.out.println("Produce " + i);
                sharedQueue.put(i);
            } catch (Exception e) {
                return ;
            }
        }
    }
}

package com.xqq.生产者与消费者BlockingQueue实现;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

public class ProducerConsumerPattern {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService exec = Executors.newCachedThreadPool();
        BlockingQueue<Integer> sharedQueue = new LinkedBlockingQueue<Integer>();
        exec.execute(new Producer(sharedQueue));
        exec.execute(new Consumer(sharedQueue));
        TimeUnit.SECONDS.sleep(3);
        exec.shutdownNow();
    }
}

运行结果:
Produce 0
Produce 1
Produce 2
Produce 3
Produce 4
Produce 5
Produce 6
Produce 7
Produce 8
Produce 9
Consumer 0
Consumer 1
Consumer 2
Consumer 3
Consumer 4
Consumer 5
Consumer 6
Consumer 7
Consumer 8
Consumer 9

25. ThreadPool用法与优势

  1. 优势:
    • 降低资源消耗:通过重复利用以创建的线程降低创建线程和销毁线程的消耗。
    • 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
    • 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
    • 用法详情请看:Java编程思想之并发

26. ThreadLocal的设计理念与作用

ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。

  • 每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
  • 将一个ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。 ThreadLocal的应用场合,我觉得最适合的是多线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。

27. Concurrent包里的其它东西
请看:Java编程思想之并发

28. wait()和sleep()的区别

  1. 这两个方法来自不同的类分别是,sleep来自Thread类,和wait来自Object类
  2. sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。
  3. 锁: 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
    • sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以使用锁。一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep()可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。
    • Thread.sleep(0)的作用是“触发操作系统立刻重新进行一次CPU竞争”。
  4. 使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。

29. foreach与正常for循环效率对比

针对列表的foreach的效率是最低,耗时是普通for循环的2倍以上。个人理解它的实现应该和iterator相似。

30. java IO 与NIO

  • Java NIO和IO之间第一个最大的区别是:IO是面向流的,NIO是面向缓冲区的。
  • Java IO面向流意味着每次从流中读出一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动流中读取的数据,需要先将它缓存到一个缓冲区。
  • Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后会处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。
  • 但是,还需要检查是否该缓冲区中包含所有你需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。

31. 反射的作用与原理

  • Java反射机制:Reflection,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。
  • 用途:Java反射机制主要提供以下功能:
    • 在运行时判断任意一个对象所属的类。
    • 在运行时构造任意一个类的对象。
    • 在运行时判断任意一个类所具有的成员变量和方法。
    • 在运行时调用任意一个对象的方法。
    • 生成动态代理。

32. 泛型常用的特点,List能否转为List

  • 泛型,即参数化类型。一提到参数,最熟悉的就是定义方法是有形参,然后调用此方法时传递实参。那么参数化类型怎么理解?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)
  • 在Java的泛型接口中可以转换。

33. 解析XML的几种方式的原理与特点:DOM,SAX、PULL

  • DOM解析:在内存中创建一个DOM树,该结构通常需要加载整个文档然后才能工作。由于它是基于信息层次的,因为DOM被认为是基于树或基于对象的,树在内存中是持久的,因此可以修改它以便应用程序对数据和结构作出更改能随机访问文件内容,也可以修改原文件内容。
  • SAX解析:SAX处理的优点非常类似于流媒体的优点。分析能够立即开始,而不是等待所有的数据被处理。SAX解析器采用了基于事件的模型,它在解析XML文档的时候可以触发一系列的事件,当发现给定的tag的时候,它可以激活一个回调方法,告诉该方法制定的标签已经找到。而且,由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中。这对于大型文档来说是个巨大的优点线性解析,不能随机访问,也无法修改源文件。

34. Java与C++对比

  1. 指针
    JAVA语言让编程者无法找到指针来直接访问内存无指针,并且增添了自动的内存管理功能,从而有效地防止了c/c++语言中指针操作失误,如野指针所造成的系统崩溃。但也不是说JAVA没有指针,虚拟机内部还是使用了指针,只是外人不得使用而已。这有利于Java程序的安全。

  2. 多重继承
    c++支持多重继承,这是c++的一个特征,它允许多父类派生一个类。尽管多重继承功能很强,但使用复杂,而且会引起许多麻烦,编译程序实现它也很不容易。Java不支持多重继承,但允许一个类继承多个接口(extends+implement),实现了c++多重继承的功能,又避免了c++中的多重继承实现方式带来的诸多不便。

  3. 数据类型及类
    Java是完全面向对象的语言,所有函数和变量部必须是类的一部分。除了基本数据类型之外,其余的都作为类对象,包括数组。对象将数据和方法结合起来,把它们封装在类中,这样每个对象都可实现自己的特点和行为。而c++允许将函数和变量定义为全局的。此外,Java中取消了c/c++中的结构和联合,消除了不必要的麻烦。

  4. 自动内存管理
    Java程序中所有的对象都是用new操作符建立在内存堆栈上,这个操作符类似于c++的new操作符。下面的语句由一个建立了一个类Read的对象,然后调用该对象的work方法: Read r=new Read(); r.work();语句Read r=new Read();在堆栈结构上建立了一个Read的实例。Java自动进行无用内存回收操作,不需要程序员进行删除。而c++中必须由程序员释放内存资源,增加了程序设计者的负担。Java中当一个对象不被再用到时,无用内存回收器将给它加上标签以示删除。Java里无用内存回收程序是以线程方式在后台运行的,利用空闲时间工作。

  5. 操作符重载
    Java不支持操作符重载。操作符重载被认为是c++的突出特征,在Java中虽然类大体上可以实现这样的功能,但操作符重载的方便性仍然丢失了不少。Java语言不支持操作符重载是为了保持Java语言尽可能简单。

  6. 预处理功能
    Java不支持预处理功能。c/c++在编译过程中都有一个预编译阶段,即众所周知的预处理器。预处理器为开发人员提供了方便,但增加丁编译的复杂性。JAVA虚拟机没有预处理器,但它提供的引入语句(import)与c++预处理器的功能类似。

  7. Java不支持缺省函数参数,而c++支持
    在c中,代码组织在函数中,函数可以访问程序的全局变量。c++增加了类,提供了类算法,该算法是与类相连的函数,c++类方法与Java类方法十分相似,然而,由于c++仍然支持c,所以不能阻止c++开发人员使用函数,结果函数和方法混合使用使得程序比较混乱。 Java没有函数,作为一个比c++更纯的面向对象的语言,Java强迫开发人员把所有例行程序包括在类中,事实上,用方法实现例行程序可激励开发人员更好地组织编码。

  8. 字符串
    c和c++不支持字符串变量,在c和c++程序中使用null终止符代表字符串的结束,在Java中字符串是用类对象(String和StringBuffer)来实现的,这些类对象是Java语言的核心,用类对象实现字符串有以下几个优点:

    • 在整个系统中建立字符串和访问字符串元素的方法是一致的;
    • 字符串类是作为Java语言的一部分定义的,而不是作为外加的延伸部分;
    • Java字符串执行运行时检空,可帮助排除一些运行时发生的错误;
    • 可对字符串用“十”进行连接操作。
  9. 数组
    java引入了真正的数组。不同于c++中利用指针实现的“伪数组”,Java引入了真正的数组,同时将容易造成麻烦的指针从语言中去掉,这将有利于防止在c++程序中常见的因为数组操作越界等指针操作而对系统数据进行非法读写带来的不安全问题。

  10. “goto语句”
    “可怕”的goto语句是c和c++的“遗物”,它是该语言技术上的合法部分,引用goto语句引起了程序结构的混乱,不易理解,goto语句子要用于无条件转移子程序和多结构分支技术。鉴于以广理由,Java不提供goto语句,它虽然指定goto作为关键字,但不支持它的使用,使程序简洁易读。

  11. 类型转换
    在c和c++中有时出现数据类型的隐含转换,这就涉及了自动强制类转换问题。例如,在c++中可将一浮点值赋予整型变量,并去掉其尾数。Java不支持c++中的自动强制类型转换,如果需要,必须由程序显式进行强制类型转换。

  12. 异常
    JAVA中的异常机制用于捕获例外事件,增强系统容错能力 try{//可能产生例外的代码 }catch(exceptionType name){ //处理 } 其中exceptionType表示异常类型。而C++则没有如此方便的机制。

35. Java1.7与1.8新特性

36. JNI的使用

JNI作为java和操作系统之间的一个直接接口,可以通过JNI使得java直接调用操作系统的资源。目前JNI只能通过c/c++实现,因为JNI只是对操作系统资源调用的一个桥接过程。所以理论上在Windows下只要是dll文件均可以被调用。
JNI一般有以下应用场景:

  • 高性能:在一些情况下因为处理运算量非常的大,为了获取高性能,直接使用java是不能胜任的,如一些图形的处理。
  • 调用一些硬件的驱动或者一些软件的驱动,比如调用一些外部系统接口的驱动,如:读卡器的驱动,OCI驱动。
  • 需要使用大内存,远远超过JVM所能分配的内存,如进程内Cache。
  • 调用C或者操作系统提供的服务,如:java调用搜索服务,其中搜索是有C/C++实现的,不过这个一般可以设计成更加通用的方式,比如soa的方式,所有这些场景的前提是牺牲了java代码的可移植性,不同的OS,甚至版本都需要些不同版本的native实现。

你可能感兴趣的:(android,面试,J2SE)