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文件解释运行
image

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

半编译半解释型语言

  1. Java源代码需要被编译为字节码才能运行,这个过程属于编译
  2. 字节码到机器码是通过JVM逐行解释,这个过程属于解释
  3. JIT会对热点代码进行优化,直接缓存其机器码,这个过程属于编译

image

基础语法

自增自减运算符

自增自减运算符并不是一个原子性操作,底层通过一条读命令和写命令组成

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++:

  1. iload_1 - 读取a的当前值到操作数栈
  2. iinc 1, 1 - a的值自增1
  3. istore_2 - 将操作数栈的值赋给b

前缀自增 ++a:

  1. iinc 1, 1 - a的值自增1
  2. iload_1 - 读取a的新值到操作数栈
  3. istore_2 - 将操作数栈的值赋给b

基本数据类型

  • 整型:byte、short、int、long
  • 浮点型:float、double
  • 字符型:char
  • 布尔类型:boolean
    除了boolean类型,其他数据类型之间可以互相转换:分为强制类型转换和隐式类型转换
特性 隐式类型转换 强制类型转换
安全性 不存在安全隐患 存在数据精度丢失或溢出风险
方向 小类型->大类型 均支持
执行对象 编译器自动完成 程序员手动转换

程序中浮点型默认为double,整型默认为int

  1. Java 里使用 long 类型的数据一定要在数值后面加上 L,否则将作为整型解析
  2. 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);
    }
}

posted @ 2025-11-01 20:00  xxs不是小学生  阅读(8)  评论(0)    收藏  举报