Java基础(上)
本文主要参考:https://javaguide.cn/java/basis/java-basic-questions-01.html ,在其基础上做了部分扩展
基础概念
Java vs. C++
- C++需要手动管理内存,Java有自动内存回收机制
- C++支持多继承,Java只支持单继承,但支持多实现
- C++支持运算符重载,Java不支持
- C++跨平台需要重新编译,Java一次编译可以处处运行
- C++提供指针直接访问内存,Java未提供,系统安全性更高
Java不支持多继承是为了避免菱形继承问题 https://c.biancheng.net/view/mbeyes5.html
字节码
字节码+JVM是实现Java一次编译处处运行的关键,字节码即编译后的class文件中的代码,相比于Java源代码,更加接近底层语言,这个class文件是平台无关的,不同系统有不同的JVM实现,JVM对class文件解释运行

Java属于解释型语言,从字节码到机器码这一步是解释运行,效率比较低。后续为了改善这种情况,引入了JIT编译器,在程序运行时对代码运行情况进行统计,运行频繁的热点代码的机器码会被保存存储至元空间,不需要重复解释,所以有一种说法:Java程序运行时间越长,运行速度越快
半编译半解释型语言
- Java源代码需要被编译为字节码才能运行,这个过程属于编译
- 字节码到机器码是通过JVM逐行解释,这个过程属于解释
- JIT会对热点代码进行优化,直接缓存其机器码,这个过程属于编译

基础语法
自增自减运算符
自增自减运算符并不是一个原子性操作,底层通过一条读命令和写命令组成
public class T1 {
private static int i = 0;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for(int k = 0; k < 5000; k++) {
i++;
}
});
Thread t2 = new Thread(() -> {
for(int k = 0; k < 5000; k++) {
i++;
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(i); //如果是原子性命令,结果一直为10000,但实测是5926,每次结果不同,说明不满足原子性
}
}
以自增为例,有两种方式:a++和++a,两者在底层命令上有些不同
后缀自增 a++:
- iload_1 - 读取a的当前值到操作数栈
- iinc 1, 1 - a的值自增1
- istore_2 - 将操作数栈的值赋给b
前缀自增 ++a:
- iinc 1, 1 - a的值自增1
- iload_1 - 读取a的新值到操作数栈
- istore_2 - 将操作数栈的值赋给b
基本数据类型
- 整型:byte、short、int、long
- 浮点型:float、double
- 字符型:char
- 布尔类型:boolean
除了boolean类型,其他数据类型之间可以互相转换:分为强制类型转换和隐式类型转换
| 特性 | 隐式类型转换 | 强制类型转换 |
|---|---|---|
| 安全性 | 不存在安全隐患 | 存在数据精度丢失或溢出风险 |
| 方向 | 小类型->大类型 | 均支持 |
| 执行对象 | 编译器自动完成 | 程序员手动转换 |
程序中浮点型默认为double,整型默认为int
- Java 里使用 long 类型的数据一定要在数值后面加上 L,否则将作为整型解析
- Java 里使用 float 类型的数据一定要在数值后面加上 f 或 F,否则将无法通过编译
long a = 1; // 类型int向上转型为long,发生隐式类型转换
long a = 1L; // 类型直接定义为long
long a = 2147483648; // 错误 int的最大表示范围是2147483647
long a = 2147483648L; // 正确 2147483648为长整型
byte和short类型在进行计算操作时,会向上转型为int
当数据超出long的表示范围,使用BigInteger来表示,底层使用int[];
包装类
Java本身为面向对象语言,同时泛型仅支持引用类型,因此为所有基本数据类型引入了各自的引用类型,也就是包装类(Byte、Short、Integer、Long、Float、Double、Boolean、Character)
| 区别 | 基本类型 | 包装类 |
|---|---|---|
| 存储方式 | 堆空间和栈空间都存在 | 几乎全部存在于堆空间 |
| 占用空间 | 占用空间较小,仅包含自身值 | 对象类型,需要保存对象头和实例数据本身,占用空间较大 |
| 默认值 | 局部变量无默认值,成员变量有各自的默认值 | 默认值均为null |
| 比较方式 | == | equals方法 |
包装类缓存机制
包装类使用缓存机制提升性能,避免频繁创建对象
Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character 创建了数值在 [0,127] 范围的缓存数据,Integer的缓存上限可以修改,但不建议过大
Flaot和Double未使用缓存机制,相较于整型,浮点型热点区间难以定位
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache { // 通过static修饰,在类加载时就会被创建
static final int low = -128;
static final int high;
static {
// high value may be configured by property
int h = 127;
}
}
自动拆箱 & 自动装箱
装箱:基本类型转化为包装类
拆箱:包装类转化为基本类型
Integer i = 10; 包装引用指向基本类型,自动装箱
int j = i; 基本类型指向包装类引用,自动拆箱
自动拆/装箱属于语法糖,编译器在编译时解析成指定的方法(自动拆箱:xxxValue*()、自动装箱:valueOf()),只对编译器可见,JVM不可见
BigDecimal
double和flaot都有精度不准确的问题,这是计算机位宽有限导致的,对于跟金钱交易相关的系统,必须保证数据是准确的,采用BigDecimal来表示,底层为BigInteger
需要注意的是:BigDecimal的equals方法同时比较整数位和浮点位(1.0不等于1.00),因此进行比较时不能使用equals,而是使用compareTo()方法
成员变量 vs 局部变量
- 局部变量不能被static和访问权限符修饰,成员变量可以
- 存储位置:成员变量几乎全部存储在堆中,局部变量如果是基本数据类型,直接存储在栈中
- 默认值:局部变量无默认值
- 生存周期:成员变量的生命周期与实例相同,局部变量的生命周期跟栈帧的周期相同
为什么static不能修饰局部变量:static的生命周期从类加载开始,类卸载结束,是跟类绑定的;而局部变量的生命周期随着方法入栈开始,方法结束出栈结束,生命周期不一致;此外,static修饰的变量可以被所有实例共享,局部变量只能被方法内部共享
static & final
static: 所有实例共享同一份变量,只会在类加载时被分配一次内存,可以通过类名直接访问
final: 表明是一个常量,如果修饰的是基本数据类型,值不可变,如果指向的是引用类型,引用不可变,但是引用指向的对象内部数据可变
final和abstract不可以共用,abstract要求必须重写或继承,final则不允许重写或继承
被static修饰的方法不能访问非静态成员,static方法在类加载时就被分配内存,而非静态成员在实例化时才会分配,如果类还没有创建实例,属于非法操作
private修饰的字段或方法一定安全吗?
private字段在正常情况下是安全的,除了一种:反射场景
public class T1 {
private int privateField;
private String privateMethod() {
return "0";
}
private String privateMethod(String s) {
return s;
}
}
public class T2 {
public static void main(String[] args) throws Exception{
T1 obj = new T1();
Class<?> clz = T1.class;
Field field = clz.getDeclaredField("privateField");
field.setAccessible(true);
int z = (int) field.get(obj);
System.out.println(z);
Method method = clz.getDeclaredMethod("privateMethod");
method.setAccessible(true);
String s1 = (String) method.invoke(obj);
System.out.println(s1);
Method method1 = clz.getDeclaredMethod("privateMethod", String.class);
method1.setAccessible(true);
String s2 = (String) method1.invoke(obj, "123");
System.out.println(s2);
}
}

浙公网安备 33010602011771号