Java 类与对象

面向对象基础

类与对象

  • :一组具有相同属性和行为的对象的抽象描述。它是创建对象的模板。
  • 对象:类的具体实例,代表现实世界中某个具体的事物。
  • 成员变量:对象的属性(状态)。
  • 成员方法:对象的行为(操作)。

关系:类是对象的抽象,对象是类的实例。类不占用内存,而对象存储在堆内存中。

示例:学生类

class Student {
    // 成员变量
    String name;  // 姓名
    char sex;     // 性别
    int age;      // 年龄

    // 成员方法
    public void learn() {
        System.out.println(this.name + "正在学习");
    }

    public void dine() {
        System.out.println(this.name + "正在吃饭");
    }
}

成员变量与局部变量

比较项 成员变量 局部变量
定义位置 类中,方法外 方法定义中、方法参数、代码块内
生命周期 随对象的创建而存在,随对象被回收而消失 随方法的调用而存在,方法结束即消失
初始化值 有默认值(如 null, 0, false) 无默认值,必须显式赋值后才能使用
内存位置 堆内存(对象本身存储在堆中) 栈内存(方法调用时压入栈帧)
作用域 在整个类中有效 仅在定义它的方法或代码块内有效

补充说明

  • 引用类型的局部变量本身存储在栈中,但指向的对象仍在堆中。
  • 类变量(static 修饰)存储在方法区。

访问权限修饰符

Java 提供了四种访问权限控制级别,用于控制类、成员变量、方法的可见性。

修饰符 本类 同包 子类(不同包) 其他包
public
protected
default(无修饰符)
private
  • public:所有类都可访问。
  • protected:同包类、不同包子类可访问。
  • default:仅同包类可访问。
  • private:仅本类内部可访问。

构造方法

构造方法是一种特殊的方法,用于初始化新创建的对象

特点

  • 方法名必须与类名完全相同。
  • 没有返回值类型(连 void 也不能写)。
  • 可以重载(多个参数列表不同的构造方法)。
  • 在创建对象时通过 new 关键字自动调用。

示例

class Apple {
    int sum;
    String color;

    // 无参构造
    public Apple() {}

    // 有参构造
    public Apple(int sum) {
        this.sum = sum;
    }

    public Apple(String color) {
        this.color = color;
    }

    public Apple(int sum, String color) {
        this.sum = sum;
        this.color = color;
    }
}

// 使用
Apple a1 = new Apple();          // 调用无参构造
Apple a2 = new Apple(10);        // 调用 int 参数构造
Apple a3 = new Apple("red");     // 调用 String 参数构造
Apple a4 = new Apple(5, "green");

注意事项

  • 如果类中没有显式定义任何构造方法,编译器会自动生成一个无参构造方法。
  • 一旦定义了任何构造方法,编译器就不再提供默认无参构造,若仍需无参构造则必须手动编写。
image-20220817215955820

上图错误原因是:必须提供Apple带有int参数的构造方法,而默认的无参构造方法没有被允许使用。


深入理解:类的生命周期(JVM层面)

Java 类从被加载到虚拟机内存中,到卸载出内存,完整生命周期包括以下阶段:

image

加载(Loading)

作用:通过类的全限定名获取二进制字节流,并将其转化为方法区的运行时数据结构,同时在堆中生成一个 java.lang.Class 对象作为访问入口。

类加载器分类

类加载器结构图

  • 启动类加载器(Bootstrap ClassLoader):加载 JAVA_HOME/lib 目录下的核心类(如 rt.jar)。
  • 扩展类加载器(Extension ClassLoader):加载 JAVA_HOME/lib/ext 目录下的类。
  • 应用程序类加载器(Application ClassLoader):加载用户类路径(classpath)上的类。

双亲委派模型
当一个类加载器收到加载请求时,首先将任务委派给父类加载器,最终由启动类加载器尝试加载。只有当父类加载器无法完成时,子加载器才会自己加载。这保证了核心类库(如 java.lang.Object)在任何环境下都是同一份。

链接(Linking)

  • 验证(Verify):确保字节码符合规范,不会危害 JVM 安全。
  • 准备(Prepare):为静态变量分配内存,并设置默认初始值(如 0, null, false)。注意:final static 变量在准备阶段直接赋值为编译时常量。
  • 解析(Resolve):将常量池中的符号引用替换为直接引用(如内存地址或偏移量)。

初始化(Initialization)

核心操作:执行类构造器 <clinit>() 方法,该方法由编译器收集静态变量赋值动作和静态代码块合并而成。

<clinit>() 方法特点

  • 由 JVM 保证线程安全(多线程环境下只会有一个线程执行)。
  • 父类的 <clinit>() 优先于子类执行。
  • 接口的 <clinit>() 不要求父接口先执行,仅当使用父接口变量时才触发。

触发类初始化的场景(主动引用)

  1. 使用 new 关键字实例化对象、读取或设置静态变量(被 final 修饰且已放入常量池的除外)、调用静态方法。
  2. 对类进行反射调用。
  3. 初始化子类时,如果父类尚未初始化,则先触发父类初始化。
  4. 虚拟机启动时,包含 main() 方法的主类会先被初始化。
  5. 使用 java.lang.invoke.MethodHandle 时,对应的方法句柄指向的类如果未初始化则触发。

不会触发类初始化的场景(被动引用)

  • 通过子类引用父类的静态字段,只会触发父类初始化,不会触发子类。
  • 定义对象数组(如 Apple[] arr = new Apple[10])不会触发 Apple 类初始化。
  • 引用编译时常量(如 static final int MAX = 100)不会触发定义常量的类初始化。
  • 通过 Class.forName() 加载类时指定 initialize=false
  • 通过 ClassLoader.loadClass() 加载类不会触发初始化。

总结

  • 类与对象是面向对象编程的核心,理解它们的区别与联系是基础。
  • 成员变量和局部变量在生命周期、存储位置、默认值等方面有明显差异。
  • 访问权限修饰符用于封装和控制可见性。
  • 构造方法负责对象初始化,注意默认构造方法的生成规则。
  • 深入 JVM 层面的类加载机制有助于理解 Java 程序的执行过程,是进阶必备知识。

掌握这些内容,你将对 Java 的类与对象有一个全面且深刻的认识。

参考自:http://www.cnblogs.com/ssslinppp/

posted @ 2022-10-27 17:23  克峰同学  阅读(70)  评论(0)    收藏  举报