第 5 篇:深入浅出学 Java 语言(JDK8 版)—— 精通类与对象进阶,掌握 Java 面向对象核心能力
简介:聚焦 Java 面向对象核心的“类与对象”进阶知识,从类的完整定义(字段、方法、构造函数)、对象的实例化与使用,到 this 关键字、访问控制、类成员与实例成员,再到嵌套类(静态/内部/局部/匿名类)、Lambda 表达式与方法引用,最后到枚举类型,结合 JDK8 代码示例与通俗解析,帮初学者从基础到进阶吃透类与对象,筑牢 Java 面向对象编程核心能力。
一、类的定义:Java 面向对象的“蓝图”
在 Java 中,类是创建对象的“蓝图”,它定义了对象的状态(字段)和行为(方法)。一个完整的类结构包含修饰符、类名、父类(可选)、接口(可选)和类体,类体中又包含字段、构造函数和方法,这三部分共同构成对象的“骨架”。
1. 类的核心组件
以经典的 Bicycle
类为例,它清晰展示了类的核心组成:
public class Bicycle {// 成员变量(字段):存储对象状态private int cadence;private int gear;private int speed;// 构造函数:初始化对象public Bicycle(int startCadence, int startSpeed, int startGear) {this.gear = startGear; // this 区分字段与参数this.cadence = startCadence;this.speed = startSpeed;}// 方法:定义对象行为public void setCadence(int newValue) {cadence = newValue;}public void speedUp(int increment) {speed += increment;}public int getSpeed() {return speed;}
}
- 成员变量(字段):分为实例变量(非静态,每个对象独有)和类变量(静态,所有对象共享)。按封装原则,通常用
private
修饰,通过getter/setter
方法间接访问,避免直接暴露内部状态。 - 方法:包含修饰符、返回类型、方法名、参数列表和方法体。Java 支持方法重载——多个方法同名但参数列表(数量/类型)不同,例如
DataArtist
类的draw(String s)
和draw(int i)
。 - 构造函数:与类名相同,无返回类型,用于初始化对象。若未显式定义,编译器会生成无参默认构造函数;若类有父类,默认构造函数会调用父类的无参构造函数(如隐式父类
Object
的构造函数)。
2. 访问控制:控制成员的可见范围
访问修饰符决定了类及成员的访问权限,是封装的核心手段,四种修饰符的访问范围如下:
- public:对所有类可见,常用于对外提供的接口方法或常量。
- protected:本类、同包类、子类可见,适合子类需要继承的成员。
- 默认(无修饰符):仅本类和同包类可见,属于包级私有。
- private:仅本类可见,用于隐藏内部实现细节(如
Bicycle
的cadence
字段)。
例如,若 Bicycle
的 speed
字段用 private
修饰,其他类无法直接修改,只能通过 speedUp
或 applyBrake
方法控制,确保速度不会被非法修改(如负数)。
二、对象的生命周期:从创建到回收
类是抽象蓝图,对象是类的具体实例。一个对象的生命周期包含“创建-使用-回收”三个阶段,每个阶段都有明确的语法和规则。
1. 创建对象:声明、实例化与初始化
创建对象需三步:声明引用变量、实例化(new
运算符)、初始化(调用构造函数),例如:
// 1. 声明引用变量(未关联对象,值为 null)
Point originOne;
// 2. 实例化+初始化(new 分配内存,调用构造函数)
originOne = new Point(23, 94);
- 声明:
Point originOne
仅告诉编译器变量类型,不创建对象,此时调用originOne.x
会报错。 - 实例化:
new Point(23, 94)
为对象分配内存,并返回内存引用。 - 初始化:构造函数
Point(int a, int b)
为字段x
、y
赋值,确保对象创建时状态合法。
若类有多个构造函数(如 Rectangle
类的无参、带 Point
参数、带宽高参数的构造函数),编译器会根据参数列表匹配对应的构造函数。
2. 使用对象:访问字段与调用方法
对象创建后,通过“对象引用.成员”的语法访问字段或调用方法:
// 访问字段(需权限允许)
System.out.println("矩形宽度:" + rectOne.width);
// 调用方法
rectTwo.move(40, 72); // 修改矩形原点
- 若在类内部使用字段,可直接用简单名称(如
Rectangle
类中直接写width
);外部需用引用变量(如rectOne.width
)。 - 方法调用时,参数需与声明的类型和顺序匹配,例如
computePayment(double, double, double, int)
需传入四个对应类型的参数。
3. 垃圾回收:自动释放无用对象
Java 无需手动销毁对象,垃圾回收器(GC)会自动回收“无引用”的对象——当对象没有任何引用指向时(如变量设为 null
、变量超出作用域),GC 会在合适时机释放其内存。例如:
// 执行后,new Rectangle() 无引用,可被 GC 回收
int tempHeight = new Rectangle().height;
注意:若对象有多个引用(如 originOne
和 rectTwo.origin
指向同一个 Point
对象),需所有引用都失效,对象才会被回收。
三、类的进阶特性:this、static 与初始化
掌握类的基础后,还需理解 this
关键字、static
修饰符和初始化机制,这些是写出优雅 Java 代码的关键。
1. this 关键字:指代当前对象
this
是实例方法或构造函数中对“当前对象”的引用,主要用于两个场景:
- 区分字段与参数:当参数名与字段名相同时(如
Point(int x, int y)
),用this.x = x
表示“当前对象的 x 字段”,避免遮蔽。 - 调用同类构造函数:在构造函数中用
this(参数)
调用其他构造函数,减少代码重复。例如Rectangle
类的无参构造函数调用四参数构造函数:public Rectangle() {this(0, 0, 1, 1); // 调用 Rectangle(int x, int y, int width, int height) }
2. static 关键字:类成员 vs 实例成员
static
修饰的成员属于“类”而非单个对象,称为类成员,与之对应的是实例成员(无 static
,每个对象独有):
- 静态变量(类变量):所有对象共享,内存中仅一份,通过“类名.变量名”访问(如
Bicycle.numberOfBicycles
)。例如用静态变量统计Bicycle
对象的创建数量:private static int numberOfBicycles = 0; public Bicycle(...) {id = ++numberOfBicycles; // 每次创建对象,计数+1 }
- 静态方法(类方法):无需创建对象即可调用(如
Math.sqrt()
),不能直接访问实例成员(需通过对象引用),也不能使用this
关键字。
注意:静态变量通常与 final
结合定义常量(如 static final double PI = 3.14159
),常量名全大写,单词间用下划线分隔。
3. 初始化机制:静态块与实例块
除了字段声明时初始化(如 public int width = 0
),Java 还支持块初始化,分为两种:
- 静态初始化块:用
static
修饰,仅在类加载时执行一次,用于初始化静态变量。例如:static {// 初始化静态变量的复杂逻辑(如读取配置)config = loadConfig(); }
- 实例初始化块:无
static
修饰,每次创建对象时执行(在构造函数前),用于多个构造函数共享的初始化逻辑:{// 所有构造函数都会执行的初始化validateDefaultValues(); }
四、嵌套类:逻辑分组与封装的进阶
Java 允许在一个类内部定义另一个类(嵌套类),按是否带 static
分为静态嵌套类和内部类,它们的核心作用是“逻辑分组仅用一次的类”和“增强封装”。
1. 静态嵌套类:独立于外部类实例
静态嵌套类用 static
修饰,属于外部类的“静态成员”,与外部类实例无关,访问规则如下:
- 可直接访问外部类的静态成员(如
OuterClass.staticOuterField
),访问实例成员需通过外部类对象引用。 - 实例化语法:
OuterClass.StaticNestedClass obj = new OuterClass.StaticNestedClass()
,无需先创建外部类对象。
例如 OuterClass
的静态嵌套类 StaticNestedClass
:
public class OuterClass {static String staticOuterField = "静态外部字段";static class StaticNestedClass {void accessMembers(OuterClass outer) {System.out.println(outer.outerField); // 需外部类对象System.out.println(staticOuterField); // 直接访问静态成员}}
}
2. 内部类:依赖外部类实例
无 static
的嵌套类称为内部类,必须依赖外部类实例存在,能直接访问外部类的所有成员(包括 private
):
- 实例化语法:先创建外部类对象,再通过
外部对象.new 内部类()
创建内部类对象:OuterClass outer = new OuterClass(); OuterClass.InnerClass inner = outer.new InnerClass();
- 典型场景:辅助类(如
DataStructure
类的EvenIterator
内部类,用于遍历数组偶数索引):public class DataStructure {private int[] arrayOfInts = new int[15];// 内部类:实现迭代逻辑private class EvenIterator implements Iterator<Integer> {private int nextIndex = 0;public boolean hasNext() {return nextIndex <= arrayOfInts.length - 1;}public Integer next() {Integer val = arrayOfInts[nextIndex];nextIndex += 2; // 步长 2,取偶数索引return val;}}// 使用内部类遍历public void printEven() {Iterator<Integer> iterator = new EvenIterator();while (iterator.hasNext()) {System.out.print(iterator.next() + " ");}} }
3. 局部类与匿名类:方法内的临时类
- 局部类:定义在方法或代码块内,仅在当前块中有效,可访问外部类成员和“实际上 final”的局部变量(JDK8+,初始化后值不变)。例如
LocalClassExample
中,PhoneNumber
类仅在validatePhoneNumber
方法中使用,用于验证手机号。 - 匿名类:无类名的局部类,是“表达式级别的类”,仅用一次,常用于实现接口(如事件监听)。例如用匿名类实现
HelloWorld
接口:HelloWorld frenchGreeting = new HelloWorld() {String name = "tout le monde";public void greet() {greetSomeone(name);}public void greetSomeone(String someone) {System.out.println("Salut " + someone);} };
注意:局部类和匿名类会产生“遮蔽”问题——若内部类的变量与外部同名,内部变量会遮蔽外部变量,需用“外部类名.this.变量”区分(如 ShadowTest.this.x
)。
五、Lambda 表达式与方法引用:简化函数式编程
JDK8 引入的 Lambda 表达式,是对“单方法接口(函数式接口)”的简洁实现,避免匿名类的冗余语法;方法引用则是 Lambda 的进一步简化,直接引用现有方法。
1. Lambda 表达式:匿名方法的简写
Lambda 表达式的语法为 (参数列表) -> 方法体
,仅适用于函数式接口(如 Predicate<T>
、Consumer<T>
)。例如用 Lambda 筛选 18-25 岁的男性:
// 函数式接口 Predicate<Person>
Predicate<Person> isEligible = (Person p) -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25;
- 语法简化:参数类型可省略(
p -> ...
),单参数可省略括号(p -> ...
),单表达式方法体可省略大括号和return
(如上例)。 - 目标类型:Lambda 的类型由上下文决定(如方法参数类型),例如
printPersons
方法参数为CheckPerson
,则 Lambda 就是CheckPerson
类型。 - 访问局部变量:Lambda 只能访问“实际上 final”的局部变量(初始化后值不变),否则编译报错。
2. 方法引用:引用现有方法
当 Lambda 仅调用一个现有方法时,可用方法引用替代,分为四种类型:
类型 | 语法 | 示例 |
---|---|---|
静态方法引用 | 类名::静态方法名 | Person::compareByAge |
特定对象实例方法引用 | 对象::实例方法名 | myApp::appendStrings2 |
任意对象实例方法引用 | 类名::实例方法名 | String::compareToIgnoreCase |
构造函数引用 | 类名::new | HashSet::new |
例如用方法引用排序 Person
数组:
Person[] rosterAsArray = roster.toArray(new Person[0]);
Arrays.sort(rosterAsArray, Person::compareByAge); // 静态方法引用
六、枚举类型:类型安全的常量
枚举(enum
)是特殊的类,用于定义一组固定常量,比 static final
常量更安全(避免类型错误),还可包含属性和方法。
1. 枚举的基础使用
定义枚举类型需用 enum
关键字,常量全大写,例如一周的日子:
public enum Day {SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
}
使用时直接通过“枚举名.常量”访问,配合 switch
语句更清晰:
public void tellItLikeItIs(Day day) {switch (day) {case MONDAY: System.out.println("Mondays are bad."); break;case SATURDAY: case SUNDAY: System.out.println("Weekends are best."); break;default: System.out.println("Midweek days are so-so.");}
}
2. 带属性和方法的枚举
枚举可包含属性、构造函数和方法,例如表示太阳系行星的 Planet
枚举,包含质量、半径属性和计算重力的方法:
public enum Planet {MERCURY(3.303e+23, 2.4397e6),VENUS(4.869e+24, 6.0518e6),EARTH(5.976e+24, 6.37814e6); // 常量列表以分号结尾private final double mass; // 质量(kg)private final double radius; // 半径(m)// 私有构造函数(枚举构造函数必须私有)Planet(double mass, double radius) {this.mass = mass;this.radius = radius;}// 计算表面重力double surfaceGravity() {final double G = 6.67300E-11; // 万有引力常量return G * mass / (radius * radius);}// 计算物体在该行星的重量double surfaceWeight(double earthWeight) {return earthWeight / EARTH.surfaceGravity() * surfaceGravity();}
}
调用时通过枚举常量调用方法:
double earthWeight = 175;
System.out.println("在水星上的重量:" + Planet.MERCURY.surfaceWeight(earthWeight));
七、问题与练习:巩固类与对象知识
1. 基础问题(自查掌握度)
IdentifyMyParts
类中,public static int x
是____(类变量/实例变量),public int y
是____(类变量/实例变量)?
答案:类变量、实例变量- 执行以下代码,输出结果是什么?
答案:IdentifyMyParts a = new IdentifyMyParts(); IdentifyMyParts b = new IdentifyMyParts(); a.y = 5; b.y = 6; a.x = 1; b.x = 2; System.out.println("a.y=" + a.y + ", b.y=" + b.y + ", a.x=" + a.x + ", IdentifyMyParts.x=" + IdentifyMyParts.x);
a.y=5, b.y=6, a.x=2, IdentifyMyParts.x=2
(类变量x
共享,最后被设为 2) - 修复
SomethingIsWrong
程序的错误:
修复:添加实例化public class SomethingIsWrong {public static void main(String[] args) {Rectangle myRect; // 仅声明,未实例化myRect.width = 40;System.out.println("面积:" + myRect.getArea());} }
myRect = new Rectangle();
2. 动手练习
- 练习1:用枚举重写 Card 类
定义Suit
枚举(花色:HEART、DIAMOND、CLUB、SPADE)和Rank
枚举(点数:ACE、TWO…KING),Card 类包含suit
和rank
字段,重写toString()
方法:public class Card {private final Suit suit;private final Rank rank;public Card(Suit suit, Rank rank) {this.suit = suit;this.rank = rank;}@Overridepublic String toString() {return rank + " of " + suit;}// Suit 和 Rank 枚举public enum Suit { HEART, DIAMOND, CLUB, SPADE }public enum Rank { ACE, TWO, THREE, ..., KING } }
- 练习2:实现 Deck 类
Deck 类包含List<Card>
字段,在构造函数中初始化 52 张牌(4 种花色 × 13 种点数),提供shuffle()
(洗牌)和deal()
(发牌)方法:public class Deck {private List<Card> cards = new ArrayList<>();public Deck() {// 初始化 52 张牌for (Card.Suit suit : Card.Suit.values()) {for (Card.Rank rank : Card.Rank.values()) {cards.add(new Card(suit, rank));}}}public void shuffle() {Collections.shuffle(cards);}public Card deal() {return cards.isEmpty() ? null : cards.remove(0);} }
八、总结:类与对象是 Java 面向对象的基石
Java 面向对象的核心是“用类描述事物,用对象模拟实例”——类定义了事物的共性(字段+方法),对象是共性的具体体现;通过封装(访问控制)隐藏细节,通过嵌套类和枚举优化代码结构,通过 Lambda 简化函数式编程。
掌握本文内容后,你已具备 Java 面向对象的核心能力:能定义符合封装原则的类、灵活创建和使用对象、用进阶特性(this、static、嵌套类)优化代码、用枚举保证常量安全。后续学习集合、框架时,这些知识将成为你理解复杂逻辑的基础。