您好,欢迎关注我的专栏,本专栏从Java基础、集合、内存GC、IO、多线程、JVM以及性能优化7个方面剖析Java,旨在帮助有一定Java开发经验的程序员提高自己。如有问题,可在我的专栏底部给我留言,我会尽我的能力给你解答。
本文会介绍一些鲜为人知的Java基础知识,包括 基本数据类型 , String , 自动装箱 , 正则表达式 , label语句 等等。
学过Java的人都知道Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。
这里有个表可以整体的看一下:
其中整数类型 | 字符型 | 浮点型 | 布尔型 | 引用类型 |
---|---|---|---|---|
byte (1字节) short(2字节) int (4字节) long (8字节) |
char(2字节) |
float (4字节) double(8字节) |
boolean(1字节) |
类 接口 数组 Null |
需要注意一下各个类型的取值范围:
byte 的取值范围:-128~127(-2的7次方到2的7次方-1) short 的取值范围:-32768~32767(-2的15次方到2的15次方-1) int 的取值范围:-2147483648~2147483647(-2的31次方到2的31次方-1) long 的取值范围:-9223372036854774808~9223372036854774807(-2的63次方到2的63次方-1)
Java中允许用_来格式化数字,例如
int num1 = 2018_07_03; // 合法的,主要用于显示上看的更明白 int num2 = 20180703; int num3 = 99_999_999;// 同理 int num4 = 99999999;
char速度比String更快,因此有条件的时候用char比String性能更好:
if(s.startsWith("a")) // 用charAt()速度较快 if('a' == s.charAt(0)) String a = "abc" + "d"; String b = "abc" + 'd'; // 更好
而且String在内存中有字符串常量池,如果作为密码存储,可以被黑客dump到内存直接获取明文密码,因此作为高可靠性存储密码时通常会选用char[]存储。
Java中用二进制数据科学计算法表示浮点数,因此当小数点后数字很多的时候容易丢失精度。除了float和double,可以考虑使用BigDecimal类。
另外浮点型数据还有三个特殊的值:
用一个正数除以0得到正无穷大,用一个负数除以0得到负无穷大,0.0除以0或对负数开方会得到非数。其中Double.POSITIVE_INFINITY等于Float.POSITIVE_INFINITY;NEGATIVE_INFINITY同理;但是NaN 不 与任何对象相等,即便另一个对象也是NaN。
因此不能使用x == Double.NaN来检查是否为非数,只能用Double.isNaN(x)来判断;用Double.isInfinite可以测试是否为无穷大;用Double.isFinite检查既不是无穷也不是NaN
这里特别要注意的是 浮点数可以除以0 ,程序不会报任何错误,包括运行时异常; 整形不可以除0 ,程序会报运行时异常。
// Java7开始支持用下划线分割长数字,用来方便查看 int a = 2014_05_31; // 20140531 long b = 3_000_000_000L; // 3000000000 double pi = 3.14_15_92_6; // 3.1415926
如果long类型不够,则使用BigInteger类;如果double类型不够则使用BigDecimal。精确的浮点数计算也需要用到BigDecimal类。
Java中boolean类型的值只能是true或false,其他数据类型无法转换成布尔型。
我们要直接用boolean变量判断,而不要用等于判定。如:
boolean flag = true; if(flag == false) {} // 不要这样使用 if(!flag) {} // 推荐这样使用
这样做的好处一个是能让代码运行更快(生成字节码少5个字节),二个是让代码看起来简洁。
另外
switch可以作用在byte/short/char/int/enum上,也可以作用在String(Java1.7)上,但是不可以作用在long上
Java中JVM会维护一个字符串常量池, 通常 情况下创建字符的过程是在池中查找是否已经存在字符内容,如果没有,就创建一个新的并加入到池中,从而节省内存;也有 特殊 情况,即new String(),它会忽略字符串常量池,而重新创建一个字符串。
我们在知道字符串存在常量池的情况下,就能更好的理解这个方法了。intern()是一个本地方法,它返回String池中的对象,如果池中没有该对象,会把其加入到池中再返回。与equals()意义完全不同。
new String()产生的对象不会加入到String池中。
例如:
String str1 = "a"; String str2 = "b"; String str3 = "ab"; String str4 = "a" + "b"; String str5 = "a" + str2; String str6 = new String("ab"); if(str3 == str6) // false // str6是new出来的String,因此它不会复用String池中的对象 if(str3.equale(str6)) // true // 但是str6内容和str3一样,因此equale方法相等 if(str6.intern() == str3) // true // str6.intern()返回的是str6在String池中相等的对象,因此就是str3。 if(str3 == str4) // true // JVM编译器会优化两个静态字符串拼接 if(str3 == str5) // false // 动态字符串拼接会在池中创建新对象 if(str3.equale(str5)) // true if(str3.intern() == str5) // false if(str5.intern() == str3) // true // str5的String池内容就是str3
String.trim()方法可以去掉收尾的空格。但是准确的说,这个方法会去掉首位所有小于等于32的char。之所以大家通常说trim方法去掉空格,是因为在char中,第32位char是空格,而之前的32个char都无法正常显示。
从String源代码中可以看到subString方法的实现是new了一个String对象,里面保存了原始的String的char[]和偏移量。这样做的好处在大多数情况下节省了内存空间,因为new出来的String复用了旧String。但是对于从一个巨大的String中截取少量的String来说,这种方式会导致大量的冗余。
因此当出现需要从巨大String中截取少量String的时候,建议new一个新的字符串用来保存。
```
static class HugeStr {
private String str = new String(new char[10000000]);
public String getSubString(int begin, int end) { return str.substring(begin, end); // return new String(str.substring(begin, end)); }
}
public static void main(String[] args) {
List