Featured image of post Java笔记

Java笔记

适合大学生期末考试复习的java笔记

Java 核心知识整理

一、Java 语言概述

1. 诞生背景

  • 1995 年 6 月由 Sun Microsystems 公司推出,创始人 James Gosling(Java 之父)。

2. 技术体系架构

(1)核心版本分类

  • Java SE(标准版):基础开发平台。
  • Java ME(微型版):针对移动设备优化。
  • Java EE(企业版):用于构建大型分布式应用。

(2)运行环境组件

  • JDK(Java Development Kit):开发工具包,包含 JRE 和编译工具(如 javac)、运行工具(如 java)等。
  • JRE(Java Runtime Environment):运行环境,包含 JVM 和核心类库。
  • JVM(Java Virtual Machine):虚拟机,实现跨平台的字节码执行。

3. 核心开发工具

工具名称 功能描述
javac.exe 编译器,将 .java 源代码编译为 .class 字节码。
java.exe 运行器,启动 JVM 执行字节码(格式:java 类名)。
javadoc.exe 文档生成器,根据代码注释生成 HTML 格式的 API 文档。
jar.exe 打包工具,将类文件及资源打包为 JAR 文件(支持双击运行)。

4. 语言特性

  1. 面向对象:支持封装、继承、多态,以对象为代码组织单位。
  2. 平台无关性:通过 JVM 实现“一次编写,到处运行”。
  3. 自动内存管理:垃圾回收机制(GC)自动处理内存分配与回收。
  4. 多线程支持:内置线程模型,需注意线程安全。

二、Java 语法基础

1. 标识符与注释

(1)标识符规则

  • 由字母、数字、_$ 组成,以字母、_$ 开头,区分大小写。
  • 命名规范:变量/方法名小写驼峰,类名大写驼峰,常量全大写(如 MAX_VALUE)。

(2)注释类型

  • 行注释:// 单行注释
  • 块注释:/* 多行注释 */
  • 文档注释:/** 文档注释 */

2. 数据类型

(1)基本数据类型

类型 字节数 范围(示例)
byte 1 -128 ~ 127
short 2 -32768 ~ 32767
int 4 -2³¹ ~ 2³¹-1
long 8 -2⁶³ ~ 2⁶³-1(后缀 L/l
float 4 单精度浮点型(后缀 f/F
double 8 双精度浮点型(默认,如 3.14
char 2 Unicode 字符(如 'A''\u0041'
boolean 1 true/false

说明

  • long 类型赋值时需加后缀 L(如 100L),避免整数溢出。
  • float 类型赋值时需加后缀 f(如 3.14f),否则会被视为 double 类型。
  • char 可存储单个 Unicode 字符,支持转义字符(如 '\n' 表示换行)。

(2)引用数据类型

分类 描述
数组 存储固定大小的同类型元素,通过索引访问。
自定义类型,包含属性和方法。
接口 抽象方法的集合,实现类必须实现所有接口方法。
枚举 特殊类,表示一组固定常量。
字符串 不可变的字符序列,属于 java.lang 包。
集合框架 动态存储对象的容器,包含 ListSetMap 等接口及实现类。
包装类 基本类型的对象形式,用于泛型、集合等场景。
其他常见类 包括日期时间、文件操作、异常类等。

说明

  • 引用类型变量存储的是对象的内存地址,而非对象本身。
  • 所有引用类型的默认值为 null,使用前需初始化(通过 new 或赋值)。
  • 集合框架 常用实现类:
    • ArrayList(动态数组)、LinkedList(双向链表)
    • HashSet(无序唯一)、TreeSet(有序唯一)
    • HashMap(键值对,无序)、TreeMap(键值对,按键排序)

3. 运算符

类型 运算符列表 示例与说明
算术运算符 +-*/%(取模)、++(自增)、--(自减)、-(负号) 5 / 2 → 2(整数除法取整)
5 % 2 → 1
a++(先使用后自增) vs ++a(先自增后使用)
关系运算符 ==!=<><=>=instanceof(类型检查) 3 > 5 → false
obj instanceof String → true/false
逻辑运算符 &&(短路与)、`
位运算符 &(按位与)、` (按位或)、^(按位异或)、~(取反)、«(左移)、»(带符号右移)、»>`(无符号右移)
赋值运算符 =+=-=*=/=%=<<=>>=>>>=&=、` =^=`
三元运算符 条件表达式 ? 表达式1 : 表达式2 int max = (a > b) ? a : b;(若 a > b 则返回 a,否则返回 b

优先级与结合性(由高到低):

  1. 括号()
  2. 单目运算符++--!~-(负号)
  3. 算术运算符*/%+-
  4. 移位运算符<<>>>>>
  5. 关系运算符<><=>=instanceof==!=
  6. 位运算符&^|
  7. 逻辑运算符&&||
  8. 三元运算符? :
  9. 赋值运算符=+=-=

三、流程控制与数组

1. 流程控制语句

(1)条件语句

类型 语法与特性
if-else 多分支判断,支持嵌套。条件表达式必须为 boolean 类型。
switch JDK 7+ 支持 String,JDK 14+ 支持箭头语法和 yield。匹配失败执行 default

示例代码

// if-else 示例
if (score >= 90) {
    System.out.println("A");
} else if (score >= 80) {
    System.out.println("B");
} else {
    System.out.println("C");
}

// switch 示例(JDK 14+ 语法)
switch (day) {
    case "Monday" -> System.out.println("工作日");
    case "Sunday" -> System.out.println("休息日");
    default -> System.out.println("其他");
}

注意事项

  • switch 表达式的返回值类型必须与 case 常量类型一致。
  • 避免 if-else 嵌套过深(超过3层),建议重构为 switch 或多态。

(2)循环语句

类型 适用场景
for 已知循环次数,支持初始化、条件和迭代部分。
while 未知循环次数,先判断条件再执行。
do-while 至少执行一次,后判断条件。
for-each 简化数组或集合遍历,无需索引。

示例代码

// for 循环示例
for (int i = 0; i < 10; i++) {
    System.out.println(i);
}

// while 循环示例
int i = 0;
while (i < 10) {
    System.out.println(i++);
}

// do-while 循环示例
int j = 0;
do {
    System.out.println(j++);
} while (j < 10);

// for-each 循环示例
int[] arr = {1, 2, 3};
for (int num : arr) {
    System.out.println(num);
}

控制关键字

  • break:终止整个循环。
  • continue:跳过当前循环,进入下一次迭代。
  • return:结束方法,返回结果(若有)。

2. 数组

(1)定义方式



// 一维数组  
int[] arr1 = new int[5];           // 动态初始化(默认值:0)  
int[] arr2 = {10, 20, 30};         // 静态初始化  
String[] arr3 = new String[]{"A", "B", "C"};  // 完整语法  

// 多维数组  
int[][] matrix = new int[3][4];    // 3行4列的二维数组  
int[][] triangle = {{1}, {2, 3}, {4, 5, 6}};  // 不规则二维数组  

// 动态初始化二维数组  
int[][] dynamic = new int[2][];    // 先定义行  
dynamic[0] = new int[3];           // 再定义列  
dynamic[1] = new int[2];  

(2)常用操作

获取长度

// 一维数组长度
int len1 = arr.length;

// 二维数组行数
int rows = matrix.length;

// 二维数组列数(假设matrix至少有一行)
int cols = matrix[0].length;

遍历数组

// for循环(带索引)
for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);
}

// for-each循环(简化遍历)
for (int num : arr) {
    System.out.println(num);
}

数组拷贝

// 浅拷贝(仅复制引用,共享同一对象)
int[] copy1 = arr;

// 深拷贝方式一:使用Arrays.copyOf
int[] copy2 = Arrays.copyOf(arr, arr.length);

// 深拷贝方式二:使用System.arraycopy(效率更高)
int[] copy3 = new int[arr.length];
System.arraycopy(arr, 0, copy3, 0, arr.length);

数组排序

// 升序排序(基本类型数组)
int[] arr = {5, 3, 8};
Arrays.sort(arr); // 结果:[3, 5, 8]

// 降序排序(需使用包装类型)
Integer[] arrObj = {3, 1, 2};
Arrays.sort(arrObj, Collections.reverseOrder()); // 结果:[3, 2, 1]

查找元素

// 线性查找(适用于未排序数组)
int target = 5;
int index = -1;
for (int i = 0; i < arr.length; i++) {
    if (arr[i] == target) {
        index = i;
        break;
    }
}

// 二分查找(仅适用于已排序数组)
Arrays.sort(arr); // 先排序
int pos = Arrays.binarySearch(arr, target);
if (pos >= 0) {
    // 找到元素,pos为索引
} else {
    // 未找到元素,pos为插入点取反-1
}

注意事项

  • 数组下标从 0 开始,访问越界会抛出 ArrayIndexOutOfBoundsException
  • 数组一旦创建,长度不可变;若需动态扩容,可使用 ArrayList

四、面向对象编程

1. 类与对象

概念 描述
对象的抽象模板,包含属性(成员变量)和行为(成员方法),是面向对象编程的基础单元。
对象 类的实例化结果,通过 new 关键字创建,每个对象独立拥有属性值和方法调用能力。
封装 通过访问修饰符(privatepublicprotected)隐藏内部实现细节,仅暴露必要接口。

核心特性说明

  • 访问修饰符优先级private(类内可见) < default(包内可见) < protected(子类+包内可见) < public(全局可见)。
  • 构造方法:无返回值,名称与类名相同,用于对象初始化;若未显式定义,编译器自动生成无参构造。

示例代码

// 类的完整定义(含封装)
public class Person {
    // 私有属性(封装核心数据)
    private String name;
    private int age;
    private static final String DEFAULT_COUNTRY = "China"; // 静态常量

    // 构造方法(初始化对象状态)
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 公共方法(暴露访问接口)
    public void sayHello() {
        System.out.println("Hello, my name is " + name + ", age " + age);
    }

    // Getter/Setter(属性访问控制)
    public String getName() { return name; }
    public void setName(String name) { 
        if (name != null && !name.isEmpty()) {
            this.name = name;
        }
    }
    public int getAge() { return age; }
    public void setAge(int age) { 
        if (age >= 0 && age <= 150) {
            this.age = age;
        }
    }

    // 静态方法(属于类而非实例)
    public static String getDefaultCountry() {
        return DEFAULT_COUNTRY;
    }
}

// 对象创建与使用示例
public class ObjectDemo {
    public static void main(String[] args) {
        // 实例化对象
        Person person = new Person("Alice", 25);

        // 调用方法
        person.sayHello(); // 输出:Hello, my name is Alice, age 25

        // 通过Setter修改属性
        person.setName("Bob");
        person.setAge(30);
        person.sayHello(); // 输出:Hello, my name is Bob, age 30

        // 访问静态成员
        System.out.println(Person.getDefaultCountry()); // 输出:China
    }
}

2. 子类与继承

概念 描述
继承 子类通过 extends 关键字继承父类的属性和方法,实现代码复用,Java 仅支持单继承。
方法重写(Override) 子类重新定义父类同名方法,需满足方法签名一致,访问权限不低于父类。
多态 父类引用指向子类对象,运行时根据实际对象类型动态调用方法,是面向对象的核心特性。

关键规则

  • super 关键字:用于访问父类成员(构造方法、属性、方法),子类构造方法必须首行调用父类构造(隐式调用 super())。
  • 重写限制:父类 final 方法不可重写,private 方法子类无法继承(不存在重写问题)。

示例代码

// 父类定义
public class Person {
    protected String name;
    protected int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void sayHello() {
        System.out.println("Hello, I'm a person.");
    }

    public void showInfo() {
        System.out.println("Name: " + name + ", Age: " + age);
    }
}

// 子类继承示例
public class Student extends Person {
    private String studentId;
    private double score;

    // 子类构造方法必须调用父类构造
    public Student(String name, int age, String studentId, double score) {
        super(name, age); // 显式调用父类构造
        this.studentId = studentId;
        this.score = score;
    }

    // 方法重写示例
    @Override
    public void sayHello() {
        super.sayHello(); // 调用父类方法
        System.out.println("I'm a student, ID: " + studentId);
    }

    // 子类特有方法
    public void showScore() {
        System.out.println("Score: " + score);
    }
}

// 多态应用示例
public class PolymorphismDemo {
    public static void main(String[] args) {
        // 父类引用指向子类对象
        Person p1 = new Student("Bob", 20, "S001", 90.5);
        Person p2 = new Person("Alice", 25);

        // 动态调用方法(根据实际对象类型)
        p1.sayHello(); // 输出子类重写的方法
        p1.showInfo(); // 输出父类方法(未重写)

        p2.sayHello(); // 输出父类方法

        // 多态与类型转换
        if (p1 instanceof Student) {
            Student student = (Student) p1; // 向下转型
            student.showScore(); // 调用子类特有方法
        }
    }
}

3. 接口与实现

概念 语法与特性
接口 使用 interface 定义抽象方法集合,方法默认 public abstract,变量默认 public static final
实现接口 类通过 implements 关键字实现接口,需重写所有抽象方法,可实现多个接口。
默认方法(Java 8+) 接口中使用 default 修饰的方法,提供默认实现,实现类可选择重写。

核心优势

  • 解耦设计:接口定义行为规范,实现类专注具体逻辑,符合“面向接口编程”原则。
  • 多实现能力:弥补 Java 单继承局限,一个类可实现多个接口,实现混合特性。

示例代码

// 接口定义(含抽象方法和默认方法)
public interface Flyable {
    // 抽象方法(必须实现)
    void fly();

    // 默认方法(可选重写)
    default void takeOff() {
        System.out.println("Preparing to take off...");
    }

    // 静态方法(接口直接调用)
    static void printFlyableInfo() {
        System.out.println("This is a flyable object.");
    }
}

// 接口定义(继承其他接口)
public interface Swimmable {
    void swim();
}

// 类实现多个接口示例
public class Duck implements Flyable, Swimmable {
    private String name;

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

    // 实现Flyable接口方法
    @Override
    public void fly() {
        System.out.println(name + " is flying low over the water.");
    }

    @Override
    public void takeOff() {
        System.out.println(name + " is flapping wings to take off.");
    }

    // 实现Swimmable接口方法
    @Override
    public void swim() {
        System.out.println(name + " is swimming in the water.");
    }
}

// 接口使用示例
public class InterfaceDemo {
    public static void main(String[] args) {
        Duck duck = new Duck("Donald");
        duck.fly();       // 输出:Donald is flying low over the water.
        duck.takeOff();   // 输出:Donald is flapping wings to take off.
        duck.swim();      // 输出:Donald is swimming in the water.

        // 调用接口静态方法
        Flyable.printFlyableInfo(); // 输出:This is a flyable object.
    }
}

4. 内部类与异常类

(1)内部类

类型 特性
成员内部类 定义在类内部,无 static 修饰,可访问外部类所有成员,依赖外部类实例。
静态内部类 使用 static 修饰,仅能访问外部类静态成员,不依赖外部类实例。
局部内部类 定义在方法或代码块中,作用域仅限于当前区域,可访问所在方法的局部变量(需为 final 或隐式 final)。
匿名内部类 没有类名的内部类,常用于创建接口或抽象类的实例,语法简洁但功能有限。

示例代码

// 成员内部类示例
public class OuterClass {
    private int outerField = 10;
    private static int staticOuterField = 20;

    // 成员内部类(非静态)
    public class InnerClass {
        private int innerField = 5;

        // 访问外部类成员
        public void accessOuter() {
            System.out.println("Outer field: " + outerField);       // 访问外部类实例变量
            System.out.println("Static outer field: " + staticOuterField); // 访问静态变量
        }

        // 外部类方法中创建内部类实例
        public void innerMethod() {
            System.out.println("Inner method, field: " + innerField);
        }
    }

    // 静态内部类示例
    public static class StaticInnerClass {
        private int staticInnerField = 100;

        // 仅能访问外部类静态成员
        public void accessOuter() {
            System.out.println("Static outer field: " + staticOuterField);
            // 无法访问 outerField(非静态)
        }
    }

    // 局部内部类示例
    public void localInnerDemo() {
        final int localVar = 50; // 局部变量需为final或隐式final

        // 局部内部类定义在方法中
        class LocalInner {
            public void printLocal() {
                System.out.println("Local variable: " + localVar);
            }
        }

        LocalInner local = new LocalInner();
        local.printLocal();
    }

    // 匿名内部类示例
    public void anonymousInnerDemo() {
        // 创建接口的匿名实现
        Flyable flyable = new Flyable() {
            @Override
            public void fly() {
                System.out.println("Anonymous inner class is flying.");
            }
        };
        flyable.fly();
    }
}

// 内部类使用示例
public class InnerClassDemo {
    public static void main(String[] args) {
        // 创建成员内部类实例
        OuterClass outer = new OuterClass();
        OuterClass.InnerClass inner = outer.new InnerClass();
        inner.accessOuter();
        inner.innerMethod();

        // 创建静态内部类实例
        OuterClass.StaticInnerClass staticInner = new OuterClass.StaticInnerClass();
        staticInner.accessOuter();

        // 调用局部内部类方法
        outer.localInnerDemo();

        // 调用匿名内部类方法
        outer.anonymousInnerDemo();
    }
}

(2)异常类

概念 特性
异常体系 所有异常继承自 Throwable,分为 Error(系统错误,不可恢复)和 Exception(程序异常)。
受检异常 继承自 Exception 且非 RuntimeException,必须在方法中处理或声明。
非受检异常 继承自 RuntimeException,无需强制处理,通常表示程序逻辑错误。
自定义异常 继承 ExceptionRuntimeException,用于封装特定业务场景的错误。

异常处理核心语法

try {
    // 可能抛出异常的代码
} catch (ExceptionType1 e1) {
    // 处理异常类型1
} catch (ExceptionType2 e2) {
    // 处理异常类型2
} finally {
    // 无论是否异常都会执行(如资源释放)
}

示例代码

// 自定义受检异常(继承Exception)
class AgeValidationException extends Exception {
    public AgeValidationException(String message) {
        super(message);
    }
}

// 自定义非受检异常(继承RuntimeException)
class NegativeValueException extends RuntimeException {
    public NegativeValueException(String message) {
        super(message);
    }
}

// 使用异常的业务类
public class Person {
    private int age;
    private double salary;

    // 抛出受检异常的方法
    public void setAge(int age) throws AgeValidationException {
        if (age < 0 || age > 150) {
            throw new AgeValidationException("年龄必须在0-150之间,当前输入:" + age);
        }
        this.age = age;
    }

    // 抛出非受检异常的方法
    public void setSalary(double salary) {
        if (salary < 0) {
            throw new NegativeValueException("薪资不能为负数,当前输入:" + salary);
        }
        this.salary = salary;
    }

    // 异常链示例
    public void processData() {
        try {
            // 调用可能抛出异常的方法
            setAge(-5);
        } catch (AgeValidationException e) {
            // 包装异常并抛出,保留原始异常信息
            throw new IllegalStateException("数据处理失败", e);
        }
    }
}

// 异常处理示例
public class ExceptionDemo {
    public static void main(String[] args) {
        Person person = new Person();

        // 处理受检异常
        try {
            person.setAge(200);
        } catch (AgeValidationException e) {
            System.err.println("错误:" + e.getMessage());
            e.printStackTrace(); // 打印异常栈轨迹
        }

        // 处理非受检异常
        try {
            person.setSalary(-5000);
        } catch (NegativeValueException e) {
            System.err.println("运行时错误:" + e.getMessage());
        }

        // 处理异常链
        try {
            person.processData();
        } catch (IllegalStateException e) {
            System.err.println("业务异常:" + e.getMessage());
            System.err.println("原始异常:" + e.getCause()); // 获取原始异常
        }

        // try-with-resources示例(自动关闭资源)
        try (java.io.FileReader reader = new java.io.FileReader("test.txt")) {
            int data = reader.read();
            // 处理文件读取
        } catch (java.io.IOException e) {
            System.err.println("文件读取失败:" + e.getMessage());
        }
    }
}

五、常用实用类

1. 字符串处理

特性
String 不可变,每次操作生成新对象。
StringBuilder 可变,非线程安全,性能高。

示例代码

// String 示例
String s1 = "hello";
String s2 = s1 + " world"; // 生成新字符串

// StringBuilder 示例
StringBuilder sb = new StringBuilder();
sb.append("a").append("b"); // 高效拼接

2. 随机数生成

示例代码

// 使用 Math.random() 生成指定范围整数
int randomNum = (int)(Math.random() * 9000 + 1000);  // 生成 1000~9999 的整数

// 使用 Random 类生成随机数
Random random = new Random();
int rand = random.nextInt(100);  // 生成 [0, 100) 的整数

3. 数组操作技巧

示例代码

// 二维数组赋值示例
String[] str2 = {"1 2", "3 4"};
String[][] str1 = new String[str2.length][];
for (int j = 0; j < str2.length; j++) {
    str1[j] = str2[j].split(" ");  // 按空格分割字符串并赋值给二维数组
}

// 数组复制示例
int[] src = {1, 2, 3, 4, 5};
int[] dest = new int[5];
System.arraycopy(src, 1, dest, 0, 3);  // 从src[1]复制3个元素到dest[0]

4. 日期与时间(Java 8+)

描述
LocalDate 日期(年、月、日),不含时间。
LocalDateTime 日期时间,包含年、月、日、时、分、秒。

示例代码

// LocalDate 示例
LocalDate today = LocalDate.now();
LocalDate birthday = LocalDate.of(2000, 1, 1);

// LocalDateTime 示例
LocalDateTime dt = LocalDateTime.now();
dt.plusDays(1); // 加一天

5. 包装类

基本类型 包装类
int Integer
double Double

示例代码

// 自动装箱/拆箱示例
Integer num = 10; // 自动装箱(int → Integer)
int value = num;  // 自动拆箱(Integer → int)

Double d = 3.14;  // 自动装箱(double → Double)
double primitive = d;  // 自动拆箱(Double → double)

六、泛型编程

1. 泛型基础概念

定义:泛型是一种参数化类型的编程模式,将类型作为参数传递给类、接口或方法,实现“类型安全的参数化”。

核心优势

  • 类型安全:编译时检查类型,避免类型转换异常(如 ClassCastException)。
  • 代码复用:一套逻辑支持多种数据类型,减少重复代码。
  • 可读性提升:明确标注数据类型,增强代码可维护性。

2. 泛型类与泛型方法

泛型类语法

public class 类名<T1, T2, ...> {  // 类型参数列表(可多个)
    private T1 field1;
    private T2 field2;

    public T1 getField1() { return field1; }
    public void setField1(T1 field1) { this.field1 = field1; }
    // ...
}

示例代码

// 泛型类:通用容器 Box
public class Box<T> {
    private T content;  // 类型参数 T 作为属性类型

    public Box() {}
    public Box(T content) {
        this.content = content;
    }

    public void setContent(T content) {
        this.content = content;
    }

    public T getContent() {
        return content;
    }

    // 泛型类中定义泛型方法
    public <S> Box<S> createBox(S content) {
        return new Box<>(content);
    }
}

// 泛型方法:打印任意类型数组
public class GenericMethods {
    // 类型参数 T 定义在方法签名中
    public static <T> void printArray(T[] array) {
        for (T element : array) {
            System.out.print(element + " ");
        }
        System.out.println();
    }

    // 多类型参数方法
    public static <K, V> void printMap(Map<K, V> map) {
        map.forEach((k, v) -> System.out.println(k + " -> " + v));
    }
}

// 使用示例
public class GenericDemo {
    public static void main(String[] args) {
        // 泛型类实例化(类型擦除前)
        Box<Integer> intBox = new Box<>(100);
        Box<String> strBox = new Box<>("Hello");

        intBox.setContent(200);
        System.out.println(intBox.getContent()); // 输出:200

        // 调用泛型方法
        Integer[] nums = {1, 2, 3, 4};
        String[] names = {"Alice", "Bob"};
        GenericMethods.printArray(nums);  // 输出:1 2 3 4
        GenericMethods.printArray(names); // 输出:Alice Bob

        // 泛型类中的泛型方法
        Box<Double> doubleBox = intBox.createBox(3.14);
        System.out.println(doubleBox.getContent()); // 输出:3.14
    }
}

3. 泛型边界与通配符

(1)类型边界(Type Bounds)

上界语法T extends 类型(表示 T 是该类型或其子类型)
下界语法T super 类型(表示 T 是该类型或其父类型)

示例场景

  • 计算数字平均值:需限制类型为 Number 及其子类(T extends Number)。
  • 插入整数到集合:需限制类型为 Integer 及其父类(T super Integer)。
(2)通配符(Wildcards)
通配符 含义 示例场景
? 未知类型(等同于 ? extends Object 仅读取不修改的场景
? extends T 上界通配符:类型为 T 或其子类型 遍历继承体系中的类型
? super T 下界通配符:类型为 T 或其父类型 向集合中添加元素

示例代码

// 上界通配符:打印数字集合(? extends Number)
public static void printNumbers(List<? extends Number> list) {
    for (Number num : list) {
        System.out.println(num);
    }
    // list.add(1); 报错:无法向? extends Number的集合添加元素
}

// 下界通配符:向集合添加整数(? super Integer)
public static void addIntegers(List<? super Integer> list) {
    list.add(10);
    list.add(20);
    // Integer num = list.get(0); 报错:无法确定具体类型
}

// 无界通配符:读取集合元素(?)
public static void printElements(List<?> list) {
    for (Object obj : list) {
        System.out.println(obj);
    }
    // list.add("abc"); 报错:未知类型无法添加
}

// 多边界泛型:类型同时继承多个接口(T extends A & B & C)
public interface Printable { void print(); }
public interface Comparable<T> { int compareTo(T o); }

public class GenericBounds<T extends Printable & Comparable<T>> {
    public void process(T obj) {
        obj.print();
        // 使用 Comparable 方法
    }
}

// 使用示例
public class WildcardDemo {
    public static void main(String[] args) {
        List<Integer> intList = Arrays.asList(1, 2, 3);
        List<Double> doubleList = Arrays.asList(3.14, 2.71);
        List<Object> objList = new ArrayList<>();

        printNumbers(intList);   // 输出整数
        printNumbers(doubleList); // 输出浮点数

        addIntegers(intList);    // 向Integer集合添加元素
        addIntegers(objList);    // 向Object集合添加元素

        // 多边界示例(假设Integer实现了Printable接口)
        GenericBounds<Integer> processor = new GenericBounds<>();
        processor.process(100);
    }
}

4. 类型擦除与限制

类型擦除机制

  • 泛型是编译时特性,运行时会擦除类型参数,替换为其边界类型(默认 Object)。
  • 擦除导致无法在运行时获取具体类型(如 new T() 会报错)。

泛型限制

  1. 不能使用基本类型作为类型参数(如 Box<int> 非法,需用 Box<Integer>)。
  2. 无法实例化类型参数(如 T t = new T(); 非法)。
  3. 不能使用 static 修饰类型参数(如 static T field; 非法)。
  4. 泛型数组创建受限(如 T[] arr = new T[10]; 非法,需强制转换)。

示例代码(类型擦除演示)

public class ErasureDemo {
    public static void main(String[] args) {
        Box<Integer> intBox = new Box<>(10);
        Box<String> strBox = new Box<>("Hello");

        // 运行时类型擦除,类型参数被替换为Object
        System.out.println(intBox.getClass() == strBox.getClass()); // 输出:true

        // 泛型限制示例
        // Box<int> errorBox = new Box<int>(5);  // 编译错误:不能使用基本类型
        // T[] arr = new T[10];  // 编译错误:无法创建泛型数组
    }
}

// 泛型限制解决方案
public class GenericRestrictions<T> {
    // 方案1:通过反射实例化类型参数
    public T createInstance(Class<T> clazz) throws Exception {
        return clazz.getDeclaredConstructor().newInstance();
    }

    // 方案2:使用数组包装类
    public static <T> List<T> createList(Class<T> clazz, int size) {
        T[] arr = (T[]) new Object[size]; // 强制转换,需确保类型安全
        return Arrays.asList(arr);
    }
}

七、数据结构(集合框架)

1. 集合接口与实现类

接口 特点 实现类 适用场景
List 有序(索引访问)、可重复,支持动态扩容。 ArrayList(动态数组)、LinkedList(双向链表)、Vector(线程安全) 频繁随机访问,优先用 ArrayList
频繁插入/删除,优先用 LinkedList
Set 无序(除 TreeSet)、元素唯一(通过 equals()hashCode() 保证)。 HashSet(基于哈希表)、LinkedHashSet(插入顺序)、TreeSet(自然排序) 去重场景
需要排序时用 TreeSet
Map 键值对(键唯一),键不可重复,值可重复。 HashMap(无序)、LinkedHashMap(插入顺序)、TreeMap(键排序)、ConcurrentHashMap(线程安全) 快速键值查找
需要排序时用 TreeMap
多线程环境用 ConcurrentHashMap

关键区别

  • ArrayList vs LinkedList

    • ArrayList 基于数组,随机访问快(O(1)),插入/删除慢(O(n))。
    • LinkedList 基于链表,随机访问慢(O(n)),插入/删除快(O(1),首尾操作)。
  • HashMap vs LinkedHashMap vs TreeMap

    • HashMap:无序,性能最高(平均 O(1))。
    • LinkedHashMap:维护插入顺序,遍历时按插入顺序返回。
    • TreeMap:按键的自然顺序或自定义比较器排序,插入/删除/查询均为 O(log n)。

2. 常用操作示例

示例代码

// List 操作示例(ArrayList vs LinkedList)
List<String> arrayList = new ArrayList<>();
List<String> linkedList = new LinkedList<>();

// 通用操作
arrayList.add("apple");
arrayList.add("banana");
arrayList.add(1, "cherry"); // 指定位置插入
System.out.println(arrayList.get(1)); // 输出:cherry
System.out.println(arrayList.size()); // 输出:3
arrayList.remove(0); // 按索引删除

// LinkedList 特有操作(双向链表)
linkedList.addFirst("head");
linkedList.addLast("tail");
System.out.println(linkedList.getFirst()); // 输出:head
System.out.println(linkedList.poll()); // 移除并返回头部元素

// Set 操作示例
Set<Integer> hashSet = new HashSet<>();
Set<Integer> treeSet = new TreeSet<>();

// 通用操作
hashSet.add(3);
hashSet.add(1);
hashSet.add(2);
System.out.println(hashSet.contains(2)); // 输出:true
System.out.println(hashSet.size()); // 输出:3
hashSet.remove(1);

// TreeSet 特有操作(有序)
treeSet.add(3);
treeSet.add(1);
treeSet.add(2);
System.out.println(treeSet.first()); // 输出:1(最小值)
System.out.println(treeSet.last());  // 输出:3(最大值)

// Map 操作示例
Map<String, Integer> hashMap = new HashMap<>();
Map<String, Integer> linkedHashMap = new LinkedHashMap<>();
Map<String, Integer> treeMap = new TreeMap<>();

// 通用操作
hashMap.put("one", 1);
hashMap.put("two", 2);
hashMap.put("three", 3);
System.out.println(hashMap.get("two")); // 输出:2
System.out.println(hashMap.containsKey("three")); // 输出:true
hashMap.remove("one");

// LinkedHashMap 维护插入顺序
linkedHashMap.put("b", 2);
linkedHashMap.put("a", 1);
linkedHashMap.put("c", 3);
System.out.println(linkedHashMap.keySet()); // 输出:[b, a, c]

// TreeMap 按键排序(自然顺序)
treeMap.put("b", 2);
treeMap.put("a", 1);
treeMap.put("c", 3);
System.out.println(treeMap.keySet()); // 输出:[a, b, c]
This blog has been running for
Published 11 posts · Total 48.46k words