本文对基础部分【1】进行补充。
教程来源为:
正文:
1、为什么不能用浮点型表示金额
由于计算机中保存的小数其实是十进制的小数的近似值,并不是准确值,所以,千万不要在代码中使用浮点数来表示金额等重要的指标。
建议使用BigDecimal或者Long(单位为分)来表示金额。
2、自动拆装箱
为什么需要包装类:
- 因为 Java 是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。比如,在集合类中,我们是无法将 int 、double 等类型放进去的。因为集合的容器要求元素是 Object 类型。
自动装箱都是通过包装类的 valueOf()
方法来实现的.自动拆箱都是通过包装类对的 xxxValue()
来实现的。
举例:
public static void main(String... strings) {
Integer integer1 = 3;
Integer integer2 = 3;
if (integer1 == integer2)
System.out.println("integer1 == integer2");
else
System.out.println("integer1 != integer2");
Integer integer3 = 300;
Integer integer4 = 300;
if (integer3 == integer4)
System.out.println("integer3 == integer4");
else
System.out.println("integer3 != integer4");
}
在 Java 中,==
比较的是对象引用,而 equals
比较的是值。但是这段代码的输出如下:
integer1 == integer2
integer3 != integer4
和 Integer 中的缓存机制有关。当需要进行自动装箱时,如果数字在 -128 至 127 之间时,会直接使用缓存中的对象,而不是重新创建一个对象。
3、Integer的缓存机制
实际上这个功能在Java 5中引入的时候,范围是固定的-128 至 +127。后来在Java 6中,可以通过java.lang.Integer.IntegerCache.high
设置最大值。这使我们可以根据应用程序的实际情况灵活地调整来提高性能。
4、关于接口的返回值类型及命名(在POJO中和RPC中)
如何评价阿里近期发布的Java编码规范? - 知乎 (zhihu.com)
在定义POJO中的布尔类型的变量时,不要使用isSuccess这种形式,而要直接使用success!
关于Boolean和boolean的使用:
- 即对象的默认值是
null
,boolean基本数据类型的默认值是false
。
建议使用的是包装类型。原文:
如何正确定义接口的返回值(boolean/Boolean)类型及命名(success/isSuccess) (gitee.io)
- 举一个扣费的例子,我们做一个扣费系统,扣费时需要从外部的定价系统中读取一个费率的值,我们预期该接口的返回值中会包含一个浮点型的费率字段。当我们取到这个值得时候就使用公式:金额*费率=费用 进行计算,计算结果进行划扣。
- 如果由于计费系统异常,他可能会返回个默认值,如果这个字段是Double类型的话,该默认值为null,如果该字段是double类型的话,该默认值为0.0。
- 如果扣费系统对于该费率返回值没做特殊处理的话,拿到null值进行计算会直接报错,阻断程序。拿到0.0可能就直接进行计算,得出接口为0后进行扣费了。这种异常情况就无法被感知。
- 这种使用包装类型定义变量的方式,通过异常来阻断程序,进而可以被识别到这种线上问题。如果使用基本数据类型的话,系统可能不会报错,进而认为无异常。
Boolean success;
5、String
字符串的不变性:一旦一个string对象在内存(堆)中被创建出来,他就无法被修改。而且,String类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象。
如果想要创建一个可以被修改的字符串,可以选择StringBuffer 或者 StringBuilder这两个代替String。
JVM中专门开辟了一部分空间来存储Java字符串,那就是字符串池。
通过字符串池,两个内容相同的字符串变量,可以从池中指向同一个字符串对象,从而节省了关键的内存资源。
由于字符串是应用最广泛的数据结构,提高字符串的性能对提高整个应用程序的总体性能有相当大的影响。
substring:
截取字符串并返回其[beginIndex,endIndex-1]范围内的内容。
JDK 6和JDK 7中substring的原理及区别 (gitee.io)
String中对“+”的重载:
Java中的+对字符串的拼接,其实现原理是使用StringBuilder.append。当然有些情况下编译器会进行常量折叠。
在循环体内,字符串的连接方式,使用StringBuilder的append方法进行扩展。
如果使用“+”,反编译出的字节码显示每次循环都会new出一个StringBuilder的对象,然后进行append操作,最后使用toString()方法返回String对象,造成内存浪费。
StringJoiner的使用:
Java 8中的StringJoiner (gitee.io)
StringJoiner其实就是依赖StringBuilder实现的。所以他的性能和StringBuilder差不多,他也是非线程安全的。链接中的作者做了很好的总结:
- 如果只是简单的字符串拼接,考虑直接使用"+"即可。
- 如果是在for循环中进行字符串拼接,考虑使用
StringBuilder
和StringBuffer
。 - 如果是通过一个
List
进行字符串拼接,则考虑使用StringJoiner
。
switch对String的支持:
到目前为止switch支持这样几种数据类型:byte
short
int
char
String
。
- switch对int的判断是直接比较整数的值。
- 对char类型进行比较的时候,实际上比较的是ascii码,编译器会把char型变量转换成对应的int型变量。
例子:
原代码
public class switchDemoString {
public static void main(String[] args) {
String str = "world";
switch (str) {
case "hello":
System.out.println("hello");
break;
case "world":
System.out.println("world");
break;
default:
break;
}
}
}
反编译后的:
public class switchDemoString {
public static void main(String[] args) {
String str = "world";
switch (str) {
case "hello":
System.out.println("hello");
break;
case "world":
System.out.println("world");
break;
default:
break;
}
}
}
总结一下,其实switch只支持一种数据类型,那就是整型,其他数据类型都是转换成整型之后再使用switch的。
3、Class常量池
在Java体系中,共用三种常量池。分别是字符串常量池、Class常量池和运行时常量池。
这里先介绍Class常量池,Class文件中包含了Java虚拟机指令集和符号表以及若干其他辅助信息。
可以使用16进制打开class文件,关于”魔数“:
Java中的”魔数”-HollisChuang's Blog
在代码中使用魔数,不仅使代码的可读性大大降低,还可能导致各种问题。所以在代码中,我们要尽量避免产生魔数。
例子:
public int getSalary(String title, int grade) {
if ("Programmer".equals(title)){
return grade * 500 + 700;
}
else if ("Tester".equals(title)){
return grade * 500 + 800;
}
else if ("Analyst".equals(title)){
return grade * 800 + 1000;
}
}
所有需要使用魔数的地方,都可以使用枚举或者静态变量来代替。
public int getSalary(String title, int grade) {
if (Constants.TITLE_PROGRAMMER.equals(title)){
return grade * Constants.BASE_SALARY_LOW + Constants.ALLOWANCE_LOW;
}
else if (Constants.TITLE_TESTER.equals(title)){
return grade * Constants.BASE_SALARY_LOW + Constants.ALLOWANCE_MEDIUM;
}
else if (Constants.TITLE_ANALYST.equals(title)){
return grade * Constants.BASE_SALARY_HIGH + Constants.ALLOWANCE_HIGH;
}
}
Class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)。
可以通过以下命令,查看常量池(Constant pool):详细说明可以见原文章。
javap -v HelloWorld.class
常量池中主要存放两大类常量:字面量(literal)和符号引用(symbolic references)。
- 字面量(literal)是用于表达源代码中一个固定值的表示法。翻译:字面量就是指由字母、数字等构成的字符串或者数值。
- 符号引用包括了以下三类常量: * 类和接口的全限定名 * 字段的名称和描述符 * 方法的名称和描述符
关于用处:Class是用来保存常量的一个媒介场所,并且是一个中间场所。在JVM真的运行时,需要把常量池中的常量加载到内存中。