👩🏻 作者:一只IT攻城狮 ,关注我不迷路
❤️《java面试核心知识》突击系列,持续更新…
💐 面试必知必会学习路线:Java技术栈面试系列+SpringCloud项目实战学习路线
📝再小的收获x365天都会成就不一样的自己,一起学习,一起进步。
文章目录
- 1、JDK 和 JRE 有什么区别?
- 2、Java为什么能跨平台?
- 3、Java基本数据类型
- 1)Java中数据类型
- 2)八大基本数据类型
- (1)数据类型转换
- (2)类型提升
- (3)基本数据内存中分配
- (4)值的初始化
- 4、java中的异常分类及处理
- 1)异常分类
- 2)异常的处理方式
- (1)遇到问题不进行具体处理,而是继续抛给调用者 (throw,throws)
- (2)try catch 捕获异常针对性处理方式
- 3)Throw 和 throws 的区别
- (1)位置不同
- ( 2)功能不同
- 5、== 和 equals 的区别是什么?
- 1)String的比较
- 2)Integer的比较
- 3)两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?
- 6、创建对象的几种方法
- 1)new关键字
- 2)Class 对象的 newInstance()
- 3)调用 Constructor 对象的 newInstance()
- 4)Clone
- 5)反序列化
- 7、重载和重写
- 1)重写(Override)
- 2)重载(Overload)
- 8、权限修饰符
- 9、String、String StringBuffer 和 StringBuilder 的区别是什么?
- 10、Collection与Collections的区别
- 1)Collection接口
- 2)Collections集合类
- 11、ArrayList和linkedList的区别
- 12、HashMap和HashTable的区别
- 13、Java自动装箱与拆箱
- 14、线程和进程的区别
- 15、什么是java的局部变量、成员变量和全局变量?
- 16、Java的四种引用,强弱软虚
- 1)强引用
- 2)软引用
- 3)弱引用
- 4)虚引用
- 17、volatile关键字
- 18、final有哪些用法?
- 19、static都有哪些用法
- 20、a=a+b与a+=b有什么区别吗?
- 21、BIO、NIO、AIO有什么区别
- 22、如何实现线程的同步
- 23、什么是Java的序列化
- 24、说说你对内部类的理解
- 1)成员内部类(Member Inner Class):
- 2)静态内部类(Static Nested Class):
- 3)局部内部类(Local Inner Class):
- 4)匿名内部类(Anonymous Inner Class):
- 5)使用场景
- 25、JDK动态代理与CGLIB实现的区别
- 1)JDK动态代理
- 2)CGLIB代理
- 26、深拷贝和浅拷贝区别
- 27、构造器是否可被重写
- 28、ThreadLocal有哪些应用场景
- 29、对CountDownLatch的理解
- 30、接口和抽象类有什么区别?
- 31、获取一个类Class对象的方式有哪些?
- 32、String str="i"与 String str=new String("i")一样吗?
- 33、Object类常用方法
- 34、ThreadLocal的理解?
- 35、sleep() 和 wait() 有什么区别?
- 36、notify和notifyAll的区别
- 37、try catch finally,try里有return,finally还执行么?
- 38、深拷贝和浅拷贝的区别是什么?
- 39、HashMap 与ConcurrentHashMap 的异同
- 40、Java 中 IO 流分为几种?
- 41、守护线程(服务线程)
- 42、红黑树有哪几个特征?
- 43、线程的 run() 和 start() 有什么区别和关系?
- 44、多线程的上下文切换
- 1)上下文
- 2)上下文切换的内容
- 45、get方式和post方式有何区别
- 46、Java中用到的线程调度算法
- 1)调度
- 2)算法
- 47、创建线程有哪几种方式?
- 48、线程有哪些状态?
- 49、线程池都有哪些状态?
- 50、线程池实现原理
- 51、线程池4种拒绝策略
- 52、Java中CycliBarriar和CountdownLatch的区别
- 53、怎么检测一个线程是否拥有锁
- 54、不可变对象
- 55、线程池中 submit() 和 execute() 方法有什么区别?
- 56、java反射
- 57、instanceof 关键字的作用
- 58、Java 序列化中如果有些字段不想进行序列化,怎么办?
- 59、&和&&的区别?
- 60、3*0.1 == 0.3返回值是什么
- 61、用最有效率的方法计算 2 乘以 8?
- 62、float f=3.4;是否正确?
- 63、Java 中的Math.round(11.5) 等于多少?Math.round(-11.5)等于多少?
- 64、switch 是否能作用在 byte 上,是否能作用在 long 上?
- 65、char 型变量中能不能存贮一个中文汉字?
- 66、String s = new String(“xyz”);创建了几个字符串对象?
- 67、如何实现字符串的反转及替换?
- 68、怎样将 GB2312 编码的字符串转换为 ISO-8859-1 编码的字符串?
- 69、阐述 final、finally、finalize 的区别
- 70、是否可以继承 String 类?
- 71、为什么重写equals方法必须要重写hashCode方法
- 72、Java泛型与泛型擦除
- 1)Java泛型好处:
- 2)泛型擦除:
- 73、静态变量和实例变量的区别
- 74、什么是死锁?
- 75、线程的 sleep()方法和 yield()方法有什么区别?
- 76、事务的 ACID 是指什么?
- 77、什么是线程局部变量?
- 78、JDBC 操作数据库的步骤
- 79、怎么确保一个集合不能被修改?
- 80、Statement 和 PreparedStatement 有什么区别?哪个性能更好?
- 81、用哪两种方式来实现集合的排序?
- 82、poll() 方法和 remove() 方法的区别?
- 83、Java 中的 LinkedList 是单向链表还是双向链表?
- 84、Java 中的 TreeMap 是采用什么实现的?
- 85、Java 中Comparator 与 Comparable 有什么不同?
1、JDK 和 JRE 有什么区别?
JDK
:java development kit (java开发工具),JDK 是用于开发 Java 程序的最小环境。
JRE
:java runtime environment (java运行时环境),是提供给 Java 程序运行的最小环境。
JRE是java运行时环境而JDK是java开发工具包。运行java程序只需安装JRE。如果需要编写java程序,需要安装JDK。但是JRE可以独立安装。
JDK包含JRE,同时还包含了编译 Java 源码的编译器 Javac,还包含了很多 Java 程序调试和分析的工具。
JRE包含了java虚拟机、java基础类库。是使用java语言编写的程序运行所需要的软件环境,是提供给想运行java程序的用户使用的。
Tip:mac怎么查看jdk目录呢?
终端输入:/usr/libexec/java_home -V会显示jdk版本和所在目录
command + shift +G前往要去的路径
2、Java为什么能跨平台?
Java是跨平台的原因是因为Java代码是运行在JVM上的,而不是直接运行在操作系统上。
这意味着,Java程序的代码不会直接与操作系统交互,而是通过JVM来间接交互。java是半编译语言,java源程序在经过java编译器编译后成为java字节码文件,再由运行在不同平台上的虚拟机(例如:windows、Linnux)将相同的字节码解释运行,转换成具体平台上的机器指令。从而实现了一次编写,到处运行。
3、Java基本数据类型
1)Java中数据类型
2)八大基本数据类型
- 整型的byte、short、int、long;
- 字符型的char;
- 浮点型的float、double;
- 布尔型的boolean。
将6种数据类型按取值范围的大小顺序排列一下:
double > float > long > int > short > byte
(1)数据类型转换
取值范围从小转换到大,是可以直接转换的,
在自动类型转化中、int–> float、long–> float、long–> double、float --> double
可能精度损失外,自动转换不会出现任何运行时异常。而从大转成小,则必须使用强制转换。
(2)类型提升
在表达式计算过程中会发生类型转换,这些类型转换就是类型提升,类型提升规则如下:
所有 byte/short/char 的表达式都会被提升为 int
除以上类型,其他类型在表达式中,都会被提升为取值范围大的那个类型,例如有一个操作数为double,整个表达式都会被提升为double
(3)基本数据内存中分配
基本数据类型它存放于“栈”中而不是存放于“堆”中。
(4)值的初始化
当基本数据类型作为类成员变量时,java 会对其进行初始化。且初始化的值分别为
而局部变量 在没有被显示初始化情况下,jdk 同样也会自动赋予他默认值,可不同之处就在于,你无法引用他,因为编译器会在你要引用的位置报错。
4、java中的异常分类及处理
1)异常分类
异常:执行中发生的不正常情况。
Throwable 是 Java 语言中所有错误或异常的超类。下一层分为 Error
和 Exception
Error:JVM系统内部错误、资源耗尽等严重情况。比如Stack OverflowError和 Out Of Memory。
Exception:空指针访问、试图读取不存在的文件、网络连接中断、数组下标越界等。
程序执行过程,是先编译后执行。所以从程序执行的过程可以将异常进一步分为:
编译时异常:执行javac.exe命名时,可能出现的异常
运行时异常:执行java.exe命名时,出现的异常
2)异常的处理方式
(1)遇到问题不进行具体处理,而是继续抛给调用者 (throw,throws)
抛出异常有三种形式,一是 throw,一个 throws,还有一种系统自动抛异常。
public static void main(String[] args) {
String s = "abc";
if(s.equals("abc")) {
throw new NumberFormatException();
} else {
System.out.println(s);
}
}
int div(int a,int b) throws Exception{
return a/b;}
(2)try catch 捕获异常针对性处理方式
3)Throw 和 throws 的区别
(1)位置不同
throws 用在函数上,后面跟的是异常类,可以跟多个;而 throw 用在函数内,后面跟的 是异常对象。
( 2)功能不同
throws 用来声明异常,让调用者只知道该功能可能出现的问题,可以给出预先的处理方式;throw 抛出具体的问题对象,执行到 throw,功能就已经结束了,跳转到调用者,并将具体的问题对象抛给调用者。也就是说 throw 语句独立存在时,下面不要定义其他语句,因为执行不到。
throws 表示出现异常的一种可能性,并不一定会发生这些异常;throw 则是抛出了异常,执行 throw 则一定抛出了某种异常对象。
两者都是消极处理异常的方式,只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。
5、== 和 equals 的区别是什么?
== 对于基本类型:比较的是值是否相同;
对于引用类型:比较的是引用是否相同;
关联知识点:
1)String的比较
String x = "string";
String y = "string";
String z = new String("string");
System.out.println(x== y); // true
System.out.println(x== z); // false
System.out.println(x.equals(y)); // true
System.out.println(x.equals(z)); // true
原因:String 、Integer 等重写了 Object 的 equals 方法,把引用比较改成了值比较。所以一般情况下 equals 比较的是值是否相等。
2)Integer的比较
Integer a1 = 1;
Integer a2 = 1;
Integer b1 = 128;
Integer b2 = 128;
System.out.println(al== a2); //true
System.out.println(b1== b2); //false
原因:Integer类内部通过静态内部类提供了一个缓存池,范围在-128~127之间,如果超过这个范围 Integer值都是new出来的新对象。
Integer的valueOf方法的具体实现:
public static Integer valueOf(int i) {
if(i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}
其中IntegerCache类的实现为:
private static class IntegerCache {
static final int high;
static final Integer cache[];
static {
final int low = -128;
// high value may be configured by property
int h = 127;
if (integerCacheHighPropValue != null) {
// Use Long.decode here to avoid invoking methods that
// require Integer's autoboxing cache to be initialized
int i = Long.decode(integerCacheHighPropValue).intValue();
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - -low);
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
private IntegerCache() {}
}
从这2段代码可以看出,在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,
便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。
3)两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?
不对。我们两个字符串的哈希值很可能一样,但是反过来不一定成立。
String str1 = "通话";
String str2 = "重地";
System. out. println(String. format("str1:%d | str2:%d", str1. hashCode(),str2. hashCode()));
System. out. println(str1. equals(str2));
执行的结果:
str1:1179395 | str2:1179395
false
6、创建对象的几种方法
1)new关键字
使用new关键字创建对象,是最常见也是最简单的创建对象的方式。
2)Class 对象的 newInstance()
使用Class对象的newInstance()方法来创建该 Class 对象对应类的实例,但是这种方法要求该Class对象对应的类有默认的空构造器。
3)调用 Constructor 对象的 newInstance()
先使用 Class 对象获取指定的 Constructor 对象,再调用 Constructor 对象的newInstance()方法来创建 Class 对象对应类的实例,通过这种方法可以选定构造方法创建实例。
//获取 Person 类的 Class 对象
Class clazz=Class.forName("reflection.Person");
//使用.newInstane 方法创建对象
Person p=(Person) clazz.newInstance();
//获取构造方法并创建对象
Constructor c=clazz.getDeclaredConstructor(String.class,String.class,int.class);
//创建对象并设置属性
Person p1=(Person) c.newInstance("测试","男",10);
4)Clone
无论何时我们调用一个对象的clone方法,JVM就会创建一个新的对象,将前面的对象的内容全部拷贝进去,用clone方法创建对象并不会调用任何构造函数。
5)反序列化
当我们序列化和反序列化一个对象,JVM会给我们创建一个单独的对象。在反序列化时,JVM创建对象并不会调用任何构造函数。
7、重载和重写
1)重写(Override)
从字面上看,重写就是重新写一遍的意思。其实就是在子类中把父类本身有的方法重新写一遍。子类继承了父类原有的方法,但有时子类并不想原封不动的继承父类中的某个方法,所以在方法名, 参数列表,返回类型(除过子类中方法的返回值是父类中方法返回值的子类时)都相同的情况下, 对方法体进行修改或重写,这就是重写。但要注意子类函数的访问修饰权限不能少于父类的。
public class Father {
public static void main(String[] args) {
// TODO Auto-generated method stub
Son s = new Son();
s.sayHello();
}
public void sayHello() {
System.out.println("Hello");
}
}
class Son extends Father{
@Override
public void sayHello() {
// TODO Auto-generated method stub
System.out.println("hello by ");
}
}
重写总结:
1.发生在父类与子类之间
2.方法名,参数列表,返回类型(除过子类中方法的返回类型是父类中返回类型的子类)必须相同
3.访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)
4.重写方法一定不能抛出新的检查异常或者比被重写方法明更加宽泛的检查型异常
2)重载(Overload)
在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同)则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同,但不能通过返回类型是否相同来判断重载。
public class Father {
public static void main(String[] args) {
// TODO Auto-generated method stub
Father s = new Father();
s.sayHello();
s.sayHello("wintershii");
}
public void sayHello() {
System.out.println("Hello");
}
public void sayHello(String name) {
System.out.println("Hello" + " " + name);
}
}
重载总结:
1.重载Overload是一个类中多态性的一种表现
2.重载要求同名方法的参数列表不同(参数类型,参数个数甚至是参数顺序)
3.重载的时候,返回值类型可以相同也可以不相同。无法以返回类型作为重载函数的区分标准
8、权限修饰符
Java规定的4种权限 (从小到大排列) : private、缺省(default)、protected、 public
Java权限修饰符public、protected、default、private置于类的成员定义前,用来限定对象对该类成员的访问权限。
对于class的权限修饰只可以用public和default(缺省)。
public类可以在任意地方被访问。
default类只可以被同一个包内部的类访问。
4种权限可以用来修饰类及类的内部结构: 属性、方法、构造器、内部类
具体的,4种权限都可以用来修饰类的内部结构,属性、方法、构造器、内部类修饰
类的话,只能使用default、 public。
总结封装性:Java提供了4种权限修饰符来修饰类及类的内部结构,体现类及类的内部结构在被调用时的可见性
9、String、String StringBuffer 和 StringBuilder 的区别是什么?
String和StringBuffer和StringBuilder的区别 因为String是不可变的,StringBuffer 和 StringBuilder 它们都是可变的字符串。
String是只读字符串,它并不是基本数据类型,而是一个对象。从底层源码来看是一个final类型的字符数组,所引用的字符串不能被改变,一经定义,无法再增删改。每次对String的操作都会生新的String对象。
private final char value[];
StringBuffer和StringBuilder他们两都继承了AbstractStringBuilder抽象类,他们的底层都是可变的字符数组
/**
* The value is used for character storage.
*/
char[] value;
所以在进行频繁的字符串操作时,建议使用StringBuffer和StringBuilder来进行操作。
StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全
;
StringBuilder 并没有对方法进行加同步锁,所以是非线程安全
的。
10、Collection与Collections的区别
1)Collection接口
Collection是集合类的上级接口,子接口有 Set、List、LinkedList、ArrayList、Vector、Stack、 Set;
2)Collections集合类
Collections是集合类的一个帮助类, 它包含有各种有关集合操作的静态多态方法,用于实现对各种集合的搜索、排序、线程安全化等操作。此类不能实例化(因为Collections的构造方法被私有化处理了),就像一个工具类,服务于Java的Collection框架, Collections类中的方法都是被static修饰了,可以直接用类名调用。
Collections类常用方法:
- public static void shuffle(List)。混乱排序
- public static boolean addAll(Collection c, T… elements)。添加一些元素
- public static void sort(List list,Comparator<? super T> )。 排序,将集合中元素按照指定规则排序
- public static <T extends Comparable<? super T>> void sort(List list)。 排序,将集合中元素按照默认规则排序
- public static int binarySearch(List list, Object key)。查找,使用二分搜索法搜索指定的 List 集合,以获得指定对象在 List 集合中的索引。如果要使该方法可以正常工作,则必须保证 List 中的元素已经处于有序状态。
- public static void copy(List <? super T> dest,List<? extends T> src) 复制, 用于将指定集合中的所有元素复制到另一个集合中。
- public static void replaceAll()。替换,替换集合中所有的旧元素为新元素。
11、ArrayList和linkedList的区别
List—是一个有序的集合,可以包含重复的元素,它继承Collection。
List有两个重要的实现类:ArrayList和LinkedList
1)ArrayList 内部使用的动态数组
来存储元素,LinkedList 内部使用的双向链表
来存储元素,这也是 ArrayList 和 LinkedList 最本质的区别。
2)正因为底层数据结构的不同,他们适用的场景不同,ArrayList 更适合随机查找
,与 ArrayList 相比,LinkedList 的增加和删除的操作效率更高,而查找和修改的操作效率较低
,因为要移动指针。数据量小时性能差别不是很大。
补充知识:ArrayList的toArray方法返回一个数组
ArrayList的asList方法返回一个列表
12、HashMap和HashTable的区别
1)安全性
Hashtable是线程安全
,HashMap是非线程安全
。HashMap的性能会高于Hashtable,我们平时使用时若无特殊需求建议使用HashMap,如果需要线程安全高并发的话,推荐使用 ConcurrentHashMap 代替 Hashtable。
2)HashMap允许null作为键或值,Hashtable不允许,运行时会报NullPointerException。HashMap以null作为key时,总是存储在table数组的第一个节点上。
3)HashMap添加元素使用的是自定义hash算法,Hashtable使用的是key的hashCode
HashMap继承了AbstractMap,HashTable继承Dictionary抽象类,两者均实现Map接口
4)HashMap和HashTable的底层实现都是数组+链表
结构实现
但是在jdk 1.8版本之后HashMap增加了一个新的结构叫做红黑树
。
5)HashMap的初始容量为16
,Hashtable初始容量为11
,两者的填充因子默认都是0.75
。HashMap扩容时是当前容量翻倍即:capacity 2,Hashtable扩容时是容量翻倍+1即:capacity (2+1)
6)HashMap只支持Iterator遍历,Hashtable支持Iterator和Enumeration
遍历的机制HashMap有一个叫keyset还有一个叫Entryset的两个方法,得到的结果都是Iterator(迭代器)。
而HashTable有一个叫elements的方法,他的结果是Enumeration,同时Hashtable也能得到key和Entry,所以Hashtable的遍历方式多了一个机制。
7)HashMap与HashTable的部分方法不同,比如HashTable有contains方法。
HashTable中有contains方法,HsahMap中就没有。HashMap只有containsKey和containsValue但是没有contains。
13、Java自动装箱与拆箱
装箱
:就是自动将基本数据类型转换为包装器类型(int–>Integer);调用方法:Integer的valueOf(int) 方法
拆箱
:就是自动将包装器类型转换为基本数据类型(Integer–>int)。调用方法:Integer的intValue方法
在Java SE5之前,如果要生成一个数值为10的Integer对象,必须这样进行: Integer i = new Integer(10);
而在从Java SE5开始就提供了自动装箱的特性,如果要生成一个数值为10的Integer对象,只需要
这样就可以了:Integer i = 10;
14、线程和进程的区别
进程
是资源分配的最小单位,线程
是CPU调度的最小单位。
一个进程可以包含多个线程。
进程要比线程消耗更多的计算机资源。
进程间不会相互影响,有独立的地址空间。
不同进程间数据很难共享,同一进程下不同线程间数据可以共享。
15、什么是java的局部变量、成员变量和全局变量?
1、局部变量:在方法内定义的变量称为“局部变量”或“临时变量”,方法结束后局部变量占用的内存将被释放。
2、成员变量:在类体的变量部分中定义的变量,也称为字段。
3、全局变量:全局变量,又称“外部变量”,它不是属于哪个方法,作用域从定义的地址开始到源文件结束。
4、局部变量和成员变量主要是他们作用域的区别:
成员变量是类内部;局部变量是定义其的方法体内部(或者方法体内部的某一程序块内——大括号,主要看定义的位置)。
成员变量可以不显式初始化,它们可以由系统设定默认值;局部变量没有默认值,所以必须设定初始赋值。
在内存中的位置也不一样。成员变量在所在类被实例化后,存在堆内存中;局部变量在所在方法调用时,存在栈内存空间中。
16、Java的四种引用,强弱软虚
1)强引用
强引用是平常中使用最多的引用,强引用在程序内存不足(OOM)的时候也不会被回收,使用方式:
String str = new String("str");
System.out.println(str);
2)软引用
软引用在程序内存不足时,会被回收,使用方式:
// 注意:wrf这个引用也是强引用,它是指向SoftReference这个对象的,
// 这里的软引用指的是指向new String("str")的引用,也就是SoftReference类中T
SoftReference<String> wrf = new SoftReference<String>(new String("str"));
可用场景: 创建缓存的时候,创建的对象放进缓存中,当内存不足时,JVM就会回收早先创建的对象。
3)弱引用
弱引用就是只要JVM垃圾回收器发现了它,就会将之回收,使用方式:
WeakReference<String> wrf = new WeakReference<String>(str);
可用场景: Java源码中的 java.util.WeakHashMap 中的 key 就是使用弱引用,可以理解, 一旦我不需要某个引用,JVM会自动帮我处理它,这样我就不需要做其它操作。
4)虚引用
虚引用的回收机制跟弱引用差不多,但是它被回收之前,会被放入 ReferenceQueue 中。注意哦,其它引用是被JVM回收后才被传入 ReferenceQueue 中的。由于这个机制,所以虚引用大多被用于引用销毁前的处理工作。还有就是,虚引用创建的时候,必须带有 ReferenceQueue , 使用例子:
PhantomReference<String> prf = new PhantomReference<String>(new String("str"),
new ReferenceQueue<>());
可用场景:
对象销毁前的一些操作,比如说资源释放等。 Object.finalize() 虽然也可以做这
类动作,但是这个方式即不安全又低效 。
上诉所说的几类引用,都是指对象本身的引用,而不是指Reference的四个子类的引用
(SoftReference等)。
17、volatile关键字
volatile保证内存可见性
和禁止指令重排
,不能保证它具有原子性
。
volatile用于多线程环境下的单次操作(单次读或者单次写)。
也就是说volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。所以对其他线程是可见的。
但是这里需要注意一个问题,volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性。
比如 volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就是这个操作同样存在线程安全问题。
18、final有哪些用法?
1)被final修饰的类不可以被继承;
2)被final修饰的方法不可以被重写;
3)被final修饰的变量不可以被改变;如果修饰引用,那么表示引用不可变,引用指向的内容可变;
4)被final修饰的方法,JVM会尝试将其内联,以提高运行效率
5)被final修饰的常量,在编译阶段会存入常量池中
除此之外,编译器对final域要遵守的两个重排序规则更好:
在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。
19、static都有哪些用法
所有的人都知道static关键字这两个基本的用法:静态变量
和静态方法
.也就是被static所修饰的变量/方法都属于类的静态资源,类实例所共享。
除了静态变量和静态方法之外,static也用于静态块
,多用于初始化操作:
public calss PreCache{
static{
//执行相关操作
}
}
此外static也多用于修饰内部类,此时称之为静态内部类
最后一种用法就是静态导包,即 import static .import static是在JDK 1.5之后引入的新特性,可以用
来指定导入某个类中的静态资源,并且不需要使用类名,可以直接使用资源名,比如:
import static java.lang.Math.*;
public class Test{
public static void main(String[] args){
//System.out.println(Math.sin(20));传统做法
System.out.println(sin(20));
}
}
20、a=a+b与a+=b有什么区别吗?
+= 操作符会进行隐式自动类型转换,此处a+=b隐式的将加操作的结果类型强制转换为持有结果的类型,而a=a+b则不会自动进行类型转换.如:
byte a = 127;
byte b = 127;
b = a + b; // 报编译错误:cannot convert from int to byte
b += a;
以下代码是否有错,有的话怎么改?
short s1= 1;
s1 = s1 + 1;
有错误.short类型在进行运算时会自动提升为int类型,也就是说 s1+1 的运算结果是int类型,而s1是 short类型,此时编译器会报错.
正确写法:
short s1= 1;
s1 += 1;
+=操作符会对右边的表达式结果强转匹配左边的数据类型,所以没错.
21、BIO、NIO、AIO有什么区别
他们三者都是Java中常用的I/O模型,主要在于其工作方式和性能特点。
- BIO(Blocking I/O)是最传统的 I/O模型,它采用
同步阻塞方式处理 I/O
操作。在BIO模型中,当一个 I/O操作发生时,线程会被阻塞,直到 I/O操作完成才能继续执行。这种模型的缺点是性能较低,因为大量的线程会被阻塞,导致资源浪费. - NIO(Non-blocking I/O)是一种更高效的I/O模型,它采用
事件驱动的方式
处理I/O操作。
NIO 提供了通道 (Channel)、选择器 (Selector) 和缓冲区 (Buffer) 等新的 I/O 操作方式。
在NIO模型中,线程可以注册对I/O事件的关注、并在事件发生时得到通知,从而可以继续执行其他任务。这种模型可以大大减少线程的阻塞,提高系统的并发性能。 - AIO (Asynchronous I/O) 是在NIO的基础上进一步发展的一种I/O模型,它采用
异步非阻塞方式处理I/O
操作。在AIO模型中,I/O操作会在后台进行,操作完成会触发回调通知
。
通知的方式可以是通过回调函数(CompletionHandler)或者通过 Future 对象。
这种模型可以进一步提高系统的并发性能,特别适用于处理大量并发的I/O操作。
22、如何实现线程的同步
线程的同步是为了保证多个线程按照特定的顺序、协调地访问共享资源,避免数据不一致和竞争条件等问题。
在 Java 中,实现线程同步可以通过多种机制来实现,包括 synchronized 关键字、ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier 等。以下是几种常用的同步方法及其使用示例。
- synchronized 关键字
synchronized 是最基本的同步机制,用于确保同一时间只有一个线程可以执行被同步的代码块或方法。 - ReentrantLock 提供了更高级的锁功能,比如公平锁、条件变量等。
- Semaphore 用于控制同时访问特定资源的线程数量。
- CountDownLatch 并发工具类,允许一个或多个线程等待,直到其他线程中的操作执行完毕。
- CyclicBarrier 并发工具类,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会打开,所有被屏障拦截的线程才会继续运行。
- 使用wait()、notify()和notifyAII()方法:这些方法是Cbjet类的方法,允许线程间进行协作和通信。通过调用wait()方法使线程进入等待状态,然后其他线程可以通过notify()或notifyAII()方法唤醒等待的线程
23、什么是Java的序列化
Java 的序列化(Serialization)是指将对象转换为字节流的过程,可以用于在网络上传输对象、对象持久化到磁盘或数据库,或者实现远程调用等场景。反序列化则是指将字节流恢复成对象的过程。
1)为什么需要序列化?
Java 序列化的主要用途是在网络上传输对象、对象持久化和跨进程通信。
2)Java 序列化的实现方式:
Java 序列化由 java.io.Serializable 接口和 java.io.ObjectOutputStream、java.io.ObjectInputStream 类来实现。
实现 Serializable 接口:
# 要使一个类可序列化,需要实现 java.io.Serializable 接口。这是一个标记接口,没有任何方法定义,只是用于标识该类可以被序列化。
import java.io.Serializable;
public class MyClass implements Serializable {
// 类的成员变量和方法
}
序列化和反序列化:
# 序列化(Serialization):将对象转换为字节流。
MyClass obj = new MyClass();
try (ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("data.ser"))) {
outputStream.writeObject(obj);
} catch (IOException e) {
e.printStackTrace();
}
# 反序列化(Deserialization):从字节流恢复对象。
MyClass obj = null;
try (ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("data.ser"))) {
obj = (MyClass) inputStream.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
24、说说你对内部类的理解
内部类(Inner Class)是定义在其他类内部的类。Java 中内部类有多种类型,包括成员内部类、静态内部类、局部内部类和匿名内部类,每种类型都有其特定的应用场景和特点。
1)成员内部类(Member Inner Class):
定义在另一个类的内部,作为其成员,可以访问外部类的成员,包括私有成员。
可以被声明为 private、protected、public 或默认(包级私有)访问权限。
成员内部类可以使用外部类的实例,但不能声明静态成员(除非是常量 static final)。
public class Outer {
private int outerField;
public class Inner {
public void display() {
System.out.println("Outer field: " + outerField);
}
}
}
2)静态内部类(Static Nested Class):
定义在另一个类内部的静态类,没有隐式引用外部类的实例,因此不能访问外部类的非静态成员。
可以直接通过 Outer.Inner 的形式访问,不需要先创建外部类的实例。
public class Outer {
private static int outerStaticField;
public static class StaticInner {
public void display() {
System.out.println("Outer static field: " + outerStaticField);
}
}
}
3)局部内部类(Local Inner Class):
定义在方法或作用域内部的类,作用域仅限于定义它的代码块内部。
可以访问外部方法的局部变量,但必须声明为 final 或事实上是 final 的变量。
public class Outer {
public void outerMethod() {
final int localVar = 10;
class LocalInner {
public void display() {
System.out.println("Local variable: " + localVar);
}
}
LocalInner inner = new LocalInner();
inner.display();
}
}
4)匿名内部类(Anonymous Inner Class):
没有显示的类名,直接创建一个对象并同时定义其类的实现。
常用于实现接口或继承抽象类,可以在创建对象的地方直接实现类的方法。
public class Outer {
public void displayMessage() {
Interface inner = new Interface() {
@Override
public void method() {
System.out.println("Anonymous Inner Class method implementation");
}
};
inner.method();
}
}
interface Interface {
void method();
}
5)使用场景
- 成员内部类:与外部类有紧密的关联,并且需要访问外部类的成员变量或方法。
- 静态内部类:当内部类不需要访问外部类的实例变量时,可以考虑使用静态内部类,避免创建外部类的实例。
- 局部内部类:当一个类只对一个方法有用时,可以使用局部内部类。
- 匿名内部类:适合只需要使用一次的类,可以简洁地实现接口或抽象类的方法。
内部类的主要作用是实现更加灵活和封装的设计。需要注意的是,过度使用内部类会增加代码的复杂性,降低可读性和可维护性。因此,在使用内部类时要老虑其是否真正有必要,并且仔细进行设计和命名。
25、JDK动态代理与CGLIB实现的区别
JDK动态代理和CGLIB是Java中常用的两种代理技术,它们在实现原理和使用方式上有一些区别。
1)JDK动态代理
实现原理:JDK动态代理基于Java的反射机制
和接口实现
。使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来动态生成代理类。 代理对象必须实现一个或多个接口。
生成的代理类是目标类的接口的实现类。在调用方法时,代理类会将调用委托给InvocationHandler的invoke方法。
2)CGLIB代理
CGLIB(Code Generation Library)代理基于ASM字节码操作框架,通过生成目标类的子类来实现代理。
使用继承
来代理目标类的方法。目标对象不需要实现任何接口。目标类和方法不能是final,因为CGLIB通过继承目标类来创建代理类,生成的代理类是目标类的子类。在调用方法时,代理类会将调用委托给方法拦截器MethodInterceptor的intercept方法。
性能:
CGLIB代理的性能通常比JDK动态代理高,因为它直接生成字节码来处理方法调用,减少了反射的开销。
26、深拷贝和浅拷贝区别
浅拷贝只复制对象引用
,新旧对象仍然指向同一块内存空间,修改其中一个对象的属性会影响另一个对象。浅拷贝的性能开销较小
。
深拷贝会复制对象本身
以及对象引用指向的其他对象,所有对象的引用都将指向全新的内存空间。深拷贝的性能开销较大
。
27、构造器是否可被重写
构造器(构造方法)不能被重写
。重写(override)是指在子类中重新定义父类中的方法,以提供新的实现,而构造器是构造器Java中是一种特殊的方法,用于创建和初始化对象
,具有与类名相同的名称,并且不能继承到子类中。
构造器不能继承
:构造器是不能被继承的,因此在子类中不存在对父类构造器的重写。
作用不同:构造器是用来初始化对象的,而方法是用来定义对象行为的。构造器和方法有不同的用途和语法结构。
构造器不能被重写,因为它们不能被继承。重写是用于方法的,而构造器是用于对象初始化的特殊方法。在子类中,可以通过 super 关键字调用父类构造器
,以确保父类的初始化逻辑被执行。
28、ThreadLocal有哪些应用场景
ThreadLocal是Java中的一个类,它提供了一种在多线程环境下实现线程局部变量存储的机制。它的应用场景包括线程池、Web开发中的请求上下文信息管理、数据库连接管理和日志记录等等
29、对CountDownLatch的理解
CountDownLatch是Java中用于多线程协作的辅助类,它可以让一个或多个线程等待其他线程完成某个任务后再继续执行。
CountDownLatch通过一个计数器来实现、计数器的初始值可以设置为等待的线程数量。每个线程在完成任务后都会调用countDown()方法来减少计数器的值。当计数器的值减至0时,等待在CountDownLatch上的线程就会被唤醒,可以继续执行后续的操作。
需要注意的是,CountDownLatch的计数器是不能被重置的,也就是说它是一次性的。一
旦计数器减至0,它将无法再次使用。如果需要多次使用可重置的计数器,则可以考虑使用CyclicBarrier。
CountDownLatch的主要作用是协调多个线程的执行顺序,使得某个线程(或多个线程)必须等待其他线程完成后才能继续执行
30、接口和抽象类有什么区别?
1)接口使用关键字 interface 来定义。 抽象类使用关键字 abstract 来定义。
2)接口里只能包含抽象方法,静态方法和默认方法,不能为普通方法提供方法实现,抽象类则完全可以包含普通方法。
3) 接口里只能定义静态常量,不能定义普通成员变量,抽象类里则既可以定义普通成员变量,也可以定义静态常量。
4)接口不能包含构造器,抽象类可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作。
5)接口里不能包含初始化块,但抽象类里完全可以包含初始化块。
6)抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。类可以实现很多个接口;但是只能继承一个抽象类。
7)访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符。
31、获取一个类Class对象的方式有哪些?
第一种:通过类对象的 getClass() 方法获取,细心点的都知道,这个 getClass 是 Object 类里面的方法。
User user=new User();
//clazz就是一个User的类对象
Class<?> clazz=user.getClass();
第二种:通过类的静态成员表示,每个类都有隐含的静态成员 class。
//clazz就是一个User的类对象
Class<?> clazz=User.class;
第三种:通过 Class 类的静态方法 forName() 方法获取。
Class<?> clazz = Class.forName("com.tian.User");
32、String str="i"与 String str=new String(“i”)一样吗?
不一样,因为内存的分配方式不一样。String str="i"
的方式,Java 虚拟机会将其分配到常量池中;而 String str=new String("i")
则会被分到堆内存中。
33、Object类常用方法
1) getClass 方法
final 方法、获取对象的运行时 class 对象,class 对象就是描述对象所属类的对象。这个方法通常是和 Java 反射机制搭配使用的。
2)hashCode 方法
该方法主要用于获取对象的散列值。Object 中该方法默认返回的是对象的堆内存地址。
3)equals 方法
public boolean equals(Object obj) { return (this == obj);}
该方法用于比较两个对象,如果这两个对象引用指向的是同一个对象,那么返回 true,否则返回 false。一般 equals 和 == 是不一样的,但是在 Object 中两者是一样的。子类一般都要重写这个方法。
4) clone 方法
protected native Object clone() throws CloneNotSupportedException;
该方法是保护方法,实现对象的浅复制,只有实现了 Cloneable 接口才可以调用该方法,否则抛出 CloneNotSupportedException 异常。
默认的 clone 方法是浅拷贝。所谓浅拷贝,指的是对象内属性引用的对象只会拷贝引用地址,而不会将引用的对象重新分配内存。深拷贝则是会连引用的对象也重新创建。
5) toString 方法
public String toString() { return getClass().getName() + “@” + Integer.toHexString(hashCode()); }
返回一个 String 对象,一般子类都有覆盖。默认返回格式如下:对象的 class 名称 + @ + hashCode 的十六进制字符串。
6) notify 方法
final 方法,主要用于唤醒在该对象上等待的某个线程。
7)notifyAll 方法
final 方法,主要用于唤醒在该对象上等待的所有线程。
8)wait(long timeout) 方法
public final native void wait(long timeout) throws InterruptedException;
wait 方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait() 方法一直等待,直到获得锁或者被中断。wait(long timeout) 设定一个超时间隔,如果在规定时间内没有获得锁就返回。
9)wait(long timeout, int nanos) 方法
参数说明:
timeout:最大等待时间(毫秒)
nanos:附加时间在毫秒范围(0-999999)
该方法导致当前线程等待,直到其他线程调用此对象的 notify() 方法或notifyAll()方法,或在指定已经过去的时间。此方法类似于 wait 方法的一个参数,但它允许更好地控制的时间等待一个通知放弃之前的量。实时量,以毫微秒计算,计算公式如下:1000000 * timeout + nanos
在所有其他方面,这种方法与 wait(long timeout) 做同样的事情。特别是 wait(0, 0) 表示和 wait(0) 相同。
10)wait 方法
public final void wait() throws InterruptedException { wait(0);}
可以看到 wait() 方法实际上调用的是 wait(long timeout) 方法,只不过 timeout 为 0,即不等待。
11)finalize 方法
该方法是保护方法,主要用于在 GC 的时候再次被调用,如果我们实现了这个方法,对象可能在这个方法中再次复活,从而避免被 GC 回收。
34、ThreadLocal的理解?
ThreadLocal为什么容易内存泄露?
35、sleep() 和 wait() 有什么区别?
- 类的不同:sleep() 来自 Thread,wait() 来自 Object。
- 释放锁:sleep() 不释放锁;wait() 释放锁。
- 用法不同:sleep() 时间到会自动恢复;wait() 可以使用 notify()/notifyAll()直接唤醒。
wait和notify都是Object中的方法
wait和notify执行前,线程都必须获得对象锁
36、notify和notifyAll的区别
锁池
:
假设线程A已经拥有对象锁,线程B、C想要获取锁就会被阻塞,进入一个地方去等待锁的等待,这个地方就是该对象的锁池;
等待池
:
假设线程A调用某个对象的wait方法,线程A就会释放该对象锁,同时线程A进入该对象的等待池中,进入等待池中的线程不会去竞争该对象的锁。
notify和notifyAll的区别
:
1、notify只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会;
2、notifyAll会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会;
37、try catch finally,try里有return,finally还执行么?
执行,并且finally的执行早于try里面的return
import static java.lang.Math.*;
public class Test{
public static void main(String[] args){
//System.out.println(Math.sin(20));传统做法
System.out.println(sin(20));
}
}
byte a = 127;
byte b = 127;
b = a + b; // 报编译错误:cannot convert from int to byte
b += a;
short s1= 1;
s1 = s1 + 1;
short s1= 1;
s1 += 1;
结论:
1、不管有没有出现异常,finally块中代码都会执行;
2、当try和catch中有return时,finally仍然会执行;
3、finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的
值保存起来,管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数
返回值是在finally执行前确定的;
4、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
38、深拷贝和浅拷贝的区别是什么?
深拷贝
:值类型字段会复制一份,引用类型字段所指向的对象,会在内存中也创建一个副本。
浅拷贝
:值类型的字段会复制一份,而引用类型的字段拷贝的仅仅是引用地址,而该引用地址指向的实际对象空间其实只有一份。Object中的clone()方法默认就是执行的浅拷贝。
39、HashMap 与ConcurrentHashMap 的异同
1、都是 key-value 形式的存储数据;
2、 HashMap 是线程不安全的,ConcurrentHashMap 是 JUC 下的线程安全的;
3、HashMap 底层数据结构是数组 + 链表(JDK 1.8 之前)。JDK 1.8 之后是数组 + 链表 + 红黑
树。当链表中元素个数达到 8 的时候,链表的查询速度不如红黑树快,链表会转为红黑树,红
黑树查询速度快;
4、 HashMap 初始数组大小为 16(默认),当出现扩容的时候,以 0.75 * 数组大小的方式进行扩
容;
5、ConcurrentHashMap 在 JDK 1.8 之前是采用分段锁来现实的 Segment + HashEntry,
Segment 数组大小默认是 16,2 的 n 次方;JDK 1.8 之后,采用 Node + CAS + Synchronized
来保证并发安全进行实现
40、Java 中 IO 流分为几种?
按功能来分:输入流(input)、输出流(output)。
按类型来分:字节流和字符流。
字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数据。
字节流默认不使用缓冲区;字符流使用缓冲区。
字节流通常用于处理二进制数据,实际上它可以处理任意类型的数据,但它不支持直接写入或读取Unicode码元;字符流通常处理文本数据,它支持写入及读取Unicode码元。
41、守护线程(服务线程)
守护线程是后台线程,为用户线程提供公共服务。任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon(bool on);true则把该线程设置为守护线程,反之则为用户线程。Thread.setDaemon()必须在Thread.start()之前调用,否则运行时会抛出异常。
守护线程优先级较低是jvm级别的,垃圾回收就是一个经典的守护线程,当垃圾回收线程是jvm仅剩的线程时,会自动离开,jvm就可以退出了,守护线程不依赖终端,与系统“同生共死”。Finalizer也是守护线程。
42、红黑树有哪几个特征?
1、每个节点是 黑色或红色
2、根节点是黑色
3、每个叶子节点都是黑色(指向空的叶子节点)
4、如果一个叶子节点是红色,那么其子节点必须都是黑色的
5、从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点
43、线程的 run() 和 start() 有什么区别和关系?
调用start方法方可启动线程,源码分析里面调用了start0,是一个native方法,start() 只能调用一次;非java实现的,这个方法里面实现了多线程并对run方法进行了调用。
而run方法用于执行线程的运行时代码,只是thread类中的一个普通方法调用,还是在主线程里执行,run() 可以重复调用。所以直接调用run,就像普通实例对象调用自己的普通方法一样,只会顺序执行!
在start这个方法的设计上,其实java源码采用了“模版模式”这种设计模式。所以一定要调用start启动线程。
44、多线程的上下文切换
1)上下文
上下文:是指某一时间点,cpu寄存器(cpu内部数量较小但是速度很快的内存)和程序计数器(表明cpu执行的位置)内容;上下文切换是内核(操作系统核心)在cpu上对进程(线程)的切换,切换过程中的信息是保存在进行控制块PCB中的,PCB还被称作“切换帧”,信息一致保存在cpu内存中,知道被再次使用。
多线程的上下文切换即任务状态的保存再加载(因为cpu时间片轮转,所以就会切换);
2)上下文切换的内容
上下文切换的内容:挂起一个进程,将这个进程在cpu中状态(上下文)存储于内存中某处;
在内存中检索下一个进程的上下文,并将其在cpu寄存器恢复;
跳到程序计数器指向的位置,恢复该进程;
45、get方式和post方式有何区别
GET方式:在URL地址后附带的参数是有限制的,其数据容量通常不能超过1K。请求参数放在URL地址后面,以?的方式来进行拼接;GET方式一般用来获取数据。
POST方式:可以在请求的实体内容中向服务器发送数据,传送的数据量无限制。请求参数放在HTTP请求包中;POST方式一般用来提交数据。
46、Java中用到的线程调度算法
1)调度
抢占式调度:线程切换由系统控制(一个线程阻塞不会导致整个进程阻塞);
协助式调度(接力):由线程本身控制(一个线程阻塞,整个系统奔溃);
jvm中线程调度是抢占式调度;
java中线程调度是时间片轮转的方式;可以设置线程的优先级,按照优先级分配时间片,但是尽量不要用,防止线程饥饿。
2)算法
(1)优先调度算法:
先来先服务调度算法;
短作业优先调度算法;
(2)高优先权优先调度算法:
非抢占式优先权调度算法;
抢占式优先权调度算法;
高响应比优先调度算法;
小结:每一个线程都是有优先级的,一般来说,高优先级的线程在运行时会具有优先权,但这依赖于线程调度的实现,这个实现是和操作系统相关的(OS dependent)。我们可以定义线程的优先级,但是这并不能保证高优先级的线程会在低优先级的线程前执行。线程优先级是一个int变量(从1-10),1代表最低优先级,10代表最高优先级。
47、创建线程有哪几种方式?
1、继承 Thread 重写 run 方法;
2、实现 Runnable 接口;
3、实现 Callable 接口;
4、通过线程池创建。
更详细介绍:java中如何创建一个线程?
48、线程有哪些状态?
1)New(初始化状态)
用new语句创建的线程处于新建状态,此时它和其他Java对象一样,仅仅在堆区中被分配了内存。如:Thread t = new MyThread();
2)Runnable(就绪状态)
当调用线程对象的start()方法,线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,Java虚拟机会为它创建方法调用栈和程序计数器。处于这个状态的线程位于可运行池中,等待获得CPU的使用权,并不是说执行了start()此线程立即就会执行。
3)Running(运行状态)
当就绪状态中的线程获得了CUP执行资源,执行run()中的代码,这样的线程我们称为运行状态的线程。
4)Blocked(阻塞状态)
处于运行中的线程,由于某种原因放弃对cpu的使用权,处于阻塞状态,直到其进入就绪状态,才有机会再次被cpu调用进入运行状态。
5)Terminated(终止状态)
更详细介绍:线程生命周期及五种状态
49、线程池都有哪些状态?
线程池生命周期的五种状态:
1)RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。
2)SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。
3)STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。
4)TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。
5)TERMINATED:terminated()方法结束后,线程池的状态就会变成这个。
更详细介绍:Java多线程之Executor框架
50、线程池实现原理
1、如果运行线程数小于corePoolSize,那么马上创建线程运行这个任务;
2、如果正在运行的线程数大于等于corePoolSize,那么将这个任务放进队列;
3、如果队列放满了,如果正在运行的线程数小于maximumPoolSize,创建线程处理任务;
4、如果队列放满了,正在运行的线程数大于等于maximumPoolSize,那么线程会抛出异常;RejectExecutionException,执行拒绝策略。
51、线程池4种拒绝策略
1、AbortPolicy :直接抛出异常,阻止系统运行(线程池默认的拒绝策略);
2、CallerRunsPolicy:由调用线程处理;
3、DiscardPolicy:丢弃无法处理的任务,不予任何处理;
4、DiscardOldestPolicy:丢弃最老的一个请求,也就是即将被执行的一个任务,重新提交被拒绝的任务。
52、Java中CycliBarriar和CountdownLatch的区别
CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;
而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
53、怎么检测一个线程是否拥有锁
在java.lang.Thread中有一个方法叫holdsLock(),它返回true如果当且仅当当前线程拥有某个具体对象的锁。
54、不可变对象
Java平台类库中包含许多不可变类,如String、基本类型的包装类、BigInteger和BigDecimal等。它们的常量(域)是在构造函数中创建的。既然它们的状态无法修改,这些常量永远不会变。
不可变对象永远是线程安全的。
只有满足如下状态,一个对象才是不可变的;
它的状态不能在创建后再被修改;
所有域都是final类型;并且它被正确创建(创建期间没有发生this引用的逸出)。
55、线程池中 submit() 和 execute() 方法有什么区别?
execute():只能执行 Runnable 类型的任务。
submit():可以执行 Runnable 和 Callable 类型的任务。
56、java反射
1)定义:
反射机制
是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象,都能够调用它的任意一个方法。在java中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。
这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
2)哪里会用到反射机制?
jdbc就是典型的反射 Class.forName('com.mysql.jdbc.Driver.class');/
/加载MySQL的驱动类这就是反射。如mybatis等框架使用反射实现的。
反射的核心是获取class对象,获取 Class 对象的 3 种方法
1)调用某个对象的 getClass()方法
2)调用某个类的 class 属性来获取该类对应的 Class 对象
3)使用 Class 类中的 forName()静态方法(最安全/性能最好)
更多详情:Java反射
57、instanceof 关键字的作用
instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例,用法为:
boolean result = obj instanceof Class
其中 obj 为一个对象,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。
注意:编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。
int i = 0;
System.out.println(i instanceof Integer);//编译不通过 i必须是引用类型,不能是基本类型
System.out.println(i instanceof Object);//编译不通过
Integer integer = new Integer(1);
System.out.println(integer instanceof Integer);//true
//false ,在 JavaSE规范 中对 instanceof 运算符的规定就是:如果 obj 为 null,那么将返回false。
System.out.println(null instanceof Object)
58、Java 序列化中如果有些字段不想进行序列化,怎么办?
对于不想进行序列化的变量,使用 transient
关键字修饰。
transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法。
59、&和&&的区别?
&运算符有两种用法:(1)按位与;(2)逻辑与。
&&运算符是短路与运算。
逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是 true 整个表达式的值才是 true。&&之所以称为短路运算是因为,如果&&左边的表达式的值是 false,右边的表达式会被直接短路掉,不会进行运算。
很多时候我们可能都需要用&&而不是&,例如在验证用户登录时判定用户名不是 null 而且不是空字符串,应当写为:username != null &&!username.equals(“”),二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行字符串的 equals 比较,否则会产生 NullPointerException 异常。注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。
60、3*0.1 == 0.3返回值是什么
false,因为有些浮点数不能完全精确的表示出来。
在计算机中浮点数的表示是误差的。所以一般情况下不进行两个浮点数是否相同的比较。而是比较两个浮点数的差点绝对值,是否小于一个很小的正数。如果条件满足,就认为这两个浮点数是相同的。
61、用最有效率的方法计算 2 乘以 8?
2 << 3(左移 3 位相当于乘以 2 的 3 次方,右移 3 位相当于除以 2 的 3 次方)。
62、float f=3.4;是否正确?
不正确。3.4 是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换 float f =(float)3.4; 或者写成 float f =3.4F;。
63、Java 中的Math.round(11.5) 等于多少?Math.round(-11.5)等于多少?
Math.round(11.5)的返回值是 12,Math.round(-11.5)的返回值是-11。四舍五入的原理是在参数上加 0.5 然后进行向下取整。
64、switch 是否能作用在 byte 上,是否能作用在 long 上?
在 Java 5 以前,switch(expr)中,expr 只能是 byte、short、char、int。从 Java5 开始,Java 中引入了枚举类型,expr 也可以是 enum 类型,从 Java 7 开始,expr 还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。
65、char 型变量中能不能存贮一个中文汉字?
char 类型可以存储一个中文汉字。
因为 Java 中使用的编码是 Unicode(不选择任何特定的编码,直接使用字符在字符集中的编号,这是统一的唯一方法),一 个 char 类型占 2 个字节(16 比特),所以放一个中文是没问题的。
补充:使用 Unicode 意味着字符在 JVM 内部和外部有不同的表现形式,在 JVM内部都是 Unicode,当这个字符被从 JVM 内部转移到外部时(例如存入文件系统中),需要进行编码转换。所以 Java 中有字节流和字符流,以及在字符流和字节流之间进行转换的转换流,如 InputStreamReader 和 OutputStreamReader,这两个类是字节流和字符流之间的适配器类,承担了编码转换的任务;对于 C 程序员来说,要完成这样的编码转换恐怕要依赖于 union(联合体/共用体)共享内存的特征来实现了。
66、String s = new String(“xyz”);创建了几个字符串对象?
两个对象,一个是静态区的”xyz”,一个是用 new 创建在堆上的对象。
67、如何实现字符串的反转及替换?
可以自己写实现也可以使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。
示例代码:
// StringBuffer reverse
StringBuffer stringBuffer = new StringBuffer();
stringBuffer. append("abcdefg");
System. out. println(stringBuffer. reverse()); // gfedcba
// StringBuilder reverse
StringBuilder stringBuilder = new StringBuilder();
stringBuilder. append("abcdefg");
System. out. println(stringBuilder. reverse());
有一道很常见的面试题是用递归实现字符串反转,代码如下所示:
public static String reverse(String originStr) {
if(originStr == null || originStr.length() <= 1)
return originStr;
return reverse(originStr.substring(1)) + originStr.charAt(0);
}
68、怎样将 GB2312 编码的字符串转换为 ISO-8859-1 编码的字符串?
String s1 = "你好";
String s2 = new String(s1.getBytes("GB2312"), "ISO-8859-1");
69、阐述 final、finally、finalize 的区别
final
:修饰符(关键字)有三种用法:如果一个类被声明为 final,意味着它不能再派生出新的子类,即不能被继承,因此它和 abstract 是反义词。将变量声明为 final,可以保证它们在使用中不被改变,被声明为 final 的变量必须在声明时给定初值,而在以后的引用中只能读取不可修改。被声明为 final 的方法也同样只能使用,不能在子类中被重写。
finally
:通常放在 try…catch…的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要 JVM 不关闭都能执行,可以将释放外部资源的代码写在 finally 块中。
finalize
:Object 类中定义的方法,Java 中允许使用 finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写 finalize()方法可以整理系统资源或者执行其他清理工作。
70、是否可以继承 String 类?
String 类是 final 类,不可以被继承。
71、为什么重写equals方法必须要重写hashCode方法
因为有强制的规范指定需要同时重写 hashcode 与 equal 是方法。
如果只重写了equals方法而没有重写hashCode方法的话,则会违反约定,因为相等的对象必须具有相等的散列码(hashCode)。许多容器类,如 HashMap、HashSet 都依赖于此规定,比较对象相等首先要比较hashCode是否相等,如果hashCode相等才会去比较equals。
72、Java泛型与泛型擦除
Java泛型是J2 SE1.5中引入的一个新特性,其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
1)Java泛型好处:
- 第一是泛化。可以用T代表任意类型Java语言中引入泛型是一个较大的功能增强不仅语言、类型系统和编译器有了较大的变化,以支持泛型,而且类库也进行了大翻修,所以许多重要的类,比如集合框架,都已经成为泛型化的了,这带来了很多好处。
- 第二是类型安全。泛型的一个主要目标就是提高Java程序的类型安全,使用泛型可以使编译器知道变量的类型限制,进而可以在更高程度上验证类型假设。如果不用泛型,则必须使用强制类型转换,而强制类型转换不安全,在运行期可能发生ClassCast Exception异常,如果使用泛型,则会在编译期就能发现该错误。
- 第三是消除强制类型转换。泛型可以消除源代码中的许多强制类型转换,这样可以使代码更加可读,并减少出错的机会。
- 第四是向后兼容。支持泛型的Java编译器(例如JDK1.5中的Javac)可以用来编译经过泛型扩充的Java程序(Generics Java程序),但是现有的没有使用泛型扩充的Java程序仍然可以用这些编译器来编译。
2)泛型擦除:
泛型信息只存在于代码编译阶段,但是在java的运行期(已经生成字节码文件后)与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。
Java 泛型擦除是 Java 泛型中的一个重要特性,其目的是避免过多的创建类而造成的运行时的过度消耗,及兼容问题。
73、静态变量和实例变量的区别
静态变量
是被 static 修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;
实例变量
必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。
静态变量可以实现让多个对象共享内存。
补充:在 Java 开发中,上下文类和工具类中通常会有大量的静态成员。
74、什么是死锁?
当线程 A 持有独占锁a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 AB 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。
75、线程的 sleep()方法和 yield()方法有什么区别?
1、sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
2、 线程执行 sleep()方法后转入阻塞(blocked)状态,而执行 yield()方法后转入就绪(ready)状态;
3、 sleep()方法声明抛出 InterruptedException,而 yield()方法没有声明任何异常;
4、 sleep()方法比 yield()方法(跟操作系统 CPU 调度相关)具有更好的可移植性。
76、事务的 ACID 是指什么?
1、原子性(Atomic):事务中各项操作,要么全做要么全不做,任何一项操作
的失败都会导致整个事务的失败;
2、一致性(Consistent):事务结束后系统状态是一致的;
3、隔离性(Isolated):并发执行的事务彼此无法看到对方的中间状态;
4、持久性(Durable):事务完成后所做的改动都会被持久化,即使发生灾难
性的失败。通过日志和同步备份可以在故障发生后重建数据。
77、什么是线程局部变量?
线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。
Java 提供 ThreadLocal 类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。
78、JDBC 操作数据库的步骤
1)加载驱动。
Class.forName("oracle.jdbc.driver.OracleDriver");
2)创建连接。
Connection con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl", "scott", "tiger");
3)创建语句。
PreparedStatement ps = con.prepareStatement("select * from emp where sal between ? and ?");
ps.setInt(1, 1000);
ps.setInt(2, 3000);
4)执行语句。
ResultSet rs = ps.executeQuery();
5)处理结果。
while(rs.next()) {
System.out.println(rs.getInt("empno") + " - " +
rs.getString("ename"));
}
6)关闭资源。
finally {
if(con != null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
提示:关闭外部资源的顺序应该和打开的顺序相反,也就是说先关闭 ResultSet、 再关闭 Statement、在关闭Connection。上面的代码只关闭了 Connection(连接),虽然通常情况下在关闭连接时,连接上创建的语句和打开的游标也会关闭,但不能保证总是如此,因此应该按照刚才说的顺序分别关闭。此外,第一步加载驱动在 JDBC 4.0 中是可以省略的(自动从类路径中加载驱动),但是我们建议保留。
79、怎么确保一个集合不能被修改?
可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。
示例代码如下:
List<String> list = new ArrayList<>();
list. add("x");
Collection<String> clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 运行时此行报错
System. out. println(list. size());
80、Statement 和 PreparedStatement 有什么区别?哪个性能更好?
与 Statement 相比,
1、PreparedStatement 接口代表预编译的语句,它主要的优势在于可以减少 SQL 的编译错误并增加 SQL 的安全性(减少 SQL 注射攻击的可能性);
2、PreparedStatement 中的 SQL 语句是可以带参数的,避免了用字符串连接拼接 SQL 语句的麻烦和不安全;
3、当批量处理 SQL 或频繁执行相同的查询时,PreparedStatement 有明显的性能上的优势,由于数据库可以将编译优化后的 SQL 语句缓存起来,下次执行相同结构的语句时就会很快(不用再次编译和生成执行计划)。
补充:为了提供对存储过程的调用,JDBC API 中还提供了 CallableStatement 接口。存储过程(Stored Procedure)是数据库中一组为了完成特定功能的 SQL 语句的集合,经编译后存储在数据库中,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。虽然调用存储过程会在网络开销、安全 性、性能上获得很多好处,但是存在如果底层数据库发生迁移时就会有很多麻烦, 因为每种数据库的存储过程在书写上存在不少的差别。
81、用哪两种方式来实现集合的排序?
你可以使用有序集合,如 TreeSet 或 TreeMap,你也可以使用有顺序的的集合,如 list,然后通过Collections.sort() 来排序。
82、poll() 方法和 remove() 方法的区别?
poll() 和 remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。
83、Java 中的 LinkedList 是单向链表还是双向链表?
是双向链表
84、Java 中的 TreeMap 是采用什么实现的?
Java 中的 TreeMap 是使用红黑树实现的。
85、Java 中Comparator 与 Comparable 有什么不同?
Comparable 接口用于定义对象的自然顺序,而 comparator 通常用于定义用户定制的顺序。Comparable 总是只有一个,但是可以有多个 comparator 来定义对象的顺序。