AI智能应用开发(Java)从起点到终点-面向对象

0 阅读10分钟

 ​编辑

面向对象基础

面向对象介绍

​编辑

自定义对象

Java中自定义对象的必要性

就像我们之前用的Scanner 和Random 都是java里面已经写好的对象,直接拿来用就好了,不用再自己写一大串代码来实现键盘录入和随机数的需求,但是有些需求是java中没有定义和写好的,,但实际开发中常遇到需要重复实现的特定功能。这个时候对象就派上用场了,我们可以把这些需求自定义对象,自定义对象可以封装这些功能,提升代码复用性和可维护性。

自定义对象的优势

封装重复逻辑 将频繁使用的代码逻辑(如数据验证、特定计算)封装为独立对象,避免代码冗余。

简化调用 通过定义清晰的方法名和属性,调用时只需关注输入输出,隐藏内部实现细节。

统一管理 修改逻辑时只需调整对象内部代码,所有调用点自动同步更新,减少维护成本。

实现自定义对象的步骤

定义类结构 使用class关键字声明类,明确其职责边界。例如处理日期格式的工具类:

public class DateFormatter {
    private static final String DEFAULT_PATTERN = "yyyy-MM-dd";
}

添加必要方法 根据需求设计公有方法。如实现日期字符串解析:

public LocalDate parse(String dateStr) throws DateTimeParseException {
    return LocalDate.parse(dateStr, DateTimeFormatter.ofPattern(DEFAULT_PATTERN));
}

考虑扩展性 通过构造函数或方法参数支持自定义配置。例如允许指定格式模式:

public String format(LocalDate date, String pattern) {
    return date.format(DateTimeFormatter.ofPattern(pattern));
}

实际应用示例

场景需求 多个模块需要生成指定长度的随机字符串。

自定义实现 创建RandomStringGenerator类:

public class RandomStringGenerator {
    private final String characterPool;
    
    public RandomStringGenerator(String characterPool) {
        this.characterPool = characterPool;
    }

    public String generate(int length) {
        StringBuilder sb = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < length; i++) {
            sb.append(characterPool.charAt(random.nextInt(characterPool.length())));
        }
        return sb.toString();
    }
}

调用方式

RandomStringGenerator generator = new RandomStringGenerator("ABCDEF123");
String randomCode = generator.generate(8); // 输出类似 "A1BF3E2D"

设计注意事项

单一职责原则 每个对象应只负责一个明确的功能领域,避免创建"万能工具类"。

适当访问控制 使用private保护内部状态,通过公有方法提供可控的访问途径。

文档注释 使用JavaDoc说明类用途和方法参数,增强代码可读性:

/**
 * 生成指定字符集范围内的随机字符串
 * @param characterPool 允许使用的字符集合
 */

通过合理设计自定义对象,可以有效组织代码结构,减少重复劳动,提升开发效率。

定义成一个对象,这样就可以反复使用了

变量与局部变量的区别

变量是程序中用于存储数据的标识符,可以在程序的多个部分访问和修改。局部变量是特定于某个函数或代码块的变量,只能在其定义的范围内使用。

变量的特点

变量具有全局或局部的生命周期,具体取决于其定义位置。全局变量在整个程序运行期间都存在,可以在任何函数或代码块中访问。局部变量仅在定义它们的函数或代码块执行期间存在。

局部变量的特点

局部变量的作用域仅限于定义它们的函数或代码块。一旦函数或代码块执行完毕,局部变量就会被销毁。局部变量有助于避免命名冲突和提高代码的模块化程度。

变量的作用域

全局变量的作用域涵盖整个程序,可以在任何地方访问。局部变量的作用域仅限于定义它们的函数或代码块。作用域规则决定了变量的可见性和生命周期。

局部变量的优势

使用局部变量可以减少内存占用,因为它们在函数执行完毕后会被释放。局部变量还能提高代码的可读性和可维护性,因为它们的生命周期和影响范围有限。

变量的内存管理

全局变量在程序启动时分配内存,直到程序结束才释放。局部变量在函数或代码块执行时分配内存,执行完毕后立即释放。合理使用局部变量有助于优化内存使用效率。

局部变量的使用场景

局部变量适用于临时存储数据或在函数内部进行计算。它们可以防止不同函数之间的数据干扰,确保每个函数的操作独立且安全。

变量的命名规范

无论是全局变量还是局部变量,都应遵循一致的命名规范。局部变量的命名应反映其用途和上下文,避免过于通用的名称。良好的命名习惯有助于代码的理解和维护。

构造方法

构造方法的概念

构造方法是一种特殊的方法,用于在创建对象时初始化对象的状态。它在对象实例化时自动调用,通常用于设置成员变量的初始值或执行必要的初始化操作。

构造方法的特点

  • 方法名必须与类名完全相同。
  • 没有返回类型(包括 void)。
  • 支持重载,即一个类可以有多个参数不同的构造方法。
  • 若未显式定义构造方法,编译器会提供一个默认的无参构造方法;若已定义,则不再提供默认构造方法。

构造方法的语法

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

    // 带参构造方法  
    public ClassName(type param1, type param2) {  
        // 使用参数初始化成员变量  
    }  
}  

构造方法的使用示例

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

    // 无参构造方法  
    public Person() {  
        name = "Unknown";  
        age = 0;  
    }  

    // 带参构造方法  
    public Person(String name, int age) {  
        this.name = name;  
        this.age = age;  
    }  

    public void display() {  
        System.out.println("Name: " + name + ", Age: " + age);  
    }  
}  

// 使用构造方法创建对象  
Person person1 = new Person();        // 调用无参构造方法  
Person person2 = new Person("Alice", 25); // 调用带参构造方法  

构造方法的注意事项

  • 若类中定义了带参构造方法,通常需要显式定义无参构造方法,否则无法直接通过 new ClassName() 实例化对象。
  • 构造方法可以通过 this() 调用同类中的其他构造方法,但必须作为方法的首条语句。
  • 构造方法不能被继承,子类需通过 super() 调用父类的构造方法。

面向对象高级

面向对象的三大特性:封装,继承,多态

封装(合理隐藏,合理暴露)

封装是将数据(属性)和操作数据的方法(行为)捆绑在一起,形成一个独立的单元(类)。通过访问修饰符(如 privateprotectedpublic)控制外部对内部细节的访问,隐藏实现细节,仅暴露必要接口。

  • 优点:提高代码安全性,减少耦合,便于维护。

  • 示例

    class BankAccount {
        private double balance; // 私有属性,外部无法直接访问
        
        public void deposit(double amount) { // 公开方法
            if (amount > 0) balance += amount;
        }
        public double getBalance() { return balance; }
    }
    

继承

继承允许子类复用父类的属性和方法,并可通过扩展或重写实现功能增强。体现“is-a”关系(如 Dog 继承 Animal)。

在继承体系中,子类可以继承到父类的方法 但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改 这就需要采用方法的重写,方法重写又称方法覆盖 子类重写父类方法,需要保证方法声明完全一致(方法名,参数,返回值类型需要保持一致)

  • 优点:代码复用,层次化设计。

  • 类型:单继承(Java、C#)、多继承(C++通过接口或虚基类实现)。

  • 示例

    class Animal {
        void eat() { System.out.println("Eating..."); }
    }
    class Dog extends Animal {
        void bark() { System.out.println("Barking..."); }
    }
    

多态

多态指同一操作作用于不同对象时产生不同行为,通常通过方法重写(子类覆盖父类方法)或接口实现。分为编译时多态(方法重载)和运行时多态(方法重写)。

  • 实现方式:继承 + 方法重写、接口。

  • 示例

    class Shape {
        void draw() { System.out.println("Drawing shape"); }
    }
    class Circle extends Shape {
        @Override
        void draw() { System.out.println("Drawing circle"); } // 运行时多态
    }
    public class Main {
        public static void main(String[] args) {
            Shape s = new Circle(); // 父类引用指向子类对象
            s.draw(); // 输出 "Drawing circle"
        }
    }
    

三者的关系

  • 封装是基础,确保对象内部稳定性。
  • 继承建立层次结构,实现代码复用。
  • 多态基于继承,提升系统灵活性和可扩展性。

通过结合三大特性,可构建高内聚、低耦合的面向对象系统。

接口

接口的定义与作用

接口(Interface)在编程中是一种抽象类型,用于定义一组方法或属性的规范,而不包含具体实现。它充当不同组件之间的契约,确保实现类必须遵循接口定义的行为。

  • 标准化交互:接口规定了类或模块必须实现的方法,确保代码一致性。
  • 解耦设计:通过接口隔离实现细节,降低模块间的直接依赖。
  • 多态支持:不同类实现同一接口后,可通过接口类型统一调用。

接口的常见应用场景

  • API设计:远程服务通过接口定义可调用的方法(如RESTful API)。
  • 插件系统:通过接口允许第三方扩展功能(如IDE插件)。
  • 测试模拟:用接口的模拟实现替代真实依赖,方便单元测试。

接口与抽象类的区别

特性接口抽象类
实现方式纯抽象(无具体方法实现)可包含抽象和具体方法
多继承支持多接口继承仅支持单继承
状态不能包含字段(仅属性)可包含字段和属性

代码示例

Java 接口定义
public interface Drawable {
    void draw();  // 抽象方法
    default void resize() {  // 默认方法(Java 8+)
        System.out.println("Resizing...");
    }
}

接口设计的最佳实践

  • 单一职责:每个接口应聚焦单一功能(如IEnumerable仅用于迭代)。
  • 明确命名:使用I前缀(如C#)或-able后缀(如Java)增强可读性。
  • 避免过度使用:优先用接口解耦复杂系统,简单场景可直接用具体类。

通过合理使用接口,能显著提升代码的可维护性和扩展性。

final关键字的基本概念

在编程中,final是一个常见的关键字,其具体含义和用法因语言而异。以下是几种主流语言中final的典型用法和区别。


Java中的final关键字

在Java中,final可以修饰变量、方法和类,具有不同的语义:

修饰变量

  • 基本类型变量:值不可更改(常量)。

    final int MAX_VALUE = 100;
    // MAX_VALUE = 200; // 编译错误
    

  • 引用类型变量:引用不可更改,但对象内部状态可能可变。

    final List<String> list = new ArrayList<>();
    list.add("item"); // 允许
    // list = new ArrayList<>(); // 编译错误
    

修饰方法

  • 方法不可被子类重写。

    class Parent {
        final void display() { System.out.println("Parent"); }
    }
    class Child extends Parent {
        // void display() { } // 编译错误
    }
    

修饰类

  • 类不可被继承。

    final class ImmutableClass { }
    // class SubClass extends ImmutableClass { } // 编译错误
    



使用场景与最佳实践

  1. 常量定义:用final声明不可变配置值或全局常量。
  2. 设计不可变类:通过final类和方法确保关键逻辑不被修改。
  3. 线程安全final变量在多线程中无需额外同步(Java中保证可见性)。