6993 字
35 min
0

面向对象

面向过程(Procedure Oriented) 和 面向对象(Object Oriented) 是两种不同的软件开发思想,前者强调以过程为中心来组织和执行代码,后者则以对象及其交互为核心来构建系统。

面向对象的概念

  1. 面向过程(Procedure Oriented)面向对象(Object Oriented) 都是对软件分析、设计和开发的一种思想,分别侧重于过程和数据的处理,以及对象的交互和组织。
  2. C语言 是一种典型的面向过程语言,而 Java 则是一种典型的面向对象语言,强调通过对象和类来组织代码和实现功能。

面向过程

  1. 面向过程 是一种将问题分解为一系列步骤,并通过函数逐步实现这些步骤的编程方法。使用时,通过依次调用函数来执行这些步骤。
  2. ✅ 优点:性能优于面向对象,因为面向过程不需要创建类的实例,避免了面向对象中对象实例化的开销,适用于对性能要求较高的场景,如:单片机、嵌入式开发和 Linux/Unix 系统等。
  3. ❌ 缺点:面向过程不如面向对象易于维护、复用和扩展,尤其是在项目规模变大时,代码的管理和维护会变得更加复杂

面向对象

  1. 面向对象 将问题分解为多个对象,每个对象代表问题中的一部分,目的是描述各个对象在解决问题过程中的行为,而不仅仅是实现一个步骤。
  2. 在宏观上采用面向对象的思想进行系统设计,微观上依然可以使用面向过程的方式处理具体的操作。
  3. ✅ 优点:易于维护、复用和扩展。面向对象的封装继承多态特性使得系统具有低耦合性,从而提高了灵活性和可维护性。
  4. ❌ 缺点:相比面向过程,面向对象的性能较低,尤其在资源有限的环境下,性能开销更为明显。

类和对象

  1. 对象:在编程设计中,几乎所有事物都可以视为对象,代表着系统中具体的实体。
  2. :类是对现实中一类事物的抽象,提取其共有的属性和行为,定义该类对象的共性特征。
  3. 对象与类对象是类的实例,而类是对象的模板

面向对象编程

类的定义

  1. 使用关键字 class 来定义一个类。一个 Java 文件可以包含多个类,但只能有一个 public class,且文件名必须与 public class 的名称相同
  2. 类由属性、方法组成。类中的属性称为 成员变量,方法称为 成员方法
  3. 成员变量定义在类中,整个类中的成员方法都可以访问这些成员变量。

// 文件名必须与 public class 名称相同,例如 Person.java
public class Person {
    // 成员变量
    String name;
    int age;
 
    // 成员方法
    void introduce() {
        System.out.println("Hello, my name is " + name + ", and I am " + age + " years old.");
    }
 
    // main 方法,程序入口
    public static void main(String[] args) {
        // 创建 Person 对象
        Person person = new Person();
        person.name = "Alice";
        person.age = 25;
 
        // 调用成员方法
        person.introduce();
    }
}
 

创建对象

  1. 通过 new 关键字来创建一个对象。
  2. 成员变量隶属于对象,可以通过 对象.成员变量 来访问和修改成员变量。
  3. 成员方法隶属于对象,可以通过 对象.成员方法(实参列表) 来调用成员方法。

public class Main {
    public static void main(String[] args) {
        // 1. 创建对象
        Person p = new Person();
 
        // 2. 访问和修改成员变量
        p.name = "Alice";
        p.age = 20;
 
        // 3. 调用成员方法
        p.introduce();
    }
}
 
class Person {
    String name;  // 成员变量
    int age;      // 成员变量
 
    void introduce() {  // 成员方法
        System.out.println("Hello, my name is " + name + ", I am " + age + " years old.");
    }
}

成员变量默认值

在对成员变量进行显式赋值之前,Java 虚拟机会自动将其初始化为默认值

  1. 整型0
  2. 浮点型0.0
  3. 字符型'\u0000'(即空字符)
  4. 布尔值false
  5. 引用类型null

public class DefaultValues {
    int intValue;          // 默认值 0
    double doubleValue;    // 默认值 0.0
    char charValue;        // 默认值 '\u0000'
    boolean booleanValue;  // 默认值 false
    String stringValue;    // 默认值 null
 
    void printValues() {
        System.out.println("int: " + intValue);
        System.out.println("double: " + doubleValue);
        System.out.println("char: [" + charValue + "]");
        System.out.println("boolean: " + booleanValue);
        System.out.println("String: " + stringValue);
    }
 
    public static void main(String[] args) {
        DefaultValues dv = new DefaultValues();
        dv.printValues();
    }
}
 

对象内存分析

  1. 栈(Stack)
    • 每个线程都会有自己的栈,用于存放方法调用的局部变量和方法调用信息(栈帧)。
    • 栈帧中包括:局部变量表、操作数栈、动态链接、方法返回地址。
    • 特点:先进后出(LIFO),当方法执行完毕,栈帧自动释放。
void foo() {
    int x = 10; // 局部变量存放在栈中
    Object obj = new Object(); // obj 引用存放在栈中
}
  1. 堆(Heap)
    • 所有通过 new 创建的对象都存放在堆中,包括对象实例和数组。
    • 堆中的对象生命周期由垃圾回收器(GC)管理,不再使用的对象会被回收。
    • 对象在堆中的存储结构:
      • 对象头(Object Header):存储对象的元数据,如:哈希码(hashcode)、GC 信息、锁信息等。
      • 实例数据(Instance Data):存放对象的成员变量(非静态字段)。
      • 对齐填充(Padding):为了满足内存对齐要求,可能存在额外填充字节。
class Person {
    int age;        // 存在堆中对象的实例数据区
    String name;    // 存放引用在堆中,引用指向堆中的字符串对象
}
 
Person p = new Person(); // p 引用在栈中,Person 对象在堆中
 
  1. 方法区(Method Area,或称元空间 MetaSpace)
    • 存放类的结构信息(类的字段、方法、常量池、静态变量等)。
    • 所有对象实例共享类的元数据信息。
    • 静态成员变量存储在方法区,而不是对象的实例数据区。
class Counter {
    static int count = 0; // 存储在方法区
}
 
  1. 运行时常量池(Runtime Constant Pool)
    • 类加载后,类文件中常量池会被放到方法区的运行时常量池中。
    • 存储编译期生成的字面量和符号引用,如:字符串字面量、final 常量。
String s = "hello";      // 字符串字面量存放在字符串常量池
final int num = 42;      // final 常量也存放在运行时常量池
 

image-20250903213945693

成员变量和局部变量的区别

局部变量定义在方法或代码块内,而成员变量定义在类中,并与类的方法处于同一层次。使用变量时遵循 就近原则优先在局部范围查找,如果没有再查找成员变量


  1. 定义的位置不同
    • 成员变量:定义在类中,类的所有成员方法都可以访问。
    • 局部变量:定义在方法或代码块中,仅在其所属的区域内有效。
  2. 内存中的位置不同
    • 成员变量:存在于堆内存中的对象中。
    • 局部变量:存在于栈内存的方法栈帧中。
  3. 生命周期不同
    • 成员变量:随着对象的创建而存在,随着对象的销毁而消失。
    • 局部变量:随着方法或代码块的执行而存在,执行结束后即被销毁。
  4. 初始化不同
    • 成员变量:有默认初始值。
    • 局部变量:没有默认初始值,必须显式初始化后才能使用。
  5. 可用修饰符不同
    • 成员变量:可以使用 publicprotectedprivatestaticfinal 等修饰符。
    • 局部变量:不能使用 publicprotectedprivatestatic 修饰符,只能用 final 修饰。

匿名对象

  1. 匿名对象 是指没有名称的对象,是定义对象时的一种简化方式。
  2. 这种对象的定义形式为:new Student();,即直接通过 new 创建对象,而不为其指定变量名。
  3. 当对象的方法仅调用一次时,可以使用匿名对象来简化代码。
  4. 匿名对象可以作为实际参数传递给方法。

public class Main {
    public static void main(String[] args) {
        Student s1 = new Student("小明");
        s1.study();
 
        new Student("小红").study();
 
        AnonymousObjectDemo demo = new AnonymousObjectDemo();
        demo.greet(new Student("小刚"));
    }
}
 
class Student {
    String name;
    Student(String name) { this.name = name; }
    void study() { System.out.println(name + " 正在学习"); }
}
 
class AnonymousObjectDemo {
    void greet(Student s) {
        System.out.println("欢迎 " + s.name + " 来上课");
    }
}
 

构造方法(construtor)

  1. 构造方法(或称构造器)用于初始化对象,即为对象的成员变量赋初始值。
  2. 构造方法的名称必须与类名相同,并遵循大驼峰命名规范。
  3. 构造方法不需要定义返回值类型,且在方法体中不能使用 return 语句返回具体的数据,显式return会导致Cannot return a value from a constructor编译异常。
  4. 构造方法通过 new 关键字调用,是一种特殊的方法。
  5. 如果类中没有显式定义构造方法,JVM 会默认提供一个无参构造方法;如果定义了带参数的构造方法,则无参构造方法会被取消
  6. 建议:不应省略无参构造方法,每个类最好都定义一个无参构造方法。
  7. 当创建对象时,JVM 会自动根据调用的构造方法的参数形式,在类中匹配对应的构造方法,并在匹配成功后执行该构造方法。

🤔 对象创建完全是由构造方法实现的吗?

不是。构造方法是创建 Java 对象的重要途径,但对象的创建过程不仅仅依赖于构造方法。通过 new 关键字调用构造方法时,构造方法确实返回了该类的对象,但这个对象的创建是分步骤完成的。

  1. 分配对象空间
    • JVM 在堆内存中为对象分配空间。
    • 对象的成员变量被初始化为默认值(如 int 为 0,boolean 为 false,引用类型为 null)。
  2. 成员变量的显式初始化
    • 对象成员变量在类中定义的初始值(直接赋值或普通初始化代码块)被执行,覆盖默认值。
  3. 执行构造方法
    • 调用构造方法,进行进一步的初始化或逻辑处理,可覆盖前面显式初始化的值。
  4. 返回对象的地址
    • 构造方法执行完毕后,返回对象在堆中的地址,将其赋值给对应变量或用于匿名对象。

public class Main {
    public static void main(String[] args) {
        System.out.println("创建对象开始...");
        Student s1 = new Student();  // 创建第一个对象
        System.out.println("第一个对象创建完成,地址赋值给变量 s1");
        s1.show();
 
        System.out.println("\n创建第二个对象...");
        Student s2 = new Student();  // 创建第二个对象
        System.out.println("第二个对象创建完成,地址赋值给变量 s2");
        s2.show();
    }
}
 
class Student {
    // 静态成员变量(类变量)
    private static int schoolCode;  // 默认值 0
 
    // 静态初始化块(类加载时执行,只执行一次)
    static {
        schoolCode = 1001;
        System.out.println("静态初始化块执行: schoolCode = " + schoolCode);
    }
 
    // 成员变量(实例变量)
    private String name;  // 默认值 null
    private int age;      // 默认值 0
 
    // 普通初始化代码块(对象创建时执行,每个对象都会执行)
    {
        name = "小明";
        age = 18;
        System.out.println("普通初始化代码块执行: name = " + name + ", age = " + age);
    }
 
    // 构造方法(对象创建时执行)
    public Student() {
        name = "小红";  // 构造方法覆盖普通初始化代码块
        age = 20;
        System.out.println("构造方法执行: name = " + name + ", age = " + age);
    }
 
    public void show() {
        System.out.println("最终对象状态: name = " + name + ", age = " + age + ", schoolCode = " + schoolCode);
    }
}
 

构造方法和成员方法的区别

  1. 定义格式区别
    • 构造方法:方法名必须与类名相同,且不需要定义返回值类型。
    • 成员方法:方法名需符合标识符规范,必须定义返回值类型。
  2. 调用时期区别
    • 构造方法:在实例化对象时自动调用。
    • 成员方法:在对象创建成功之后,可以多次调用。
  3. 调用方式区别
    • 构造方法:通过 new 关键字来调用。
    • 成员方法:通过对象来调用。
  4. 调用次数区别
    • 构造方法:每个对象只能调用一次,且仅在对象创建时调用。
    • 成员方法:可以在对象生命周期内任意调用多次。

💡注意:

  • 在构造方法中,你可以调用类中的成员方法,因为构造方法是在对象实例化过程中执行的,它可以访问类中的所有成员变量和成员方法。
  • 成员方法无法直接调用构造方法。例如,不能通过 this()super() 来调用构造方法。构造方法只会在对象创建时被调用一次,而成员方法是对象创建之后可以多次调用的。

this关键字

  1. 当一个对象成功创建后,虚拟机(JVM)会动态分配一个引用,指向当前对象,这个引用叫做 this
  2. 在成员方法中this 指向调用当前方法的对象,也就是调用该方法的实例。例如,在成员方法中,this 引用的是当前调用该方法的对象。
  3. 在构造方法中this 指向正在被创建的那个新对象。也就是说,this 在构造方法中代表当前正在初始化的对象。

this关键字的主要应用

  1. this 调用本类中的成员变量
    • 在局部变量和成员变量同名的情况下,成员变量会被局部变量屏蔽。为了区分局部变量和成员变量,我们可以使用 this.成员变量名 来引用成员变量。
    • 如果没有同名的局部变量,则可以直接使用成员变量名,编译器会自动帮我们加上 this
  2. this 调用本类中的成员方法
    • 访问本类中的成员方法时,可以使用 this.成员方法() 来调用成员方法。省略 this 关键字也可以直接调用,编译器会自动帮我们加上 this
  3. this 调用本类中的其它构造方法
    • 在一个类的构造方法内部,可以使用 this(参数列表) 来调用其它构造方法,这样可以减少代码重复,提高代码的可维护性。
    • this(参数列表) 只能用在构造方法中,并且必须是构造方法中的第一行语句。
    • 在编译时,当使用this(参数列表)调用构造方法时,编译器会根据参数的数量和类型(参数签名)从重载的构造方法中选择匹配的构造方法。
    • 构造方法不可递归调用,否则会导致死循环

static关键字

  1. 在类中(方法之外),使用 static 修饰的变量称为静态变量或类变量,使用 static 修饰的方法称为静态方法或类方法
  2. static 关键字不能用于修饰局部变量,局部变量的生命周期是在方法调用时创建的,不能与类的生命周期绑定。
  3. 静态变量优先于对象存在,随着类的加载,静态变量就已经存在了,而不需要创建对象。静态方法也优先于对象存在,随着类的加载,静态方法就可以被调用。
  4. 一个类中,静态变量只有一份。静态变量是属于类的,而不是某个特定的对象,因此它是所有对象共享的。
  5. 静态变量可以通过 类名.静态变量对象.静态变量 来访问,静态方法可以通过 类名.静态方法()对象.静态方法() 来调用。
  6. 类中声明的静态变量存储在方法区中,而非堆内存。它是类加载后在内存中就存在的,且所有对象共享这份数据。

静态变量和成员变量的区别

  1. 生命周期不同
    • 成员变量:随着对象的创建而创建,随着对象的回收而释放。每个对象都有一份独立的成员变量。
    • 静态变量:随着类的第一次加载而存在,类消失时(类卸载时)才会释放。静态变量在类的整个生命周期内只有一份,所有对象共享这份数据。
  2. 调用方式不同
    • 成员变量:只能通过对象来访问。你需要先创建类的实例对象,再通过对象来访问成员变量。
    • 静态变量:可以通过类名直接调用,也可以通过对象来访问。尽管可以通过对象访问静态变量,但通常推荐使用类名来访问。
  3. 数据存储位置不同
    • 成员变量:存储在堆内存中。每个对象在创建时都会在堆内存中分配一份独立的成员变量,彼此之间互不影响。
    • 静态变量:存储在方法区(共享数据区)中。静态变量属于类本身,而不是某个特定的对象,因此它存储在方法区中,所有对象共享这一份数据。
  4. 创建次数不同
    • 成员变量:每次创建一个对象时,成员变量都会被创建。因此,每个对象都有自己独立的成员变量。
    • 静态变量:静态变量在类加载时只会创建一次,之后无论创建多少个对象,静态变量都不会再被创建。所有对象共享这一份静态变量。

静态方法和成员方法的区别

  1. 访问变量区别

    • 成员方法:可以访问成员变量静态变量,因为成员方法是由对象调用的,调用时对象已经被初始化,可以访问所有实例数据和类数据。
    • 静态方法:只能访问静态变量,不能直接访问成员变量。因为静态方法在类加载时就存在,不依赖于任何对象的实例化,而成员变量是对象的特有属性,静态方法在没有对象实例的情况下不能访问它。
  2. 访问方法区别

    • 成员方法:可以访问成员方法静态方法。成员方法可以通过对象访问,也可以访问类的静态方法,因为它已经被对象初始化,可以直接访问类的成员和静态内容。
    • 静态方法:只能访问静态方法,不能直接访问成员方法。静态方法在没有对象的情况下就存在,因此不能调用依赖于对象实例的成员方法。
  3. 访问this区别

    • 成员方法:可以使用 thisthis 引用当前对象的实例。成员方法是由对象实例调用的,因此 this 是有效的,指向调用该方法的对象。

      public class Main {
          public static void main(String[] args) {
              // 使用无参构造创建对象
              Person p1 = new Person();
              p1.show();
       
              // 使用有参构造创建对象
              Person p2 = new Person("Alice", 25);
              p2.show();
          }
      }
       
      class Person {
          private String name;
          private int age;
       
          // 构造方法演示 this
          public Person() {
              // 调用另一个构造方法
              this("张三", 0);
              System.out.println("无参构造执行");
          }
       
          public Person(String name, int age) {
              // this 指向当前对象
              this.name = name;
              this.age = age;
              System.out.println("双参数构造执行: " + this.name + ", " + this.age);
          }
       
          // 成员方法演示 this
          public void show() {
              // this 指向调用该方法的对象
              System.out.println("成员方法输出 name = " + this.name + ", age = " + this.age);
          }
      }
    • 静态方法:不能使用 this,因为静态方法不依赖于对象实例,而是属于类本身。this 是一个指向当前对象实例的引用,而静态方法在类加载时就存在,它没有与对象绑定,因此无法使用 this

      public class Main {
          public static void main(String[] args) {
              // 调用静态方法
              Demo.show();
          }
      }
       
      class Demo {
          private static int count = 5;
       
          // 静态方法演示
          public static void show() {
              // ❌ 编译错误,静态方法没有 this
              // System.out.println(this.count);
       
              // ✅ 正确访问静态变量
              System.out.println("静态方法输出 count = " + count);
          }
      }
       

代码块

静态代码块

静态代码块是在类中使用 static 关键字修饰的代码块,主要用于给类进行初始化操作。它在类加载时自动执行,并且只会执行一次

静态代码块的特点

  1. 位置限制:静态代码块只能定义在类里面,独立于任何方法,不能定义在方法里面。
  2. 多个静态代码块:一个类中可以定义多个静态代码块,它们的执行顺序与定义顺序一致。
  3. 访问限制:静态代码块中的变量都是局部变量,只能访问类的静态变量,不能访问成员变量。
  4. 执行时机:静态代码块会在类被加载时自动执行,并且只执行一次。

静态代码块的作用

  1. 初始化静态变量:静态代码块常用于给静态变量赋初始值,实际开发中比较常用,一般用于执行一些全局性初始化操作。
  2. 执行其他初始化任务,例如:创建工厂、加载数据库初始信息、加载配置文件等。

构造代码块

构造代码块(非静态代码块)是没有用 static 修饰的代码块,主要用于给所有对象进行初始化操作。它在每次创建对象时都会执行

构造代码块的特点

  1. 位置限制:构造代码块只能定义在类里面,独立于任何方法,不能定义在方法里面。
  2. 多个构造代码块:一个类中可以定义多个构造代码块,它们的执行顺序与定义顺序一致。
  3. 访问限制:构造代码块中的变量都是局部变量,可以访问静态变量和实例变量。
  4. 执行时机:构造代码块随着对象的创建而加载,每创建一个对象就执行一次。

构造代码块的作用

  1. 提取公共代码:可以将多个构造方法中重复的代码提取到构造代码块中,避免重复编写。
  2. 匿名内部类初始化匿名内部类不能提供构造方法,因此可以将初始化操作放在构造代码块中

代码执行顺序

在类加载时,静态代码块会先于非静态代码块执行,接着执行构造方法。

  1. 静态代码块(静态代码块中初始化类的静态资源)
  2. 非静态代码块(构造代码块,进行实例化前的准备工作)
  3. 构造方法(完成对象的创建和初始化)
class Demo {
    private static int staticValue;
    private int instanceValue;
 
    // 静态代码块
    static {
        staticValue = 100;
        System.out.println("静态代码块执行,staticValue = " + staticValue);
    }
 
    // 构造代码块
    {
        instanceValue = 50;
        System.out.println("构造代码块执行,instanceValue = " + instanceValue);
    }
 
    // 构造方法
    public Demo() {
        System.out.println("构造方法执行");
    }
 
    public Demo(int val) {
        this.instanceValue = val;
        System.out.println("带参数构造方法执行,instanceValue = " + instanceValue);
    }
}
 
public class Main {
    public static void main(String[] args) {
        System.out.println("创建第一个对象...");
        Demo d1 = new Demo();
 
        System.out.println("\n创建第二个对象...");
        Demo d2 = new Demo(200);
    }
}

package包

包(Package) 是 Java 中用于组织类和接口的机制,它提供了多层命名空间,使得相同名字的类可以共存并且不产生冲突。包机制有助于代码的组织、管理和命名空间的避免冲突。

包的作用

  1. 避免类名冲突:通过包机制,可以避免多个类拥有相同名称的冲突。特别是当项目庞大时,包可以有效避免命名上的冲突。
  2. 分类管理类:包不仅有助于类的命名空间管理,还有助于代码的组织与模块化。在大规模的项目中,包可以帮助将相关的类进行分组,方便代码的维护和管理。
  3. 权限控制:包还起到了访问控制的作用。Java 中不同的访问控制符(如 publicprotectedprivate)和包的配合可以限制类的访问范围。

包的命名规则

  1. 公司域名反写:Java中包名的一般规范是使用公司的域名反写,如 com.example,这是为了避免与其他公司或开发者的包名发生冲突。

  2. 模块名和功能名:在公司域名反写之后,可以通过添加模块名、功能名等进一步区分包的层次。比如 com.example.project.module

  3. 小写字母:包名应全部小写,以保持一致性和避免与类名(大驼峰命名法)冲突。多个词之间可以使用点(.)分隔。

  4. 多层包结构:可以根据需要创建多层嵌套的包结构。比如,com.example.project.model 表示 project 包下的 model 子包。

使用包中的类

  1. 同包类:如果要使用一个类,并且这个类与当前程序在同一个包中(即同一个文件夹中),可以直接使用该类名,不需要显式导入包。
  2. 其他包中的类:如果类不在当前包中,则需要通过 import 语句导入类。这样就可以在当前类中直接使用其他包中的类。
  3. java.lang 包:Java的 java.lang 包是默认导入的包,因此可以直接使用其中的类(如 StringSystemMath 等)而不需要显式导入。

静态导入(Static Import)

从 JDK 1.5 开始,Java 引入了 静态导入(static import)功能。静态导入允许直接导入某个类的静态变量或静态方法,使得可以直接使用静态成员而不需要通过类名访问。


import static java.lang.Math.*; // 静态导入 Math 类的所有静态成员
 
public class Main {
    public static void main(String[] args) {
        // 直接使用 Math 类的静态方法,无需前缀 Math.
        double num = 16;
 
        double squareRoot = sqrt(num);       // Math.sqrt()
        double power = pow(num, 2);          // Math.pow()
        double maxVal = max(10, 20);         // Math.max()
        double randomVal = random();          // Math.random()
 
        System.out.println("平方根: " + squareRoot);
        System.out.println("平方: " + power);
        System.out.println("最大值: " + maxVal);
        System.out.println("随机数: " + randomVal);
    }
}
 

相关文章

评论区