Java 核心知识点查漏补缺(二)
在 Java 中,增强 for 循环(Enhanced for Loop) 也称为 “for-each 循环”,是 JDK 5 引入的一种简化集合和数组遍历的语法。它提供了一种更简洁、可读性更高的方式来遍历元素,无需手动控制索引或迭代器。
增强 for 循环的语法
for (元素类型 变量名 : 遍历对象) {// 循环体:使用变量名访问当前元素
}
- 遍历对象:可以是数组,或实现了
java.lang.Iterable接口的集合(如List、Set等,Java 集合框架中的集合均实现了该接口)。 - 元素类型:必须与遍历对象中元素的类型一致(或兼容,如父类)。
- 变量名:临时变量,代表当前遍历到的元素。
适用场景
- 遍历数组:直接遍历数组中的每个元素。
- 遍历集合:无需获取迭代器,直接遍历集合元素。
示例代码
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 循环简化了遍历,但也有一些限制:
无法获取索引:遍历过程中无法直接获取元素的索引(数组或 List 的下标),如需索引需使用普通 for 循环。
不能修改集合结构:遍历集合时,不能通过集合自身的方法(如
add()、remove())修改集合结构,否则会抛出ConcurrentModificationException(与迭代器的 “快速失败” 机制一致)。- 注意:可以修改元素的内容(如对象的属性),但不能增删元素。
只能单向遍历:与迭代器类似,增强 for 循环只能从集合头部向尾部遍历,无法反向或随机访问。
不适用于需要中途终止或跳过元素的场景:虽然可以用
break终止循环或continue跳过当前元素,但无法像普通 for 循环那样灵活控制循环变量(如i += 2跳过元素)。
增强 for 循环 vs 普通 for 循环 vs 迭代器
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 增强 for 循环 | 语法简洁,可读性高,无需处理索引 / 迭代器 | 无法获取索引,不能修改集合结构 | 仅需遍历元素,不涉及修改集合 |
| 普通 for 循环 | 可获取索引,灵活控制遍历过程 | 语法较繁琐,需手动控制索引边界 | 需要索引或灵活控制遍历 |
| 迭代器 | 支持在遍历中安全删除元素(remove()) | 语法较繁琐,需显式调用迭代器方法 | 遍历中需要删除元素 |
总结
- 增强 for 循环是遍历数组和集合的简化语法,底层依赖迭代器(集合)或索引(数组)。
- 适合仅读取元素的场景,代码更简洁易读。
- 不适合需要索引、修改集合结构或灵活控制遍历过程的场景,此时应使用普通 for 循环或迭代器。
迭代器(Iterator)是一种用于遍历集合(如 List、Set、Map 等)元素的接口,它提供了统一的遍历方式,无需关心集合的具体实现细节。迭代器属于 Java 集合框架的一部分,位于 java.util 包下。
迭代器的核心方法
java.util.Iterator 接口定义了以下三个核心方法:
boolean hasNext()判断集合中是否还有下一个元素。如果有,返回true;否则返回false。E next()返回集合中的下一个元素(E是元素的泛型类型)。如果没有下一个元素,会抛出NoSuchElementException。void remove()删除next()方法返回的最后一个元素(可选操作)。如果在调用next()之前调用remove(),会抛出IllegalStateException。
迭代器的使用步骤
- 通过集合的
iterator()方法获取迭代器实例。 - 使用
hasNext()判断是否有下一个元素。 - 使用
next()获取下一个元素并移动指针。 - (可选)使用
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]}
}
迭代器的特点
- 统一遍历接口:无论集合类型(List、Set 等)如何,都可以通过相同的迭代器方法遍历,降低了代码耦合度。
- 快速失败机制(Fail-Fast):如果在迭代过程中通过集合自身的方法(如
add()、remove())修改集合结构,迭代器会立即抛出ConcurrentModificationException,避免数据不一致。- 注意:通过迭代器的
remove()方法修改集合是允许的。
- 注意:通过迭代器的
- 单向遍历:迭代器只能从集合头部向尾部遍历,无法反向或随机访问(如需反向遍历,可使用
ListIterator)。
泛型(Generic)以 <E> 形式出现(E 是 “Element” 的缩写,也可替换为其他标识符,如 T、K、V 等),它是 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); // 直接使用,无转换
泛型的核心作用
编译时类型安全检查限制集合只能添加指定类型的元素,不符合类型的操作在编译阶段就会报错,避免运行时异常。
消除强制类型转换取出元素时,编译器已知元素类型,无需手动转换,简化代码并提高可读性。
代码复用通过泛型可以编写通用的集合操作逻辑,适用于多种数据类型。例如
ArrayList<E>可以是ArrayList<String>、ArrayList<Integer>等,无需为每种类型单独实现集合类。
要实现 “随机读取”(如随机访问数组、集合中的元素),可以使用 java.util.Random 类生成随机索引,再通过索引获取对应元素。以下是具体实现方式:
核心思路
- 生成随机索引:通过
Random类的nextInt(n)方法生成[0, n)范围内的随机整数(n为数组 / 集合的长度)。 - 随机访问元素:利用生成的随机索引,直接访问数组或支持随机访问的集合(如
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);}}
}
注意事项
- 随机索引范围:
nextInt(n)生成的索引是[0, n),需确保n等于数组 / 集合的长度(避免索引越界)。 - 集合类型限制:
- 支持随机访问的集合(如
ArrayList)可通过get(index)高效获取元素。 - 不支持随机访问的集合(如
LinkedList),随机访问效率低(需从头遍历),建议先转为数组或ArrayList再操作。
- 支持随机访问的集合(如
- 去重逻辑:若需避免重复元素,可通过
remove()移除已选元素,或用HashSet记录已选索引。
扩展:使用 ThreadLocalRandom(JDK 7+)
在多线程环境下,ThreadLocalRandom 比 Random 更高效(减少线程竞争),用法类似:
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 字节 | 布尔值,true 或 false(仅用于逻辑判断) |
2. 引用数据类型
- 类(
class):如String、Integer、自定义类等。 - 接口(
interface):如List、Map等。 - 数组(
[]):如int[]、String[]等。 - 枚举(
enum)、注解(@interface)等。
特点:变量存储的是对象的引用(内存地址),默认值为 null。
二、JavaScript(JS)数据类型
JS 是弱类型、动态类型语言,变量类型无需声明,运行时自动推断,类型可动态改变。
1. 基本数据类型(6 种)
| 类型 | 描述 |
|---|---|
Number | 数字(整数、浮点数、NaN、Infinity),如 123、3.14、NaN |
String | 字符串(单 / 双引号包裹),如 "hello"、'world' |
Boolean | 布尔值:true 或 false |
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) 替代 |
