面试题java 面试

java面试题集合

2017-08-20  本文已影响78人  小庄bb

1.Switch能否用String?

在java7之前,Switch值能支持int,byte,short,char以及他们的扩展类型,在java7之后支持了String。(ps:其实也不是支持了Switch,而是对String值去hasCode值,再相互比较)。

2.equals与==的区别?

==判断两个变量是不是指向同一个内存空间,而equals判断两个变量所指向的内存空间的值是否相等。

3.Object有哪些公用的方法?

4.实际开发中强软弱虚软弱引用的使用场景:

5.String、StringBuffer与StringBuilder的区别

String与其他两个的区别是,String是不可变的,StringBuffer与StringBuilder实质上是基于Char[]实现的。StringBuffer是线程安全的,而StringBuilder是线程不安全的。

6.Overrider和Overload的含义及区别

Overrider:方法重写,在子类重新定义从父类继承的方法,从而覆盖父类方法,要求方法名,方法类型,参数,以及返回值类型必须一致。
Overload:方法重载,表现java的多态性,重写已有的方法,要求方法名一致,返回值一致,参数不一致 。

7.抽象类和接口的区别

一个类只能继承一个抽象类,但是一个类可以继承无数个接口。抽象类强调所属关系,抽象类里的方法不一定都是抽象的,它在一定程度上规定了子类的一些属性。而接口要求所有的方法都必须是抽象的(ps:那是在过去,现在接口已经支持default,static关键字,来定义默认方法,和静态方法。那就是说抽象方法能做的接口完全可以替代)

8.解析XML的几种方式的原理和特点

9.java多态的实现原理

所谓多态,指的就是父类引用指向子类对象,调用方法时会调用子类的方法而不是父类的方法。多态的实现的关键在于动态绑定。

方法区:方法区和java堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的信息,常量,静态变量,即时编译器编译后的代码等数据。运行常量池:它是方法区的一部分,Class文件中除了有关类的版本,方法,字段描述信息以外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分信息在类加载时进入方法区的运行时常量池中。方法区的内存回收目标是针对常量池的回收以及对类型的卸载。

由于这样的特性,使得方法表的偏移量总是固定的,例如对于任何方类来说,其方法表的equals方法的偏移量都是一个定值,所有继承父类的方法在方法表中的偏移量也都是一个定值。

流程:调用方法时,虚拟机通过对象引用得到方法区中类型信息的方法表的指针入口,查询类的方法表,根据实例方法的符号引用解析出该方法在方法表的偏移量,子类对象声明为父类类型时,形式上调用的是父类的方法,此时虚拟机会从实际的方法表中找到方法地址,从而定位到实际类的方法。注:所有引用为父类,但方法区的类型信息中存放的是子类的信息,所以调用的是子类的方法表。

10.hashCode的作用是:

什么是hashCode:

1.HashCode的存在就是为了查找的快捷性,HashCode用来在散列存储结构中对定对象的存储地址。
2.如果两个对象equals相等,那么它们的hashCode值一定相等,反之则不一定,因为相同的hashCode只能说明其存在于散列存储结构中的同一个位置。
3.当重写equals方法时,HashCode方法也尽量重写。

HashCode有什么用?举个例子:

1.假设内存中有0 1 2 3 4 5 6 7 8这8个位置,如果我有个字段叫做ID,那么我要把这个字段存放在以上8个位置之一,如果不用HashCode而任意存放,那么当查找时就需要到8个位置中去挨个查找
使用HashCode则效率会快很多,把ID的HashCode%8,然后把ID存放在取得余数的那个位置,然后每次查找该类的时候都可以通过ID的HashCode%8求余数直接找到存放的位置了
2.如果ID的HashCode%8算出来的位置上本身已经有数据了怎么办?这就取决于算法的实现了,比如ThreadLocal中的做法就是从算出来的位置向后查找第一个为空的位置,放置数据;HashMap的做法就是通过链式结构连起来。反正,只要保证放的时候和取的时候的算法一致就行了。
3.如果ID的HashCode%8相等怎么办(这种对应的是第三点说的链式结构的场景)?这时候就需要定义equals了。先通过HashCode%8来判断类在哪一个位置,再通过equals来在这个位置上寻找需要的类。对比两个类的时候也差不多,先通过HashCode比较,假如HashCode相等再判断equals。如果两个类的HashCode都不相同,那么这两个类必定是不同的。

11.ArrayList,LinkedList,Vector的区别是什么?

12.Map,Set,List,Queue,Stack的特点及用法。

13.HashMap和HashTable的区别

14.Java中的另一个线程安全的与HashMap极其类似的类是什么?同样是线程安全,它与HashTable在线程同步上有什么不同?

concurrentHashMap是HashMap的线程安全的实现,而它与hashTable不同的是,HashTable使用synchronized实现同步机制,无论你在哪个位置添加语句块,封锁的都是对象整体,而concurrentHashMap是利用lock实现的同步机制,它基于concurrentLevel划分出了多个Segment来对Key-value进行存储,从而避免了每次锁定整个数组,在默认情况下,允许16个线程并发无阻塞的操作集合对象,尽可能的减少并发时的阻塞现象。

15.ConcurrentHashMap的实现原理

ConcurrentHashMap是支持并发读写的HashMap,他的特点是读取数据时无需加锁,写数据是可以保证枷锁粒度尽可能的小。由于其内部采用分段存储,只需对要进行写操作的数据所在的“段”进行加锁。

16.TreeMap,LinkedMap,HashMap的区别是什么?

17.Collection与Collections的区别是:

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

18.Set,List,Map的区别是什么:

Collection是最基本的集合接口,Set和List都继承了Collection,Map没有。

19.特殊关键字:final

20.对于“try{}catch(){}finally{}”,若try语句块中包含“return”语句,finally语句块会执行吗?

会执行,无论是try,还是catch中含有return,都会执行return后的运算,然后将返回值保存起来,然后执行finally块儿的内容,最后返回保存的值。(如果return i (此时i=1),而finally中赋值i=2,此时返回值仍然是1)
有两种情况能使finally中的语句块不会执行:

21.泛型的优缺点

22.java中的异常层级结构

java中的异常层次结构如下图所示:

java中的异常

Throwable类是异常层级中的基类。Error表示内部错误,这类错误是我们无法控制的;Exception表示异常,RuntimeException及其子类属于未检查异常,这类异常包括下标越界,空指针异常等,我们应该通过条件判断等方式语句避免未检查异常的发生。IOExcepion及其子类属于已检查异常,编译器会检查我们是否为所有可能抛出的已检查异常提供了异常捕获器,若没有则报错。对于未检查异常我们无需捕获。

23.Interface与abstract类的区别。

抽象类和接口都不能够实例化,但是可以定义抽象类和接口类型的引用。一个类如果继承了某个抽象类或是接口都需要对其中的抽象方法进行事实现,除非子类也是抽象类。有抽象方法的类必须被声明为抽象类,而抽象类不一定有抽象方法。

24.java面向对象的三个特征与含义。

三大特征:封装,多态,继承

25.接口与抽象类的区别

接口是一种约定,实现接口的类要遵循这个约定;抽象类本质是一个类。使用抽象类的代价要比接口大。接口与抽象类的对比如下:

26.静态内部类与非静态内部类的区别

内部静态类不需要有指向外部类的引用,但非静态内部类需要持有外部类的引用。非静态内部类能够访问,外部类的非静态成员和静态成员,而静态内部类不可访问外部类的非静态成员,只能访问静态成员。一个非静态内部类不能脱离外部类实例而被创建,一个非静态内部类可以访问外部类的数据和方法。

27.简述java中创建新线程的两种用法。

实现接口与继承方式有什么区别?

28简述java中进行线程同步的方法。

29.简述java中具有哪几种粒度的锁

java中可以对,类,对象,方法,和代码块上锁。

30.ThreadLocal的设计理念与作用

ThreadLocal的作用是提供该线程内的局部变量,在多线程环境下访问时能保证各个线程内的ThreadLocal变量独立。也就是说,每个线程的ThreadLocal变量是自己专用的,其他线程是访问不到的。ThreadLocal最常用于以下这个场景:多线程环境下存在对线程安全对象的并发访问,而且该对象不在线程间共享,但我们不想加锁,这时候可以使用ThreadLocal
来使得每个线程都持有一个该对象的副本。

31.concurrent包的整体架构

concurrent包的整体架构

32.同步器(Synchronizer)

concurrent包为我们提供了以下几个帮助我们管理相互合作的线程的类:

33.ArrayBlockingQueue,CountDownLatch类的作用。

34.wait()和sleep()的区别:

35.线程池的用法和优势

36.for-each与常规的for循环的效率对比

使用for-each循环与常规的for循环相比,并不存在性能损失,即使对数组进行迭代也是如此。实际上在有些场合他还能带来微小的性能提升,因为它只计算一次数组上限。

所以我们要尽可能的选择for-each。但是有些场景无法使用for-each。

37.简述java IO 与java NIO的区别

38.父类的静态方法能否被子类重写?

父类的静态方法可以被子类继承,也就是说子类可以调用父类的静态方法,但是不能重写。

39.反射的作用与原理

java反射机制是在运行状态中,对于任意一个类,都能知道这个类的所有方法和属性;对于任意一个对象,都能够调用他的任意一个方法和属性,这种动态获取信息以及动态调用对象的方法的功能称为java语言的反射机制。
主要作用有三:

用处如下:

40.java中的泛型机制

泛型:是一个类安全机制。其好处是:

泛型技术是给编译器使用的技术,用于编译时期。确保了类型的安全。运行时会将泛型擦除掉,生成的class文件是不带泛型的。为了兼容运行的类加载器,我们将泛型替换为最大容许类型。泛型的补偿:在类加载器原有的基础上,编写一个补偿程序,在运行时,通过反射,获取元素的类型进行转换动作。

41.进程和线程的区别

进程就是一个应用进程在处理机上的一次执行过程,它是一个动态的概念,而线程是进程的一部分,一个进程可以包含很多线程,进程是一个具有独立功能的程序关于某个数据集合的一次活动过程。他可以申请资源是一个动态的概念,是一个活动的实体。他不只表示程序的代码,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。

进程是一个“执行中的程序”。程序是一个没有生命的个体,只有处理器赋予生命时,才能成为一个有生命的活体,我们称之为进程。

通常在一个进程中包含若干个线程,他们可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和调度的基本单位。由于进程比线程更小,基本上不拥有系统资源,故而对它的调度所付出的开销就会很多,能够高效的提高系统内部多个程序之间并发执行的程度。

线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程都要有自己的执行堆栈和程序计数器为其执行上下文。多线程主要为了节约CPU空间,发挥利用,根据具体情况而定。线程的运行需要计算机的内存资源与CPU。

线程与进程的区别归纳:

进程是具有一定独立功能的程序关于某个数据集合上的一次活动,进程是系统进行资源分布和调度的一个独立单位。线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如:程序计数器,一组寄存器和栈)。但是它可与同属一个进程的其他线程共享所拥有的全部资源。

42.java7与java8的新特性

java7:

java8:

43.常见的设计模式

44.注解的基本概念与使用

45.java动态代理

public class ClassA {

    public void operateMethod1() {};
    public void operateMethod2() {};
    public void operateMethod3() {};

}

public class ClassB {

private ClassA a;
public ClassB(ClassA a) {
    this.a = a;
}
public void operateMethod1() {
    a.operateMethod1();
};
public void operateMethod2() {
    a.operateMethod2();
};
// not export operateMethod3()

}

``
上面ClassA是委托类,ClassB是代理类,ClassB中的函数都是直接调用ClassA相应函数,并且隐藏了Class的operateMethod3()函数。
静态代理中代理类和委托类也常常继承同一父类或实现同一接口。

代理类在程序运行前不存在、运行时由程序动态生成的代理方式称为动态代理。
Java 提供了动态代理的实现方式,可以在运行时刻动态生成代理类。这种代理方式的一大好处是可以方便对代理类的函数做统一或特殊处理,如记录所有函数执行时间、所有函数执行前添加验证判断、对某个特殊函数进行特殊操作,而不用像静态代理方式那样需要修改每个函数。

动态代理实现
(1)新建委托类
``
public interface Operate {

public void operateMethod1();

public void operateMethod2();

public void operateMethod3();

}

public class OperateImpl implements Operate {

@Override
public void operateMethod1() {
    System.out.println("Invoke operateMethod1");
    sleep(110);
}

@Override
public void operateMethod2() {
    System.out.println("Invoke operateMethod2");
    sleep(120);
}

@Override
public void operateMethod3() {
    System.out.println("Invoke operateMethod3");
    sleep(130);
}

private static void sleep(long millSeconds) {
    try {
        Thread.sleep(millSeconds);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

}
``
Operate是一个接口,定了了一些函数,我们要统计这些函数的执行时间。
OperateImpl是委托类,实现Operate接口。每个函数简单输出字符串,并等待一段时间。
动态代理要求委托类必须实现了某个接口,比如这里委托类OperateImpl实现了Operate,原因会后续在微博公布。

(2)实现InvocationHandler接口,这是负责连接代理类和委托类的中间类,必须实现InvocationHandler接口。

``
public class TimingInvocationHandler implements InvocationHandler {

private Object target;

public TimingInvocationHandler() {}

public TimingInvocationHandler(Object target) {
    this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    long start = System.currentTimeMillis();
    Object obj = method.invoke(target, args);
    System.out.println(method.getName() + " cost time is:" + (System.currentTimeMillis() - start));
    return obj;
}

}
``
target属性表示委托类对象。
InvocationHandler是负责连接代理类和委托类的中间类必须实现的接口。其中只有一个

public Object invoke(Object proxy, Method method, Object[] args)
函数需要去实现,参数:
proxy表示下面2.3 通过 Proxy.newProxyInstance() 生成的代理类对象。
method表示代理对象被调用的函数。
args表示代理对象被调用的函数的参数。
调用代理对象的每个函数实际最终都是调用了InvocationHandler的invoke函数。这里我们在invoke实现中添加了开始结束计时,其中还调用了委托类对象target的相应函数,这样便完成了统计执行时间的需求。
invoke函数中我们也可以通过对method做一些判断,从而对某些函数特殊处理。
(3)通过Proxy类新建代理类对象。
``
public class demo {

    public class Main {
        public static void main(String[] args) {
            // create proxy instance
            TimingInvocationHandler timingInvocationHandler = new       TimingInvocationHandler(new OperateImpl());
            Operate operate = (Operate)(Proxy.newProxyInstance(Operate.class.getClassLoader(), new Class[] {Operate.class},
                    timingInvocationHandler));

            // call method of proxy instance
            operate.operateMethod1();
            System.out.println();
            operate.operateMethod2();
            System.out.println();
            operate.operateMethod3();
        }
    }

}

这里我们先将委托类对象new OperateImpl()作为TimingInvocationHandler构造函数入参创建timingInvocationHandler对象;
然后通过Proxy.newProxyInstance(…)函数新建了一个代理对象,实际代理类就是在这时候动态生成的。我们调用该代理对象的函数就会调用到timingInvocationHandler的invoke函数(是不是有点类似静态代理),而invoke函数实现中调用委托类对象new OperateImpl()相应的 method(是不是有点类似静态代理)。
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
loader表示类加载器
interfaces表示委托类的接口,生成代理类时需要实现这些接口
h是InvocationHandler实现类对象,负责连接代理类和委托类的中间类
我们可以这样理解,如上的动态代理实现实际是双层的静态代理,开发者提供了委托类 B,程序动态生成了代理类 A。开发者还需要提供一个实现了InvocationHandler的子类 C,子类 C 连接代理类 A 和委托类 B,它是代理类 A 的委托类,委托类 B 的代理类。用户直接调用代理类 A 的对象,A 将调用转发给委托类 C,委托类 C 再将调用转发给它的委托类 B。

文章整理至http://www.jianshu.com/p/4765eef0c979

上一篇下一篇

猜你喜欢

热点阅读