J2SE基础
1. 九种基本数据类型的大小,以及他们的封装类。
基本类型 大小(字节) 默认值 封装类
byte 1 (byte)0 Byte
short 2 (short)0 Short
int 4 0 Integer
long 8 0L Long
float 4 0.0f Float
double 8 0.0d Double
boolean - false Boolean
char 2 \u0000(null) Character
void - - Void
基本类型所占的存储空间是不变的:这种不变性也是java具有可移植性的原因之一。
基本类型是放在栈中,直接存储值。
所有数值类型都有正负号,没有无符号的数值类型。
注意:
1.int是基本数据类型,Integer是int的封装类,是引用类型。int默认值是0,而Integer默认值是null,所以Integer能区分出0和null的情况。一旦java看到null,就知道这个引用还没有指向某个对象,再任何引用使用前,必须为其指定一个对象,否则会报错。
2.基本数据类型在声明时系统会自动给它分配空间,而引用类型声明时只是分配了引用空间,必须通过实例化开辟数据空间之后才可以赋值
3.数组对象也是一个引用对象,将一个数组赋值给另一个数组时只是复制了一个引用,所以通过某一个数组所做的修改在另一个数组中也看的见。
2. Switch能否用string做参数?
在 Java 7之前,switch 只能支持 byte、short、char、int或者其对应的封装类以及Enum 类型。在 Java 7中,String支持被加上了。
3. equals与==的区别。
1.基本数据类型,也称原始数据类型。byte,short,char,int,long,float,double,boolean。他们之间的比较,应用双等号(==),比较的是他们的值。
2.复合数据类型(类),当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址,所以,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。JAVA当中所有的类都是继承于Object这个基类的,在Object中的基类中定义了一个equals的方法,这个方法的初始行为是比较对象的内存地址,但在一些类库当中这个方法被覆盖掉了,如String,Integer,Date在这些类当中equals有其自身的实现,而不再是比较类在堆内存中的存放地址了。 对于复合数据类型之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是基于他们在内存中的存放位置的地址值的,因为Object的equals方法也是用双等号(==)进行比较的,所以比较后的结果跟双等号(==)的结果相同。
3.String比较特殊:new的作为复合数据类型,直接双引号的作为基本类型。
4. Object有哪些公用方法?
1).clone方法
保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。
主要是JAVA里除了8种基本类型传参数是值传递,其他的类对象传参数都是引用传递,我们有时候不希望在方法里讲参数改变,这是就需要在类中复写clone方法。
2).getClass方法
final方法,获得运行时类型。
3).toString方法
该方法用得比较多,一般子类都有覆盖。
4).finalize方法
该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。
5).equals方法
该方法是非常重要的一个方法。一般equals和==是不一样的,但是在Object中两者是一样的。子类一般都要重写这个方法。
6).hashCode方法
该方法用于哈希查找,可以减少在查找中使用equals的次数,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。
一般必须满足obj1.equals(obj2)==true。可以推出obj1.hash-Code()==obj2.hashCode(),但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价。
如果不重写hashcode(),在HashSet中添加两个equals的对象,会将两个对象都加入进去。
7).wait方法
wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生。
(1)其他线程调用了该对象的notify方法。
(2)其他线程调用了该对象的notifyAll方法。
(3)其他线程调用了interrupt中断该线程。
(4)时间间隔到了。
此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。
8).notify方法
该方法唤醒在该对象上等待的某个线程。
9).notifyAll方法
该方法唤醒在该对象上等待的所有线程。
5. Java的四种引用,强弱软虚,用到的场景。
1、强引用
强引用不会被GC回收,并且在java.lang.ref里也没有实际的对应类型,平时工作接触的最多的就是强引用。
Object obj = new Object();这里的obj引用便是一个强引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
2、软引用
如果一个对象只具有软引用,那就类似于可有可物的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
这里有几点需要说明:
1、System.gc()告诉JVM这是一个执行GC的好时机,但具体执不执行由JVM决定(事实上这段代码一般都会执行GC)
2、Thread.sleep(200); 这是因为从对象被回收到JVM将引用加入refQueue队列,需要一定的时间。而且poll并不是一个阻塞方法,如果没有数据会返回null,所以我们选择等待一段时间。
3、弱引用
如果一个对象只具有弱引用,那就类似于可有可物的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。这里需要注意下:
1、remove这是一个阻塞方法,类似于J.U.C并发包下的阻塞队列,如果没有队列没有数据,那么当前线程一直等待。
2、如果队列有数据,那么remove和pool都会将第一个元素出队。
4、幽灵引用(虚引用)
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。由于Object.finalize()方法的不安全性、低效性,常常使用虚引用完成对象回收前的资源释放工作。参考我的另一篇博客:解释为什么finalize是不安全的,不建议使用
这里特别需要注意:当JVM将虚引用插入到引用队列的时候,虚引用执行的对象内存还是存在的。但是PhantomReference并没有暴露API返回对象。所以如果我想做清理工作,需要继承PhantomReference类,以便访问它指向的对象。如NIO直接内存的自动回收,就使用到了sun.misc.Cleaner。
6. Hashcode的作用。
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. ArrayList、LinkedList、Vector的区别。
ArrayList 是一个可改变大小的数组.当更多的元素加入到ArrayList中时,其大小将会动态地增长.内部的元素可以直接通过get与set方法进行访问,因为ArrayList本质上就是一个数组.
LinkedList 是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList.
当然,这些对比都是指数据量很大或者操作很频繁的情况下的对比,如果数据和运算量很小,那么对比将失去意义.
Vector 和ArrayList类似,但属于强同步类。如果你的程序本身是线程安全的(thread-safe,没有在多个线程之间共享同一个集合/对象),那么使用ArrayList是更好的选择。
Vector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间,而ArrayList每次对size增长50%.
而 LinkedList 还实现了 Queue 接口,该接口比List提供了更多的方法,包括offer(),peek(),poll()等.
注意: 默认情况下ArrayList的初始容量非常小,所以如果可以预估数据量的话,分配一个较大的初始值属于最佳实践,这样可以减少调整大小的开销。
8. String、StringBuffer与StringBuilder的区别。
1.可变与不可变
String类中使用字符数组保存字符串,如下就是,因为有“final”修饰符,所以可以知道string对象是不可变的。
private final char value[];
StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,如下就是,可知这两种对象都是可变的。
char[] value;
2.是否多线程安全
String中的对象是不可变的,也就可以理解为常量,显然线程安全。
AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。
StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
3.StringBuilder与StringBuffer共同点
StringBuilder与StringBuffer有公共父类AbstractStringBuilder(抽象类)。
抽象类与接口的其中一个区别是:抽象类中可以定义一些子类的公共方法,子类只需要增加新的功能,不需要重复写已经存在的方法;而接口中只是对方法的申明和常量的定义。
StringBuilder、StringBuffer的方法都会调用AbstractStringBuilder中的公共方法,如super.append(...)。只是StringBuffer会在方法上加synchronized关键字,进行同步。
最后,如果程序不是多线程的,那么使用StringBuilder效率高于StringBuffer。
9. Map、Set、List、Queue、Stack的特点与用法。
Set集合类似于一个罐子,"丢进"Set集合里的多个对象之间没有明显的顺序。
List集合代表元素有序、可重复的集合,集合中每个元素都有其对应的顺序索引。
Stack是Vector提供的一个子类,用于模拟"栈"这种数据结构(LIFO后进先出)
Queue用于模拟"队列"这种数据结构(先进先出 FIFO)。
Map用于保存具有"映射关系"的数据,因此Map集合里保存着两组值。
10. HashMap和HashTable的区别。
Hashtable是基于陈旧的Dictionary类的,HashMap是Map接口的一个实现 。
Hashtable的方法是线程同步的,而HashMap的方法不是。
只有HashMap可以让你将空值作为一个表的条目的key或value。
11. HashMap和ConcurrentHashMap的区别,HashMap的底层源码。
Hashmap本质是数组加链表。根据key取得hash值,然后计算出数组下标,如果多个key对应到同一个下标,就用链表串起来,新插入的在前面。
ConcurrentHashMap:在hashMap的基础上,ConcurrentHashMap将数据分为多个segment,默认16个(concurrencylevel),然后每次操作对一个segment加锁,避免多线程锁的几率,提高并发效率。
12. TreeMap、HashMap、LindedHashMap的区别。
Hashmap 是一个最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。 LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.也可以在构造时用带参数,按照应用次数排序 TreeMap取出来的是排序后的键值对。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。
13. Collection包结构,与Collections的区别。
Collection 是单列集合
List 元素是有序的、可重复
有序的collection,可以对列表中每个元素的插入位置进行精确地控制。
可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
可存放重复元素,元素存取是有序的。
List接口中常用类
l Vector:线程安全,但速度慢,已被ArrayList替代。
底层数据结构是数组结构
l ArrayList:线程不安全,查询速度快。
底层数据结构是数组结构
l LinkedList:线程不安全。增删速度快。
底层数据结构是列表结构
Set(集) 元素无序的、不可重复。
取出元素的方法只有迭代器。不可以存放重复元素,元素存取是无序的。
Set接口中常用的类
l HashSet:线程不安全,存取速度快。
它是如何保证元素唯一性的呢?依赖的是元素的hashCode方法和euqals方法。
l TreeSet:线程不安全,可以对Set集合中的元素进行排序。
它的排序是如何进行的呢?通过compareTo或者compare方法中的来保证元素的唯一性。元素是以二叉树的形式存放的。
Map 是一个双列集合
|--Hashtable:线程安全,速度快。底层是哈希表数据结构。是同步的。
不允许null作为键,null作为值。
|--Properties:用于配置文件的定义和操作,使用频率非常高,同时键和值都是字符串。
是集合中可以和IO技术相结合的对象。(到了IO在学习它的特有和io相关的功能。)
|--HashMap:线程不安全,速度慢。底层也是哈希表数据结构。是不同步的。
允许null作为键,null作为值。替代了Hashtable.
|--LinkedHashMap: 可以保证HashMap集合有序。存入的顺序和取出的顺序一致。
|--TreeMap:可以用来对Map集合中的键进行排序。
Collection 和 Collections的区别
Collection是集合类的上级接口,子接口主要有Set 和List、Map。
Collections是针对集合类的一个帮助类,提供了操作集合的工具方法:一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。
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是不会执行的
在存在try-catch-finally的方法中,return可能出现的位置有4个,在try中,在catch中,在finally中,在finally后(try-catch-finally外的语句块)。
在这4个位置都出现return的情况下(事实上应该是不可能的,如果前面3个位置都存在return,那么最后一个位置的return就成了unreachable code,编译不会通过),最终会执行的return应该是finally中的return。也就是finally中的return会覆盖掉其它位置的return。
try中有return语句
-------- try catch finally顺序:
1.try {}.
2.如果有Error Exception则,执行catch(){}中的代码。
3.无论有没有 Error Exception都要执行finally{}中的代码。
4.执行 try 中的 return
catch中有return
但当finally中不存在return,而catch中存在return,但finally中的语句又会对catch中的return的值产生影响时,情况就有点复杂。
int ret = 0;
try{
throw new Exception();
}
catch(Exception e){
ret = 1;
return ret;
}
finally{
ret = 2;
}
这里finally中没有return,但是将catch中要return的ret赋值为2.那么最后返回的值是1. 为什么?
从调试中可以知道Finally中的赋值语句的确被执行了,而执行完这条finally语句后的下一条语句就是catch中的return,那么为啥返回的是1呢?
catch中有return,finally中没有return,return的值在执行finally之前已经确定下来了。
另一个值得注意的是最后的return(位于try-catch-finally外)并没有被执行。
15. Excption与Error包结构。OOM你遇到过哪些情况,SOF你遇到过哪些情况。
OOM:
1, OutOfMemoryError异常
除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(OOM)异常的可能,
Java Heap 溢出
一般的异常信息:java.lang.OutOfMemoryError:Javaheap spacess
java堆用于存储对象实例,我们只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,就会在对象数量达到最大堆容量限制后产生内存溢出异常。
出现这种异常,一般手段是先通过内存映像分析工具(如Eclipse Memory Analyzer)对dump出来的堆转存快照进行分析,重点是确认内存中的对象是否是必要的,先分清是因为内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。
如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链。于是就能找到泄漏对象时通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收。
如果不存在泄漏,那就应该检查虚拟机的参数(-Xmx与-Xms)的设置是否适当。
2, 虚拟机栈和本地方法栈溢出
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常
这里需要注意当栈的大小越大可分配的线程数就越少。
3, 运行时常量池溢出
异常信息:java.lang.OutOfMemoryError:PermGenspace
如果要向运行时常量池中添加内容,最简单的做法就是使用String.intern()这个Native方法。该方法的作用是:如果池中已经包含一个等于此String的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。由于常量池分配在方法区内,我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容量。
4, 方法区溢出
方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。
异常信息:java.lang.OutOfMemoryError:PermGenspace
方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收,判定条件是很苛刻的。在经常动态生成大量Class的应用中,要特别注意这点。
SOF:
StackOverFlow。函数栈溢出,一般无限递归的时候会出现。
16. Java面向对象的三个特征与含义。
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。封装是面向对象的特征之一,是对象和类概念的主要特性。简单的说,一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。
继承是指可以让某个类型的对象获得另一个类型的对象的属性的方法。它支持按级分类的概念。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。继承概念的实现方式有二类:实现继承与接口继承。实现继承是指直接使用基类的属性和方法而无需额外编码的能力;接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力。
多态就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。
多态是面向对象三大特征里相对难理解和表述的一个特征。
多态的定义:指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)
多态存在的三个必要条件
一、要有继承;
二、要有重写;
三、父类引用指向子类对象。
17. Override和Overload的含义与区别。
方法的重写(Overriding)和重载(Overloading)是Java多态性的不同表现。
重写(Overriding)是父类与子类之间多态性的一种表现,而重载(Overloading)是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding) 。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被"屏蔽"了。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型或有不同的参数次序,则称为方法的重载(Overloading)。不能通过访问权限、返回类型、抛出的异常进行重载。
1. Override 特点
1、覆盖的方法的标志必须要和被覆盖的方法的标志完全匹配,才能达到覆盖的效果;
2、覆盖的方法的返回值必须和被覆盖的方法的返回一致;
3、覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类;
4、方法被定义为final不能被重写。
5、对于继承来说,如果某一方法在父类中是访问权限是private,那么就不能在子类对其进行重写覆盖,如果定义的话,也只是定义了一个新方法,而不会达到重写覆盖的效果。(通常存在于父类和子类之间。)
2.Overload 特点
1、在使用重载时只能通过不同的参数样式。例如,不同的参数类型,不同的参数个数,不同的参数顺序(当然,同一方法内的几个参数类型必须不一样,例如可以是fun(int, float),但是不能为fun(int, int));
2、不能通过访问权限、返回类型、抛出的异常进行重载;
3、方法的异常类型和数目不会对重载造成影响;
4、重载事件通常发生在同一个类中,不同方法之间的现象。
5、存在于同一类中,但是只有虚方法和抽象方法才能被覆写。
18. Interface与abstract类的区别。
1.abstract class 在Java中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。
2.在abstract class 中可以有自己的数据成员,也可以有非abstarct的方法,而在interface中,只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在 interface中一般不定义数据成员),所有的方法都是public abstract的。
3.抽象类中的变量默认是 friendly 型,其值可以在子类中重新定义,也可以重新赋值。接口中定义的变量默认是public static final 型,且必须给其赋初值,所以实现类中不能重新定义,也不能改变其值。
4.abstract class和interface所反映出的设计理念不同。其实abstract class表示的是"is-a"关系,interface表示的是"like-a"关系。
5.实现抽象类和接口的类必须实现其中的所有方法。抽象类中可以有非抽象方法。接口中则不能有实现方法。
abstract class 和 interface 是 Java语言中的两种定义抽象类的方式,它们之间有很大的相似性。但是对于它们的选择却又往往反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理,因为它们表现了概念间的不同的关系。
19. Static class 与non staticclass的区别。
java允许我们在一个类里面定义静态类。比如内部类(nested class)。把nested class封闭起来的类叫外部类。在java中,我们不能用static修饰顶级类(top level class)。只有内部类可以为static。
静态内部类和非静态内部类之间到底有什么不同呢?下面是两者间主要的不同。
(1)内部静态类不需要有指向外部类的引用。但非静态内部类需要持有对外部类的引用。
(2)非静态内部类能够访问外部类的静态和非静态成员。静态类不能访问外部类的非静态成员。他只能访问外部类的静态成员。
(3)一个非静态内部类不能脱离外部类实体被创建,一个非静态内部类可以访问外部类的数据和方法,因为他就在外部类里面。
20. java多态的实现原理。
众所周知,多态是面向对象编程语言的重要特性,它允许基类的指针或引用指向派生类的对象,而在具体访问时实现方法的动态绑定。C++ 和 Java 作为当前最为流行的两种面向对象编程语言,其内部对于多态的支持到底是如何实现的呢,本文对此做了全面的介绍。
注意到在本文中,指针和引用会互换使用,它们仅是一个抽象概念,表示和另一个对象的连接关系,无须在意其具体的实现。
Java 的实现方式
Java 对于方法调用动态绑定的实现主要依赖于方法表,但通过类引用调用和接口引用调用的实现则有所不同。总体而言,当某个方法被调用时,JVM 首先要查找相应的常量池,得到方法的符号引用,并查找调用类的方法表以确定该方法的直接引用,最后才真正调用该方法。以下分别对该过程中涉及到的相关部分做详细介绍。
JVM 的结构
此结构中,我们只探讨和本文密切相关的方法区 (method area)。当程序运行需要某个类的定义时,载入子系统 (class loader subsystem) 装入所需的 class 文件,并在内部建立该类的类型信息,这个类型信息就存贮在方法区。类型信息一般包括该类的方法代码、类变量、成员变量的定义等等。可以说,类型信息就是类的 Java 文件在运行时的内部结构,包含了改类的所有在 Java 文件中定义的信息。
注意到,该类型信息和 class 对象是不同的。class 对象是 JVM 在载入某个类后于堆 (heap) 中创建的代表该类的对象,可以通过该 class 对象访问到该类型信息。比如最典型的应用,在 Java 反射中应用 class 对象访问到该类支持的所有方法,定义的成员变量等等。可以想象,JVM 在类型信息和 class 对象中维护着它们彼此的引用以便互相访问。两者的关系可以类比于进程对象与真正的进程之间的关系。
Java 的方法调用方式
Java 的方法调用有两类,动态方法调用与静态方法调用。静态方法调用是指对于类的静态方法的调用方式,是静态绑定的;而动态方法调用需要有方法调用所作用的对象,是动态绑定的。类调用 (invokestatic) 是在编译时刻就已经确定好具体调用方法的情况,而实例调用 (invokevirtual) 则是在调用的时候才确定具体的调用方法,这就是动态绑定,也是多态要解决的核心问题。
JVM 的方法调用指令有四个,分别是invokestatic,invokespecial,invokesvirtual 和 invokeinterface。前两个是静态绑定,后两个是动态绑定的。本文也可以说是对于 JVM 后两种调用实现的考察。
21. 实现多线程的两种方法:Thread与Runable。
实现Runnable接口,继承Thread类来说实现Runnable接口好处如下:
(1)适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码,数据有效的分离,较好地体现了面向对象的设计思想。
(2)可以避免由于Java的单继承特性带来的局限。我们经常碰到这样一种情况,即当我们要将已经继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么,这个类就只能采用实现Runnable接口的方式了。
(3)有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码。多个线程操作相同的数据,与它们的代码无关。当共享访问相同的对象是,即它们共享相同的数据。当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参传递进去,这个对象就是一个实现了Runnable接口的类的实例。
22. 线程同步的方法:sychronized、lock、reentrantLock等。
在并发量比较小的情况下,使用synchronized是个不错的选择,但是在并发量比较高的情况下,其性能下降很严重,此时ReentrantLock是个不错的方案。
1、ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候
线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,
如果使用synchronized ,如果A不释放,B将一直等下去,不能被中断
如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情
ReentrantLock获取锁定与三种方式:
a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
c)tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断
2、synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中
3、在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
5.0的多线程任务包对于同步的性能方面有了很大的改进,在原有synchronized关键字的基础上,又增加了ReentrantLock,以及各种Atomic类。了解其性能的优劣程度,有助与我们在特定的情形下做出正确的选择。
总体的结论先摆出来:
synchronized:
在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize,另外可读性非常好,不管用没用过5.0多线程包的程序员都能理解。
ReentrantLock:
ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。
Atomic:
和上面的类似,不激烈情况下,性能比synchronized略逊,而激烈的时候,也能维持常态。激烈的时候,Atomic的性能会优于ReentrantLock一倍左右。但是其有一个缺点,就是只能同步一个值,一段代码中只能出现一个Atomic的变量,多于一个同步无效。因为他不能在多个Atomic之间同步。
所以,我们写同步的时候,优先考虑synchronized,如果有特殊需要,再进一步优化。ReentrantLock和Atomic如果用的不好,不仅不能提高性能,还可能带来灾难。
23. 锁的等级:方法锁、对象锁、类锁。
首先介绍一下对象锁(也叫方法锁)与类锁有那些不同。下文中使用对象锁称呼代替方法锁。
对于对象锁,是针对一个对象的,它只在该对象的某个内存位置声明一个标志位标识该对象是否拥有锁,所以它只会锁住当前的对象。一般一个对象锁是对一个非静态成员变量进行syncronized修饰,或者对一个非静态方法进行syncronized修饰。对于对象锁,不同对象访问同一个被syncronized修饰的方法的时候不会阻塞住。
类锁是锁住整个类的,当有多个线程来声明这个类的对象的时候将会被阻塞,直到拥有这个类锁的对象被销毁或者主动释放了类锁。这个时候在被阻塞住的线程被挑选出一个占有该类锁,声明该类的对象。其他线程继续被阻塞住。
无论是类锁还是对象锁,父类和子类之间是否阻塞没有直接关系。当对一个父类加了类锁,子类是不会受到影响的,相反也是如此。因为synchronized关键字并不是方法签名的一部分,它是对方法进行修饰的。当子类覆写父类中的同步方法或是接口中声明的同步方法的时候,synchronized修饰符是不会被自动继承的,所以相应的阻塞问题不会出现。
注意:这里的阻塞问题是指的按照正常情况下应该阻塞,而因为synchronized是父类与子类之间不可传递导致不会阻塞。那正常情况下阻塞是什么那,下面会详细介绍。但是,当一个子类没有覆盖父类的方法的时候,这时候通过子类访问方法则会产生阻塞。
对于类锁,则会把整个类锁住,也就说只能有一个对象拥有当前类的锁。当一个对象拥有了类锁之后,另外一个对象还想竞争锁的话则会被阻塞。两个对象A,B,如果A正在访问一个被类锁修饰的方法function,那么B则不能访问。因为类锁只能在同一时刻被一个对象拥有。相对于对象锁,则是不同。还是A,B两个对象,如果A正在访问对象锁修饰的function,那么这个时候B也可以同时访问。
对于对象锁,当一个对象拥有锁之后,访问一个加了对象锁的方法,而该方法中又调用了该类中其他加了对象锁的方法,那么这个时候是不会阻塞住的。这是java通过可重入锁机制实现的。可重入锁指的是当一个对象拥有对象锁之后,可以重复获取该锁。因为synchronized块是可重入的,所以当你访问一个对象锁的方法的时候,在该方法中继续访问其他对象锁方法是不会被阻塞的。
24. 写出生产者消费者模式。
import java.util.LinkedList;
public class ProducerConsumer {
private LinkedList<Object> storeHouse = newLinkedList<Object>();
private int MAX = 10;
public ProducerConsumer() {
}
public void start() {
new Producer().start();
new Comsumer().start();
}
class Producer extends Thread {
public void run() {
while (true) {
synchronized (storeHouse) {
try {
while(storeHouse.size() == MAX) {
System.out.println("storeHouseis full , please wait");
storeHouse.wait();
}
Object newOb = newObject();
if(storeHouse.add(newOb)) {
System.out.println("Producer put a Object to storeHouse");
Thread.sleep((long)(Math.random() * 3000));
storeHouse.notify();
}
} catch(InterruptedException ie) {
System.out.println("producer is interrupted!");
}
}
}
}
}
class Comsumer extends Thread {
public void run() {
while (true) {
synchronized (storeHouse) {
try {
while(storeHouse.size() == 0) {
System.out.println("storeHouse is empty , please wait");
storeHouse.wait();
}
storeHouse.removeLast();
System.out.println("Comsumer get a Object from storeHouse");
Thread.sleep((long)(Math.random() * 3000));
storeHouse.notify();
} catch(InterruptedException ie) {
System.out.println("Consumer is interrupted");
}
}
}
}
}
public static void main(String[] args) throws Exception {
ProducerConsumer pc = new ProducerConsumer();
pc.start();
}
}
25. ThreadLocal的设计理念与作用。
总之,ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。归纳了两点:
1。每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
2。将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。
ThreadLocal的应用场合,我觉得最适合的是多线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。
当然如果要把本来线程共享的对象通过ThreadLocal.set()放到线程中也可以,可以实现避免参数传递的访问方式,但是要注意get()到的是那同一个共享对象,并发访问问题要靠其他手段来解决。但一般来说线程共享的对象通过设置为某类的静态变量就可以实现方便的访问了,似乎没必要放到线程中。
26. ThreadPool用法与优势。
合理利用线程池能够带来三个好处。第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。但是要做到合理的利用线程池,必须对其原理了如指掌。
线程池的处理流程如下:
首先线程池判断基本线程池是否已满?没满,创建一个工作线程来执行任务。满了,则进入下个流程。
其次线程池判断工作队列是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程。
最后线程池判断整个线程池是否已满?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务。
27. Concurrent包里的其他东西:ArrayBlockingQueue、CountDownLatch等等。
我们都知道,在JDK1.5之前,Java中要进行业务并发时,通常需要有程序员独立完成代码实现,当然也有一些开源的框架提供了这些功能,但是这些依然没有JDK自带的功能使用起来方便。而当针对高质量Java多线程并发程序设计时,为防止死蹦等现象的出现,比如使用java之前的wait()、notify()和synchronized等,每每需要考虑性能、死锁、公平性、资源管理以及如何避免线程安全性方面带来的危害等诸多因素,往往会采用一些较为复杂的安全策略,加重了程序员的开发负担.万幸的是,在JDK1.5出现之后,Sun大神(Doug
Lea)终于为我们这些可怜的小程序员推出了java.util.concurrent工具包以简化并发完成。开发者们借助于此,将有效的减少竞争条件(race conditions)和死锁线程。concurrent包很好的解决了这些问题,为我们提供了更实用的并发程序模型。
Executor :具体Runnable任务的执行者。
ExecutorService :一个线程池管理者,其实现类有多种,我会介绍一部分。我们能把Runnable,Callable提交到池中让其调度。
Semaphore :一个计数信号量
ReentrantLock :一个可重入的互斥锁定 Lock,功能类似synchronized,但要强大的多。
Future :是与Runnable,Callable进行交互的接口,比如一个线程执行结束后取返回的结果等等,还提供了cancel终止线程。
BlockingQueue :阻塞队列。
CompletionService : ExecutorService的扩展,可以获得线程执行结果的
CountDownLatch :一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
CyclicBarrier :一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点
Future :Future 表示异步计算的结果。
ScheduledExecutorService :一个ExecutorService,可安排在给定的延迟后运行或定期执行的命令。
Semaphore
一个计数信号量。从概念上讲,信号量维护了一个许可集合。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。
ReentrantLock
一个可重入的互斥锁定 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁定相同的一些基本行为和语义,但功能更强大。
ReentrantLock 将由最近成功获得锁定,并且还没有释放该锁定的线程所拥有。当锁定没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁定并返回。如果当前线程已经拥有该锁定,此方法将立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查此情况是否发生。
此类的构造方法接受一个可选的公平参数。
当设置为 true时,在多个线程的争用下,这些锁定倾向于将访问权授予等待时间最长的线程。否则此锁定将无法保证任何特定访问顺序。
与采用默认设置(使用不公平锁定)相比,使用公平锁定的程序在许多线程访问时表现为很低的总体吞吐量(即速度很慢,常常极其慢),但是在获得锁定和保证锁定分配的均衡性时差异较小。不过要注意的是,公平锁定不能保证线程调度的公平性。因此,使用公平锁定的众多线程中的一员可能获得多倍的成功机会,这种情况发生在其他活动线程没有被处理并且目前并未持有锁定时。还要注意的是,未定时的 tryLock 方法并没有使用公平设置。因为即使其他线程正在等待,只要该锁定是可用的,此方法就可以获得成功。
BlockingQueue
支持两个附加操作的 Queue,这两个操作是:检索元素时等待队列变为非空,以及存储元素时等待空间变得可用。
BlockingQueue 不接受 null 元素。试图 add、put 或 offer 一个 null 元素时,某些实现会抛出NullPointerException。null 被用作指示 poll 操作失败的警戒值。
BlockingQueue 可以是限定容量的。它在任意给定时间都可以有一个 remainingCapacity,超出此容量,便无法无阻塞地 put 额外的元素。
没有任何内部容量约束的 BlockingQueue 总是报告 Integer.MAX_VALUE 的剩余容量。
BlockingQueue 实现主要用于生产者-使用者队列,但它另外还支持Collection 接口。因此,举例来说,使用 remove(x) 从队列中移除任意一个元素是有可能的。
然而,这种操作通常不会有效执行,只能有计划地偶尔使用,比如在取消排队信息时。
BlockingQueue 实现是线程安全的。所有排队方法都可以使用内部锁定或其他形式的并发控制来自动达到它们的目的。
然而,大量的 Collection 操作(addAll、containsAll、retainAll 和 removeAll)没有必要自动执行,除非在实现中特别说明。
因此,举例来说,在只添加了 c 中的一些元素后,addAll(c) 有可能失败(抛出一个异常)。
BlockingQueue 实质上不 支持使用任何一种“close”或“shutdown”操作来指示不再添加任何项。
这种功能的需求和使用有依赖于实现的倾向。例如,一种常用的策略是:对于生产者,插入特殊的 end-of-stream 或 poison 对象,并根据使用者获取这些对象的时间来对它们进行解释。
CountDownLatch
一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
用给定的计数初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。
之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。
CountDownLatch 是一个通用同步工具,它有很多用途。将计数 1 初始化的 CountDownLatch 用作一个简单的开/关锁存器,
或入口:在通过调用 countDown() 的线程打开入口前,所有调用 await 的线程都一直在入口处等待。
用 N 初始化的 CountDownLatch 可以使一个线程在 N 个线程完成某项操作之前一直等待,或者使其在某项操作完成 N 次之前一直等待。
CountDownLatch 的一个有用特性是,它不要求调用 countDown 方法的线程等到计数到达零时才继续,
而在所有线程都能通过之前,它只是阻止任何线程继续通过一个 await。
28. wait()和sleep()的区别。
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
29. foreach与正常for循环效率对比。
直接for循环效率最高,其次是迭代器和 ForEach操作。作为语法糖,其实 ForEach 编译成字节码之后,使用的是迭代器实现的。
30. Java IO与NIO。
就速度来说 CPU> 内存 > 硬盘
I- 就是从硬盘到内存
O- 就是从内存到硬盘
第一种方式:我从硬盘读取数据,然后程序一直等,数据读完后,继续操作。这种方式是最简单的,叫阻塞IO。
第二种方式:我从硬盘读取数据,然后程序继续向下执行,等数据读取完后,通知当前程序(对硬件来说叫中断,对程序来说叫回调),然后此程序可以立即处理数据,也可以执行完当前操作在读取数据。
在以前的 Java IO 中,都是阻塞式 IO,NIO 引入了非阻塞式 IO。
还有一种就是同步 IO 和异步 IO。经常说的一个术语就是“异步非阻塞”,好象异步和非阻塞是同一回事,这大概是一个误区吧。
至于 Java NIO 的 Selector,在旧的 Java IO 系统中,是基于 Stream 的,即“流”,流式 IO。
当程序从硬盘往内存读取数据的时候,操作系统使用了 2 个“小伎俩”来提高性能,那就是预读,如果我读取了第一扇区的第三磁道的内容,那么你很有可能也会使用第二磁道和第四磁道的内容,所以操作系统会把附近磁道的内容提前读取出来,放在内存中,即缓存。
(PS:以上过程简化了)
通过上面可以看到,操作系统是按块 Block从硬盘拿数据,就如同一个大脸盆,一下子就放入了一盆水。但是,当 Java 使用的时候,旧的 IO 确实基于流 Stream的,也就是虽然操作系统给我了一脸盆水,但是我得用吸管慢慢喝。
于是,NIO 横空出世。
31. 反射的作用与原理。
Java的反射机制是Java特性之一,反射机制是构建框架技术的基础所在。灵活掌握Java反射机制,对大家以后学习框架技术有很大的帮助。
那么什么是Java的反射呢?
大家都知道,要让Java程序能够运行,那么就得让Java类要被Java虚拟机加载。Java类如果不被Java虚拟机加载,是不能正常运行的。现在我们运行的所有的程序都是在编译期的时候就已经知道了你所需要的那个类的已经被加载了。
Java的反射机制是在编译并不确定是哪个类被加载了,而是在程序运行的时候才加载、探知、自审。使用在编译期并不知道的类。这样的特点就是反射。
那么Java反射有什么作用呢?
假如我们有两个程序员,一个程序员在写程序的时候,需要使用第二个程序员所写的类,但第二个程序员并没完成他所写的类。那么第一个程序员的代码能否通过编译呢?这是不能通过编译的。利用Java反射的机制,就可以让第一个程序员在没有得到第二个程序员所写的类的时候,来完成自身代码的编译。
Java的反射机制它知道类的基本结构,这种对Java类结构探知的能力,我们称为Java类的“自审”。大家都用过Jcreator和eclipse。当我们构建出一个对象的时候,去调用该对象的方法和属性的时候。一按点,编译工具就会自动的把该对象能够使用的所有的方法和属性全部都列出来,供用户进行选择。这就是利用了Java反射的原理,是对我们创建对象的探知、自审。
Class类
要正确使用Java反射机制就得使用java.lang.Class这个类。它是Java反射机制的起源。当一个类被加载以后,Java虚拟机就会自动产生一个Class对象。通过这个Class对象我们就能获得加载到虚拟机当中这个Class对象对应的方法、成员以及构造方法的声明和定义等信息。
反射API
反射API用于反应在当前Java虚拟机中的类、接口或者对象信息
功能
—获取一个对象的类信息.
—获取一个类的访问修饰符、成员、方法、构造方法以及超类的信息.
—检获属于一个接口的常量和方法声明.
—创建一个直到程序运行期间才知道名字的类的实例.
—获取并设置一个对象的成员,甚至这个成员的名字是
在程序运行期间才知道.
—检测一个在运行期间才知道名字的对象的方法
利用Java反射机制我们可以很灵活的对已经加载到Java虚拟机当中的类信息进行检测。当然这种检测在对运行的性能上会有些减弱,所以什么时候使用反射,就要靠业务的需求、大小,以及经验的积累来决定。
32. 泛型常用特点,List<String>能否转为List<Object>。
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
由此,我们发现,在使用泛型类时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,传入不同泛型实参的泛型类在内存上只有一个,即还是原来的最基本的类型(本实例中为Box),当然,在逻辑上我们可以理解成多个不同的泛型类型。
究其原因,在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。
对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。
33. 解析XML的几种方式的原理与特点:DOM、SAX、PULL。
1.DOM生成和解析XML文档
为 XML 文档的已解析版本定义了一组接口。解析器读入整个文档,然后构建一个驻留内存的树结构,然后代码就可以使用 DOM 接口来操作这个树结构。优点:整个文档树在内存中,便于操作;支持删除、修改、重新排列等多种功能;缺点:将整个文档调入内存(包括无用的节点),浪费时间和空间;使用场合:一旦解析了文档还需多次访问这些数据;硬件资源充足(内存、CPU)。
2.SAX生成和解析XML文档
为解决DOM的问题,出现了SAX。SAX ,事件驱动。当解析器发现元素开始、元素结束、文本、文档的开始或结束等时,发送事件,程序员编写响应这些事件的代码,保存数据。优点:不用事先调入整个文档,占用资源少;SAX解析器代码比DOM解析器代码小,适于Applet,下载。缺点:不是持久的;事件过后,若没保存数据,那么数据就丢了;无状态性;从事件中只能得到文本,但不知该文本属于哪个元素;使用场合:Applet;只需XML文档的少量内容,很少回头访问;机器内存少。
3.DOM4J生成和解析XML文档
DOM4J 是一个非常非常优秀的Java XMLAPI,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件。如今你可以看到越来越多的 Java 软件都在使用 DOM4J 来读写 XML,特别值得一提的是连 Sun 的 JAXM 也在用 DOM4J。
4.PULL解析器:
PULL解析器的运行方式和SAX类似,都是基于事件的模式。不同的是,在PULL解析过程中,我们需要自己获取产生的事件然后做相应的操作,而不像SAX那样由处理器触发一种事件的方法,执行我们的代码。PULL解析器小巧轻便,解析速度快,简单易用,非常适合在Android移动设备中使用,Android系统内部在解析各种XML时也是用PULL解析器。
34. Java与C++对比。
然而,C++和Java之间仍存在一些显著的差异。可以这样说,这些差异代表着技术的极大进步。一旦我们弄清楚了这些差异,就会理解为什么说Java是一种优秀的程序设计语言。本附录将引导大家认识用于区分Java和C++的一些重要特征。
(1) 最大的障碍在于速度:解释过的Java要比C的执行速度慢上约20倍。无论什么都不能阻止Java语言进行编译。写作本书的时候,刚刚出现了一些准实时编译器,它们能显著加快速度。当然,我们完全有理由认为会出现适用于更多流行平台的纯固有编译器,但假若没有那些编译器,由于速度的限制,必须有些问题是Java不能解决的。
(2) 和C++一样,Java也提供了两种类型的注释。
(3) 所有东西都必须置入一个类。不存在全局函数或者全局数据。如果想获得与全局函数等价的功能,可考虑将static方法和static数据置入一个类里。注意没有象结构、枚举或者联合这一类的东西,一切只有“类”(Class)!
(4) 所有方法都是在类的主体定义的。所以用C++的眼光看,似乎所有函数都已嵌入,但实情并非如何(嵌入的问题在后面讲述)。
(5) 在Java中,类定义采取几乎和C++一样的形式。但没有标志结束的分号。没有class foo这种形式的类声明,只有类定义。
class aType()
void aMethod() {/* 方法主体*/}
}
(6) Java中没有作用域范围运算符“::”。Java利用点号做所有的事情,但可以不用考虑它,因为只能在一个类里定义元素。即使那些方法定义,也必须在一个类的内部,所以根本没有必要指定作用域的范围。我们注意到的一项差异是对static方法的调用:使用ClassName.methodName()。除此以外,package(包)的名字是用点号建立的,并能用import关键字实现C++的“#include”的一部分功能。例如下面这个语句:
import java.awt.*;
(#include并不直接映射成import,但在使用时有类似的感觉。)
(7) 与C++类似,Java含有一系列“主类型”(Primitive type),以实现更有效率的访问。在Java中,这些类型包括boolean,char,byte,short,int,long,float以及double。所有主类型的大小都是固有的,且与具体的机器无关(考虑到移植的问题)。这肯定会对性能造成一定的影响,具体取决于不同的机器。对类型的检查和要求在Java里变得更苛刻。例如:
■条件表达式只能是boolean(布尔)类型,不可使用整数。
■必须使用象X+Y这样的一个表达式的结果;不能仅仅用“X+Y”来实现“副作用”。
(8) char(字符)类型使用国际通用的16位Unicode字符集,所以能自动表达大多数国家的字符。
(9) 静态引用的字串会自动转换成String对象。和C及C++不同,没有独立的静态字符数组字串可供使用。
(10) Java增添了三个右移位运算符“>>>”,具有与“逻辑”右移位运算符类似的功用,可在最末尾插入零值。“>>”则会在移位的同时插入符号位(即“算术”移位)。
(11) 尽管表面上类似,但与C++相比,Java数组采用的是一个颇为不同的结构,并具有独特的行为。有一个只读的length成员,通过它可知道数组有多大。而且一旦超过数组边界,运行期检查会自动丢弃一个异常。所有数组都是在内存“堆”里创建的,我们可将一个数组分配给另一个(只是简单地复制数组句柄)。数组标识符属于第一级对象,它的所有方法通常都适用于其他所有对象。
(12) 对于所有不属于主类型的对象,都只能通过new命令创建。和C++不同,Java没有相应的命令可以“在堆栈上”创建不属于主类型的对象。所有主类型都只能在堆栈上创建,同时不使用new命令。所有主要的类都有自己的“封装(器)”类,所以能够通过new创建等价的、以内存“堆”为基础的对象(主类型数组是一个例外:它们可象C++那样通过集合初始化进行分配,或者使用new)。
(13) Java中不必进行提前声明。若想在定义前使用一个类或方法,只需直接使用它即可——编译器会保证使用恰当的定义。所以和在C++中不同,我们不会碰到任何涉及提前引用的问题。
(14) Java没有预处理机。若想使用另一个库里的类,只需使用import命令,并指定库名即可。不存在类似于预处理机的宏。
(15) Java用包代替了命名空间。由于将所有东西都置入一个类,而且由于采用了一种名为“封装”的机制,它能针对类名进行类似于命名空间分解的操作,所以命名的问题不再进入我们的考虑之列。数据包也会在单独一个库名下收集库的组件。我们只需简单地“import”(导入)一个包,剩下的工作会由编译器自动完成。
(16) 被定义成类成员的对象句柄会自动初始化成null。对基本类数据成员的初始化在Java里得到了可靠的保障。若不明确地进行初始化,它们就会得到一个默认值(零或等价的值)。可对它们进行明确的初始化(显式初始化):要么在类内定义它们,要么在构建器中定义。采用的语法比C++的语法更容易理解,而且对于static和非static成员来说都是固定不变的。我们不必从外部定义static成员的存储方式,这和C++是不同的。
(17) 在Java里,没有象C和C++那样的指针。用new创建一个对象的时候,会获得一个引用(本书一直将其称作“句柄”)。例如:
String s = new String("howdy");
然而,C++引用在创建时必须进行初始化,而且不可重定义到一个不同的位置。但Java引用并不一定局限于创建时的位置。它们可根据情况任意定义,这便消除了对指针的部分需求。在C和C++里大量采用指针的另一个原因是为了能指向任意一个内存位置(这同时会使它们变得不安全,也是Java不提供这一支持的原因)。指针通常被看作在基本变量数组中四处移动的一种有效手段。Java允许我们以更安全的形式达到相同的目标。解决指针问题的终极方法是“固有方法”(已在附录A讨论)。将指针传递给方法时,通常不会带来太大的问题,因为此时没有全局函数,只有类。而且我们可传递对对象的引用。Java语言最开始声称自己“完全不采用指针!”但随着许多程序员都质问没有指针如何工作?于是后来又声明“采用受到限制的指针”。大家可自行判断它是否“真”的是一个指针。但不管在何种情况下,都不存在指针“算术”。
(18) Java提供了与C++类似的“构建器”(Constructor)。如果不自己定义一个,就会获得一个默认构建器。而如果定义了一个非默认的构建器,就不会为我们自动定义默认构建器。这和C++是一样的。注意没有复制构建器,因为所有自变量都是按引用传递的。
(19) Java中没有“破坏器”(Destructor)。变量不存在“作用域”的问题。一个对象的“存在时间”是由对象的存在时间决定的,并非由垃圾收集器决定。有个finalize()方法是每一个类的成员,它在某种程度上类似于C++的“破坏器”。但finalize()是由垃圾收集器调用的,而且只负责释放“资源”(如打开的文件、套接字、端口、URL等等)。如需在一个特定的地点做某样事情,必须创建一个特殊的方法,并调用它,不能依赖finalize()。而在另一方面,C++中的所有对象都会(或者说“应该”)破坏,但并非Java中的所有对象都会被当作“垃圾”收集掉。由于Java不支持破坏器的概念,所以在必要的时候,必须谨慎地创建一个清除方法。而且针对类内的基础类以及成员对象,需要明确调用所有清除方法。
(20) Java具有方法“过载”机制,它的工作原理与C++函数的过载几乎是完全相同的。
(21) Java不支持默认自变量。
(22) Java中没有goto。它采取的无条件跳转机制是“break 标签”或者“continue 标准”,用于跳出当前的多重嵌套循环。
(23) Java采用了一种单根式的分级结构,因此所有对象都是从根类Object统一继承的。而在C++中,我们可在任何地方启动一个新的继承树,所以最后往往看到包含了大量树的“一片森林”。在Java中,我们无论如何都只有一个分级结构。尽管这表面上看似乎造成了限制,但由于我们知道每个对象肯定至少有一个Object接口,所以往往能获得更强大的能力。C++目前似乎是唯一没有强制单根结构的唯一一种OO语言。
(24) Java没有模板或者参数化类型的其他形式。它提供了一系列集合:Vector(向量),Stack(堆栈)以及Hashtable(散列表),用于容纳Object引用。利用这些集合,我们的一系列要求可得到满足。但这些集合并非是为实现象C++“标准模板库”(STL)那样的快速调用而设计的。Java 1.2中的新集合显得更加完整,但仍不具备正宗模板那样的高效率使用手段。
(25) “垃圾收集”意味着在Java中出现内存漏洞的情况会少得多,但也并非完全不可能(若调用一个用于分配存储空间的固有方法,垃圾收集器就不能对其进行跟踪监视)。然而,内存漏洞和资源漏洞多是由于编写不当的finalize()造成的,或是由于在已分配的一个块尾释放一种资源造成的(“破坏器”在此时显得特别方便)。垃圾收集器是在C++基础上的一种极大进步,使许多编程问题消弥于无形之中。但对少数几个垃圾收集器力有不逮的问题,它却是不大适合的。但垃圾收集器的大量优点也使这一处缺点显得微不足道。
(26) Java内建了对多线程的支持。利用一个特殊的Thread类,我们可通过继承创建一个新线程(放弃了run()方法)。若将synchronized(同步)关键字作为方法的一个类型限制符使用,相互排斥现象会在对象这一级发生。在任何给定的时间,只有一个线程能使用一个对象的synchronized方法。在另一方面,一个synchronized方法进入以后,它首先会“锁定”对象,防止其他任何synchronized方法再使用那个对象。只有退出了这个方法,才会将对象“解锁”。在线程之间,我们仍然要负责实现更复杂的同步机制,方法是创建自己的“监视器”类。递归的synchronized方法可以正常运作。若线程的优先等级相同,则时间的“分片”不能得到保证。
(27) 我们不是象C++那样控制声明代码块,而是将访问限定符(public,private和protected)置入每个类成员的定义里。若未规定一个“显式”(明确的)限定符,就会默认为“友好的”(friendly)。这意味着同一个包里的其他元素也可以访问它(相当于它们都成为C++的“friends”——朋友),但不可由包外的任何元素访问。类——以及类内的每个方法——都有一个访问限定符,决定它是否能在文件的外部“可见”。private关键字通常很少在Java中使用,因为与排斥同一个包内其他类的访问相比,“友好的”访问通常更加有用。然而,在多线程的环境中,对private的恰当运用是非常重要的。Java的protected关键字意味着“可由继承者访问,亦可由包内其他元素访问”。注意Java没有与C++的protected关键字等价的元素,后者意味着“只能由继承者访问”(以前可用“private
protected”实现这个目的,但这一对关键字的组合已被取消了)。
(28) 嵌套的类。在C++中,对类进行嵌套有助于隐藏名称,并便于代码的组织(但C++的“命名空间”已使名称的隐藏显得多余)。Java的“封装”或“打包”概念等价于C++的命名空间,所以不再是一个问题。Java 1.1引入了“内部类”的概念,它秘密保持指向外部类的一个句柄——创建内部类对象的时候需要用到。这意味着内部类对象也许能访问外部类对象的成员,毋需任何条件——就好象那些成员直接隶属于内部类对象一样。这样便为回调问题提供了一个更优秀的方案——C++是用指向成员的指针解决的。
(29) 由于存在前面介绍的那种内部类,所以Java里没有指向成员的指针。
(30) Java不存在“嵌入”(inline)方法。Java编译器也许会自行决定嵌入一个方法,但我们对此没有更多的控制权力。在Java中,可为一个方法使用final关键字,从而“建议”进行嵌入操作。然而,嵌入函数对于C++的编译器来说也只是一种建议。
(31) Java中的继承具有与C++相同的效果,但采用的语法不同。Java用extends关键字标志从一个基础类的继承,并用super关键字指出准备在基础类中调用的方法,它与我们当前所在的方法具有相同的名字(然而,Java中的super关键字只允许我们访问父类的方法——亦即分级结构的上一级)。通过在C++中设定基础类的作用域,我们可访问位于分级结构较深处的方法。亦可用super关键字调用基础类构建器。正如早先指出的那样,所有类最终都会从Object里自动继承。和C++不同,不存在明确的构建器初始化列表。但编译器会强迫我们在构建器主体的开头进行全部的基础类初始化,而且不允许我们在主体的后面部分进行这一工作。通过组合运用自动初始化以及来自未初始化对象句柄的异常,成员的初始化可得到有效的保证。
public class Foo extends Bar {
public Foo(String msg) {
super(msg); // Calls base constructor
}
public baz(int i) { // Override
super.baz(i); // Calls base method
}
}
(32) Java中的继承不会改变基础类成员的保护级别。我们不能在Java中指定public,private或者protected继承,这一点与C++是相同的。此外,在衍生类中的优先方法不能减少对基础类方法的访问。例如,假设一个成员在基础类中属于public,而我们用另一个方法代替了它,那么用于替换的方法也必须属于public(编译器会自动检查)。
(33) Java提供了一个interface关键字,它的作用是创建抽象基础类的一个等价物。在其中填充抽象方法,且没有数据成员。这样一来,对于仅仅设计成一个接口的东西,以及对于用extends关键字在现有功能基础上的扩展,两者之间便产生了一个明显的差异。不值得用abstract关键字产生一种类似的效果,因为我们不能创建属于那个类的一个对象。一个abstract(抽象)类可包含抽象方法(尽管并不要求在它里面包含什么东西),但它也能包含用于具体实现的代码。因此,它被限制成一个单一的继承。通过与接口联合使用,这一方案避免了对类似于C++虚拟基础类那样的一些机制的需要。
为创建可进行“例示”(即创建一个实例)的一个interface(接口)的版本,需使用implements关键字。它的语法类似于继承的语法,如下所示:
public interface Face {
public void smile();
}
public class Baz extends Bar implements Face {
public void smile( ) {
System.out.println("a warm smile");
}
}
(34) Java中没有virtual关键字,因为所有非static方法都肯定会用到动态绑定。在Java中,程序员不必自行决定是否使用。
35. Java1.7与1.8新特性。
Java7:
1.对集合类的语言支持;
2.自动资源管理;
3.改进的通用实例创建类型推断;
4.数字字面量下划线支持;
5.switch中使用string;
6.二进制字面量;
7.简化可变参数方法调用。
Java8:
一、接口的默认方法
二、Lambda 表达式
三、函数式接口
四、方法与构造函数引用
五、Lambda 作用域
六、访问局部变量
七、访问对象字段与静态变量
八、访问接口的默认方法
九、Date API
十、Annotation 注解
36. 设计模式:单例、工厂、适配器、责任链、观察者等等。
http://www.cnblogs.com/maowang1991/archive/2013/04/15/3023236.html
这篇博文介绍得很详细。
37. JNI的使用。
调用本地方法。
了解JNI:JAVA因其跨平台特性而受人们喜爱,也正因此,使得它和本机各种内部联系变得很少,所以JNI(Java Native Interface)就是用来解决JAVA本地操作的一种方式。JAVA通过JNI调用本地方法,而本地方法是以库文件的形式存放的(在WINDOWS平台上是DLL文件形式,在UNIX机器上是SO文件形式)。通过调用本地的库文件的内部方法,使JAVA可以实现和本地机器的紧密联系,调用系统级的各接口方法。