Java 核心知识点查漏补缺(二)

在 Java 中,增强 for 循环(Enhanced for Loop) 也称为 "for-each 循环",是 JDK 5 引入的一种简化集合和数组遍历的语法。它提供了一种更简洁、可读性更高的方式来遍历元素,无需手动控制索引或迭代器。

增强 for 循环的语法

复制代码
for (元素类型 变量名 : 遍历对象) {
    // 循环体:使用变量名访问当前元素
}
  • 遍历对象 :可以是数组,或实现了 java.lang.Iterable 接口的集合(如 ListSet 等,Java 集合框架中的集合均实现了该接口)。
  • 元素类型:必须与遍历对象中元素的类型一致(或兼容,如父类)。
  • 变量名:临时变量,代表当前遍历到的元素。

适用场景

  1. 遍历数组:直接遍历数组中的每个元素。
  2. 遍历集合:无需获取迭代器,直接遍历集合元素。

示例代码

1. 遍历数组
复制代码
public class ForEachArray {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 3, 4, 5};
        
        // 增强 for 循环遍历数组
        for (int num : numbers) {
            System.out.println(num); // 依次输出 1, 2, 3, 4, 5
        }
    }
}
2. 遍历集合
复制代码
import java.util.ArrayList;
import java.util.List;

public class ForEachCollection {
    public static void main(String[] args) {
        List<String> fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Cherry");
        
        // 增强 for 循环遍历集合
        for (String fruit : fruits) {
            System.out.println(fruit); // 依次输出 Apple, Banana, Cherry
        }
    }
}

增强 for 循环的原理

增强 for 循环本质上是迭代器(Iterator)的语法糖。对于集合来说,编译器会自动将其转换为迭代器的遍历方式;对于数组,则转换为普通的 for 循环(通过索引访问)。

例如,遍历集合的增强 for 循环:

复制代码
for (String fruit : fruits) { ... }

会被编译器转换为:

复制代码
Iterator<String> iterator = fruits.iterator();
while (iterator.hasNext()) {
    String fruit = iterator.next();
    ...
}

局限性

虽然增强 for 循环简化了遍历,但也有一些限制:

  1. 无法获取索引:遍历过程中无法直接获取元素的索引(数组或 List 的下标),如需索引需使用普通 for 循环。

  2. 不能修改集合结构 :遍历集合时,不能通过集合自身的方法(如 add()remove())修改集合结构,否则会抛出 ConcurrentModificationException(与迭代器的 "快速失败" 机制一致)。

    • 注意:可以修改元素的内容(如对象的属性),但不能增删元素。
  3. 只能单向遍历:与迭代器类似,增强 for 循环只能从集合头部向尾部遍历,无法反向或随机访问。

  4. 不适用于需要中途终止或跳过元素的场景 :虽然可以用 break 终止循环或 continue 跳过当前元素,但无法像普通 for 循环那样灵活控制循环变量(如 i += 2 跳过元素)。

增强 for 循环 vs 普通 for 循环 vs 迭代器

方式 优点 缺点 适用场景
增强 for 循环 语法简洁,可读性高,无需处理索引 / 迭代器 无法获取索引,不能修改集合结构 仅需遍历元素,不涉及修改集合
普通 for 循环 可获取索引,灵活控制遍历过程 语法较繁琐,需手动控制索引边界 需要索引或灵活控制遍历
迭代器 支持在遍历中安全删除元素(remove() 语法较繁琐,需显式调用迭代器方法 遍历中需要删除元素

总结

  • 增强 for 循环是遍历数组和集合的简化语法,底层依赖迭代器(集合)或索引(数组)。
  • 适合仅读取元素的场景,代码更简洁易读。
  • 不适合需要索引、修改集合结构或灵活控制遍历过程的场景,此时应使用普通 for 循环或迭代器。

迭代器(Iterator)是一种用于遍历集合(如 List、Set、Map 等)元素的接口,它提供了统一的遍历方式,无需关心集合的具体实现细节。迭代器属于 Java 集合框架的一部分,位于 java.util 包下。

迭代器的核心方法

java.util.Iterator 接口定义了以下三个核心方法:

  1. boolean hasNext() 判断集合中是否还有下一个元素。如果有,返回 true;否则返回 false

  2. E next() 返回集合中的下一个元素(E 是元素的泛型类型)。如果没有下一个元素,会抛出 NoSuchElementException

  3. void remove() 删除 next() 方法返回的最后一个元素(可选操作)。如果在调用 next() 之前调用 remove(),会抛出 IllegalStateException

迭代器的使用步骤

  1. 通过集合的 iterator() 方法获取迭代器实例。
  2. 使用 hasNext() 判断是否有下一个元素。
  3. 使用 next() 获取下一个元素并移动指针。
  4. (可选)使用 remove() 删除当前元素。

示例代码

ArrayList 为例,演示迭代器的基本用法:

复制代码
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratorDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Cherry");

        // 1. 获取迭代器
        Iterator<String> iterator = list.iterator();

        // 2. 遍历集合
        while (iterator.hasNext()) {
            String element = iterator.next(); // 获取下一个元素
            System.out.println(element);

            // 示例:删除元素 "Banana"
            if ("Banana".equals(element)) {
                iterator.remove(); // 注意:必须先调用 next() 才能调用 remove()
            }
        }

        System.out.println("删除后的集合:" + list); // [Apple, Cherry]
    }
}

迭代器的特点

  1. 统一遍历接口:无论集合类型(List、Set 等)如何,都可以通过相同的迭代器方法遍历,降低了代码耦合度。
  2. 快速失败机制(Fail-Fast) :如果在迭代过程中通过集合自身的方法(如 add()remove())修改集合结构,迭代器会立即抛出 ConcurrentModificationException,避免数据不一致。
    • 注意:通过迭代器的 remove() 方法修改集合是允许的。
  3. 单向遍历 :迭代器只能从集合头部向尾部遍历,无法反向或随机访问(如需反向遍历,可使用 ListIterator)。

泛型(Generic)以 <E> 形式出现(E 是 "Element" 的缩写,也可替换为其他标识符,如 TKV 等),它是 JDK 5 引入的特性,用于限制集合中元素的类型,实现编译时类型检查,避免运行时类型转换错误。

为什么需要泛型?

在没有泛型的早期版本中,集合可以存储任意类型的对象,取出时需要手动强制类型转换,容易出现 ClassCastException。例如:

复制代码
import java.util.ArrayList;
import java.util.List;

public class NoGenericDemo {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("Hello"); // 存入字符串
        list.add(123);     // 存入整数(编译不报错)

        // 取出元素时需强制转换,运行时出错
        String str = (String) list.get(1); // 报错:ClassCastException
    }
}

泛型的出现解决了这个问题:通过指定集合中元素的类型,编译器会在编译阶段检查元素类型是否匹配,避免类型转换错误。

泛型 <E> 的基本用法

在集合中声明泛型 <E>,表示该集合只能存储类型为 E 的元素。例如:

复制代码
// 声明一个只能存储 String 类型的 List
List<String> strList = new ArrayList<String>();
// JDK 7+ 支持菱形语法,右侧泛型可省略
List<String> strList = new ArrayList<>();

strList.add("Apple"); // 正确:类型匹配
strList.add(123);     // 错误:编译直接报错(类型不匹配)

// 取出元素时无需强制转换,编译器已确认类型
String s = strList.get(0); // 直接使用,无转换

泛型的核心作用

  1. 编译时类型安全检查限制集合只能添加指定类型的元素,不符合类型的操作在编译阶段就会报错,避免运行时异常。

  2. 消除强制类型转换取出元素时,编译器已知元素类型,无需手动转换,简化代码并提高可读性。

  3. 代码复用 通过泛型可以编写通用的集合操作逻辑,适用于多种数据类型。例如 ArrayList<E> 可以是 ArrayList<String>ArrayList<Integer> 等,无需为每种类型单独实现集合类。

要实现 "随机读取"(如随机访问数组、集合中的元素),可以使用 java.util.Random 类生成随机索引,再通过索引获取对应元素。以下是具体实现方式:

核心思路

  1. 生成随机索引 :通过 Random 类的 nextInt(n) 方法生成 [0, n) 范围内的随机整数(n 为数组 / 集合的长度)。
  2. 随机访问元素 :利用生成的随机索引,直接访问数组或支持随机访问的集合(如 ArrayList)中的元素。

示例代码

1. 随机读取数组元素
复制代码
import java.util.Random;

public class RandomArrayAccess {
    public static void main(String[] args) {
        String[] fruits = {"苹果", "香蕉", "橙子", "草莓", "西瓜"};
        Random random = new Random();

        // 生成随机索引(0 到 fruits.length-1)
        int randomIndex = random.nextInt(fruits.length);
        // 随机访问元素
        String randomFruit = fruits[randomIndex];

        System.out.println("随机选中的水果:" + randomFruit);
    }
}
2. 随机读取集合元素(以 ArrayList 为例)

ArrayList 实现了 RandomAccess 接口,支持通过索引快速随机访问:

复制代码
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class RandomListAccess {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            numbers.add(i); // 集合元素:1,2,3,...,10
        }

        Random random = new Random();
        // 生成随机索引(0 到 numbers.size()-1)
        int randomIndex = random.nextInt(numbers.size());
        // 随机访问元素
        int randomNum = numbers.get(randomIndex);

        System.out.println("随机选中的数字:" + randomNum);
    }
}
3. 多次随机读取(去重)

若需要多次随机读取且不重复(如抽奖不重复),可通过移除已选中元素或记录已选索引实现:

复制代码
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class RandomUniqueAccess {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("张三");
        names.add("李四");
        names.add("王五");
        names.add("赵六");

        Random random = new Random();
        int count = 2; // 随机选取 2 个不重复的元素

        for (int i = 0; i < count; i++) {
            // 生成当前集合长度范围内的随机索引
            int randomIndex = random.nextInt(names.size());
            // 获取并移除元素(确保不重复)
            String randomName = names.remove(randomIndex);
            System.out.println("第 " + (i + 1) + " 个选中:" + randomName);
        }
    }
}

注意事项

  1. 随机索引范围nextInt(n) 生成的索引是 [0, n),需确保 n 等于数组 / 集合的长度(避免索引越界)。
  2. 集合类型限制
    • 支持随机访问的集合(如 ArrayList)可通过 get(index) 高效获取元素。
    • 不支持随机访问的集合(如 LinkedList),随机访问效率低(需从头遍历),建议先转为数组或 ArrayList 再操作。
  3. 去重逻辑 :若需避免重复元素,可通过 remove() 移除已选元素,或用 HashSet 记录已选索引。

扩展:使用 ThreadLocalRandom(JDK 7+)

在多线程环境下,ThreadLocalRandomRandom 更高效(减少线程竞争),用法类似:

复制代码
import java.util.concurrent.ThreadLocalRandom;

public class ThreadLocalRandomDemo {
    public static void main(String[] args) {
        int[] arr = {10, 20, 30, 40};
        // 生成随机索引
        int index = ThreadLocalRandom.current().nextInt(arr.length);
        System.out.println("随机元素:" + arr[index]);
    }
}

I/O(Input/Output,输入 / 输出)流是用于处理设备间数据传输的机制,比如读写文件、网络通信等。I/O 流通过 "流" 的形式(数据按顺序传输)实现数据的输入和输出,是 Java 中处理数据传输的核心 API。

I/O 流的分类

Java 的 I/O 流体系庞大,可按不同维度分类:

1. 按数据流向划分
  • 输入流(Input Stream):数据从外部设备(如文件、网络)流向程序(内存),用于 "读" 操作。
  • 输出流(Output Stream):数据从程序(内存)流向外部设备,用于 "写" 操作。
2. 按处理数据类型划分
  • 字节流 :以字节(8 位二进制)为单位处理数据,可处理所有类型的数据(文本、图片、音频等)。核心抽象类:InputStream(输入)、OutputStream(输出)。
  • 字符流 :以字符(16 位 Unicode)为单位处理数据,仅用于处理文本数据(如 .txt 文件),会涉及字符编码(如 UTF-8、GBK)。核心抽象类:Reader(输入)、Writer(输出)。
3. 按流的角色划分
  • 节点流 :直接连接数据源(如文件、内存)的流,直接操作数据(如 FileInputStream 直接读文件)。
  • 处理流 :包裹在节点流或其他处理流之上,增强功能(如缓冲、转换编码),如 BufferedReader 提供缓冲和按行读取。

字节流(InputStream / OutputStream

字节流是所有流的基础,可处理任意类型数据。以下是常用实现类及示例:

1. 文件字节流(节点流)
  • FileInputStream:从文件读取字节数据。
  • FileOutputStream:向文件写入字节数据。

示例:复制文件(字节流)

复制代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileByteStreamDemo {
    public static void main(String[] args) {
        // 源文件和目标文件路径
        String sourcePath = "source.jpg";
        String destPath = "copy.jpg";

        // 声明流对象(try-with-resources 自动关闭流)
        try (FileInputStream fis = new FileInputStream(sourcePath);
             FileOutputStream fos = new FileOutputStream(destPath)) {

            byte[] buffer = new byte[1024]; // 缓冲区(提高效率)
            int len; // 每次读取的字节数

            // 循环读取:当 len = -1 时表示读取完毕
            while ((len = fis.read(buffer)) != -1) {
                // 写入缓冲区中的有效字节(避免写入多余数据)
                fos.write(buffer, 0, len);
            }
            System.out.println("文件复制完成!");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
2. 缓冲字节流(处理流)

BufferedInputStream / BufferedOutputStream:通过内部缓冲区减少磁盘 IO 次数,提高读写效率(建议优先使用)。

示例:缓冲流复制文件

复制代码
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class BufferedByteStreamDemo {
    public static void main(String[] args) {
        try (// 缓冲流包裹文件流
             BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.txt"));
             BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("b.txt"))) {

            byte[] buffer = new byte[1024];
            int len;
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
            System.out.println("复制完成!");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

字符流(Reader / Writer

字符流专为文本处理设计,自动处理字符编码转换(需注意编码格式,如 UTF-8)。

1. 文件字符流(节点流)
  • FileReader:读取文本文件(默认使用系统编码,可能存在乱码)。
  • FileWriter:写入文本文件。
2. 缓冲字符流(处理流)

BufferedReader / BufferedWriter:提供缓冲功能,且 BufferedReader 支持 readLine() 按行读取文本,极为常用。

示例:读取文本文件(按行读取)

复制代码
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class BufferedReaderDemo {
    public static void main(String[] args) {
        try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
            String line; // 存储每行内容
            // 按行读取,当 line 为 null 时表示读取完毕
            while ((line = br.readLine()) != null) {
                System.out.println(line); // 打印每行内容
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

示例:写入文本文件(带换行)

复制代码
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedWriterDemo {
    public static void main(String[] args) {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
            bw.write("Hello, 字符流!");
            bw.newLine(); // 写入换行符(跨平台兼容)
            bw.write("这是第二行文本");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

转换流(字节流 ↔ 字符流)

当需要指定字符编码(如避免乱码)时,需使用转换流:

  • InputStreamReader:将字节输入流转换为字符输入流(可指定编码)。
  • OutputStreamWriter:将字节输出流转换为字符输出流(可指定编码)。

示例:指定编码读取文本(UTF-8)

复制代码
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

public class InputStreamReaderDemo {
    public static void main(String[] args) {
        // 字节流 → 转换流(指定 UTF-8 编码)→ 缓冲流
        try (BufferedReader br = new BufferedReader(
                new InputStreamReader(new FileInputStream("utf8.txt"), StandardCharsets.UTF_8))) {

            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

流的关闭

  • 流操作会占用系统资源(如文件句柄),必须关闭!
  • 推荐使用 try-with-resources 语法 (JDK 7+):在 try() 中声明流对象,系统会自动关闭,无需手动调用 close()
  • 手动关闭需在 finally 中调用 close(),避免资源泄漏。

一、Java 数据类型

Java 是强类型语言 ,数据类型分为基本数据类型引用数据类型,编译时严格检查类型。

1. 基本数据类型(8 种)
类型 占用空间 描述
byte 1 字节 整数,范围 -128 ~ 127
short 2 字节 整数,范围 -32768 ~ 32767
int 4 字节 整数,范围 -2^31 ~ 2^31-1(最常用)
long 8 字节 长整数,范围 -2^63 ~ 2^63-1(需加后缀 L,如 100L
float 4 字节 单精度浮点数(需加后缀 F,如 3.14F
double 8 字节 双精度浮点数(默认浮点类型,如 3.14
char 2 字节 单个 Unicode 字符(用单引号,如 'A''中'
boolean 1 字节 布尔值,truefalse(仅用于逻辑判断)
2. 引用数据类型
  • 类(class):如 StringInteger、自定义类等。
  • 接口(interface):如 ListMap 等。
  • 数组([]):如 int[]String[] 等。
  • 枚举(enum)、注解(@interface)等。

特点 :变量存储的是对象的引用(内存地址),默认值为 null

二、JavaScript(JS)数据类型

JS 是弱类型、动态类型语言,变量类型无需声明,运行时自动推断,类型可动态改变。

1. 基本数据类型(6 种)
类型 描述
Number 数字(整数、浮点数、NaN、Infinity),如 1233.14NaN
String 字符串(单 / 双引号包裹),如 "hello"'world'
Boolean 布尔值:truefalse
Null 表示空值(仅一个值 null
Undefined 变量未赋值时的默认值(仅一个值 undefined
Symbol ES6 新增,唯一不可变的值(用于对象属性的唯一键)
2. 引用数据类型(1 种,统称 Object
类型 描述
Object 复杂数据类型的基类,包括:- 普通对象:{name: "Tom"}- 数组:[1, 2, 3]- 函数:function() {}- 日期:new Date()- 正则:/abc/

特点:基本类型存储值,引用类型存储地址(指针),赋值时传递引用。

三、MySQL 数据类型

MySQL 是关系型数据库,数据类型围绕存储优化业务场景设计,主要分为数值、字符串、日期等。

1. 数值类型
类型 范围 / 说明 适用场景
TINYINT 1 字节,范围 -128 ~ 127(无符号 0 ~ 255 小整数(如状态值 0/1/2)
SMALLINT 2 字节,范围 -32768 ~ 32767(无符号 0 ~ 65535 较小整数(如数量)
INT 4 字节,范围 -2^31 ~ 2^31-1(最常用) 一般整数(如 ID、年龄)
BIGINT 8 字节,范围 -2^63 ~ 2^63-1 大整数(如雪花 ID、时间戳)
FLOAT 4 字节,单精度浮点数(精度较低) 非精确小数(如温度)
DOUBLE 8 字节,双精度浮点数 较高精度小数
DECIMAL(M,D) 定点数(精确计算),M 总位数,D 小数位数(如 DECIMAL(10,2) 金额、汇率等精确数值
2. 字符串类型
类型 长度限制 / 说明 适用场景
CHAR(N) 固定长度 N(1~255),不足补空格(查询时自动截断) 短字符串(如手机号、性别)
VARCHAR(N) 可变长度 N(1~65535),按实际长度存储 变长字符串(如姓名、地址)
TEXT 长文本(最大 65535 字节) 较长文本(如描述)
LONGTEXT 极大文本(最大 4GB) 超大文本(如文章、日志)
BLOB 二进制大对象(存储图片、文件等二进制数据) 非文本数据存储
3. 日期时间类型
类型 格式 / 范围 适用场景
DATE YYYY-MM-DD(1000-01-01 ~ 9999-12-31) 仅日期(如生日)
TIME HH:MM:SS(-838:59:59 ~ 838:59:59) 仅时间
DATETIME YYYY-MM-DD HH:MM:SS(1000-01-01 ~ 9999-12-31) 日期 + 时间(如创建时间)
TIMESTAMP YYYY-MM-DD HH:MM:SS(1970-01-01 ~ 2038-01-19),受时区影响 需时区转换的时间(如日志)
YEAR 年份(1901 ~ 2155) 仅年份

核心差异总结

维度 Java JavaScript MySQL
类型特性 强类型、编译时检查 弱类型、动态类型、运行时推断 存储导向,按数据特性划分
数值类型 细分为 byte/short/int/long 等 统一为 Number(包含整数、浮点数) 按存储范围和精度细分(如 INT/BIGINT)
字符串类型 String 类(引用类型) 基本类型 String 按长度和用途分 CHAR/VARCHAR/TEXT 等
日期类型 无原生类型,依赖 java.util.Date Date 对象(基于时间戳) 原生支持 DATE/DATETIME 等
布尔类型 明确 boolean(true/false) boolean(true/false,可自动转换) 无专门布尔类型,常用 TINYINT (1) 替代
相关推荐
Lacrimosa&L5 小时前
OS_2 进程与线程(进程管理)
java·开发语言
zl9798995 小时前
SpringBoot-Web开发之嵌入式容器
java·spring boot
zzywxc7875 小时前
解锁 Rust 开发新可能:从系统内核到 Web 前端的全栈革命
开发语言·前端·python·单片机·嵌入式硬件·rust·scikit-learn
大雨淅淅5 小时前
【编程语言】Rust 入门
开发语言·后端·rust
桃花键神5 小时前
【送书福利-第四十四期】《 深入Rust标准库》
开发语言·后端·rust
像风一样自由20205 小时前
使用Rust构建高性能文件搜索工具
开发语言·后端·rust
攻城狮CSU5 小时前
类型转换汇总 之C#
java·算法·c#
墨咖5 小时前
java实现NTP服务以及服务调用端(Client)功能
java·开发语言·时间同步·ntp·时钟源同步
敲敲了个代码5 小时前
UniApp 多页面编译优化:编译时间从10分钟到1分钟
开发语言·前端·javascript·学习·uni-app