目录
2、volatile、Lock、transient 哪个关键字不能用来处理线程安全
3、Hashtable 和 HashMap 的区别是?(容易忽略的两点)
9、final、finally、finalize三个关键字的区别
前言
讲实在的,刷了三遍牛客网844道Java题,就是为了检测一下Java的基础,没想到第一遍的时候正确率大概30%~40%左右,第二遍的才差不多稳固在90%左右,第三遍的时候可谓是感悟良多啊~ 于是打算写一篇Java基础题目的易错总结,带你清扫Java基础面试障碍;
1、子类通过哪些办法,可以调用继承自父类的方法?
子类构造函数中调用父类构造函数用super;
若想调用父类中被重写的方法,用super;
未被重写的方法可以直接调用。
2、volatile、Lock、transient 哪个关键字不能用来处理线程安全
volatile关键字语义是禁用缓存,也就是保证读到的是内存中的值,保证内存的可见性,禁止指令重排序,但是他不具备原子性(满足线程安全需要同时具备:原子性,可见性,有序性);
Lock接口提供了与synchronized关键字类似的同步功能,但需要在使用时手动获取锁和释放锁,因此可以处理线程安全;
transient关键字 简单地说,就是让某些被修饰的成员属性变量不被序列化,因此不可以处理线程安全;
补充:什么是序列化?就是将java对象转换为字节序列的过程称为对象的序列化;
3、Hashtable 和 HashMap 的区别是?(容易忽略的两点)
HashMap 是内部基于哈希表实现,该类继承AbstractMap,实现Map接口;
Properties 类 继承了 Hashtable 类,而 Hashtable 类则继承Dictionary 类;
4、如何声明了一个适合于创建50个字符串对象数组的变量?
注意:Java声明一个数组时,不能直接限定数组长度,只有在创建实例化对象时,才能对给定数组长度;
String a[];
String[] a;
Object a[];
5、什么是强引用、软引用、弱引用、虚引用?
强引用:
Object obj = new Object(); //只要obj还指向Object对象,Object对象就不会被回收
只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收。
软引用:
软引用是用来描述一些非必需但仍有用的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。
弱引用:
弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。
虚引用:
虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收;
6、异常有哪两种?分别是干什么的?
java中的异常通常分为编译时异常和运行异常。编译时异常需要我们手动的进行捕捉处理,也就是我们用try....catch块进行捕捉处理。对于运行时异常只有在编译器在编译运行时才会出现,这些不需要我们手动进行处理。
7、如何实现多态?
Java通过方法重写和方法重载实现多态;
方法重写是指子类重写了父类的同名方法;
方法重载是指在同一个类中,方法的名字相同,但是参数列表不同;
8、集合框架图,你忘记了吗?
9、final、finally、finalize三个关键字的区别
final:可用来定义变量、方法传入的参数、类、方法。
finally:只能跟在try/catch语句中,并且附带一个语句块,表示最后执行。
finalize:是垃圾回收器操作的运行机制中的一部分,进行垃圾回收器操作时会调用finalize方法;
10、重写和重载的区别
重载:
在同一个类中,如果多个方法,方法名相同、参数不同,即称为重载。
在编译器眼里,方法名称+参数类型+参数个数,组成一个唯一键,称为方法签名,JVM通过这个唯一键决定调用哪个重载的方法。
重写:
方法重写是存在子父类之间的,子类定义的方法与父类中的方法具有相同的名字、参数、返回类型。
11、throw 和throws 的区别
提示:以下可以按照序号顺序对比着看~
throw:
throw是用来抛出一个具体的异常类型;
一般出现在函数体;
throw只能用于抛出一种异常;
throws:
throw是用来声明一个方法可能产生的所有异常;
一般出现在函数头;
throws可以抛出多个异常;
示例如下:
//throw使用如下
private void fun1() {
Scanner in = new Scanner(System.in);
int ans = in.nextInt();
if(ans == 0) {
throw new NumberFormatException("除数不能是0");
} else {
int result = 10 / ans;
System.out.println(result);
}
}
//throws使用如下
private void fun2(Runnable task) throws InterruptedException {
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
queue.put(task);
}
12、谈谈你对Java反射的了解
1.(从定义的角度)反射就是在程序运行期间动态获取对象的属性和方法的功能叫做反射。
2.(从功能的角度)在程序运行期间,对任意一个类(除了枚举)能够直到他的方法和属性,对于任意一个对象,都能直到他的方法和属性。
3.(从用法的角度)获取class对象的三种方式:getClass(); xx. class; Class.forName("xx");
4.(从优点及缺点的角度回答)优点:运行期间能够动态的获取类,提高代码的灵活性;缺点:性能上相比较直接通过类或对象获取方法和属性,要慢的多
5.(从应用场景的角度回答)例如模拟 Spring框架、模拟Spring 加载 XML 配置文件、JDBC操作进行数据库连接。
13、常见的垃圾回收算法有哪些?
标记清除法:
过程:将垃圾标记出来,直接清除;
分析:缺点就是有可能会造成大量的内存碎片,可能导致当需要申请一块足够大的、连续的的内存空间时申请不了,另外,标记,和清除这两步的效率也不是很高;
复制算法:
过程:将内存分成两块大小相等的空间,只使用其中的一块空间;一轮GC下来,存活的对象将会被复制到另一个未被使用的空间上,而被GC的那部分空间将被全部清除;
分析:解决内存碎片问题,但是空间利用率上甚至低于标记清除法,并且如果一轮GC下来,存活下来的对象很多,那么复制的开销也是很大的;
标记整理法:
过程:GC时跟标记清除法一样,先标记出垃圾,然后将存活的对象向内存的一端覆盖,再将存活对象这端边界以外的空间全部清除;
分析:不仅解决了内存碎片问题,还解决空间利用率问题,但是搬运操作比较耗时;
分代算法:
过程:将内存区域化成为两大块,分别是:新生代、老年代;在新生代中继续被划分成三大块,分别是:伊甸区、两个生存区;新的对象被创建好会先来到伊甸区,经历一轮GC后存活下来的对象会通过复制算法来到生存区,生存区也会进行GC,每轮GC下来,都会被复制到另一个生存区(存活对象在两个生存区来回复制),GC到达了一定次数后,存活下来的对象就会被分配到老年代;老年代采用标记整理法进行GC,但是相比于新生代,GC的频率要低;特判:如果创建的一个对象比较大,会直接放入老年代,因为既然创建这么大一个对象,没有理由刚创建好,就被一轮GC释放掉;
14、谈谈类加载的过程?
加载过程主要可以分为三个阶段,细分可以分为五个阶段:加载、连接(验证、准备、解析)、初始化;
加载:
找到.class文件,读取文件内容,按照.class文件标准内容规范格式进行解析;
连接:
(1)验证:检查当前.class文件格式是否符合规范格式要求;
(2)准备:给静态变量分配内存空间;例如:int a = 10; 这里就会给 a 变量分配4个字节的内存空间,并初始化为0;
(3)解析:初始化常量字符串,将符号引用替换成直接引用;例如:String str = "hello";在类加载之前没有分配内存空间,只是给str一个占位符,表示这里是"hello"这个字符串的位置,类加载之后给"hello"分配了内存空间,就会把这个占位符去掉,换成"hello"的地址;
初始化:
就是对类进行初始化;
初始化顺序:父类(静态变量、静态代码块)–>子类(静态变量、静态代码块)–>父类(变量、代码块)–> 父类构造器–>子类(变量、初始化块)–>子类构造器。
15、访问修饰符
16、双亲委派模型
涉及到的类加载器:
1. Bootstrap ClassLoader :负责加载标准库中的类;
2. Extension ClassLoader:负责加载JVM扩展的库的类(标准库中没有,但JVM自己实现出了);
3. Application ClassLoader :负责加载我们自己的项目中的自定义类;
工作过程:(下图)
17、如何解决HashMap冲突问题
解决哈希冲突两种常见的方法是:闭散列(开放定址法)和开散列(链地址法);
闭散列(开放地址法):
具体做法:当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以 把key存放到冲突位置中的“下一个” 空位置中去;
开散列(链地址法):
首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子 集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
例如下图:开散列每一个桶都放的是冲突元素;
18、排序算法的时间复杂度&空间都复杂度比较
19、接口和抽象类对比
提示:以下可以按照序号顺序对比着看~
接口:
(1)接口是公开的,里面不能有私有的方法或变量;
(2)接口可以实现多重继承;
(3)接口的一定要实现接口里定义的所有方法;
(4)接口中定义的变量只能是public static final类型,并且默认即为public static final类型,方法定义默认为public abstract类型;
(5)接口中不能包含静态方法;
(6)JDK 1.8时,接口中的方法可以是public的,也可以是default的;
抽象类:
(1)抽象类是可以有私有方法或私有变量的;
(2)一个类只能继承一个超类;
(3)实现抽象类可以有选择地重写需要用到的方法;
(4)抽象类中的方法不一定是abstract;
(5)抽象类中可以包含静态方法;
(6)JDK 1.8时,抽象类的方法默认访问权限为default;
总的来说:
接口体现的是一种规范和实现分离的设计哲学,代码编写过程中充分利用接口可以很大程度的降低程序各个模块之间的耦合,从而提高系统的可扩展性和可维护性。基于这一原则,很多软件架构更提倡面向接口编程而不是实现类编程。(更提倡使用接口,少用抽象类)
20、super关键字的作用
(1)用来访问父类被隐藏的非私有成员变量
(2)用来调用父类中被重写的方法
(3)用来调用父类的构造函数
总的来说,super代表父类对应的对象,所以用super访问在子类中无法直接使用的父类成员和方法;
21、super与this关键字对比
提示:以下可以按照序号顺序对比着看~
super():
(1)super()表示调用父类构造函数,在构造函数中必须出现在第一行;
(2)super()指的是对象,所以,不可以在static环境中使用。包括:static变量,static方法,static语句块
this():
(1)this()调用自己的构造函数,在构造函数中必须出现在第一行;
(2)this()指的是对象,所以,不可以在static环境中使用。包括:static变量,static方法,static语句块
综上分析:super()和this()必须在构造函数第一行,所以这一点也表明他俩不能在一个构造函数中;
22、servlet的声明周期
总结
初始阶段,实例化的时候,会调用一次init;
每次收到请求,就会调用service,service根据请求中不同的方法,调用不同的doXXX;
结束销毁之前,调用destroy,用来关闭数据库连接、停止后台线程、把 Cookie 列表或点击计数器写入到磁盘,并执行其他类似的清理活动;
23、Java中异常类的描述
1.Exception(异常) :
是程序本身可以处理的异常。
2.Error(错误):
是程序无法处理的错误。这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执应用时,一般不需要程序处理。
3.检查异常(编译器要求必须处置的异常) :
除了Error,RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。
4.非检查异常(编译器不要求处置的异常):
包括运行时异常(RuntimeException与其子类)和错误(Error)。
24、volatile的作用
(1)禁止了指令重排序;
(2)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量值,这个新值对其他线程是立即可见的;
(3)不保证原子性(线程不安全);
总的来讲:synchronized保证三大性,原子性,有序性,可见性;volatile保证有序性,可见性,不能保证原子性,所以synchronized可以解决线程安全问题,而volatile不可以;
25、final关键字使用易错点
(1)final 定义的方法里,不是必须要用 final 定义方法内的变量。
(2)final 定义的变量,可以在不是必须要在定义的同时完成初始化,也可以在构造方法中完成初始化。
(3)final修饰方法,不能被子类重写,但是可以被重载。
(4)final 定义变量,可以用 static 也可以不用。
26、Java中哪种复制数组效率最高?
分析:
在System类源码中给出了arraycopy的方法,是native方法,也就是本地方法,肯定是最快的。而Arrays.copyOf(注意是Arrays类,不是Array)的实现,在源码中是调用System.copyOf的,多了一个步骤,肯定就不是最快的。
结论:
效率 :System.arraycopy > clone > Arrays.copyOf > for循环;
27、Object中的方法
1.clone方法
保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。
主要是JAVA里除了8种基本类型传参数是值传递,其他的类对象传参数都是引用传递,我们有时候不希望在方法里讲参数改变,这是就需要在类中复写clone方法。
2.getClass方法
final方法,获得运行时类型。
3.toString方法
该方法用得比较多,一般子类都有覆盖。
4.finalize方法
该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。
5.equals方法
该方法是非常重要的一个方法。一般equals和==是不一样的,但是在Object中两者是一样的。子类一般都要重写这个方法。
6.hashCode方法
该方法用于哈希查找,可以减少在查找中使用equals的次数,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。
一般必须满足obj1.equals(obj2)==true。可以推出obj1.hash- Code()==obj2.hashCode(),但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价。
如果不重写hashcode(),在HashSet中添加两个equals的对象,会将两个对象都加入进去。
7.wait方法
wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生。
(1)其他线程调用了该对象的notify方法。
(2)其他线程调用了该对象的notifyAll方法。
(3)其他线程调用了interrupt中断该线程。
(4)时间间隔到了。
此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。
8.notify方法
该方法唤醒在该对象上等待的某个线程。
9.notifyAll方法
该方法唤醒在该对象上等待的所有线程