Java面试宝典——基础部分(二)

41、运行时异常与一般异常有何异同?
异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能
遇到的异常,是一种常见运行错误。java 编译器要求方法必须声明抛出可能发生的非运行
时异常,但是并不要求必须声明抛出未被捕获的运行时异常。


42、errorexception 有什么区别? 
error 表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出。不可能指
望程序能处理这样的情况。 exception 表示一种设计或实现问题。也就是说,它表示如果程
序运行正常,从不会发生的情况。
 
43、Java 中的异常处理机制的简单原理和应用。
异常是指 java 程序运行时(非编译)所发生的非正常情况或错误,与现实生活中的事件很
相似,现实生活中的事件可以包含事件发生的时间、地点、人物、情节等信息,可以用一个
对象来表示,Java 使用面向对象的方式来处理异常,它把程序中发生的每个异常也都分别
封装到一个对象来表示的,该对象中包含有异常的信息。
Java 对异常进行了分类,不同类型的异常分别用不同的 Java 类表示,所有异常的根类为
java.lang.Throwable,Throwable 下面又派生了两个子类:Error 和 Exception,Error 表示
应用程序本身无法克服和恢复的一种严重问题,程序只有死的份了,例如,说内存溢出和线
程死锁等系统问题。Exception 表示程序还能够克服和恢复的问题,其中又分为系统异常和
普通异常,系统异常是软件本身缺陷所导致的问题,也就是软件开发人员考虑不周所导致的
问题,软件使用者无法克服和恢复这种问题,但在这种问题下还可以让软件系统继续运行或
者让软件死掉,例如,数组脚本越界(ArrayIndexOutOfBoundsException),空指针异常
(NullPointerException)、类转换异常(ClassCastException);普通异常是运行环境的变
化或异常所导致的问题,是用户能够克服的问题,例如,网络断线,硬盘空间不够,发生这
样的异常后,程序不应该死掉。
java 为系统异常和普通异常提供了不同的解决方案,编译器强制普通异常必须 try..catch 处
理或用 throws 声明继续抛给上层调用方法处理,所以普通异常也称为 checked 异常,而系
统异常可以处理也可以不处理,所以,编译器不强制用 try..catch 处理或用 throws 声明,所
以系统异常也称为 unchecked 异常。
 
提示答题者:就按照三个级别去思考:虚拟机必须宕机的错误,程序可以死掉也可以不死掉
的错误,程序不应该死掉的错误;


44、请写出你最常见到的 5runtime exception
这道题主要考你的代码量到底多大,如果你长期写代码的,应该经常都看到过一些系统方面
的异常,你不一定真要回答出5个具体的系统异常,但你要能够说出什么是系统异常,以及
几个系统异常就可以了,当然,这些异常完全用其英文名称来写是最好的,如果实在写不出,
那就用中文吧,有总比没有强!
所谓系统异常,就是…..,它们都是 RuntimeException 的子类,在 jdk doc 中查
RuntimeException 类,就可以看到其所有的子类列表,也就是看到了所有的系统异常。我
比较有印象的系统异常有:NullPointerException、ArrayIndexOutOfBoundsException、
ClassCastException。


45、JAVA 语言如何进行异常处理,关键字:throws,throw,try,catch,finally
别代表什么意义?在 try 块中可以抛出异常吗?
throws 捕获并向外抛出异常
throw 抛出异常
try catch 是内部捕获异常并做自定义处理
finally 是无论是否有异常都会被处理的语句,除非在 finally 前存在被执行的
System.exit(int i)时除外


46、java 中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop()
suspend()方法为何不推荐使用?
 
java5以前,有如下两种:
第一种:
new Thread(){}.start();这表示调用 Thread 子类对象的 run 方法,new Thread(){}表示一个
Thread 的匿名子类的实例对象,子类加上 run 方法后的代码如下:

new Thread(){
    public void run(){
    } 
}.start();

 
第二种:
new Thread(new Runnable(){}).start();这表示调用 Thread 对象接受的 Runnable 对象的 run
方法,new Runnable(){}表示一个 Runnable 的匿名子类的实例对象,runnable 的子类加上
run 方法后的代码如下:

new Thread(new Runnable(){
    public voidrun(){
    } 
    } 
).start();

从 java5开始,还有如下一些线程池创建多线程的方式:

ExecutorService pool = Executors.newFixedThreadPool(3)
for(int i=0;i<10;i++)
{ 
    pool.execute(newRunable(){public void run(){}});
} 
Executors.newCachedThreadPool().execute(new Runable(){publicvoid run(){}});
Executors.newSingleThreadExecutor().execute(new Runable(){publicvoid run(){}});

有两种实现方法,分别使用 new Thread()和 new Thread(runnable)形式,第一种直接调用
thread 的 run 方法,所以,我们往往使用 Thread 子类,即 new SubThread()。第二种调用
runnable 的 run 方法。
 
有两种实现方法,分别是继承 Thread 类与实现 Runnable 接口
synchronized 关键字修饰同步方法
反对使用 stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象处于一
种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题
所在。suspend()方法容易发生死锁。调用 suspend()的时候,目标线程会停下来,但却仍
然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的
线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定
的资源,就会造成死锁。所以不应该使用 suspend(),而应在自己的 Thread 类中置入一个
标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用 wait()命其进入等待状
态。若标志指出线程应当恢复,则用一个 notify()重新启动线程。


47、sleep()和 wait()有什么区别? 
 (网上的答案:sleep 是线程类(Thread)的方法,导致此线程暂停执行指定时间,
给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用 sleep 不会释放
对象锁。 wait 是 Object 类的方法,对此对象调用 wait 方法导致本线程放弃对象锁,进入
等待此对象的等待锁定池,只有针对此对象发出 notify 方法(或 notifyAll)后本线程才进入
对象锁定池准备获得对象锁进入运行状态。)
 
sleep 就是正在执行的线程主动让出 cpu,cpu 去执行其他线程,在 sleep 指定的时间过后,
cpu 才会回到这个线程上继续往下执行,如果当前线程进入了同步锁,sleep 方法并不会释
放锁,即使当前线程使用 sleep 方法让出了 cpu,但其他被同步锁挡住了的线程也无法得到
执行。wait 是指在一个已经进入了同步锁的线程内,让自己暂时让出同步锁,以便其他正在
等待此锁的线程可以得到同步锁并运行,只有其他线程调用了 notify 方法(notify 并不释放
锁,只是告诉调用过 wait 方法的线程可以去参与获得锁的竞争了,但不是马上得到锁,因
为锁还在别人手里,别人还没释放。如果 notify 方法后面的代码还有很多,需要这些代码执
行完后才会释放锁,可以在 notfiy 方法后增加一个等待和一些代码,看看效果),调用 wait
方法的线程就会解除 wait 状态和程序可以再次得到锁后继续向下运行。对于 wait 的讲解一
定要配合例子代码来说明,才显得自己真明白。

package com.huawei.interview; 
publicclass MultiThread {
   /**
    * @paramargs
    */
    public static voidmain(String[] args) {
        // TODO Auto-generated method stub
        new Thread(newThread1()).start();
        try {             
            Thread.sleep(10);             
        }
        catch (InterruptedException e) {
            // TODO Auto-generated catchblock
            e.printStackTrace();             
        }         
        new Thread(newThread2()).start(); 
    } 
    private static classThread1implements Runnable
    {   
        @Override
        public void run() {
            // TODO Auto-generated methodstub
            //由于这里的 Thread1和下面的 Thread2内部 run 方法要用同一对象作为监视器,我们这里不能用 this,因为在 Thread2里面的 this 和这个 Thread1的 this 不是同一个对象。我们用MultiThread.class 这个字节码对象,当前虚拟机里引用这个变量时,指向的都是同一个对象。
            synchronized (MultiThread.class){                 
                System.out.println("enterthread1...");                 
                System.out.println("thread1is waiting");
                try {
                    //释放锁有两种方式,第一种方式是程序自然离开监视器的范围,也就是离开
                    了 synchronized 关键字管辖的代码范围,另一种方式就是在 synchronized 关键字管辖的代
                    码内部调用监视器对象的 wait 方法。这里,使用 wait 方法释放锁。
                     MultiThread.class.wait();                     
                }
                catch(InterruptedException e) {
                     // TODO Auto-generatedcatch block
                     e.printStackTrace();                     
                } 
                System.out.println("thread1is going on...");
                System.out.println("thread1is being over!");                 
                 
            } 
        }    
    }
    private static classThread2implements Runnable
    {
        @Override
        public void run() {
            // TODO Auto-generated methodstub
            synchronized (MultiThread.class){                 
                System.out.println("enterthread2...");                 
                System.out.println("thread2notify other thread can release wait status..");
                //由于 notify 方法并不释放锁,即使 thread2调用下面的 sleep 方法休息了10毫秒,但 thread1
                仍然不会执行,因为 thread2没有释放锁,所以 Thread1无法得不到锁。
                 
                MultiThread.class.notify();                 
                System.out.println("thread2is sleeping ten millisecond...");
                try {
                    Thread.sleep(10);                     
                }
                catch (InterruptedExceptione) {
                     // TODO Auto-generatedcatch block
                     e.printStackTrace();                     
                }
                System.out.println("thread2is going on...");
                System.out.println("thread2is being over!");
            }
        }
    }
}
 

 
48、同步和异步有何异同,在什么情况下分别使用他们?举例说明。
如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数
据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。
当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方
法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。
 
49. 下面两个方法同步吗?(自己发明)

class Test
{ 
    synchronized static void say Hello3()
    { 
 
    } 
 
    synchronized void getX(){}
} 

50、多线程有几种实现方法?同步有几种实现方法? 
多线程有两种实现方法,分别是继承 Thread 类与实现 Runnable 接口
同步的实现方面有两种,分别是 synchronized,wait 与 notify
wait():
使一个线程处于等待状态,并且释放所持有的对象的 lock。
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉
InterruptedException(中断异常)异常。
notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒
某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且不是按优先级。
Allnotity():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是
让它们竞争。
  
51、启动一个线程是用 run()还是 start()? .
启动一个线程是调用 start()方法,使线程就绪状态,以后可以被调度为运行状态,一个线程
必须关联一些具体的执行代码,run()方法是该线程所关联的执行代码。
 
52、当一个线程进入一个对象的一个 synchronized 方法后,其它线程是否可
进入此对象的其它方法? 
分几种情况:
 1. 其他方法前是否加了 synchronized 关键字,如果没加,则能。
 2. 如果这个方法内部调用了 wait,则可以进入其他 synchronized 方法。
 3. 如果其他个方法都加了 synchronized 关键字,并且内部没有调用 wait,则不能。
 4. 如果其他方法是 static,它用的同步锁是当前类的字节码,与非静态的方法不能同
步,因为非静态的方法用的是 this。
 
53、线程的基本概念、线程的基本状态以及状态之间的关系
 
一个程序中可以有多条执行线索同时执行,一个线程就是程序中的一条执行线索,每个线程
上都关联有要执行的代码,即可以有多段程序代码同时运行,每个程序至少都有一个线程,
即 main 方法执行的那个线程。如果只是一个 cpu,它怎么能够同时执行多段程序呢?这是
从宏观上来看的,cpu 一会执行 a 线索,一会执行 b 线索,切换时间很快,给人的感觉是
a,b 在同时执行,好比大家在同一个办公室上网,只有一条链接到外部网线,其实,这条网
线一会为 a 传数据,一会为 b 传数据,由于切换时间很短暂,所以,大家感觉都在同时上
网。
 
 状态:就绪,运行,synchronize 阻塞,wait 和 sleep 挂起,结束。wait 必须在 synchronized
内部调用。
 调用线程的 start 方法后线程进入就绪状态,线程调度系统将就绪状态的线程转为运行状
态,遇到 synchronized 语句时,由运行状态转为阻塞,当 synchronized 获得锁后,由阻塞
转为运行,在这种情况可以调用 wait 方法转为挂起状态,当线程关联的代码执行完后,线
程变为结束状态。
 
54、简述 synchronized java.util.concurrent.locks.Lock 的异同?
主要相同点:Lock 能完成 synchronized 所实现的所有功能
主要不同点:Lock 有比 synchronized 更精确的线程语义和更好的性能。synchronized 会自
动释放锁,而 Lock 一定要求程序员手工释放,并且必须在 finally 从句中释放。Lock 还有更
强大的功能,例如,它的 tryLock 方法可以非阻塞方式去拿锁。
举例说明(对下面的题用 lock 进行了改写):

package com.huawei.interview; 
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
publicclass ThreadTest { 
    /**
     * @paramargs
     */ 
    private int j;
    private Lock lock =newReentrantLock();
    public static voidmain(String[] args) {
        // TODO Auto-generated method stub
        ThreadTest tt = new ThreadTest();
        for(int i=0;i<2;i++)
        { 
            new Thread(tt.new Adder()).start();
            new Thread(tt.new Subtractor()).start();
        } 
    } 
 
    private class SubtractorimplementsRunnable
    { 
 
        @Override
        public void run() {
            // TODO Auto-generated methodstub
            while(true) 
            { 
                 /*synchronized (ThreadTest.this) { 
                     System.out.println("j--="+ j--);
                     //这里抛异常了,锁能释放吗?
                 }*/
                lock.lock();
                try
                { 
                    System.out.println("j--="+ j--);
                }finally
                { 
                    lock.unlock();
                } 
            } 
        } 
 
    } 
 
    private class AdderimplementsRunnable
    { 
 
        @Override
        public void run() {
            // TODO Auto-generated methodstub
            while(true) 
            { 
                 /*synchronized (ThreadTest.this) {
                     System.out.println("j++="+ j++); 
                 }*/
                lock.lock();
                try
                { 
                    System.out.println("j++="+ j++);
                }finally
                { 
                    lock.unlock();
                } 
            } 
        }  
    } 
} 

55、设计 4 个线程,其中两个线程每次对 j 增加 1,另外两个线程对 j 每次减少
1。写出程序。
以下程序使用内部类实现线程,对 j 增减的时候没有考虑顺序问题。

public class ThreadTest1
{ 
    private int j;
    public static void main(String args[]){
        ThreadTest1 tt=newThreadTest1();
        Inc inc=tt.new Inc();
        Dec dec=tt.new Dec();
        for(inti=0;i<2;i++){
            Thread t=newThread(inc);
            t.start();
            t=new Thread(dec);
            t.start();
        } 
    } 
    private synchronized void inc(){
        j++;
        System.out.println(Thread.currentThread().getName()+"-inc:"+j);
    } 
    private synchronized void dec(){
        j--; 
        System.out.println(Thread.currentThread().getName()+"-dec:"+j);
    } 
    class Inc implements Runnable{
        public void run(){
            for(inti=0;i<100;i++){
                inc();
            } 
        } 
    } 
    class Dec implements Runnable{
        public void run(){
            for(inti=0;i<100;i++){
                dec();
            } 
        } 
    } 
} 


----------随手再写的一个-------------

class A
{ 
    JManger j =new JManager();
    main()
    { 
        new A().call();
    }  
    void call
    { 
        for(int i=0;i<2;i++) { 
            new Thread(
                newRunnable(){ public void run(){while(true){j.accumulate()}}}
            ).start();
            new Thread(newRunnable(){ public void run(){while(true){j.sub()}}}).start();
         } 
    } 
}  
class JManager
{ 
    private j = 0; 
    public synchronized voidsubtract()
    { 
        j--
    }  
    public synchronized voidaccumulate()
    { 
        j++;
    }  
} 


56、子线程循环 10 次,接着主线程循环 100,接着又回到子线程循环 10 次,
接着再回到主线程又循环 100,如此循环 50 次,请写出程序。
 
最终的程序代码如下:

public class ThreadTest { 
    /**
     * @paramargs
     */
    public static voidmain(String[] args) {
        // TODO Auto-generated method stub
        new ThreadTest().init();
    } 
    public void init()
    {         
        final Business business =newBusiness();         
        new Thread(
            new Runnable()
            {
                public voidrun() {
                    for (inti=0;i<50;i++)
                    {                     
                        business.SubThread(i);
                    }                 
                }             
            }
        ).start();
         
        for (int i=0;i<50;i++)
        {             
            business.MainThread(i);             
        }
    }
    private class Business 
    {
        booleanbShouldSub =true;
        //这里相当于定义了控制该谁执行的一个信号灯
        public synchronized voidMainThread(int i)
        {
            if(bShouldSub) 
            try {                 
                this.wait();                 
            }
            catch(InterruptedException e) {
                // TODO Auto-generatedcatch block
                e.printStackTrace();
            }
            for (int j=0;j<5;j++)
            {                 
                System.out.println(Thread.currentThread().getName()+ ":i=" + i +",j=" + j);                 
            }
            bShouldSub =true;             
            this.notify();
        }
        public synchronized voidSubThread(int i)
        {             
            if(!bShouldSub) 
            try {                 
                this.wait();                 
            }
            catch (InterruptedExceptione) {
                // TODO Auto-generatedcatch block
                e.printStackTrace();                 
            }
            for (intj=0;j<10;j++)
            {                 
                System.out.println(Thread.currentThread().getName()+ ":i=" + i +",j=" + j);                 
            }             
            bShouldSub =false;             
            this.notify();  
        } 
    }     
}
 

备注:不可能一上来就写出上面的完整代码,最初写出来的代码如下,问题在于两个线程的
代码要参照同一个变量,即这两个线程的代码要共享数据,所以,把这两个线程的执行代码
搬到同一个类中去:

package com.huawei.interview.lym; 
publicclass ThreadTest {     
    private static booleanbShouldMain=false;
    public static void main(String[]args) {
        // TODO Auto-generated method stub
         
        /*new Thread(){
            public void run()
            { 
                for(int i=0;i<50;i++)
                { 
                    for(int j=0;j<10;j++)
                    { 
                        System.out.println("i="+ i + ",j=" + j);
                    } 
                } 
            }  
        }.start();*/
        //final String str = newString("");         
        new Thread(
            new Runnable() 
            {             
                public voidrun()
                {                 
                    for (inti=0;i<50;i++)
                    {                     
                        synchronized(ThreadTest.class) {
                            if(bShouldMain) 
                            {                             
                                try {                                 
                                    ThreadTest.class.wait();
                                }
                                catch(InterruptedException e) {
                                    e.printStackTrace();                                 
                                }
                            }
                         
                            for (intj=0;j<10;j++)
                            {                             
                                System.out.println(
                                    Thread.currentThread().getName()+
                                    "i="+ i + ",j=" + j);                             
                            } 
                            bShouldMain= true;                         
                            ThreadTest.class.notify();                         
                        }
                    }
                }
            }         
        ).start();
         
        for (int i=0;i<50;i++)
        {             
            synchronized (ThreadTest.class){
                if(!bShouldMain) 
                {
                    try {
                        ThreadTest.class.wait();
                    }
                    catch(InterruptedException e) {
                        e.printStackTrace();                         
                    } 
                }                 
                for (intj=0;j<5;j++)
                {
                    System.out.println(
                    Thread.currentThread().getName()+ 
                    "i=" + i +",j=" + j);                     
                }
                bShouldMain =false;
                ThreadTest.class.notify();
            }
        }
    }
}
 

 下面使用 jdk5中的并发库来实现的:

import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
 
public class ThreadTest
{     
    private static Locklock = new ReentrantLock();
    private staticCondition subThreadCondition = lock.newCondition();
    private staticBoolean bBhouldSubThread = false;
    public static voidmain(String [] args)
    {         
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        threadPool.execute(newRunnable(){
            publicvoid run()
            {                 
                for (inti=0;i<50;i++)
                {
                    lock.lock();                     
                    try
                    {                         
                        if(!bBhouldSubThread) 
                        subThreadCondition.await();
                        for (intj=0;j<10;j++) {
                            System.out.println(Thread.currentThread().getName()+ ",j=" + j);                             
                        }                         
                        bBhouldSubThread= false;
                        subThreadCondition.signal();                         
                    }
                    catch(Exceptione)
                    {                         
                         
                    }                     
                    finally
                    {                         
                        lock.unlock();                         
                    }
                }
            }
        }
        );
        threadPool.shutdown();
        for (inti=0;i<50;i++)
        {             
            lock.lock();             
            try
            { 
                if(bBhouldSubThread)
                    subThreadCondition.await();
                 
                for (intj=0;j<10;j++)
                {
                    System.out.println(Thread.currentThread().getName()+ ",j=" + j);                    
                }                 
                bBhouldSubThread= true;
                subThreadCondition.signal();
            }
            catch(Exceptione)
            {  
            }             
            finally
            {                 
                lock.unlock();                 
            }
        }
    }
}
 

57、介绍 Collection 框架的结构
答:随意发挥题,天南海北谁便谈,只要让别觉得你知识渊博,理解透彻即可。

来源于Java.util包,是非常实用常用的数据结构!!!!!字面意思就是容器。具体的继承实现关系如下图,先整体有个印象,再依次介绍各个部分的方法,注意事项,以及应用场景。
 
58、Collection 框架中实现比较要实现什么接口
comparable/comparator


59、ArrayListVector 的区别
答:
这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合,即存
储在这两个集合中的元素的位置都是有顺序的,相当于一种动态的数组,我们以后可以按位
置索引号取出某个元素,,并且其中的数据是允许重复的,这是 HashSet 之类的集合的最大
不同处,HashSet 之类的集合不可以按索引号去检索其中的元素,也不允许有重复的元素
(本来题目问的与 hashset 没有任何关系,但为了说清楚 ArrayList 与 Vector 的功能,我们
使用对比方式,更有利于说明问题)。
 
接着才说 ArrayList 与 Vector 的区别,这主要包括两个方面:. 
(1)同步性:
 Vector 是线程安全的,也就是说是它的方法之间是线程同步的,而 ArrayList 是线程
序不安全的,它的方法之间是线程不同步的。如果只有一个线程会访问到集合,那最好是使
用 ArrayList,因为它不考虑线程安全,效率会高些;如果有多个线程会访问到集合,那最
好是使用 Vector,因为不需要我们自己再去考虑和编写线程安全的代码。
 
备注:对于 Vector&ArrayList、Hashtable&HashMap,要记住线程安全的问题,记住 Vector
与 Hashtable 是旧的,是 java 一诞生就提供了的,它们是线程安全的,ArrayList 与 HashMap
是 java2时才提供的,它们是线程不安全的。所以,我们讲课时先讲老的。
(2)数据增长:
 ArrayList 与 Vector 都有一个初始的容量大小,当存储进它们里面的元素的个数超过
了容量时,就需要增加 ArrayList 与 Vector 的存储空间,每次要增加存储空间时,不是只增
加一个存储单元,而是增加多个存储单元,每次增加的存储单元的个数在内存空间利用与程
序效率之间要取得一定的平衡。Vector 默认增长为原来两倍,而 ArrayList 的增长策略在文
档中没有明确规定(从源代码看到的是增长为原来的1.5倍)。ArrayList 与 Vector 都可以设
置初始的空间大小,Vector 还可以设置增长的空间大小,而 ArrayList 没有提供设置增长空
间的方法。
 总结:即 Vector 增长原来的一倍,ArrayList 增加原来的0.5倍。


60、HashMapHashtable 的区别
(条理上还需要整理,也是先说相同点,再说不同点)
HashMap 是 Hashtable 的轻量级实现(非线程安全的实现),他们都完成了 Map 接口,主
要区别在于 HashMap 允许空(null)键值(key),由于非线程安全,在只有一个线程访问
的情况下,效率要高于 Hashtable。
HashMap 允许将 null 作为一个 entry 的 key 或者 value,而 Hashtable 不允许。
HashMap 把 Hashtable 的 contains 方法去掉了,改成 containsvalue 和 containsKey。因为
contains 方法容易让人引起误解。
Hashtable 继承自 Dictionary 类,而 HashMap 是 Java1.2引进的 Map interface 的一个实现。
最大的不同是,Hashtable 的方法是 Synchronize 的,而 HashMap 不是,在多个线程访问
Hashtable 时,不需要自己为它的方法实现同步,而 HashMap 就必须为之提供外同步。
Hashtable 和 HashMap 采用的 hash/rehash 算法都大概一样,所以性能不会有很大的差异。
 
就 HashMap 与 HashTable 主要从三方面来说。
一.历史原因:Hashtable 是基于陈旧的 Dictionary 类的,HashMap 是 Java 1.2引进的 Map
接口的一个实现
二.同步性:Hashtable 是线程安全的,也就是说是同步的,而 HashMap 是线程序不安全的,
不是同步的
三.值:只有 HashMap 可以让你将空值作为一个表的条目的 key 或 value

 

61、List Map 区别? 
一个是存储单列数据的集合,另一个是存储键和值这样的双列数据的集合,List 中存储的数
据是有顺序,并且允许重复;Map 中存储的数据是没有顺序的,其键是不能重复的,它的
值是可以有重复的。

62、List, Set, Map 是否继承自 Collection 接口? 
 List,Set 是,Map 不是


63、List、Map、Set 三个接口,存取元素时,各有什么特点?
这样的题属于随意发挥题:这样的题比较考水平,两个方面的水平:一是要真正明白这些内
容,二是要有较强的总结和表述能力。如果你明白,但表述不清楚,在别人那里则等同于不
明白。
 
首先,List 与 Set 具有相似性,它们都是单列元素的集合,所以,它们有一个功共同的父接
口,叫 Collection。Set 里面不允许有重复的元素,所谓重复,即不能有两个相等(注意,
不是仅仅是相同)的对象,即假设 Set 集合中有了一个 A 对象,现在我要向 Set 集合再存
入一个 B 对象,但 B 对象与 A 对象 equals 相等,则 B 对象存储不进去,所以,Set 集合的
add 方法有一个 boolean 的返回值,当集合中没有某个元素,此时 add 方法可成功加入该
元素时,则返回 true,当集合含有与某个元素 equals 相等的元素时,此时 add 方法无法加
入该元素,返回结果为 false。Set 取元素时,没法说取第几个,只能以 Iterator 接口取得所
有的元素,再逐一遍历各个元素。
 List 表示有先后顺序的集合,注意,不是那种按年龄、按大小、按价格之类的排序。
当我们多次调用 add(Obj e)方法时,每次加入的对象就像火车站买票有排队顺序一样,按先
来后到的顺序排序。有时候,也可以插队,即调用 add(int index,Obj e)方法,就可以指定当
前对象在集合中的存放位置。一个对象可以被反复存储进 List 中,每调用一次 add 方法,
这个对象就被插入进集合中一次,其实,并不是把这个对象本身存储进了集合中,而是在集
合中用一个索引变量指向这个对象,当这个对象被 add 多次时,即相当于集合中有多个索
引指向了这个对象,如图 x 所示。List 除了可以以 Iterator 接口取得所有的元素,再逐一遍
历各个元素之外,还可以调用 get(index i)来明确说明取第几个。
 Map 与 List 和 Set 不同,它是双列的集合,其中有 put 方法,定义如下:put(obj 
key,objvalue),每次存储时,要存储一对 key/value,不能存储重复的 key,这个重复的规
则也是按 equals 比较相等。取则可以根据 key 获得相应的 value,即 get(Object key)返回
值为 key 所对应的 value。另外,也可以获得所有的 key 的结合,还可以获得所有的 value
的结合,还可以获得 key 和 value 组合成的 Map.Entry 对象的集合。
 
List 以特定次序来持有元素,可有重复元素。Set 无法拥有重复元素,内部排序。Map 保存
key-value 值,value 可多值。
 
HashSet 按照 hashcode 值的某种运算方式进行存储,而不是直接按 hashCode 值的大小进
行存储。例如,"abc"---> 78,"def" ---> 62,"xyz" ---> 65在 hashSet 中的存储顺序不是
62,65,78,这些问题感谢以前一个叫崔健的学员提出,最后通过查看源代码给他解释清楚,
看本次培训学员当中有多少能看懂源码。LinkedHashSet 按插入的顺序存储,那被存储对象
的 hashcode 方法还有什么作用呢?学员想想!hashset 集合比较两个对象是否相等,首先看
hashcode 方法是否相等,然后看 equals 方法是否相等。new 两个 Student 插入到 HashSet
中,看 HashSet 的 size,实现 hashcode 和 equals 方法后再看 size。
 
同一个对象可以在 Vector 中加入多次。往集合里面加元素,相当于集合里用一根绳子连接
到了目标对象。往 HashSet 中却加不了多次的。
 
64、说出 ArrayList,Vector, LinkedList 的存储性能和特性
ArrayList 和 Vector 都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增
加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存
操作,所以索引数据快而插入数据慢,Vector 由于使用了 synchronized 方法(线程安全),
通常性能上较 ArrayList 差,而 LinkedList 使用双向链表实现存储,按序号索引数据需要进
行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。
 
LinkedList 也是线程不安全的,LinkedList 提供了一些方法,使得 LinkedList 可以被当作堆
栈和队列来使用。


65、去掉一个 Vector 集合中重复的元素

Vector newVector = new Vector();
For (int i=0;i<vector.size();i++)
{ 
    Object obj = vector.get(i);
    if(!newVector.contains(obj);
    newVector.add(obj);
} 


还有一种简单的方式,HashSet set = new HashSet(vector);


66、CollectionCollections 的区别。
Collection 是集合类的上级接口,继承与他的接口主要有 Set 和 List.
Collections 是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、
排序、线程安全化等操作。


67、Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢?是用==
equals()?它们有何区别? 
Set 里的元素是不能重复的,元素重复与否是使用 equals()方法进行判断的。
 equals()和==方法决定引用值是否指向同一对象 equals()在类中被覆盖,为的是当两个
分离的对象的内容和类型相配的话,返回真值。


68、你所知道的集合类都有哪些?主要方法?
最常用的集合类是 List 和 Map。 List 的具体实现包括 ArrayList 和 Vector,它们是可变
大小的列表,比较适合构建、存储和操作任何类型对象的元素列表。 List 适用于按数值索
引访问元素的情形。
Map 提供了一个更通用的元素存储方法。 Map 集合类用于存储元素对(称作"键"和"值"),
其中每个键映射到一个值。
 
ArrayList/VectoràList
 àCollection
HashSet/TreeSetàSet
 
PropetiesàHashTable
 àMap
 Treemap/HashMap
 
我记的不是方法名,而是思想,我知道它们都有增删改查的方法,但这些方法的具体名称,
我记得不是很清楚,对于 set,大概的方法是 add,remove, contains;对于 map,大概的方
法就是 put,remove,contains 等,因为,我只要在 eclispe 下按点操作符,很自然的这些方
法就出来了。我记住的一些思想就是 List 类会有 get(int index)这样的方法,因为它可以按
顺序取元素,而 set 类中没有 get(int index)这样的方法。List 和 set 都可以迭代出所有元素,
迭代时先要得到一个 iterator 对象,所以,set 和 list 类都有一个 iterator 方法,用于返回那
个 iterator 对象。map 可以返回三个集合,一个是返回所有的 key 的集合,另外一个返回的
是所有 value 的集合,再一个返回的 key 和 value 组合成的 EntrySet 对象的集合,map 也
有 get 方法,参数是 key,返回值是 key 对应的 value。
 
69、两个对象值相同(x.equals(y) == true),但却可有不同的 hash code,这句
话对不对? 
对。
如果对象要保存在 HashSet 或 HashMap 中,它们的 equals 相等,那么,它们的 hashcode
值就必须相等。
如果不是要保存在HashSet或HashMap,则与hashcode没有什么关系了,这时候hashcode
不等是可以的,例如 arrayList 存储的对象就不用实现 hashcode,当然,我们没有理由不实
现,通常都会去实现的。


70、TreeSet 里面放对象,如果同时放入了父类和子类的实例对象,那比较时使
用的是父类的 compareTo 方法,还是使用的子类的 compareTo 方法,还是抛
异常!
(应该是没有针对问题的确切的答案,当前的 add 方法放入的是哪个对象,就调用哪个对
象的 compareTo 方法,至于这个 compareTo 方法怎么做,就看当前这个对象的类中是如何
编写这个方法的)
实验代码:

public class ParentimplementsComparable {
    private int age = 0;
    public Parent(int age){
        this.age = age;         
    }     
    public int compareTo(Object o){
        // TODO Auto-generated method stub
        System.out.println("method ofparent");
        Parent o1 = (Parent)o;
        return age>o1.age?1:age<o1.age?-1:0;         
    }
}
public class Childextends Parent {     
    public Child(){
        super(3);         
    }     
    public int compareTo(Object o){
        // TODO Auto-generated methodstub
        System.out.println("methodof child");
        // Child o1 = (Child)o;
        return 1;
    }     
}
 
public class TreeSetTest {
    /**
     * @paramargs
     */
    public static voidmain(String[] args) {
        // TODO Auto-generated method stub
        TreeSet set = new TreeSet();
        set.add(newParent(3));
        set.add(new Child());
        set.add(newParent(4));
        System.out.println(set.size());
    }
}
 

 
71、说出一些常用的类,包,接口,请各举 5
要让人家感觉你对 java ee 开发很熟,所以,不能仅仅只列 core java 中的那些东西,要多
列你在做 ssh 项目中涉及的那些东西。就写你最近写的那些程序中涉及的那些类。
 
常用的类:BufferedReader BufferedWriter FileReader FileWirter String Integer
java.util.Date,System,Class,List,HashMap
 
常用的包:java.lang java.io java.util 
java.sql,javax.servlet,org.apache.strtuts.action,org.hibernate
常用的接口:Remote List Map Document 
NodeList,Servlet,HttpServletRequest,HttpServletResponse,Transaction(Hibernate)、
Session(Hibernate),HttpSession


72、java 中有几种类型的流?JDK 为每种类型的流提供了一些抽象类以供继承,
请说出他们分别是哪些类?
字节流,字符流。字节流继承于 InputStream OutputStream,字符流继承于
InputStreamReaderOutputStreamWriter。在 java.io 包中还有许多其他的流,主要是为了提
高性能和使用方便。


73、字节流与字符流的区别
 要把一片二进制数据数据逐一输出到某个设备中,或者从某个设备中逐一读取一片
二进制数据,不管输入输出设备是什么,我们要用统一的方式来完成这些操作,用一种抽象
的方式进行描述,这个抽象描述方式起名为 IO 流,对应的抽象类为 OutputStream 和
InputStream,不同的实现类就代表不同的输入和输出设备,它们都是针对字节进行操作的。
 在应用中,经常要完全是字符的一段文本输出去或读进来,用字节流可以吗?计算
机中的一切最终都是二进制的字节形式存在。对于“中国”这些字符,首先要得到其对应的字
节,然后将字节写入到输出流。读取时,首先读到的是字节,可是我们要把它显示为字符,
我们需要将字节转换成字符。由于这样的需求很广泛,人家专门提供了字符流的包装类。
 底层设备永远只接受字节数据,有时候要写字符串到底层设备,需要将字符串转成字节再
进行写入。字符流是字节流的包装,字符流则是直接接受字符串,它内部将串转成字节,
再写入底层设备,这为我们向 IO 设别写入或读取字符串提供了一点点方便。
 字符向字节转换时,要注意编码的问题,因为字符串转成字节数组,
 其实是转成该字符的某种编码的字节形式,读取也是反之的道理。
 
讲解字节流与字符流关系的代码案例:

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.io.PrintWriter;
 
public class IOTest {
    public static void main(String[]args) throws Exception {
        String str = "中国人";         
        /*FileOutputStreamfos = newFileOutputStream("1.txt");
            fos.write(str.getBytes("UTF-8"));
            fos.close();*/
        /*FileWriter fw =new FileWriter("1.txt");
            fw.write(str);
            fw.close();*/
        PrintWriter pw =new PrintWriter("1.txt","utf-8");
        pw.write(str);
        pw.close();
         
        /*FileReader fr =new FileReader("1.txt");
             char[] buf = newchar[1024];
             int len =fr.read(buf);
             String myStr = newString(buf,0,len);
             System.out.println(myStr);*/
         
        /*FileInputStreamfr = new FileInputStream("1.txt");
             byte[] buf = newbyte[1024];
             int len =fr.read(buf);
             String myStr = newString(buf,0,len,"UTF-8");
             System.out.println(myStr);*/
        BufferedReader br =new BufferedReader(
            newInputStreamReader(
                newFileInputStream("1.txt"),"UTF-8"
            ) 
        );
        String myStr =br.readLine();
        br.close();
        System.out.println(myStr);         
    }
}
 

74、什么是 java 序列化,如何实现 java 序列化?或者请解释 Serializable 接口
的作用。
 
我们有时候将一个 java 对象变成字节流的形式传出去或者从一个字节流中恢复成一个 java
对象,例如,要将 java 对象存储到硬盘或者传送给网络上的其他计算机,这个过程我们可
以自己写代码去把一个 java 对象变成某个格式的字节流再传输,但是,jre 本身就提供了这
种支持,我们可以调用 OutputStream 的 writeObject 方法来做,如果要让 java 帮我们做,
要被传输的对象必须实现 serializable 接口,这样,javac 编译时就会进行特殊处理,编译
的类才可以被 writeObject 方法操作,这就是所谓的序列化。需要被序列化的类必须实现
Serializable 接口,该接口是一个 mini 接口,其中没有需要实现的方法,
implementsSerializable 只是为了标注该对象是可被序列化的。
 
 
例如,在 web 开发中,如果对象被保存在了 Session 中,tomcat 在重启时要把 Session 对
象序列化到硬盘,这个对象就必须实现 Serializable 接口。如果对象要经过分布式系统进行
网络传输或通过 rmi 等远程调用,这就需要在网络上传输对象,被传输的对象就必须实现
Serializable 接口。
 
75、描述一下 JVM 加载 class 文件的原理机制? 
JVM 中类的装载是由 ClassLoader 和它的子类来实现的,Java ClassLoader 是一个重要的
Java 运行时系统组件。它负责在运行时查找和装入类文件的类。
 
76、heap 和 stack 有什么区别。
java 的内存分为两类,一类是栈内存,一类是堆内存。栈内存是指程序进入一个方法时,
会为这个方法单独分配一块私属存储空间,用于存储这个方法内部的局部变量,当这个方法
结束时,分配给这个方法的栈会释放,这个栈中的变量也将随之释放。
堆是与栈作用不同的内存,一般用于存放不放在当前方法栈中的那些数据,例如,使用 new
创建的对象都放在堆里,所以,它不会随方法的结束而消失。方法中的局部变量使用 final
修饰后,放在堆中,而不是栈中。
 
77、GC 是什么?为什么要有 GC?
GC 是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,
忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以
自动监测对象是否超过作用域从而达到自动回收内存的目的,Java 语言没有提供释放已分
配内存的显示操作方法。
 
78、垃圾回收的优点和原理。并考虑 2 种回收机制。
Java 语言中一个显著的特点就是引入了垃圾回收机制,使 c++程序员最头疼的内存管理的
问题迎刃而解,它使得 Java 程序员在编写程序的时候不再需要考虑内存管理。由于有个垃
圾回收机制,Java 中的对象不再有"作用域"的概念,只有对象的引用才有"作用域"。垃圾回
收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独
的低级别的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象
进行清楚和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。
回收机制有分代复制垃圾回收和标记垃圾回收,增量垃圾回收。
 
79、垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么
办法主动通知虚拟机进行垃圾回收?
对于 GC 来说,当程序员创建对象时,GC 就开始监控这个对象的地址、大小以及使用情况。
通常,GC 采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对
象是"可达的",哪些对象是"不可达的"。当 GC 确定一些对象为"不可达"时,GC 就有责任回
收这些内存空间。可以。程序员可以手动执行 System.gc(),通知 GC 运行,但是 Java 语
言规范并不保证 GC 一定会执行。
 
 
80、什么时候用 assert
assertion(断言)在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制。在
实现中,assertion 就是在程序中的一条语句,它对一个 boolean 表达式进行检查,一个正
确程序必须保证这个 boolean 表达式的值为 true;如果该值为 false,说明程序已经处于不
正确的状态下,assert 将给出警告或退出。一般来说,assertion 用于保证程序最基本、关
键的正确性。assertion 检查通常在开发和测试时开启。为了提高性能,在软件发布后,
assertion 检查通常是关闭的。

package com.huawei.interview;
 
publicclass AssertTest {
     
    /**
     * @paramargs
     */
    public static voidmain(String[] args) {
        // TODO Auto-generated method stub
        int i = 0;
        for (i=0;i<5;i++)
        {
            System.out.println(i);             
        }         
        //假设程序不小心多了一句--i;
        --i;
        assert i==5;         
    }
}
 

 
81、java 中会存在内存泄漏吗,请简单描述。
所谓内存泄露就是指一个不再被程序使用的对象或变量一直被占据在内存中。java 中有垃
圾回收机制,它可以保证一对象不再被引用的时候,即对象编程了孤儿的时候,对象将自动
被垃圾回收器从内存中清除掉。由于 Java 使用有向图的方式进行垃圾回收管理,可以消除
引用循环的问题,例如有两个对象,相互引用,只要它们和根进程不可达的,那么 GC 也是
可以回收它们的,例如下面的代码可以看到这种情况的内存回收:

package com.huawei.interview;
 
import java.io.IOException;
 
publicclass GarbageTest {
    /**
     * @paramargs
     * @throwsIOException
     */
    public static voidmain(String[] args)throws IOException {
        // TODO Auto-generated method stub
        try {
            gcTest();             
        }
        catch (IOException e) {
             // TODO Auto-generated catch block
             e.printStackTrace();             
        }         
        System.out.println("hasexited gcTest!");
        System.in.read();
        System.in.read();         
        System.out.println("out begingc!");         
        for (int i=0;i<100;i++)
        {             
            System.gc();
            System.in.read();
            System.in.read();             
        } 
    }
    private static voidgcTest()throws IOException {
        System.in.read();
        System.in.read();
         
        Person p1 = new Person();
        System.in.read();
        System.in.read();
         
        Person p2 = new Person();
        p1.setMate(p2);
        p2.setMate(p1);
        System.out.println("beforeexit gctest!");
        System.in.read();
        System.in.read();
         
        System.gc();
        System.out.println("exitgctest!");         
    }
    private static classPerson
    {         
        byte[] data =new byte[20000000];
        Person mate = null;
         
        public void setMate(Personother)
        {
            mate = other;             
        }
    }
}
 

java 中的内存泄露的情况:长生命周期的对象持有短生命周期对象的引用就很可能发生内
存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致
不能被回收,这就是 java 中内存泄露的发生场景,通俗地说,就是程序员可能创建了一个
对象,以后一直不再使用这个对象,这个对象却一直被引用,即这个对象无用但是却无法被
垃圾回收器回收的,这就是 java 中可能出现内存泄露的情况,例如,缓存系统,我们加载
了一个对象放在缓存中(例如放在一个全局 map 对象中),然后一直不再使用它,这个对象
一直被缓存引用,但却不再被使用。
检查 java 中的内存泄露,一定要让程序将各种分支情况都完整执行到程序结束,然后看某
个对象是否被使用过,如果没有,则才能判定这个对象属于内存泄露。
 
如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引
用了,即使那个外部类实例对象不再被使用,但由于内部类持久外部类的实例对象,这个外
部类对象将不会被垃圾回收,这也会造成内存泄露。
 
下面内容来自于网上(主要特点就是清空堆栈中的某个元素,并不是彻底把它从数组中拿掉,
而是把存储的总数减少,本人写得可以比这个好,在拿掉某个元素时,顺便也让它从数组中
消失,将那个元素所在的位置的值设置为 null 即可):
我实在想不到比那个堆栈更经典的例子了,以致于我还要引用别人的例子,下面的例子不是
我想到的,是书上看到的,当然如果没有在书上看到,可能过一段时间我自己也想的到,可
是那时我说是我自己想到的也没有人相信的。
 

 public class Stack {
     private Object[] elements=new Object[10];
     private int size = 0;
     public void push(Object e){
         ensureCapacity();
         elements[size++] = e;         
    }     
     public Object pop(){
         if( size == 0)
         throw new EmptyStackException();
         return elements[--size];         
    }     
     private void ensureCapacity(){
         if(elements.length == size){
             Object[] oldElements = elements;
             elements = new Object[2 * elements.length+1];
             System.arraycopy(oldElements,0, elements, 0, size);             
        } 
    }    
}
 


 上面的原理应该很简单,假如堆栈加了10个元素,然后全部弹出来,虽然堆栈是空的,
没有我们要的东西,但是这是个对象是无法回收的,这个才符合了内存泄露的两个条件:无
用,无法回收。
 但是就是存在这样的东西也不一定会导致什么样的后果,如果这个堆栈用的比较少,也
就浪费了几个 K 内存而已,反正我们的内存都上 G 了,哪里会有什么影响,再说这个东西
很快就会被回收的,有什么关系。下面看两个例子。
 例子1 
 

public class Bad{
     public static Stack s=Stack();
     static{         
         s.push(new Object());
         s.pop();
        //这里有一个对象发生内存泄露
         s.push(new Object());
        //上面的对象可以被回收了,等于是自愈了         
    }
}

 因为是 static,就一直存在到程序退出,但是我们也可以看到它有自愈功能,就是说如
果你的 Stack 最多有100个对象,那么最多也就只有100个对象无法被回收其实这个应该很
容易理解,Stack 内部持有100个引用,最坏的情况就是他们都是无用的,因为我们一旦放
新的进取,以前的引用自然消失!
 
内存泄露的另外一种情况:当一个对象被存储进 HashSet 集合中以后,就不能修改这个对
象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进 HashSet
集合中时的哈希值就不同了,在这种情况下,即使在 contains 方法使用该对象的当前引用
作为的参数去 HashSet 集合中检索对象,也将返回找不到对象的结果,这也会导致无法从
HashSet 集合中单独删除当前对象,造成内存泄露。
 
 
 
82、能不能自己写个类,也叫 java.lang.String
 
可以,但在应用的时候,需要用自己的类加载器去加载,否则,系统的类加载器永远只是去
加载 jre.jar 包中的那个 java.lang.String。由于在 tomcat 的 web 应用程序中,都是由 webapp
自己的类加载器先自己加载 WEB-INF/classess 目录中的类,然后才委托上级的类加载器加
载,如果我们在 tomcat 的 web 应用程序中写一个 java.lang.String,这时候 Servlet 程序加
载的就是我们自己写的 java.lang.String,但是这么干就会出很多潜在的问题,原来所有用
了 java.lang.String 类的都将出现问题。
 
虽然 java 提供了 endorsed 技术,可以覆盖 jdk 中的某些类,具体做法是….。但是,能够
被覆盖的类是有限制范围,反正不包括 java.lang 这样的包中的类。
 
(下面的例如主要是便于大家学习理解只用,不要作为答案的一部分,否则,人家怀疑是题
目泄露了)例如,运行下面的程序:

package java.lang;
 
public class String {
    /**
     * @paramargs
     */
     public static void main(String[] args) {
         // TODO Auto-generated method stub
         System.out.println("string");
    }
}
 


报告的错误如下:
java.lang.NoSuchMethodError:main
Exception inthread "main"
这是因为加载了 jre 自带的 java.lang.String,而该类中没有 main 方法。
 
83. Java 代码查错
1.

abstract class Name {
    private String name;
    public abstract boolean isStupidName(String name) {}
} 

大侠们,这有何错误? 
答案: 错。abstract method 必须以分号结尾,且不带花括号。
2.

public class Something {
    void doSomething () {
        private String s = "";
        int l = s.length();
    } 
} 

有错吗? 
答案: 错。局部变量前不能放置任何访问修饰符 (private,public,和 protected)。final 可
以用来修饰局部变量
(final 如同 abstract 和 strictfp,都是非访问修饰符,strictfp 只能修饰 class 和 method 而非
variable)。
3.

abstract class Something {
    private abstract String doSomething ();
} 

这好像没什么错吧? 
答案: 错。abstract 的 methods 不能以 private 修饰。abstract 的 methods 就是让子类
implement(实现)具体细节的,怎么可以用 private 把 abstract 
method 封锁起来呢? (同理,abstract method 前不能加 final)。
4.

public class Something {
    public int addOne(final int x) {
        return ++x;
    } 
} 

这个比较明显。
答案: 错。int x 被修饰成 final,意味着 x 不能在 addOne method 中被修改。
5.

public class Something {
    public static void main(String[] args) {
        Other o = new Other();
        new Something().addOne(o);
    } 
    public void addOne(final Other o) {
        o.i++;
    } 
} 
class Other {
    public int i;
} 

和上面的很相似,都是关于 final 的问题,这有错吗? 
答案: 正确。在 addOne method 中,参数 o 被修饰成 final。如果在 addOne method 里我
们修改了 o 的 reference
(比如: o = new Other();),那么如同上例这题也是错的。但这里修改的是 o 的 member 
vairable
(成员变量),而 o 的 reference 并没有改变。
6.

class Something {
    int i;
    public void doSomething() {
        System.out.println("i = "+ i);
    } 
} 

有什么错呢? 看不出来啊。
答案: 正确。输出的是"i = 0"。int i 属於 instant variable (实例变量,或叫成员变量)。instant 
variable 有 default value。int 的 default value 是0。
7.

class Something {
    final int i;
    public void doSomething() {
        System.out.println("i = "+ i);
    } 
} 

和上面一题只有一个地方不同,就是多了一个 final。这难道就错了吗? 
答案: 错。final int i 是个 final 的 instant variable (实例变量,或叫成员变量)。final 的 instant 
variable 没有 default value,必须在 constructor (构造器)结束之前被赋予一个明确的值。可
以修改为"final int i =0;"。
8.

public class Something {
    public static void main(String[] args) {
        Something s = new Something();
        System.out.println("s.doSomething() returns " + doSomething());
    } 
    public String doSomething() {
        return "Do something ...";
    } 
} 

看上去很完美。
答案: 错。看上去在 main 里 call doSomething 没有什么问题,毕竟两个 methods 都在同一
个 class 里。但仔细看,main 是 static 的。static method 不能直接 call non-staticmethods。
可改成"System.out.println("s.doSomething()returns " + s.doSomething());"。同理,static 
method 不能访问 non-static instant variable。
9.
此处,Something 类的文件名叫 OtherThing.java

class Something {
    private static void main(String[] something_to_do){ 
        System.out.println("Dosomething ...");
    } 
} 

这个好像很明显。
答案: 正确。从来没有人说过 Java 的 Class 名字必须和其文件名相同。但 public class 的
名字必须和文件名相同。
10.

interface A{
    int x = 0;
} 
class B{
    int x =1;
} 
class C extends B implements A {
    public void pX(){
        System.out.println(x);
    } 
    public static void main(String[] args) {
        new C().pX();
    } 
} 

答案:错误。在编译时会发生错误(错误描述不同的 JVM 有不同的信息,意思就是未明确的
x 调用,两个 x 都匹配(就象在同时 import java.util 和 java.sql 两个包时直接声明 Date 一
样)。对于父类的变量,可以用 super.x 来明确,而接口的属性默认隐含为 public staticfinal.
所以可以通过 A.x 来明确。
11.

interface Playable {
     void play();
} 
interface Bounceable {
     void play();
} 
interface Rollable extends Playable, Bounceable {
     Ball ball = new Ball("PingPang");
} 
class Ball implements Rollable {
     private String name;
     public String getName() {
         return name;         
    }     
     public Ball(String name) {
         this.name =name;
    }     
    public void play() {
        ball = newBall("Football");
        System.out.println(ball.getName());         
    }     
}
 

这个错误不容易发现。
答案: 错。"interfaceRollable extends Playable, Bounceable"没有问题。interface 可继承多
个 interfaces,所以这里没错。问题出在 interface Rollable 里的"Ball ball =new 
Ball("PingPang");"。任何在 interface 里声明的 interface variable (接口变量,也可称成员变
量),默认为 public static final。也就是说"Ball ball = new Ball("PingPang");"实际上是"public 
staticfinal Ball ball = new Ball("PingPang");"。在 Ball 类的 Play()方法中,"ball = 
newBall("Football");"改变了ball的reference,而这里的ball来自Rollable interface,Rollable 
interface 里的 ball 是 public static final 的,final 的 object 是不能被改变 reference 的。因此
编译器将在"ball = newBall("Football");"这里显示有错。

 

------------------------------------------------------------------------------

上一篇:Java面试宝典——基础部分(一)

下一篇:Java面试宝典——算法与编程(三)

------------------------------------------------------------------------------
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值