欢迎光临
我们一直在努力

【Java SE】类与对象

类与对象

  • 类的定义
    • 类的基本语法
    • 修饰符详解
      • a) 访问控制修饰符
      • b) 类型修饰符
    • 继承与实现
      • extends – 类的继承
      • implements – 接口的实现
    • 类体组成
      • 成员变量的定义
      • 成员方法的定义
  • 对象的创建与成员访问
    • 对象的创建
      • 成员变量的默认值
    • 对象的成员访问
    • 对象与成员的生命周期
      • a) 对象的生命周期
      • b) 成员变量的生命周期
    • 成员变量的作用域
  • 对象的初始化和构造方法
    • 对象初始化概述
    • 三种初始化方式详解
      • a) 定义时指定初值
      • b) 初始化代码块
      • c) 构造方法初始化
      • 初始化执行顺序演示
    • 构造方法的特性与注意事项
      • 基本语法规则
      • 构造方法的重载
      • 默认构造方法
      • 构造方法不能被继承和重写
    • 引用变量的理解
  • 深入理解 `this` 关键字
    • `this` 的基本概念(类型/使用范围/引用约束)
    • `this` 的三种核心用途
      • a) 通过 `this` 访问当前对象的成员变量
      • b) 通过 `this` 访问当前对象的非静态成员方法
      • c) 通过 `this` 访问当前对象的其他构造方法
    • 深入理解:`this` 是如何传递的?
    • 深入理解:对象创建时 `this` 是如何出现的?
      • 对象创建的六个步骤
      • 代码层面的体现
      • 为什么要有这些步骤

简单理解,对象是一种实体,而类是一种规范。

类的定义

类是Java面向对象编程中最基本的组成单位,它是一种抽象的数据类型,用于描述具有相同属性和行为的一组对象。类是创建对象的模板或蓝图。

类的基本语法

在Java中,使用class关键字来定义一个类,完整的类定义语法如下:

[修饰符] class 类名 [extends 父类名] [implements 接口列表] {
// 类体部分
// 1. 成员变量的定义
[修饰符] 数据类型 变量名;

// 2. 成员方法的定义
[修饰符] 返回值类型 方法名([参数列表]) [throws 异常列表] {
// 方法体
}
}

语法说明:

语法元素说明是否必需
[修饰符] 类的访问权限和特性修饰符 可选
class 定义类的关键字 必需
[extends 父类名] 指定继承的父类 可选(默认继承Object类)
[implements 接口列表] 指定实现的接口,多个接口用逗号分隔 可选

修饰符详解

类的修饰符可以分为两大类:访问控制修饰符和类型修饰符。多个修饰符可以同时使用,它们之间用空格隔开,顺序可以任意排列。

a) 访问控制修饰符

修饰符说明
public 公有访问级别,任何位置都可以访问
缺省(默认) 不写任何访问修饰符,只能被同一包中的类访问

注意: 一个Java源文件中只能有一个public修饰的类,且该类名必须与文件名相同。

b) 类型修饰符

修饰符说明
abstract 抽象类,不能被实例化,只能被继承
final 最终类,不能被继承

修饰符使用示例:

// public修饰的类,可以被任何包访问
public class Student {
// 类体
}

// 缺省(默认)访问级别的类,只能被同一包中的类访问
class Teacher {
// 类体
}

// 抽象类,不能被实例化
public abstract class Animal {
// 类体
}

// 最终类,不能被继承
public final class MathUtils {
// 类体
}

// 多个修饰符组合使用
public abstract class Shape {
// 类体
}

继承与实现

extends – 类的继承

extends关键字用于指定当前类要继承的父类。Java是单继承语言,即一个类只能有一个直接父类。

// 父类定义
public class Animal {
protected String name;

public void eat() {
System.out.println(name + "正在进食");
}
}

// 子类继承父类
public class Dog extends Animal {
public void bark() {
System.out.println(name + "正在汪汪叫");
}
}

继承的特点:

  • 子类继承父类所有的非私有成员(变量和方法)
  • 如果没有使用extends指定父类,则默认继承java.lang.Object类
  • 所有类最终都继承自Object类

implements – 接口的实现

implements关键字用于指定当前类要实现的接口。一个类可以实现多个接口,接口之间用逗号分隔。

// 定义接口
public interface Flyable {
void fly();
}

public interface Swimable {
void swim();
}

// 类实现多个接口
public class Bird implements Flyable, Swimable {
@Override
public void fly() {
System.out.println("鸟儿在飞翔");
}

@Override
public void swim() {
System.out.println("鸟儿在游泳");
}
}

类体组成

类体由两部分核心内容组成:成员变量(属性)和成员方法(行为)。

成员变量的定义

[修饰符] 数据类型 变量名 [= 初始值];

成员变量的修饰符:

修饰符分类具体修饰符
访问控制 public、protected、private
静态修饰 static
常量修饰 final
特性修饰 transient、volatile

示例:

public class Person {
// 成员变量定义
public String name; // 公有访问
private int age; // 私有访问
protected String idCard; // 受保护访问
static String country = "中国"; // 静态变量
final double PI = 3.14159; // 常量
}

成员方法的定义

[修饰符] 返回值类型 方法名([参数列表]) [throws 异常列表] {
// 方法体
[return 返回值;]
}

成员方法的修饰符:

修饰符分类具体修饰符
访问控制 public、protected、private
静态修饰 static
抽象修饰 abstract
最终修饰 final
同步修饰 synchronized
本地修饰 native

示例:

public class Calculator {
// 无参数无返回值的方法
public void showInfo() {
System.out.println("这是一个计算器类");
}

// 带参数带返回值的方法
public int add(int a, int b) {
return a + b;
}

// 静态方法
public static void printVersion() {
System.out.println("版本 1.0");
}

// 声明抛出异常的方法
public void divide(int a, int b) throws ArithmeticException {
if (b == 0) {
throw new ArithmeticException("除数不能为0");
}
System.out.println("结果:" + a / b);
}
}

语法元素作用示例
extends 指定继承的父类 class Dog extends Animal { }
implements 指定实现的接口 class Bird implements Flyable { }
访问修饰符 控制访问权限 public、private、protected
类型修饰符 定义类的特性 abstract、final
成员变量 定义类的属性 private String name;
成员方法 定义类的行为 public void study() { }

对象的创建与成员访问

对象的创建

在Java中,对象是类的实例,创建对象需要通过new关键字来完成。创建对象的同时,通常会将对象的引用赋值给一个变量,语法格式如下:

<类名> 引用变量名 = new <类名>([参数列表])

其中,[参数列表]是可选的,用于调用类的构造方法进行对象初始化。

示例:

// 创建Person类的对象,并将引用赋值给p
Person p = new Person();

// 创建带参数的构造方法的对象
Person p2 = new Person("张三", 20);

成员变量的默认值

如果在定义成员变量时没有显式指定初始值,Java虚拟机会自动为其赋予默认值,这是Java语言的安全机制,确保成员变量在使用前总有一个确定的初始状态。

成员变量类型默认值
byte、short、int、long 0
float、double 0.0
char ‘\\u0000’(空字符)
boolean false
任何引用类型(如String、数组、对象) null

示例:

public class DefaultValuesExample {
// 实例变量的默认值
int num; // 默认值:0
double price; // 默认值:0.0
boolean flag; // 默认值:false
char ch; // 默认值:\\u0000
String name; // 默认值:null
int[] numbers; // 默认值:null

// 静态变量的默认值
static int staticNum; // 默认值:0
static String staticName; // 默认值:null

public static void main(String[] args) {
DefaultValuesExample obj = new DefaultValuesExample();

// 输出结果均为对应类型的默认值
System.out.println("num: " + obj.num); // 0
System.out.println("name: " + obj.name); // null
System.out.println("staticNum: " + staticNum); // 0
}
}

对象的成员访问

对象创建完成后,可以通过引用变量来访问对象的成员,包括实例变量(成员变量)和实例方法(成员方法)。访问语法为:

引用变量名.成员变量名 // 访问对象的属性
引用变量名.成员方法名() // 调用对象的方法

示例:

Person p = new Person();
p.name = "李四"; // 访问成员变量
p.age = 25; // 访问成员变量
p.sayHello(); // 调用成员方法

对象与成员的生命周期

a) 对象的生命周期

  • 对象通过new关键字创建后,将在堆内存中分配空间。
  • 当没有任何引用变量指向该对象时,对象将变为垃圾对象。
  • Java的垃圾回收处理器会自动发现并清除这些无引用的对象,释放内存空间。

b) 成员变量的生命周期

成员变量的生命周期与其所属对象完全一致:

  • 创建时:当对象通过new创建时,成员变量被分配内存并完成初始化。
  • 销毁时:当对象被垃圾回收时,成员变量所占用的内存也随之释放。

class MyClass {
int x; // 成员变量
}

MyClass obj = new MyClass(); // 成员变量x被创建
obj = null; // 对象成为垃圾,成员变量x等待回收

成员变量的作用域

成员变量的作用域是整个类内部,具体特点如下:

  • 成员变量可以在类的任何成员方法、构造方法或初始化块中直接访问。
  • 无论成员变量使用何种访问修饰符(public、protected、private、缺省),在类内部都可以直接使用变量名进行访问。
  • 成员变量的作用域不受其声明位置在类中前后顺序的影响。
  • class MyClass {
    private int x; // 私有成员变量
    public int y; // 公有成员变量

    public void setX(int value) {
    x = value; // 在成员方法中直接访问私有变量x
    }

    public int getX() {
    return x; // 在成员方法中直接访问私有变量x
    }

    public void printY() {
    System.out.println(y); // 访问公有变量y
    }
    }

    概念说明
    对象创建 类名 引用变量 = new 类名(参数)
    成员访问 引用变量.成员变量 或 引用变量.成员方法()
    对象销毁 无引用指向时,由垃圾回收器自动回收
    成员变量生命周期 与所属对象相同,随对象创建而创建,随对象销毁而释放
    成员变量作用域 整个类内部,所有方法均可直接访问

    对象的初始化和构造方法

    对象初始化概述

    当使用new关键字创建对象时,Java虚拟机会为对象分配内存并进行初始化。对象的初始化是指为对象的成员变量设置初始值的过程。在Java中,对象的初始化可以通过以下三种方式完成:

  • 定义时指定初值:在声明成员变量时直接赋值
  • 初始化代码块:使用{}定义的代码块,在构造方法执行前自动执行
  • 构造方法:通过构造方法为成员变量赋值
  • 这三种初始化方式的执行顺序为:

    定义时赋值 → 初始化代码块 → 构造方法

    三种初始化方式详解

    a) 定义时指定初值

    在声明成员变量的同时直接赋值,是最简单直接的初始化方式。

    public class Point {
    private int x = 10; // 定义时赋初值
    private int y = 20; // 定义时赋初值
    private String name = "原点"; // 引用类型赋初值
    }

    b) 初始化代码块

    初始化代码块是使用{}括起来的代码段,在每次创建对象时都会自动执行(在构造方法之前)。

    public class Point {
    private int x;
    private int y;

    // 实例初始化块
    {
    x = 20; // 覆盖定义时的赋值
    y = 30;
    System.out.println("实例初始化块执行");
    }

    // 静态初始化块:在类加载时执行一次,用于初始化静态变量
    static {
    System.out.println("静态初始化块执行");
    // 可以初始化静态变量
    }
    }

    c) 构造方法初始化

    通过构造方法为成员变量赋值,是最常用、最灵活的初始化方式。

    public class Point {
    private int x;
    private int y;

    // 无参构造方法
    public Point() {
    this.x = 0;
    this.y = 0;
    }

    // 带参构造方法
    public Point(int x, int y) {
    this.x = x;
    this.y = y;
    }
    }

    初始化执行顺序演示

    public class Point {
    private int x = 10; // 第一步:定义时赋值

    // 第二步:实例初始化块
    {
    x = 20;
    System.out.println("初始化块执行,x = " + x);
    }

    // 第三步:构造方法
    public Point() {
    x = 30;
    System.out.println("构造方法执行,x = " + x);
    }

    public static void main(String[] args) {
    Point p = new Point();
    // 输出顺序:
    // 初始化块执行,x = 20
    // 构造方法执行,x = 30
    }
    }

    构造方法的特性与注意事项

    构造方法是Java中一种特殊的方法,用于初始化新创建的对象。它具有以下重要特性:

    基本语法规则

    • 名称必须与类名完全相同
    • 没有返回值类型(连void也不能写)
    • 不能被static、final、synchronized、abstract、native修饰

    public class Student {
    private String name;
    private int age;

    // 正确的构造方法
    public Student() {
    name = "未知";
    age = 0;
    }

    // 错误示例:不能有返回值类型
    // public void Student() { } // 这不是构造方法,而是普通方法
    }

    构造方法的重载

    一个类可以有多个构造方法,只要参数列表不同即可。这称为构造方法的重载。

    public class Point {
    private int x;
    private int y;

    // 无参构造方法
    public Point() {
    this(0, 0); // 调用另一个构造方法
    }

    // 两个参数的构造方法
    public Point(int x, int y) {
    this.x = x;
    this.y = y;
    }

    // 一个参数的构造方法
    public Point(int value) {
    this(value, value); // 调用两个参数的构造方法
    }
    }

    // 使用时,系统会根据参数自动匹配对应的构造方法
    Point p1 = new Point(); // 调用无参构造
    Point p2 = new Point(5, 8); // 调用两个参数的构造
    Point p3 = new Point(10); // 调用一个参数的构造

    默认构造方法

    如果类中没有定义任何构造方法,系统会自动提供一个无参的默认构造方法。

    public class Point {
    private int x;
    private int y;
    // 没有定义任何构造方法
    }

    // 仍然可以调用无参构造
    Point p = new Point(); // 系统提供的默认构造方法

    注意: 如果类中已经定义了任何构造方法,系统将不再提供默认的无参构造。

    public class Point {
    private int x;
    private int y;

    public Point(int x, int y) { // 定义了带参构造
    this.x = x;
    this.y = y;
    }
    }

    // 错误!此时系统不再提供默认无参构造
    // Point p = new Point(); // 编译错误

    // 正确方式
    Point p = new Point(5, 8); // 必须使用已定义的构造方法

    构造方法不能被继承和重写

    • 构造器不能被继承:子类不能继承父类的构造方法
    • 构造器不能被重写(Override):构造方法的名称必须与当前类名相同,因此无法重写

    class Parent {
    public Parent() {
    System.out.println("Parent constructor");
    }

    public Parent(String name) {
    System.out.println("Parent constructor with name");
    }
    }

    class Child extends Parent {
    // 这不是重写,而是定义子类自己的构造方法
    public Child() {
    System.out.println("Child constructor");
    }

    public Child(String name) {
    // 可以通过super()调用父类构造方法
    super(name); // 调用父类的带参构造
    System.out.println("Child constructor with name");
    }
    }

    引用变量的理解

    在Java中,对象是通过引用变量来操作和访问的。理解引用变量的本质对于掌握Java内存模型至关重要。

    public class Point {
    private int x = 10; // 定义时赋初值
    private int y = 20; // 定义时赋初值

    // 无参构造方法
    public Point() {
    this(0, 0); // 调用另一个构造方法
    }

    // 两个参数的构造方法
    public Point(int x, int y) {
    this.x = x;
    this.y = y;
    }

    }
    public void main(String[] args) {
    Point p1 = new Point(); // p1指向新创建的Point对象
    Point p2 = new Point(5, 8); // p2指向另一个新创建的Point对象
    Point p3 = p1; // p3和p1指向同一个Point对象
    }

    引用变量的内存示意图: 在这里插入图片描述

    深入理解 this 关键字

    this 的基本概念(类型/使用范围/引用约束)

    this 是一个指向当前对象的引用,它在实例方法或构造方法中使用。

    核心要点:

  • this 的类型:this 的类型是对应类的引用类型,即哪个对象调用,this 就是哪个对象的引用类型。

    public class Student {
    public void printThis() {
    System.out.println(this); // 输出:Student@15db9742(某个具体的对象地址)
    }

    public static void main(String[] args) {
    Student s1 = new Student();
    Student s2 = new Student();
    s1.printThis(); // 这里的 this 指向 s1
    s2.printThis(); // 这里的 this 指向 s2
    }
    }

  • this 的使用范围:this 只能在"成员方法"中使用(包括实例方法和构造方法),不能在静态方法中使用。

  • this 的引用约束:在"成员方法"中,this 只能引用当前对象,不能再引用其他对象。

    public void test() {
    this = new Student(); // 编译错误!不能修改 this 的指向
    }

  • this 的三种核心用途

    根据上述基本概念,this 主要有以下三种用法:

    a) 通过 this 访问当前对象的成员变量

    当实例变量(成员变量)和局部变量(方法参数或方法内定义的变量)重名时,可以使用 this 来明确指代实例变量。这是最常见的 this 使用场景。

    public class Student {
    private String name; // 实例变量
    private int age; // 实例变量

    // 构造方法:参数名与实例变量同名
    public Student(String name, int age) {
    // this.name 指实例变量,name 指方法参数
    this.name = name;
    this.age = age;
    }

    // Setter 方法:参数名与实例变量同名
    public void setName(String name) {
    this.name = name; // 必须使用 this 区分
    }

    public void setAge(int age) {
    this.age = age; // 必须使用 this 区分
    }

    public void introduce() {
    System.out.println("我叫" + name + ",今年" + age + "岁");
    // 此处虽然没有 this,但实际相当于 this.name 和 this.age
    }
    }

    b) 通过 this 访问当前对象的非静态成员方法

    this 可以用来调用当前对象的其他实例方法,虽然通常可以省略,但在某些场景下显式使用 this 可以提高代码的可读性。

    public class Calculator {
    private int result;

    public Calculator add(int value) {
    this.result += value;
    this.display(); // 使用 this 调用当前对象的方法
    return this; // 返回当前对象,支持链式调用
    }

    public void display() {
    System.out.println("当前结果:" + this.result);
    }

    public static void main(String[] args) {
    Calculator calc = new Calculator();
    calc.add(5).add(3); // 链式调用
    }
    }

    c) 通过 this 访问当前对象的其他构造方法

    在一个构造方法中,可以使用 this(…) 调用类中的另一个构造方法。这种方式称为构造方法的重用,可以有效减少代码重复。

    使用规则:

    • this(…) 必须放在构造方法的第一行
    • 一个构造方法中只能调用一次 this(…)
    • 如果使用了 this(…),就不能再使用 super(…)(因为都要求在第一行)

    public class Rectangle {
    private double width;
    private double height;
    private String color;

    // 完整构造方法
    public Rectangle(double width, double height, String color) {
    this.width = width;
    this.height = height;
    this.color = color;
    System.out.println("完整构造方法被调用");
    }

    // 构造方法1:只指定宽高,颜色默认为黑色
    public Rectangle(double width, double height) {
    this(width, height, "黑色"); // 调用完整构造方法
    }

    // 构造方法2:无参构造方法,默认宽高为1,颜色为黑色
    public Rectangle() {
    this(1, 1); // 调用二参构造方法
    }

    // 构造方法3:正方形,颜色默认为黑色
    public Rectangle(double side) {
    this(side, side); // 调用二参构造方法
    }
    }

    核心要点说明注意事项
    this 的类型 对应类类型的引用 哪个对象调用,this 就是哪个对象的引用
    使用范围 只能在成员方法中使用 不能在静态方法中使用
    引用约束 只能引用当前对象 不能修改 this 的指向
    三种用法语法形式主要作用
    访问成员变量 this.成员变量 解决局部变量与成员变量同名问题
    访问成员方法 this.成员方法() 调用当前对象的其他实例方法
    调用其他构造方法 this(参数) 实现构造方法重用

    深入理解:this 是如何传递的?

    很多初学者会疑惑:this 到底是怎么来的?其实,this 是 Java 编译器自动传递的隐藏参数。

    来看下面的代码对比:

    public class Date {
    private int year;
    private int month;
    private int day;

    // 我们平时写的代码(使用 this)
    public void setDate(int year, int month, int day) {
    this.year = year;
    this.month = month;
    this.day = day;
    }
    }

    实际上,Java 编译器在处理实例方法时,会自动在参数列表的最前面添加一个隐藏参数,即当前对象的引用。上面的代码在编译层面相当于:

    // 编译器内部视角:隐藏的 this 参数
    public void setDate(Date this, int year, int month, int day) {
    this.year = year;
    this.month = month;
    this.day = day;
    }

    • 当调用 d1.setDate(2024, 1, 15) 时,JVM 会自动将 d1 的引用作为第一个参数传递进去
    • 方法内部的 this 就指向了调用该方法的对象
    • 这就是为什么我们说:哪个对象的引用调用了该方法,this 就代表哪个对象

    验证示例:

    public class Date {
    private int year;
    private int month;
    private int day;

    public void setDate(int year, int month, int day) {
    this.year = year;
    this.month = month;
    this.day = day;
    }

    public void printThis() {
    System.out.println("当前 this 指向:" + this);
    }

    public static void main(String[] args) {
    Date d1 = new Date();
    Date d2 = new Date();

    d1.printThis(); // 输出:当前 this 指向:Date@15db9742 (d1的地址)
    d2.printThis(); // 输出:当前 this 指向:Date@6d06d69c (d2的地址)

    d1.setDate(2024, 1, 15); // 调用时,JVM自动传递 d1 作为 this
    d2.setDate(2024, 1, 16); // 调用时,JVM自动传递 d2 作为 this
    }
    }

    这个理解为什么重要?

  • 解释了 this 的本质:它不是魔法,而是编译器提供的语法糖
  • 解释了为什么静态方法没有 this:静态方法属于类,调用时不需要传递对象引用

    为什么静态方法不需要传递对象引用?

  • 帮助理解方法调用:每个实例方法的调用都隐含了对象引用的传递
  • 深入理解:对象创建时 this 是如何出现的?

    为了更深入地理解 this,我们需要知道在 JVM 层面,一个对象是如何被创建出来的。当我们在程序中写下一行简单的代码:

    Date d = new Date(2024, 3, 10);

    在 JVM 层面,实际上经历了复杂的六个步骤:

    对象创建的六个步骤

    在这里插入图片描述

    步骤描述关键动作
    1. 类加载检查 检测 Date 类是否已加载 如果没有加载,JVM 通过类加载器加载该类
    2. 分配内存 为对象分配堆空间 计算对象所需内存大小,在堆中划分一块内存
    3. 并发安全处理 保证线程安全 多个线程同时创建对象时,保证空间分配不冲突
    4. 内存空间初始化 设置默认值 所有成员变量设置为默认值(0、false、null)
    5. 设置对象头 存储对象元数据 记录对象的哈希码、GC分代年龄、类型指针等
    6. 调用构造方法 执行初始化代码 执行程序员编写的构造方法,给成员变量赋值

    代码层面的体现

    public class Date {
    private int year; // 步骤4:初始化为0
    private int month; // 步骤4:初始化为0
    private int day; // 步骤4:初始化为0

    // 构造方法 – 步骤6被调用
    public Date(int year, int month, int day) {
    // 此时对象已经存在,this 已经指向这个对象
    // 成员变量已经有默认值(0,0,0)

    this.year = year; // 将形参year的值赋给对象的year
    this.month = month; // 将形参month的值赋给对象的month
    this.day = day; // 将形参day的值赋给对象的day
    }
    }

    为什么要有这些步骤

  • 步骤1-3:JVM 需要确保对象能够被正确创建,不会因为类未加载或多线程冲突而出错

  • 步骤4(初始化默认值):保证对象在任何构造方法执行之前都处于一个确定的状态

    // 即使不在构造方法中赋值,成员变量也有默认值
    Date d = new Date(); // 如果不赋值,year=0, month=0, day=0

  • 步骤5(对象头):每个对象都需要一些元数据来支持 JVM 的功能(如垃圾回收、同步等)

  • 步骤6(构造方法):这是程序员可以控制的步骤,通过 this 给成员变量赋予想要的值

  • 在步骤6(构造方法执行)时:

    • 对象已经存在(步骤1-5已经完成)
    • 内存已经分配,对象头已经设置好
    • this 引用已经存在,指向这块内存
    • 构造方法中的 this 就是在这个时候被传入的

    这就是为什么我们可以在构造方法中使用 this 来给成员变量赋值:

    public Date(int year, int month, int day) {
    // this 已经指向步骤1-5创建好的对象
    this.year = year; // 给对象的year字段赋值
    this.month = month; // 给对象的month字段赋值
    this.day = day; // 给对象的day字段赋值
    }

    赞(0)
    未经允许不得转载:171主机测评 » 【Java SE】类与对象
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址