面试题来源:https://mp.weixin.qq.com/s/p3l9wr4DX976Lr62-dYe8w 感谢洋神的分享
(一) java基础面试知识点
- java中==和equals和hashCode的区别
==是运算符,用来比较两个值、两个对象的内存地址是否相等;
equals是Object类的方法,默认情况下比较两个对象是否是同一个对象,内部实现是通过“==”来实现的。
hashCoed也是Object类里面的方法,返回值是一个对象的哈希码,同一个对象哈希码一定相等,但不同对象哈希码也有可能相等。
如果两个对象通过equals方法比较相等,那么他的hashCode一定相等;
如果两个对象通过equals方法比较不相等,那么他的hashCode有可能相等;
- int、char、long各占多少字节数
1字节: byte , boolean
2字节: short , char
4字节: int , float
8字节: long , double
- int与integer的区别
1、Integer是int的包装类,int则是java的一种基本数据类型
2、Integer变量必须实例化后才能使用,而int变量不需要
3、Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值
4、Integer的默认值是null,int的默认值是0
-
对java多态的理解
重写Overriding是父类与子类之间多态性的一种表现。重载Overloading是一个类中多态性的一种表现
- String、StringBuffer、StringBuilder区别
长度是否可变:
String 是被 final 修饰的,他的长度是不可变的,就算调用 String 的concat 方法,那也是把字符串拼接起来并重新创建一个对象,把拼接后的 String 的值赋给新创建的对象
StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象,StringBuffer 与 StringBuilder 中的方法和功能完全是等价的。调用StringBuffer 的 append 方法,来改变 StringBuffer 的长度,并且,相比较于 StringBuffer,String 一旦发生长度变化,是非常耗费内存的!
执行效率:
三者在执行速度方面的比较:StringBuilder > StringBuffer > String
应用场景:
如果要操作少量的数据用 = String
单线程操作字符串缓冲区下操作大量数据 = StringBuilder
多线程操作字符串缓冲区下操作大量数据 = StringBuffer
StringBuffer和StringBuilder区别:
是否线程安全:
StringBuilder 的方法不是线程安全的(不能同步访问)
StringBuffer是线程安全的。只是StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的
- 什么是内部类?内部类的作用
静态内部类是指被声明为static的内部类,他可以不依赖内部类而实例,而通常的内部类需要实例化外部类,从而实例化。静态内部类不可以有与外部类有相同的类名。不能访问外部类的普通成员变量,但是可以访问静态成员变量和静态方法(包括私有类型)
一个 静态内部类去掉static 就是成员内部类,他可以自由的引用外部类的属性和方法,无论是静态还是非静态。但是不可以有静态属性和方法、
局部内部类 就是定义在一个代码块的内类,他的作用范围是所在代码块,是内部类中最少使用的一类型。局部内部类跟局部变量一样,不能被public ,protected,private以及static修饰,只能访问方法中定义final类型的局部变量。
匿名内部类是一种没有类名的内部类,不使用class,extends,implements,没有构造函数,他必须继承其他类或实现其他接口。匿名内部类的好处是使代码更加简洁,紧凑,但是带来的问题是易读性下降。他一般应用于GUI编程来实现时间处理等
在使用匿名内部类时,需要牢记以下几个原则。
1》内部类没有构造方法
2》匿名内部类不能定义静态成员,方法和类
3》匿名内部类不能是public protected private static
4》只能创建匿名内部类的一个实例
5》一个匿名内部类可以在new后面,这个匿名类必须继承一个父类或实现接口
6》因为匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效
- 泛型中extends和super的区别
经常发现有List<? super T>、Set<? extends T>的声明,是什么意思呢?<? super T>表示包括T在内的任何T的父类,<? extends T>表示包括T在内的任何T的子类
- 父类的静态方法能否被子类重写
不能,父类的静态方法能够被子类继承,但是不能够被子类重写,即使子类中的静态方法与父类中的静态方法完全一样,也是两个完全不同的方法。
- final,finally,finalize的区别
final是关键字,当final用于修饰类时,这个类不可以被继承,所以自然也就不能是抽象类(abstract);当final修饰方法时,这个方法不能够被重写;当final修饰变量时,这个变量不能被修改。
finally是异常处理的出口,在异常处理流程中,如果有了finally代码块,则程序不管是否被捕捉到异常或者异常是否被处理,都要执行finally代码块中的程序。
finalize是方法名称,是Object中的方法,这个方法在对象被垃圾回收集收集之前进行调用,可以在此进行一些扩展,如果调用这个方法时,抛出了无法补货的异常,GC将终止对这个对象的回收,等到下次GC时再进行回收。
- Serializable 和Parcelable 的区别
一、对象为什么需要序列化
1.永久性保存对象,保存对象的字节序列到本地文件。
2.通过序列化对象在网络中传递对象。
3.通过序列化对象在进程间传递对象。
二、当对象需要被序列化时如何选择所使用的接口
1.在使用内存的时候Parcelable比Serializable的性能高。
2.Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC(内存回收)。
3.Parcelable不能使用在将对象存储在磁盘上这种情况,因为在外界的变化下Parcelable不能很好的保证数据的持续性。
(二) java深入源码级的面试题(有难度)
- 哪些情况下的对象会被垃圾回收机制处理掉?
jvm虚拟机的使用的是根寻路算法,其大致思想是看除堆区以外的内存区域能否通过引用链找到堆中的对象,找不到就证明该对象可以被回收。
- List,Set,Map的区别
List 是可重复集合,Set 是不可重复集合,这两个接口都实现了 Collection 父接口。
Map 未继承 Collection,而是独立的接口,Map 是一种把键对象和值对象进行映射的集合,它的每一个元素都包含了一对键对象和值对象,Map 中存储的数据是没有顺序的, 其 key 是不能重复的,它的值是可以有重复的。
List 的实现类有 ArrayList,Vector 和 LinkedList:
ArrayList 和 Vector 内部是线性动态数组结构,在查询效率上会高很多,Vector 是线程安全的,相比 ArrayList 线程不安全的,性能会稍慢一些。
LinkedList:是双向链表的数据结构存储数据,在做查询时会按照序号索引数据进行前向或后向遍历,查询效率偏低,但插入数据时只需要记录本项的前后项即可,所以插入速度较快。
Set 的实现类有 HashSet 和 TreeSet;
HashSet:内部是由哈希表(实际上是一个 HashMap 实例)支持的。它不保证 set 元素的迭代顺序。
TreeSet:TreeSet 使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序。
Map 接口有三个实现类:Hashtable,HashMap,TreeMap,LinkedHashMap;
Hashtable:内部存储的键值对是无序的是按照哈希算法进行排序,与 HashMap 最大的区别就是线程安全。键或者值不能为 null,为 null 就会抛出空指针异常。
TreeMap:基于红黑树 (red-black tree) 数据结构实现,按 key 排序,默认的排序方式是升序。
LinkedHashMap:有序的 Map 集合实现类,相当于一个栈,先 put 进去的最后出来,先进后出。
- HashSet与HashMap怎么判断集合元素重复?
HashSet要求不能存储相同的对象,HashMap要求不能存储相同的键
判断HashSet和HastMap中的两个对象相同或不同。我的推断是:先判断hashcode是否相等,再判断是否equals。
-
开启线程的三种方式?
1.继承Thread类,并复写run方法,创建该类对象,调用start方法开启线程。
2.实现Runnable接口,复写run方法,创建Thread类对象,将Runnable子类对象传递给Thread类对象。调用start方法开启线程。
3.创建FutureTask对象,创建Callable子类对象,复写call(相当于run)方法,将其传递给FutureTask对象(相当于一个Runnable)。 创建Thread类对象,将FutureTask对象传递给Thread对象。调用start方法开启线程。这种方式可以获得线程执行完之后的返回值。
- run()和start()方法区别
线程的run()方法是由java虚拟机直接调用的,如果我们没有启动线程(没有调用线程的start()方法)而是在应用代码中直接调用run()方法,那么这个线程的run()方法其实运行在当前线程(即run()方法的调用方所在的线程)之中,而不是运行在其自身的线程中,从而违背了创建线程的初衷;
- 如何控制某个方法允许并发访问线程的个数?
Semaphore semaphore = new Semaphore(5); //设置允许5个线程访问某方法
1、semaphore.acquire();
请求一个信号量,这时候信号量个数-1,当减少到0的时候,下一次acquire不会再执行,只有当执行一个release()的时候,信号量不为0的时候才可以继续执行acquire
2、semaphore.release();
释放一个信号量,这时候信号量个数+1
- 在Java中wait和seelp方法的不同
由于sleep()方法是Thread类的方法,因此它不能改变对象的机锁。所以当在一个Synchronized方法中调用sleep()时,线程虽然休眠了,但是对象的机锁没有被释放,其他线程仍然无法访问这个对象。
而wait()方法则会在线程休眠的同时释放掉机锁,其他线程可以访问该对象。