J2EE基础知识点总结

Posted sunsfan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了J2EE基础知识点总结相关的知识,希望对你有一定的参考价值。

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虚拟机当中的类信息进行检测。当然这种检测在对运行的性能上会有些减弱,所以什么时候使用反射,就要靠业务

以上是关于J2EE基础知识点总结的主要内容,如果未能解决你的问题,请参考以下文章

J2EE基础知识点总结

[转] J2EE基础知识

J2EE基础总结——EJB

Spring 知识点总结

J2EE基础

2016-09-06 J2EE基础知识之不知