面试常见考题_java基础(全)
J2SE 基础
九种基本数据类型的大小,以及他们的封装类。
基本类型:boolean byte char short int long float double void 封装类型:Boolean Byte
● 基本数据类型与其对应的封装类由于本质的不同,具有一些区别:
基本数据类型只能按值传递,而封装类按引用传递。
基本类型在堆栈中创建;而对于对象类型,对象在堆中创建,对象的引用在堆栈中创建。基本类型由于在堆栈中,效率会比较高,但是可能会存在内存泄漏的问题。
2. Switch能否用string 做参数?
在java7之前,switch 只支持byte 、short 、char 、int 或者其对应的封装类以及Eumn 类型, 在java7中,String 支持被加上了。
3. equals 与==的区别。
值类型是存储在内存的栈中,引用类型的变量在栈中存放其引用类型变量的地址,其值存储在堆中。
== 比较的是两个变量的值是否相等,对于引用变量表示的是两个变量在堆中存储的地址是否相同,即栈中的内容是否相同。
Equals 比较的是两个变量是否是对同一个对象的引用,即堆中的内容是否相同。
创建对象之前会在字符串缓冲池中查找是否有此字符串常量,如果存在直接返回其引用,s1、s2两者的引用都是相同的。
本程序中的s1指向字符串缓冲区中的常量的引用,s2根据字符串缓冲区中的常量作为构造函数的参数,新建了一个对象并存储在内存中的另一个位置。(如果常量不存在与字符串缓冲区,则先在字符串缓冲区中建立常量,然后newString ,这样就创建了两个对象!) 附:构造方法original 即为字符串缓冲池中得到的
java 字符串缓冲池分析java 字符串缓冲池分析: java的虚拟机在内存中开辟出一块
单独的区域,用来存储字符串对象,这块内存区域被称为字符串缓冲池。 到底创建了几个对象?1,0,2,1,1
为什么相等?
Intern()方法返回的是在字符串缓冲池中的对象引用! 对比:
只有加号左右两边的都是字符串常量时,才能在编译阶段指向缓冲池的常量,day 是一个变量,在运行时指向内存中某个地址
Final 修饰的day 变成一个常量,在编译阶段指向缓冲池。
4. Object有哪些公用方法?
● Java 中的深复制和浅复制? 什么是影子clone ?
}
public void doublevalue() { i *= 2; }
public String toString() { return Integer.toString(i); } }
class CloneB implements Cloneable { public int aInt;
public UnCloneA unCA = new UnCloneA(111);
public Object clone() { CloneB o = null; try {
o = (CloneB) super.clone();
} catch (CloneNotSupportedException e) { e.printStackTrace(); }
return o; } }
public class ObjRef {
public static void main(String[] a) { CloneB b1 = new CloneB(); b1.aInt = 11;
System.out.println("before clone,b1.aInt = " + b1.aInt); System.out.println("before clone,b1.unCA = " + b1.unCA);
CloneB b2 = (CloneB) b1.clone(); b2.aInt = 22;
b2.unCA.doublevalue();
System.out.println("================================="); System.out.println("after clone,b1.aInt = " + b1.aInt); System.out.println("after clone,b1.unCA = " + b1.unCA);
System.out.println("================================="); System.out.println("after clone,b2.aInt = " + b2.aInt); System.out.println("after clone,b2.unCA = " + b2.unCA); } }
原样拷贝原始对象中的内容。对基本数据类型,这样的操作是没有问题的,但对非基本类型变量,我们知道它们保存的仅仅是对象的引用,这也导致clone 后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。
深拷贝的改进方法:改成深度clone 很简单,需要两个改变:一是让UnCloneA 类也实现和CloneB 类一样的clone 功能(实现Cloneable 接口,重载clone()方法)。二是在CloneB 的clone()方法中加入一句o.unCA = (UnCloneA)unCA.clone(); 深拷贝:
● 注意:要知道不是所有的类都能实现深度clone 的,StringBuffer 没有重载clone()方法,
更为严重的是StringBuffer 还是一个final 类,这也是说我们也不能用继承的办法间接实现StringBuffer 的clone 。
● 我们有两种选择:要么只能实现影子clone ,要么就在类的clone()方法中加一句(假设
是SringBuffer 对象,而且变量名仍为
unCA ): o.unCA = new StringBuffer(unCA.toString());
● String 对象是一个例外,它clone 后的表现好象也实现了深度clone ,虽然这只是一个
假象,但却大大方便了我们的编程。
秘密就在于c2.str = c2.str.substring(0,5)这一语句!实质上,在clone 的时候c1.str 与c2.str 仍然是引用,而且都指向了同一个String 对象。但在执行c2.str = c2.str.substring(0,5)的时候,它作用相当于生成了一个新的String 类型,然后又赋回给c2.str 。这是因为String 被Sun 公司的工程师写成了一个不可更改的类(immutable class),在所有String 类中的函数都不能更改自身的值。
5. Java的四种引用,强弱软虚,用到的场景。
四种引用类型包括:强引用、弱引用、软引用、虚引用(幽灵引用),使程序能够更灵活的控制对象的生命周期。
1. 强引用(StrongReference ):强引用不会被垃圾回收器回收,并且也没有实际的对应
类型,平时接触最多的就是强引用。例如Object obj = new Object();这里obj 即为强引用。如果一个对象具有强引用,垃圾回收器绝不会回收它。当内存空间不足,java 虚拟机宁愿抛出out of MemoryError 错误,是程序异常终止,也不会靠回收具有强引用的对象来解决内存不足问题。
2. 软引用(SoftReference )如果一个对象只具有软引用,如果内存空间足够,垃圾回收
器就不会回收它,如果内存空间不足,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可以用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue )联合使用,如果软引用所引用的对象被垃圾回收,java 虚拟机就会把这个软引用加入到与之关联的引用队列。
ReferenceQueue 清除无用的引用
3. 弱引用:如果一个对象只具有弱引用,类似于可有可无的生活用品。弱引用和软引用的
区别:只具有弱引用的对象拥有更短的生命周期。在垃圾回收器线程扫描他所管辖的内存区域过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue )联合使用,如果弱引用所引用的对象被垃圾回收,java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。
什么是socket ?
使用弱引用构建非敏感数据的缓存
a) 全局 Map 造成的内存泄漏
b) 用 WeakHashMap 堵住泄漏
4. 虚引用(PhantomReference ):“虚引用”顾名思义,就是形同虚设,与其他几种引用都
不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue )联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚
引用加入到与之关联的引用队列中。
程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
GC 一但发现了虚引用对象,将会将PhantomReference 对象插入ReferenceQueue 队列. * 而此时PhantomReference 所指向的对象并没有被GC 回收,而是要等到ReferenceQueue 被你真正的处理后才会被回收
对象可及性的判断
四种引用的应用场景:
6. Hashcode的作用。
Java 中的集合(Collection )有两类,一类是List ,再有一类是Set 。前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?这就是Object.equals 方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次
equals 方法。这显然会大大降低效率。 于是,Java 采用了哈希表的原理。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。初学者可以这样理解,hashCode 方法实际上返回的就是对象存储的物理地址(实际可能并不是)。
这样一来,当集合要添加新的元素时,先调用这个元素的hashCode 方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位
置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals 方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。这样一来实际调用equals 方法的次数就大大降低了,几乎只需要一两次。 a) hashCode 的存在主要是用于查找的快捷性,如Hashtable ,HashMap 等,hashCode
是用来在散列存储结构中确定对象的存储地址的;
b) 如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的
hashCode 一定要相同;
c) 如果对象的equals 方法被重写,那么对象的hashCode 也尽量重写,并且产生
hashCode 使用的对象,一定要和equals 方法中使用的一致,否则就会违反上面提到的第2点;
d) 两个对象的hashCode 相同,并不一定表示两个对象就相同,也就是不一定适用于
equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable ,他们“存放在同一个篮子里”。
7. ArrayList、LinkedList 、Vector 的区别。
● List 接口下的实现类:
● ArrayList:
List 接口的大小可变数组的实现,实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。(此类大致上等同于 Vector 类,除了此类是不同步的。)size 、isEmpty 、get 、set 、iterator 和 listIterator 操作都以固定时间运行。add 操作以分摊的固定时间 运行,也就是说,添加 n 个元素需要 O(n) 时间。其他所有操作都以线性时间运行(大体上讲)。与用于 LinkedList 实现的常数因子相比,此实现的常数因子较低。
每个 ArrayList 实例都有一个容量。该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。在添加大量元素前,应用程序可以使用 ensureCapacity 操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。
此实现不是同步的。如果多个线程同时访问一个 ArrayList 实例,而其中至少一个线程从结构上修改了列表,那么它必须 保持外部同步。(这一般通过对自然封装该列表的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedList 方法将该列表“包装”起来。这最好在创建时完成,以防止意外对列表进行不同步的访问: List list = Collections.synchronizedList(new ArrayList(...));
此类的 iterator 和 listIterator 方法返回的迭代器是快速失败的:在创建迭代器之后,除非通过迭代器自身的 remove 或 add 方法从结构上对列表进行修改,否则在任何时间以任何方式对列表进行修改,迭代器都会抛出 ConcurrentModificationException 。因此,面对并发的修改,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。
LinkedList:
List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null )。除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get 、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。
此类实现 Deque 接口,为 add 、poll 提供先进先出队列操作,以及其他堆栈和双端队列操作。
所有操作都是按照双重链接列表的需要执行的。在列表中编索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。
使用 Collections.synchronizedList 方法将该列表“包装”起来。这最好在创建时完成,以防
止意外对列表进行不同步的访问:
List list = Collections.synchronizedList(new ArrayList(...));
(同上)
方法中的add 、get 、remove 、offer 、poll 、peek 都是搭配first 、last 成对出现的。 ● Vector:
Vector 类可以实现可增长的对象数组。与数组一样,它包含可以使用整数索引进行访问的组件。但是,Vector 的大小可以根据需要增大或缩小,以适应创建 Vector 后进行添加或移除项的操作。
每个向量会试图通过维护 capacity 和 capacityIncrement 来优化存储管理。capacity 始终至少应与向量的大小相等;这个值通常比后者大些,因为随着将组件添加到向量中,其存储将按 capacityIncrement 的大小增加存储块。应用程序可以在插入大量组件前增加向量的容量;这样就减少了增加的重分配的量。
线程同步。
(其他同上)
8. String、StringBuffer 与StringBuilder 的区别。
● String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成
了一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。
● StringBuffer 类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作,
而不是生成新的对象,再改变对象引用。所以在一般情况下我们推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。
● Java.lang.StringBuffer 线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,
但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。
● java.lang.StringBuilder 一个可变的字符序列是5.0新增的。此类提供一个与
StringBuffer 兼容的 API ,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。两者的方法基本相同。 执行速度逐渐变快,StringBuilder 最快。
9. Map、Set 、List 、Queue 、Stack 的特点与用法。
1) Map
Map 中存储的是key-value 组成的键值对,key 是唯一的,value 可以不唯一。 同一个Map 对象的任何两个key 通过equals 方法比较结果总是返回false
TreeMap 是有序的Map (存储的对象需要实现Comparable 接口和其中的compareTo 方法实现比较大小),HashMap 是无序的Map 。
各种Map 实现类
2) List
存储有序的可重复的集合。可以在任何位置添加和删除元素。
可以用Iterator 实现单向遍历,也可以使用ListIterator 实现双向遍历(LinkedList )。 ArrayList 、vector 、LinkedList(实现Deque 接口)
3)Set
不包含重复的元素的集合,set 中最多包含一个null 元素, 只能用Iterator 实现单向遍历,Set 中没有同步的方法。 HashSet (LinkedHashSet 实现了看似按先后顺序存储) TreeSet 是有序的实现Comparable 接口。
4) Queue
遵循先进先出的原则。(LinkedList实现了Queue ,Deque) 使用时尽量避免add\remove方法,用offer\poll方法来添加和移除元素,后两种方法的优点是可以通过返回值判断操作是否成功。通常不允许插入null;
PriorityQueue 并不是一个比较标准的队列实现,PriorityQueue 保存队列元素的顺序并不是按照加入队列的顺序,而是按照队列元素的大小进行重新排序,这点从它的类名也可以
看出来。其中保存的对象同TreeSet 类似,要实现Comparable 接口和相应的compareTo 方法,为了比较大小。
5) Stack
Stack 遵从后进先出原则。 Stack继承自Vector 。
它通过五个操作对类Vector 进行扩展,允许将向量视为堆栈,它提供了通常的push 和pop 操作,以及取堆栈顶点的peek()方法、测试堆栈是否为空的empty 方法等
10. HashMap 和HashTable 的区别。
● Hashmap 是一个数组和链表的结合体(在数据结构称“链表散列“),如下图示:
当我们往hashmap 中put 元素的时候,先根据key 的hash 值得到这个元素在数组中的位置(即下标),然后就可以把这个元素放到对应的位置中了。如果这个元素所在的位子上已经存放有其他元素了,那么在同一个位子上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。
HashTable 和HashMap 区别
第一,继承不同。
public class Hashtable extends Dictionary implements Map
public class HashMap extends AbstractMap implements Map
第二
Hashtable 中的方法是同步的,而HashMap 中的方法在缺省情况下是非同步的。在多线程并发的环境下,可以直接使用Hashtable ,但是要使用HashMap 的话就要自己增加同步处理了。
第三
Hashtable 中,key 和value 都不允许出现null 值。
在HashMap 中,null 可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null 。当get()方法返回null 值时,即可以表示 HashMap 中没有该键,也可以表示该键所对应的值为null 。因此,在HashMap 中不能由get()方法来判断HashMap 中是否存在某个键, 而应该用containsKey()方法来判断。
第四,两个遍历方式的内部实现上不同。
Hashtable 、HashMap 都使用了 Iterator 。而由于历史原因,Hashtable 还使用了Enumeration 的方式 。
第五
哈希值的使用不同,HashTable 直接使用对象的hashCode 。而HashMap 重新计算hash 值。
第六
Hashtable 和HashMap 它们两个内部实现方式的数组的初始大小和扩容的方式。HashTable 中hash 数组默认大小是11,增加的方式是 old*2+1。HashMap 中hash 数组的默认大小是16,而且一定是2的指数。
11. HashMap和ConcurrentHashMap 的区别,HashMap
的底层源码。
12. TreeMap、HashMap 、LindedHashMap 的区别。 ✓ Hashmap 是一个最常用的Map, 它根据键的HashCode 值存储数据, 根据键可以直接
获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。HashMap
最多只允许一条记录的键为Null; 允许多条记录的值为 Null;HashMap 不支持线程的同步,即任一时刻可以有多个线程同时写HashMap; 可能会导致数据的不一致。如果需要同步,可以用 Collections 的synchronizedMap 方法使HashMap 具有同步的能力,或者使用ConcurrentHashMap 。
✓ LinkedHashMap 保存了记录的插入顺序,在用Iterator 遍历LinkedHashMap 时,先得
到的记录肯定是先插入的. 也可以在构造时用带参数,按照应用次数排序。在遍历的时候会比HashMap 慢,不过有种情况例外,当HashMap 容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap 慢,因为LinkedHashMap 的遍历速度只和实际数据有关,和容量无关,而HashMap 的遍历速度和他的容量有关。
✓ TreeMap 实现SortMap 接口,能够把它保存的记录根据键排序, 默认是按键值的升序排
序,也可以指定排序的比较器,当用Iterator 遍历TreeMap 时,得到的记录是排过序的。
13. Collection包结构,与Collections 的区别。
Collection 是单列集合(Map 是双列集合),是集合类的上级接口。
Collections 是针对集合类的一个帮助类,提供了操作集合的工具方法:一系列静态方法实现对各种集合的搜索、排序、线程安全化的操作。此类不能实例化,就像一个工具类
14. try catch finally,try 里有return ,finally 还执行么? a) 不管有木有出现异常,finally 块中代码都会执行;
b) 当try 和catch 中有return 时,finally 仍然会执行;
c) finally 是在return 后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把
要返回的值保存起来,管finally 中的代码怎么样,返回的值都不会改变,任然是之前
保存的值),所以函数返回值是在finally 执行前确定的;
d) finally 中最好不要包含return ,否则程序会提前退出,返回值不是try 或catch 中保存
的返回值。
注意:finally 中对变量值得修改不会影响返回值;
在try 语句中,在执行return 语句时,要返回的结果已经准备好了,就在此时,程序转到finally 执行了。
在转去之前,try 中先把要返回的结果存放到不同于x 的局部变量中去,执行完finally 之后,在从中取出返回结果,
因此,即使finally 中对变量x 进行了改变,但是不会影响返回结果。
它应该使用栈保存返回值。
15. Excption与Error 包结构。OOM 你遇到过哪些情况,
SOF 你遇到过哪些情况。
OOM: OUT OF MEMORY
SOF:Stack overflow
16. Java面向对象的三个特征与含义。
封装可以隐藏实现细节,是代码模块化;
继承可以扩展已存在的代码模块(类);
他们的目的都是代码重用。
多态是为了实现另一个目的:接口重用。
✧ 封装
什么是封装?封装又叫隐藏实现,只公开代码单元的对外接口,而隐藏其具体实现。 也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
如何实现?在程序设计里,封装通过访问控制实现。通过访问控制符,用public 把信息暴露,private ,protected 将信息隐藏,来实现封装。(对代码访问控制的越严格,对代码修改的自由度就越大。)
封装好处?
1) 封装将代码分成了一个个相对独立的单元,使得对代码的修改更加安全和容易。明
确的指出哪些属性和方法是外部可以访问的,当代吗需要调整时,只要保证共有的属性不变,共有参数和返回类型不变,就可以自由的修改代码。
2) 封装使得整个软件开发复杂度降低。可以容易的使用别人编写的代码,不必关心内
部实现。
3) 封装避免了命名冲突问题。封装有隔离的作用,不同类中有同名的方法和属性,不
会混淆。
✧ 继承:核心思想是重用现有的代码,并用一些已有的类创建新的类。
1) 什么是继承?指可以让一个对象获得另一个对象的属性和方法。继承类可以使用现
有类的所有功能,不需要重新编写,并且对这些功能进行扩展。通过继承创建的新类成为“子类”“派生类”,被继承的类称为“基类”“父类”。继承的过程是一般到特殊的过程。
2) 如何实现?
实现继承和接口继承;实现继承可以直接使用基类的属性和方法无需额外的编码。接口继承指使用属性和方法名,需要自己实现方法。
3) 复合和继承,何时使用?
复合,将各个部分组合在一起,程序设计实现时用已有类的对象产生新的类。
a ) 需要向上转型时考虑继承
b ) 用“has a”“is a”区分复合和继承。
✧ 多态
1) 什么是多态?指允许不同类的对象对同一消息作出相应。即同一消息可以根据发送
对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)
2) 多态存在的三个必要条件:
a) 要有继承
b) 要有重写
父类引用指向子类对象:1. 该引用只能调用父类中定义的方法和变量;2. 如果子类中重写了父类中的一个方法,那么调用时将会调用子类中的方法(动态链接、动态调用)
20. java 多态的实现原理。
Java 多态概述:
多态是面向对象元的重要特性,它允许基类的指针或引用指向派生类的对象,在具体访问时实现方法的动态绑定。Java 对于方法的动态绑定的实现主要依赖于方法表,分为类引用调用和方法引用调用。
● 类引用调用过程:java 编译器将java 源代码编译成class 文件,编译过程中,会根据
静态类型将调用的符号引用写到class 文件中。运行时,虚拟机根据class 文件找到调用方法的符号引用,然后在静态类型方法表中找到偏移量,根据this 指针确定对象的实际类型,使用实际类型方法表(偏移量和静态类型中的偏移量一样),如果实际类型的方法表中找到该方法,直接调用,否则认为没有重写父类该方法,按照继承关系从下往上搜索。
● JVM 结构
Java 方法调用方式:
方法表和方法调用
如果子类改写了父类的方法,那么子类和父类的那些同名的方法共享一个方法表项。
所有继承父类的子类的方法表中,其父类所定义的方法的偏移量也总是一个定值。这样 JVM 在调用实例方法其实只需要指定调用方法表中的第几个方法即可。
(1)在常量池(这里有个错误,上图为ClassReference 常量池而非Party 的常量池)中找到方法调用的符号引用 。
(2)查看Person 的方法表,得到speak 方法在该方法表的偏移量(假设为15),这样就得到该方法的直接引用。
(3)根据this 指针得到具体的对象(即 girl 所指向的位于堆中的对象)。
(4)根据对象得到该对象对应的方法表,根据偏移量15查看有无重写(override )该方法,如果重写,则可以直接调用(Girl 的方法表的speak 项指向自身的方法而非父类);如果没有重写,则需要拿到按照继承关系从下往上的基类(这里是Person 类)的方法表,同样按照这个偏移量15查看有无该方法。 接口调用 因为 Java 类是可以同时实现多个接口的,而当用接口引用调用某个方法的时候,情况就有所不同了。
Java 允许一个类实现多个接口,从某种意义上来说相当于多继承,这样同样的方法在基类和派生类的方法表的位置就可能不一样了。
可以看到,
由于接口的介入,继承自于接口 IDance 的方法 dance()在类 Dancer 和 Snake 的方法表中的位置已经不一样了,显然我们无法仅根据偏移量来进行方法的调用。
Java 对于接口方法的调用是采用搜索方法表的方式,如,要在Dancer 的方法表中找到dance()方法,必须搜索Dancer 的整个方法表。
因为每次接口调用都要搜索方法表,所以从效率上来说,接口方法的调用总是慢于类方法的调用的。
17. Override和Overload 的含义去区别。
Override(重写、覆盖)
1、 方法名、参数、返回值相同。
2、 子类方法不能缩小父类方法的访问权限(访问控制符)。
3、 子类方法不能抛出比父类方法更多的异常(子类可以不抛)
4、 存在于父类和子类之间。
5、 方法被定义为Final 不能被重写。
特点:1. 方法的标志必须完全匹配,才能覆盖;
2. 返回值一致;
3. 抛出异常需要一致,或者为其子类;
4. 被覆盖方法不能为private, 否则不能覆盖
Overload (重载)
1、 参数类型、数量、顺序至少一个不同;
2、 不能重载只有返回值不同的方法名;
3、 存在父类子类、同类中
特点:1. 使用重载时只能通过不同的参数样式。如参数类型、参数个数、参数顺序
2. 不能通过访问权限、返回类型、抛出异常进行重载;
3. 方法的异常类型、数目不会对重载造成影响。
4. 如果父类中方法访问权限是private ,不能在子类中重载。
18. Interface与abstract 类的区别。 abstract 修饰符 抽象类abstract 修饰符用来修饰类和成员方法
1)
2)
✧ 用abstract 修饰的类表示抽象类,抽象类位于继承树的抽象层,抽象类不能被实例化。 用abstract 修饰的方法表示抽象方法, 抽象方法没有方法体。抽象方法用来描述系统具有什么功能,但不提供具体的实现。 abstract 规则:
1) 抽象类可以没有抽象方法,但是有抽象方法的类必须定义为抽象类,如果一个子类继承一个抽
象类,子类没有实现父类的所有抽象方法,那么子类也要定义为抽象类,否则的话编译会出错
的。
2)
3)
4)
● 抽象类没有抽象静态方法,可以有非抽象的构造方法。(用于子类构造方法中调用父类构造方法) 抽象类不能被实例化,但是可以创建一个引用变量,类型是一个抽象类,并让它引用非抽象类的子类的一个实例 (父类引用指向子类实例) 不能用final 修饰符修饰(抽象类是需要被继承的,final 修饰的类无法被继承) Interface 接口
Interface 关键字定义接口,也称为接口类型,用于明确的描述系统对外提供的所有服务,清晰的把系统的实现细节与分离,实现传说中的解耦合。
✧
1)
2)
3)
4)
5)
6)
●
1) Interface 规则: 接口的成员变量都是public static final类型的,必须显示初始化 接口的成员方法都是public abstract 类型的 接口只能包含public static final类型的成员变量和public abstract类型的成员方法 接口中没有构造方法,不能实例化,同abstract 一样,可以定义一个引用变量,让实现了Interface 的具体类来构造。 实现了一个接口,必须实现接口所有的抽象方法,除非该类定义为抽象类 可以实现多个接口,用JAVA 语言实现多继承。 abstract 和Interface 的区别 都代表系统的抽象层
2)
3)
4)
5) 都不能被实例化,但都可以继承或者实现抽象类或者接口的具体类,通过定义引用变量来构造。(多态) 都包含抽象方法(abstract 也可以不包含) 抽象类中可以为部分方法提供默认的实现,可以避免子类中重复实现它们,提高代码的可重用性,接口中只能包含抽象方法。 一个类只能继承一个直接的父类,比如抽象类,但是可以实现多个接口
19. Static class 与non static class的区别。
内部静态类不需要有指向外部类的引用。但非静态内部类需要持有对外部类的引用。非静态内部类能够访问外部类的静态和非静态成员。静态类不能访问外部类的非静态成员。他只能访问外部类的静态成员。一个非静态内部类不能脱离外部类实体被创建,一个非静态内部类可以访问外部类的数据和方法,因为他就在外部类里面。
21. 实现多线程的两种方法:Thread 与Runable 。
22. 线程同步的方法:sychronized 、lock 、reentrantLock
等。
23. 锁的等级:方法锁、对象锁、类锁。
对象级别锁
对象级别锁是一个机制,当你想同步一个非静态方法或者非静态代码块,让在给定的类实例中只有一个线程来执行这个代码块,这就可以使得实例级别的数据是线程安全的。
类级别锁
在所有可变的实例或者运行环境中,类级别锁阻止多线程进入同步块,也就是说,如果运行环境中有DemoClass 的100个实例,在任何时刻,只能有DemoClass 的一个实例来执行它的demoMethod()方法,所有其他的DemoClass 实例在其他线程中只能处于阻塞状态,这使得静态数据是线程安全的。
24. 写出生产者消费者模式。
25. ThreadLocal的设计理念与作用。
26. ThreadPool用法与优势。
27. Concurrent包里的其他东西:ArrayBlockingQueue 、
CountDownLatch 等等。
28. wait()和sleep()的区别。
29. foreach与正常for 循环效率对比。
30. Java IO与NIO 。
31. 反射的作用于原理。
32. 泛型常用特点,List能否转为List。
33. 解析XML 的几种方式的原理与特点:DOM 、SAX 、PULL 。
DOM 树形,必须要整个读入到内存
SAX :可以部分读入
34. Java与C++对比。
35. Java1.7与1.8新特性。
36. 设计模式:单例、工厂、适配器、责任链、观察者等等。
37. JNI的使用。