没有理想的人不伤心

Java - 常见知识点总结

2025/08/13
1
0

1 基本类型的包装类

image.png
Java 的基本类型包装类(Wrapper Classes)是为了解决基本数据类型无法参与面向对象编程的问题而设计的。它们将基本数据类型封装成对象,使得基本类型具备了对象的特性(如方法调用、实现接口、存储在集合中等)。

  • 基本类型不支持范型,因为基本类型不属于 Object 的子类,而包装类支持
  • 基本类型无法实现Serializable接口或参与反射 API。包装类解决了这个问题,允许在网络传输或文件存储中使用基本类型。
  • 包装类对象一旦创建,其值不可变,避免了数据被意外修改。
  • 包装类(除 Float 和 Double 外)采用常量池技术(对象缓存机制),预创建某些常用值的对象实例,重复使用这些实例而非每次都创建新对象。

所有包装类之间值的比较,都使用 equals 方法!

2 常量池

对于包装类,常量池特指对象缓存机制,即预创建某些常用值的对象实例,重复使用这些实例而非每次都创建新对象。

  • Integer:缓存 -128  到 127 之间的数值(可通过 -XX:AutoBoxCacheMax  参数调整上限)。
  • Byte/Short/Long:缓存 -128  到 127 之间的数值。
  • Character:缓存 \u0000  到 \u007F (即 0-127 的 ASCII 字符)。
  • Boolean:缓存 TRUE 和 FALSE
  • Float/Double不支持常量池,因为浮点数精度问题导致缓存意义不大。

3 装箱和拆箱

在 Java 中,装箱(Boxing) 和 拆箱(Unboxing) 是基本数据类型与对应的包装类之间的自动转换机制。

  • 装箱(Boxing):将基本类型转换为包装类对象。
  • 拆箱(Unboxing):将包装类对象转换为基本类型。
  • 自动装箱 / 拆箱:Java 编译器自动完成上述转换,无需手动调用方法。

手动装箱与拆箱:
手动调用包装类的构造函数和方法

// 手动装箱
Integer a = new Integer(10);     // 方式 1:构造函数(已过时)
Integer b = Integer.valueOf(10); // 方式 2:静态工厂方法(推荐)

// 手动拆箱
int c = a.intValue();            // 调用 intValue()方法
boolean d = Boolean.TRUE.booleanValue();

自动装箱与拆箱:
Java 5 之后,编译器自动插入装箱 / 拆箱代码,简化写法:

// 自动装箱
Integer a = 10;  // 等价于 Integer a = Integer.valueOf(10);

// 自动拆箱
int b = a;       // 等价于 int b = a.intValue();

// 运算中的自动拆箱与装箱
Integer x = 5;
Integer y = 10;
Integer sum = x + y; // 等价于:int tmp = x.intValue() + y.intValue();
                     // 然后 Integer sum = Integer.valueOf(tmp);

实现原理:
编译器在编译时自动插入装箱 / 拆箱代码:

  • 装箱:调用包装类的  valueOf() 方法(如  Integer.valueOf())。
  • 拆箱:调用包装类的  xxxValue() 方法(如  intValue())。

各类型的装箱和拆箱方法:
image.png

自动拆箱时,若包装类为 NULL ,会引发空指针异常

Integer nullable = null; 
int value = nullable; // 抛出 NullPointerException

4 构造方法与方法重载

  1. 构造方法名 == 类名
  2. 没有返回值
  3. 调用构造方法必须用 new,在创建实例的时候
  4. 定义了构造方法后,编译器不再创建默认构造方法;没有定义构造方法,则编译器会创建一个默认构造方法
  5. 在同一个类中,一个构造方法可以调用其他构造方法,调用语法: this(参数列表),这样可以进行代码复用
  6. 可以定义多个构造方法(方法重载),多个构造方法通过参数列表、顺序、类型来确定调用哪一个

方法重载:

  • 方法重载是指多个方法的方法名相同,但各自的参数不同;
  • 重载方法应该完成类似的功能,参考 String indexOf()
  • 重载方法返回值类型应该相同。

5 继承与多态

继承

继承使用 extends 关键字

在 Java 中,没有明确写extends的类,编译器会自动加上extends ObjectObject 是所有类的父类

Java 只允许单继承(一个 class 继承一个类),即只允许有一个爹,可以有无数个娃

子类无法访问父类的private字段或者private方法,这就使得继承的作用被削弱了

成员变量基本都是 private,可不就是削弱了嘛

为了让子类可以访问父类的字段与方法,而又不至于被外部访问,就可以使用 protected 关键字修饰,用protected修饰的字段可以被子类访问

protected关键字可以把字段和方法的访问权限控制在继承树内部,一个protected字段和方法可以被其子类,以及子类的子类所访问

子类访问父类的字段和方法可以使用 super 关键字

如果父类定义了有参构造方法,那么子类就必须显式调用父类的构造方法 super(参数)并给出参数以便让编译器定位到父类的一个合适的构造方法,否则会编译失败

子类_不会继承_任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。

多态

方法重写(override):在继承关系中,子类定义了一个与父类方法签名完全相同的方法
重写在前面加上注解 @override ,编译器会帮助检查是否正确

签名完全相同是重写,不同则是重载
方法名相同,方法参数相同,但方法返回值不同,也是不同的方法。在 Java 程序中,出现这种情况,编译器会报错。

问题来了,子类重写了父类某个方法,父类变量接收子类实例后,父类变量调用该方法,调用的是父类还是子类的方法:
答案:子类

// override
public class Main {
    public static void main(String[] args) {
        Person p = new Student();
        p.run(); // 应该打印 Person.run 还是 Student.run?
    }
}

class Person {
    public void run() {
        System.out.println("Person.run");
    }
}

class Student extends Person {
    @Override
    public void run() {
        System.out.println("Student.run");
    }
}

上面代码实际上调用的方法是 Student run()方法

如果子类单独定义的方法,同样用父类变量接收子类实例,则需要向下转换类型后才能调用子类方法

结论:Java 的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。

这个非常重要的特性在面向对象编程中称之为多态。它的英文拼写非常复杂:Polymorphic。

多态是指,针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法

多态的特性就是,运行期才能动态决定调用的子类方法。对某个类型调用某个方法,执行的实际方法可能是某个子类的重写方法

Person p = new Student();
p.run(); // 无法确定运行时究竟调用哪个 run()方法

public void runTwice(Person p) {
    p.run();
    p.run();
}

上面的代码无法确定, p.run() 调用的是父类还是子类的方法,也无法确定 runTwice()传入的参数是 Person 类型还是子类类型

那么这种特性看似稀里糊涂,到底有什么作用呢?
观察下面的代码:
假设我们定义一种收入,需要给它报税,那么先定义一个 Income类,其中包含 getTax()方法,表示普通税收
再定义 Salary 通过继承 Income类并重写 getTax() 方法,表示薪水收入
国务院特殊津贴享受免税,则定义 StateCouncilSpecialAllowance,继承 Income 并重写 getTax()方法
这样我们就定义了三个不同类型的税收项目,现在,我们要编写一个报税的财务软件,对于一个人的所有收入进行报税,定义 totalTax() 函数,只需要通过 Income 类型来获取各个类型的税收,并最终得出总税收
如此一来,利用多态, totalTax()方法只需要和 Income打交道,它完全不需要知道 Salary StateCouncilSpecialAllowance的存在,就可以正确计算出总的税。如果我们要新增一种稿费收入,只需要从 Income派生,然后正确覆写 getTax()方法就可以。把新的类型传入 totalTax(),不需要修改任何代码。

// Polymorphic
public class Main {
    public static void main(String[] args) {
        // 给一个有普通收入、工资收入和享受国务院特殊津贴的小伙伴算税:
        Income[] incomes = new Income[] {
            new Income(3000),
            new Salary(7500),
            new StateCouncilSpecialAllowance(15000)
        };
        System.out.println(totalTax(incomes));
    }

    public static double totalTax(Income... incomes) {
        double total = 0;
        for(Income income:incomes) {
            total = total + income.getTax();
        }
        return total;
    }
}

class Income {
    protected double income;

    public Income(double income) {
        this.income = income;
    }

    public double getTax() {
        return income * 0.1; // 税率 10%
    }
}

class Salary extends Income {
    public Salary(double income) {
        super(income);
    }

    @Override
    public double getTax() {
        if(income <= 5000) {
            return 0;
        }
        return(income - 5000) * 0.2;
    }
}

class StateCouncilSpecialAllowance extends Income {
    public StateCouncilSpecialAllowance(double income) {
        super(income);
    }

    @Override
    public double getTax() {
        return 0;
    }
}

重写 Object 类的方法

因为所有的 class最终都继承自 Object,而 Object定义了几个重要的方法:

  • toString():把 instance 输出为 String
  • equals():判断两个 instance 是否逻辑相等;
  • hashCode():计算一个 instance 的哈希值。

final

final

  • 修饰类:表示此类不可被继承
  • 修饰方法:表示此方法不可被子类重写
  • 修饰成员变量:表示此字段不可被修改,修改则会编译错误
  • 修饰局部变量:不可被重新赋值

使用 final 修饰成员变量时,一般在类的构造方法中对该不可更改的变量进行初始化,这保证了实例一旦创建,其 final 修饰字段不可更改

6 接口

一个类只能继承一个类,但可以实现(implements)多个接口

接口定义的所有方法默认都是public abstract的,所以这两个修饰符不需要写出来(写不写效果都一样)

抽象类与接口对比
image.png

一个接口可以继承另外一个接口,使用 extends 关键字

接口中定义 default 方法:

相当于实现了一个方法

// interface
public class Main {
    public static void main(String[] args) {
        Person p = new Student("Xiao Ming");
        p.run();
    }
}

interface Person {
    String getName();
    default void run() {
        System.out.println(getName() + "run");
    }
}

class Student implements Person {
    private String name;

    public Student(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}

实现类可以不必覆写 default方法。default方法的目的是,当我们需要给接口新增一个方法时,会涉及到修改全部子类。如果新增的是default方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。

default方法和抽象类的普通方法是有所不同的。因为interface没有字段,default方法无法访问字段,而抽象类的普通方法可以访问实例字段。

接口的静态字段:
因为interface是一个纯抽象类,所以它不能定义实例字段。但是,interface是可以有静态字段的,并且静态字段必须为final类型:

public interface Person {
    public static final int MALE = 1;
    public static final int FEMALE = 2;
}

实际上,因为 interface的字段只能是 public static final类型,所以我们可以把这些修饰符都去掉,编译器会自动把该字段变为 public static final类型。

public interface Person {
    // 编译器会自动加上 public static final:
    int MALE = 1;
    int FEMALE = 2;
}

7 包 Package

完整的类名: 包名.类名

定义 class 时,需要在第一行声明属于哪个包
如: package com.kuaishou.csc.workbench.component.hotline.service.checker;

包没有父子关系。java.util 和 java.util.zip 是不同的包,两者没有任何继承关系。

包作用域:
不使用 publicprivateprotected 修饰的字段和方法就是包的作用域
位于同一个包内的类,可以访问该包下作用域内的字段和方法,不需要 import

不同包下需要使用 import 来导入其他包,也可以具体指定导入的类,使用 * 就是导入该包下的所有类

注意,由于 Java 中的包没有父子关系,导入所有类时不包含子包下的类
如, import com.kuaishou.*,只导入了 kuaishou 包 下的类,即声明了 package com.kuaishou 的类文件,而 com.kuaishou.staging 下的类并未导入。

为了看起来结构清晰明了,且占用更少的资源,一般导入指定的类,而不是包下的所有类

如果两个包有同名类,则只能 import 一个,另一个使用完整类名

为避免类名冲突,要确定唯一的包名,推荐使用倒置的域名来确保唯一性,如:

  • com.kuaishou.framework
  • com.kuaishou.csc.workbench
    子包根据功能来命名

8 作用域总结:

  • public :都能访问
  • private :类内访问
  • protected:继承树内可以访问
  • 包作用域:没有上面三个关键字修饰时,就是包作用域,包内可以访问
  • 局部变量作用域:定义开始,块内作用

最佳实践:
非必要,不使用 public
包作用域有助于测试,

9 classpath

classpath是 JVM 用到的一个环境变量,它用来指示 JVM 如何搜索class

JVM 在查找某个类时,依次查找:

  1. 当前目录 ./
  2. 设置的 classpath 目录

强烈不推荐在系统环境变量中设置 classpath,会污染整个环境
推荐在启动 JVM 时通过 -classpath -cp 来配置,如:
java -classpath .;C:\work\project1\bin;C:\shared abc.xyz.Hello 如果没有配置classpath `,则默认为当前目录

JVM 不依赖 classpath 来加载 Java 核心库,因此不需要在 classpath 中指定 Java 核心库路径

10 注解

Java 注解(Annotation)是 JDK 5 引入的一种特殊语法元数据(metadata),它不直接影响程序的执行逻辑,但可以为编译器、工具或运行时提供额外信息。简单来说,注解就像一种 “标签”,可以标记在类、方法、变量等程序元素上,用于描述这些元素的额外信息。

注解的基本特性:

  • 注解本身不会改变程序的运行结果
  • 注解可以被编译器或运行时解析
  • 注解可以带参数,提供更丰富的信息
  • 注解可以被继承或限制使用范围

常见的内置注解:

  • @Override:标记重写父类方法
  • @Deprecated:标记元素即将被遗弃
  • @SuppressWarining:抑制编译器警告

元注解:用于修饰其他注解的注解,Java 共提供了五个

  1. @Target:指定注解可以修饰的程序元素(类、方法、字段等)

    @Target({ElementType.TYPE,ElementType.METHOD})
    public @interface MyAnnotation { ... }
    
  2. @Retention:指定注解的保留策略

    • RetentionPolicy.SOURCE:仅在源代码中保留,编译时丢弃
    • RetentionPolicy.CLASS:保留到编译后的字节码中,但运行时不加载(默认)
    • RetentionPolicy.RUNTIME:保留到运行时,可以通过反射获取
  3. @Documented:标记注解会被 Javadoc 工具提取到文档中

  4. @Inherited:标记注解可以被子类继承

  5. @Repeatable:允许注解在同一元素上重复使用(Java 8+)

自定义注解:

定义注解:

import java.lang.annotation.*;

// 定义注解
@Target({ElementType.TYPE,ElementType.METHOD}) // 可用于类和方法
@Retention(RetentionPolicy.RUNTIME) // 运行时可获取
@Documented // 生成文档
public @interface MyAnnotation {
    // 注解的属性(类似方法定义)
    String value()default "默认值";
    int version()default 1;
    String[] authors();
}

使用注解:

// 使用注解
@MyAnnotation(value = "用户服务",version = 2,authors = {"张三", "李四"})
public class AnnotationDemo {
    
    @MyAnnotation(value = "获取用户信息",authors = {"张三"})
    public String getUserInfo() {
        return"用户信息";
    }
}

解析注解:

import java.lang.reflect.Method;

// 解析注解
public class AnnotationProcessor {
    public static void main(String[] args)throws Exception {
        // 处理类上的注解
        Class<AnnotationDemo> clazz = AnnotationDemo.class;
        if(clazz.isAnnotationPresent(MyAnnotation.class)) {
            MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
            System.out.println("类注解信息:");
            System.out.println("描述:" + annotation.value());
            System.out.println("版本:" + annotation.version());
            System.out.println("作者:" + String.join(",",annotation.authors()));
        }
        
        // 处理方法上的注解
        Method method = clazz.getMethod("getUserInfo");
        if(method.isAnnotationPresent(MyAnnotation.class)) {
            MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
            System.out.println("\n 方法注解信息:");
            System.out.println("描述:" + annotation.value());
            System.out.println("作者:" + String.join(",",annotation.authors()));
        }
    }
}

11 类字面量(.class)

在 Java 中,.class 是一种获取类的 Class 对象 的语法,称为 “类字面量”(class literal)。