Java基础 ¶
核心概念与环境搭建 ¶
Java 三大版本与应用场景 ¶
| 版本 | 全称 | 核心定位 | 典型应用场景 |
|---|---|---|---|
| Java SE | Standard Edition | 标准版(核心基础),包含 JVM + 标准库 | 桌面应用、控制台程序、后端基础 |
| Java EE | Enterprise Edition | 企业版(基于 SE 扩展),提供 Web/数据库/消息服务等企业级 API | 分布式系统、微服务、电商平台 |
| Java ME | Micro Edition | 微型版(瘦身版),针对嵌入式设备优化,仅支持简化版 JVM 和库 | 智能硬件、嵌入式设备(逐渐被替代) |
JDK 与 JRE 核心区别 ¶
- JDK(Java Development Kit):Java 开发工具包
✅ 包含 JRE + 编译器(javac)、调试器(jdb)、打包工具(jar)等开发工具
✅ 开发人员必备(编写、编译、调试代码) - JRE(Java Runtime Environment):Java 运行环境
✅ 包含 JVM + 核心类库(rt.jar)
✅ 仅用于运行已编译的.class文件(用户/服务器部署场景)
最佳实践:开发环境安装 JDK(推荐 JDK 11/17 LTS 版本),生产环境可仅安装 JRE 减少资源占用
环境变量配置 ¶
核心配置项
bash
1# 1. 配置 JAVA_HOME(指向 JDK 安装根目录)
2JAVA_HOME=/usr/local/jdk-17.0.9 # Linux
3JAVA_HOME=C:\Program Files\Java\jdk-17.0.9 # Windows
4
5# 2. 配置 PATH(添加 JDK 二进制目录)
6PATH=$JAVA_HOME/bin:$PATH # Linux
7PATH=%JAVA_HOME%\bin;%PATH% # Windows
8
9# 3. 验证配置(命令行执行)
10java -version # 输出版本信息即成功
11javac -version常见问题排查
- 报错 “javac 不是内部或外部命令”:检查 PATH 是否正确添加
%JAVA_HOME%\bin - 版本不匹配:确认 JAVA_HOME 指向的 JDK 版本与预期一致
- Linux 权限问题:给 JDK 目录添加执行权限
chmod +x $JAVA_HOME/bin/*
Hello World 入门 ¶
java
1/**
2 * Java 入门示例(遵循 Google Java 规范)
3 * 类名使用 PascalCase 命名法,文件名与公共类名一致
4 */
5public class HelloWorld {
6 // 程序入口:main 方法固定格式(public static void 不可修改)
7 public static void main(String[] args) {
8 // 标准输出:使用 println 自动换行(推荐),print 不换行
9 System.out.println("Hello, Java SE!");
10 }
11}编译与运行流程
bash
1# 1. 编译:javac + 文件名(带 .java 后缀)
2javac HelloWorld.java # 生成 HelloWorld.class 字节码文件
3
4# 2. 运行:java + 类名(不带 .class 后缀)
5java HelloWorld # 输出:Hello, Java SE!核心原理
ascii
1┌──────────────────┐ 编译(javac) ┌──────────────────┐ 运行(java) ┌──────────────────┐
2│ HelloWorld.java │ ────────────────> │ HelloWorld.class │ ───────────────> │ JVM 执行字节码 │
3│ (源码:文本文件) │ │ (字节码:二进制) │ │ (跨平台核心) │
4└──────────────────┘ └──────────────────┘ └──────────────────┘Java 基础语法 ¶
数据类型(4大类8小类) ¶
基础数据类型
| 类型 | 占用字节 | 取值范围 | 默认值 | 核心注意事项 |
|---|---|---|---|---|
| byte | 1 | -128 ~ 127 | 0 | 适合存储小范围整数(如状态码),避免内存浪费 |
| short | 2 | -32768 ~ 32767 | 0 | 较少直接使用,多作为中间类型转换 |
| int | 4 | -2¹⁴⁷⁴⁸³⁶⁴⁸ ~ 2¹⁴⁷⁴⁸³⁶⁴⁷ | 0 | 整数默认类型(推荐优先使用) |
| long | 8 | -9²³³³⁷²⁰³⁶⁸⁵⁴⁷⁷⁵⁸⁰⁸ ~ … | 0L | 赋值需加后缀 L(如 100L),避免溢出 |
| float | 4 | 单精度浮点型(精度约 6-7 位) | 0.0f | 赋值需加后缀 f(如 3.14f),适合不需要高精度场景 |
| double | 8 | 双精度浮点型(精度约 15-17 位) | 0.0d | 浮点默认类型(推荐优先使用) |
| boolean | 1 | true / false | false | 不可用 0/1 替代,仅表示布尔逻辑 |
| char | 2 | 0 ~ 65535(Unicode 字符) | ‘\u0000’ | 可存储中文(如 ‘中’),需用单引号包裹 |
引用数据类型
- 包括:类(Class)、接口(Interface)、数组(Array)、枚举(Enum)等
- 默认值:
null(表示未引用任何对象) - 核心注意:
null引用调用方法/属性会抛出NullPointerException(NPE)
类型转换(6大核心规则) ¶
自动类型转换(隐式转换)
- 规则:小范围类型 → 大范围类型(无精度损失)
- 顺序:
byte < short(char) < int < long < float < double - 示例:java
1int num = 100; 2long bigNum = num; // 自动转换(int → long) 3double d = 3.14f; // 自动转换(float → double)
强制类型转换(显式转换)
- 规则:大范围类型 → 小范围类型(可能损失精度,需手动添加转换符)
- 语法:
目标类型 变量名 = (目标类型) 源变量; - 示例(合法/非法场景):java
1long bigNum = 200L; 2int num = (int) bigNum; // 合法(200 在 int 范围内) 3 4double d = 3.14; 5int num2 = (int) d; // 合法但精度损失(结果为 3) 6 7long overflowNum = 2147483648L; // 超出 int 最大值 8int num3 = (int) overflowNum; // 非法(溢出,结果为负数)
避坑指南
- 整数常量直接赋值给
byte/short/char时,若值在取值范围内可自动转换:java1byte b = 127; // 合法(127 在 byte 范围内) 2byte b2 = 128; // 编译报错(超出范围) byte/short/char混合运算时,先自动转换为int:java1byte a = 10; 2short b = 20; 3int c = a + b; // 结果为 int 类型- 浮点数转整数时,直接舍弃小数部分(非四舍五入):java
1double d = 3.99; 2int num = (int) d; // 结果为 3(不是 4)
运算符 ¶
核心运算符分类
| 类别 | 运算符 | 核心注意事项 |
|---|---|---|
| 算术运算符 | +、-、*、/、%、++、– | 1. 整数除法会舍弃小数(如 5/2=2);2. ++i 先自增后使用,i++ 先使用后自增 |
| 关系运算符 | >、>=、<、<=、==、!= | 1. 比较对象地址用 ==,比较内容用 equals();2. 浮点数避免用 == 比较(精度问题) |
| 逻辑运算符 | &、|、!、&&、|| | 1. && 短路与(左为 false 则右不执行);2. || 短路或(左为 true 则右不执行) |
| 赋值运算符 | =、+=、-=、*=、/=、%= | 扩展赋值运算符自动类型转换(如 byte b=1; b+=2; 不会报错) |
| 条件运算符 | 布尔表达式 ? 表达式1 : 表达式2 | 结果必须被使用(赋值/打印),否则编译报错 |
| 字符串连接符 | +(一方为字符串则触发拼接) | 从左到右执行(如 “a”+1+2 → “a12”,1+2+“a” → “3a”) |
最佳实践示例
java
1// 1. 浮点数比较(使用误差范围)
2double a = 0.1 + 0.2;
3double b = 0.3;
4boolean isEqual = Math.abs(a - b) < 1e-6; // 推荐(误差小于 10^-6 则认为相等)
5
6// 2. 字符串拼接(大量拼接用 StringBuilder,避免 String 低效)
7StringBuilder sb = new StringBuilder();
8sb.append("Hello").append(" ").append("Java");
9String result = sb.toString(); // 高效拼接
10
11// 3. 条件运算符简化判断
12int score = 85;
13String grade = score >= 60 ? "及格" : "不及格"; // 简洁替代 if-else控制语句 ¶
条件语句(if-else/switch)
if-else 规范
- 单条语句也建议保留
{}(避免后续扩展出错) - 多条件判断优先使用
else if,避免嵌套过深 - 示例:java
1int age = 25; 2if (age < 0 || age > 150) { 3 System.out.println("年龄非法"); 4} else if (age <= 18) { 5 System.out.println("未成年人"); 6} else if (age <= 60) { 7 System.out.println("成年人"); 8} else { 9 System.out.println("老年人"); 10}
switch 规范
- 支持类型:
int、String、enum(JDK 7+ 支持 String) - 推荐使用
break避免穿透(如需穿透需加注释) - JDK 14+ 支持
yield返回值(替代 break + 赋值) - 示例(增强版 switch):java
1String grade = "A"; 2int score = switch (grade) { 3 case "A" -> 90; 4 case "B" -> 80; 5 case "C" -> 70; 6 default -> { 7 System.out.println("无效等级"); 8 yield 0; // 返回默认值 9 } 10}; 11System.out.println(score); // 输出 90
循环语句(for/while/do-while)
for 循环(推荐用于已知循环次数)
- 规范:循环变量声明在循环体内(避免污染外部作用域)
- 示例:java
1// 普通 for 循环 2for (int i = 0; i < 10; i++) { 3 System.out.println("循环次数:" + (i + 1)); 4} 5 6// 增强 for 循环(遍历数组/集合,JDK 5+) 7int[] arr = {1, 2, 3, 4, 5}; 8for (int num : arr) { 9 System.out.println(num); // 依次输出 1-5 10}
while 循环(推荐用于未知循环次数)
- 规则:先判断条件,条件成立才执行循环体
- 示例:java
1int count = 0; 2while (count < 5) { 3 System.out.println("count:" + count); 4 count++; 5}
do-while 循环(最少执行一次)
- 规则:先执行循环体,再判断条件
- 示例:java
1int num = 0; 2do { 3 System.out.println("num:" + num); 4 num++; 5} while (num < 3); // 输出 0、1、2(执行 3 次)
转向语句(break/continue/return)
- break:跳出当前循环/switch(可配合标签跳出多层循环)java
1// 跳出多层循环(标签用法) 2outer: for (int i = 0; i < 3; i++) { 3 inner: for (int j = 0; j < 3; j++) { 4 if (j == 1) { 5 break outer; // 跳出 outer 循环 6 } 7 System.out.println(i + "," + j); 8 } 9} - continue:跳过当前循环次,进入下一次循环
- return:终止当前方法(带返回值时需指定返回值)
面向对象编程(OOP 三大特性) ¶
封装(Encapsulation) ¶
核心思想
- 隐藏对象内部细节,仅通过公共接口(getter/setter)暴露对外访问
- 目的:保证数据安全性(防止非法赋值)、简化使用(屏蔽复杂逻辑)
封装步骤(规范实现)
- 属性私有化(
private修饰,仅本类可访问) - 提供公共 getter 方法(获取属性值)和 setter 方法(设置属性值)
- 在 setter 方法中添加数据校验逻辑
- 示例:
java
1/**
2 * 人员类(遵循封装规范)
3 */
4public class Person {
5 // 1. 属性私有化
6 private String name; // 姓名
7 private int age; // 年龄
8
9 // 2. getter 方法(获取属性值)
10 public String getName() {
11 return name;
12 }
13
14 // 3. setter 方法(设置属性值,添加校验)
15 public void setName(String name) {
16 // 校验:姓名不能为空
17 if (name == null || name.trim().isEmpty()) {
18 throw new IllegalArgumentException("姓名不能为空");
19 }
20 this.name = name;
21 }
22
23 public int getAge() {
24 return age;
25 }
26
27 public void setAge(int age) {
28 // 校验:年龄必须在 0-150 之间
29 if (age < 0 || age > 150) {
30 throw new IllegalArgumentException("年龄必须在 0-150 之间");
31 }
32 this.age = age;
33 }
34}调用示例(安全访问)
java
1Person person = new Person();
2person.setName("张三"); // 合法赋值
3person.setAge(25); // 合法赋值
4
5// person.setAge(200); // 抛出异常(年龄非法)
6// person.setName(""); // 抛出异常(姓名为空)
7
8System.out.println(person.getName() + "," + person.getAge() + "岁");继承(Inheritance) ¶
核心思想
- 子类(Subclass)继承父类(Superclass)的属性和方法(除
private修饰) - 目的:代码复用、建立类之间的层级关系(如 “Cat is a Animal”)
- 关键字:
extends(Java 仅支持单继承,一个子类只能有一个直接父类)
继承核心规则
- 可继承的成员:
- 父类的
public、protected、默认访问权限(同包)的属性和方法 - 构造方法不可继承,但子类可通过
super()调用父类构造方法
- 父类的
- 默认继承:所有类默认继承
java.lang.Object(Java 根类),拥有toString()、equals()等基础方法 - 继承传递性:若 C 继承 B,B 继承 A,则 C 间接继承 A 的所有可继承成员
构造方法的继承规则
- 子类构造方法执行时,会先调用父类的构造方法(默认调用无参构造)
- 若父类无无参构造,子类必须通过
super(参数)显式调用父类有参构造(且必须在子类构造方法第一行) - 示例:java
1// 父类 2public class Animal { 3 private String name; 4 5 // 父类有参构造(无无参构造) 6 public Animal(String name) { 7 this.name = name; 8 } 9 10 // getter/setter 11 public String getName() { 12 return name; 13 } 14} 15 16// 子类 17public class Cat extends Animal { 18 // 子类构造方法必须显式调用父类有参构造 19 public Cat(String name) { 20 super(name); // 调用父类构造方法(必须在第一行) 21 } 22} 23 24// 调用示例 25public class Test { 26 public static void main(String[] args) { 27 Cat cat = new Cat("小白"); 28 System.out.println(cat.getName()); // 输出:小白 29 } 30}
方法重写
- 核心场景:子类继承的父类方法无法满足需求时,重新实现该方法
- 重写规则(五大黄金法则):
- 方法名、参数列表、返回值类型必须与父类完全一致(返回值为引用类型时,可子类化,如父类返回
Animal,子类可返回Cat) - 访问权限不能低于父类(如父类为
public,子类不能为protected) - 不能抛出比父类更多的异常(可抛出更少或子类异常)
- 静态方法不能重写(静态方法属于类,不属于对象)
final方法不能重写(final修饰的方法不可修改)
- 方法名、参数列表、返回值类型必须与父类完全一致(返回值为引用类型时,可子类化,如父类返回
- 示例:java
1// 父类 2public class Animal { 3 public void move() { 4 System.out.println("动物在移动"); 5 } 6} 7 8// 子类重写 move 方法 9public class Bird extends Animal { 10 @Override // 注解:标记方法重写(编译器校验合法性) 11 public void move() { 12 System.out.println("鸟儿在飞翔"); // 子类特有实现 13 } 14} 15 16// 调用示例 17public class Test { 18 public static void main(String[] args) { 19 Animal bird = new Bird(); 20 bird.move(); // 输出:鸟儿在飞翔(执行子类重写后的方法) 21 } 22}
多态(Polymorphism) ¶
核心思想
- 同一行为在不同对象上表现出不同的形态(如 “move” 行为:鸟飞、鱼游、狗跑)
- 实现条件:
- 存在继承关系(子类继承父类)
- 子类重写父类方法
- 父类引用指向子类对象(向上转型)
多态的两种核心表现
向上转型(自动转换):父类引用指向子类对象
- 语法:
父类类型 引用名 = new 子类类型(); - 特点:只能调用父类中定义的方法(包括子类重写的方法),不能调用子类特有方法
- 示例:java
1Animal cat = new Cat(); // 向上转型(父类引用指向子类对象) 2cat.move(); // 执行子类重写的 move 方法 3// cat.catchMouse(); // 编译报错(父类无该方法,无法调用子类特有方法)
- 语法:
向下转型(强制转换):将父类引用转换为子类类型
- 场景:需要调用子类特有方法时
- 语法:
子类类型 引用名 = (子类类型) 父类引用; - 风险:若父类引用实际指向的对象不是该子类类型,会抛出
ClassCastException(类型转换异常) - 解决方案:先通过
instanceof判断类型,再转型 - 示例:java
1Animal animal = new Cat(); 2 3// 安全向下转型 4if (animal instanceof Cat) { 5 Cat cat = (Cat) animal; // 强制转换 6 cat.catchMouse(); // 调用子类特有方法 7} 8 9// 若 animal 指向 Dog 对象,instanceof 判断为 false,避免转型异常
多态的核心价值(OCP 原则)
- OCP 原则:对扩展开放,对修改关闭(软件开发七大原则之一)
- 核心价值:降低代码耦合度,提高扩展性(新增子类无需修改原有代码)
- 示例(多态在实际开发中的应用):java
1// 抽象父类(或接口) 2public abstract class Shape { 3 public abstract void draw(); // 抽象方法(无实现) 4} 5 6// 子类1:圆形 7public class Circle extends Shape { 8 @Override 9 public void draw() { 10 System.out.println("绘制圆形"); 11 } 12} 13 14// 子类2:矩形 15public class Rectangle extends Shape { 16 @Override 17 public void draw() { 18 System.out.println("绘制矩形"); 19 } 20} 21 22// 工具类(无需修改,支持所有 Shape 子类) 23public class DrawTool { 24 // 父类引用作为参数(多态核心) 25 public static void drawShape(Shape shape) { 26 shape.draw(); // 执行子类具体实现 27 } 28} 29 30// 调用示例 31public class Test { 32 public static void main(String[] args) { 33 DrawTool.drawShape(new Circle()); // 输出:绘制圆形 34 DrawTool.drawShape(new Rectangle()); // 输出:绘制矩形 35 // 新增三角形子类时,无需修改 DrawTool 代码,直接调用即可 36 } 37}
核心关键字 ¶
this 关键字 ¶
核心作用
- 指代当前对象(当前正在执行方法的对象)
- 只能在实例方法/构造方法中使用(不能在静态方法中使用,静态方法属于类,无对象)
常见用法
区分成员变量和局部变量(当变量名重名时):
java1public class Person { 2 private String name; 3 4 public void setName(String name) { 5 this.name = name; // this.name 指代成员变量,name 指代局部变量 6 } 7}调用当前类的其他构造方法:
- 语法:
this(参数列表);(必须在构造方法第一行) - 目的:代码复用(避免构造方法中重复逻辑)
- 示例:java
1public class Person { 2 private String name; 3 private int age; 4 5 // 无参构造 6 public Person() { 7 this("未知", 0); // 调用有参构造 8 } 9 10 // 有参构造 11 public Person(String name, int age) { 12 this.name = name; 13 this.age = age; 14 } 15}
- 语法:
返回当前对象(链式调用):
java1public class Person { 2 private String name; 3 4 public Person setName(String name) { 5 this.name = name; 6 return this; // 返回当前对象 7 } 8 9 // 链式调用示例 10 public static void main(String[] args) { 11 Person person = new Person().setName("张三"); 12 } 13}
super 关键字 ¶
核心作用
- 指代父类对象(访问父类的成员变量、方法、构造方法)
- 只能在实例方法/构造方法中使用(不能在静态方法中使用)
常见用法
访问父类的成员变量/方法(当子类与父类成员重名时):
java1public class Animal { 2 protected String name = "动物"; 3 4 public void eat() { 5 System.out.println("动物在进食"); 6 } 7} 8 9public class Cat extends Animal { 10 private String name = "猫咪"; 11 12 @Override 13 public void eat() { 14 super.eat(); // 调用父类的 eat 方法 15 System.out.println(super.name + "的子类" + this.name + "在吃鱼"); 16 } 17} 18 19// 调用示例 20public class Test { 21 public static void main(String[] args) { 22 Cat cat = new Cat(); 23 cat.eat(); 24 // 输出: 25 // 动物在进食 26 // 动物的子类猫咪在吃鱼 27 } 28}调用父类的构造方法:
- 语法:
super(参数列表);(必须在子类构造方法第一行) - 注意:
this()和super()不能同时出现(都要求在第一行) - 示例:参考 3.2 继承中的构造方法示例
- 语法:
static 关键字 ¶
核心作用
- 修饰的成员(变量/方法/代码块)属于类级别的(不属于对象,所有对象共享)
- 生命周期:类加载时初始化,类卸载时销毁(早于对象创建)
常见用法
静态变量(类变量):
- 语法:
public static 数据类型 变量名; - 访问方式:
类名.变量名(推荐)或对象名.变量名(不推荐) - 场景:存储所有对象共享的数据(如常量、统计信息)
- 示例:java
1public class Student { 2 // 静态变量(所有学生共享学校名称) 3 public static String SCHOOL_NAME = "北京大学"; 4 private String name; 5 6 // 构造方法 7 public Student(String name) { 8 this.name = name; 9 } 10} 11 12// 调用示例 13public class Test { 14 public static void main(String[] args) { 15 Student s1 = new Student("张三"); 16 Student s2 = new Student("李四"); 17 18 System.out.println(s1.SCHOOL_NAME); // 输出:北京大学 19 System.out.println(s2.SCHOOL_NAME); // 输出:北京大学 20 System.out.println(Student.SCHOOL_NAME); // 推荐用法 21 22 // 修改静态变量(所有对象共享修改结果) 23 Student.SCHOOL_NAME = "清华大学"; 24 System.out.println(s1.SCHOOL_NAME); // 输出:清华大学 25 } 26}
- 语法:
静态方法(类方法):
- 语法:
public static 返回值类型 方法名(参数列表) { } - 访问方式:
类名.方法名()(推荐)或对象名.方法名()(不推荐) - 限制:
- 不能访问实例变量和实例方法(只能访问静态变量和静态方法)
- 不能使用
this和super关键字
- 场景:工具类方法(如
Math.random()、Arrays.sort()) - 示例:java
1public class MathUtil { 2 // 静态工具方法(计算两数之和) 3 public static int add(int a, int b) { 4 return a + b; 5 } 6} 7 8// 调用示例 9public class Test { 10 public static void main(String[] args) { 11 int result = MathUtil.add(10, 20); // 直接通过类名调用 12 System.out.println(result); // 输出:30 13 } 14}
- 语法:
静态代码块:
- 语法:
static { 代码逻辑 } - 执行时机:类加载时执行(仅执行一次,早于构造方法)
- 场景:初始化静态变量、加载资源(如配置文件)
- 示例:java
1public class StaticDemo { 2 // 静态变量 3 public static String CONFIG; 4 5 // 静态代码块(初始化静态变量) 6 static { 7 System.out.println("静态代码块执行"); 8 CONFIG = "加载配置文件成功"; 9 } 10 11 // 构造方法 12 public StaticDemo() { 13 System.out.println("构造方法执行"); 14 } 15} 16 17// 调用示例 18public class Test { 19 public static void main(String[] args) { 20 StaticDemo demo1 = new StaticDemo(); 21 StaticDemo demo2 = new StaticDemo(); 22 // 输出: 23 // 静态代码块执行(仅执行一次) 24 // 构造方法执行 25 // 构造方法执行 26 System.out.println(StaticDemo.CONFIG); // 输出:加载配置文件成功 27 } 28}
- 语法:
final 关键字 ¶
核心作用
- 修饰变量:变量值不可修改(常量)
- 修饰方法:方法不可重写
- 修饰类:类不可继承
常见用法
修饰变量(常量):
- 局部变量:声明时可不赋值,但使用前必须赋值(且赋值后不可修改)
- 成员变量:声明时必须赋值(或在构造方法中赋值),不可修改
- 推荐命名规范:常量名全大写,多个单词用下划线分隔(如
MAX_AGE) - 示例:java
1public class FinalDemo { 2 // 成员常量(声明时赋值) 3 public static final int MAX_AGE = 150; 4 private final String NAME; 5 6 // 构造方法中给成员常量赋值 7 public FinalDemo(String name) { 8 this.NAME = name; // 仅可赋值一次 9 } 10 11 public static void main(String[] args) { 12 // 局部常量 13 final int MIN_AGE = 0; 14 // MIN_AGE = 1; // 编译报错(不可修改) 15 16 FinalDemo demo = new FinalDemo("张三"); 17 // demo.NAME = "李四"; // 编译报错(不可修改) 18 } 19}
修饰方法(不可重写):
java1public class Father { 2 // final 方法不可被子类重写 3 public final void sayHello() { 4 System.out.println("Hello"); 5 } 6} 7 8public class Son extends Father { 9 // @Override 10 // public void sayHello() { } // 编译报错(无法重写 final 方法) 11}修饰类(不可继承):
java1// final 类不可被继承 2public final class String { 3 // ... 4} 5 6// public class MyString extends String { } // 编译报错(无法继承 final 类)
内部类(四种类型+应用场景) ¶
成员内部类 ¶
定义与特点 ¶
- 定义在类的内部,无
static修饰 - 属于外部类的实例成员(需通过外部类对象创建内部类对象)
- 可访问外部类的所有成员(包括
private修饰的成员) - 外部类访问内部类成员需通过内部类对象
语法与示例 ¶
java
1// 外部类
2public class Outer {
3 private String outerName = "外部类";
4
5 // 成员内部类
6 public class Inner {
7 private String innerName = "内部类";
8
9 // 内部类方法(可访问外部类成员)
10 public void show() {
11 System.out.println(outerName); // 访问外部类私有成员
12 System.out.println(innerName);
13 }
14 }
15
16 // 外部类方法(访问内部类成员需创建内部类对象)
17 public void callInner() {
18 Inner inner = new Inner();
19 inner.show();
20 }
21
22 // 调用示例
23 public static void main(String[] args) {
24 // 方式1:通过外部类对象创建内部类对象
25 Outer outer = new Outer();
26 Outer.Inner inner = outer.new Inner();
27 inner.show();
28
29 // 方式2:外部类方法间接调用
30 outer.callInner();
31 }
32}静态内部类 ¶
定义与特点 ¶
- 定义在类的内部,有
static修饰 - 属于外部类的类成员(无需外部类对象,直接通过外部类名创建)
- 只能访问外部类的静态成员(不能访问实例成员)
- 外部类访问内部类成员:直接通过
内部类名.成员(静态成员)或创建内部类对象(实例成员)
语法与示例 ¶
java
1// 外部类
2public class Outer {
3 private static String outerStaticName = "外部类静态变量";
4 private String outerInstanceName = "外部类实例变量";
5
6 // 静态内部类
7 public static class StaticInner {
8 private String innerName = "静态内部类";
9
10 public void show() {
11 System.out.println(outerStaticName); // 可访问外部类静态成员
12 // System.out.println(outerInstanceName); // 编译报错(不能访问实例成员)
13 System.out.println(innerName);
14 }
15 }
16
17 // 调用示例
18 public static void main(String[] args) {
19 // 直接通过外部类名创建静态内部类对象(无需外部类对象)
20 Outer.StaticInner inner = new Outer.StaticInner();
21 inner.show();
22 }
23}局部内部类 ¶
定义与特点 ¶
- 定义在方法/代码块内部(无
static修饰) - 作用域仅限于当前方法/代码块(外部无法访问)
- 可访问外部类的所有成员,以及方法中的
final局部变量(JDK 8+ 可省略final,但变量仍为隐式常量) - 编译后类名:
外部类名$数字内部类名.class(如Outer$1LocalInner.class)
语法与示例 ¶
java
1// 外部类
2public class Outer {
3 private String outerName = "外部类";
4
5 public void method() {
6 // 方法中的局部变量(JDK 8+ 隐式 final)
7 String localVar = "局部变量";
8
9 // 局部内部类(定义在方法内部)
10 class LocalInner {
11 public void show() {
12 System.out.println(outerName); // 访问外部类成员
13 System.out.println(localVar); // 访问方法局部变量(隐式 final)
14 }
15 }
16
17 // 只能在当前方法中创建内部类对象并调用
18 LocalInner inner = new LocalInner();
19 inner.show();
20 }
21
22 // 调用示例
23 public static void main(String[] args) {
24 Outer outer = new Outer();
25 outer.method();
26 // 输出:
27 // 外部类
28 // 局部变量
29 }
30}匿名内部类 ¶
定义与特点 ¶
- 无类名的局部内部类(简化局部内部类的写法)
- 必须继承一个父类或实现一个接口(只能继承一个类或实现一个接口)
- 作用域仅限于当前方法/代码块(外部无法访问)
- 编译后类名:
外部类名$数字.class(如Outer$1.class) - 核心场景:简化单次使用的类(如回调接口、线程创建)
语法与示例 ¶
java
1// 接口(匿名内部类实现该接口)
2public interface Greeting {
3 void sayHello();
4}
5
6// 外部类
7public class Outer {
8 public void callGreeting() {
9 // 匿名内部类(实现 Greeting 接口)
10 Greeting greeting = new Greeting() {
11 @Override
12 public void sayHello() {
13 System.out.println("Hello, 匿名内部类!");
14 }
15 };
16
17 greeting.sayHello(); // 调用匿名内部类的方法
18 }
19
20 // 简化写法(直接作为方法参数)
21 public void callGreeting2(Greeting greeting) {
22 greeting.sayHello();
23 }
24
25 // 调用示例
26 public static void main(String[] args) {
27 Outer outer = new Outer();
28 outer.callGreeting(); // 输出:Hello, 匿名内部类!
29
30 // 匿名内部类作为方法参数(常用场景)
31 outer.callGreeting2(new Greeting() {
32 @Override
33 public void sayHello() {
34 System.out.println("匿名内部类作为参数!");
35 }
36 }); // 输出:匿名内部类作为参数!
37 }
38}Lambda 表达式替代(JDK 8+) ¶
- 匿名内部类实现函数式接口(仅一个抽象方法的接口)时,可简化为 Lambda 表达式
- 示例:java
1// Lambda 表达式替代匿名内部类(实现 Greeting 接口) 2Greeting greeting = () -> System.out.println("Lambda 替代匿名内部类!"); 3greeting.sayHello(); // 输出:Lambda 替代匿名内部类! 4 5// 作为方法参数 6outer.callGreeting2(() -> System.out.println("Lambda 作为参数!"));
常见问题与避坑指南 ¶
语法层面 ¶
变量命名规范:
- 实例变量/局部变量:驼峰命名(首字母小写,如
userName) - 静态常量:全大写+下划线(如
MAX_RETRY_COUNT) - 类名:帕斯卡命名(首字母大写,如
UserService) - 方法名:驼峰命名(首字母小写,如
getUserInfo())
- 实例变量/局部变量:驼峰命名(首字母小写,如
空指针异常(NPE):
- 常见场景:
null引用调用方法/属性、数组为null时访问索引 - 避坑方案:
- 调用方法前先判断非空(
if (obj != null) { obj.method(); }) - 使用
Objects.requireNonNull(obj, "对象不能为空")校验参数 - JDK 8+ 用
Optional类处理可能为null的值
- 调用方法前先判断非空(
- 常见场景:
数组越界异常(ArrayIndexOutOfBoundsException):
- 避坑方案:访问数组前先判断索引范围(
if (index >= 0 && index < array.length) { ... })
- 避坑方案:访问数组前先判断索引范围(
面向对象层面 ¶
equals() 与 == 的区别:
==:比较基本数据类型的值,比较引用数据类型的地址equals():默认继承自Object,比较地址;子类可重写(如String重写后比较内容)- 示例:java
1String s1 = new String("abc"); 2String s2 = new String("abc"); 3System.out.println(s1 == s2); // 输出:false(地址不同) 4System.out.println(s1.equals(s2)); // 输出:true(内容相同)
toString() 方法的作用:
- 默认继承自
Object,返回类名@哈希码(如com.example.Person@1b6d3586) - 推荐重写:返回对象的属性信息(便于调试)
- 示例:java
1public class Person { 2 private String name; 3 private int age; 4 5 @Override 6 public String toString() { 7 return "Person{name='" + name + "', age=" + age + "}"; 8 } 9} 10 11// 调用 12Person person = new Person("张三", 25); 13System.out.println(person); // 输出:Person{name='张三', age=25}
- 默认继承自
性能层面 ¶
字符串拼接:
- 避免使用
String +=进行大量拼接(每次拼接创建新对象,效率低) - 推荐使用
StringBuilder(单线程)或StringBuffer(多线程) - 示例:java
1// 低效写法 2String str = ""; 3for (int i = 0; i < 1000; i++) { 4 str += i; // 每次创建新 String 对象 5} 6 7// 高效写法 8StringBuilder sb = new StringBuilder(); 9for (int i = 0; i < 1000; i++) { 10 sb.append(i); // 仅操作一个对象 11} 12String result = sb.toString();
- 避免使用
静态方法与实例方法的选择:
- 无状态的工具方法(如数学计算、字符串处理)推荐用静态方法(无需创建对象,效率高)
- 依赖对象状态的方法用实例方法