基础知识
JVM、JRE、JDK有什么联系与区别?
JVM是java虚拟机,能够将 class 文件中的字节码指令进行识别并调用操作系统向上的 API 完成动作。
JRE是java运行时环境,它主要包含两个部分,jvm 的标准实现和 Java 的一些基本类库。它相对于 jvm 来说,多出来的是一部分的 Java 类库。换句话说,JRE包含JVM。
JDK是java开发工具包,它集成了 jre 和一些好用的小工具。例如:javac.exe,java.exe,jar.exe 等。JDK包含JRE。
所以总得来说,JDK>JRE>JVM。
面向对象的特征有哪些?
有三大特征,继承,封装,多态。
为什么java可以实现跨平台?
因为java是编译成.class文件运行在JVM上的。针对不同的系统有不同的JVM实现,在不同的JVM实现上会映射到不同系统的 API 调用,从而实现代码的跨平台运行。
类的加载顺序?
静态成员变量、静态代码块、实例成员变量,实例代码块,构造器,实例方法。
接口和抽象类有什么共同点和不同点?
共同点:
1.都可以定义抽象方法,子类都要实现定义的抽象方法。
2.都不能被实例化,但是可以定义抽象类和接口类型的引用。
不同点:
1.接口没有构造器,抽象类可以定义构造器。
2.接口定义具体方法只能定义default修饰,抽象类可以直接定义具体方法。
3.接口的子类是实现接口,关键字是implements,抽象类的子类是继承,关键字是extends。
4.接口不能定义成员变量,只能定义常量。抽象类可以定义成员变量。
static关键字有哪些用法?
①修饰成员变量,用static修饰的成员变量就成为静态变量,静态变量只会存在一份,在类被加载时会初始化,且只会加载一次,通过类名访问。一般可以用static和final定义一些String类型,boolean类型,int类型的变量作为常量,可以减少资源的消耗。
②static修饰方法,该方法就被定义为静态方法,静态方法是不能被方法重写的,通过类名调用。一般用static定义一些工具类的方法。
③用static修饰代码块,该代码块就被定义为静态代码块,静态代码块在类初始化时被执行,且执行一次。一般用于初始化一些静态的成员变量的值。
Switch能用什么数据类型作为参数?
JDK1.5前:byte、short、char、int
JDK1.5:枚举
JDK1.7:String
枚举有哪些特点?在项目中如何使用?
特点:
1.枚举的构造器是私有的。
2.枚举不能被继承。
3.枚举是绝对的单例,即使是反序列化也无法创建多个实例。
使用场景:
当变量只能从一堆固定的值中取出一个时,那么就应该使用枚举。比如时间的单位,季度等等。
什么是方法重载?什么是方法重写?
方法重载,一个类中允许同时存在一个以上的同名方法,主要体现在方法参数的类型和数量不同,方法名相同,与访问修饰符和返回值类型都是无关的。口诀是”一同两不同“。
方法重写一般在继承中,子类重写父类的方法,既然是重写一遍,那么方法名和参数部分一定是相同的。只是实现的功能不同。声明为 final 的方法不能被重写,声明为 static 的方法不能被重写,声明为 private 的方法不能被重写。
静态变量和实例变量有什么不同?分别位于内存的什么区域?
1.静态变量使用static修饰,实例变量不需要。
2.静态变量在类被加载时就会分配内存空间,就可以使用。实例变量需要实例对象才会分配内存空间,才可以被引用,是属于实例的。
3.静态变量是存在于静态区(全局区)的,实例变量位于堆内存中。
java的内部类的分类有哪些?
实例内部类、静态内部类、局部内部类、匿名内部类。
break、continue、return 的作用是什么?
- break:结束循环。不仅可以结束其所在的循环,还可结束其外层循环。
- continue:跳过本次循环,开始下一次循环。
- return:不是专用于结束循环,而是用于结束方法。如果在循环中使用return,就会结束整个方法,循环当然也会结束。
Object类有哪些常用的方法?
toString()、equals()、hashCode()。
toString()默认输出对象的内存地址,一般不希望输出内存地址可以重写toString()方法。equals()方法用于比较对象是否相等,默认比较是内存地址,所以要正确比较两个对象是否值相等,此方法必须被重写。hashCode()方法用来返回其所在对象的物理地址(哈希码值),常会和equals()方法同时重写,确保相等的两个对象拥有相等的hashCode。==与equals()的区别?
equals()方法属于Object对象的,所以比较基础数据类型是不能使用equals()。必须使用==。
在默认情况下,equals()与==是一样的,都是比较内存地址。所以在业务逻辑中,我们一般会重写equals()方法。
equals()与hashCode()有什么联系?
1.equals()相等的两个对象他们的hashCode()肯定相等,也就是用equals()对比是绝对可靠的。
2.hashCode()相等的两个对象他们的equals()不一定相等,也就是hashCode()不是绝对可靠的。
在使用HashSet或者HashMap集合中,比较两个对象是否相等时,会先调用hashCode()比较,如果hashCode()相等,则会继续调用equals()比较,equals()也相等才会认为是同一个对象。如果hashCode()返回不相等,则认为是不相等的对象。
所以一般我们会同时重写hashCode()和equals()方法。
& 和 &&有什么区别?
&&具有短路的功能,也就是如果&&左边的条件为fasle就不再执行后面的条件判断。&则会执行完左右两边的条件判断。
final、finalize()、finally{}分别有什么作用?
final修饰类,表明这个类不可被其他类继承。final修饰成员变量,表示此变量为常量,只能在初始化时被赋值一次,赋值后不能修改。final修饰方法。把方法锁定,不能被子类重写,以防止子类对其进行更改。finalize()是Object里定义的,也就是说每一个对象都有这么个方法。这个方法在gc启动,该对象被回收的时候被调用。一个对象的finalize()方法只会被调用一次。finally作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块。
Cloneable接口有什么作用?
Cloneable接口是一个标记接口,实现了此接口,表示可以使用clone()方法,没有实现此接口使用clone()会抛出CloneNotSupportedException异常。
什么是浅克隆,什么是深克隆?
浅克隆是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。
深克隆不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。
什么是序列化?什么是反序列化?
序列化:把对象转换为字节序列的过程称为对象的序列化。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
Serializable接口有什么作用?
Serializable接口是一个标记接口,一个类只有实现了Serializable接口,它的对象才是可序列化的。否则序列化时会报NotSerializableException异常。如果不显性声明serialVersionUID,则会默认生成一个。为了serialVersionUID的确定性,最好是显性声明。
String、StringBuffer、StringBuilder有什么区别?
String被声明为final class,是由定义final的字符数组实现的,因为它的不可变性,所以拼接字符串时候会产生很多无用的中间对象,如果频繁的进行这样的操作对性能有所影响。StringBuffer是由定义了临时数据transient的字符数组实现的,提供append()和add()方法,可以将字符串添加到已有序列的末尾或指定位置,它的本质是一个线程安全的可修改的字符序列,所有修改数据的方法都加上synchronized。性能相对StringBuilder会差一点。-
StringBuilder和StringBuffer本质上没什么区别,区别是去掉了保证线程安全的synchronized,减少了开销,性能有所提高。什么是泛型?什么是泛型的上界和下界?
Java 泛型是 JDK1.5中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
上界用extends关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
下界用super进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至Object。什么是反射机制?
Java反射机制是在运行状态中,对于任意一个类,都能够获得这个类的所有属性和方法,对于任意一个对象都能够调用它的任意一个属性和方法。这种在运行时动态的获取信息以及动态调用对象的方法的功能称为Java的反射机制。
获取Class对象的方式有哪些?
- 通过
Object类中的getClass()方法,想要用这种方法必须要明确具体的类并且创建该类的对象。 - 所有数据类型都具备一个静态的属性
.class来获取对应的Class对象。但是还是要明确到类,然后才能调用类中的静态成员。 - 通过
Class.forName()方法完成,必须要指定类的全限定名,由于前两种方法都是在知道该类的情况下获取该类的字节码对象,因此不会有异常,但是Class.forName()方法如果写错类的路径会报ClassNotFoundException的异常。java中的异常有哪几种异常?
Throwable类是Java异常类型的顶层父类,Throwable包含了Error和Excetion。Excetion分为两种,一种是**非运行时异常(又称为检查异常),另一种是运行时异常(RuntimeException)**。java是如何处理异常的?
Error是程序无法处理的, 比如OutOfMemoryError、OutOfMemoryError等等, 这些异常发生时,JVM一般会终止线程。- 运行时异常(
RuntimeException),如NullPointerException、IndexOutOfBoundsException等,是在程序运行的时候可能会发生的,所以程序可以捕捉,也可以不捕捉。这些错误一般是由程序的逻辑错误引起的,程序应该从逻辑角度去尽量避免。 - 非运行时异常是
RuntimeException以外的异常,是Exception及其子类,这些异常从程序的角度来说是必须经过捕捉检查处理的,否则不能通过编译。如IOException、SQLException等。java集合、IO流、日期处理等
常用的集合有哪些?
常用集合有Map、List、Set。HashMap是线程安全的吗?
不是线程安全的。如何使HashMap线程安全?
使用Collections类的synchronizedMap()方法包装。使用1
Map<String, Object> map = Collections.synchronizedMap(new HashMap<>());
java.util.concurrent包下的ConcurrentHashMap类也可以获得线程安全的Map。使用1
ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap();
Hashtable类,也可以获得线程安全的Map1
Map<String,Object> hashtable = new Hashtable<>();
HashMap和Hashtable的区别是什么?
Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map接口。Hashtable是线程安全的,HashMap是线程不安全的。-
Hashtable中,key和value都不允许出现null值。 -
HashTable在不指定容量的情况下的默认容量为11,而HashMap为16,Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。HashMap是如何解决哈希冲突的?
- 在JDK1.8前,
HashMap是采用链表法解决哈希冲突的。当put()一个值到Map时,会通过Key拿到一个哈希值,通过哈希值获取数组下标,先查询是否存在该hash值。若不存在,则直接以Entry<V,V>的方式存放在数组中。若存在,则再调用equals()方法对比key是否相同,若hashcode()值和key都相同,则替换value,若hashcode()值相同,key不相同,则形成一个单链表,将hashcode()值相同,key不同的元素以Entry<V,V>的方式存放在链表中,这样就解决了哈希冲突。 - JDK1.8以后,当链表的长度达到某个限制值(默认是8),就会转换成红黑树,提高性能。
HashMap初始大小是多少?负载因子是多少?
默认的数组初始大小是16。负载因子是0.75。
(为什么初始值是2的n次方,为什么负载因子取0.75,这两个问题可以网上找资料看看,这里就不详述了)
简述一下HashMap的扩容机制?
HashMap是懒加载的,当调用put()方法时,会先初始化Map的大小,默认数组长度是16,负载因子是0.75,所以阈值是12。当HashMap元素的个数超过阈值时,就会把数组的大小扩展到原来的2倍,然后重新计算每个元素在数组中的位置。
List有哪些常用的子类?
ArrayList和LinkedList。
ArrayList和LinkedList有什么区别?
- 底层数据结构不同。
ArrayList基于数组+动态扩容实现的,LinkedList基于双向链表实现。从储存结构上分析,LinkedList更加占内存,因为每个节点除了存储数据外还要存储指向前节点的引用和指向后节点的引用。 - 效率不同。当随机访问时,
ArrayList是基于数组下标访问,查询效率较高,但是由于数组的长度是固定的,所以当添加的元素到一定的阈值时会扩容数组,消耗性能,增删效率偏低。LinkedList在查询时,需要从前到后依次遍历,所以查询效率不高,但是在增删时只需要更改节点的引用,开销较少,所以增删效率较高。List集合排序的方式有哪些?
使用List接口定义的sort()方法。使用1
list.sort(Comparator.comparingInt(User::getAge));
Collections的sort()方法,排序的对象需要实现Comparable接口,重写compareTo()方法。使用1
2
3
4
5
6
7
8//实现Comparable接口
public class User implements Comparable<User> {
//重写compareTo方法
public int compareTo(User user) {
return Integer.compare(this.getAge(), user.getAge());
}
}Collections的sort()方法使用Stream流操作的1
2
3Collections.sort(list);
//如果不想实现Comparable接口,也可以使用这个方法
Collections.sort(list,Comparator.comparingInt(User::getAge));sort()方法,传入一个Comparator接口。1
list.stream().sorted(Comparator.comparingInt(User::getAge)).collect(Collectors.toList());
栈和队列的特点分别是什么?在java中有哪些实现的类?
栈是先进后出,队列是先进先出。Stack类是栈在java中的实现,继承Vector类,底层是基于数组存储数据。Queue接口是队列在java中的代表,Queue接口有几个常用的子类ArrayDeque、LinkedList。IO、NIO有什么区别?
IO包括:File、OutputStream、InputStream、Writer,Reader。
NIO三大核心:selector(选择器),channel(通道),buffer(缓冲区)
NIO与IO区别在于,IO面向流,NIO面向缓冲区。IO是阻塞,NIO是非阻塞。如何进行日期的转换?
使用SimpleDateFormat类进行String和Date之间的转换。如何获取上一年的今天的日期?
使用Calendar对象。如下所示:1
2
3
4
5
6
7
8//创建Calendar对象
Calendar calendar = Calendar.getInstance();
//设置年份,当前年份减去一年
calendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR) - 1);
//以下是打印结果
Date time = calendar.getTime();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(time));//2019-06-08 23:43:14 正确BigDecimal类型一定不会失真吗?
不一定。
参数类型为double的构造方法的结果有一定的不可预知性,是有可能产生失真的。使用参数类型1
2
3BigDecimal bigDecimal = new BigDecimal(0.99);
System.out.println(bigDecimal);//结果如下
//0.9899999999999999911182158029987476766109466552734375String构造方法是完全可预知的,不会产生失真。所以在开发中推荐使用参数类型String构造方法。java并发编程
为什么要使用多线程?
- 避免主线程阻塞,可以使用多线程做成异步调用。
- 提升性能,充分利用CPU资源。
创建线程有哪几种方法?
- 通过继承
Thread类创建线程类。 - 通过实现
Runnable接口创建线程类。 - 通过实现
Callable接口创建线程类。如何获取多线程的返回值?
使用Callable和FutureTask接口,获取返回值。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public static void main(String[] args) throws Exception {
try {
//使用匿名内部类创建Callable
Callable callable = () -> "hello call";
FutureTask futureTask = new FutureTask(callable);
//执行线程
new Thread(futureTask).start();
if (!futureTask.isDone()) {
//获取返回值
System.out.println(futureTask.get());
}
} catch (Exception e) {
e.printStackTrace();
}
}多线程的生命周期?
新建状态、就绪状态、运行状态、阻塞状态、死亡状态如何进行线程之间的通信?
- 使用
synchronized、wait()、notify() - 使用JUC工具类
CountDownLatch - 使用
ReentrantLock结合Condition - 基本
LockSupport实现线程间的阻塞和唤醒
以上几种方式的具体实现代码,可以网上找一下资料,这里不演示了。
说说 sleep() 方法和 wait() 方法区别和共同点?
相同点:
sleep()方法和wait()方法都用来改变线程的状态,能够让线程从运行状态,转变为休眠状态。
不同点:
sleep()方法是Thread类中的静态方法,而wait()方法是Object类中的方法。sleep()方法可以在任何地方调用,而wait()方法只能在同步代码块或同步方法中使用(即使用synchronized关键字修饰的)。- 这两个方法都在同步代码块或同步方法中使用时,
sleep()方法不会释放对象锁。而wait()方法则会释放对象锁。如何停止线程?
- 使用退出标志,使线程正常退出,也就是当
run()方法完成后线程终止。 - 使用
stop()方法强行终止(不推荐),可能会出现数据不同步,或者资源未释放等问题。 - 使用
interrupt()方法中断线程。什么是线程的死锁?如何避免线程死锁?
多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进,这种现象称为死锁。
避免死锁的三种方式: