八股文(Java基础部分)
文章目录
- 一、基础概念与常识
- 1. Java语言有哪些特点?
- 2. JVM JDK JRE
- 3. 字节码
- 4. JAVA程序从源代码到运行
- 5.AOT(Ahead of Time Compilation)
- 6. JAVA语言编译与解释并存
- 7. Oracle JDK与Open JDK
- 8. JAVA和C++区别
- 9. Java是值传递还是引用传递
- 10. Jdk1.8新特性
- 二、基本语法
- 1. 注释有几种形式
- 2. 标识符和关键字
- 3. JAVA语言关键字
- 3.1 关键字汇总
- 3.2 常用关键字含义
- 4. 自增自减运算符
- 5. 移位运算符
- 6. continue,break 和 return
- 7. 变量
- 7.1 成员变量与局部变量的区别
- 7.2 静态变量的作用
- 7.3 字符型常量和字符串常量
- 8. 方法
- 8.1 静态方法和实例方法
- 8.2 重载和重写
- 8.3 可变长参数
- 8.4 Java 中是否可以重写一个 private 或者 static 方法?
- 8.5 在 Java 中定义一个不做事且没有参数的构造方法有什么作用?
- 三、基本数据类型
- 1. 基本数据类型有8种
- 2. 基本类型和包装类型的区别
- 3. 自动装箱和拆箱
- 4. 包装类型的缓存机制
- 5. 浮点数运算的精度丢失
- 6. 超过 long 整型的数据的表示
- 7. short s1 = 1;s1 = s1 + 1;有什么错?那么 short s1 = 1; s1 += 1呢?有没有错误?
- 8. Integer变量和Int变量比较
- 四、面向对象
- 1. 面向对象和面向过程
- 2. 对象实例和对象引用
- 3. 对象的相等和引用相等
- 4. 类的构造方法
- 5. 面向对象
- 6. 接口和抽象类
- 7. 浅拷贝、深拷贝、引用拷贝(克隆)
- 8. Java 中创建对象的几种方式?
- 9. 如何实现对象的克隆
- 五、Java常见类
- 1. Object类
- 1.1 定义
- 1.2 == 和 equals()
- 1.3 HashCode
- 1.4 为什么重写equals方法需要重写hashcode
- 1.5 Object类常用方法
- 2. String类
- 2.1 String、StringBuilder、StringBuffer的区别
- 2.2 解释String的不可变 **
- 2.3 Java9为何把String的底层实现由char[]改成byte[]
- 2.4 字符拼接:+ 和 stringbuilder
- 2.5 String#equals() 和 Object#equals()
- 2.6 字符串常量池
- 2.7 String 类型的变量和常量做“+”运算时发生了什么?
- 六、异常
- 1. 异常层次结构图
- 2. Exception和Error的区别
- 3.Throwable常用方法
- 4. try-catch-finally如何使用
- 5. finally中的语句一定执行吗
- 6. 如何使用 try-with-resources 代替try-catch-finally?
- 7. 异常使用注意的地方
- 8. try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
- 8. try-catch-finally 中那个部分可以省略?
- 9. 运行时异常与受检异常有何异同?
- 10. throw 和 throws 的区别?
- 七、泛型
- 1. 定义及作用
- 2. 泛型的使用方法
- 3. 项目中哪里使用了泛型
- 4. Java 的泛型是如何工作的 ? 什么是类型擦除 ?
- 5. 什么是泛型中的限定通配符和非限定通配符
- 6. 既然存在泛型擦除,那么java如何保证ArrayList<Integer>添加字符串会报错?
- 八、 反射
- 1. 定义及优缺点
- 2. 应用场景
- 3. 获取对象的四种方式
- 4. 补充原理
- 九、 注解
- 1. 定义
- 2. 注解的解释方式
- 十、 SPI
- 1. 作用及优缺点
- 2. SPI和API的区别
- 十一、序列化和反序列化
- 1. 定义及作用
- 2. 序列化的应用场景
- 3. transient关键字
- 4. JDK自带序列化定义及缺点
- 5. 常见序列化协议
- 十二、IO流
- 1. 定义及分类
- 2. BIO/AIO/NIO
- 3.用户空间和内核空间
- 十三、语法糖
- 1. 定义
- 2. 常见语法糖
- 十四、值传递
- 十五、代理模式
- 1. 定义及作用
- 2. 静态代理
- 3. 动态代理
- 3.1 JDK动态代理原理
- 3.2 CGLIB动态代理
- 十六、BigDecimal
- 1. 介绍
- 2. 常用方法
- 十七、魔法类unsafe
- 十八. 枚举
一、基础概念与常识
1. Java语言有哪些特点?
- 平台无关性(Java虚拟机实现平台无关性);
- 支持多线程(C++语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持)
- 编译与解释并存;一次编写,随处运行
2. JVM JDK JRE
- JVM虚拟机:运行java字节码的虚拟机。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。JVM 并不是只有一种。
- JDK Java Development Kit,是功能齐全的 Java SDK,包括java运行环境JRE,JAVA工具和JAVA基础类库。
- JRE Java 运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的一些基础构件。
JDK与JRE区别
JDK能够创建和编译程序,JRE不能创建新程序
如果你只是为了运行一下 Java 程序的话,那么你只需要安装 JRE 就可以了。如果你需要进行一些 Java 编程方面的工作,那么你就需要安装 JDK 了。但是,这不是绝对的。有时,即使您不打算在计算机上进行任何 Java 开发,仍然需要安装 JDK。
补充
SDK,即Software Development Kit的缩写,译作软件开发工具包。软件开发工具包是一个覆盖面相当广泛的名词,你甚至可以这么理解:辅助开发某一类软件的相关文档、范例和工具的集合都可以叫做SDK。
3. 字节码
- 定义:JVM可以理解的代码,即.class文件。
- 好处:相对高效(没有C++,Rust,Go语言高效);字节码并不针对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。
4. JAVA程序从源代码到运行
JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。
因此引进JIT
JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。Java 是编译与解释共存的语言 。
编译:JIT 解释:上边的图
5.AOT(Ahead of Time Compilation)
- 定义:JDK 9 引入了一种新的编译模式 AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码
- 为什么不全部使用AOT
和 Java 语言的动态特性相关,CGLIB 动态代理使用的是 ASM 技术,而这种技术大致原理是运行时直接在内存中生成并加载修改后的字节码文件也就是 .class 文件,如果全部使用 AOT 提前编译,也就不能使用 ASM 技术了。为了支持类似的动态特性,所以选择使用 JIT 即时编译器
6. JAVA语言编译与解释并存
- 编译型:编译型语言open in new window 会通过编译器open in new window将源代码一次性翻译成可被该平台执行的机器码。一般情况下,编译语言的执行速度比较快,开发效率比较低。常见的编译性语言有 C、C++、Go、Rust 等等。
解释型:解释型语言open in new window会通过解释器open in new window一句一句的将代码解释(interpret)为机器代码后再执行。解释型语言开发效率比较快,执行速度比较慢。常见的解释性语言有 Python、JavaScript、PHP 等等。 - 并存的原因
因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码(.class 文件),这种字节码必须由 Java 解释器来解释执行。
7. Oracle JDK与Open JDK
区别
- 发布时间: oracle 6个月, open 3个月
- 开源问题: oracle 部分开源,open完全开源
- 稳定性:oracle更稳定
- 响应性和jvm性能: oracle更优
- 版本支持问题:Oracle JDK 不会为即将发布的版本提供长期支持
- 协议: oracle: BCL/OTN 协议 OpenJDK :GPL V2
open jdk存在的必要
8. JAVA和C++区别
- 相同点 面向对象的语言,支持继承、封装和多态
- 不同点
(1) 指针:java无指针 程序内存更安全
(2) 继承: java的类单继承,接口多继承;C++类多继承
(3) 垃圾回收机制:java自动内存管理垃圾回收机制
(4) 重载:C ++同时支持方法重载和操作符重载,但是 Java 只支持方法重载
9. Java是值传递还是引用传递
public void test() {String str = "hello";change(str);System.out.println(str);
}
private void change(String str) {str = "world";
}//输出hello
所以是值传递
引用传递(pass by reference)是指在调用方法时将实际参数的地址直接传递到方法中,那么在方法中对参数所进行的修改,将影响到实际参数。
值传递(pass by value)是指在调用方法时将实际参数拷贝一份传递到方法中,这样在方法中如果对参数进行修改,将不会影响到实际参数。
10. Jdk1.8新特性
- Lamdba表达式
一个匿名函数,我们可以将Lambda表达式理解为一段可以传递的代码(将代码像数据一样传递) - 函数式接口
只包含一个抽象方法的接口,称为函数式接口,并且可以使用lambda表达式来创建该接口的对象,可以在任意函数式接口上使用@FunctionalInterface注解,来检测它是否是符合函数式接口。
函数式接口就是可以适用于Lambda使用的接口。 - 方法引用和构造引用
- Stream API
- 接口中的默认方法和静态方法
java8允许接口中包含具体实现的方法体,该方法是默认方法,它需要使用default关键字修饰
java8中允许接口中定义静态方法,使用static关键字修饰 - 新时间日期API
- OPtional
- 函数式编程部分
添加链接描述
二、基本语法
1. 注释有几种形式
- 单行
- 多行
- 文档
2. 标识符和关键字
标识符就是一个名字 。关键字是被赋予特殊含义的标识符。
3. JAVA语言关键字
3.1 关键字汇总
3.2 常用关键字含义
重点:static、final、this、super
陌生:
native 加了该关键字 的方法没有方法体的,也就是说没有实现,而真正的实现是由C/C++编写的 (少见)
strictfp 保证浮点数运算的精确性,而且在不同的硬件平台会有一致的运行结果 (少见)
synchronized 添加同步代码块或是同步方法
transient 果不希望某个属性被序列化 (少见)
volatile 保证变量的可见性
enum 定义枚举类型:
instanceof 判断某个对象是否为某个类型或是某个类型/接口的实现
goto,const目前没有任何作用 (少见)
- final关键字:
对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;
如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
当用final修饰一个类时,表明这个类不能被继承。
修饰方法,表示方法不可以重写 - super关键字
(1)访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。
(2)访问父类的成员:如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。
(3)this 和 super 不能同时出现在一个构造函数里面,因为 this 必然会调用其它的构造函数,其它的构造函数必然也会有 super 语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
4. 自增自减运算符
++ – 当运算符放在变量之前时(前缀),先自增/减,再赋值;当运算符放在变量之后时(后缀),先赋值,再自增/减。
5. 移位运算符
三种运算符的定义及使用
(1)<< 左移 高位丢弃 低位补0 x << 1,表示乘2(不溢出的情况)
(2)>> 带符号右移 高位补符号位,低位丢弃。正数高位补 0,负数高位补 1 (x>>1 相当于x除以2)
(3)>>> 无符号右移,忽略符号位,空位都以 0 补齐
移位运算符支持的类型
仅支持int和long 类型,short,byte,char类型先转为int类型。float和double不能进行移位操作
移位位数超出数值所占有位数
当 int 类型左移/右移位数大于等于 32 位操作时,会先求余(%)后再进行左移/右移操作。
int 32%32 相当于移动0位 42%32=10 移动10位
6. continue,break 和 return
continue跳出当前循环,继续下一次循环 // break终止循环体// return跳出方法,结束该方法
7. 变量
7.1 成员变量与局部变量的区别
具体细节可以参考博文添加链接描述
语法形式 成员变量属于类,局部变量属于方法
成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。存储方式
生存时间 成员变量随对象创建而存在 局部变量随方法调用生成,方法调用结束消亡
默认值 自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值,局部变量不会自动赋值
== 分类 ==
7.2 静态变量的作用
参考博文 添加链接描述
静态变量可以被类的所有实例共享。无论一个类创建了多少个对象,它们都共享同一份静态变量。
通常情况下,静态变量会被 final 关键字修饰成为常量。
7.3 字符型常量和字符串常量
- 形式:单引号 和 双引号的区别
- 含义: 字符型常量相当于整型值(ASCII值),参与表达式运算;字符串常量相当于地址值
- 占内存大小: 字符常量占2个字节;字符串常量占若干个字节
8. 方法
8.1 静态方法和实例方法
- 调用方式
静态方法:类名.方法名 或者 对象.方法名 (不建议)
实例方法: 需要创建对象,对象.方法名 - 访问类成员是否有限制
静态方法只允许访问静态成员,不允许访问实例成员
8.2 重载和重写
都是实现多态的方式
8.3 可变长参数
(1) 定义 允许在调用方法时传入不定长度的参数,可变长参数只能作为最后一个参数
public static void method2(String arg1, String... args) {//......
}
(2) 遇到方法重载情况, 优先匹配固定参数还是可变长参数
答案是会优先匹配固定参数的方法,因为固定参数的方法匹配度更高。
8.4 Java 中是否可以重写一个 private 或者 static 方法?
Java 中 static 方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而 static 方法是编译时静态绑定的。static 方法跟类的任何实例都不相关,所以概念上不适用。
Java 中也不可以覆盖 private 的方法,因为 private 修饰的变量和方法只能在当前类中使用, 如果是其他的类继承当前类是不能访问到 private 变量或方法的,当然也不能覆盖
8.5 在 Java 中定义一个不做事且没有参数的构造方法有什么作用?
Java 程序在执行子类的构造方法之前,如果没有用 super() 来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。
因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super() 来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是:在父类里加上一个不做事且没有参数的构造方法。
三、基本数据类型
1. 基本数据类型有8种
包装类型: Byte Short Integer Long Float Double Character Boolean
bit位数/比特
byte字节
2. 基本类型和包装类型的区别
- 初始值: 基本类型有默认值且不是null 包装类型没有默认值,不赋值就是null
- 能否用于泛型:包装类型可以用于泛型
- 存储位置:包装类型属于对象,存放在堆内存,基本类型:局部变量放在栈,成员变量放在堆内存
- 占用空间:基本数据类型占用空间小
3. 自动装箱和拆箱
定义:装箱就是把基本类型用对应的引用类型包装起来, 拆箱是把包装类型转换为基本数据类型
Integer i = 10; //装箱
int n = i; //拆箱
调用的方法:装箱调用了valueof方法,拆箱用了xxxValue()方法
Integer i = 10 等价于 Integer i = Integer.valueOf(10)
int n = i 等价于 int n = i.intValue();
4. 包装类型的缓存机制
(1)作用: Java 基本数据类型的包装类型的大部分都用到了缓存机制来提升性能。
(2) Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character 创建了数值在 [0,127] 范围的缓存数据,Boolean 直接返回 True or False。
两种浮点数类型的包装类 Float,Double 并没有实现缓存机制。
(3) 区别
Integer i1 = 40;
Integer i2 = new Integer(40);
System.out.println(i1==i2);//false
//Integer i1=40 这一行代码会发生装箱,也就是说这行代码等价于 Integer i1=Integer.valueOf(40) 。因此,i1 直接使用的是缓存中的对象。而Integer i2 = new Integer(40) 会直接创建新的对象。
(4) 所有整型包装类对象之间值的比较,全部使用 equals 方法比较。
5. 浮点数运算的精度丢失
(1) 代码演示
float a = 2.0f - 1.9f;
float b = 1.8f - 1.7f;
System.out.println(a);// 0.100000024
System.out.println(b);// 0.099999905
System.out.println(a == b);// false
(2) 原因
这个和计算机保存浮点数的机制有很大关系。我们知道计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况。
(3) 解决方案
BigDecimal **可以实现对浮点数的运算,**不会造成精度丢失。通常情况下,大部分需要浮点数精确运算结果的业务场景(比如涉及到钱的场景)都是通过 BigDecimal 来做的。
6. 超过 long 整型的数据的表示
BigInteger 或者 BigDecimal
7. short s1 = 1;s1 = s1 + 1;有什么错?那么 short s1 = 1; s1 += 1呢?有没有错误?
对于 short s1 = 1; s1 = s1 + 1; 来说,在 s1 + 1 运算时会自动提升表达式的类型为 int ,那么将 int 型值赋值给 short 型变量,s1 会出现类型转换错误。
对于 short s1 = 1; s1 += 1; 来说,+= 是 Java 语言规定的运算符,Java 编译器会对它进行特殊处理,因此可以正确编译。
复合赋值运算符+=里面隐含强制类型转换
8. Integer变量和Int变量比较
Integer 变量和 int 变量比较时,只要两个变量的值是相等的,则结果为 true。因为包装类 Integer 和基本数据类型 int 类型进行比较时,Java 会自动拆包装类为 int,然后进行比较,实际上就是两个 int 型变量在进行比较;
四、面向对象
1. 面向对象和面向过程
面向过程把解决问题的过程拆成一个个方法,通过一个个方法的执行解决问题。
面向对象会先抽象出对象,然后用对象执行方法的方式解决问题。
2. 对象实例和对象引用
new 创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)
一个对象引用可以指向 0 个或 1 个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有 n 个引用指向它(可以用 n 条绳子系住一个气球)
3. 对象的相等和引用相等
对象相等:内存中存放的内容是否相等。
引用相等:指向的内存地址是否相等。
4. 类的构造方法
(1) 作用 一种特殊的方法,主要作用是完成对象的初始化工作
(2) 如果一个类没有声明构造方法,该程序能正确执行吗?
可以。一个类即使没有声明构造方法也会有默认的不带参数的构造方法。
(3) 构造方法有哪些特点?是否可被 override?
名字与类名相同。没有返回值,但不能用 void 声明构造函数。生成类的对象时自动执行,无需调用。
能被重载
5. 面向对象
(1) 封装
通常认为封装是把数据和操作数据的方法封装起来,对数据的访问只能通过已定义的接口。
(2) 继承
- 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。
- 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。
(3) 多态
多态分为编译时多态和运行时多态:
编译时多态主要指方法的重载
运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定
另一种解释
表示一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。 - 对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
- 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
- 多态不能调用“只在子类存在但在父类不存在”的方法;
- 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。
6. 接口和抽象类
(1) 相同点
都不能被实例化。都可以包含抽象方法。
(2) 区别
作用:接口主要用于对类的行为进行约束。抽象类主要用于代码复用
继承:一个类只能继承一个类,但是可以实现多个接口
成员变量:接口中的成员变量只能是 public static final 类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。
- 抽象类补充
抽象方法,是指没有方法体的方法,同时抽象方法还必须使用关键字abstract做修饰
而拥有抽象方法的类就是抽象类,抽象类要使用abstract关键字声明。
(1)抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public;
(2)抽象类不能直接实例化,需要依靠子类采用向上转型的方式处理;
(3)抽象类必须有子类,使用extends继承,一个子类只能继承一个抽象类;
(4)子类(如果不是抽象类)则必须覆写抽象类之中的全部抽象方法(如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。);
7. 浅拷贝、深拷贝、引用拷贝(克隆)
引用拷贝
引用拷贝就是两个不同的引用指向同一个对象。浅拷贝
浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。深拷贝
深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。clone方法
Object对象有个clone()方法,实现了对象中各个属性的复制,但它的可见范围是protected的,所以实体类使用克隆的前提是:
① 实现Cloneable接口,这是一个标记接口,自身没有方法。
② 覆盖clone()方法,可见性提升为public。实现深拷贝两种方式
重载clone方法
序列化
8. Java 中创建对象的几种方式?
1、使用 new 关键字;
2、使用 Class 类的 newInstance 方法,该方法调用无参的构造器创建对象(反射):Class.forName.newInstance();
3、使用 clone() 方法;
4、反序列化,比如调用 ObjectInputStream 类的 readObject() 方法。
9. 如何实现对象的克隆
(1)实现 Cloneable 接口并重写 Object 类中的 clone() 方法;
(2)实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深克隆。
五、Java常见类
1. Object类
1.1 定义
Object 类是一个特殊的类,是所有类的父类
1.2 == 和 equals()
- ==的使用
基本数据类型:比较值
引用数据类型 :比较对象的内存地址 - equlas的使用
作用:不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals方法存在于object类中,所有的类都有equals()方法
使用的两种情况: 类没有重写equals方法,等价于 == ;重写了equals方法
1.3 HashCode
(1) 为什么要有HashCode()
hashCode() 的作用是获取哈希码(int 整数),也称为散列码。这个哈希码的作用是确定该对象在哈希表中的索引位置。
- 如果两个对象的hashCode 值相等,那这两个对象不一定相等(哈希碰撞)。
- 如果两个对象的hashCode 值相等并且equals()方法也返回 true,我们才认为这两个对象相等。
- 如果两个对象的hashCode 值不相等,我们就可以直接认为这两个对象不相等。
1.4 为什么重写equals方法需要重写hashcode
因为两个相等的对象的 hashCode 值必须是相等。也就是说如果 equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。如果重写 equals() 时没有重写 hashCode() 方法的话就可能会导致 equals 方法判断是相等的两个对象,hashCode 值却不相等。
1.5 Object类常用方法
/*** native 方法,用于返回当前运行时对象的 Class 对象,使用了 final 关键字修饰,故不允许子类重写。*/
public final native Class<?> getClass()
/*** native 方法,用于返回对象的哈希码,主要使用在哈希表中,比如 JDK 中的HashMap。*/
public native int hashCode()
/*** 用于比较 2 个对象的内存地址是否相等,String 类对该方法进行了重写以用于比较字符串的值是否相等。*/
public boolean equals(Object obj)
/*** naitive 方法,用于创建并返回当前对象的一份拷贝。*/
protected native Object clone() throws CloneNotSupportedException
/*** 返回类的名字实例的哈希码的 16 进制的字符串。建议 Object 所有的子类都重写这个方法。*/
public String toString()
/*** native 方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。*/
public final native void notify()
/*** native 方法,并且不能重写。跟 notify 一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。*/
public final native void notifyAll()
/*** native方法,并且不能重写。暂停线程的执行。注意:sleep 方法没有释放锁,而 wait 方法释放了锁 ,timeout 是等待时间。*/
public final native void wait(long timeout) throws InterruptedException
/*** 多了 nanos 参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 毫秒。。*/
public final void wait(long timeout, int nanos) throws InterruptedException
/*** 跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念*/
public final void wait() throws InterruptedException
/*** 实例被垃圾回收器回收的时候触发的操作*/
protected void finalize() throws Throwable { }
2. String类
2.1 String、StringBuilder、StringBuffer的区别
- 可变性 string不可变
- 线程安全性 string线程安全;StringBuffer加了锁,线程安全;StringBuilder非线程安全
- 性能 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险
- 使用总结 :操作少量数据,用string;单线程操作字符串缓冲区下操作大量数据,用StringBuilder;多线程操作字符串缓冲区下操作大量数据,用StringBuffer
2.2 解释String的不可变 **
String 类中使用 final 关键字修饰字符数组来保存字符串。
- 保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法;
- String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。
- 不可变性的好处
- 可以缓存hash值
因为string的hash值经常被使用,例如用String用作HashMap的Key。不可变的特性也让hash值也不变 - 常量池优化
String对象创建后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回缓存的引用 - 线程安全
不可变性保证了不可变性
2.3 Java9为何把String的底层实现由char[]改成byte[]
Latin-1 编码方案下,byte 占一个字节(8 位),char 占用 2 个字节(16),byte 相较 char 节省一半的内存空间。
2.4 字符拼接:+ 和 stringbuilder
Java 语言本身并不支持运算符重载,“+”和“+=”是专门为 String 类重载过的运算符,也是 Java 中仅有的两个重载过的运算符。
字符串对象通过**“+”的字符串拼接方式**,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。
在循环内使用“+”进行字符串的拼接的话,存在比较明显的缺陷:编译器不会创建单个 StringBuilder 以复用,会导致创建过多的 StringBuilder 对象。
2.5 String#equals() 和 Object#equals()
String 中的 equals 方法是被重写过的,比较的是 String 字符串的值是否相等
Object 的 equals 方法是比较的对象的内存地址。
2.6 字符串常量池
(1) 作用
字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
// 在堆中创建字符串对象”ab“
// 将字符串对象”ab“的引用保存在字符串常量池中
String aa = "ab";
// 直接返回字符串常量池中字符串对象”ab“的引用
String bb = "ab";
System.out.println(aa==bb);// true
(2) String s1 = new String(“abc”);这句话创建了几个字符串对象?
如果字符串常量池中不存在字符串对象“abc”的引用,那么会在堆中创建 2 个字符串对象“abc”。
如果字符串常量池中已存在字符串对象“abc”的引用,则只会在堆中创建 1 个字符串对象“abc”。
(3) intern 方法有什么作用?
String.intern() 是一个 native(本地)方法,其作用是将指定的字符串对象的引用保存在字符串常量池中,可以简单分为两种情况:
如果字符串常量池中保存了对应的字符串对象的引用,就直接返回该引用。
如果字符串常量池中没有保存了对应的字符串对象的引用,那就在常量池中创建一个指向该字符串对象的引用并返回。
// 在堆中创建字符串对象”Java“
// 将字符串对象”Java“的引用保存在字符串常量池中
String s1 = "Java";
// 直接返回字符串常量池中字符串对象”Java“对应的引用
String s2 = s1.intern();
// 会在堆中在单独创建一个字符串对象
String s3 = new String("Java");
// 直接返回字符串常量池中字符串对象”Java“对应的引用
String s4 = s3.intern();
// s1 和 s2 指向的是堆中的同一个对象
System.out.println(s1 == s2); // true
// s3 和 s4 指向的是堆中不同的对象
System.out.println(s3 == s4); // false
// s1 和 s4 指向的是堆中的同一个对象
System.out.println(s1 == s4); //true
2.7 String 类型的变量和常量做“+”运算时发生了什么?
String str1 = "str";//
String str2 = "ing";
String str3 = "str" + "ing";//常量池
String str4 = str1 + str2;//堆
String str5 = "string";//常量池
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false
//加了final关键字
final String str1 = "str";//加了final关键字就是常量
final String str2 = "ing";
// 下面两个表达式其实是等价的
String c = "str" + "ing";// 常量池中的对象
String d = str1 + str2; // 常量池中的对象
System.out.println(c == d);// true
补充常量折叠会把常量表达式的值求出来作为常量嵌在最终生成的代码中,这是 Javac 编译器会对源代码做的极少量优化措施之一(代码优化几乎都在即时编译器中进行)。
对于 String str3 = “str” + “ing”; 编译器会给你优化成 String str3 = “string”; 。
对象引用和“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。
String str4 = new StringBuilder().append(str1).append(str2).toString();
尽量避免多个字符串对象拼接,因为这样会重新创建对象。如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer。
六、异常
1. 异常层次结构图
父类名称+两大类三小类+如何处理
2. Exception和Error的区别
exception和error共同祖先 java.lang中的throwable
exception: 程序本身可以处理的异常,可以用catch来捕获。
具体又可以分为checked exception必须处理,unchecked exception可以不处理Checked Exception(受检异常):这种异常在编译时就可以被检测出来,必须要在代码中进行处理或者声明抛出,否则编译不通过。这类异常主要是由程序的外部环境引起的,例如文件不存在、网络连接失败等。常见的Checked Exception包括IOException、SQLException等。
Unchecked Exception(非受检异常):这种异常通常是由程序内部错误引起的,例如NullPointerException、ArrayIndexOutOfBoundsException、IllegalArgumentException等。这类异常不需要在代码中声明抛出,也可以不进行处理,但是如果不进行处理,程序会崩溃。
error程序无法处理的异常。不建议用catch来捕获
3.Throwable常用方法
- String getMessage() 返回异常发生时的简要描述
- String toString() 返回异常发生时的详细信息
- String getLocalizedMessage() 返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 getMessage()返回的结果相同
- void printStackTrace() 在控制台上打印 Throwable 对象封装的异常信息
4. try-catch-finally如何使用
- try: 用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
- catch:用于处理 try 捕获到的异常。
- finally: 无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。
不要在 finally 语句块中使用 return! 当 try 语句和 finally 语句中都有 return 语句时,try 语句块中的 return 语句会被忽略。
5. finally中的语句一定执行吗
不一定。以下几种情况: 1)finally之前虚拟机被终止运行;2)程序所在线程死亡;3)关闭CPU
另外一个答案
(1)当程序进入 try 块之前就出现异常时,会直接结束,不会执行 finally 块中的代码;
(2)当程序在 try 块中强制退出时也不会去执行 finally 块中的代码,比如在 try 块中执行 exit 方法。
6. 如何使用 try-with-resources 代替try-catch-finally?
- 适用范围:任何实现 java.lang.AutoCloseable或者java.io.Closeable的对象 (面对必须要关闭的资源,优先考虑)
- 关闭资源和finally块执行的顺序: 在 try-with-resources 语句中,任何 catch 或 finally 块在声明的资源关闭后运行
7. 异常使用注意的地方
- 不要把异常定义为静态变量,因为这样会导致异常栈信息错乱
- 抛出的异常信息一定要有意义
- 建议抛出更加具体的异常
- 使用日志打印异常之后就不要再抛出异常了(两者不要同时存在一段代码逻辑中)
8. try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
会。程序在执行到 return 时会首先将返回值存储在一个指定的位置,其次去执行 finally 块,最后再返回。因此,对基本数据类型,在 finally 块中改变 return 的值没有任何影响,直接覆盖掉;而对引用类型是有影响的,返回的是在 finally 对 前面 return 语句返回对象的修改值。
8. try-catch-finally 中那个部分可以省略?
catch 和 finally可以省略其中一个,但必须保留其中一个。try 只适合处理运行时异常,try+catch 适合处理运行时异常+普通异常。也就是说,如果你只用 try 去处理普通异常却不加以 catch 处理,编译是通不过的,因为编译器硬性规定,普通异常如果选择捕获,则必须用 catch 显示声明以便进一步处理。而运行时异常在编译时没有如此规定,所以 catch 可以省略,你加上 catch 编译器也觉得无可厚非。
9. 运行时异常与受检异常有何异同?
运行时异常:如:空指针异常、指定的类找不到、数组越界、方法传递参数错误、数据类型转换错误。可以编译通过,但是一运行就停止了,程序不会自己处理;
受检查异常:要么用 try … catch… 捕获,要么用 throws 声明抛出,交给父类处理。
10. throw 和 throws 的区别?
(1)throw:在方法体内部,表示抛出异常,由方法体内部的语句处理;throw 是具体向外抛出异常的动作,所以它抛出的是一个异常实例;
(2)throws:在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常的处理;表示出现异常的可能性,并不一定会发生这种异常。
七、泛型
1. 定义及作用
Java 泛型(Generics) 是 JDK 5 中引入的一个新特性。使用泛型参数,可以增强代码的可读性以及稳定性。
2. 泛型的使用方法
泛型类、泛型接口、泛型方法。
3. 项目中哪里使用了泛型
- 自定义接口通用返回结果 CommonResult 通过参数 T 可根据具体的返回类型动态指定结果的数据类型
- 定义 Excel 处理类 ExcelUtil 用于动态指定 Excel 导出的数据类型
- 构建集合工具类(参考 Collections 中的 sort, binarySearch 方法)
4. Java 的泛型是如何工作的 ? 什么是类型擦除 ?
泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如:List<String> 在运行时仅用一个 List 来表示。这样做的目的,是确保能和 Java 5 之前的版本开发二进制类库进行兼容。
类型擦除:泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如 < T > 则会被转译成普通的 Object 类型,如果指定了上限如 < T extends String > 则类型参数就被替换成类型上限。
5. 什么是泛型中的限定通配符和非限定通配符
- 限定通配符
< ? extends T > 和< ? super T >
一种是< ? extends T > 它通过确保类型必须是 T 的子类来设定类型的上界,另一种是< ? super T >它通过确保类型必须是 T 的父类来设定类型的下界。
非限定通配符
< ? > 可以用任意类型来替代。
6. 既然存在泛型擦除,那么java如何保证ArrayList添加字符串会报错?
八、 反射
1. 定义及优缺点
- 通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性
- 优点 : 可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利
- 缺点:让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。
2. 应用场景
框架、注解
应用举例:工厂模式,使用反射机制,根据全限定类名获得某个类的 Class 实例。
3. 获取对象的四种方式
- 知道具体类的情况下可以使用
- 通过 Class.forName()传入类的全路径获取
- 通过对象实例instance.getClass()获取
- 通过类加载器xxxClassLoader.loadClass()传入类路径获取
4. 补充原理
Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。
九、 注解
1. 定义
Annotation (注解) 是 Java5 开始引入的新特性,可以看作是一种特殊的注释,主要用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用。注解本质是一个继承了Annotation 的特殊接口。
2. 注解的解释方式
- 编译期直接扫描 :编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用@Override
注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。 - 运行期通过反射处理 :像框架中自带的注解(比如 Spring 框架的 @Value 、@Component)都是通过反射来进行处理的。
十、 SPI
1. 作用及优缺点
- SPI 即 Service Provider Interface,字面意思就是:“服务提供者的接口”,我的理解是:专门提供给服务提供者或者扩展框架功能的开发者去使用的一个接口。
- 优点:能够大大地提高接口设计的灵活性
- 缺点:需要遍历加载所有的实现类,不能做到按需加载,这样效率还是相对较低的;当多个 ServiceLoader 同时 load
时,会有并发问题。
SPI接口的使用
比如:Spring 框架、数据库加载驱动、日志接口、以及 Dubbo 的扩展实现等等。
2. SPI和API的区别
API是实现方提供接口,SPI是调用方提供接口
SPI提供实现的实现类打包成Jar文件,这个Jar文件里面必须有META-INF目录,其下又有services目录,其下有一个文本文件,文件名即为SPI接口的全名,文件的内容该jar包中提供的SPI接口的实现类名
核心图解
十一、序列化和反序列化
1. 定义及作用
- 序列化: 将数据结构或对象转换成二进制字节流的过程
- 反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程
- 序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。
2. 序列化的应用场景
- 对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化;
- 将对象存储到文件之前需要进行序列化,将对象从文件中读取出来需要进行反序列化;
- 将对象存储到数据库(如 Redis)之前需要用到序列化,将对象从缓存数据库中读取出来需要反序列化;
- 将对象存储到内存之前需要进行序列化,从内存中读取出来之后需要进行反序列化。
3. transient关键字
- transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。
transient 只能修饰变量,不能修饰类和方法。
transient 修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰 int 类型,那么反序列后结果就是 0
static 变量因为不属于任何对象(Object),所以无论有没有 transient 关键字修饰,均不会被序列化。
4. JDK自带序列化定义及缺点
- JDK 自带的序列化,只需实现 java.io.Serializable接口即可。
- 不支持跨语言调用;性能差 ;存在安全问题
5. 常见序列化协议
Hessian、Kryo、Protobuf、ProtoStuff,这些都是基于二进制的序列化协议。
十二、IO流
1. 定义及分类
- 数据输入到计算机内存的过程即输入,反之输出到外部存储(比如数据库,文件,远程主机)的过程即输出。
- IO 流在 Java 中分为输入流和输出流,而根据数据的处理方式又分为字节流和字符流。
- InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流
- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
2. BIO/AIO/NIO
BIO同步阻塞IO模型:只要有连接就启动线程
NIO同步非阻塞IO模型:多路复用器轮询道IO请求才启动线程
AIO异步IO模型:操作系统完成后再通知服务器启动线程
3.用户空间和内核空间
为了保证操作系统的稳定性和安全性,一个进程的地址空间划分为 用户空间(User space) 和 内核空间(Kernel space ) 。
像我们平常运行的应用程序都是运行在用户空间,只有内核空间才能进行系统态级别的资源有关的操作,比如文件管理、进程通信、内存管理等等。
也就是说,我们想要进行 IO 操作,一定是要依赖内核空间的能力。并且,用户空间的程序不能直接访问内核空间。当想要执行 IO 操作时,由于没有执行这些操作的权限,只能发起系统调用请求操作系统帮忙完成。因此,用户进程想要执行 IO 操作的话,必须通过 系统调用 来间接访问内核空间
十三、语法糖
1. 定义
- 语法糖(Syntactic sugar代指的是编程语言为了方便程序员开发程序而设计的一种特殊语法,这种语法对编程语言的功能并没有影响。实现相同的功能,基于语法糖写出来的代码往往更简单简洁且更易阅读
- Java 虚拟机并不支持这些语法糖。这些语法糖在编译阶段就会被还原成简单的基础语法结构,这个过程就是解语法糖。
- Java 语言中,javac命令可以将后缀名为.java的源文件编译为后缀名为.class的可以运行于 Java虚拟机的字节码。如果你去看com.sun.tools.javac.main.JavaCompiler的源码,你会发现在compile()中有一个步骤就是调用desugar(),这个方法就是负责解语法糖的实现的。
2. 常见语法糖
- switch支持string与枚举
字符串的 switch 是通过equals()和hashCode()方法来实现的 - 泛型
虚拟机中没有泛型,只有普通类和普通方法,所有泛型类的类型参数在编译时都会被擦除,泛型类并没有自己独有的Class类对象。比如并不存在List.class或是List.class,而只有List.class - 自动拆装箱
装箱过程是通过调用包装器的 valueOf 方法实现的,而拆箱过程是通过调用包装器的 xxxValue 方法实现的 - 可变长参数
可变参数在被使用的时候,他首先会创建一个数组,数组的长度就是调用该方法是传递的实参的个数,然后再把参数值全部放到这个数组当中,然后再把这个数组作为参数传递到被调用的方法中 - 枚举
当我们使用enum来定义一个枚举类型的时候,编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能被继承。 - 内部类
内部类又称为嵌套类,可以把内部类理解为外部类的一个普通成员。 - 条件编译
Java 语法的条件编译,是通过判断条件为常量的 if 语句实现的。其原理也是 Java 语言的语法糖。根据 if 判断条件的真假,编译器直接把分支为 false 的代码块消除。通过该方式实现的条件编译,必须在方法体内实现,而无法在正整个 Java 类的结构或者类的属性上进行条件编译,这与 C/C++的条件编译相比,确实更有局限性。 - 断言
断言的底层实现就是 if 语言,如果断言结果为 true,则什么都不做,程序继续执行,如果断言结果为 false,则程序抛出 AssertError 来打断程序的执行。 - 数值字面量
在 java 7 中,数值字面量,不管是整数还是浮点数,都允许在数字之间插入任意多个下划线。这些下划线不会对字面量的数值产生影响,目的就是方便阅读。 - for-each
for-each 的实现原理其实就是使用了普通的 for 循环和迭代器。 - try-with-resources
从 Java 7 开始,jdk 提供了一种更好的方式关闭资源,使用try-with-resources语句
其实背后的原理也很简单,那些我们没有做的关闭资源的操作,编译器都帮我们做了 - lambda表达式
Labmda 表达式不是匿名内部类的语法糖,但是他也是一个语法糖。实现方式其实是依赖了几个 JVM 底层提供的 lambda 相关 api。
十四、值传递
Java 中将实参传递给方法(或函数)的方式是 值传递 :
值传递:方法接收的是实参值的拷贝,会创建副本
引用传递:方法接收的直接是实参所引用的对象在堆中的地址,不会创建副本,对形参的修改将影响到实参。
十五、代理模式
1. 定义及作用
- 代理模式是一种比较好理解的设计模式。简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
- 代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。
2. 静态代理
- 静态代理中,我们对目标对象的每个方法的增强都是手动完成的(后面会具体演示代码),非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)
- 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。
- 上面我们是从实现和应用角度来说的静态代理,从 JVM 层面来说, 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。
3. 动态代理
- 我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类( CGLIB 动态代理机制)。
- 动态代理在我们日常开发中使用的相对较少,但是在框架中的几乎是必用的一门技术
- 从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
- 主要又分为JDK动态代理和CGLIB动态代理
JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。
3.1 JDK动态代理原理
添加链接描述
动态代理:当想要给实现了某个接口的类中的方法,加一些额外的处理。比如说加日志,加事务等。可以给这个类创建一个代理,故名思议就是创建一个新的类,这个类不仅包含原来类方法的功能,而且还在原来的基础上添加了额外处理的新功能。这个代理类并不是定义好的,是动态生成的。具有解耦意义,灵活,扩展性强。
动态代理的应用:Spring 的 AOP 、加事务、加权限、加日志。
三部曲:1. 自定义接口及实现类
2. 自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;
3.2 CGLIB动态代理
- 实现定义一个类
- 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK动态代理中的 invoke 方法类似
- 通过 Enhancer 类的 create()创建代理类
十六、BigDecimal
1. 介绍
- BigDecimal 可以实现对浮点数的运算,不会造成精度丢失。
- 通常情况下,大部分需要浮点数精确运算结果的业务场景(比如涉及到钱的场景)都是通过 BigDecimal 来做的。
- 《阿里巴巴 Java 开发手册》中提到:浮点数之间的等值判断,基本数据类型不能用 == 来比较,包装数据类型不能用 equals 来判断。
2. 常用方法
- 创建
BigDecimal(String val)构造方法或者 BigDecimal.valueOf(double val) 静态方法来创建对象。 - 加减乘除
add 方法用于将两个 BigDecimal 对象相加,subtract 方法用于将两个 BigDecimal 对象相减。multiply 方法用于将两个 BigDecimal 对象相乘,divide 方法用于将两个 BigDecimal 对象相除。 - 大小比较
a.compareTo(b) : 返回 -1 表示 a 小于 b,0 表示 a 等于 b , 1 表示 a 大于 b。 - 保留几位小数
通过 setScale方法设置保留几位小数以及保留规则
十七、魔法类unsafe
Unsafe 是位于 sun.misc 包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升 Java 运行效率、增强 Java 语言底层资源操作能力方面起到了很大的作用。但由于 Unsafe 类使 Java 语言拥有了类似 C 语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。在程序中过度、不正确使用 Unsafe 类会使得程序出错的概率变大,使得 Java 这种安全的语言变得不再“安全”,因此对 Unsafe 的使用一定要慎重。主要有以下作用:
- 内存操作
- 内存屏障
- 对象操作
- 数据操作
- CAS 操作
- 线程调度
- Class 操作
- 系统信息
十八. 枚举
Java 枚举是一个特殊的类,一般表示一组常量,比如一年的 4 个季节,一个年的 12 个月份,一个星期的 7 天,方向有东南西北等。
可以用来实现单例模式
/*** 8. 枚举*/
enum Singleton8{INSTANCE;public void sayOK(){System.out.println("ok");}
}
八股文(Java基础部分)相关推荐
- 八股文--Java 基础上
微信搜索公众号[程序员舒克],获取更多内容 目录 Java 基础上 Java概述 Java语言有哪些特点? Java和C++有什么关系,它们有什么区别? JVM.JRE和JDK的关系是什么? 什么是字 ...
- 八股文--Java基础下
目录 String相关 字符型常量和字符串常量的区别? 什么是字符串常量池? String str="aaa"与 String str=new String("aaa&q ...
- Java基础、多线程、JVM、集合八股文自述(持续更新)
Java基础.多线程.JVM.集合八股文自述 一.Java基础 1.1 object类有哪些方法? getClass().hashCode().equals().clone().toString(). ...
- 后端面试八股文骚套路之Java基础
一年一度的秋招大戏又快拉开序幕了,近年来后端岗位越来越卷,毕业生的压力也越来越大.个人感觉目前各个大中厂校招面试不好的地方是,都在朝着背面试八股文的方向发展(曾经面试某二线厂,对着面试官纯背了 40 ...
- Java八股文一:java基础知识
文章目录 一.Java 基础知识 1.Object 类相关方法 2.基本数据类型 3.序列化 4.String.StringBuffer.StringBuilder 5.重载与重写 6.final 7 ...
- Java八股文(Java基础面试题)
JDK.JRE.JVM 三者之间的关系? JDK(Java Development Kit):是Java开发工具包,是整个Java的核心,包括了Java运行环境JRE.Java工具和Java基础类库. ...
- Java基础入门语法和安装
1. Java概述 1.1 Java语言背景介绍(了解) 语言:人与人交流沟通的表达方式 计算机语言:人与计算机之间进行信息交流沟通的一种特殊语言 Java语言是美国Sun公司(Stanford Un ...
- Java笔记整理-02.Java基础语法
1,标识符 由英文字母.数字._(下划线)和$组成,长度不限.其中英文字母包含大写字母(A-Z)和小写字母(a-z),数字包含0到9. 标识符的第一个字符不能是数字(即标识符不能以数字开头). 标识符 ...
- java基础(十三)-----详解内部类——Java高级开发必须懂的
java基础(十三)-----详解内部类--Java高级开发必须懂的 目录 为什么要使用内部类 内部类基础 静态内部类 成员内部类 成员内部类的对象创建 继承成员内部类 局部内部类 推荐博客 匿名内部 ...
最新文章
- 程序员会懂的冷笑话:各大编程语言的内心独白
- C语言结构体自动初始化实现,C语言中结构体(struct)的几种初始化方法
- 中如何构造有参和无惨_CAD制图初学入门:CAD机械软件中如何构造孔?
- 浅读:ITSM信息技术服务管理
- 关于USB的8个问题
- 嫦娥之死天蓬元帅的转世
- NHibernate Step By Step(2)-继承映射
- 高并发大流量专题---6、独立图片服务器的部署
- 求“厉害”数 (10 分)
- matlab 1 3倍频分析,[转载]1/3倍频程及Matlab程序实现
- 树莓派(Raspberry Pi)搭建简单的lamp服务
- arm linux驱动 知乎_Linux初级驱动-字符设备驱动-点亮LED
- matlab自带的信号,实验一 连续时间信号在MATLAB中的表示..ppt
- chrome devTool
- nginx red5 流媒体服务器
- Hystrix php,详解 hystrix-go 使用与原理
- 梦幻西游原服务器物品,梦幻西游:物品贱如粪土的服务器,强化石摆3万无人要...
- 谷歌学术 rss_如何自动将博客RSS供稿发布到Google plus页面?
- 笔记本电脑键盘按键有两个功能,如何切换
- 汉字的国标码和机内码是怎么回事?
热门文章
- 用VC++6.0制作简易浏览器
- 数据是企业和社会发展的重要动力,AI从边缘发展到主流,未来十年信息技术将带来巨大“红利” | 大咖周语录
- B - Ternary Logic
- ES冷热分离架构设计:一招让你的ELK日志系统节省 50% 的硬盘成本
- Linux进阶 | 万字详解Docker镜像的制作,手把手学会!
- 腾讯云国外服务器2核4G服务器新用户全攻略
- 设计模式中的撩妹神技--下篇
- 【windows】win10如何安装使用bitlocker
- 加减法、原码一位乘法、Booth算法、恢复余数法、加减交替法符号位及小结
- 微信小程序之短信验证码