java面试题_java面试题2020 - CSDN
  • JAVA经典面试题

    千次阅读 2019-06-03 23:54:46
    九种基本类型及封装类 基本类型 boolean byte char short int long double void 二进制位数 1 8(一字节) 16(2字节) 16(2字节) 32(4字节) ... ...

    九种基本类型及封装类

     

    基本类型 boolean byte char short int long double void
    二进制位数 1 8(一字节) 16(2字节) 16(2字节) 32(4字节) 64(8字节) 64(8字节) --
    封装器类 Boolean Byte Character Short Integer Long Double Void

     

    switch语句后的控制表达式只能是short、char、int、long整数类型和枚举类型,不能是float,double和boolean类型。String类型是java7开始支持。

     

    位运算符

     

    • 左移(<<)

    • 右移(>>):int是32位,最高位是符号位,0代表正数,1代表负数,负数以补码的形式存储在计算机中。右移规则:最高位是什么(0或者1),右移的时候左边就补什么。即正数右移用0补位左边,负数右移用1补位左边。

    • 无符号右移(>>>):不管是负数还是正数,右移总是左边补0。

    • 与运算(&)

    • 或运算(|)

    • 非运算(~)

    • 异或运算(^):位相同为0,相异为1

     

    -5右移3位后结果为-1,-1的二进制为: 
    1111 1111 1111 1111 1111 1111 1111 1111   // (用1进行补位)
    -5无符号右移3位后的结果 536870911 换算成二进制: 
    0001 1111 1111 1111 1111 1111 1111 1111   // (用0进行补位)
    

     

    应用:不用临时变量交换两个数

     

    void swap(int argc, char *argv[])
    {
        a = a ^ b;
        b = b ^ a;
        a = a ^ b;
    }
    

     

    for循环,ForEach,迭代器效率

    直接for循环效率最高,其次是迭代器和 ForEach操作。
    其实 ForEach 编译成字节码之后,使用的是迭代器实现的。

    synchronized和volatile

    volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
    volatile保证了变量的可见性,synchronized保证了原子性和可见性。

    volatile

     

    原理:首先我们要先意识到有这样的现象,编译器为了加快程序运行的速度,对一些变量的写操作会先在寄存器或者是CPU缓存上进行,最后才写入内存。而在这个过程,变量的新值对其他线程是不可见的,而volatile的作用就是使它修饰的变量的读写操作都必须在内存中进行。volatile告诉JVM, 它所修饰的变量不保留拷贝,直接访问主内存中的。

     

    volatile与synchronized

     

    • volatile本质是在告诉JVM当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。

    • volatile仅能使用在变量级别,synchronized则可以使用在变量,方法.

    • volatile仅能实现变量的修改可见性,但不具备原子特性,而synchronized则可以保证变量的修改可见性和原子性。

    • volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞。

    • volatile标记的变量不会被编译器优化,而synchronized标记的变量可以被编译器优化。

     

    volatile不能保证原子性原因:线程A修改了变量还没结束时,另外的线程B可以看到已修改的值,而且可以修改这个变量,而不用等待A释放锁,因为Volatile 变量没上锁。

     

    注意


    声明为volatile的简单变量如果当前值由该变量以前的值相关,那么volatile关键字不起作用。

    
     

    也就是说如下的表达式都不是原子操作:

     

    也就是说如下的表达式都不是原子操作: 
    n  =  n  +   1 ; 
    n ++ ;
    
    
     

     

     

    只有当变量的值和自身上一个值无关时对该变量的操作才是原子级别的,如n = m + 1。

     

    Java内存模型的抽象(volatile)

     

    在java中,所有实例域、静态域和数组元素存储在堆内存中,堆内存在线程之间共享(本文使用“共享变量”这个术语代指实例域,静态域和数组元素)。局部变量,方法定义参数和异常处理器参数不会在线程之间共享,在栈内存中,不需要同步处理,因为栈内存是线程独享的,它们不会有内存可见性问题,也不受内存模型的影响。

     

    Java线程之间的通信由Java内存模型(本文简称为JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本(寄存器或CPU缓存)本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。Java内存模型的抽象示意图如下:

     

    java内存模型

     

    从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:

    • 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。

    • 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。

     

    下面通过示意图来说明这两个步骤:

     

     

    如上图所示,本地内存A和B有主内存中共享变量x的副本。假设初始时,这三个内存中的x值都为0。线程A在执行时,把更新后的x值(假设值为1)临时存放在自己的本地内存A中。当线程A和线程B需要通信时,线程A首先会把自己本地内存中修改后的x值刷新到主内存中,此时主内存中的x值变为了1。随后,线程B到主内存中去读取线程A更新后的x值,此时线程B的本地内存的x值也变为了。

     

    从整体来看,这两个步骤实质上是线程A在向线程B发送消息,而且这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。

     

    equals与==的区别

     

    ==常用于比较原生类型,而equals()方法用于检查对象的相等性。另一个不同的点是:如果==和equals()用于比较对象,当两个引用地址相同,== 返回true。而equals()可以返回true或者false主要取决于重写实现。最常见的一个例子,字符串的比较,不同情况 == 和equals()返回不同的结果。

     

    看Object源码:

     

     public boolean equals(Object obj) {
            return (this == obj);
        }
    

     

    ==表示的是比较两个对象实例的内存地址是否相同。如果不重写equal(),就和==等效,

     

    • 相等(相同)的对象必须具有相等的哈希码(或者散列码)。

    • 如果两个对象的hashCode相同,它们并不一定相同。

     

    术语来讲的区别:

     

    • ==是判断两个变量或实例是不是指向同一个内存空间。
      equals是判断两个变量或实例所指向的内存空间的值是不是相同。

    • ==指引用是否相同
      equals()指的是值是否相同

     

    hashCode作用

    以java.lang.Object来理解JVM每new一个Object,它都会将这个Object丢到一个Hash哈希表中去,这样的话,下次做Object的比较或者取这个对象的时候,它会根据对象的hashcode再从Hash表中取这个对象。这样做的目的是提高取对象的效率。

    具体过程是这样:

     

    • new Object(),JVM根据这个对象的Hashcode值放入到对应的Hash表对应的Key上,如果不同的对象却产生了相同的hash值,也就是发生了Hash key相同导致冲突的情况,那么就在这个Hash key的地方产生一个链表,将所有产生相同hashcode的对象放到这个单链表上串在一起。

     

    • 比较两个对象的时候,首先根据他们的hashcode去hash表中找他的对象,当两个对象的hashcode相同,那么就是说他们这两个对象放在Hash表中的同一个key上,那么他们一定在这个key上的链表上。那么此时就只能根据Object的equal方法来比较这个对象是否equal。当两个对象的hashcode不同的话,肯定他们不能equal。

     

    java.lang.Object中对hashCode的约定:

     

    • 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。

     

    • 如果两个对象根据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果。

     

    • 如果两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。

     

    Object的公用方法

     

    • clone 保护方法,只有实现了Cloneable接口才可以调用,否则抛异常

    • getClass final方法,获得运行时类型

    • toString

    • equals

    • hashCode

    • wait 就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait方法一直等待,直到获得锁或者被中断。wait设定一个超时间隔,如果在规定时间内没有获得锁就返回。
      调用该方法后当前线程进入睡眠状态,直到以下事件发生。

      • 其他线程调用了该对象的notify方法。

      • 其他线程调用了该对象的notifyAll方法。

      • 其他线程调用了interrupt中断该线程。

      • 时间间隔到了。
        此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。

    • notify

    • notifyAll

     

    Java四种引用 --- 这里指的是“引用“,不是对象

     

    强引用

    平常我们使用对象的方式Object object = new Object();如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。例如下面的代

    public class Main {
        public static void main(String[] args) {
            new Main().fun1();
        }
    
        public void fun1() {
            Object object = new Object();
            Object[] objArr = new Object[1000];
        }
    }
    

     

    当运行至Object[] objArr = new Object[1000];这句时,如果内存不足,JVM会抛出OOM错误也不会回收object指向的对象。不过要注意的是,当fun1运行完之后,object和objArr都已经不存在了,所以它们指向的对象都会被JVM回收。


    但如果想中断强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。

     

    软引用

    软引用通过SoftReference创建,在使用软引用时,如果内存的空间足够,软引用就能继续被使用,而不会被垃圾回收器回收,只有在内存不足时,软引用才会被垃圾回收器回收。

    软引用的这种特性使得它很适合用来解决 OOM 问题,实现缓存机制,例如:图片缓存、网页缓存等等……
    软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被JVM回收,这个软引用就会被加入到与之关联的引用队列中。

     

    弱引用

    事实上软引用和弱引用非常类似,两者的区别在于:只具有弱引用的对象拥有的生命周期更短暂。因为当 JVM 进行垃圾回收,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。不过由于垃圾回收器是一个优先级较低的线程,所以并不一定能迅速发现弱引用对象。

    弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被JVM回收,这个弱引用就会被加入到与之关联的引用队列中。

     

    虚引用

    虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会影响对象的生命周期。如果一个对象仅持有虚引用,那么它相当于没有引用,在任何时候都可能被垃圾回收器回收。

    • 强引用:不管什么时候都不会被回收。

    • 软引用:当内存不足的时候,JVM垃圾回收会回收。

    • 弱引用:不管内存足不足,只要发生JVM垃圾回收就会回收。

    • 虚引用:随时都可能会被回收。

     

    小结

    引用和引用队列提供了一种通知机制,允许我们知道对象已经被销毁或者即将被销毁。GC要回收一个对象的时候,如果发现该对象有软、弱、虚引用的时候,会将这些引用加入到注册的引用队列中。软引用和弱引用差别不大,JVM都是先把SoftReference和WeakReference中的referent字段值设置成null,之后加入到引用队列;而虚引用则不同,如果某个堆中的对象,只有虚引用,那么JVM会将PhantomReference加入到引用队列中,JVM不会自动将referent字段值设置成null。

    实际应用:利用软引用和弱引用缓存解决OOM问题。

     

    如:Bitmap的缓存

    设计思路是:用一个HashMap来保存图片的路径和相应图片对象(Bitmap)的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题。在Android开发中对于大量图片下载会经常用到。

    wait()、notify()和sleep()

     

    wait()和notify()

     

    • wait()和notify()是直接隶属于Object类,在JAVA中的Object类型中,都是带有一个内存锁的,在有线程获取该内存锁后,其它线程无法访问该内存,从而实现JAVA中简单的同步、互斥操作。明白这个原理,就能理解为什么synchronized(this)与synchronized(static XXX)的区别了,synchronized就是针对内存区块申请内存锁,this关键字代表类的一个对象,所以其内存锁是针对相同对象的互斥操作,而static成员属于类专有,其内存空间为该类所有成员共有,这就导致synchronized()对static成员加锁,相当于对类加锁,也就是在该类的所有成员间实现互斥,在同一时间只有一个线程可访问该类的实例。wait只能由持有对像锁的线程来调用。

    • Obj.wait()与Obj.notify()必须要与synchronized(Obj)一起使用,从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。

    • wait():促使当前线程等待直到另外一个线程调用这个对象的notify()方法唤醒。和synchronized块使用的时候,synchronized获取对象的锁以后,可以通过wait()方法释放,同时阻塞当前线程,停止执行(这也是和sleep的区别)。

    public static void firstMethod(){
            synchronized (a){
                System.out.println(Thread.currentThread().getName() + "  firstMethod--死锁");
                try {
    //                Thread.sleep(10);
                    a.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (b){
                    System.out.println(Thread.currentThread().getName() + "  firstMethod--解锁");
                }
            }
        }
    
        public static void seconedMethod(){
            synchronized (b){
                System.out.println(Thread.currentThread().getName() + "  seconedMethod--死锁");
                synchronized (a){
                    System.out.println(Thread.currentThread().getName() + "  seconedMethod--解锁");
                    a.notify();
                }
            }
        }
    

     

    如果用两个线程分别执行这两个方法

     

     

    public static void main(String[] args) {
    
            Runnable runnable1 = new Runnable() {
                @Override
                public void run() {
                    firstMethod();
                }
            };
            Runnable runnable2 = new Runnable() {
                @Override
                public void run() {
                    seconedMethod();
                }
            };
            Thread thread1 = new Thread(runnable1);
            Thread thread2 = new Thread(runnable2);
            thread1.start();
            thread2.start();
    
        }
    

     

    如果是用sleep方法替换掉wait方法,就是一个死锁,线程thread1先执行拿到a对象的锁,然后阻塞10ms(并没有释放锁),thread2然后拿到对象b的锁,这时候seconedMethod需要a对象的锁,但是firstMethod并没有释放,然后10ms过后,firstMethod需要b的锁,然后b的锁也没有在seconedMethod方法中释放,两个线程相互等待对方释放锁,就形成了死锁。

    运行结果:

    Thread-0 firstMethod--死锁
    Thread-1 seconedMethod--死锁

    如果不使用sleep而是使用wait方法,就不会发生死锁。因为wait释放了firstMethod中的a对象的锁,当seconedMethod需要a对象锁的时候就可以用了。

    运行结果:

    Thread-0 firstMethod--死锁
    Thread-1 seconedMethod--死锁
    Thread-1 seconedMethod--解锁
    Thread-0 firstMethod--解锁

    notify():唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程(随机)。直到当前的线程放弃此对象上的锁,才能继续执行被唤醒的线程。

    sleep()

    通过Thread.sleep()使当前线程暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁。也就是说如果有synchronized同步块,其他线程仍然不能访问共享数据。注意该方法要捕捉异常。

    public class ThreadLock {
    
        Object lock = new Object();
        int num = 0;
    
        public static void main(String[] args) {
    
            ThreadLock test = new ThreadLock();
    
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    test.method2();
                }
            };
    
            Thread thread1 = new Thread(runnable);
            thread1.start();
            test.method1();
        }
    
        public void method1(){
            synchronized (lock){
                try {
                    Thread.sleep(1000);
    //                lock.wait(1000);
                    num += 100;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }
        }
    
        public void method2(){
            synchronized (lock){
                num += 9;
                System.out.println(num);
            }
        }
    
    }

     

    因为在main线程调用方法,因此先执行主线程的method1,对象锁被主线程拿走了,那么子线程执行method2的时候就需要等待1秒后把锁还回来。

    1秒后输出结果:
    109

    如果替换成lock.wait(1000);

    lock.wait(1000)会让当前线程(main线程)睡眠1秒,同时释放synchronized的对象锁,因此小于1秒输出9

     

    synchronized和lock

     

    几个概念

    • 共享变量(shared variable):多个线程都能访问的变量。

    • 变量可见性(variable visibility):一个线程更新了共享变量,对其他线程立刻可见。

    • 互斥(mutual exclusion ):几个线程中的任何一个不能与其他一个或多个同时操作一个变量。

    • 临界区(critical section):访问共享资源的一段代码块。

    synchronized

    • 保证共享变量的可见性:变量缓存与编译器指令优化会导致变量修改的不可见性。

    • 保证共享变量的互斥性:同一时刻只能有一个线程对共享变量的修改(注意修改一次,是先读再写,是两个操作)。

    特点:

    • 当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。

    • 如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。

    • 线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。

    • 线程同步方法是通过锁来实现,每个对象都有且仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他同步方法。

    • 对于同步,要时刻清醒在哪个对象上同步,这是关键。

    • 死锁是线程间相互等待锁造成的。

    lock

    lock提供了如下的方法:

    • void lock(),获取一个锁,如果锁当前被其他线程获得,当前的线程将被休眠。

    • boolean tryLock(),尝试获取一个锁,如果当前锁被其他线程持有,则返回false,不会使当前线程休眠。

    • boolean tryLock(long timeout,TimeUnit unit),如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false。

    • void lockInterruptibly(),如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断。

     

    synchronized和lock区别

     

    • synchronized是在JVM层面上实现的,如果代码执行时出现异常,JVM会自动释放monitor锁。而lock代码是用户写的,需要用户来保证最终释放掉锁。

    • lock提供了一个重要的方法newConditon(),ConditionObject有await()、signal()、signalAll(),类似于Ojbect类的wait()、notify()、notifyAll()。这些方法都是用来实现线程间通信。lock将synchronized的互斥性和线程间通信分离开来,一个lock可以有多个condition。另外lock的signal可以实现公平的通知,而notify是随机从锁等待队列中唤醒某个进程。

    • 性能上来说,在多线程竞争比较激烈地情况下,lock比synchronize高效得多。

     

    public class ThreadLock {
    
        public static void main(String[] args) {
            Test test = new Test();
            Lock lock = new ReentrantLock();
    
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                        lock.lock();
                        for (int i = 0; i < 50 ; i++) {
                            test.setX(1);
                            System.out.println(Thread.currentThread().getName() + " : " +test.getX());
                        }
                        lock.unlock();
                    }
            };
    
            Thread thread1 = new Thread(runnable);
            Thread thread2 = new Thread(runnable);
            thread1.start();
            thread2.start();
        }
    
        static class Test{
            private int x = 100;
    
            public int getX(){
                return x;
            }
    
            public void setX(int y){
                x = x - y;
            }
    
        }
    
    }
    

     

    ReentrantLock与synchronized的比较

     

    ReentrantLocak(可重入锁)

    简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。
    ReentrantLock提供了synchronized类似的功能和内存语义。

    不同

     

    • ReentrantLock功能性方面更全面,比如时间锁等候,可中断锁等候,锁投票等,因此更有扩展性。在多个条件变量和高度竞争锁的地方,用ReentrantLock更合适,ReentrantLock还提供了Condition,对线程的等待和唤醒等操作更加灵活,一个ReentrantLock可以有多个Condition实例,所以更有扩展性。

    • ReentrantLock的性能比synchronized会好点。

    • ReentrantLock提供了可轮询的锁请求,他可以尝试的去取得锁,如果取得成功则继续处理,取得不成功,可以等下次运行的时候处理,所以不容易产生死锁,而synchronized则一旦进入锁请求要么成功,要么一直阻塞,所以更容易产生死锁。

    •  

    缺点

     

    • lock 必须在 finally 块中释放。否则,如果受保护的代码将抛出异常,锁就有可能永远得不到释放!这一点区别看起来可能没什么,但是实际上,它极为重要。忘记在 finally 块中释放锁,可能会在程序中留下一个定时炸弹,当有一天炸弹爆炸时,您要花费很大力气才能找到源头在哪。而使用同步,JVM 将确保锁会获得自动释放。

    • 当 JVM 用 synchronized 管理锁定请求和释放时,JVM 在生成线程转储时能够包括锁定信息。这些对调试非常有价值,因为它们能标识死锁或者其他异常行为的来源。 Lock 类只是普通的类,JVM 不知道具体哪个线程拥有 Lock 对象。

     

    ArrayList,LinkedList和Vector

     

    • ArrayList和Vector都是基于数组实现的,所以查询效率高,插入效率低。

    • LinkedList基于双向链表实现的,所以插入效率高,查询效率低。

    • Vector使用了synchronized方法,所以线程安全,性能比ArrayList低。

    • LinkedList实现了List接口,还提供了额外的get,remove,insert方法在LinkedList的首部或尾部,这些操作使LinkedList可被用作栈(Stack),队列(Queue)或双向队列(deque)。

    • ArrayList和LinkedList允许null元素,重复元素。

     

    HashMap和HashTable

     

    • 都实现了Map接口

    • HashMap允许key为null,value为null而HashTable不允许,如果新加入的key和之前重复了,会覆盖之前的value。

    • HashTable线程安全,而HashMap不是线程安全。因此单线程下HashTable比HashMap慢。

    • HashMap不能保证随着时间推移Map中的元素次序是不变的。

    • Hashtable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。

     

    ConcurrentHashMap

     

    锁分段技术

    HashTable容器在竞争激烈的并发环境下表现出效率低下的原因是所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

     

    HashSet

     

    实现了Set接口,HashSet< T >本质就是一个HashMap<T , Object>,把HashMap的key作为HashSet的值,HashMap的value是一个固定的Object对象,因为HashMap的key是不允许重复的,所以HashSet里的元素也是不能重复的,也可以看出HashSet的查询效率很高。

     

    String,StringBuilder和StringBuffer

     

    • CharSequence接口:一个字符序列。String,StringBuilder 和 StringBuffer都实现了它。

    • String类:是常量,不可变.

    • StringBuilder类:只可以在单线程的情况下进行修改(线程不安全),字符串拼接用,除了StringBuffer可用场景外。

    • StringBuffer类:可以在多线程的情况下进行改变(线程安全),比如:在http请求中拼接。

    • StringBuilder比StringBuffer效率高,应该尽量使用StringBuilder。

     

    Excption与Error包结构

     

    结构图:

     

    结构图

     

    Throwable

     

    • Throwable是 Java 语言中所有错误或异常的超类。

    • Throwable包含两个子类: Error 和 Exception 。它们通常用于指示发生了异常情况。

    • Throwable包含了其线程创建时线程执行堆栈的快照,它提供了printStackTrace()等接口用于获取堆栈跟踪数据等信息。

    Exception

     

    • Exception及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件。

     

    RuntimeException

     

    • RuntimeException是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。

    • 编译器不会检查RuntimeException异常。 例如,除数为零时,抛出ArithmeticException异常。RuntimeException是ArithmeticException的超类。当代码发生除数为零的情况时,倘若既"没有通过throws声明抛出ArithmeticException异常",也"没有通过try...catch...处理该异常",也能通过编译。这就是我们所说的"编译器不会检查RuntimeException异常"!

    • 如果代码会产生RuntimeException异常,则需要通过修改代码进行避免。 例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生!

    Error

    • 和Exception一样, Error也是Throwable的子类。 它用于指示合理的应用程序不应该试图捕获的严重问题,大多数这样的错误都是异常条件。

    • 和RuntimeException一样, 编译器也不会检查Error。

     

    Interface与abstract类的区别

     

    参数 抽象类 接口
    默认的方法实现 它可以有默认的方法实现 接口完全是抽象的。它根本不存在方法的实现
    实现 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现
    构造器 抽象类可以有构造器 接口不能有构造器
    与正常Java类的区别 除了你不能实例化抽象类之外,它和普通Java类没有任何区别 接口是完全不同的类型
    访问修饰符 抽象方法可以有public、protected和default这些修饰符 接口方法默认修饰符是public。你不可以使用其它修饰符。
    main方法 抽象方法可以有main方法并且我们可以运行它 接口没有main方法,因此我们不能运行它。
    多继承 抽象类可以继承一个类和实现多个接口 接口只可以继承一个或多个其它接口
    速度 它比接口速度要快 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。
    添加新方法 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 如果你往接口中添加方法,那么你必须改变实现该接口的类。

     

    静态内部类和非静态内部类

     

    相同点

    • 内部类都可以用public,protected,private修饰。

    • 方法中都可以调用外部类的静态成员变量。

    不同点

    • 静态内部类可以声明静态和非静态成员变量,非静态内部类只能声明非静态成员变量。

    • 静态内部类可以声明静态和非静态方法,非静态内部类只能声明非静态方法。

    • 静态内部类不能调用外部类的非静态成员变量(静态方法和非静态方法都一样),非静态内部类都可以调用。

     

    泛型擦除

    一篇好博客

    泛型在JDK5以后才有的,擦除是为了兼容之前没有的使用泛型的类库和代码。如:ArrayList< String >和ArrayList< Integer > 在编译器编译的时候都变成了ArrayList。

     

     

    List<Integer> list = new ArrayList<Integer>();
    Map<Integer, String> map = new HashMap<Integer, String>();
    System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
    System.out.println(Arrays.toString(map.getClass().getTypeParameters()));
    
    /* Output
    [E]
    [K, V]
    */
    

     

    我们期待的是得到泛型参数的类型,但是实际上我们只得到了一堆占位符。

     

    public class Main<T> {
    
        public T[] makeArray() {
            // error: Type parameter 'T' cannot be instantiated directly
            return new T[5];
        }
    }
    

     

    我们无法在泛型内部创建一个T类型的数组,原因也和之前一样,T仅仅是个占位符,并没有真实的类型信息,实际上,除了new表达式之外,instanceof操作和转型(会收到警告)在泛型内部都是无法使用的,而造成这个的原因就是之前讲过的编译器对类型信息进行了擦除。

     

     

    public class Main<T> {
    
        private T t;
    
        public void set(T t) {
            this.t = t;
        }
    
        public T get() {
            return t;
        }
    
        public static void main(String[] args) {
            Main<String> m = new Main<String>();
            m.set("findingsea");
            String s = m.get();
            System.out.println(s);
        }
    }
    
    /* Output
    findingsea
    */
    

     

    虽然有类型擦除的存在,使得编译器在泛型内部其实完全无法知道有关T的任何信息,但是编译器可以保证重要的一点:内部一致性,也是我们放进去的是什么类型的对象,取出来还是相同类型的对象,这一点让Java的泛型起码还是有用武之地的。

     

    代码片段展现就是编译器确保了我们放在T上的类型的确是T(即便它并不知道有关T的任何类型信息)。这种确保其实做了两步工作:

     

    • set()处的类型检验

    • get()处的类型转换

     

    这两步工作也成为边界动作。

     

    public class Main<T> {
    
        public List<T> fillList(T t, int size) {
            List<T> list = new ArrayList<T>();
            for (int i = 0; i < size; i++) {
                list.add(t);
            }
            return list;
        }
    
        public static void main(String[] args) {
            Main<String> m = new Main<String>();
            List<String> list = m.fillList("findingsea", 5);
            System.out.println(list.toString());
        }
    }
    
    /* Output
    [findingsea, findingsea, findingsea, findingsea, findingsea]
    */
    

     

    代码片段同样展示的是泛型的内部一致性。

     

    擦除的补偿


    如上看到的,但凡是涉及到确切类型信息的操作,在泛型内部都是无法共工作的。那是否有办法绕过这个问题来编程,答案就是显示地传递类型标签。

     

    public class Main<T> {
    
        public T create(Class<T> type) {
            try {
                return type.newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        public static void main(String[] args) {
            Main<String> m = new Main<String>();
            String s = m.create(String.class);
        }
    }
    代码片段展示了一种用类型标签生成新对象的方法,但是这个办法很脆弱,因为这种办法要求对应的类型必须有默认构造函数,遇到Integer类型的时候就失败了,而且这个错误还不能在编译器捕获。
    
    public class Main<T> {
    
        public T[] create(Class<T> type) {
            return (T[]) Array.newInstance(type, 10);
        }
    
        public static void main(String[] args) {
            Main<String> m = new Main<String>();
            String[] strings = m.create(String.class);
        }
    }
    代码片段七展示了对泛型数组的擦除补偿,本质方法还是通过显示地传递类型标签,通过Array.newInstance(type, size)来生成数组,同时也是最为推荐的在泛型内部生成数组的方法。
    

     

    
     
    展开全文
  • Java面试题及答案2019版(上)

    万次阅读 多人点赞 2020-06-11 09:27:50
    1、面向对象的特征有哪些方面? 答:面向对象的特征主要有以下几个方面: 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些...

    1、面向对象的特征有哪些方面?

    答:面向对象的特征主要有以下几个方面:

    • 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
    • 继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段(如果不能理解请阅读阎宏博士的《Java与模式》或《设计模式精解》中关于桥梁模式的部分)。
    • 封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口(可以想想普通洗衣机和全自动洗衣机的差别,明显全自动洗衣机封装更好因此操作起来更简单;我们现在使用的智能手机也是封装得足够好的,因为几个按键就搞定了所有的事情)。
    • 多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的服务,那么运行时的多态性可以解释为:当A系统访问B系统提供的服务时,B系统有多种提供服务的方式,但一切对A系统来说都是透明的(就像电动剃须刀是A系统,它的供电系统是B系统,B系统可以使用电池供电或者用交流电,甚至还有可能是太阳能,A系统只会通过B类对象调用供电的方法,但并不知道供电系统的底层实现是什么,究竟通过何种方式获得了动力)。方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事:1). 方法重写(子类继承父类并重写父类中已有的或抽象的方法);2). 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。

    2、访问修饰符public,private,protected,以及不写(默认)时的区别?

    答:


    类的成员不写访问修饰时默认为default。默认对于同一个包中的其他类相当于公开(public),对于不是同一个包中的其他类相当于私有(private)。受保护(protected)对子类相当于公开,对不是同一包中的没有父子关系的类相当于私有。Java中,外部类的修饰符只能是public或默认,类的成员(包括内部类)的修饰符可以是以上四种。

    3、String是最基本的数据类型吗?

    答:不是。Java中的基本数据类型只有8个:byte、short、int、long、float、double、char、boolean;除了基本类型(primitive type),剩下的都是引用类型(reference type),Java 5以后引入的枚举类型也算是一种比较特殊的引用类型。

    4、float f=3.4;是否正确?

    答:不正确。3.4是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换float f =(float)3.4; 或者写成float f =3.4F;。

    5、short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗?

    答:对于short s1 = 1; s1 = s1 + 1;由于1是int类型,因此s1+1运算结果也是int 型,需要强制转换类型才能赋值给short型。而short s1 = 1; s1 += 1;可以正确编译,因为s1+= 1;相当于s1 = (short)(s1 + 1);其中有隐含的强制类型转换。
    示例代码如下:

    前者有错,s1会自动提升为int类型,结果赋值给short类型,所以报错。
    后者无错,+=这种赋值运算符隐含了强制类型转换。其实变量s1的值未被使用。
    
    public class Test {
        public static void main(String[] args) {
            System.out.println(Math.round(11.5));    // 12
            System.out.println(Math.round(-11.5));   // -11
    
            // short s1 = 1; 
            // s1 = s1 + 1; // Type mismatch: cannot convert from int to short 类型不匹配:不能从int转换为short
    
            short s1 = 1; // The value of the local variable s1 is not used 局部变量s1的值未被使用 出现了警告
            s1 += 1;
        }
    }
    

    6、Java有没有goto?

    答:goto 是Java中的保留字,在目前版本的Java中没有使用。(根据James Gosling(Java之父)编写的《The Java Programming Language》一书的附录中给出了一个Java关键字列表,其中有goto和const,但是这两个是目前无法使用的关键字,因此有些地方将其称之为保留字,其实保留字这个词应该有更广泛的意义,因为熟悉C语言的程序员都知道,在系统类库中使用过的有特殊意义的单词或单词的组合都被视为保留字)

    7、int和Integer有什么区别?

    答:Java是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型(wrapper class),int的包装类就是Integer,从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
    Java 为每个原始类型提供了包装类型:

    • 原始类型:boolean,char,byte,short,int,long,float,double
    • 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double

    示例代码如下:

    class AutoUnboxingTest {
    
        public static void main(String[] args) {
            Integer a = new Integer(3);
            Integer b = 3;                  // 将3自动装箱成Integer类型
            int c = 3;
            System.out.println(a == b);     // false 两个引用没有引用同一对象
            System.out.println(a == c);     // true  a自动拆箱成int类型再和c比较
        }
    }
    

    最近还遇到一个面试题,也是和自动装箱和拆箱有点关系的,代码如下所示:

    public class Test03 {
    
        public static void main(String[] args) {
            Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150;
    
            System.out.println(f1 == f2);
            System.out.println(f3 == f4);
        }
    }
    

    如果不明就里很容易认为两个输出要么都是true要么都是false。首先需要注意的是f1、f2、f3、f4四个变量都是Integer对象引用,所以下面的==运算比较的不是值而是引用。装箱的本质是什么呢?当我们给一个Integer对象赋一个int值的时候,会调用Integer类的静态方法valueOf,如果看看valueOf的源代码就知道发生了什么。

        public static Integer valueOf(int i) {
            if (i >= IntegerCache.low && i <= IntegerCache.high)
                return IntegerCache.cache[i + (-IntegerCache.low)];
            return new Integer(i);
        }
    

    IntegerCache是Integer的内部类,其代码如下所示:

    /**
         * Cache to support the object identity semantics of autoboxing for values between
         * -128 and 127 (inclusive) as required by JLS.
         *
         * The cache is initialized on first usage.  The size of the cache
         * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
         * During VM initialization, java.lang.Integer.IntegerCache.high property
         * may be set and saved in the private system properties in the
         * sun.misc.VM class.
         */
    
        private static class IntegerCache {
            static final int low = -128;
            static final int high;
            static final Integer cache[];
    
            static {
                // high value may be configured by property
                int h = 127;
                String integerCacheHighPropValue =
                    sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
                if (integerCacheHighPropValue != null) {
                    try {
                        int i = parseInt(integerCacheHighPropValue);
                        i = Math.max(i, 127);
                        // Maximum array size is Integer.MAX_VALUE
                        h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                    } catch( NumberFormatException nfe) {
                        // If the property cannot be parsed into an int, ignore it.
                    }
                }
                high = h;
    
                cache = new Integer[(high - low) + 1];
                int j = low;
                for(int k = 0; k < cache.length; k++)
                    cache[k] = new Integer(j++);
    
                // range [-128, 127] must be interned (JLS7 5.1.7)
                assert IntegerCache.high >= 127;
            }
    
            private IntegerCache() {}
        }
    

    简单的说,如果整型字面量的值在-128到127之间,那么不会new新的Integer对象,而是直接引用常量池中的Integer对象,所以上面的面试题中f1==f2的结果是true,而f3==f4的结果是false。

    提醒:越是貌似简单的面试题其中的玄机就越多,需要面试者有相当深厚的功力。

    8、&和&&的区别?

    答:&运算符有两种用法:(1)按位与;(2)逻辑与。&&运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是true整个表达式的值才是true。&&之所以称为短路运算是因为,如果&&左边的表达式的值是false,右边的表达式会被直接短路掉,不会进行运算。很多时候我们可能都需要用&&而不是&,例如在验证用户登录时判定用户名不是null而且不是空字符串,应当写为:username != null && !username.equals(""),二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行字符串的equals比较,否则会产生NullPointerException异常。注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。

    补充:如果你熟悉JavaScript,那你可能更能感受到短路运算的强大,想成为JavaScript的高手就先从玩转短路运算开始吧。

    9、解释内存中的栈(stack)、堆(heap)和方法区(method area)的用法。

    答:通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用JVM中的栈空间;而通过new关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器都采用分代收集算法,所以堆空间还可以细分为新生代和老生代,再具体一点可以分为Eden、Survivor(又可分为From Survivor和To Survivor)、Tenured;方法区和堆都是各个线程共享的内存区域,用于存储已经被JVM加载的类信息、常量、静态变量、JIT编译器编译后的代码等数据;程序中的字面量(literal)如直接书写的100、"hello"和常量都是放在常量池中,常量池是方法区的一部分,。栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过JVM的启动参数来进行调整,栈空间用光了会引发StackOverflowError,而堆和常量池空间不足则会引发OutOfMemoryError。

    String str = new String("hello");
    

    上面的语句中变量str放在栈上,用new创建出来的字符串对象放在堆上,而"hello"这个字面量是放在方法区的。

    补充1:较新版本的Java(从Java 6的某个更新开始)中,由于JIT编译器的发展和"逃逸分析"技术的逐渐成熟,栈上分配、标量替换等优化技术使得对象一定分配在堆上这件事情已经变得不那么绝对了。
    补充2:运行时常量池相当于Class文件常量池具有动态性,Java语言并不要求常量一定只有编译期间才能产生,运行期间也可以将新的常量放入池中,String类的intern()方法就是这样的。

    看看下面代码的执行结果是什么并且比较一下jdk7以前和以后的运行结果是否一致。
      String.intern()是一个Native方法,它的作用是:如果字符串常量池中已经包含了一个等于此String对象的字符串,则返回代表池(运行时常量池)中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中并且返回此String对象的引用。此方法在jdk1.6和jdk1.7中有差异。

    String s1 = new StringBuilder("go").append("od").toString();
    System.out.println(s1.intern() == s1);
    String s2 = new StringBuilder("ja").append("va").toString();
    System.out.println(s2.intern() == s2);
    

      这段代码在jdk1.6中运行,会得到两个false,而在jdk1.7中运行会得到一个true一个false。产生差异的原因是:在jdk1.6中,intern()方法会把首次遇到的字符串实例复制到永久代中,返回的也是永久代中这个字符串实例的引用,而用StringBuilder创建的字符串实例在Java堆上,所以必然不是同一个引用,将返回false。而jdk1.7中的intern()实现不会再复制实例,只是在常量池中记录首次出现的实例引用,因此intern()返回的引用和由StringBuilder创建的那个字符串实例是同一个。对比str2返回false是因为“java”这个字符串在执行StringBuilder.toString()之前已经出现过,字符串常量池中已经有它的引用了,不符合首次出现的原则,而“good”这个字符串则是首次出现的,因此返回true。
      现在的疑问是“java”这个字符串在常量池中什么时候存在了?
    我最开始的猜想是“java”这个字符串是不是常驻在常量池中的?那为什么常驻在常量池中呢?Java虚拟机什么时候加载了“java”这个字符串?
      答:java虚拟机会自动调用System类,代码如下:

    /* register the natives via the static initializer.
     *
     * VM will invoke the initializeSystemClass method to complete
     * the initialization for this class separated from clinit.
     * Note that to use properties set by the VM, see the constraints
     * described in the initializeSystemClass method.
     */
    在System类中的注释可以知道,调用了initializeSystemClass方法,在此方法中调用了Version对象的init静态方法
    sun.misc.Version.init();
    因此sun.misc.Version类会在JDK类库的初始化过程中被加载并初始化。
    查看Version类定义的私有静态字符串常量如下:
    private static final String launcher_name = "java";
    private static final String java_version = "1.7.0_51";
    private static final String java_runtime_name = "Java(TM) SE Runtime Environment";
    private static final String java_runtime_version = "1.7.0_51-b13";
    在初始化Version类时,对其静态常量字段根据指定的常量值做默认初始化,所以"java"被加载到了字符串常量池中,修改上面代码使字符串值为上面常量中的任意一个都会返回false。
    String str2=new StringBuilder("1.7.0").append("_51").toString();
    System.out.println(str2.intern()==str2);
    

      这个问题解决了,上面这些在虚拟机加载时就初始化的常量,我们再定义其他的字符串常量试试,比如“xiaoyi and heize”,运行结果:true,可以知道"xiaoyi and heize"这个字符串常量没有被预先加载到常量池中。

    ps:在虚拟机上进行开发的开发人员称方法区为“永久代”,但两者本质上并不等价,仅仅是因为HotSpot虚拟机的设计团队选择把GC分代收集器扩展至方法区,或者说使用永久代来实现方法区而已,但现在看来使用永久代实现方法区并不是一个好主意,因为这样更容易遇到内存溢出问题,在jdk1.7中的HotSpot中,已经把原本放在永久代中的字符串常量池移除---摘自《深入理解Java虚拟机》

    10、Math.round(11.5) 等于多少?Math.round(-11.5)等于多少?

    答:Math.round(11.5)的返回值是12,Math.round(-11.5)的返回值是-11。四舍五入的原理是在参数上加0.5然后进行下取整。

    Math类的成员方法:
        public static int round(float a)   四舍五入(参数为double的自学)
        要深刻理解四舍五入的具体含义:
            满足五入的条件后,得到的值要比原来的值大;
            满足四舍的条件后,得到的值要比原来的值小;
            不管是正数还是负数。
    

    11、switch是否能作用在byte上,是否能作用在long上,是否能作用在String上?

    答:在Java 5以前,switch(expr)中,expr只能是byte、short、char、int;从Java 5开始,Java中引入了枚举类型,expr也可以是enum类型;从Java 7开始,expr还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。

    12、用最有效率的方法计算2乘以8?

    答: 2 << 3(左移3位相当于乘以2的3次方,右移3位相当于除以2的3次方)。

    补充:我们为编写的类重写hashCode方法时,可能会看到如下所示的代码,其实我们不太理解为什么要使用这样的乘法运算来产生哈希码(散列码),而且为什么这个数是个素数,为什么通常选择31这个数?前两个问题的答案你可以自己百度一下,选择31是因为可以用移位和减法运算来代替乘法,从而得到更好的性能。说到这里你可能已经想到了:31 * num 等价于(num << 5) - num,左移5位相当于乘以2的5次方再减去自身就相当于乘以31,现在的VM都能自动完成这个优化。

    为编写的类重写hashCode方法

    public class PhoneNumber {
        private int areaCode;
        private String prefix;
        private String lineNumber;
    
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + areaCode;
            result = prime * result
                    + ((lineNumber == null) ? 0 : lineNumber.hashCode());
            result = prime * result + ((prefix == null) ? 0 : prefix.hashCode());
            return result;
        }
    
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            PhoneNumber other = (PhoneNumber) obj;
            if (areaCode != other.areaCode)
                return false;
            if (lineNumber == null) {
                if (other.lineNumber != null)
                    return false;
            } else if (!lineNumber.equals(other.lineNumber))
                return false;
            if (prefix == null) {
                if (other.prefix != null)
                    return false;
            } else if (!prefix.equals(other.prefix))
                return false;
            return true;
        }
    }
    

    13、数组有没有length()方法?String有没有length()方法?

    答:数组没有length()方法,有length的属性。
    String有length()方法。在JavaScript中,获得字符串的长度是通过length属性得到的,这一点容易和Java混淆。

    14、在Java中,如何跳出当前的多重嵌套循环?

    答:在最外层循环前加一个标记如A,然后用break A;可以跳出多重循环。(Java中支持带标签的break和continue语句,作用有点类似于C和C++中的goto语句,但是就像要避免使用goto一样,应该避免使用带标签的break和continue,因为它不会让你的程序变得更优雅,很多时候甚至有相反的作用,所以这种语法其实不知道更好,为什么会让程序变得不优雅呢?一个程序跳来跳去,太灵活了,我们不能够控制了,就不好了)

    15、构造器(constructor)是否可被重写(override)?

    答:构造器不能被继承,因此不能被重写,但可以被重载。

    16、两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?

    答:不对,如果两个对象x和y满足x.equals(y) == true,它们的哈希码(hash code)应当相同。Java对于eqauls方法和hashCode方法是这样规定的:
      (1) 如果两个对象相同(equals方法返回true),那么它们的hashCode值一定要相同;
      (2) 如果两个对象的hashCode相同,它们并不一定相同。
    当然,你未必要按照要求去做,但是如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现在Set集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。

    补充:关于equals和hashCode方法,很多Java程序都知道,但很多人也就是仅仅知道而已,在Joshua Bloch的大作《Effective Java》(很多软件公司,《Effective Java》、《Java编程思想》以及《重构:改善既有代码质量》是Java程序员必看书籍,如果你还没看过,那就赶紧去亚马逊买一本吧)中是这样介绍equals方法的:首先equals方法必须满足自反性(x.equals(x)必须返回true)、对称性(x.equals(y)返回true时,y.equals(x)也必须返回true)、传递性(x.equals(y)和y.equals(z)都返回true时,x.equals(z)也必须返回true)和一致性(当x和y引用的对象信息没有被修改时,多次调用x.equals(y)应该得到同样的返回值),而且对于任何非null值的引用x,x.equals(null)必须返回false。
    实现高质量的equals方法的诀窍包括:
      1. 使用==操作符检查"参数是否为这个对象的引用";
      2. 使用instanceof操作符检查"参数是否为正确的类型";
      3. 对于类中的关键属性,检查参数传入对象的属性是否与之相匹配;
      4. 编写完equals方法后,问自己它是否满足对称性、传递性、一致性;
      5. 重写equals时总是要重写hashCode;
      6. 不要将equals方法参数中的Object对象替换为其他的类型,在重写时不要忘掉@Override注解。

    17、是否可以继承String类?

    答:String 类是final类,不可以被继承。

    补充:继承String本身就是一个错误的行为,对String类型最好的重用方式是关联关系(Has-A)和依赖关系(Use-A)而不是继承关系(Is-A)。

    18、当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?

    答:是值传递。Java语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用(地址值)。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的。C++和C#中可以通过传引用或传输出参数来改变传入的参数的值。在C#中可以编写如下所示的代码,但是在Java中却做不到。

    using System;
    
    namespace CS01 {
    
        class Program {
            public static void swap(ref int x, ref int y) {
                int temp = x;
                x = y;
                y = temp;
            }
    
            public static void Main (string[] args) {
                int a = 5, b = 10;
                swap (ref a, ref b);
                // a = 10, b = 5;
                Console.WriteLine ("a = {0}, b = {1}", a, b);
            }
        }
    }
    

    说明:Java中没有传引用实在是非常的不方便,这一点在Java 8中仍然没有得到改进,正是如此在Java编写的代码中才会出现大量的Wrapper类(将需要通过方法调用修改的引用置于一个Wrapper类中,再将Wrapper对象传入方法),这样的做法只会让代码变得臃肿,尤其是让从C和C++转型为Java程序员的开发者无法容忍。

    其实还是不够明白,我们来看看下面的例子吧:
    强烈推荐鄙人的例子,参考链接:https://www.cnblogs.com/chenmingjun/p/8698719.html

    19、String和StringBuilder、StringBuffer的区别?

    答:Java平台提供了两种类型的字符串:String和StringBuffer/StringBuilder,它们可以储存和操作字符串。其中String是只读字符串,也就意味着String引用的字符串内容是不能被改变的。而StringBuffer/StringBuilder类表示的字符串对象可以直接进行修改。StringBuilder是Java 5中引入的,它和StringBuffer的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被synchronized修饰(非同步),因此它的效率也比StringBuffer要高。

    面试题1:什么情况下用+运算符进行字符串连接比调用StringBuffer/StringBuilder对象的append方法连接字符串性能更好?
    面试题2:请说出下面程序的输出。

    class StringEqualTest {
    
        public static void main(String[] args) {
            String s1 = "Programming";
            String s2 = new String("Programming");
            String s3 = "Program";
            String s4 = "ming";
            String s5 = "Program" + "ming";
            String s6 = s3 + s4;
            System.out.println(s1 == s2); // false
            System.out.println(s1 == s5); // true
            System.out.println(s1 == s6); // false
            System.out.println(s1 == s6.intern()); // true
            System.out.println(s2 == s2.intern()); // false
        }
    }
    

    补充:解答上面的面试题需要清除两点:

    1. String对象的intern方法会得到字符串对象在常量池中对应的版本的引用(如果常量池中有一个字符串与String对象的equals结果是true),如果常量池中没有对应的字符串,则该字符串将被添加到常量池中,然后返回常量池中字符串的引用。
    2. 字符串的+操作其本质是创建了StringBuilder对象进行append操作,然后将拼接后的StringBuilder对象用toString方法处理成String对象,这一点可以用javap -c StringEqualTest.class命令获得class文件对应的JVM字节码指令就可以看出来。
    3. 要想获取对象的内存地址应使用System.identityHashCode()方法。

    20、重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?

    答:方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求。

    面试题:华为的面试题中曾经问过这样一个问题:"为什么不能根据返回类型来区分重载",快说出你的答案吧!

    21、描述一下JVM加载class文件的原理机制?

    答:JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。
      由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载连接(验证、准备和解析)初始化。类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM对类进行初始化,包括:1) 如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;2) 如果类中存在初始化语句,就依次执行这些初始化语句。
      类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。从Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM更好的保证了Java平台的安全性,在该机制中,JVM自带的Bootstrap是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java程序提供对Bootstrap的引用。下面是关于几个类加载器的说明:

    Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar);
    Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;
    System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。

    22、char 型变量中能不能存贮一个中文汉字,为什么?

    答:char类型可以存储一个中文汉字,因为Java中使用的编码是Unicode(不选择任何特定的编码,直接使用字符在字符集中的编号,这是统一的唯一方法),一个char类型占2个字节(16比特),所以放一个中文是没问题的。

    补充:使用Unicode意味着字符在JVM内部和外部有不同的表现形式,在JVM内部都是Unicode,当这个字符被从JVM内部转移到外部时(例如存入文件系统中),需要进行编码转换。所以Java中有字节流和字符流,以及在字符流和字节流之间进行转换的转换流,如InputStreamReader和OutputStreamReader,这两个类是字节流和字符流之间的适配器类,承担了编码转换的任务;对于C程序员来说,要完成这样的编码转换恐怕要依赖于union(联合体/共用体)共享内存的特征来实现了。

    23、抽象类(abstract class)和接口(interface)有什么异同?

    答:抽象类和接口都不能够实例化,但可以定义抽象类和接口类型的引用。一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类。接口比抽象类更加抽象,因为抽象类中可以定义构造器,可以有抽象方法和具体方法,而接口中不能定义构造器而且其中的方法全部都是抽象方法。抽象类中的成员可以是private、默认、protected、public的,而接口中的成员全都是public的。抽象类中可以定义成员变量,而接口中定义的成员变量实际上都是常量。有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法。

    24、静态嵌套类(Static Nested Class)和内部类(Inner Class)的不同?

    答:Static Nested Class是被声明为静态(static)的内部类,它可以不依赖于外部类实例被实例化。而通常的内部类需要在外部类实例化后才能实例化,其语法看起来挺诡异的。

    面试题:下面的代码哪些地方会产生编译错误?

    class Outer {
    
        class Inner {}
    
        public static void foo() { 
            new Inner(); 
        }
    
        public void bar() { 
            new Inner(); 
        }
    
        public static void main(String[] args) {
            new Inner();
        }
    }
    

    注意Java中非静态内部类对象的创建要依赖其外部类对象,上面的面试题中foo和main方法都是静态方法,静态方法中没有this,也就是说没有所谓的外部类对象,因此无法创建内部类对象,如果要在静态方法中创建内部类对象,可以这样做:new Outer().new Inner();

    25、Java中会存在内存泄漏吗?请简单描述。

    答:理论上Java因为有垃圾回收机制(GC)不会存在内存泄露问题(这也是Java被广泛使用于服务器端编程的一个重要原因);然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被GC回收,因此也会导致内存泄露的发生。例如Hibernate的Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中可能存在无用的垃圾对象,如果不及时关闭(close)或清空(flush)一级缓存就可能导致内存泄露。下面例子中的代码也会导致内存泄露。

    import java.util.Arrays;
    import java.util.EmptyStackException;
    
    public class MyStack<T> {
        private T[] elements;
        private int size = 0;
    
        private static final int INIT_CAPACITY = 16;
    
        public MyStack() {
            elements = (T[]) new Object[INIT_CAPACITY];
        }
    
        public void push(T elem) {
            ensureCapacity();
            elements[size++] = elem;
        }
    
        public T pop() {
            if(size == 0) 
                throw new EmptyStackException();
            return elements[--size];
        }
    
        private void ensureCapacity() {
            if(elements.length == size) {
                elements = Arrays.copyOf(elements, 2 * size + 1);
            }
        }
    }
    

    上面的代码实现了一个栈(先进后出(FILO))结构,乍看之下似乎没有什么明显的问题,它甚至可以通过你编写的各种单元测试。然而其中的pop方法却存在内存泄露的问题,当我们用pop方法弹出栈中的对象时,该对象不会被当作垃圾回收,即使使用栈的程序不再引用这些对象,因为栈内部维护着对这些对象的过期引用(obsolete reference)。在支持垃圾回收的语言中,内存泄露是很隐蔽的,这种内存泄露其实就是无意识的对象保持。如果一个对象引用被无意识的保留起来了,那么垃圾回收器不会处理这个对象,也不会处理该对象引用的其他对象,即使这样的对象只有少数几个,也可能会导致很多的对象被排除在垃圾回收之外,从而对性能造成重大影响,极端情况下会引发Disk Paging(物理内存与硬盘的虚拟内存交换数据),甚至造成OutOfMemoryError。

    26、抽象的(abstract)方法是否可同时是静态的(static),是否可同时是本地方法(native),是否可同时被synchronized修饰?

    答:都不能。抽象方法需要子类重写,而静态的方法是无法被重写的,因此二者是矛盾的。本地方法是由本地代码(如C代码)实现的方法,而抽象方法是没有实现的,也是矛盾的。synchronized和方法的实现细节有关,抽象方法不涉及实现细节,因此也是相互矛盾的

    27、阐述静态变量和实例变量的区别。

    答:静态变量是被static修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存。

    补充:在Java开发中,上下文类和工具类中通常会有大量的静态成员。

    28、是否可以从一个静态(static)方法内部发出对非静态(non-static)方法的调用?

    答:不可以,静态方法只能访问静态成员,因为非静态方法的调用要先创建对象,在调用静态方法时可能对象并没有被初始化。

    29、如何实现对象克隆?

    答:有两种方式:
      1) 实现Cloneable接口并重写Object类中的clone()方法;
      2) 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。

    注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是好过把问题留到运行时

    30、GC是什么?为什么要有GC?

    答:GC是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。Java程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一:System.gc() 或 Runtime.getRuntime().gc() ,但JVM可以屏蔽掉显示的垃圾回收调用。
      垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。在Java诞生初期,垃圾回收是Java最大的亮点之一,因为服务器端的编程需要有效的防止内存泄露问题,然而时过境迁,如今Java的垃圾回收机制已经成为被诟病的东西。移动智能终端用户通常觉得iOS的系统比Android系统有更好的用户体验,其中一个深层次的原因就在于Android系统中垃圾回收的不可预知性

    补充:垃圾回收机制有很多种,包括:分代复制垃圾回收、标记垃圾回收、增量垃圾回收等方式。标准的Java进程既有栈又有堆。栈保存了原始型局部变量,堆保存了要创建的对象。Java平台对堆内存回收和再利用的基本算法被称为标记和清除,但是Java对其进行了改进,采用“分代式垃圾收集”。这种方法会跟据Java对象的生命周期将堆内存划分为不同的区域,在垃圾收集过程中,可能会将对象移动到不同区域:

    • 伊甸园(Eden):这是对象最初诞生的区域,并且对大多数对象来说,这里是它们唯一存在过的区域。
    • 幸存者乐园(Survivor):从伊甸园幸存下来的对象会被挪到这里。
    • 终身颐养园(Tenured):这是足够老的幸存对象的归宿。年轻代收集(Minor-GC)过程是不会触及这个地方的。当年轻代收集不能把对象放进终身颐养园时,就会触发一次完全收集(Major-GC),这里可能还会牵扯到压缩,以便为大对象腾出足够的空间。

    与垃圾回收相关的JVM参数:

    • -Xms / -Xmx --> 堆的初始大小 / 堆的最大大小
    • -Xmn --> 堆中年轻代的大小
    • -XX:-DisableExplicitGC --> 让System.gc()不产生任何作用
    • -XX:+PrintGCDetails --> 打印GC的细节
    • -XX:+PrintGCDateStamps --> 打印GC操作的时间戳
    • -XX:NewSize --> 设置新生代大小
    • -XX:MaxNewSize --> 设置新生代最大大小
    • -XX:NewRatio --> 可以设置老生代和新生代的比例
    • -XX:PrintTenuringDistribution --> 设置每次新生代GC后输出幸存者乐园中对象年龄的分布
    • -XX:InitialTenuringThreshold --> 设置老年代阀值的初始值
    • -XX:MaxTenuringThreshold --> 设置老年代阀值的最大值
    • -XX:TargetSurvivorRatio --> 设置幸存区的目标使用率

    31、String s = new String("xyz");创建了几个字符串对象?

    答:两个对象,一个是静态区的"xyz",一个是用new创建在堆上的对象。

    32、接口是否可继承(extends)接口?抽象类是否可实现(implements)接口?抽象类是否可继承具体类(concrete class)?

    答:接口可以继承接口,而且支持多重继承。抽象类可以实现(implements)接口,抽象类可继承具体类也可以继承抽象类。

    33、一个".java"源文件中是否可以包含多个类(不是内部类)?有什么限制?

    答:可以,但一个源文件中最多只能有一个公开类(public class)而且文件名必须和公开类的类名完全保持一致。

    34、Anonymous Inner Class(匿名内部类)是否可以继承其它类?是否可以实现接口?

    答:可以继承其他类或实现其他接口,在Swing编程和Android开发中常用此方式来实现事件监听和回调。

    35、内部类可以引用它的包含类(外部类)的成员吗?有没有什么限制?

    答:一个内部类对象可以访问创建它的外部类对象的成员,包括私有成员。

    36、Java 中的final关键字有哪些用法?

    答:
      (1)修饰类:表示该类不能被继承;
      (2)修饰方法:表示方法不能被重写;
      (3)修饰变量:表示变量只能一次赋值以后值不能被修改(常量)。

    37、指出下面程序的运行结果。

    class A {
    
        static {
            System.out.print("1");
        }
    
        public A() {
            System.out.print("2");
        }
    }
    
    class B extends A {
    
        static {
            System.out.print("a");
        }
    
        public B() {
            System.out.print("b");
        }
    }
    
    public class Hello {
    
        public static void main(String[] args) {
            A ab = new B();
            ab = new B();
        }
    }
    

    答:执行结果:1a2b2b。创建对象时构造器的调用顺序是:先初始化静态成员,然后调用父类构造器,再初始化非静态成员,最后调用自身构造器。

    提示:如果不能给出此题的正确答案,说明之前第21题Java类加载机制还没有完全理解,赶紧再看看吧。

    38、数据类型之间的转换

    如何将字符串转换为基本数据类型?
      答:调用基本数据类型对应的包装类中的方法parseXXX(String)valueOf(String)即可返回相应基本数据类型。
    如何将基本数据类型转换为字符串?
      答:一种方法是将基本数据类型与空字符串("")连接(+)即可获得其所对应的字符串;另一种方法是调用String 类中的valueOf()方法返回相应字符串。

    39、如何实现字符串的反转及替换?

    答:方法很多,可以自己写实现也可以使用String或StringBuffer/StringBuilder中的方法。有一道很常见的面试题是用递归实现字符串反转,代码如下所示:

        public static String reverse(String originStr) {
            if (originStr == null || originStr.length() <= 1)
                return originStr;
            return reverse(originStr.substring(1)) + originStr.charAt(0);
        }
    

    40、怎样将GB2312编码的字符串转换为ISO-8859-1编码的字符串?

    答:代码如下所示:

    String s1 = "你好";
    String s2 = new String(s1.getBytes("GB2312"), "ISO-8859-1");
    

    41、日期和时间

    • 如何取得年月日、小时分钟秒?
    • 如何取得从1970年1月1日0时0分0秒到现在的毫秒数?
    • 如何取得某月的最后一天?
    • 如何格式化日期?
      答:
      问题1:创建java.util.Calendar实例,调用其get()方法传入不同的参数即可获得参数所对应的值。
      Java 8中可以使用java.time.LocalDateTimel来获取,代码如下所示。
    public class DateTimeTest {
        public static void main(String[] args) {
            Calendar cal = Calendar.getInstance();
            System.out.println(cal.get(Calendar.YEAR));
            System.out.println(cal.get(Calendar.MONTH)); // 0 - 11
            System.out.println(cal.get(Calendar.DATE));
            System.out.println(cal.get(Calendar.HOUR_OF_DAY));
            System.out.println(cal.get(Calendar.MINUTE));
            System.out.println(cal.get(Calendar.SECOND));
    
            // Java 8
            LocalDateTime dt = LocalDateTime.now();
            System.out.println(dt.getYear());
            System.out.println(dt.getMonthValue()); // 1 - 12
            System.out.println(dt.getDayOfMonth());
            System.out.println(dt.getHour());
            System.out.println(dt.getMinute());
            System.out.println(dt.getSecond());
        }
    }
    

    问题2:以下方法均可获得该毫秒数。

        Calendar.getInstance().getTimeInMillis();
        System.currentTimeMillis();
        Clock.systemDefaultZone().millis(); // Java 8
    

    问题3:代码如下所示。

        Calendar time = Calendar.getInstance();
        time.getActualMaximum(Calendar.DAY_OF_MONTH);
    

    问题4:利用java.text.DataFormat 的子类(如SimpleDateFormat类)中的format(Date)方法可将日期格式化。Java 8中可以用java.time.format.DateTimeFormatter来格式化时间日期,代码如下所示。

    import java.text.SimpleDateFormat;
    import java.time.LocalDate;
    import java.time.format.DateTimeFormatter;
    import java.util.Date;
    
    class DateFormatTest {
    
        public static void main(String[] args) {
            SimpleDateFormat oldFormatter = new SimpleDateFormat("yyyy/MM/dd");
            Date date1 = new Date();
            System.out.println(oldFormatter.format(date1));
    
            // Java 8
            DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
            LocalDate date2 = LocalDate.now();
            System.out.println(date2.format(newFormatter));
        }
    }
    

    补充:Java的时间日期API一直以来都是被诟病的东西,为了解决这一问题,Java 8中引入了新的时间日期API,其中包括LocalDate、LocalTime、LocalDateTime、Clock、Instant等类,这些的类的设计都使用了不变模式,因此是线程安全的设计。如果不理解这些内容,可以参考我的另一篇文章《关于Java并发编程的总结和思考》

    42、打印昨天的当前时刻。

    import java.util.Calendar;
    
    class YesterdayCurrent {
        public static void main(String[] args){
            Calendar cal = Calendar.getInstance();
            cal.add(Calendar.DATE, -1);
            System.out.println(cal.getTime());
        }
    }
    

    在Java 8中,可以用下面的代码实现相同的功能。

    import java.time.LocalDateTime;
    
    class YesterdayCurrent {
    
        public static void main(String[] args) {
            LocalDateTime today = LocalDateTime.now();
            LocalDateTime yesterday = today.minusDays(1);
    
            System.out.println(yesterday);
        }
    }
    

    43、比较一下Java和JavaSciprt。

    答:JavaScript 与Java是两个公司开发的不同的两个产品。Java 是原Sun Microsystems公司推出的面向对象的程序设计语言,特别适合于互联网应用程序开发;而JavaScript是Netscape公司的产品,为了扩展Netscape浏览器的功能而开发的一种可以嵌入Web页面中运行的基于对象和事件驱动的解释性语言。JavaScript的前身是LiveScript;而Java的前身是Oak语言。
    下面对两种语言间的异同作如下比较:

    • 基于对象和面向对象:Java是一种真正的面向对象的语言,即使是开发简单的程序,必须设计对象;JavaScript是种脚本语言,它可以用来制作与网络无关的,与用户交互作用的复杂软件。它是一种基于对象(Object-Based)和事件驱动(Event-Driven)的编程语言,因而它本身提供了非常丰富的内部对象供设计人员使用。
    • 解释和编译:Java的源代码在执行之前,必须经过编译。JavaScript是一种解释性编程语言,其源代码不需经过编译,由浏览器解释执行。(目前的浏览器几乎都使用了JIT(即时编译)技术来提升JavaScript的运行效率)
    • 强类型变量和类型弱变量:Java采用强类型变量检查,即所有变量在编译之前必须作声明;JavaScript中变量是弱类型的,甚至在使用变量前可以不作声明,JavaScript的解释器在运行时检查推断其数据类型。
    • 代码格式不一样。

    补充:上面列出的四点是网上流传的所谓的标准答案。其实Java和JavaScript最重要的区别是一个是静态语言,一个是动态语言。目前的编程语言的发展趋势是函数式语言和动态语言。在Java中类(class)是一等公民,而JavaScript中函数(function)是一等公民,因此JavaScript支持函数式编程,可以使用Lambda函数和闭包(closure),当然Java 8也开始支持函数式编程,提供了对Lambda表达式以及函数式接口的支持。对于这类问题,在面试的时候最好还是用自己的语言回答会更加靠谱,不要背网上所谓的标准答案。

    44、什么时候用断言(assert)?

    答:断言在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制。一般来说,断言用于保证程序最基本、关键的正确性。断言检查通常在开发和测试时开启。为了保证程序的执行效率,在软件发布后断言检查通常是关闭的。断言是一个包含布尔表达式的语句,在执行这个语句时假定该表达式为true;如果表达式的值为false,那么系统会报告一个AssertionError。断言的使用如下面的代码所示:

    assert(a > 0); // throws an AssertionError if a <= 0

    断言可以有两种形式:
      assert Expression1;
      assert Expression1 : Expression2 ;
    Expression1 应该总是产生一个布尔值。
    Expression2 可以是得出一个值的任意表达式;这个值用于生成显示更多调试信息的字符串消息。

    要在运行时启用断言,可以在启动JVM时使用-enableassertions或者-ea标记。要在运行时选择禁用断言,可以在启动JVM时使用-da或者-disableassertions标记。要在系统类中启用或禁用断言,可使用-esa或-dsa标记。还可以在包的基础上启用或者禁用断言。

    注意:断言不应该以任何方式改变程序的状态。简单的说,如果希望在不满足某些条件时阻止代码的执行,就可以考虑用断言来阻止它。

    45、Error和Exception有什么区别?

    答:Error表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情况下的一种严重问题;比如内存溢出,不可能指望程序能处理这样的情况;Exception表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题;也就是说,它表示如果程序运行正常,从不会发生的情况。

    面试题:2005年摩托罗拉的面试中曾经问过这么一个问题“If a process reports a stack overflow run-time error, what’s the most possible cause?”,给了四个选项a. lack of memory; b. write on an invalid memory space; c. recursive function calling; d. array index out of boundary. Java程序在运行时也可能会遭遇StackOverflowError,这是一个无法恢复的错误,只能重新修改代码了,这个面试题的答案是c。如果写了不能迅速收敛的递归,则很有可能引发栈溢出的错误,如下所示:

    class StackOverflowErrorTest {
    
        public static void main(String[] args) {
            main(null);
        }
    }
    

    提示:用递归编写程序时一定要牢记两点:1. 递归公式;2. 收敛条件(什么时候就不再继续递归)。

    46、try{}里有一个return语句,那么紧跟在这个try后的finally{}里的代码会不会被执行,什么时候被执行,在return前还是后?

    答:会执行,在方法返回调用者前执行。

    注意:在finally中改变返回值的做法是不好的,因为如果存在finally代码块,try中的return语句不会立马返回调用者,而是记录下返回值待finally代码块执行完毕之后再向调用者返回其值,然后如果在finally中修改了返回值,就会返回修改后的值。显然,在finally中返回或者修改返回值会对程序造成很大的困扰,C#中直接用编译错误的方式来阻止程序员干这种龌龊的事情,Java中也可以通过提升编译器的语法检查级别来产生警告或错误,Eclipse中可以在如图所示的地方进行设置,强烈建议将此项设置为编译错误。

    47、Java语言如何进行异常处理,关键字:throws、throw、try、catch、finally分别如何使用?

    答:Java通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接口。在Java中,每个异常都是一个对象,它是Throwable类或其子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并可以对其进行处理。Java的异常处理是通过5个关键词来实现的:try、catch、throw、throws和finally。一般情况下是用try来执行一段程序,如果系统会抛出(throw)一个异常对象,可以通过它的类型来捕获(catch)它,或通过总是执行代码块(finally)来处理;try用来指定一块预防所有异常的程序;catch子句紧跟在try块后面,用来指定你想要捕获的异常的类型;throw语句用来明确地抛出一个异常;throws用来声明一个方法可能抛出的各种异常(当然声明异常时允许无病呻吟);finally为确保一段代码不管发生什么异常状况都要被执行;try语句可以嵌套,每当遇到一个try语句,异常的结构就会被放入异常栈中,直到所有的try语句都完成。如果下一级的try语句没有对某种异常进行处理,异常栈就会执行出栈操作,直到遇到有处理这种异常的try语句或者最终将异常抛给JVM。

    48、运行时异常与受检异常有何异同?

    答:异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。Java编译器要求方法必须声明抛出可能发生的受检异常,但是并不要求必须声明抛出未被捕获的运行时异常。异常和继承一样,是面向对象程序设计中经常被滥用的东西,在《Effective Java》中对异常的使用给出了以下指导原则:

    • 不要将异常处理用于正常的控制流(设计良好的API不应该强迫它的调用者为了正常的控制流而使用异常)
    • 对可以恢复的情况使用受检异常,对编程错误使用运行时异常
    • 避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发生)
    • 优先使用标准的异常
    • 每个方法抛出的异常都要有文档
    • 保持异常的原子性
    • 不要在catch中忽略掉捕获到的异常

    49、列出一些你常见的运行时异常?

    答:

    • ArithmeticException(算术异常)
    • ClassCastException (类转换异常)
    • IllegalArgumentException (非法参数异常)
    • IndexOutOfBoundsException (下标越界异常)
    • NullPointerException (空指针异常)
    • SecurityException (安全异常)

    50、阐述final、finally、finalize的区别。

    答:

    • final:修饰符(关键字)有三种用法:如果一个类被声明为final,意味着它不能再派生出新的子类,即不能被继承,因此它和abstract是反义词。将变量声明为final,可以保证它们在使用中不被改变,被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取不可修改。被声明为final的方法也同样只能使用,不能在子类中被重写。
    • finally:通常放在try…catch…的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中。
    • finalize:Object类中定义的方法,Java中允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize()方法可以整理系统资源或者执行其他清理工作。

    我有一个微信公众号,经常会分享一些Java技术相关的干货;如果你喜欢我的分享,可以用微信搜索“Java团长”或者“javatuanzhang”关注。


    下一篇:Java面试题及答案2019版(下)

    展开全文
  • java面试题大全(整理版)

    万次阅读 多人点赞 2018-03-05 19:12:11
    这几天在网上搜集各种java面试题:一是为了自己能复习方便,二是为了分享给大家~~题目都是来自网上大佬的分享,感谢大佬们的贡献~~(持续更新中...)1、面向对象的特征有哪些方面?- 抽象:抽象是将一类对象的共同...

    这几天在网上搜集各种java面试题:

    一是为了自己能复习方便,二是为了分享给大家~~
    题目都是来自网上大佬的分享,感谢大佬们的贡献~~

    (持续更新中...)

    1、面向对象的特征有哪些方面?

    - 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。

    - 继承:继承是从已有类得到继承信息创建新类的过程。提供继承的类叫父类(超类、基类)、得到继承的类叫子类(派生类)。

    - 封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口(可以想想普通洗衣机和全自动洗衣机的差别,明显全自动洗衣机封装更好因此操作起来更简单;我们现在使用的智能手机也是封装得足够好的,因为几个按键就搞定了所有的事情)。

    - 多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。实现多态需要做两件事:1). 方法重写(子类继承父类并重写父类中的方法);2). 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)

    2、访问修饰符public,private,protected,以及不写(默认)时的区别?

    修饰符当前类同 包子 类其他包
    public
    protected×
    default××
    private×××
    3、String 是最基本的数据类型吗?

    答:不是。Java中的基本数据类型只有8个:byte、short、int、long、float、double、char、boolean;除了基本类型(primitive type)和枚举类型(enumeration type),剩下的都是引用类型(reference type)。

    4、float f=3.4;是否正确?

    答:不正确。3.4是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换float f =(float)3.4; 或者写成float f =3.4F;。

    5、short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗?

    答:对于short s1 = 1; s1 = s1 + 1;由于1是int类型,因此s1+1运算结果也是int 型,需要强制转换类型才能赋值给short型。而short s1 = 1; s1 += 1;可以正确编译,因为s1+= 1;相当于s1 = (short)(s1 + 1);其中有隐含的强制类型转换。

    6、int和Integer有什么区别?

    答:Java是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型(wrapper class),int的包装类就是Integer,从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。

    class AutoUnboxingTest {
     
        public static void main(String[] args) {
            Integer a = new Integer(3);
            Integer b = 3;                  // 将3自动装箱成Integer类型
            int c = 3;
            System.out.println(a == b);     // false 两个引用没有引用同一对象
            System.out.println(a == c);     // true a自动拆箱成int类型再和c比较
        }
    }

    最近还遇到一个面试题,也是和自动装箱和拆箱有点关系的,代码如下所示:

    public class Test03 {
     
        public static void main(String[] args) {
            Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150;
     
            System.out.println(f1 == f2);
            System.out.println(f3 == f4);
        }
    }

    如果不明就里很容易认为两个输出要么都是true要么都是false。首先需要注意的是f1、f2、f3、f4四个变量都是Integer对象引用,所以下面的==运算比较的不是值而是引用。装箱的本质是什么呢?当我们给一个Integer对象赋一个int值的时候,会调用Integer类的静态方法valueOf

    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

    简单的说,如果整型字面量的值在-128到127之间,那么不会new新的Integer对象,而是直接引用常量池中的Integer对象,所以上面的面试题中f1==f2的结果是true,而f3==f4的结果是false。

    7、&和&&的区别?

    虽然二者都要求运算符左右两端的布尔值都是true整个表达式的值才是true。&&之所以称为短路运算是因为,如果&&左边的表达式的值是false,右边的表达式会被直接短路掉,不会进行运算。很多时候我们可能都需要用&&而不是&,例如在验证用户登录时判定用户名不是null而且不是空字符串,应当写为:username != null &&!username.equals(“”),二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行字符串的equals比较,否则会产生NullPointerException异常。注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。

    8、解释内存中的栈(stack)、堆(heap)和静态区(static area)的用法。

    栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间

    String str = new String("hello");

    上面的语句中变量str放在栈上,用new创建出来的字符串对象放在堆上,而”hello”这个字面量放在静态区。

    9、Math.round(11.5) 等于多少?Math.round(-11.5)等于多少?

    答:Math.round(11.5)的返回值是12,Math.round(-11.5)的返回值是-11。四舍五入的原理是在参数上加0.5然后进行下取整。

    10、switch 是否能作用在byte 上,是否能作用在long 上,是否能作用在String上?

    expr可以是byte、short、char、int、enum、String类型,但是long类型不能

    11、用最有效率的方法计算2乘以8?

    答: 2 << 3(左移3位相当于乘以2的3次方,右移3位相当于除以2的3次方)。

    12、数组有没有length()方法?String有没有length()方法?

    答:数组没有length()方法,有length 的属性。String 有length()方法。JavaScript中,获得字符串的长度是通过length属性得到的,这一点容易和Java混淆。

    13、构造器(constructor)是否可被重写(override)?

    答:构造器不能被继承,因此不能被重写,但可以被重载。

    14、两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?

    答:不对,如果两个对象x和y满足x.equals(y) == true,它们的哈希码(hash code)应当相同。Java对于eqauls方法和hashCode方法是这样规定的:(1)如果两个对象相同(equals方法返回true),那么它们的hashCode值一定要相同;(2)如果两个对象的hashCode相同,它们并不一定相同

    15、是否可以继承String类?

    答:String 类是final类,不可以被继承。

    16、当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?

    答:是值传递。Java语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的

    17、String和StringBuilder、StringBuffer的区别?

    答:Java平台提供了两种类型的字符串:String和StringBuffer/StringBuilder,它们可以储存和操作字符串。其中String是只读字符串,也就意味着String引用的字符串内容是不能被改变的。而StringBuffer/StringBuilder类表示的字符串对象可以直接进行修改。StringBuilder是Java 5中引入的,它和StringBuffer的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被synchronized修饰,因此它的效率也比StringBuffer要高。

    18、重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?

    答:方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求。

    19、描述一下JVM加载class文件的原理机制?

    答:JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件

    20、抽象类(abstract class)和接口(interface)有什么异同?

    答:抽象类和接口都不能够实例化,但可以定义抽象类和接口类型的引用。一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类。接口比抽象类更加抽象,因为抽象类中可以定义构造器,可以有抽象方法和具体方法,而接口中不能定义构造器而且其中的方法全部都是抽象方法。抽象类中的成员可以是private、默认、protected、public的,而接口中的成员全都是public的。抽象类中可以定义成员变量,而接口中定义的成员变量实际上都是常量。有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法。

    21、Java 中会存在内存泄漏吗,请简单描述。

    答:理论上Java因为有垃圾回收机制(GC)不会存在内存泄露问题(这也是Java被广泛使用于服务器端编程的一个重要原因);然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被GC回收,因此也会导致内存泄露的发生。例如hibernate的Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中可能存在无用的垃圾对象,如果不及时关闭(close)或清空(flush)一级缓存就可能导致内存泄露

    22、GC是什么?为什么要有GC?

    答:GC是垃圾收集的意思,垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存

    23、String s = new String(“xyz”);创建了几个字符串对象?

    答:两个对象,一个是静态区的”xyz”,一个是用new创建在堆上的对象。

    24、接口是否可继承(extends)接口?抽象类是否可实现(implements)接口?抽象类是否可继承具体类(concrete class)?

    答:接口可以继承接口,而且支持多重继承。抽象类可以实现(implements)接口,抽象类可继承具体类也可以继承抽象类。

    25、Java 中的final关键字有哪些用法?

    答:(1)修饰类:表示该类不能被继承;(2)修饰方法:表示方法不能被重写;(3)修饰变量:表示变量只能一次赋值以后值不能被修改(常量)。

    26、指出下面程序的运行结果。

    class A {
     
        static {
            System.out.print("1");
        }
     
        public A() {
            System.out.print("2");
        }
    }
     
    class B extends A{
     
        static {
            System.out.print("a");
        }
     
        public B() {
            System.out.print("b");
        }
    }
     
    public class Hello {
     
        public static void main(String[] args) {
            A ab = new B();
            ab = new B();
        }
     
    }

    答:执行结果:1a2b2b。创建对象时构造器的调用顺序是:先初始化静态成员,然后调用父类构造器,再初始化非静态成员,最后调用自身构造器。

    27、数据类型之间的转换:
    - 如何将字符串转换为基本数据类型?
    - 如何将基本数据类型转换为字符串?

    答:
    - 调用基本数据类型对应的包装类中的方法parseXXX(String)或valueOf(String)即可返回相应基本类型;

    - 一种方法是将基本数据类型与空字符串(”")连接(+)即可获得其所对应的字符串;另一种方法是调用String 类中的valueOf()方法返回相应字符串

    28、如何实现字符串的反转及替换?

    答:方法很多,可以自己写实现也可以使用String或StringBuffer/StringBuilder中的方法。有一道很常见的面试题是用递归实现字符串反转,代码如下所示:

    public static String reverse(String originStr) {
          if(originStr == null || originStr.length() <= 1)
              return originStr;
          return reverse(originStr.substring(1)) + originStr.charAt(0);
      }
    29、怎样将GB2312编码的字符串转换为ISO-8859-1编码的字符串?

    答:代码如下所示:

    String s1 = "你好";
    String s2 = new String(s1.getBytes("GB2312"), "ISO-8859-1");

    30、利用java.text.DataFormat 的子类(如SimpleDateFormat类)中的format(Date)方法可将日期格式化

    class DateFormatTest {
     
        public static void main(String[] args) {
            SimpleDateFormat oldFormatter = new SimpleDateFormat("yyyy/MM/dd");
            Date date1 = new Date();
            System.out.println(oldFormatter.format(date1));
     
        }
    }

    31、比较一下Java和JavaSciprt。

    java是静态语言,js是动态语言

    - 基于对象和面向对象:Java是一种真正的面向对象的语言,即使是开发简单的程序,必须设计对象;JavaScript是种脚本语言,它可以用来制作与网络无关的,与用户交互作用的复杂软件。它是一种基于对象(Object-Based)和事件驱动(Event-Driven)的编程语言,因而它本身提供了非常丰富的内部对象供设计人员使用。
    - 解释和编译:Java的源代码在执行之前,必须经过编译。JavaScript是一种解释性编程语言,其源代码不需经过编译,由浏览器解释执行。(目前的浏览器几乎都使用了JIT(即时编译)技术来提升JavaScript的运行效率)
    - 强类型变量和类型弱变量:Java采用强类型变量检查,即所有变量在编译之前必须作声明;JavaScript中变量是弱类型的,甚至在使用变量前可以不作声明,JavaScript的解释器在运行时检查推断其数据类型。

    - 代码格式不一样。

    32、try{}里有一个return语句,那么紧跟在这个try后的finally{}里的代码会不会被执行,什么时候被执行,在return前还是后?

    答:会执行,在方法返回调用者前执行。

    在finally中改变返回值的做法是不好的,因为如果存在finally代码块,try中的return语句不会立马返回调用者,而是记录下返回值待finally代码块执行完毕之后再向调用者返回其值,然后如果在finally中修改了返回值,就会返回修改后的值

    33、列出一些你常见的运行时异常?
    答:
    - ArithmeticException(算术异常)
    - ClassCastException (类转换异常)
    - IllegalArgumentException (非法参数异常)
    - IndexOutOfBoundsException (下标越界异常)
    - NullPointerException (空指针异常)

    - SecurityException (安全异常)

    34、类ExampleA继承Exception,类ExampleB继承ExampleA。
    有如下代码片断:

    try {
        throw new ExampleB("b")
    } catch(ExampleA e){
        System.out.println("ExampleA");
    } catch(Exception e){
        System.out.println("Exception");
    }
    请问执行此段代码的输出是什么?
    答:输出:ExampleA。(根据里氏代换原则[能使用父类型的地方一定能使用子类型],抓取ExampleA类型异常的catch块能够抓住try块中抛出的ExampleB类型的异常)

    面试题 - 说出下面代码的运行结果。(此题的出处是《Java编程思想》一书)

    class Annoyance extends Exception {}
    class Sneeze extends Annoyance {}
     
    class Human {
     
        public static void main(String[] args)
            throws Exception {
            try {
                try {
                    throw new Sneeze();
                }
                catch ( Annoyance a ) {
                    System.out.println("Caught Annoyance");
                    throw a;
                }
            }
            catch ( Sneeze s ) {
                System.out.println("Caught Sneeze");
                return ;
            }
            finally {
                System.out.println("Hello World!");
            }
        }
    }

    输出:

    Caught Annoyance

     Caught Sneeze 

    Hello World!

    try {      
        throw new Annoyance();  
    } catch (Sneeze s) {  
        System.out.println("Caught Sneeze");  
        return;  
    } finally {  
        System.out.println("Hello World!");  
    }  

    输出:(父类throw出来的异常,子类并没有捕获到

    Hello World!  
    Exception in thread "main" com.xq.exceptions.Annoyance  

        at com.xq.exceptions.Human.main(ExceptionTest.java:14) 

    try {  
        throw new Annoyance();  
    } catch (Sneeze s) {  
        System.out.println("Caught Sneeze");  
        return;  
    } catch (Exception e) {  
        System.out.println("Caught Exception");  
        return;  
    } finally {  
        System.out.println("Hello World!");  
    }  
    输出:(既然子类捕获不了,那就使用Exception),可以看到结果如下:

    Caught Exception

    Hello World!

    35、List、Set、Map是否继承自Collection接口?

    答:List、Set 是,Map 不是。Map是键值对映射容器,与List和Set有明显的区别,而Set存储的零散的元素且不允许有重复元素(数学中的集合也是如此),List是线性结构的容器,适用于按数值索引访问元素的情形。

    36、Collection和Collections的区别?

    答:Collection是一个接口,它是Set、List等容器的父接口;Collections是个一个工具类,提供了一系列的静态方法来辅助容器操作,这些方法包括对容器的搜索、排序、线程安全化等等。

    37、List、Map、Set三个接口存取元素时,各有什么特点?

    答:List以特定索引来存取元素,可以有重复元素。Set不能存放重复元素(用对象的equals()方法来区分元素是否重复)。Map保存键值对(key-value pair)映射,映射关系可以是一对一或多对一

    38、Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,它们有什么区别?

    答:sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复。wait()是Object类的方法,调用对象的wait()方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的notify()方法(或notifyAll()方法)时才能唤醒等待池中的线程进入等锁池(lock pool),如果线程重新获得对象的锁就可以进入就绪状态。

    39、线程的sleep()方法和yield()方法有什么区别?
    答:
    ① sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
    ② 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;
    ③ sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;

    ④ sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性。

    40、当一个线程进入一个对象的synchronized方法A之后,其它线程是否可进入此对象的synchronized方法B?
    答:不能。其它线程只能访问该对象的非同步方法,同步方法则不能进入。因为非静态方法上的synchronized修饰符要求执行方法时要获得对象的锁,如果已经进入A方法说明对象锁已经被取走,那么试图进入B方法的线程就只能在等锁池(注意不是等待池哦)中等待对象的锁。

    41、请说出与线程同步以及线程调度相关的方法。
    答:
    - wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
    - sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
    - notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
    - notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

    42、编写多线程程序有几种实现方式?

    答:一种是继承Thread类;另一种是实现Runnable接口。两种方式都要通过重写run()方法来定义线程的行为,推荐使用后者,因为Java中的继承是单继承,一个类有一个父类,如果继承了Thread类就无法再继承其他类了,显然使用Runnable接口更为灵活。

    43、synchronized关键字的用法?

    答:synchronized关键字可以将对象或者方法标记为同步,以实现对对象和方法的互斥访问,可以用synchronized(对象) { … }定义同步代码块,或者在声明方法时将synchronized作为方法的修饰符。

    44、举例说明同步和异步。

    答:如果系统中存在临界资源(资源数量少于竞争资源的线程数量的资源),例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就必须进行同步存取(数据库操作中的排他锁就是最好的例子)。当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。事实上,所谓的同步就是指阻塞式操作,而异步就是非阻塞式操作。

    45、简述synchronized 和java.util.concurrent.locks.Lock的异同?
    答:Lock是Java 5以后引入的新的API,和关键字synchronized相比主要相同点:Lock 能完成synchronized所实现的所有功能;主要不同点:Lock有比synchronized更精确的线程语义和更好的性能,而且不强制性的要求一定要获得锁。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且最好在finally 块中释放(这是释放外部资源的最好的地方)

    46、事务的ACID是指什么?
    答:
    - 原子性(Atomic):事务中各项操作,要么全做要么全不做,任何一项操作的失败都会导致整个事务的失败;
    - 一致性(Consistent):事务结束后系统状态是一致的;
    - 隔离性(Isolated):并发执行的事务彼此无法看到对方的中间状态;
    - 持久性(Durable):事务完成后所做的改动都会被持久化,即使发生灾难性的失败。通过日志和同步备份可以在故障发生后重建数据。

    47、获得一个类的类对象有哪些方式?
    答:
    - 方法1:类型.class,例如:String.class
    - 方法2:对象.getClass(),例如:”hello”.getClass()

    - 方法3:Class.forName(),例如:Class.forName(“java.lang.String”)

    48、简述一下面向对象的”六原则一法则”。
    答:

    - 单一职责原则:一个类只做它该做的事情。(单一职责原则想表达的就是”高内聚”,写代码最终极的原则只有六个字”高内聚、低耦合”,就如同葵花宝典或辟邪剑谱的中心思想就八个字”欲练此功必先自宫”,所谓的高内聚就是一个代码模块只完成一项功能,在面向对象中,如果只让一个类完成它该做的事,而不涉及与它无关的领域就是践行了高内聚的原则,这个类就只有单一职责。我们都知道一句话叫”因为专注,所以专业”,一个对象如果承担太多的职责,那么注定它什么都做不好。这个世界上任何好的东西都有两个特征,一个是功能单一,好的相机绝对不是电视购物里面卖的那种一个机器有一百多种功能的,它基本上只能照相;另一个是模块化,好的自行车是组装车,从减震叉、刹车到变速器,所有的部件都是可以拆卸和重新组装的,好的乒乓球拍也不是成品拍,一定是底板和胶皮可以拆分和自行组装的,一个好的软件系统,它里面的每个功能模块也应该是可以轻易的拿到其他系统中使用的,这样才能实现软件复用的目标。)

    - 开闭原则:软件实体应当对扩展开放,对修改关闭。(在理想的状态下,当我们需要为一个软件系统增加新功能时,只需要从原来的系统派生出一些新类就可以,不需要修改原来的任何一行代码。要做到开闭有两个要点:①抽象是关键,一个系统中如果没有抽象类或接口系统就没有扩展点;②封装可变性,将系统中的各种可变因素封装到一个继承结构中,如果多个可变因素混杂在一起,系统将变得复杂

    - 依赖倒转原则:面向接口编程。

    里氏替换原则:任何时候都可以用子类型替换掉父类型。(关于里氏替换原则的描述,Barbara Liskov女士的描述比这个要复杂得多,但简单的说就是能用父类型的地方就一定能使用子类型。里氏替换原则可以检查继承关系是否合理,如果一个继承关系违背了里氏替换原则,那么这个继承关系一定是错误的,需要对代码进行重构。例如让猫继承狗,或者狗继承猫,又或者让正方形继承长方形都是错误的继承关系,因为你很容易找到违反里氏替换原则的场景。需要注意的是:子类一定是增加父类的能力而不是减少父类的能力,因为子类比父类的能力更多,把能力多的对象当成能力少的对象来用当然没有任何问题。)

    - 接口隔离原则:接口要小而专,绝不能大而全。(臃肿的接口是对接口的污染,既然接口表示能力,那么一个接口只应该描述一种能力,接口也应该是高度内聚的。例如,琴棋书画就应该分别设计为四个接口,而不应设计成一个接口中的四个方法,因为如果设计成一个接口中的四个方法,那么这个接口很难用,毕竟琴棋书画四样都精通的人还是少数,而如果设计成四个接口,会几项就实现几个接口,这样的话每个接口被复用的可能性是很高的。Java中的接口代表能力、代表约定、代表角色,能否正确的使用接口一定是编程水平高低的重要标识。)

    - 合成聚合复用原则:优先使用聚合或合成关系复用代码。

    - 迪米特法则:迪米特法则又叫最少知识原则,一个对象应当对其他对象有尽可能少的了解。(迪米特法则简单的说就是如何做到”低耦合”,门面模式和调停者模式就是对迪米特法则的践行。对于门面模式可以举一个简单的例子,你去一家公司洽谈业务,你不需要了解这个公司内部是如何运作的,你甚至可以对这个公司一无所知,去的时候只需要找到公司入口处的前台美女,告诉她们你要做什么,她们会找到合适的人跟你接洽,前台的美女就是公司这个系统的门面。再复杂的系统都可以为用户提供一个简单的门面,Java Web开发中作为前端控制器的Servlet或Filter不就是一个门面吗,浏览器对服务器的运作方式一无所知,但是通过前端控制器就能够根据你的请求得到相应的服务。调停者模式也可以举一个简单的例子来说明,例如一台计算机,CPU、内存、硬盘、显卡、声卡各种设备需要相互配合才能很好的工作,但是如果这些东西都直接连接到一起,计算机的布线将异常复杂,在这种情况下,主板作为一个调停者的身份出现,它将各个设备连接在一起而不需要每个设备之间直接交换数据,这样就减小了系统的耦合度和复杂度,如下图所示。迪米特法则用通俗的话来将就是不要和陌生人打交道,如果真的需要,找一个自己的朋友,让他替你和陌生人打交道。)

    49、简述一下你了解的设计模式。

    答:所谓设计模式,就是一套被反复使用的代码设计经验的总结(情境中一个问题经过证实的一个解决方案)。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性

    几个常用的设计模式:

    - 工厂模式:工厂类可以根据条件生成不同的子类实例,这些子类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作(多态方法)。当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例。

    - 代理模式:给一个对象提供一个代理对象,并由代理对象控制原对象的引用。

    - 适配器模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起使用的类能够一起工作。

    单例模式:一个类只有一个实例,即一个类只有一个对象实例。

    懒汉式单例模式,线程不安全,致命的是在多线程不能正常工作

    public class Singleton {
        private static Singleton instance = null;
        private Singleton() {}
        public static synchronized Singleton getInstance(){
            if (instance == null) instance = new Singleton();
            return instance;
        }
    }

    饿汉式单例模式,避免了多线程的同步问题

    public class Singleton {
        private Singleton(){}
        private static Singleton instance = new Singleton();
        public static Singleton getInstance(){
            return instance;
        }
    }

    50、用Java写一个冒泡排序。

    for(int i=0;i<arr.length-1;i++){//外层循环控制排序趟数
          for(int j=0;j<arr.length-1-i;j++){//内层循环控制每一趟排序多少次
            if(arr[j]>arr[j+1]){
              int temp=arr[j];
              arr[j]=arr[j+1];
              arr[j+1]=temp;
            }
          }
        } 

    51、用Java写一个二分查找。

    递归实现:

    public static int biSearch(int []array,int a){
            int lo=0;
            int hi=array.length-1;
            int mid;
            while(lo<=hi){
                mid=(lo+hi)/2;
                if(array[mid]==a){
                    return mid+1;
                }else if(array[mid]<a){
                    lo=mid+1;
                }else{
                    hi=mid-1;
                }
            }
            return -1;
        }
    递归实现:
    public static int sort(int []array,int a,int lo,int hi){
            if(lo<=hi){
                int mid=(lo+hi)/2;
                if(a==array[mid]){
                    return mid+1;
                }
                else if(a>array[mid]){
                    return sort(array,a,mid+1,hi);
                }else{
                    return sort(array,a,lo,mid-1);
                }
            }
            return -1;
        }

    52、Servlet的运行过程? 

    Web容器加载Servlet并将其实例化后,Servlet生命周期开始,容器运行其init()方法进行Servlet的初始化;请求到达时调用Servlet的service()方法,service()方法会根据需要调用与请求对应的doGet或doPost等方法;当服务器关闭或项目被卸载时服务器会将Servlet实例销毁,此时会调用Servlet的destroy()方法。

    53、转发(forward)和重定向(redirect)的区别? 
    答:forward是容器中控制权的转向,是服务器请求资源,服务器直接访问目标地址的URL,把那个URL 的响应内容读取过来,然后把这些内容再发给浏览器,浏览器根本不知道服务器发送的内容是从哪儿来的,所以它的地址栏中还是原来的地址。redirect就是服务器端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址,因此从浏览器的地址栏中可以看到跳转后的链接地址,很明显redirect无法访问到服务器保护起来资源,但是可以从一个网站redirect到其他网站。forward更加高效,所以在满足需要时尽量使用forward(通过调用RequestDispatcher对象的forward()方法,该对象可以通过ServletRequest对象的getRequestDispatcher()方法获得),并且这样也有助于隐藏实际的链接;在有些情况下,比如需要访问一个其它服务器上的资源,则必须使用重定向(通过HttpServletResponse对象调用其sendRedirect()方法实现)。

    54、JSP有哪些内置对象?作用分别是什么? 
    答:JSP有9个内置对象: 
    - request:封装客户端的请求,其中包含来自GET或POST请求的参数; 
    - response:封装服务器对客户端的响应; 
    - pageContext:通过该对象可以获取其他对象; 
    - session:封装用户会话的对象; 
    - application:封装服务器运行环境的对象; 
    - out:输出服务器响应的输出流对象; 
    - config:Web应用的配置对象; 
    - page:JSP页面本身(相当于Java程序中的this); 
    - exception:封装页面抛出异常的对象。

    55、讲解JSP中的四种作用域。 
    答:JSP中的四种作用域包括page、request、session和application,具体来说: 
    - page代表与一个页面相关的对象和属性。 
    - request代表与Web客户机发出的一个请求相关的对象和属性。一个请求可能跨越多个页面,涉及多个Web组件;需要在页面显示的临时数据可以置于此作用域。 
    - session代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的session中。 

    - application代表与整个Web应用程序相关的对象和属性,它实质上是跨越整个Web应用程序,包括多个页面、请求和会话的一个全局作用域。


    展开全文
  • 经典Java面试题汇总及答案解析

    千次阅读 2018-12-13 09:53:30
    Java是一个支持并发、基于类和面向对象的计算机编程语言。下面列出了面向对象软件开发的优点: 代码开发模块化,更易维护和修改。 代码复用。 增强代码的可靠性和灵活性。 增加代码的可理解性。 面向对象编程有...

    面向对象编程(OOP)

    Java是一个支持并发、基于类和面向对象的计算机编程语言。下面列出了面向对象软件开发的优点:

    • 代码开发模块化,更易维护和修改。
    • 代码复用。
    • 增强代码的可靠性和灵活性。
    • 增加代码的可理解性。

    面向对象编程有很多重要的特性,比如:封装,继承,多态和抽象。下面的章节我们会逐个分析这些特性。

    封装

    封装给对象提供了隐藏内部特性和行为的能力。对象提供一些能被其他对象访问的方法来改变它内部的数据。在Java当中,有3种修饰符:public,private和protected。每一种修饰符给其他的位于同一个包或者不同包下面对象赋予了不同的访问权限。

    下面列出了使用封装的一些好处:

    • 通过隐藏对象的属性来保护对象内部的状态。
    • 提高了代码的可用性和可维护性,因为对象的行为可以被单独的改变或者是扩展。
    • 禁止对象之间的不良交互提高模块化。

    参考这个文档获取更多关于封装的细节和示例。

    多态

    多态是编程语言给不同的底层数据类型做相同的接口展示的一种能力。一个多态类型上的操作可以应用到其他类型的值上面。

    继承

    继承给对象提供了从基类获取字段和方法的能力。继承提供了代码的重用行,也可以在不修改类的情况下给现存的类添加新特性。

    抽象

    抽象是把想法从具体的实例中分离出来的步骤,因此,要根据他们的功能而不是实现细节来创建类。Java支持创建只暴漏接口而不包含方法实现的抽象的类。这种抽象技术的主要目的是把类的行为和实现细节分离开。

    抽象和封装的不同点

    抽象和封装是互补的概念。一方面,抽象关注对象的行为。另一方面,封装关注对象行为的细节。一般是通过隐藏对象内部状态信息做到封装,因此,封装可以看成是用来提供抽象的一种策略。

     

    常见的Java问题

    1.什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?

    Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。

    Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。

     

    2.JDK和JRE的区别是什么?

    Java运行时环境(JRE)是将要执行Java程序的Java虚拟机。它同时也包含了执行applet需要的浏览器插件。Java开发工具包(JDK)是完整的Java软件开发包,包含了JRE,编译器和其他的工具(比如:JavaDoc,Java调试器),可以让开发者开发、编译、执行Java应用程序。

     

    3.”static”关键字是什么意思?Java中是否可以覆盖(override)一个private或者是static的方法?

    “static”关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例变量的情况下被访问。

    Java中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何实例都不相关,所以概念上不适用。

     

    4.是否可以在static环境中访问非static变量?

    static变量在Java中是属于类的,它在所有的实例中的值是一样的。当类被Java虚拟机载入的时候,会对static变量进行初始化。如果你的代码尝试不用实例来访问非static的变量,编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例关联上。

     

    5.Java支持的数据类型有哪些?什么是自动拆装箱?

    Java语言支持的8中基本数据类型是:

    • byte
    • short
    • int
    • long
    • float
    • double
    • boolean
    • char

    自动装箱是Java编译器在基本数据类型和对应的对象包装类型之间做的一个转化。比如:把int转化成Integer,double转化成double,等等。反之就是自动拆箱。

     

    6.Java中的方法覆盖(Overriding)和方法重载(Overloading)是什么意思?

    Java中的方法重载发生在同一个类里面两个或者是多个方法的方法名相同但是参数不同的情况。与此相对,方法覆盖是说子类重新定义了父类的方法。方法覆盖必须有相同的方法名,参数列表和返回类型。覆盖者可能不会限制它所覆盖的方法的访问。

     

    7.Java中,什么是构造函数?什么是构造函数重载?什么是复制构造函数?

    当新对象被创建的时候,构造函数会被调用。每一个类都有构造函数。在程序员没有给类提供构造函数的情况下,Java编译器会为这个类创建一个默认的构造函数。

    Java中构造函数重载和方法重载很相似。可以为一个类创建多个构造函数。每一个构造函数必须有它自己唯一的参数列表。

    Java不支持像C++中那样的复制构造函数,这个不同点是因为如果你不自己写构造函数的情况下,Java不会创建默认的复制构造函数。

     

    8.Java支持多继承么?

    不支持,Java不支持多继承。每个类都只能继承一个类,但是可以实现多个接口。

     

    9.接口和抽象类的区别是什么?

    Java提供和支持创建抽象类和接口。它们的实现有共同点,不同点在于:

    • 接口中所有的方法隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。
    • 类可以实现很多个接口,但是只能继承一个抽象类
    • 类如果要实现一个接口,它必须要实现接口声明的所有方法。但是,类可以不实现抽象类声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。
    • 抽象类可以在不提供接口方法实现的情况下实现接口。
    • Java接口中声明的变量默认都是final的。抽象类可以包含非final的变量。
    • Java接口中的成员函数默认是public的。抽象类的成员函数可以是private,protected或者是public。
    • 接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含main方法的话是可以被调用的。

    也可以参考JDK8中抽象类和接口的区别

     

    10.什么是值传递和引用传递?

    对象被值传递,意味着传递了对象的一个副本。因此,就算是改变了对象副本,也不会影响源对象的值。

    对象被引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象所做的改变会反映到所有的对象上。

     

    Java线程

    11.进程和线程的区别是什么?

    进程是执行着的应用程序,而线程是进程内部的一个执行序列。一个进程可以有多个线程。线程又叫做轻量级进程。

     

    12.创建线程有几种不同的方式?你喜欢哪一种?为什么?

    有三种方式可以用来创建线程:

    • 继承Thread类
    • 实现Runnable接口
    • 应用程序可以使用Executor框架来创建线程池

    实现Runnable接口这种方式更受欢迎,因为这不需要继承Thread类。在应用设计中已经继承了别的对象的情况下,这需要多继承(而Java不支持多继承),只能实现接口。同时,线程池也是非常高效的,很容易实现和使用。

     

    13.概括的解释下线程的几种可用状态。

    线程在执行过程中,可以处于下面几种状态:

    • 就绪(Runnable):线程准备运行,不一定立马就能开始执行。
    • 运行中(Running):进程正在执行线程的代码。
    • 等待中(Waiting):线程处于阻塞的状态,等待外部的处理结束。
    • 睡眠中(Sleeping):线程被强制睡眠。
    • I/O阻塞(Blocked on I/O):等待I/O操作完成。
    • 同步阻塞(Blocked on Synchronization):等待获取锁。
    • 死亡(Dead):线程完成了执行。

     

    14.同步方法和同步代码块的区别是什么?

    在Java语言中,每一个对象有一把锁。线程可以使用synchronized关键字来获取对象上的锁。synchronized关键字可应用在方法级别(粗粒度锁)或者是代码块级别(细粒度锁)。

     

    15.在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步?

    监视器和锁在Java虚拟机中是一块使用的。监视器监视一块同步代码块,确保一次只有一个线程执行同步代码块。每一个监视器都和一个对象引用相关联。线程在获取锁之前不允许执行同步代码。

     

    16.什么是死锁(deadlock)?

    两个进程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是两个进程都陷入了无限的等待中。

     

    17.如何确保N个线程可以访问N个资源同时又不导致死锁?

    使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。

     

    Java集合类

    18.Java集合类框架的基本接口有哪些?

    集合类接口指定了一组叫做元素的对象。集合类接口的每一种具体的实现类都可以选择以它自己的方式对元素进行保存和排序。有的集合类允许重复的键,有些不允许。

    Java集合类提供了一套设计良好的支持对一组对象进行操作的接口和类。Java集合类里面最基本的接口有:

    • Collection:代表一组对象,每一个对象都是它的子元素。
    • Set:不包含重复元素的Collection。
    • List:有顺序的collection,并且可以包含重复元素。
    • Map:可以把键(key)映射到值(value)的对象,键不能重复。

     

    19.为什么集合类没有实现Cloneable和Serializable接口?

    克隆(cloning)或者是序列化(serialization)的语义和含义是跟具体的实现相关的。因此,应该由集合类的具体实现来决定如何被克隆或者是序列化。

     

    20.什么是迭代器(Iterator)?

    Iterator接口提供了很多对集合元素进行迭代的方法。每一个集合类都包含了可以返回迭代器实例的

    迭代方法。迭代器可以在迭代的过程中删除底层集合的元素。

     

    21.Iterator和ListIterator的区别是什么?

    下面列出了他们的区别:

    • Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
    • Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
    • ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。

     

    22.快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?

    Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。java.util包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。快速失败的迭代器会抛出

    ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。

     

    23.Java中的HashMap的工作原理是什么?

    Java中的HashMap是以键值对(key-value)的形式存储元素的。HashMap需要一个hash函数,它使用hashCode()和equals()方法来向集合/从集合添加和检索元素。当调用put()方法的时候,HashMap会计算key的hash值,然后把键值对存储在集合中合适的索引上。如果key已经存在了,value会被更新成新值。

    HashMap的一些重要的特性是它的容量(capacity),负载因子(load factor)和扩容极限(threshold resizing)。

     

    24.hashCode()和equals()方法的重要性体现在什么地方?

    Java中的HashMap使用hashCode()和equals()方法来确定键值对的索引,当根据键获取值的时候也会用到这两个方法。如果没有正确的实现这两个方法,两个不同的键可能会有相同的hash值,因此,可能会被集合认为是相等的。而且,这两个方法也用来发现重复元素。所以这两个方法的实现对HashMap的精确性和正确性是至关重要的。

     

    25.HashMap和Hashtable有什么区别?

    HashMap和Hashtable都实现了Map接口,因此很多特性非常相似。但是,他们有以下不同点:

    • HashMap允许键和值是null,而Hashtable不允许键或者值是null。
    • Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。
    • HashMap提供了可供应用迭代的键的集合,因此,HashMap是快速失败的。另一方面,Hashtable提供了对键的列举(Enumeration)。
      • 一般认为Hashtable是一个遗留的类。

     

    26.数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用Array而不是ArrayList?

    下面列出了Array和ArrayList的不同点:

    • Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
    • Array大小是固定的,ArrayList的大小是动态变化的。
    • ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。
    • 对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。

     

    27.ArrayList和LinkedList有什么区别?

    ArrayList和LinkedList都实现了List接口,他们有以下的不同点:

    • ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。
    • 相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。
    • LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。

    也可以参考ArrayList vs. LinkedList。

     

    28.Comparable和Comparator接口是干什么的?列出它们的区别。

    Java提供了只包含一个compareTo()方法的Comparable接口。这个方法可以个给两个对象排序。具体来说,它返回负数,0,正数来表明输入对象小于,等于,大于已经存在的对象。

    Java提供了包含compare()和equals()两个方法的Comparator接口。compare()方法用来给两个输入参数排序,返回负数,0,正数表明第一个参数是小于,等于,大于第二个参数。equals()方法需要一个对象作为参数,它用来决定输入参数是否和comparator相等。只有当输入参数也是一个comparator并且输入参数和当前comparator的排序结果是相同的时候,这个方法才返回true。

     

    29.什么是Java优先级队列(Priority Queue)?

    PriorityQueue是一个基于优先级堆的无界队列,它的元素是按照自然顺序(natural order)排序的。在创建的时候,我们可以给它提供一个负责给元素排序的比较器。PriorityQueue不允许null值,因为他们没有自然顺序,或者说他们没有任何的相关联的比较器。最后,PriorityQueue不是线程安全的,入队和出队的时间复杂度是O(log(n))。

     

    30.你了解大O符号(big-O notation)么?你能给出不同数据结构的例子么?

    大O符号描述了当数据结构里面的元素增加的时候,算法的规模或者是性能在最坏的场景下有多么好。

    大O符号也可用来描述其他的行为,比如:内存消耗。因为集合类实际上是数据结构,我们一般使用大O符号基于时间,内存和性能来选择最好的实现。大O符号可以对大量数据的性能给出一个很好的说明。

     

    31.如何权衡是使用无序的数组还是有序的数组?

    有序数组最大的好处在于查找的时间复杂度是O(log n),而无序数组是O(n)。有序数组的缺点是插入操作的时间复杂度是O(n),因为值大的元素需要往后移动来给新元素腾位置。相反,无序数组的插入时间复杂度是常量O(1)。

     

    32.Java集合类框架的最佳实践有哪些?

    根据应用的需要正确选择要使用的集合的类型对性能非常重要,比如:假如元素的大小是固定的,而且能事先知道,我们就应该用Array而不是ArrayList。

    有些集合类允许指定初始容量。因此,如果我们能估计出存储的元素的数目,我们可以设置初始容量来避免重新计算hash值或者是扩容。

    为了类型安全,可读性和健壮性的原因总是要使用泛型。同时,使用泛型还可以避免运行时的ClassCastException。

    使用JDK提供的不变类(immutable class)作为Map的键可以避免为我们自己的类实现hashCode()和equals()方法。

    编程的时候接口优于实现。

    底层的集合实际上是空的情况下,返回长度是0的集合或者是数组,不要返回null。

     

    33.Enumeration接口和Iterator接口的区别有哪些?

    Enumeration速度是Iterator的2倍,同时占用更少的内存。但是,Iterator远远比Enumeration安全,因为其他线程不能够修改正在被iterator遍历的集合里面的对象。同时,Iterator允许调用者删除底层集合里面的元素,这对Enumeration来说是不可能的。

     

    34.HashSet和TreeSet有什么区别?

    HashSet是由一个hash表来实现的,因此,它的元素是无序的。add(),remove(),contains()方法的时间复杂度是O(1)。

    另一方面,TreeSet是由一个树形的结构来实现的,它里面的元素是有序的。因此,add(),remove(),contains()方法的时间复杂度是O(logn)。

     

    垃圾收集器(Garbage Collectors)

     

    35.Java中垃圾回收有什么目的?什么时候进行垃圾回收?

    垃圾回收的目的是识别并且丢弃应用不再使用的对象来释放和重用资源。

     

    36.System.gc()和Runtime.gc()会做什么事情?

    这两个方法用来提示JVM要进行垃圾回收。但是,立即开始还是延迟进行垃圾回收是取决于JVM的。

     

    37.finalize()方法什么时候被调用?析构函数(finalization)的目的是什么?

    在释放对象占用的内存之前,垃圾收集器会调用对象的finalize()方法。一般建议在该方法中释放对象持有的资源。

     

    38.如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存?

    不会,在下一个垃圾回收周期中,这个对象将是可被回收的。

     

    39.Java堆的结构是什么样子的?什么是堆中的永久代(Perm Gen space)?

    JVM的堆是运行时数据区,所有类的实例和数组都是在堆上分配内存。它在JVM启动的时候被创建。对象所占的堆内存是由自动内存管理系统也就是垃圾收集器回收。

    堆内存是由存活和死亡的对象组成的。存活的对象是应用可以访问的,不会被垃圾回收。死亡的对象是应用不可访问尚且还没有被垃圾收集器回收掉的对象。一直到垃圾收集器把这些对象回收掉之前,他们会一直占据堆内存空间。

     

    40.串行(serial)收集器和吞吐量(throughput)收集器的区别是什么?

    吞吐量收集器使用并行版本的新生代垃圾收集器,它用于中等规模和大规模数据的应用程序。而串行收集器对大多数的小应用(在现代处理器上需要大概100M左右的内存)就足够了。

     

    41.在Java中,对象什么时候可以被垃圾回收?

    当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被回收了。

     

    42.JVM的永久代中会发生垃圾回收么?

    垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。请参考下Java8:从永久代到元数据区

    (译者注:Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区)

     

    异常处理

    43.Java中的两种异常类型是什么?他们有什么区别?

    Java中有两种异常:受检查的(checked)异常和不受检查的(unchecked)异常。不受检查的异常不需要在方法或者是构造函数上声明,就算方法或者是构造函数的执行可能会抛出这样的异常,并且不受检查的异常可以传播到方法或者是构造函数的外面。相反,受检查的异常必须要用throws语句在方法或者是构造函数上声明。这里有Java异常处理的一些小建议。

     

    44.Java中Exception和Error有什么区别?

    Exception和Error都是Throwable的子类。Exception用于用户程序可以捕获的异常情况。Error定义了不期望被用户程序捕获的异常。

     

    45.throw和throws有什么区别?

    throw关键字用来在程序中明确的抛出异常,相反,throws语句用来表明方法不能处理的异常。每一个方法都必须要指定哪些异常不能处理,所以方法的调用者才能够确保处理可能发生的异常,多个异常是用逗号分隔的。

     

    45.异常处理的时候,finally代码块的重要性是什么?(译者注:作者标题的序号弄错了)

    无论是否抛出异常,finally代码块总是会被执行。就算是没有catch语句同时又抛出异常的情况下,finally代码块仍然会被执行。最后要说的是,finally代码块主要用来释放资源,比如:I/O缓冲区,数据库连接。

     

    46.异常处理完成以后,Exception对象会发生什么变化?

    Exception对象会在下一个垃圾回收过程中被回收掉。

     

    47.finally代码块和finalize()方法有什么区别?

    无论是否抛出异常,finally代码块都会执行,它主要是用来释放应用占用的资源。finalize()方法是Object类的一个protected方法,它是在对象被垃圾回收之前由Java虚拟机来调用的。

     

    Java小应用程序(Applet)

    48.什么是Applet?

    java applet是能够被包含在HTML页面中并且能被启用了java的客户端浏览器执行的程序。Applet主要用来创建动态交互的web应用程序。

     

    49.解释一下Applet的生命周期

    applet可以经历下面的状态:

    • Init:每次被载入的时候都会被初始化。
    • Start:开始执行applet。
    • Stop:结束执行applet。
    • Destroy:卸载applet之前,做最后的清理工作。

     

    50.当applet被载入的时候会发生什么?

    首先,创建applet控制类的实例,然后初始化applet,最后开始运行。

     

    51.Applet和普通的Java应用程序有什么区别?

    applet是运行在启用了java的浏览器中,Java应用程序是可以在浏览器之外运行的独立的Java程序。但是,它们都需要有Java虚拟机。

    进一步来说,Java应用程序需要一个有特定方法签名的main函数来开始执行。Java applet不需要这样的函数来开始执行。

    最后,Java applet一般会使用很严格的安全策略,Java应用一般使用比较宽松的安全策略。

     

    52.Java applet有哪些限制条件?

    主要是由于安全的原因,给applet施加了以下的限制:

    • applet不能够载入类库或者定义本地方法。
    • applet不能在宿主机上读写文件。
    • applet不能读取特定的系统属性。
    • applet不能发起网络连接,除非是跟宿主机。
    • applet不能够开启宿主机上其他任何的程序。

     

    53.什么是不受信任的applet?

    不受信任的applet是不能访问或是执行本地系统文件的Java applet,默认情况下,所有下载的applet都是不受信任的。

     

    54.从网络上加载的applet和从本地文件系统加载的applet有什么区别?

    当applet是从网络上加载的时候,applet是由applet类加载器载入的,它受applet安全管理器的限制。

    当applet是从客户端的本地磁盘载入的时候,applet是由文件系统加载器载入的。

    从文件系统载入的applet允许在客户端读文件,写文件,加载类库,并且也允许执行其他程序,但是,却通不过字节码校验。

     

    55.applet类加载器是什么?它会做哪些工作?

    当applet是从网络上加载的时候,它是由applet类加载器载入的。类加载器有自己的java名称空间等级结构。类加载器会保证来自文件系统的类有唯一的名称空间,来自网络资源的类有唯一的名称空间。

    当浏览器通过网络载入applet的时候,applet的类被放置于和applet的源相关联的私有的名称空间中。然后,那些被类加载器载入进来的类都是通过了验证器验证的。验证器会检查类文件格式是否遵守Java语言规范,确保不会出现堆栈溢出(stack overflow)或者下溢(underflow),传递给字节码指令的参数是正确的。

     

    56.applet安全管理器是什么?它会做哪些工作?

    applet安全管理器是给applet施加限制条件的一种机制。浏览器可以只有一个安全管理器。安全管理器在启动的时候被创建,之后不能被替换覆盖或者是扩展。

     

    Swing

    57.弹出式选择菜单(Choice)和列表(List)有什么区别

    Choice是以一种紧凑的形式展示的,需要下拉才能看到所有的选项。Choice中一次只能选中一个选项。List同时可以有多个元素可见,支持选中一个或者多个元素。

     

    58.什么是布局管理器?

    布局管理器用来在容器中组织组件。

     

    59.滚动条(Scrollbar)和滚动面板(JScrollPane)有什么区别?

    Scrollbar是一个组件,不是容器。而ScrollPane是容器。ScrollPane自己处理滚动事件。

     

    60.哪些Swing的方法是线程安全的?

    只有3个线程安全的方法: repaint(), revalidate(), and invalidate()。

     

    61.说出三种支持重绘(painting)的组件。

    Canvas, Frame, Panel,和Applet支持重绘。

     

    62.什么是裁剪(clipping)?

    限制在一个给定的区域或者形状的绘图操作就做裁剪。

     

    63.MenuItem和CheckboxMenuItem的区别是什么?

    CheckboxMenuItem类继承自MenuItem类,支持菜单选项可以选中或者不选中。

     

    64.边缘布局(BorderLayout)里面的元素是如何布局的?

    BorderLayout里面的元素是按照容器的东西南北中进行布局的。

     

    65.网格包布局(GridBagLayout)里面的元素是如何布局的?

    GridBagLayout里面的元素是按照网格进行布局的。不同大小的元素可能会占据网格的多于1行或一列。因此,行数和列数可以有不同的大小。

     

    66.Window和Frame有什么区别?

    Frame类继承了Window类,它定义了一个可以有菜单栏的主应用窗口。

     

    67.裁剪(clipping)和重绘(repainting)有什么联系?

    当窗口被AWT重绘线程进行重绘的时候,它会把裁剪区域设置成需要重绘的窗口的区域。

     

    68.事件监听器接口(event-listener interface)和事件适配器(event-adapter)有什么关系?

    事件监听器接口定义了对特定的事件,事件处理器必须要实现的方法。事件适配器给事件监听器接口提供了默认的实现。

     

    69.GUI组件如何来处理它自己的事件?

    GUI组件可以处理它自己的事件,只要它实现相对应的事件监听器接口,并且把自己作为事件监听器。

     

    70.Java的布局管理器比传统的窗口系统有哪些优势?

    Java使用布局管理器以一种一致的方式在所有的窗口平台上摆放组件。因为布局管理器不会和组件的绝对大小和位置相绑定,所以他们能够适应跨窗口系统的特定平台的不同。

     

    71.Java的Swing组件使用了哪种设计模式?

    Java中的Swing组件使用了MVC(视图-模型-控制器)设计模式。

     

    JDBC

     

    72.什么是JDBC?

    JDBC是允许用户在不同数据库之间做选择的一个抽象层。JDBC允许开发者用JAVA写数据库应用程序,而不需要关心底层特定数据库的细节。

     

    73.解释下驱动(Driver)在JDBC中的角色。

    JDBC驱动提供了特定厂商对JDBC API接口类的实现,驱动必须要提供java.sql包下面这些类的实现:Connection, Statement, PreparedStatement,CallableStatement, ResultSet和Driver。

     

    74.Class.forName()方法有什么作用?

    这个方法用来载入跟数据库建立连接的驱动。

     

    75.PreparedStatement比Statement有什么优势?

    PreparedStatements是预编译的,因此,性能会更好。同时,不同的查询参数值,PreparedStatement可以重用。

     

    76.什么时候使用CallableStatement?用来准备CallableStatement的方法是什么?

    CallableStatement用来执行存储过程。存储过程是由数据库存储和提供的。存储过程可以接受输入参数,也可以有返回结果。非常鼓励使用存储过程,因为它提供了安全性和模块化。准备一个CallableStatement的方法是:

    1 CallableStament.prepareCall();

     

    77.数据库连接池是什么意思?

    像打开关闭数据库连接这种和数据库的交互可能是很费时的,尤其是当客户端数量增加的时候,会消耗大量的资源,成本是非常高的。可以在应用服务器启动的时候建立很多个数据库连接并维护在一个池中。连接请求由池中的连接提供。在连接使用完毕以后,把连接归还到池中,以用于满足将来更多的请求。

    远程方法调用(RMI)

     

    78.什么是RMI?

    Java远程方法调用(Java RMI)是Java API对远程过程调用(RPC)提供的面向对象的等价形式,支持直接传输序列化的Java对象和分布式垃圾回收。远程方法调用可以看做是激活远程正在运行的对象上的方法的步骤。RMI对调用者是位置透明的,因为调用者感觉方法是执行在本地运行的对象上的。看下RMI的一些注意事项。

     

    79.RMI体系结构的基本原则是什么?

    RMI体系结构是基于一个非常重要的行为定义和行为实现相分离的原则。RMI允许定义行为的代码和实现行为的代码相分离,并且运行在不同的JVM上。

     

    80.RMI体系结构分哪几层?

    RMI体系结构分以下几层:

    存根和骨架层(Stub and Skeleton layer):这一层对程序员是透明的,它主要负责拦截客户端发出的方法调用请求,然后把请求重定向给远程的RMI服务。

    远程引用层(Remote Reference Layer):RMI体系结构的第二层用来解析客户端对服务端远程对象的引用。这一层解析并管理客户端对服务端远程对象的引用。连接是点到点的。

    传输层(Transport layer):这一层负责连接参与服务的两个JVM。这一层是建立在网络上机器间的TCP/IP连接之上的。它提供了基本的连接服务,还有一些防火墙穿透策略。

     

    81.RMI中的远程接口(Remote Interface)扮演了什么样的角色?

    远程接口用来标识哪些方法是可以被非本地虚拟机调用的接口。远程对象必须要直接或者是间接实现远程接口。实现了远程接口的类应该声明被实现的远程接口,给每一个远程对象定义构造函数,给所有远程接口的方法提供实现。

     

    82.java.rmi.Naming类扮演了什么样的角色?

    java.rmi.Naming类用来存储和获取在远程对象注册表里面的远程对象的引用。Naming类的每一个方法接收一个URL格式的String对象作为它的参数。

     

    83.RMI的绑定(Binding)是什么意思?

    绑定是为了查询找远程对象而给远程对象关联或者是注册以后会用到的名称的过程。远程对象可以使用Naming类的bind()或者rebind()方法跟名称相关联。

     

    84.Naming类的bind()和rebind()方法有什么区别?

    bind()方法负责把指定名称绑定给远程对象,rebind()方法负责把指定名称重新绑定到一个新的远程对象。如果那个名称已经绑定过了,先前的绑定会被替换掉。

     

    85.让RMI程序能正确运行有哪些步骤?

    为了让RMI程序能正确运行必须要包含以下几个步骤:

    • 编译所有的源文件。
    • 使用rmic生成stub。
    • 启动rmiregistry。
    • 启动RMI服务器。
    • 运行客户端程序。

     

    86.RMI的stub扮演了什么样的角色?

    远程对象的stub扮演了远程对象的代表或者代理的角色。调用者在本地stub上调用方法,它负责在远程对象上执行方法。当stub的方法被调用的时候,会经历以下几个步骤:

    • 初始化到包含了远程对象的JVM的连接。
    • 序列化参数到远程的JVM。
    • 等待方法调用和执行的结果。
    • 反序列化返回的值或者是方法没有执行成功情况下的异常。
    • 把值返回给调用者。

     

    87.什么是分布式垃圾回收(DGC)?它是如何工作的?

    DGC叫做分布式垃圾回收。RMI使用DGC来做自动垃圾回收。因为RMI包含了跨虚拟机的远程对象的引用,垃圾回收是很困难的。DGC使用引用计数算法来给远程对象提供自动内存管理。

     

    88.RMI中使用RMI安全管理器(RMISecurityManager)的目的是什么?

    RMISecurityManager使用下载好的代码提供可被RMI应用程序使用的安全管理器。如果没有设置安全管理器,RMI的类加载器就不会从远程下载任何的类。

     

    89.解释下Marshalling和demarshalling。

    当应用程序希望把内存对象跨网络传递到另一台主机或者是持久化到存储的时候,就必须要把对象在内存里面的表示转化成合适的格式。这个过程就叫做Marshalling,反之就是demarshalling。

     

    90.解释下Serialization和Deserialization。

    Java提供了一种叫做对象序列化的机制,他把对象表示成一连串的字节,里面包含了对象的数据,对象的类型信息,对象内部的数据的类型信息等等。因此,序列化可以看成是为了把对象存储在磁盘上或者是从磁盘上读出来并重建对象而把对象扁平化的一种方式。反序列化是把对象从扁平状态转化成活动对象的相反的步骤。

     

    Servlet

    91.什么是Servlet?

    Servlet是用来处理客户端请求并产生动态网页内容的Java类。Servlet主要是用来处理或者是存储HTML表单提交的数据,产生动态内容,在无状态的HTTP协议下管理状态信息。

     

    92.说一下Servlet的体系结构。

    所有的Servlet都必须要实现的核心的接口是javax.servlet.Servlet。每一个Servlet都必须要直接或者是间接实现这个接口,或者是继承javax.servlet.GenericServlet或者javax.servlet.http.HTTPServlet。最后,Servlet使用多线程可以并行的为多个请求服务。

     

    93.Applet和Servlet有什么区别?

    Applet是运行在客户端主机的浏览器上的客户端Java程序。而Servlet是运行在web服务器上的服务端的组件。applet可以使用用户界面类,而Servlet没有用户界面,相反,Servlet是等待客户端的HTTP请求,然后为请求产生响应。

     

    94.GenericServlet和HttpServlet有什么区别?

    GenericServlet是一个通用的协议无关的Servlet,它实现了Servlet和ServletConfig接口。继承自GenericServlet的Servlet应该要覆盖service()方法。最后,为了开发一个能用在网页上服务于使用HTTP协议请求的Servlet,你的Servlet必须要继承自HttpServlet。这里有Servlet的例子。

     

    95.解释下Servlet的生命周期。

    对每一个客户端的请求,Servlet引擎载入Servlet,调用它的init()方法,完成Servlet的初始化。然后,Servlet对象通过为每一个请求单独调用service()方法来处理所有随后来自客户端的请求,最后,调用Servlet(译者注:这里应该是Servlet而不是server)的destroy()方法把Servlet删除掉。

     

    96.doGet()方法和doPost()方法有什么区别?

    doGet:GET方法会把名值对追加在请求的URL后面。因为URL对字符数目有限制,进而限制了用在客户端请求的参数值的数目。并且请求中的参数值是可见的,因此,敏感信息不能用这种方式传递。

    doPOST:POST方法通过把请求参数值放在请求体中来克服GET方法的限制,因此,可以发送的参数的数目是没有限制的。最后,通过POST请求传递的敏感信息对外部客户端是不可见的。

     

    97.什么是Web应用程序?

    Web应用程序是对Web或者是应用服务器的动态扩展。有两种类型的Web应用:面向表现的和面向服务的。面向表现的Web应用程序会产生包含了很多种标记语言和动态内容的交互的web页面作为对请求的响应。而面向服务的Web应用实现了Web服务的端点(endpoint)。一般来说,一个Web应用可以看成是一组安装在服务器URL名称空间的特定子集下面的Servlet的集合。

     

    98.什么是服务端包含(Server Side Include)?

    服务端包含(SSI)是一种简单的解释型服务端脚本语言,大多数时候仅用在Web上,用servlet标签嵌入进来。SSI最常用的场景把一个或多个文件包含到Web服务器的一个Web页面中。当浏览器访问Web页面的时候,Web服务器会用对应的servlet产生的文本来替换Web页面中的servlet标签。

     

    99.什么是Servlet链(Servlet Chaining)?

    Servlet链是把一个Servlet的输出发送给另一个Servlet的方法。第二个Servlet的输出可以发送给第三个Servlet,依次类推。链条上最后一个Servlet负责把响应发送给客户端。

     

    100.如何知道是哪一个客户端的机器正在请求你的Servlet?

    ServletRequest类可以找出客户端机器的IP地址或者是主机名。getRemoteAddr()方法获取客户端主机的IP地址,getRemoteHost()可以获取主机名。看下这里的例子。

     

    101.HTTP响应的结构是怎么样的?

    HTTP响应由三个部分组成:

    状态码(Status Code):描述了响应的状态。可以用来检查是否成功的完成了请求。请求失败的情况下,状态码可用来找出失败的原因。如果Servlet没有返回状态码,默认会返回成功的状态码HttpServletResponse.SC_OK。

    HTTP头部(HTTP Header):它们包含了更多关于响应的信息。比如:头部可以指定认为响应过期的过期日期,或者是指定用来给用户安全的传输实体内容的编码格式。如何在Serlet中检索HTTP的头部看这里。

    主体(Body):它包含了响应的内容。它可以包含HTML代码,图片,等等。主体是由传输在HTTP消息中紧跟在头部后面的数据字节组成的。

     

    102.什么是cookie?session和cookie有什么区别?

    cookie是Web服务器发送给浏览器的一块信息。浏览器会在本地文件中给每一个Web服务器存储cookie。以后浏览器在给特定的Web服务器发请求的时候,同时会发送所有为该服务器存储的cookie。下面列出了session和cookie的区别:

    • 无论客户端浏览器做怎么样的设置,session都应该能正常工作。客户端可以选择禁用cookie,但是,session仍然是能够工作的,因为客户端无法禁用服务端的session。
    • 在存储的数据量方面session和cookies也是不一样的。session能够存储任意的Java对象,cookie只能存储String类型的对象。

     

    103.浏览器和Servlet通信使用的是什么协议?

    浏览器和Servlet通信使用的是HTTP协议。

     

    104.什么是HTTP隧道?

    HTTP隧道是一种利用HTTP或者是HTTPS把多种网络协议封装起来进行通信的技术。因此,HTTP协议扮演了一个打通用于通信的网络协议的管道的包装器的角色。把其他协议的请求掩盖成HTTP的请求就是HTTP隧道。

     

    105.sendRedirect()和forward()方法有什么区别?

    sendRedirect()方法会创建一个新的请求,而forward()方法只是把请求转发到一个新的目标上。重定向(redirect)以后,之前请求作用域范围以内的对象就失效了,因为会产生一个新的请求,而转发(forwarding)以后,之前请求作用域范围以内的对象还是能访问的。一般认为sendRedirect()比forward()要慢。

     

    106.什么是URL编码和URL解码?

    URL编码是负责把URL里面的空格和其他的特殊字符替换成对应的十六进制表示,反之就是解码。

     

    JSP

     

    107.什么是JSP页面?

    JSP页面是一种包含了静态数据和JSP元素两种类型的文本的文本文档。静态数据可以用任何基于文本的格式来表示,比如:HTML或者XML。JSP是一种混合了静态内容和动态产生的内容的技术。这里看下JSP的例子。

     

    108.JSP请求是如何被处理的?

    浏览器首先要请求一个以.jsp扩展名结尾的页面,发起JSP请求,然后,Web服务器读取这个请求,使用JSP编译器把JSP页面转化成一个Servlet类。需要注意的是,只有当第一次请求页面或者是JSP文件发生改变的时候JSP文件才会被编译,然后服务器调用servlet类,处理浏览器的请求。一旦请求执行结束,servlet会把响应发送给客户端。这里看下如何在JSP中获取请求参数。

     

    109.JSP有什么优点?

    下面列出了使用JSP的优点:

    • JSP页面是被动态编译成Servlet的,因此,开发者可以很容易的更新展现代码。
    • JSP页面可以被预编译。
    • JSP页面可以很容易的和静态模板结合,包括:HTML或者XML,也可以很容易的和产生动态内容的代码结合起来。
    • 开发者可以提供让页面设计者以类XML格式来访问的自定义的JSP标签库。
    • 开发者可以在组件层做逻辑上的改变,而不需要编辑单独使用了应用层逻辑的页面。

     

    110.什么是JSP指令(Directive)?JSP中有哪些不同类型的指令?

    Directive是当JSP页面被编译成Servlet的时候,JSP引擎要处理的指令。Directive用来设置页面级别的指令,从外部文件插入数据,指定自定义的标签库。Directive是定义在 <%@ 和 %>之间的。下面列出了不同类型的Directive:

    • 包含指令(Include directive):用来包含文件和合并文件内容到当前的页面。
    • 页面指令(Page directive):用来定义JSP页面中特定的属性,比如错误页面和缓冲区。
    • Taglib指令: 用来声明页面中使用的自定义的标签库。

     

    111.什么是JSP动作(JSP action)?

    JSP动作以XML语法的结构来控制Servlet引擎的行为。当JSP页面被请求的时候,JSP动作会被执行。它们可以被动态的插入到文件中,重用JavaBean组件,转发用户到其他的页面,或者是给Java插件产生HTML代码。下面列出了可用的动作:

    • jsp:include-当JSP页面被请求的时候包含一个文件。
    • jsp:useBean-找出或者是初始化Javabean。
    • jsp:setProperty-设置JavaBean的属性。
    • jsp:getProperty-获取JavaBean的属性。
    • jsp:forward-把请求转发到新的页面。
    • jsp:plugin-产生特定浏览器的代码。

     

    112.什么是Scriptlets?

    JSP技术中,scriptlet是嵌入在JSP页面中的一段Java代码。scriptlet是位于标签内部的所有的东西,在标签与标签之间,用户可以添加任意有效的scriplet。

     

    113.声明(Decalaration)在哪里?

    声明跟Java中的变量声明很相似,它用来声明随后要被表达式或者scriptlet使用的变量。添加的声明必须要用开始和结束标签包起来。

     

    114.什么是表达式(Expression)?

    【列表很长,可以分上、中、下发布】

    JSP表达式是Web服务器把脚本语言表达式的值转化成一个String对象,插入到返回给客户端的数据流中。表达式是在<%=和%>这两个标签之间定义的。

     

    115.隐含对象是什么意思?有哪些隐含对象?

    JSP隐含对象是页面中的一些Java对象,JSP容器让这些Java对象可以为开发者所使用。开发者不用明确的声明就可以直接使用他们。JSP隐含对象也叫做预定义变量。下面列出了JSP页面中的隐含对象:

    • application
    • page
    • request
    • response
    • session
    • exception
    • out
    • config
    • pageContext

     

    其他

    ThreadLocal(线程变量副本)
    Synchronized实现内存共享,ThreadLocal为每个线程维护一个本地变量。
    采用空间换时间,它用于线程间的数据隔离,为每一个使用该变量的线程提供一个副本,每个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。
    ThreadLocal类中维护一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值为对应线程的变量副本。
    ThreadLocal在Spring中发挥着巨大的作用,在管理Request作用域中的Bean、事务管理、任务调度、AOP等模块都出现了它的身影。
    Spring中绝大部分Bean都可以声明成Singleton作用域,采用ThreadLocal进行封装,因此有状态的Bean就能够以singleton的方式在多线程中正常工作了。
    友情链接:深入研究java.lang.ThreadLocal类


    Java内存模型:
    Java虚拟机规范中将Java运行时数据分为六种。
    1.程序计数器:是一个数据结构,用于保存当前正常执行的程序的内存地址。Java虚拟机的多线程就是通过线程轮流切换并分配处理器时间来实现的,为了线程切换后能恢复到正确的位置,每条线程都需要一个独立的程序计数器,互不影响,该区域为“线程私有”。
    2.Java虚拟机栈:线程私有的,与线程生命周期相同,用于存储局部变量表,操作栈,方法返回值。局部变量表放着基本数据类型,还有对象的引用。
    3.本地方法栈:跟虚拟机栈很像,不过它是为虚拟机使用到的Native方法服务。
    4.Java堆:所有线程共享的一块内存区域,对象实例几乎都在这分配内存。
    5.方法区:各个线程共享的区域,储存虚拟机加载的类信息,常量,静态变量,编译后的代码。
    6.运行时常量池:代表运行时每个class文件中的常量表。包括几种常量:编译时的数字常量、方法或者域的引用。
    友情链接: Java中JVM虚拟机详解


    “你能不能谈谈,java GC是在什么时候,对什么东西,做了什么事情?”
    在什么时候:
    1.新生代有一个Eden区和两个survivor区,首先将对象放入Eden区,如果空间不足就向其中的一个survivor区上放,如果仍然放不下就会引发一次发生在新生代的minor GC,将存活的对象放入另一个survivor区中,然后清空Eden和之前的那个survivor区的内存。在某次GC过程中,如果发现仍然又放不下的对象,就将这些对象放入老年代内存里去。
    2.大对象以及长期存活的对象直接进入老年区。
    3.当每次执行minor GC的时候应该对要晋升到老年代的对象进行分析,如果这些马上要到老年区的老年对象的大小超过了老年区的剩余大小,那么执行一次Full GC以尽可能地获得老年区的空间。
    对什么东西:从GC Roots搜索不到,而且经过一次标记清理之后仍没有复活的对象。
    做什么: 新生代:复制清理; 老年代:标记-清除和标记-压缩算法; 永久代:存放Java中的类和加载类的类加载器本身。
    GC Roots都有哪些: 1. 虚拟机栈中的引用的对象 2. 方法区中静态属性引用的对象,常量引用的对象 3. 本地方法栈中JNI(即一般说的Native方法)引用的对象。
    友情链接:Java GC的那些事(上)
    友情链接:Java GC的那些事(下)
    友情链接:CMS垃圾收集器介绍


    Synchronized 与Lock都是可重入锁,同一个线程再次进入同步代码的时候.可以使用自己已经获取到的锁。
    Synchronized是悲观锁机制,独占锁。而Locks.ReentrantLock是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。 ReentrantLock适用场景

    1. 某个线程在等待一个锁的控制权的这段时间需要中断
    2. 需要分开处理一些wait-notify,ReentrantLock里面的Condition应用,能够控制notify哪个线程,锁可以绑定多个条件。
    3. 具有公平锁功能,每个到来的线程都将排队等候。

    友情链接: Synchronized关键字、Lock,并解释它们之间的区别


    StringBuffer是线程安全的,每次操作字符串,String会生成一个新的对象,而StringBuffer不会;StringBuilder是非线程安全的
    友情链接:String、StringBuffer与StringBuilder之间区别


    fail-fast:机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
    例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件


    happens-before:如果两个操作之间具有happens-before 关系,那么前一个操作的结果就会对后面一个操作可见。
    1.程序顺序规则:一个线程中的每个操作,happens- before 于该线程中的任意后续操作。
    2.监视器锁规则:对一个监视器锁的解锁,happens- before 于随后对这个监视器锁的加锁。
    3.volatile变量规则:对一个volatile域的写,happens- before于任意后续对这个volatile域的读。
    4.传递性:如果A happens- before B,且B happens- before C,那么A happens- before C。
    5.线程启动规则:Thread对象的start()方法happens- before于此线程的每一个动作。


    Volatile和Synchronized四个不同点:
    1 粒度不同,前者针对变量 ,后者锁对象和类
    2 syn阻塞,volatile线程不阻塞
    3 syn保证三大特性,volatile不保证原子性
    4 syn编译器优化,volatile不优化 volatile具备两种特性:

    1.保证此变量对所有线程的可见性,指一条线程修改了这个变量的值,新值对于其他线程来说是可见的,但并不是多线程安全的。
    2.禁止指令重排序优化。

    Volatile如何保证内存可见性:

    1.当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。
    2.当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

    同步:就是一个任务的完成需要依赖另外一个任务,只有等待被依赖的任务完成后,依赖任务才能完成。
    异步:不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,只要自己任务完成了就算完成了,被依赖的任务是否完成会通知回来。(异步的特点就是通知)。 打电话和发短信来比喻同步和异步操作。
    阻塞:CPU停下来等一个慢的操作完成以后,才会接着完成其他的工作。
    非阻塞:非阻塞就是在这个慢的执行时,CPU去做其他工作,等这个慢的完成后,CPU才会接着完成后续的操作。
    非阻塞会造成线程切换增加,增加CPU的使用时间能不能补偿系统的切换成本需要考虑。
    友情链接:Java并发编程之volatile关键字解析


    CAS(Compare And Swap) 无锁算法: CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
    友情链接:非阻塞同步算法与CAS(Compare and Swap)无锁算法


    线程池的作用: 在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程
    第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
    第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
    第三:提高线程的可管理性。
    常用线程池:ExecutorService 是主要的实现类,其中常用的有 Executors.newSingleThreadPool(),newFixedThreadPool(),newcachedTheadPool(),newScheduledThreadPool()。
    友情链接:线程池原理
    友情链接:线程池原理解析


    类加载器工作机制:
    1.装载:将Java二进制代码导入jvm中,生成Class文件。
    2.连接:a)校验:检查载入Class文件数据的正确性 b)准备:给类的静态变量分配存储空间 c)解析:将符号引用转成直接引用
    3:初始化:对类的静态变量,静态方法和静态代码块执行初始化工作。
    双亲委派模型:类加载器收到类加载请求,首先将请求委派给父类加载器完成 用户自定义加载器->应用程序加载器->扩展类加载器->启动类加载器。
    友情链接:深入理解Java虚拟机笔记—双亲委派模型 
    友情链接:JVM类加载的那些事
    友情链接:JVM(1):Java 类的加载机制


    一致性哈希:
    Memcahed缓存:
    数据结构:key,value对
    使用方法:get,put等方法
    友情链接:hashcode(),equal()方法深入解析


    Redis数据结构: String—字符串(key-value 类型)
    Hash—字典(hashmap) Redis的哈希结构可以使你像在数据库中更新一个属性一样只修改某一项属性值
    List—列表 实现消息队列
    Set—集合 利用唯一性
    Sorted Set—有序集合 可以进行排序 可以实现数据持久化
    友情链接: Spring + Redis 实现数据的缓存


    java自动装箱拆箱深入剖析
    谈谈Java反射机制
    如何写一个不可变类?


    索引:B+,B-,全文索引
    Mysql的索引是一个数据结构,旨在使数据库高效的查找数据。
    常用的数据结构是B+Tree,每个叶子节点不但存放了索引键的相关信息还增加了指向相邻叶子节点的指针,这样就形成了带有顺序访问指针的B+Tree,做这个优化的目的是提高不同区间访问的性能。
    什么时候使用索引:

    1. 经常出现在group by,order by和distinc关键字后面的字段
    2. 经常与其他表进行连接的表,在连接字段上应该建立索引
    3. 经常出现在Where子句中的字段
    4. 经常出现用作查询选择的字段

    友情链接:MySQL:InnoDB存储引擎的B+树索引算法
    友情链接:MySQL索引背后的数据结构及算法原理


    Spring IOC (控制反转,依赖注入)
    Spring支持三种依赖注入方式,分别是属性(Setter方法)注入,构造注入和接口注入。
    在Spring中,那些组成应用的主体及由Spring IOC容器所管理的对象被称之为Bean。
    Spring的IOC容器通过反射的机制实例化Bean并建立Bean之间的依赖关系。
    简单地讲,Bean就是由Spring IOC容器初始化、装配及被管理的对象。
    获取Bean对象的过程,首先通过Resource加载配置文件并启动IOC容器,然后通过getBean方法获取bean对象,就可以调用他的方法。
    Spring Bean的作用域:
    Singleton:Spring IOC容器中只有一个共享的Bean实例,一般都是Singleton作用域。
    Prototype:每一个请求,会产生一个新的Bean实例。
    Request:每一次http请求会产生一个新的Bean实例。
    友情链接: Spring框架IOC容器和AOP解析
    友情链接:浅谈Spring框架注解的用法分析
    友情链接:关于Spring的69个面试问答——终极列表


    代理的共有优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。
    Java静态代理:
    代理对象和目标对象实现了相同的接口,目标对象作为代理对象的一个属性,具体接口实现中,代理对象可以在调用目标对象相应方法前后加上其他业务处理逻辑。
    缺点:一个代理类只能代理一个业务类。如果业务类增加方法时,相应的代理类也要增加方法。
    Java动态代理:
    Java动态代理是写一个类实现InvocationHandler接口,重写Invoke方法,在Invoke方法可以进行增强处理的逻辑的编写,这个公共代理类在运行的时候才能明确自己要代理的对象,同时可以实现该被代理类的方法的实现,然后在实现类方法的时候可以进行增强处理。
    实际上:代理对象的方法 = 增强处理 + 被代理对象的方法

    JDK和CGLIB生成动态代理类的区别:
    JDK动态代理只能针对实现了接口的类生成代理(实例化一个类)。此时代理对象和目标对象实现了相同的接口,目标对象作为代理对象的一个属性,具体接口实现中,可以在调用目标对象相应方法前后加上其他业务处理逻辑
    CGLIB是针对类实现代理,主要是对指定的类生成一个子类(没有实例化一个类),覆盖其中的方法 。
    Spring AOP应用场景
    性能检测,访问控制,日志管理,事务等。
    默认的策略是如果目标类实现接口,则使用JDK动态代理技术,如果目标对象没有实现接口,则默认会采用CGLIB代理


    SpringMVC运行原理

    1. 客户端请求提交到DispatcherServlet
    2. 由DispatcherServlet控制器查询HandlerMapping,找到并分发到指定的Controller中。
    3. Controller调用业务逻辑处理后,返回ModelAndView
    4. DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图
    5. 视图负责将结果显示到客户端

    友情链接:Spring:基于注解的Spring MVC(上)
    友情链接: Spring:基于注解的Spring MVC(下) 
    友情链接:SpringMVC与Struts2区别与比较总结
    友情链接:SpringMVC与Struts2的对比


    一个Http请求
    DNS域名解析 –> 发起TCP的三次握手 –> 建立TCP连接后发起http请求 –> 服务器响应http请求,浏览器得到html代码 –> 浏览器解析html代码,并请求html代码中的资源(如javascript、css、图片等) –> 浏览器对页面进行渲染呈现给用户

    设计存储海量数据的存储系统:设计一个叫“中间层”的一个逻辑层,在这个层,将数据库的海量数据抓出来,做成缓存,运行在服务器的内存中,同理,当有新的数据到来,也先做成缓存,再想办法,持久化到数据库中,这是一个简单的思路。主要的步骤是负载均衡,将不同用户的请求分发到不同的处理节点上,然后先存入缓存,定时向主数据库更新数据。读写的过程采用类似乐观锁的机制,可以一直读(在写数据的时候也可以),但是每次读的时候会有个版本的标记,如果本次读的版本低于缓存的版本,会重新读数据,这样的情况并不多,可以忍受。

    友情链接: HTTP与HTTPS的区别
    友情链接: HTTPS 为什么更安全,先看这些 
    友情链接: HTTP请求报文和HTTP响应报文
    友情链接: HTTP 请求方式: GET和POST的比较


    Session与Cookie:Cookie可以让服务端跟踪每个客户端的访问,但是每次客户端的访问都必须传回这些Cookie,如果Cookie很多,则无形的增加了客户端与服务端的数据传输量,
    而Session则很好地解决了这个问题,同一个客户端每次和服务端交互时,将数据存储通过Session到服务端,不需要每次都传回所有的Cookie值,而是传回一个ID,每个客户端第一次访问服务器生成的唯一的ID,客户端只要传回这个ID就行了,这个ID通常为NAME为JSESSIONID的一个Cookie。这样服务端就可以通过这个ID,来将存储到服务端的KV值取出了。
    Session和Cookie的超时问题,Cookie的安全问题


    分布式Session框架

    1. 配置服务器,Zookeeper集群管理服务器可以统一管理所有服务器的配置文件
    2. 共享这些Session存储在一个分布式缓存中,可以随时写入和读取,而且性能要很好,如Memcache,Tair。
    3. 封装一个类继承自HttpSession,将Session存入到这个类中然后再存入分布式缓存中
    4. 由于Cookie不能跨域访问,要实现Session同步,要同步SessionID写到不同域名下。

    适配器模式:将一个接口适配到另一个接口,Java I/O中InputStreamReader将Reader类适配到InputStream,从而实现了字节流到字符流的准换。
    装饰者模式:保持原来的接口,增强原来有的功能。
    FileInputStream 实现了InputStream的所有接口,BufferedInputStreams继承自FileInputStream是具体的装饰器实现者,将InputStream读取的内容保存在内存中,而提高读取的性能。


    Spring事务配置方法:
    1.切点信息,用于定位实施事物切面的业务类方法
    2.控制事务行为的事务属性,这些属性包括事物隔离级别,事务传播行为,超时时间,回滚规则。

    Spring通过aop/tx Schema 命名空间和@Transaction注解技术来进行声明式事物配置。


    Mybatis
    每一个Mybatis的应用程序都以一个SqlSessionFactory对象的实例为核心。首先用字节流通过Resource将配置文件读入,然后通过SqlSessionFactoryBuilder().build方法创建SqlSessionFactory,然后再通过SqlSessionFactory.openSession()方法创建一个SqlSession为每一个数据库事务服务。
    经历了Mybatis初始化 –>创建SqlSession –>运行SQL语句,返回结果三个过程


    Servlet和Filter的区别:
    整的流程是:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。

    Filter有如下几个用处:
    Filter可以进行对特定的url请求和相应做预处理和后处理。
    在HttpServletRequest到达Servlet之前,拦截客户的HttpServletRequest。
    根据需要检查HttpServletRequest,也可以修改HttpServletRequest头和数据。
    在HttpServletResponse到达客户端之前,拦截HttpServletResponse。
    根据需要检查HttpServletResponse,也可以修改HttpServletResponse头和数据。

    实际上Filter和Servlet极其相似,区别只是Filter不能直接对用户生成响应。实际上Filter里doFilter()方法里的代码就是从多个Servlet的service()方法里抽取的通用代码,通过使用Filter可以实现更好的复用。

    Filter和Servlet的生命周期:
    1.Filter在web服务器启动时初始化
    2.如果某个Servlet配置了 1 ,该Servlet也是在Tomcat(Servlet容器)启动时初始化。
    3.如果Servlet没有配置1 ,该Servlet不会在Tomcat启动时初始化,而是在请求到来时初始化。
    4.每次请求, Request都会被初始化,响应请求后,请求被销毁。
    5.Servlet初始化后,将不会随着请求的结束而注销。
    6.关闭Tomcat时,Servlet、Filter依次被注销。


    HashMap与HashTable的区别。
    1、HashMap是非线程安全的,HashTable是线程安全的。
    2、HashMap的键和值都允许有null值存在,而HashTable则不行。
    3、因为线程安全的问题,HashMap效率比HashTable的要高。

    HashMap的实现机制:

    1. 维护一个每个元素是一个链表的数组,而且链表中的每个节点是一个Entry[]键值对的数据结构。
    2. 实现了数组+链表的特性,查找快,插入删除也快。
    3. 对于每个key,他对应的数组索引下标是 int i = hash(key.hashcode)&(len-1);
    4. 每个新加入的节点放在链表首,然后该新加入的节点指向原链表首

    HashMap和TreeMap区别
    友情链接: Java中HashMap和TreeMap的区别深入理解

    HashMap冲突
    友情链接: HashMap冲突的解决方法以及原理分析
    友情链接: HashMap的工作原理
    友情链接: HashMap和Hashtable的区别
    友情链接: 2种办法让HashMap线程安全


    HashMap,ConcurrentHashMap与LinkedHashMap的区别

    1. ConcurrentHashMap是使用了锁分段技术技术来保证线程安全的,锁分段技术:首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问
    2. ConcurrentHashMap 是在每个段(segment)中线程安全的
    3. LinkedHashMap维护一个双链表,可以将里面的数据按写入的顺序读出

    ConcurrentHashMap应用场景
    1:ConcurrentHashMap的应用场景是高并发,但是并不能保证线程安全,而同步的HashMap和HashMap的是锁住整个容器,而加锁之后ConcurrentHashMap不需要锁住整个容器,只需要锁住对应的Segment就好了,所以可以保证高并发同步访问,提升了效率。
    2:可以多线程写。
    ConcurrentHashMap把HashMap分成若干个Segmenet
    1.get时,不加锁,先定位到segment然后在找到头结点进行读取操作。而value是volatile变量,所以可以保证在竞争条件时保证读取最新的值,如果读到的value是null,则可能正在修改,那么就调用ReadValueUnderLock函数,加锁保证读到的数据是正确的。
    2.Put时会加锁,一律添加到hash链的头部。
    3.Remove时也会加锁,由于next是final类型不可改变,所以必须把删除的节点之前的节点都复制一遍。
    4.ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对Hash表的不同Segment进行的修改。

    ConcurrentHashMap的应用场景是高并发,但是并不能保证线程安全,而同步的HashMap和HashTable的是锁住整个容器,而加锁之后ConcurrentHashMap不需要锁住整个容器,只需要锁住对应的segment就好了,所以可以保证高并发同步访问,提升了效率。

    ConcurrentHashMap能够保证每一次调用都是原子操作,但是并不保证多次调用之间也是原子操作。
    友情链接:Java集合—ConcurrentHashMap原理分析


    Vector和ArrayList的区别
    友情链接:Java中Vector和ArrayList的区别


    ExecutorService service = Executors…. ExecutorService service = new ThreadPoolExecutor() ExecutorService service = new ScheduledThreadPoolExecutor();

    ThreadPoolExecutor源码分析

    线程池本身的状态:

    等待任务队列和工作集:

    线程池的主要状态锁:

    线程池的存活时间和大小:

    1.2 ThreadPoolExecutor 的内部工作原理
    有了以上定义好的数据,下面来看看内部是如何实现的 。 Doug Lea 的整个思路总结起来就是 5 句话:

    1. 如果当前池大小 poolSize 小于 corePoolSize ,则创建新线程执行任务。
    2. 如果当前池大小 poolSize 大于 corePoolSize ,且等待队列未满,则进入等待队列
    3. 如果当前池大小 poolSize 大于 corePoolSize 且小于 maximumPoolSize ,且等待队列已满,则创建新线程执行任务。
    4. 如果当前池大小 poolSize 大于 corePoolSize 且大于 maximumPoolSize ,且等待队列已满,则调用拒绝策略来处理该任务。
    5. 线程池里的每个线程执行完任务后不会立刻退出,而是会去检查下等待队列里是否还有线程任务需要执行,如果在 keepAliveTime 里等不到新的任务了,那么线程就会退出。

    Executor包结构

    CopyOnWriteArrayList : 写时加锁,当添加一个元素的时候,将原来的容器进行copy,复制出一个新的容器,然后在新的容器里面写,写完之后再将原容器的引用指向新的容器,而读的时候是读旧容器的数据,所以可以进行并发的读,但这是一种弱一致性的策略。
    使用场景:CopyOnWriteArrayList适合使用在读操作远远大于写操作的场景里,比如缓存。


    Linux常用命令:cd,cp,mv,rm,ps(进程),tar,cat(查看内容),chmod,vim,find,ls


    死锁的必要条件

    1. 互斥 至少有一个资源处于非共享状态
    2. 占有并等待
    3. 非抢占
    4. 循环等待

    解决死锁,第一个是死锁预防,就是不让上面的四个条件同时成立。二是,合理分配资源。
    三是使用银行家算法,如果该进程请求的资源操作系统剩余量可以满足,那么就分配。


    进程间的通信方式

    1. 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
    2. 有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
    3. 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
    4. 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
    5. 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
    6. 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
    7. 套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。

    进程与线程的区别和联系
    操作系统的进程调度算法
    计算机系统的层次存储结构详解


    数据库事务是指作为单个逻辑工作单元执行的一系列操作。

    友情链接:数据库事务的四大特性以及事务的隔离级别


    MySQL数据库优化总结
    MYSQL 优化常用方法
    MySQL存储引擎--MyISAM与InnoDB区别
    关于SQL数据库中的范式


    Hibernate的一级缓存是由Session提供的,因此它只存在于Session的生命周期中,当程序调用save(),update(),saveOrUpdate()等方法 及调用查询接口list,filter,iterate时,如Session缓存中还不存在相应的对象,Hibernate会把该对象加入到一级缓存中,当Session关闭的时候缓存也会消失。

    Hibernate的一级缓存是Session所内置的,不能被卸载,也不能进行任何配置一级缓存采用的是key-value的Map方式来实现的,在缓存实体对象时,对象的主关键字ID是Map的key,实体对象就是对应的值。

    Hibernate二级缓存:把获得的所有数据对象根据ID放入到第二级缓存中。Hibernate二级缓存策略,是针对于ID查询的缓存策略,删除、更新、增加数据的时候,同时更新缓存。

    进程和线程的区别:

    进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1–n个线程。

    线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。

    线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。

    多进程是指操作系统能同时运行多个任务(程序)。

    多线程是指在同一程序中有多个顺序流在执行。

    在java中要想实现多线程,有三种手段,一种是继续Thread类,另外一种是实现Runable接口,还有就是实现Callable接口。


    Switch能否用string做参数?

    a.在 Java 7 之前, switch 只能支持byte,short,char,int 或者其对应的封装类以及 Enum 类型。在Java 7中,String 支持被加上了。


    Object有哪些公用方法?

    a.方法equals测试的是两个对象是否相等

    b.方法clone进行对象拷贝

    c.方法getClass返回和当前对象相关的Class对象

    d.方法notify,notifyall,wait都是用来对给定对象进行线程同步的


    Java的四种引用,强弱软虚,以及用到的场景

    a.利用软引用和弱引用解决OOM问题:用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题。

    b.通过软可及对象重获方法实现Java对象的高速缓存:比如我们创建了一Employee的类,如果每次需要查询一个雇员的信息。哪怕是几秒中之前刚刚查询过的,都要重新构建一个实例,这是需要消耗很多时间的。我们可以通过软引用和 HashMap 的结合,先是保存引用方面:以软引用的方式对一个Employee对象的实例进行引用并保存该引用到HashMap 上,key 为此雇员的 id,value为这个对象的软引用,另一方面是取出引用,缓存中是否有该Employee实例的软引用,如果有,从软引用中取得。如果没有软引用,或者从软引用中得到的实例是null,重新构建一个实例,并保存对这个新建实例的软引用。

    c.强引用:如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。如果想中断强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。

    d.软引用:在使用软引用时,如果内存的空间足够,软引用就能继续被使用,而不会被垃圾回收器回收,只有在内存不足时,软引用才会被垃圾回收器回收。

    e.弱引用:具有弱引用的对象拥有的生命周期更短暂。因为当 JVM 进行垃圾回收,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。不过由于垃圾回收器是一个优先级较低的线程,所以并不一定能迅速发现弱引用对象。

    f.虚引用:顾名思义,就是形同虚设,如果一个对象仅持有虚引用,那么它相当于没有引用,在任何时候都可能被垃圾回收器回收。


    Hashcode的作用,与 equal 有什么区别?

    a.同样用于鉴定2个对象是否相等的,java集合中有 list 和 set 两类,其中 set不允许元素重复实现,那个这个不允许重复实现的方法,如果用 equal 去比较的话,如果存在1000个元素,你 new 一个新的元素出来,需要去调用1000次 equal 去逐个和他们比较是否是同一个对象,这样会大大降低效率。hashcode实际上是返回对象的存储地址,如果这个位置上没有元素,就把元素直接存储在上面,如果这个位置上已经存在元素,这个时候才去调用equal方法与新元素进行比较,相同的话就不存了,散列到其他地址上。


    Override和Overload的含义以及区别
    a.Overload顾名思义是重新加载,它可以表现类的多态性,可以是函数里面可以有相同的函数名但是参数名、返回值、类型不能相同;或者说可以改变参数、类型、返回值但是函数名字依然不变。
    b.就是ride(重写)的意思,在子类继承父类的时候子类中可以定义某方法与其父类有相同的名称和参数,当子类在调用这一函数时自动调用子类的方法,而父类相当于被覆盖(重写)了。
    具体可前往C++中重载、重写(覆盖)的区别实例分析查看


    抽象类和接口的区别

    a.一个类只能继承单个类,但是可以实现多个接口

    b.抽象类中可以有构造方法,接口中不能有构造方法

    c.抽象类中的所有方法并不一定要是抽象的,你可以选择在抽象类中实现一些基本的方法。而接口要求所有的方法都必须是抽象的

    d.抽象类中可以包含静态方法,接口中不可以

    e.抽象类中可以有普通成员变量,接口中不可以

    解析XML的几种方式的原理与特点:DOM、SAX、PULL

    a.DOM:消耗内存:先把xml文档都读到内存中,然后再用DOM API来访问树形结构,并获取数据。这个写起来很简单,但是很消耗内存。要是数据过大,手机不够牛逼,可能手机直接死机

    b.SAX:解析效率高,占用内存少,基于事件驱动的:更加简单地说就是对文档进行顺序扫描,当扫描到文档(document)开始与结束、元素(element)开始与结束、文档(document)结束等地方时通知事件处理函数,由事件处理函数做相应动作,然后继续同样的扫描,直至文档结束。

    c.PULL:与 SAX 类似,也是基于事件驱动,我们可以调用它的next()方法,来获取下一个解析事件(就是开始文档,结束文档,开始标签,结束标签),当处于某个元素时可以调用XmlPullParser的getAttributte()方法来获取属性的值,也可调用它的nextText()获取本节点的值。


    wait()和sleep()的区别

    sleep来自Thread类,和wait来自Object类

    调用sleep()方法的过程中,线程不会释放对象锁。而 调用 wait 方法线程会释放对象锁

    sleep睡眠后不出让系统资源,wait让出系统资源其他线程可以占用CPU

    sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒


    JAVA 中堆和栈的区别,说下java 的内存机制

    a.基本数据类型比变量和对象的引用都是在栈分配的

    b.堆内存用来存放由new创建的对象和数组

    c.类变量(static修饰的变量),程序在一加载的时候就在堆中为类变量分配内存,堆中的内存地址存放在栈中

    d.实例变量:当你使用java关键字new的时候,系统在堆中开辟并不一定是连续的空间分配给变量,是根据零散的堆内存地址,通过哈希算法换算为一长串数字以表征这个变量在堆中的”物理位置”,实例变量的生命周期–当实例变量的引用丢失后,将被GC(垃圾回收器)列入可回收“名单”中,但并不是马上就释放堆中内存

    e.局部变量: 由声明在某方法,或某代码段里(比如for循环),执行到它的时候在栈中开辟内存,当局部变量一但脱离作用域,内存立即释放


    JAVA多态的实现原理

    a.抽象的来讲,多态的意思就是同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)

    b.实现的原理是动态绑定,程序调用的方法在运行期才动态绑定,追溯源码可以发现,JVM 通过参数的自动转型来找到合适的办法。

     

    来源:

    https://www.cnblogs.com/pureEve/p/6546280.html

    https://www.cnblogs.com/java1024/p/7685400.html

    展开全文
  • 2020JAVA面试题附答案(持续更新版)

    万次阅读 多人点赞 2020-04-21 23:52:57
    JAVA基础 JAVA中的几种基本类型,各占用多少字节? 下图单位是bit,非字节 1B=8bit String能被继承吗?为什么? 不可以,因为String类有final修饰符,而final修饰的类是不能被继承的,实现细节不允许改变。平常我们...
  • 【面试】JAVA三年经验面试题

    万次阅读 多人点赞 2020-03-07 19:50:36
    整理一下最近面试遇到过的问题,有一些想不起来了,希望能给大家一点帮助吧,也给自己留个底,嘿嘿,平时还是得多注意知识的积累,以及技术细节 - 1.JAVA基础类型各占几个字节? int 32bit short 16bit long 64...
  • Java基础面试题整理

    万次阅读 多人点赞 2019-02-08 09:11:37
    面向对象的三个特征 封装,继承,多态.这个应该是人人皆知.有时候也会加上抽象. 多态的好处 允许不同类对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用多种不同的行为方式(发送消息就是函数调用)....
  • Java 最常见的 200+ 面试题:面试必备

    万次阅读 多人点赞 2020-09-17 23:01:58
    这份面试清单是从我 2015 年做了 TeamLeader 之后开始收集的,一方面是给公司招聘用,另一方面是想用它来挖掘在 Java 技术栈中,还有那些知识点是我不知道的,我想找到这些技术盲点,然后修复它,以此来提高自己的...
  • Java面试笔试大汇总(最全+详细答案) Java面试笔试大汇总(最全+详细答案)
  • Java面试题大全(2020版)

    万次阅读 多人点赞 2020-05-24 15:23:09
    发现网上很多Java面试题都没有答案,所以花了很长时间搜集整理出来了这套Java面试题大全,希望对大家有帮助哈~ 本套Java面试题大全,全的不能再全,哈哈~ 一、Java 基础 1. JDK 和 JRE 有什么区别? JDK:Java ...
  • Java面试题全集(上)

    万次阅读 多人点赞 2016-12-28 22:56:50
    2013年年底的时候,我看到了网上流传的一个叫做《Java面试题大全》的东西,认真的阅读了以后发现里面的很多题目是重复且没有价值的题目,还有不少的参考答案也是错误的,于是我花了半个月时间对这个所谓的《Java面试...
  • Java面试题内容聚合

    万次阅读 多人点赞 2019-12-04 14:08:10
    这是一份常见Java面试题分类汇总,希望对大家有用! 初级面试题 Java面试题-基础篇一 Java面试题-基础篇二 Java面试题-集合框架篇三 Java面试题-基础篇四 Java面试题-基础篇五 Java面试题-javaweb篇六 ...
  • Java面试题全集(中)

    万次阅读 多人点赞 2015-04-12 12:05:13
    2015年重新整理发布的Java面试题全集,这部分主要是与Java Web和Web Service相关的面试题。
  • 2020最新Java面试题,常见面试题及答案汇总

    万次阅读 多人点赞 2020-06-02 09:18:05
    发现网上很多Java面试题都没有答案,所以花了很长时间搜集整理出来了这套Java面试题大全,希望对大家有帮助哈~ 一、Java 基础 1. JDK 和 JRE 有什么区别? JDK:Java Development Kit 的简称,java 开发工具包,...
  • 最新Java面试题,常见面试题及答案汇总

    万次阅读 多人点赞 2020-07-27 17:29:03
    Java最新面试题面试题答案汇总
  • 2019 java面试题基础

    万次阅读 多人点赞 2019-07-18 17:20:12
    如有需要获取最新资料,以及更全面的PDF版面试题,请关注微信公众号“西柚向西”,回复“面试题”... 最新最全的面试集合,请参考《最全最新java面试题系列全家桶(带答案)》 更多精彩,持续更新中。。。。 4、&a...
  • java面试题大全

    万次阅读 多人点赞 2018-02-05 17:56:43
    过完年,又有大批人要换工作了,这里整理了很全的java面试笔试题,希望对大家有所帮助! 面试题部分! SSH框架面试题集锦 ...Java面试题-基础篇一 ...Java面试题-基础篇二 ...Java面试题-集合框架篇三
  • Java面试题集(1-50)

    万次阅读 多人点赞 2015-04-12 00:02:26
    下面的内容是对网上原有的Java面试题集及答案进行了全面修订之后给出的负责任的题目和答案,原来的题目中有很多重复题目和无价值的题目,还有不少的参考答案也是错误的,修改后的Java面试题集参照了JDK最新版本,...
  • Java面试题集锦

    万次阅读 多人点赞 2019-05-29 21:25:27
    Java面试题(01) Java面试题(02) Java面试题(03) Java面试题(04) Java面试题(05) Java面试题(06) Java面试题(07) Java面试题(08) Java面试题(09) Java面试题(10) Java面试题(11) ...
  • java面试题及答案2020 大汇总

    千次阅读 多人点赞 2020-05-13 12:55:27
    java面试题及答案2020 java面试题大汇总 百度第一篇 java面试题及答案2020 先点赞后收藏,以后更新及时看 文末后续更新答案,持续更新 一面 2018/9/11 来自于牛客网 1、手写ArrayList 2、手写进制转换算法,求出一...
1 2 3 4 5 ... 20
收藏数 187,362
精华内容 74,944
关键字:

java面试题