2)只有涉及多线程的场景,才需要线程同步,如果wait与notify放在Thread,则每个Thread都需要分配Monitor,浪费资源。
3)如果放在Object,单线程场景不分配Monitor,只在多线程分配。分配Monitor的方法为检测threadId的不同。
12. 装箱和拆箱
装箱是通过调用包装器类的 valueOf 方法实现的;拆箱是通过调用包装器类的 xxxValue 方法实现的,xxx代表对应的基本数据类型。如int装箱的时候自动调用Integer的valueOf(int)方法;Integer拆箱的时候自动调用Integer的intValue方法。包含算术运算会触发自动拆箱。存在大量自动装箱的过程,如果装箱返回的包装对象不是从缓存中获取,会创建很多新的对象,比较消耗内存。
-
整型的包装类 valueOf 方法返回对象时,在常用的取值范围内,会返回缓存对象。
-
浮点型的包装类 valueOf 方法返回新的对象。
-
布尔型的包装类 valueOf 方法 Boolean类的静态常量 TRUE | FALSE。
13. Integer和String的比较操作
-
使用 == 比较:
-
基本类型 - 基本类型、基本类型 - 包装对象返回 true
-
包装对象 - 包装对象,非同一个对象(对象的内存地址不同)返回 false;对象的内存地址相同返回 true,如值等于 100 的两个 Integer 对象(原因是 JVM 缓存部分基本类型常用的包装类对象,如 Integer -128 ~ 127 是被缓存的)
-
使用 equals() 比较
-
包装对象-基本类型返回 true
-
包装对象-包装对象返回 true
Integer a = 1;
Integer b = 1;
Integer c = 128;
Integer d = 128;
// [-128,127]范围的自动装箱(box),同值是同一个对象
System.out.println(a == b); // true
// 不在[-128,127]范围装箱的Integer,值相同也不是同一个对象
System.out.println(c == d); // false
// 使用new一个对象的方法
Integer a = new Integer(1);
Integer b = new Integer(1);
System.out.println(a == b); // false
System.out.println(a.equals(b)); // true
System.out.println(a.equals(1)); // true
System.out.println(a == 1); // true, Integer和int用==比较,Integer自动拆箱unbox,转换为普通int间的比较
/**
- String的比较,==是引用比较,比较字符串是否相同用equals
*/
//用引号创建一个字符串的时候,首先会去常量池中寻找有没有相等的常量对象,没有的话就在常量池中创建这个常量对象;有的话就直接返回这个常量对象的引用
String str1 = “haha”;
String str2 = “haha”;
String str3 = new String(“haha”);
String str4 = new String(“haha”);
// true,首先 String str1 = “hello”,会先到常量池中检查是否有“hello”的存在,发现是没有的,于是在常量池中创建“hello”对象,并将常量池中的引用赋值给str1;第二个字面量 String str2 = “hello”,在常量池中检测到该对象了,直接将引用赋值给str2。
System.out.println(str1 == str2);
// false, 每个String对象都是不同的,所以引用指向的堆地址肯定也不同,所以false。
System.out.println(str3 == str4);
// false,因为==比较的是引用的地址,s2指的是常量池中常量对象的地址,而s1指的是堆中String对象的地址,肯定不同。
System.out.println(str1 == str3);
// true,因为jdk重写了equals()方法,比较的是字符串的内容。
System.out.println(str1.equals(str2));
//true, JDK 1.7后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。
System.out.println(str3.intern()==str1);
14. 动态链接库和静态链接库
-
静态链接库:当要使用时,连接器会找出程序所需的函数,然后将它们拷贝到执行文件,由于这种拷贝是完整的,所以一旦连接成功,静态程序库也就不再需要了。
-
动态链接库:某个程序在运行中要调用某个动态链接库函数的时候,操作系统首先会查看所有正在运行的程序,看在内存里是否已有此库函数的拷贝了。如果有,则让其共享那一个拷贝;只有没有才链接载入。在程序运行的时候,被调用的动态链接库函数被安置在内存的某个地方,所有调用它的程序将指向这个代码段。因此,这些代码必须使用相对地址,而不是绝对地址。在编译的时候,我们需要告诉编译器,这些对象文件是用来做动态链接库的,所以要用地址无关代码(Position Independent Code (PIC))。动态链接库的加载方式有两种:隐式加载和显示加载。
15. 正则表达式
-
定义:在编写处理字符串的程序时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。
-
Java中的String类提供了支持正则表达式操作的方法,包括:matches()、replaceAll()、replaceFirst()、split()。
16. Java基本数据类型及其包装类
Java 为每个原始类型提供了包装类型:
-
原始类型:boolean,char,byte,short,int,long,float,double
-
包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
17. 值传递和引用传递
一般认为,java内的传递都是值传递
-
值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量。
-
引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身。所以对引用对象进行操作会同时改变原对象。
18. 深拷贝和浅拷贝
-
浅拷贝:复制基本类型的属性、引用类型的属性、栈中的变量和变量指向堆内存中的对象的指针,不复制堆内存中的对象。
-
深拷贝:复制基本类型的属性、引用类型的属性、栈中的变量和变量指向堆内存中的对象的指针和堆内存中的对象。
19. 为什么会出现4.0-3.6=0.40000001这种现象?
2进制的小数无法精确的表达10进制小数,计算机在计算10进制小数的过程中要先转换为2进制进行计算,这个过程中出现了误差。
20. 十进制的数在内存中是怎么存的?
补码的形式
-
正数的原反补一样
-
负数的反码是将原码除了符号位的其余位取反,补码是给反码加1.
21. Lamda表达式
-
定义:Lambda 表达式(lambda expression)是一个匿名函数,Lambda 规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法
-
思想:函数式编程思想
-
优点:1. 简洁。2. 非常容易并行计算。
-
缺点:1. 若不用并行计算,很多时候计算速度没有比传统的 for 循环快。(并行计算有时需要预热才显示出效率优势)2. 不容易调试。
22. Java 8系列之Stream
Stream 是用函数式编程方式在集合类上进行复杂操作的工具,其集成了Java 8中的众多新特性之一的聚合操作,开发者可以更容易地使用Lambda表达式,并且更方便地实现对集合的查找、遍历、过滤以及常见计算等。
- Stream的操作分类,在一次聚合操作中,可以有多个Intermediate,但是有且只有一个Terminal。Intermediate主要是用来对Stream做出相应转换及限制流,实际上是将源Stream转换为一个新的Stream,以达到需求效果。
-
Intermediate:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 skip、 parallel、 sequential、 unordered
-
Terminal:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、iterator
-
Short-circuiting:anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
23. final关键字
-
使用final的原因:第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。
-
作用:
1)当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。
2)对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;
3)如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
24. final/finally/finalize
-
final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。
-
finally是异常处理语句结构的一部分,表示总是执行。
-
finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。
25. Java中的IO流
java.io包中还有许多其他的流,主要是为了提高性能和使用方便。
-
按照流向划分为输入流和输出流
-
按照操作单元分划分为字节流和字符流
-
字节流:InputStream、OutputStream
-
字符流:InputStreamReader、OutputStreamWriter
-
按照流的角色划分为节点流和处理流
26. 既然有了字节流,为什么还要有字符流?
字符流是由 Java 虚拟机将字节转换得到的,这个过程非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。
27. java序列化
-
定义:序列化是将 Java 对象转换成字节流的过程。反序列化是将字节流转换成 Java 对象的过程。
-
作用:当Java对象需要在网络上传输或者持久化存储到文件时,就需要对Java对象进行序列化处理。
-
实现:类实现 Serializable 接口,这个接口没有需要实现的方法。实现 Serializable 接口是为了告诉 jvm 这个类的对象可以被序列化。
28. Java 序列化中如果有些字段不想进行序列化,怎么办?
对于不想进行序列化的变量,使用 transient 关键字修饰。transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法。
29. 泛型
-
定义:泛型,即“参数化类型”。将类型作为参数传入方法中,如List。
-
优点:在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
-
Java泛型的实现方法:类型擦除
Java的泛型是伪泛型,因为Java在编译期间,所有的泛型信息都会被擦掉。Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程称为类型擦除。
30. 抽象类和接口
-
声明方法的存在而不去实现它的类被叫做抽象类(abstract class),它用于要创建一个体现某些基本行为的类,并为该类声明方法,但不能在该类中实现该类的情况。不能创建abstract 类的实例。然而可以创建一个变量,其类型是一个抽象类,并让它指向具体子类的一个实例。不能有抽象构造函数或抽象静态方法。Abstract 类的子类为它们父类中的所有抽象方法提供实现,否则它们也是抽象类。取而代之,在子类中实现该方法。知道其行为的其它类可以在类中实现这些方法。
-
接口(interface)是抽象类的变体。在接口中,所有方法都是抽象的。多继承性可通过实现这样的接口而获得。接口中的所有方法都是抽象的,没有一个有程序体。接口只可以定义static final成员变量。接口的实现与子类相似,除了该实现类不能从接口定义中继承行为。当类实现特殊接口时,它定义(即将程序体给予)所有这种接口的方法。然后,它可以在实现了该接口的类的任何对象上调用接口的方法。由于有抽象类,它允许使用接口名作为引用变量的类型。通常的动态联编将生效。引用可以转换到接口类型或从接口类型转换,instanceof 运算符可以用来决定某对象的类是否实现了接口。
-
区别:
1)接口中所有的方法隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。
2)类可以实现很多个接口,但是只能继承一个抽象类
3)Java接口中声明的变量默认都是final的。抽象类可以包含非final的变量。
4)Java接口中的成员函数默认是public的。抽象类的成员函数可以是private,protected或者是public。
5)抽象类可以在不提供接口方法实现的情况下实现接口。
6)类可以不实现抽象类和接口声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。
7)接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含main方法的话是可以被调用的。
31. java类的里面可以再定义一个类吗
java类里面还可以定义一个类,即内部类。java内部类分为: 成员内部类、方法(局部)内部类、静态内部类、匿名内部类 。
-
成员内部类:
-
成员内部类可以无条件访问外部类的属性和方法,但是外部类想要访问内部类属性或方法时,必须要创建一个内部类对象,然后通过该对象访问内部类的属性或方法
-
局部内部类
-
局部内部类存在于方法中。
-
他和成员内部类的区别在于局部内部类的访问权限仅限于方法或作用域内。
-
静态内部类:
-
静态内部类和成员内部类相比多了一个static修饰符。只能访问外部类的静态成员变量与静态方法。
-
静态内部类的非静态成员可访问外部类的静态变量,而不可访问外部类的非静态变量。
-
匿名内部类:
-
没有类名,没有class关键字也没有extends和implements等关键字修饰。唯一没有构造方法的内部类。
-
类的定义和对象的实例化同时进行。
-
内部类的好处
-
完善了Java多继承机制,由于每一个内部类都可以独立的继承接口或类,所以无论外部类是否继承或实现了某个类或接口,对于内部类没有影响。
-
方便写事件驱动程序。
32. &和&&、|和||
-
&:逻辑与,& 两边的表达式都会进行运算
-
&&:短路与,&& 左边的表达式结果为 false 时,&& 右边的表达式不参与计算
-
|:逻辑或,| 两边的表达式都会进行运算
-
||:短路或,|| 左边的表达式结果为 true 时,|| 右边的表达式不参与计算
33. 不使用任何中间变量,交换a,b两个数字的值
-
a=a+b;b=a-b;a=a-b;(a+b可能越界)
-
a = a ^ b;b = a ^ b;a = a ^ b;
34. 常见异常
常见异常类及其父子关系:
Throwable
| ├ Error
| │ ├ IOError
| │ ├ LinkageError
| │ ├ ReflectionError
| │ ├ ThreadDeath
| │ └ VirtualMachineError
| │
| ├ Exception
| │ ├ CloneNotSupportedException
| │ ├ DataFormatException
| │ ├ InterruptedException
| │ ├ IOException
| │ ├ ReflectiveOperationException
| │ ├ RuntimeException(不需要代码显式捕获处理)
| │ ├ ArithmeticException
| │ ├ ClassCastException
| │ ├ ConcurrentModificationException
| │ ├ IllegalArgumentException
| │ ├ IndexOutOfBoundsException
| │ ├ NoSuchElementException
| │ ├ NullPointerException
| │ └ SecurityException
| │ └ SQLException
运行时异常都是 RuntimeException 子类异常
-
NullPointerException - 空指针异常
-
ClassCastException - 类转换异常
-
IndexOutOfBoundsException - 下标越界异常
-
ArithmeticException - 计算异常
-
IllegalArgumentException - 非法参数异常
-
NumberFormatException - 数字格式异常
-
UnsupportedOperationException 操作不支持异常
-
ArrayStoreException - 数据存储异常,操作数组时类型不一致
-
BufferOverflowException - IO 操作时出现的缓冲区上溢异常
-
NoSuchElementException - 元素不存在异常
-
InputMismatchException - 输入类型不匹配异常
-
ConcurrentModificationException – 并发修改异常
35. 设计模式
总体来说设计模式分为三大类(25种):
-
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
-
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
-
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
-
其实还有两类:并发型模式和线程池模式。
36. 设计模式的六大原则
总原则:开闭原则,开闭原则就是说对扩展开放,对修改关闭。
-
单一职责原则:每个类应该实现单一的职责,如若不然,就应该把类拆分。
-
里氏替换原则:任何基类可以出现的地方,子类一定可以出现。
-
依赖倒转原则:面向接口编程,依赖于抽象而不依赖于具体。
-
接口隔离原则:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。
-
最少知道原则:一个类对自己依赖的类
《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》
【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享
知道的越少越好。
- 合成复用原则:尽量首先使用合成/聚合的方式,而不是使用继承。
37. 单例模式
-
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
-
作用:解决一个全局使用的类频繁地创建与销毁。
-
主要优点:
-
提供了对唯一实例的受控访问。
-
由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
-
允许可变数目的实例。
-
主要缺点:
-
由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
-
单例类的职责过重,在一定程度上违背了“单一职责原则”。
-
滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
-
分类:
1)饿汉式(线程安全):在调用getInstance()方法前就初始化instance实例
-
优点:没有加锁,执行效率会提高。
-
缺点:类加载时就初始化,浪费内存。
public class Singleton {
//在类的内部创建一个类的实例,且为static
private static Singleton instance = new Singleton();
//私有化构造器
private Singleton (){}
//此公共方法只能通过类来调用,因为设置的是static
public static Singleton getInstance() {
return instance;
}
}
2)懒汉式(线程不安全):调用getInstance()方法时才创建instance实例
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
解决方法:
a) 加Synchorized锁
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
b) 双端检锁(加锁前后都进行判断)
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
c) 静态内部类
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
d) 枚举是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
public enum Singleton {