S01-10 JavaSE-面向对象-高阶
[TOC]
类变量和类方法
类变量-提出问题
需求:一群小孩玩堆雪人,不时有新小孩加入,如何统计当前玩游戏的总人数?
传统方法的问题
- 定义独立变量
count统计,不遵循OOP思想 - 访问和维护麻烦,不共享于对象
类变量快速入门(ChildGame.java)
java
package com.hspedu.static_;
public class ChildGame {
public static void main(String[] args) {
Child child1 = new Child("白骨精");
Child child2 = new Child("狐狸精");
Child child3 = new Child("老鼠精");
child1.join();
child2.join();
child3.join();
// 类变量通过类名访问(推荐)
System.out.println("共有" + Child.count + " 小孩加入了游戏...");
System.out.println("child1.count=" + child1.count); // 3
System.out.println("child2.count=" + child2.count); // 3
System.out.println("child3.count=" + child3.count); // 3
}
}
class Child {
private String name;
// 类变量(静态变量):所有对象共享
public static int count = 0;
public Child(String name) {
this.name = name;
}
public void join() {
System.out.println(name + " 加入了游戏..");
count++; // 共享变量自增
}
}什么是类变量
- 类变量(静态变量):该类所有对象共享的变量
- 任何对象访问时,取值/修改的都是同一个变量
类变量定义语法
java
访问修饰符 static 数据类型 变量名; // 推荐
// 或
static 访问修饰符 数据类型 变量名;类变量访问方式
java
// 1. 类名.类变量名(推荐)
System.out.println(A.name);
// 2. 对象名.类变量名(不推荐,可读性差)
A a = new A();
System.out.println(a.name);类变量使用注意事项和细节
- 适用场景:多个对象共享一个变量时(例:统计学生总缴费
Student.static fee) - 类变量 vs 实例变量:
- 类变量:所有对象共享
- 实例变量:每个对象独享
- 类变量随类加载而初始化(无需创建对象即可访问)
- 实例变量不能通过类名访问
- 类变量生命周期:随类加载开始,随类消亡而销毁
案例代码(StaticDetail.java)
java
package com.hspedu.static_;
public class StaticDetail {
public static void main(String[] args) {
// 类变量无需创建对象即可访问
System.out.println(B.n2); // 200
System.out.println(C.address); // 北京
// 实例变量不能通过类名访问(错误)
// System.out.println(B.n1);
}
}
class B {
public int n1 = 100; // 实例变量
public static int n2 = 200; // 类变量
}
class C {
public static String address = "北京"; // 类变量
}类方法基本介绍
类方法(静态方法)定义语法:
java
访问修饰符 static 数据返回类型 方法名(参数列表) { // 推荐
// 方法体
}
// 或
static 访问修饰符 数据返回类型 方法名(参数列表) {
// 方法体
}类方法调用方式
java
// 1. 类名.类方法名(推荐)
Stu.payFee(100);
// 2. 对象名.类方法名(不推荐)
Stu tom = new Stu("tom");
tom.payFee(200);类方法应用案例(StaticMethod.java)
java
package com.hspedu.static_;
public class StaticMethod {
public static void main(String[] args) {
// 静态方法无需创建对象即可调用
Stu.payFee(100);
Stu.payFee(200);
Stu.showFee(); // 总学费有:300
// 工具类静态方法调用(示例)
System.out.println("9 开平方的结果是=" + Math.sqrt(9));
System.out.println(MyTools.calSum(10, 30)); // 40.0
}
}
// 工具类:静态方法便于调用
class MyTools {
// 静态方法:求两数之和
public static double calSum(double n1, double n2) {
return n1 + n2;
}
}
class Stu {
private String name;
private static double fee = 0; // 静态变量:累积学费
public Stu(String name) {
this.name = name;
}
// 静态方法:访问静态变量
public static void payFee(double fee) {
Stu.fee += fee;
}
// 静态方法:显示总学费
public static void showFee() {
System.out.println("总学费有:" + Stu.fee);
}
}类方法经典使用场景
- 方法不涉及对象相关成员(无
this/super,无实例变量) - 工具类方法(例:
Math、Arrays、Collections) - 通用功能(打印数组、排序、计算等)
类方法使用注意事项和细节
- 类方法无
this参数,普通方法隐含this参数 - 类方法可通过类名/对象名调用;普通方法只能通过对象名调用
- 类方法不能使用
this/super关键字 - 类方法只能访问静态成员(静态变量/静态方法)
- 普通方法可访问静态成员和非静态成员(遵循访问权限)
案例代码(StaticMethodDetail.java)
java
package com.hspedu.static_;
public class StaticMethodDetail {
public static void main(String[] args) {
D.hi(); // 静态方法:直接通过类名调用
new D().say(); // 普通方法:需创建对象调用
}
}
class D {
private int n1 = 100; // 非静态变量
private static int n2 = 200; // 静态变量
// 静态方法
public static void hi() {
// 错误:不能使用 this
// System.out.println(this.n1);
// 正确:访问静态变量
System.out.println(n2);
// 正确:调用静态方法
hello();
// 错误:调用普通方法
// say();
}
// 静态方法
public static void hello() {
System.out.println("静态方法 hello");
}
// 普通方法
public void say() {
// 正确:访问非静态变量
System.out.println(n1);
// 正确:访问静态变量
System.out.println(n2);
// 正确:调用静态方法
hello();
}
}课堂练习1(StaticExercise01.java)
java
public class Test {
static int count = 9;
public void count() {
System.out.println("count=" + (count++));
}
public static void main(String args[]) {
new Test().count(); // 输出:9
new Test().count(); // 输出:10
System.out.println(Test.count); // 输出:11
}
}课堂练习2(StaticExercise02.java)
java
class Person {
private int id;
private static int total = 0;
// 静态方法:返回总人数
public static int getTotalPerson() {
// id++;// 错误:静态方法不能访问非静态变量
return total;
}
public Person() { // 构造器
total++; // 总人数自增
id = total; // 给id赋值
}
}
public class TestPerson {
public static void main(String[] args) {
System.out.println("Number of total is " + Person.getTotalPerson()); // 0
Person p1 = new Person();
System.out.println("Number of total is " + Person.getTotalPerson()); // 1
}
}课堂练习3(StaticExercise03.java)
java
class Person {
private int id;
private static int total = 0;
// 静态方法:设置总人数
public static void setTotalPerson(int total) {
// this.total = total; // 错误:静态方法不能使用 this
Person.total = total;
}
public Person() { // 构造器
total++;
id = total;
}
}
public class TestPerson {
public static void main(String[] args) {
Person.setTotalPerson(3);
new Person(); // total = 3 + 1 = 4
System.out.println(Person.getTotalPerson()); // 输出:4
}
}理解 main 方法语法
main 方法定义解析
java
public static void main(String[] args) {
// 方法体
}- 访问权限
public:JVM 需调用,必须公开 static:JVM 调用时无需创建对象- 返回值
void:JVM 无需接收返回值 - 参数
String[] args:接收命令行参数(执行java 类名 参数1 参数2时传入)
特别提示
- main 方法(静态方法)可直接调用本类的静态成员(静态变量/静态方法)
- main 方法不能直接访问本类的非静态成员,需创建对象后通过对象访问
案例代码(Main01.java)
java
package com.hspedu.main_;
public class Main01 {
// 静态变量
private static String name = "韩顺平教育";
// 非静态变量
private int n1 = 10000;
// 静态方法
public static void hi() {
System.out.println("Main01 的hi 方法");
}
// 非静态方法
public void cry() {
System.out.println("Main01 的cry 方法");
}
public static void main(String[] args) {
// 1. 直接访问静态成员
System.out.println("name=" + name);
hi();
// 2. 不能直接访问非静态成员(错误)
// System.out.println("n1=" + n1);
// cry();
// 3. 访问非静态成员:创建对象后调用
Main01 main01 = new Main01();
System.out.println(main01.n1); // ok
main01.cry(); // ok
}
}命令行参数案例(CommandPara.java)
java
package com.hspedu.main_;
public class CommandPara {
public static void main(String[] args) {
// 遍历命令行参数
for (int i = 0; i < args.length; i++) {
System.out.println("args[" + i + "]= " + args[i]);
}
}
}运行方式
bash
java CommandPara "lisa" "bily" "Mr Brown"输出结果
args[0]= lisa
args[1]= bily
args[2]= Mr Brown代码块
基本介绍
- 代码块(初始化块):类的成员之一,封装逻辑语句(
{}包围) - 无方法名、无返回值、无参数
- 无需显式调用,类加载或创建对象时隐式执行
基本语法
java
[修饰符] {
逻辑语句;
};- 修饰符可选,仅能写
static - 分号可省略
- 分类:
- 静态代码块(
static修饰) - 普通代码块(无
static修饰)
- 静态代码块(
代码块的好处和案例
- 作用:补充构造器,提取多个构造器的重复逻辑,提高代码复用性
- 执行顺序:代码块优先于构造器执行
案例代码(CodeBlock01.java)
java
package com.hspedu.codeblock_;
public class CodeBlock01 {
public static void main(String[] args) {
Movie movie = new Movie("你好,李焕英");
System.out.println("===============");
Movie movie2 = new Movie("唐探3", 100, "陈思诚");
}
}
class Movie {
private String name;
private double price;
private String director;
// 普通代码块:提取构造器重复逻辑
{
System.out.println("电影屏幕打开...");
System.out.println("广告开始...");
System.out.println("电影正式开始...");
}
// 构造器重载
public Movie(String name) {
System.out.println("Movie(String name) 被调用...");
this.name = name;
}
public Movie(String name, double price) {
System.out.println("Movie(String name, double price) 被调用...");
this.name = name;
this.price = price;
}
public Movie(String name, double price, String director) {
System.out.println("Movie(String name, double price, String director) 被调用...");
this.name = name;
this.price = price;
this.director = director;
}
}代码块使用注意事项和细节
- 静态代码块:
- 类加载时执行,仅执行一次
- 类加载的3种情况:
- 创建对象实例(
new) - 创建子类对象时,父类先加载
- 使用类的静态成员(静态变量/静态方法)
- 创建对象实例(
- 普通代码块:
- 创建对象时执行,每创建一个对象执行一次
- 仅创建对象时执行,使用静态成员时不执行
案例代码(CodeBlockDetail01.java)
java
package com.hspedu.codeblock_;
public class CodeBlockDetail01 {
public static void main(String[] args) {
// 1. 使用类的静态成员:静态代码块执行,普通代码块不执行
System.out.println(DD.n1); // 输出:DD 的静态代码1 被执行... → 8888
// 2. 创建对象:普通代码块执行
DD dd = new DD(); // 输出:DD 的普通代码块...
DD dd1 = new DD(); // 输出:DD 的普通代码块...
}
}
class DD {
public static int n1 = 8888; // 静态属性
// 静态代码块
static {
System.out.println("DD 的静态代码1 被执行...");
}
// 普通代码块
{
System.out.println("DD 的普通代码块...");
}
}- 创建对象时的执行顺序(单个类):
- 静态代码块和静态属性初始化(按定义顺序执行)
- 普通代码块和普通属性初始化(按定义顺序执行)
- 构造方法
案例代码(CodeBlockDetail02.java)
java
package com.hspedu.codeblock_;
public class CodeBlockDetail02 {
public static void main(String[] args) {
A a = new A();
// 执行顺序:
// 1. A 静态代码块01
// 2. getN1 被调用...(静态属性初始化)
// 3. A 普通代码块01
// 4. getN2 被调用...(普通属性初始化)
// 5. A() 构造器被调用
}
}
class A {
// 普通代码块
{
System.out.println("A 普通代码块01");
}
// 普通属性初始化
private int n2 = getN2();
// 静态代码块
static {
System.out.println("A 静态代码块01");
}
// 静态属性初始化
private static int n1 = getN1();
// 静态方法
public static int getN1() {
System.out.println("getN1 被调用...");
return 100;
}
// 普通方法
public int getN2() {
System.out.println("getN2 被调用...");
return 200;
}
// 构造器
public A() {
System.out.println("A() 构造器被调用");
}
}- 构造器的隐含执行逻辑:
- 构造器最前面隐含
super()(调用父类构造器) - 随后调用本类的普通代码块
- 构造器最前面隐含
案例代码(CodeBlockDetail03.java)
java
package com.hspedu.codeblock_;
public class CodeBlockDetail03 {
public static void main(String[] args) {
new BBB();
// 执行顺序:
// 1. AAA 的普通代码块(父类普通代码块)
// 2. AAA() 构造器被调用....(父类构造器)
// 3. BBB 的普通代码块...(子类普通代码块)
// 4. BBB() 构造器被调用....(子类构造器)
}
}
class AAA { // 父类
// 普通代码块
{
System.out.println("AAA 的普通代码块");
}
public AAA() {
// 隐含 super() 和 普通代码块调用
System.out.println("AAA() 构造器被调用....");
}
}
class BBB extends AAA { // 子类
// 普通代码块
{
System.out.println("BBB 的普通代码块...");
}
public BBB() {
// 隐含 super() 和 普通代码块调用
System.out.println("BBB() 构造器被调用....");
}
}创建子类对象时的执行顺序(继承关系):
- 父类的静态代码块和静态属性(按定义顺序)
- 子类的静态代码块和静态属性(按定义顺序)
- 父类的普通代码块和普通属性(按定义顺序)
- 父类的构造方法
- 子类的普通代码块和普通属性(按定义顺序)
- 子类的构造方法
代码块访问成员规则:
- 静态代码块:仅能访问静态成员(静态变量/静态方法)
- 普通代码块:可访问任意成员(静态/非静态)
课堂练习1(CodeBlockExercise01.java)
java
class Person {
public static int total; // 静态变量
// 静态代码块
static {
total = 100;
System.out.println("in static block!"); // (1)
}
}
public class Test {
public static void main(String[] args) {
System.out.println("total = " + Person.total); // 100
System.out.println("total = " + Person.total); // 100
}
}输出结果
in static block!
total = 100
total = 100课堂练习2(CodeBlockExercise02.java)
java
class Sample {
Sample(String s) {
System.out.println(s);
}
Sample() {
System.out.println("Sample 默认构造函数被调用");
}
}
class Test {
Sample sam1 = new Sample("sam1 成员初始化"); // (3)
static Sample sam = new Sample("静态成员sam 初始化"); // (1)
static {
System.out.println("static 块执行"); // (2)
if (sam == null) {
System.out.println("sam is null");
}
}
Test() {
System.out.println("Test 默认构造函数被调用"); // (4)
}
public static void main(String[] args) {
Test a = new Test();
}
}输出结果
静态成员sam 初始化
static 块执行
sam1 成员初始化
Sample 默认构造函数被调用
Test 默认构造函数被调用单例设计模式
什么是设计模式
- 设计模式:大量实践中总结的优选代码结构、编程风格、问题解决方案
- 类似棋谱:针对特定场景的最优解
什么是单例模式
- 单例模式:保证一个类在整个软件系统中仅存在一个对象实例
- 核心:
- 构造器私有化(防止直接
new) - 类内部创建对象
- 提供静态公共方法获取对象实例
- 构造器私有化(防止直接
- 两种实现方式:饿汉式、懒汉式
单例模式实现
1) 饿汉式(SingleTon01.java)
java
package com.hspedu.single_;
public class SingleTon01 {
public static void main(String[] args) {
// 不能直接 new(构造器私有化)
// GirlFriend xh = new GirlFriend("小红");
// 通过静态方法获取对象
GirlFriend instance = GirlFriend.getInstance();
GirlFriend instance2 = GirlFriend.getInstance();
System.out.println(instance == instance2); // true(同一对象)
System.out.println(instance);
System.out.println(instance2);
}
}
class GirlFriend {
private String name;
// 1. 构造器私有化
private GirlFriend(String name) {
System.out.println("构造器被调用...");
this.name = name;
}
// 2. 类内部创建对象(静态变量)
private static GirlFriend gf = new GirlFriend("小红红");
// 3. 提供静态公共方法获取对象
public static GirlFriend getInstance() {
return gf;
}
@Override
public String toString() {
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}2) 懒汉式(SingleTon02.java)
java
package com.hspedu.single_;
public class SingleTon02 {
public static void main(String[] args) {
// 首次调用 getInstance() 时创建对象
Cat instance = Cat.getInstance();
Cat instance2 = Cat.getInstance();
System.out.println(instance == instance2); // true
System.out.println(instance);
System.out.println(instance2);
}
}
class Cat {
private String name;
public static int n1 = 999;
// 2. 定义静态变量(默认 null)
private static Cat cat;
// 1. 构造器私有化
private Cat(String name) {
System.out.println("构造器调用...");
this.name = name;
}
// 3. 提供静态公共方法:使用时才创建对象
public static Cat getInstance() {
if (cat == null) {
cat = new Cat("小可爱");
}
return cat;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}饿汉式 VS 懒汉式
| 对比项 | 饿汉式 | 懒汉式 |
|---|---|---|
| 创建时机 | 类加载时创建 | 使用时(调用 getInstance)创建 |
| 线程安全 | 安全(类加载时仅创建一次) | 不安全(需后续优化) |
| 资源浪费 | 可能浪费(未使用也创建) | 不浪费(按需创建) |
应用场景
java.lang.Runtime类:经典的单例模式实现
final 关键字
基本介绍
final中文意思:最后的、最终的- 可修饰:类、属性、方法、局部变量
- 核心用途:
- 修饰类:类不能被继承
- 修饰方法:方法不能被重写(override)
- 修饰属性:属性值不能被修改(常量)
- 修饰局部变量:局部变量值不能被修改
案例代码(Final01.java)
java
package com.hspedu.final_;
public class Final01 {
public static void main(String[] args) {
E e = new E();
// e.TAX_RATE = 0.09; // 错误:常量不能修改
}
}
// 1. final 修饰类:不能被继承
final class A {}
// class B extends A {} // 错误
class C {
// 2. final 修饰方法:不能被重写
public final void hi() {}
}
class D extends C {
// @Override
// public void hi() {} // 错误:不能重写 final 方法
}
class E {
// 3. final 修饰属性:常量(命名规范:全大写,下划线分隔)
public final double TAX_RATE = 0.08;
}
class F {
public void cry() {
// 4. final 修饰局部变量:不能修改
final double NUM = 0.01;
// NUM = 0.9; // 错误
System.out.println("NUM=" + NUM);
}
}final 使用注意事项和细节
final修饰的属性(常量)必须赋初值,赋值位置:- 定义时:
public final double TAX_RATE = 0.08; - 构造器中
- 代码块中
- 定义时:
final修饰的静态属性,初始化位置:- 定义时
- 静态代码块中(不能在构造器中赋值)
final类不能继承,但可以实例化对象- 非
final类中的final方法:不能重写,但可以继承 final不能修饰构造器final和static搭配使用:效率更高(编译器优化,不触发类加载)- 包装类(
Integer、Double等)和String类都是final类(不能被继承)
案例代码(FinalDetail01.java)
java
package com.hspedu.final_;
public class FinalDetail01 {
public static void main(String[] args) {
CC cc = new CC(); // final 类可以实例化
new EE().cal(); // 继承 final 方法并调用
}
}
// 1. final 属性赋值示例
class AA {
// 定义时赋值
public final double TAX_RATE = 0.08;
// 构造器中赋值
public final double TAX_RATE2;
// 代码块中赋值
public final double TAX_RATE3;
public AA() {
TAX_RATE2 = 1.1;
}
{
TAX_RATE3 = 8.8;
}
}
// 2. final 静态属性赋值示例
class BB {
public static final double TAX_RATE = 99.9; // 定义时赋值
public static final double TAX_RATE2;
static {
TAX_RATE2 = 3.3; // 静态代码块中赋值
}
}
// 3. final 类
final class CC {}
// 4. 非 final 类中的 final 方法
class DD {
public final void cal() {
System.out.println("cal()方法");
}
}
class EE extends DD {} // 可以继承,不能重写 cal()案例代码(FinalDetail02.java)
java
package com.hspedu.final_;
public class FinalDetail02 {
public static void main(String[] args) {
// final + static 优化:不触发类加载(静态代码块不执行)
System.out.println(BBB.num); // 输出:10000(静态代码块未执行)
}
}
class BBB {
public final static int num = 10000; // final + static
static {
System.out.println("BBB 静态代码块被执行");
}
}
// 5. final 类无需再修饰方法为 final
final class AAA {
// public final void cry() {} // 多余
}应用实例(FinalExercise01.java)
计算圆形面积(圆周率 PI 为 final 常量):
java
package com.hspedu.final_;
public class FinalExercise01 {
public static void main(String[] args) {
Circle circle = new Circle(5.0);
System.out.println("面积=" + circle.calArea()); // 78.5
}
}
class Circle {
private double radius;
private final double PI; // final 常量
// 构造器中赋值
public Circle(double radius) {
this.radius = radius;
PI = 3.14;
}
// 计算面积
public double calArea() {
return PI * radius * radius;
}
}课堂练习
java
// 下面代码是否有误?为什么?
public int addOne(final int x) {
// ++x; // 错误:final 局部变量不能修改
return x + 1; // 正确:仅使用值,不修改
}抽象类
问题引入
父类方法的不确定性:当父类的某些方法无法确定实现逻辑时,直接实现无意义。
java
class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
// 问题:不同动物的 eat() 实现不同,父类无法确定
public void eat() {
// System.out.println("这是一个动物,但是不知道吃什么..");
}
}解决之道-抽象类
- 用
abstract修饰方法:抽象方法(无方法体) - 用
abstract修饰类:抽象类(包含抽象方法的类必须是抽象类) - 抽象类的价值:设计规范,由子类继承并实现抽象方法
快速入门代码
java
abstract class class Animal {
String name;
int age;
// 抽象方法(无方法体)
abstract public void eat();
}
// 子类继承抽象类,必须实现抽象方法
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}抽象类的基本语法
- 抽象类定义:
java
访问修饰符 abstract 类名 {
// 成员(属性、方法、构造器、静态成员等)
}- 抽象方法定义:
java
访问修饰符 abstract 返回类型 方法名(参数列表); // 无方法体抽象类使用注意事项和细节
- 抽象类不能被实例化(
new) - 抽象类可以没有抽象方法(仅作为不能实例化的类)
- 包含抽象方法的类必须声明为抽象类
abstract只能修饰类和方法,不能修饰属性和其他- 抽象类可以有任意成员(属性、非抽象方法、构造器、静态成员等)
- 抽象方法不能有方法体
- 子类继承抽象类:
- 必须实现所有抽象方法(除非子类也是抽象类)
- 抽象方法不能用
private、final、static修饰(与重写冲突)
案例代码(AbstractDetail01.java)
java
package com.hspedu.abstract_;
public class AbstractDetail01 {
public static void main(String[] args) {
// new A(); // 错误:抽象类不能实例化
}
}
// 1. 抽象类可以没有抽象方法
abstract class A {
public void hi() {
System.out.println("hi");
}
}
// 2. 包含抽象方法的类必须是抽象类
abstract class B {
public abstract void hi();
}
// 3. abstract 不能修饰属性
class C {
// public abstract int n1 = 1; // 错误
}案例代码(AbstractDetail02.java)
java
package com.hspedu.abstract_;
public class AbstractDetail02 {
public static void main(String[] args) {
new G().hi(); // 输出:子类实现的 hi()
}
}
// 4. 抽象类可以有任意成员
abstract class D {
public int n1 = 10; // 普通属性
public void hi() { // 普通方法
System.out.println("hi");
}
public abstract void hello(); // 抽象方法
public static void ok() { // 静态方法
System.out.println("ok");
}
}
// 5.