Java基础04-面向对象

概述

面向过程&面向对象

面向对象(Object-Oriented,简称OOP)和面向过程(Procedure-Oriented)是两种不同的编程范式,它们反映了程序设计的不同思维方式和组织代码的方法。

面向对象(Object-Oriented,OOP)

面向对象是一种编程范式,它将程序中的数据和对数据的操作封装到对象中。一个对象是一个包含数据和方法的实体,这些方法定义了对象的行为。面向对象的编程思想基于以下几个主要概念:

概念 解释
封装(Encapsulation) 将数据和操作封装到对象中,对外部隐藏对象的内部实现细节。
继承(Inheritance) 允许一个类(子类)继承另一个类(父类)的属性和方法,以及扩展或修改它们。
多态(Polymorphism) 允许使用一个接口来表示多个不同的对象,提高代码的灵活性和可复用性。
抽象(Abstraction) 提取共性的特征,形成类或接口,简化复杂系统的设计。

在面向对象的程序设计中,程序被组织为对象的集合,对象之间通过消息传递进行交互。Java、C++、Python等是支持面向对象编程的语言。

面向过程(Procedure-Oriented)

面向过程是一种以过程为中心的编程范式,程序被组织为一系列的函数或过程,这些函数描述了系统中的各个步骤。在面向过程的编程中,数据和函数是分离的,函数通过参数传递数据。

主要特点包括:

特点 解释
过程 程序主要由一系列函数或过程组成,每个函数执行特定的任务。
数据与行为分离 数据和函数是独立的,通过参数传递数据。
可维护性差 随着程序规模的增大,维护和扩展变得更加困难。

C语言是一种典型的面向过程的编程语言。

比较

  • 封装性: 面向对象更强调封装,将数据和操作封装到对象中;面向过程将数据和函数分离,通过参数传递数据。

  • 可维护性: 面向对象的代码通常更易于维护和扩展,因为对象提供了一种组织代码的自然方式;面向过程的代码可能随着程序规模增大而变得难以维护。

  • 复用性: 面向对象提倡通过继承和组合实现代码的复用;面向过程主要通过函数的重用来实现。

类与对象

名词解释

  • 在Java中,类(Class)和对象(Object)是面向对象编程的基本概念,它们是构建程序的主要组成部分。

类(Class)

  • 类是一种抽象的数据类型,用于描述具有相同属性和行为的对象集合。类定义了对象的属性(成员变量)和行为(方法)。类可以看作是对象的模板或蓝图,它定义了对象应该有哪些特征和行为。类定义了对象的结构和行为,但它本身并不占用内存,只有当创建对象时,内存中才会分配空间用于存储对象的实际数据。

对象(Object)

  • 对象是类的实例,是类在内存中的具体表现。 创建一个对象意味着根据类的定义分配内存,并初始化对象的属性。对象是类的具体实体,它可以调用类中定义的方法,访问类中定义的属性。

成员变量&局部变量

成员变量(实例变量)

  1. 定义:

    • 成员变量是定义在类中,方法外的变量。
    • 它们属于对象的一部分,每个对象都有自己的一份成员变量副本。
  2. 作用域:

    • 成员变量的作用域是整个类,可以在类的任何方法、构造方法或块中使用。
  3. 初始化:

    • 成员变量可以手动初始化,如果没有手动初始化,它们将有默认值(如数值型变量默认为0,布尔型变量默认为false,引用类型默认为null)。
  4. 生命周期:

    • 成员变量的生命周期与对象的生命周期相同。它们在对象创建时被分配内存,在对象被销毁时释放内存。
  5. 访问修饰符:

    • 成员变量可以使用访问修饰符,如publicprivateprotected,以控制对它们的访问权限。
public class MyClass {
    // 成员变量
    int memberVariable;  // 默认初始化为0
    private String privateVariable;  // 私有成员变量,需要通过方法进行访问

    // 构造方法
    public MyClass(int value, String str) {
        memberVariable = value;
        privateVariable = str;
    }
}

局部变量

  1. 定义:

    • 局部变量是在方法、构造方法或块中声明的变量。
    • 它们属于方法的一部分,只在方法内可见。
  2. 作用域:

    • 局部变量的作用域限定在声明它们的方法、构造方法或块内。
  3. 初始化:

    • 局部变量必须手动初始化,没有默认值。
  4. 生命周期:

    • 局部变量的生命周期仅在方法被调用时存在,并在方法执行完毕后销毁。
  5. 访问修饰符:

    • 局部变量不能使用访问修饰符,因为它们的作用域是在方法内。
public class MyClass {
    // 方法示例
    public void myMethod() {
        // 局部变量
        int localVar = 10;  // 必须手动初始化

        // 其他代码
    }
}

构造方法

  1. 定义:

    • 构造方法是一种特殊类型的方法,与类同名,没有返回类型,用于在创建对象时进行初始化。
    • 当使用 new 关键字实例化一个对象时,构造方法被调用。
  2. 命名规则:

    • 构造方法的名称必须与类名完全相同。
  3. 特点:

    • 构造方法在对象创建时自动调用,不能手动调用。
    • 一个类可以有多个构造方法,根据参数的不同进行重载。
  4. 默认构造方法:

    • 如果在类中没有显式定义构造方法,Java 将提供一个默认的无参数构造方法。
    • 如果你自定义了构造方法,Java 不再提供默认构造方法。
  5. 初始化对象:

    • 构造方法用于初始化对象的状态,为对象的属性赋初值。
public class MyClass {
    // 无参数构造方法(默认构造方法)
    public MyClass() {
        // 初始化代码
    }

    // 带参数的构造方法
    public MyClass(int value, String name) {
        // 使用参数初始化对象的属性
        this.someInt = value;
        this.someString = name;
    }

    // 类的其他成员和属性
    private int someInt;
    private String someString;
}

使用构造方法

  1. 实例化对象:
    • 构造方法通过 new 关键字来实例化对象。
MyClass myObject = new MyClass();  // 调用无参数构造方法
  1. 重载构造方法:
    • 通过提供不同的参数列表,可以创建多个构造方法。
MyClass obj1 = new MyClass();          // 调用无参数构造方法
MyClass obj2 = new MyClass(42, "John"); // 调用带参数构造方法

类和对象的定义格式

类的定义格式

public class ClassName {
    // 成员变量(属性)
    type variable1;
    type variable2;
    // ...

    // 构造方法
    public ClassName() {
        // 构造方法的初始化代码
    }

    // 成员方法(行为)
    public returnType methodName1(parameter1Type parameter1, parameter2Type parameter2, ...) {
        // 方法的实现代码
        // 可以使用成员变量和参数来完成特定的任务
        return someValue; // 如果有返回值的话
    }

    // 其他成员方法和属性
    // ...
}

// 定义学生类
public class Student {
    // 成员变量
    private String name;
    private int age;

    // 构造方法
    public Student(String studentName, int studentAge) {
        name = studentName;
        age = studentAge;
    }

    // 成员方法
    public void displayStudentInfo() {
        System.out.println("Student Name: " + name);
        System.out.println("Student Age: " + age);
    }
}

在这个模板中,你需要将 ClassName 替换为你所创建类的名称,并定义类中的成员变量(属性)和成员方法(行为)。构造方法用于初始化对象,在创建对象时执行。

对象的创建和使用格式

public class Main {
    public static void main(String[] args) {
        // 创建类的对象
        ClassName objectName = new ClassName();

        // 使用对象的成员变量和方法
        objectName.variable1 = someValue;
        objectName.methodName1(parameter1, parameter2, ...);
    }
}

// 调用示例
public class Main {
    public static void main(String[] args) {
        // 创建学生对象并初始化
        Student student1 = new Student("John Doe", 20);

        // 访问对象的成员变量
        System.out.println("Before setting new values:");
        student1.displayStudentInfo();

        // 设置新的属性值
        student1.name = "Jane Smith";
        student1.age = 22;

        // 访问对象的成员方法
        System.out.println("\nAfter setting new values:");
        student1.displayStudentInfo();
    }
}

当你创建一个类的对象后,你可以使用点操作符 . 来访问该对象的属性和方法。下面是如何访问类的属性和方法的一些基本示例:

使用类的属性和方法

访问类的属性

假设你有一个Student类,有一个name属性:

// 创建一个Student对象
Student student = new Student();

// 设置属性值
student.name = "John Doe";

// 获取属性值
String studentName = student.name;
System.out.println("Student Name: " + studentName);

在这个例子中,我们通过 student.name 来设置和获取 Student 对象的 name 属性。

访问类的方法

同样,你可以使用点操作符来调用类的方法:

// 创建一个Student对象
Student student = new Student();

// 调用方法
student.displayStudentInfo();

在这个例子中,我们调用了 displayStudentInfo 方法,前提是 displayStudentInfoStudent 类的一个公共方法

请注意,如果类的属性被声明为
private,你不能直接使用点操作符访问它们。通常,你会提供公共的getter和setter方法来访问和修改私有属性

public class Student {
    private String name;

    // getter方法
    public String getName() {
        return name;
    }

    // setter方法
    public void setName(String newName) {
        name = newName;
    }
}

// 在主程序中使用getter和setter
Student student = new Student();
student.setName("John Doe");
String studentName = student.getName();
System.out.println("Student Name: " + studentName);

在这个例子中,通过 getNamesetName 方法来获取和设置 name 属性的值,因为 name 被声明为 private

这是一种封装的做法,使得类的内部实现对外部不可见,同时提供了安全的方式来访问属性。

面向对象内存分析

在Java中,内存分为栈内存(Stack Memory)和堆内存(Heap Memory),而对象的内存分配涉及到这两种类型的内存。

1. 栈内存(Stack Memory)

  • 存储内容: 栈内存主要用于存储局部变量和方法调用
  • 生命周期: 局部变量在方法调用时创建,方法结束时销毁。
  • 特点: 存取速度较快,生命周期短暂,对于基本数据类型和对象引用存储在栈内存中。

在对象内存分析中,栈内存主要用于存储对象的引用变量,而不是对象本身。当你创建一个对象时,栈内存中的引用变量会指向堆内存中对象的实际位置。

完成类的定义后,便在栈内存中保存

2. 堆内存(Heap Memory)

  • 存储内容: 堆内存用于存储对象的实例和数组
  • 生命周期: 对象的生命周期通常比方法调用的生命周期长,由垃圾回收来管理对象的释放。
  • 特点: 存取速度相对较慢,但能存储大量的数据,生命周期相对较长。

当你使用 new 关键字创建对象时,对象的实例会被分配到堆内存中。

堆内存的好处是它的空间较大,可以容纳大量的对象实例。对象在堆内存中的分配和释放是由Java的垃圾回收器(Garbage Collector)负责的。

对象内存分配过程

  1. 栈内存中的引用: 在栈内存中,会创建一个对象引用变量,指向堆内存中的实际对象。

  2. 堆内存中的对象实例: 使用 new 关键字在堆内存中分配内存空间,创建对象的实例。

下面是一个简单的示例:

public class ObjectMemoryAnalysis {
    public static void main(String[] args) {
        // 在栈内存中创建引用变量
        MyClass objRef;

        // 在堆内存中创建对象实例
        objRef = new MyClass();
    }
}

class MyClass {
    // 类的成员变量和方法
}

在这个例子中,objRef 是在栈内存中创建的对象引用变量,而 new MyClass() 则在堆内存中创建了 MyClass 类的对象实例。

关键字

static关键字

当在Java中使用static关键字时,它通常用于创建静态成员,这些成员与类相关,而不是与类的实例相关。以下是static关键字在不同上下文中的用法:

  1. 静态变量(静态字段):

    • 静态变量属于类,而不是属于类的实例。它们在类加载时初始化,并且在整个程序运行期间保持不变。
    • 静态变量通过类名访问,而不是通过对象的实例。
    • 例如:
      public class Example {
          public static int staticVariable = 10;
      }
      
      在其他类中,可以通过Example.staticVariable来访问这个静态变量。
  2. 静态方法:

    • 静态方法也属于类,而不是属于实例。它们不能直接访问类的非静态成员,因为它们在没有类实例的情况下执行。
    • 静态方法通过类名调用,而不是通过类的实例。
    • 例如:
      public class Example {
          public static void staticMethod() {
              System.out.println("This is a static method.");
          }
      }
      
      在其他类中,可以通过Example.staticMethod()来调用这个静态方法。
  3. 静态块:

    • 静态块是用static关键字标记的代码块,它在类被加载时执行,且只执行一次。
    • 通常用于执行一些静态初始化操作。
    • 例如:
      public class Example {
          static {
              System.out.println("This is a static block.");
          }
      }
      
      静态块中的代码在类加载时执行。

使用static关键字可以帮助在不创建类实例的情况下访问和调用类级别的成员。但请注意,过度使用静态成员可能导致代码耦合性增加,降低了代码的可测试性和可维护性。因此,在使用static关键字时,需要谨慎考虑其影响。

在Java中,静态变量是属于类的变量,而不是实例的变量。这意味着无论创建了多少个类的实例,静态变量都只有一份拷贝,它被所有实例共享。

静态变量是可以被修改的,但它们的修改是作用于整个类,而不是某个特定的实例。当你修改一个静态变量时,这个变化对于所有类的实例都是可见的。因此,你可以说修改静态变量是修改了类的状态。

下面是一个简单的例子:

public class MyClass {
    // 静态变量
    static int staticVariable = 10;
}
public class Class {
    public static void main(String[] args) {
        System.out.println(MyClass.staticVariable);
    }
}
// 输出 10

在上面的例子中,修改了静态变量 staticVariable 的值,这个修改对所有实例都产生了影响。希望这能帮助你理解静态变量的性质。如果有其他问题,随时问我!

this关键字:

  1. 引用当前对象:
    • 在Java中,this 关键字代表当前对象的引用。
    • 当你在类的方法内部引用一个成员变量或方法时,使用 this 可以明确指示这是属于当前对象的。
public class MyClass {
    private int myValue;

    // 构造方法
    public MyClass(int myValue) {
        this.myValue = myValue;  // 使用this引用当前对象的成员变量
    }

    // 方法
    public void setMyValue(int myValue) {
        this.myValue = myValue;  // 使用this引用当前对象的成员变量
    }
}
  1. 区分参数和成员变量:
    • 在构造方法或方法中,当参数的名称与成员变量的名称相同时,使用 this 可以明确指出要操作的是成员变量而不是参数。
public class MyClass {
    private int value;

    // 构造方法
    public MyClass(int value) {
        this.value = value;  // 使用this明确指出是成员变量
    }

    // 方法
    public void setValue(int value) {
        this.value = value;  // 使用this明确指出是成员变量
    }
}
  1. 在构造方法中调用另一个构造方法:
    • 通过 this 关键字,可以在一个构造方法中调用同一类的另一个构造方法。
    • 这种方式被称为构造方法的重载。
public class MyClass {
    private int defaultValue;

    // 无参数构造方法
    public MyClass() {
        this(42);  // 调用带参数的构造方法,传递默认值
    }

    // 带参数的构造方法
    public MyClass(int value) {
        this.defaultValue = value;
    }
}

总体而言,this 关键字在Java中用于引用当前对象,解决同名问题,以及在构造方法中调用其他构造方法。

匿名对象

在Java中,匿名对象是指没有明确赋予引用变量的对象。这样的对象通常在创建和使用的地方结合在一起,不分配给变量。匿名对象通常用于一次性的场景,无需重复使用对象,节省了命名对象的步骤。

以下是一个简单的例子,演示了匿名对象的概念:

public class Example {
    public static void main(String[] args) {
        // 创建匿名对象
        new MyClass().displayMessage();

        // 匿名对象作为方法参数
        printMessage(new AnotherClass());
    }

    static void printMessage(AnotherClass obj) {
        obj.display();
    }
}

class MyClass {
    void displayMessage() {
        System.out.println("Hello from MyClass");
    }
}

class AnotherClass {
    void display() {
        System.out.println("Hello from AnotherClass");
    }
}

在上面的例子中,new MyClass().displayMessage() 创建了一个匿名对象,调用了 MyClass 类的 displayMessage 方法。这个对象没有被分配给变量,而是直接在创建和使用的地方组合在一起。

同样,printMessage(new AnotherClass()) 也使用了匿名对象,将一个新创建的 AnotherClass 对象作为参数传递给 printMessage 方法。

使用匿名对象可以使代码更紧凑,特别是在某些简单的场景下,无需为对象分配变量。然而,在需要多次使用同一对象或需要在后续代码中引用对象时,通常更好地使用具名对象。

面向对象性质

封装性

封装就是隐藏实现细节,仅对外提供访问接口。

封装的好处

  1. 模块化:
    封装有助于将代码模块化,使得每个模块(类、方法、组件等)都有特定的功能,易于理解和维护。这种模块化的设计有助于团队协作和代码的组织。

  2. 信息隐藏:
    封装允许你隐藏类的内部实现细节,只暴露给外部需要知道的接口。这样可以减少对其他部分的代码产生影响,同时提高了代码的安全性。

  3. 代码复用:
    封装促使你设计可重用的代码。通过将相关的功能集中到一个模块中,可以在其他部分的代码中重复使用该模块,提高了代码的可维护性和可扩展性。

  4. 插件化易于调试:
    封装使得系统的不同部分可以独立开发和测试,这样在调试过程中可以更容易定位和解决问题。每个模块都可以被单独测试,而不必关心整个系统的复杂性。

  5. 安全性:
    通过隐藏内部实现,封装可以提高代码的安全性。外部代码只能通过提供的接口来访问对象,防止直接对对象的状态进行不当修改。

封装的缺点

  1. 影响执行效率:
    封装可能引入一些性能开销,因为在访问对象的属性或调用方法时需要经过额外的层级。但在大多数情况下,这个性能损耗相对较小,而封装带来的优势通常远远大于这个缺点。

代码块

在Java中,代码块是一组被大括号 {} 包围的语句。有几种不同类型的代码块,包括普通代码块、构造代码块、静态代码块和同步代码块。

  1. 普通代码块:
    普通代码块是一组在方法中或其他代码块内部定义的普通语句。这些代码块不属于任何特殊类型,只是用于在某个特定范围内定义局部变量或执行一组语句。

    public class Example {
        public void method() {
            // 普通代码块
            {
                int x = 10;
                System.out.println(x);
            }
        }
    }
    
  2. 构造代码块:
    构造代码块是在类中定义的,不带任何修饰符,也不带参数。它在每次创建对象时都会被调用,位于所有构造方法的最前面。

    public class Example {
        // 构造代码块
        {
            System.out.println("构造代码块");
        }
    
        // 构造方法
        public Example() {
            System.out.println("构造方法");
        }
    
        public static void main(String[] args) {
            Example obj = new Example();
        }
    }
    
  3. 静态代码块:
    静态代码块使用 static 关键字,它在类加载时执行,仅执行一次。通常用于初始化静态变量或执行一些静态的初始化操作。

    public class Example {
        // 静态代码块
        static {
            System.out.println("静态代码块");
        }
    
        public static void main(String[] args) {
            System.out.println("Main 方法");
        }
    }
    
  4. 同步代码块:
    同步代码块使用 synchronized 关键字,用于确保多个线程不会同时执行该代码块。它需要一个对象作为锁。

    public class Example {
        // 同步方法
        public synchronized void syncMethod() {
            // 同步代码块
            synchronized (this) {
                // 执行需要同步的操作
            }
        }
    }
    

单例设计模式

单例设计模式确保一个类仅有一个实例,并提供一个访问它的全局访问点。这在某些情况下非常有用,例如控制对资源的访问,共享某个对象等。在Java中,单例模式的实现通常包括以下步骤:

通用步骤

  1. 构造方法私有化: 防止外部通过构造方法创建多个实例。
  2. 声明一个本类对象: 在类中声明一个私有静态成员变量,用于存储该类的唯一实例。
  3. 给外部提供一个静态方法获取对象实例: 提供一个公有的静态方法,允许外部代码获取该类的唯一实例。

两种实现方式

1. 懒汉式(Lazy Initialization)

懒汉式是指在需要时才创建对象实例,避免了在应用启动时就创建对象,节省了资源。

public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton() {}

    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

在懒汉式中,通过判断实例是否为null,如果是,则创建一个实例,否则返回已存在的实例。

2. 饿汉式(Eager Initialization)

饿汉式是指在类加载的时候就创建好对象实例,因此在应用启动时就会占用系统资源。

public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();

    private EagerSingleton() {}

    public static EagerSingleton getInstance() {
        return instance;
    }
}

在饿汉式中,通过在声明时直接初始化实例,保证了在类加载时就已经创建了唯一的实例。

注意事项

  • 懒汉式线程安全问题: 在懒汉式中,如果多个线程同时进入getInstance方法,可能会创建多个实例。可以通过在方法上加锁或使用双重检查锁定(Double-Checked Locking)来解决这个问题。
  • 饿汉式的资源占用: 饿汉式在应用启动时就创建实例,可能会占用一些资源。在某些情况下,可能不是最优选择。

示例

public class Test8 {
    public static void main(String[] args) {
        // 懒汉式
        Singleton1 s = Singleton1.getInstance();
        Singleton1 s1 = Singleton1.getInstance();
        s.print();
        s1.print();
        System.out.println(s == s1);
    }
}

class Singleton1 {
    private Singleton1() {
    }

    private static Singleton1 instance = new Singleton1();

    public static Singleton1 getInstance() {
        return instance;
    }

    public void print() {
        System.out.println("测试方法");
    }
}

测试方法
测试方法
true

单例设计模式的好处

单例设计模式的好处主要体现在以下几个方面:

  1. 节省资源: 单例模式确保一个类只有一个实例,避免了重复创建对象的内存浪费。在一些频繁被使用的对象中,通过单例模式可以节省系统资源,提高性能。

  2. 全局访问点: 单例模式提供了一个全局访问点,使得整个系统都可以方便地访问这个单一实例。这对于需要共享某个资源或状态的场景非常有用。

  3. 避免冲突: 单例模式可以避免多个实例之间的冲突。在某些情况下,多个实例可能导致状态不一致或冲突,单例模式通过保证只有一个实例存在,解决了这个问题。

  4. 延迟实例化: 在懒汉式的单例模式中,对象在需要时才被创建,实现了延迟实例化。这对于一些资源较为庞大的对象或需要耗费较多时间初始化的对象来说,可以提高系统启动速度。

关于使用构造方法私有化+静态方法替代单例,这是实现单例的一种常见方式,通常被称为静态工厂方法。这种方式确实可以实现全局访问点和节省资源的目的,但在实际应用中,还是推荐使用经典的单例设计模式,因为它更清晰、更符合单一职责原则。通过将单例的创建和获取集中在一个类中,更容易维护和理解代码。

你可能感兴趣的:(#,Java基础,java)