前言
昨天,很意外的接到了猎趣的电话,直奔主题就开始面试了,我都不知道我投过这家公司,后来才知道,是朋友推荐的。面试官人很好,问的问题分了 Java,Android 两类,从简单到深入,一点一点来,事后,我觉得这是很难得的经历,打算记录下来,以备后用。
Java 问题
- 简单说一说,Java 基本类型
- 关于 String 的了解,是否可以被继承,以及 StringBuffer,StringBuilder 三者的关系
- 说一说,对于 Collection 和 Collections 的理解
- Map 继承自什么?以及 HashMap,HashTable 的区别
- 接口和抽象类的区别
- 说一说,线程同步的几种方法
下面我一个一个整理资料,为自己做一个总结。
1,Java 基本类型
这个很简单:byte,short,int,long,float,double,char,boolean;
关于这个点,没什么说的,只是要注意,回答的顺序,先整型,后浮点型,之后再特殊的,这样的好处,一是容易记,二是体现自己逻辑清晰,千万不要想起一个说一个 = =
2,String,StringBuffer,StringBuilder
我个人的使用频率 String > StringBuffer > StringBuilder,= =,还好前两天刷牛客网的题,有遇到过,简单总结一下:
- String
不可以被继承,原因是因为,String 类是被 final 修饰的;
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
...
}
String 是只读字符串,也就意味着 String 引用的字符串内容是不能被改变的,有人说:
String a = "a";
a = "b";
这不就改变了吗?其实不然,变量 a 在栈中,"a","b" 则在常量池中,以上两行代码,只是把 String a 的引用指向了 "b",而非,将"a"直接改为"b"。
另外,String a = "a" 与 String a = new String("a") 也是不一样的,主要区别就是,后者在栈中创建了对象,看下面代码一目了然:
String str = new String("hello");
String str_1 = "hello";
System.out.println(str == "hello"); // false
System.out.println(str_1 == "hello"); // true
System.out.println(str == str_1); // false
System.out.println(str.equals(str_1)); // true
- StringBuffer 与 StringBuilder
StringBuffer 和 StringBulder 类表示的字符串对象可以直接进行修改,
例如以下几个方法,String 类是没有的。
StringBuffer sb = new StringBuffer("Hello");
sb.append('a'); // Helloa
sb.insert(0, 'b'); // bHelloa
sb.deleteCharAt(sb.length() - 3); // bHeloa
System.out.println(sb.toString());
StringBuilder 是 JDK1.5 引入的,它和 StringBuffer 的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被 synchronized 修饰,因此它的效率也比 StringBuffer 略高。
3,Collection,Collections
关于 Java 的集合类,牵扯比较广,我不多做拓展,只简单说一下,这 Collection,Collections 两个东西:
- Collection
java.util.Collection 是一个集合接口,是为各种具体的集合提供了最大化的统一操作方式,它下面有很多实现,比如常见的 List,Set 等; - Collections
java.util.Collections 是一个包装类(工具类),它包含有各种有关集合操作的静态多态方法。该类不能实例化,详情见源码:
public class Collections {
// Suppresses default constructor, ensuring non-instantiability.
private Collections() {
}
...
}
Collections 的常见/用方法有:sort(),swap(),reverse() 等。
4,Map,HashMap,HashTable
- 直接看源码的话,Map 是没有显式的继承类的,但在 Java 中所有的类或接口都有共同的父类,即java.lang.Object 类,所以可以说,Map接口继承了 java.lang.Object 类,但没有实现任何接口;
- Map 是和 Collection 同级别的接口 ;
- HashMap 与 HashTable
-
相同点
- 均基于哈希表实现的,每一个元素都是一个 key-value 对;
- 均实现了 Serializable 接口,因此支持序列化;
(相同点,我只了解到这里,下面的都是补充 = =)
均实现了 Cloneable 接口,能被克隆; - 其内部通过单链表解决冲突问题,容量不足(超过了阈值)时,会自动增长;
- 二者的存储结构和解决冲突的方法都是相同的。
-
不同点
- Hashtable 中 key 和 value 都不允许为 null,而 HashMap 中 key 和 value 都允许为 null;
- HashMap 是非线程安全的,只是用于单线程环境下,Hashtable 则是线程安全的,能用于多线程环境中;
(不同点,我只了解到这里,下面的都是补充 = =)
HashMap 若想在多线程环境下使用,可以采用 concurrent 并发包下的 concurrentHashMap,或者使用 Collections.synchronizedMap() 方法来获取一个线程安全的集合; - 继承关系的不同;
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {}
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, Serializable {}
- HashTable 在不指定容量的情况下的默认容量为 11,而 HashMap 为 16,Hashtable 不要求底层数组的容量一定要为 2 的整数次幂,而 HashMap 则要求一定为2的整数次幂;
- 关于 HashMap 中 key 和 value 都允许为 null,有更详细的解释和描述
(key 只能有一个为 null,而 value 则可以有多个为 null),
但是如果在 Hashtable 中有类似put(null,null)
的操作,编译同样可以通过,因为 key 和 value 都是 Object 类型,但运行时会抛出 NullPointerException 异常,这是 JDK 的规范规定的; - Hashtable 扩容时,将容量变为原来的 2 倍加 1,而 HashMap 扩容时,将容量变为原来的 2 倍;
- Hashtable 和 HashMap 都重新计算了 key 的 hash 值,Hashtable 在求 hash 值对应的位置索引时,用取模运算,而 HashMap 在求位置索引时,则用与运算,且这里一般先用 hash & 0x7FFFFFFF 后,再对 length 取模,& 0x7FFFFFFF 的目的是为了将负的 hash 值转化为正值,因为 hash 值有可能为负数,而 & 0x7FFFFFFF 后,只有符号外改变,而后面的位都不变。
-
5,接口和抽象类
- 相同点
- 抽象类和接口都不能够实例化,但可以定义抽象类和接口类型的引用。
- 不同点
- 抽象类中可以定义构造器,可以有抽象方法和具体方法;而接口中不能定义构造器而且其中的方法全部都是抽象方法;
- 抽象类中的成员可以是 Private、默认、Protected 、Public 的,而接口中的成员全都是 public 的;
- 抽象类中可以定义成员变量,而接口中定义的成员变量实际上都是常量。
- 稍微总结和多说两句
- 一个类如果继承了某个抽象类或者实现了某个接口,需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类;
- 有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法。
6,线程同步
这个问题,我第一反应就是,Android 里的 Handler 啊,runOnUiThread() 什么的,结果面试官强调,是 Java 中 线程同步的方法 = =
这个我了解的不多,找到一些资料,总结如下:
- 利用 Synchronized 关键字:同步关键字,或者同步代码块,这个比较简单,基本都用到过;
- 利用 Wait() 与 Notify(),只见过,没用过,更没有阅读过相关源码; = =
- wait():使一个线程处于等待状态,并且释放所持有的对象的lock。
- sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
- notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
- notifyAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。
- 利用特殊域变量 (volatile) 实现线程同步, 这个更不知道,只在单例模式中见到过;
- 利用 Lock 接口及其实现类 ReentrantLock(重入锁),实现线程同步。
- 利用 ThreadLocal,它是一个方便解决多线程并发问题的工具类。
以上就是我整理出的,一些常见的方法,肯定不全,而且我自己都没用过几个,这几天,好好研究一下,觉得自己好菜 = = !!
后记
没想到,光是 Java 的问题,就总结了这么长,关于集合的知识点,知道的太少,看来得好好巩固了 ,不然实在是找不到工作啊 = =
额,Android 部分的估计就更长了,就此分篇,详见下一篇吧。