Skip to content

第 4 章 面向对象编程基础:创造你的第一个机器人​

约 3070 个字 453 行代码 预计阅读时间 16 分钟

阅读目标理解面向对象编程(OOP)的核心思想:将世界看作对象的集合。掌握类与对象的概念,并能创建和使用它们。学习方法的定义、调用,以及成员变量与局部变量的区别。深入理解封装、继承、多态三大特性如何协同工作。掌握构造器的使用方法,为对象进行初始化。

阅读目标

  • 理解面向对象编程(OOP)的核心思想:将世界看作对象的集合。

  • 掌握类与对象的概念,并能创建和使用它们。

  • 学习方法的定义、调用,以及成员变量与局部变量的区别。

  • 深入理解封装、继承、多态三大特性如何协同工作。

  • 掌握构造器的使用方法,为对象进行初始化。

4.1 思想的转变:从“过程”到“对象”​

在之前的学习中,我们写的代码像一份菜谱:第一步做什么,第二步做什么……严格按照顺序执行。这叫“面向过程”。

现在,我们要学习一种更强大、更接近现实世界的思想:面向对象编程 (Object-Oriented Programming, OOP)。

想象一下真实的 FTC 赛场,我们不会去想“让左前轮的电机转动3圈,同时让右前轮的电机转动3圈...”。我们的大脑会直接下达一个指令:“机器人,前进!”

这就是面向对象的思想:我们把程序世界看作一个由许多高智能的对象组成的团队。每个对象都有它自己的属性(它是什么样的)和行为(它能做什么)。我们通过指挥这些对象协同工作来完成任务。

核心思想转变

  • 面向过程:关心“第一步做什么,第二步做什么”。像一份菜谱,严格按照步骤一步步执行。

  • 面向对象:关心“谁能做什么事”。像一个团队,每个成员(对象)有自己的职责,你只需要告诉“谁”去做“什么事”。

4.2 类和对象:从机器人蓝图到实体机器人​

在我们正式开始学习代码之前,让我们先玩一个思维游戏。环顾一下你的房间,或者想象一下你熟悉的任何一个地方,比如学校或公园。

你能发现哪些具体的东西?可能有一张桌子、一把椅子、一个水杯,或者窗外的一棵树、一辆汽车。 现在,我们来试着描述其中一样东西,比如“一辆汽车”。它有哪些特征呢?

  • 属性 (它是什么样的): 它有颜色(红色)、品牌(比如特斯拉)、四个轮子。

  • 行为 (它能做什么): 它能前进、能后退、能鸣笛、能开关车门。

在面向对象的世界里,有两个核心概念:类 (Class)和对象 (Object)。

  • 类 (Class):是一份蓝图或模板,它定义了一类事物应该具有的通用属性和行为。

一个类就是一份蓝图,这份蓝图主要由三个部分构成:成员变量 (Fields):描述这类事物有什么样的属性。构造器 (Constructors):描述当创造一个新对象时,如何进行初始化。方法 (Methods):描述这类事物能做些什么样的行为。

一个类就是一份蓝图,这份蓝图主要由三个部分构成:

  • 成员变量 (Fields):描述这类事物有什么样的属性。

  • 构造器 (Constructors):描述当创造一个新对象时,如何进行初始化。

  • 方法 (Methods):描述这类事物能做些什么样的行为。

  • 对象 (Object):是根据这个蓝图创造出来的一个具体的、真实的个体。

类与对象的关系

  • 类:就像一份“FTC机器人设计图纸”。图纸上画着机器人应该有轮子、有机械臂,并且能前进、能抬手。

  • 对象:就是根据这份图纸,在工厂里生产出来的“每一台具体的机器人”。

  • 我们可以用同一份图纸(同一个类),造出成千上万台独立工作的机器人(对象)。

┌────────────────────────────┐
   Robot Class (机器人蓝图)  
  ┌────────────────────┐    
   成员变量               
   - 名字 (name)          
   - 电量 (battery)       
  └────────────────────┘    
  ┌────────────────────┐    
   构造器                 
   + Robot(initialName)   
   (如何制造一台新机器人) 
  └────────────────────┘    
  ┌────────────────────┐    
   方法                   
   + drive()              
   + charge()             
   (机器人能做什么)        
  └────────────────────┘    
└────────────────────────────┘
/**
 * Robot.java
 * 定义一个“机器人”类(FTC机器人蓝图)
 */
public class Robot {
    // 属性 (成员变量):描述这个机器人有什么
    String name;
    double power; // 动力,范围 0.0 (停止) 到 1.0 (全速)

    // 行为 (方法):描述这个机器人能做什么
    public void drive() {
        System.out.println(name + "号机器人正在以 " + (power * 100) + "% 的动力前进!");
    }
}

/**
 * Test.java
 * 这里是我们的机器人测试场,我们在这里创造并指挥机器人
 */
public class Test {
    public static void main(String[] args) {
        // 1. 创建对象:根据 Robot 蓝图,用 `new` 关键字创造一个机器人对象
        Robot robot1 = new Robot();

        // 2. 设置属性:给这台机器人赋值
        robot1.name = "先锋号";
        robot1.power = 0.8; // 设置 80% 的动力

        // 3. 调用行为:命令它执行任务
        robot1.drive(); // 输出:先锋号机器人正在以 80.0% 的动力前进!

        // 我们可以根据同一份蓝图,再造一台完全独立的机器人
        Robot robot2 = new Robot();
        robot2.name = "守护者";
        robot2.power = 0.5;
        robot2.drive(); // 输出:守护者号机器人正在以 50.0% 的动力前进!
    }
}

4.3 方法:对象的超能力​

方法 (Method)定义了对象能执行的操作或行为,是对象的“技能”。

一个方法由四个核心部分组成:

组成部分 例子public void drive(double seconds) 说明
返回类型 void 方法执行完毕后返回的数据类型。void表示不返回任何值。
方法名 drive 方法的名称,遵循驼峰命名法。
参数列表 (double seconds) 调用方法时需要传入的数据,像技能的“施法材料”。
方法体 { ... } 方法的具体实现代码,用花括号包裹。
/**
 * Robot.java (带方法的版本)
 */
public class Robot {
    String name;

    /**
     * 这是一个没有返回值(void)且有参数的方法
     * @param seconds 需要行驶的秒数
     */
    public void driveFor(double seconds) {
        System.out.println(this.name + " 开始前进,持续 " + seconds + " 秒。");
    }

    /**
     * 这是一个有返回值(String)且没有参数的方法
     * @return 返回机器人的状态报告
     */
    public String getStatus() {
        // 使用 return 关键字返回一个字符串结果
        return "机器人 " + this.name + " 当前状态正常。";
    }
}

4.4 成员变量 vs 局部变量​

变量根据定义位置的不同,可以分为两种:

特性 成员变量 (对象的属性) 局部变量 (方法的临时工)
定义位置 类的内部,方法的外部 方法的内部或参数列表里
作用范围 在整个类中都有效 只在定义它的那个方法内有效
生命周期 随对象的创建而生,随对象的销毁而亡 随方法的调用而生,随方法的结束而亡
初始值 有默认值 (int为0, boolean为false) 没有默认值,必须先赋值再使用
public class Robot {
    // name 是成员变量,代表机器人的核心属性,它的生命周期和机器人对象一样长
    private String name = "先锋号"; 

    public void driveFor(double seconds) {
        // distance 是局部变量,它只是为了完成本次driveFor任务临时使用的“草稿纸”
        // 一旦 driveFor 方法执行完毕,distance 就会被销毁
        double distance = 0.5 * seconds; 

        System.out.println(name + " 行驶了 " + distance + " 米。");
    }

    public void test() {
        // System.out.println(distance); // 错误!这里无法访问 driveFor 方法的局部变量
    }
}

为什么要区分成员变量和局部变量?

就像你的“书包”(对象)里放着“课本”(成员变量),而课堂上用的“草稿纸”(局部变量)用完就扔掉。所有东西都塞进书包会让书包变得混乱不堪。各司其职能让程序更清晰、更安全。

4.5 封装:为你的机器人装上智能保护壳​

我们的机器人power属性可以直接被赋值,如果有人不小心写了robot1.power = 999;,这在现实中可能会烧毁电机!

封装 (Encapsulation)就是为了解决这个问题。它就像给机器人的核心部件装上一个保护壳,并提供一些安全的按钮(方法)来操作它。

我们使用private(私有的) 和public(公共的) 关键字来实现封装:

  • private:将属性设为私有,像日记本一样,只有机器人自己能访问。

  • public:将方法设为公共,像操作按钮一样,任何人都可以调用。

/**
 * Robot.java (封装升级版)
 */
public class Robot {
    // 1. 将属性设为私有,外部无法直接访问
    private String name;
    private double power;

    // ... 构造器将在后面介绍 ...

    /**
     * 2. 提供一个公共的、安全的方法来设定动力 (Setter方法)
     */
    public void setPower(double p) {
        // 在公共方法里设置“安全门卫”,检查传入值的合法性
        if (p >= 0.0 && p <= 1.0) {
            this.power = p; // 合法,接受赋值
        } else {
            System.out.println("错误:动力值 " + p + " 超出范围 (0.0 - 1.0)!");
        }
    }

    /**
     * 3. 提供一个公共方法让外部读取动力值 (Getter方法)
     */
    public double getPower() {
        return this.power;
    }

    // ... 其他方法 ...
}

/**
 * Test.java
 */
public class Test {
    public static void main(String[] args) {
        Robot myRobot = new Robot();
        // myRobot.power = 999; // 编译错误!power 是 private 的,无法直接访问。

        // 必须通过我们提供的安全按钮来操作
        myRobot.setPower(0.75);       // 安全的操作
        myRobot.setPower(999);        // 危险的操作被安全卫士阻止了
    }
}

this 关键字

this代表“当前这个对象自己”。当方法参数名和成员变量名可能冲突时,this.power能明确地指向“我这个对象的 power 属性”。

4.6 构造器:机器人的诞生仪式​

构造器(Constructor)是一个特殊的方法,它在对象被创建的那一刻(new的时候)自动被调用,专门用来做“初始化”工作,比如给对象的属性赋上初始值。

  • 默认构造器:如果你不写任何构造器,Java 会送你一个看不见的、没有参数的空构造器。new Robot();

  • 有参构造器:我们通常会自定义有参构造器,强制在创建对象时就提供必要的初始信息。

💡 构造器的特点

  • 方法名必须与类名完全相同。

  • 没有返回类型(连void也没有)。

  • 创建对象时(new的时候)会自动调用,且只调用一次。

每次创建一个新机器人都得手动pioneer.name = "先锋号"太麻烦了。我们希望在它“诞生”时就必须给它一个名字。

/**
 * Robot.java (带有构造器)
 */
public class Robot {
    private String name;
    private double power;

    /**
     * 这是 Robot 类的构造器。
     * 在 `new Robot(...)` 的时候自动运行。
     * @param robotName 这是给新机器人起的名字
     */
    public Robot(String robotName) {
        this.name = robotName; // 将传入的名字赋给 name 属性
        this.power = 0.0;      // 默认初始动力为 0
        System.out.println("一台名为【" + this.name + "】的机器人被制造出来了!");
    }

    // ... 其他 getter/setter 方法 ...
}

/**
 * Test.java
 */
public class Test {
    public static void main(String[] args) {
        // 创建对象时,必须在括号里提供构造器需要的参数
        Robot robot1 = new Robot("闪电号"); // 调用构造器
        // Robot robot2 = new Robot(); // 错误!因为已经定义了有参构造器,默认的无参构造器就失效了。
    }
}

深入构造器:有参数的构造器和无参数的构造器之间有什么关系?

public class Robot {
    private String name;
    private double power;

    // 1. 无参数的构造器
    public Robot() {
        // this(...) 可以调用本类中其他的构造器,必须放在第一行!
        // 这里调用了下面的有参构造器,给了一个默认名字
        this("无名氏"); 
    }

    // 2. 有参数的构造器
    public Robot(String name) {
        this.name = name;
        this.power = 0; // 初始化动力
        System.out.println("【" + this.name + "】诞生了!");
    }
}

public class Test {
    public static void main(String[] args) {
        Robot r1 = new Robot(); // 调用无参构造器
        Robot r2 = new Robot("探路者"); // 调用有参构造器
    }
}

对象创建全过程

执行Robot r2 = new Robot("探路者");时发生了什么?

  • 分配内存:Java 在内存(堆)中为新的Robot对象分配一块空间。

  • 默认初始化:对象的成员变量被赋予默认值(name为null,power为0.0)。

  • 调用构造器:匹配的Robot(String name)构造器被调用。

  • 执行构造器代码:this.name = "探路者"和this.power = 0被执行,成员变量被赋予了初始值。

  • 返回引用:对象的内存地址被返回,并赋值给r2这个引用变量。

4.7 继承:站在巨人的肩膀上​

在FTC中,我们有驱动轮电机、机械臂电机、爪子电机等。它们都是“电机”,都有端口号、都能设置功率。我们可以创建一个通用的Motor父类来包含这些共性,然后让具体的电机类来继承 (Inheritance)它。

  • 父类 (Superclass): 包含通用属性和行为的类 (如Motor)。

  • 子类 (Subclass): 继承父类并可以添加自己特性的类 (如DriveMotor)。

/**
 * Motor.java (父类)
 */
public class Motor {
    // protected 关键字表示这个属性可以被自己和所有子类访问
    protected int port;
    protected double power;

    public Motor(int port) {
        this.port = port;
        System.out.println("端口 " + port + " 上的电机已初始化。");
    }

    public void setPower(double power) {
        this.power = power;
        System.out.println("端口 " + port + " 电机功率设为 " + power);
    }
}

/**
 * DriveMotor.java (子类)
 * extends 关键字表示 DriveMotor 继承自 Motor
 */
public class DriveMotor extends Motor {
    /**
     * 子类的构造器
     * @param port 电机连接的端口号
     */
    public DriveMotor(int port) {
        // super() 用来调用父类的构造器,必须放在第一行
        super(port);
    }

    // 子类可以有自己的专属方法
    public void goForward() {
        // setPower 方法是从父类 Motor 继承来的
        setPower(1.0);
        System.out.println("驱动电机全速前进!");
    }
}

/**
 * Test.java
 */
public class Test {
    public static void main(String[] args) {
        // 创建一个驱动电机对象
        DriveMotor leftMotor = new DriveMotor(0);
        leftMotor.setPower(0.8); // 调用继承来的方法
        leftMotor.goForward();   // 调用自己的方法
    }
}

什么是 extendssuper

extends(扩展)是Java中用于声明继承关系的关键字,class A extends B的意思就是“A类继承自B类”。super(超级)则是一个指向父类的“代词”。super(参数)用于在子类的构造器里调用父类的构造器,确保父类的部分也被正确初始化。super.方法名()则可以让你在子类中,调用那个被你不小心“覆盖”掉的父类原始方法。

4.8 多态:一个遥控器控制所有设备​

多态 (Polymorphism)是面向对象最强大的特性。它的意思是“多种形态”。

想象一下,你有一个万能遥控器,上面有个红色的“启动”按钮。当你用它对着电视按,电视就打开了;对着空调按,空调就开始吹风。这个“启动”按钮的行为,作用在不同对象上,产生了不同的结果。这就是多态!

实现多态的三要素:

  • 继承:必须有父子类关系。

  • 方法重写 (Override):子类要重新实现父类的方法。

什么是重写?

重写就是子类重新实现父类的方法,这样调用时,会调用子类的方法(如果子类没有实现该方法,则调用父类的方法)。重写和重载的区别在于,重写是子类重新实现父类的方法,而重载是同一个类中,方法名相同,参数列表不同。

  • 父类引用指向子类对象:父类类型 变量 = new 子类对象();

【生活中的例子:谁在叫?】

class Animal {
    public void makeSound() {
        System.out.println("动物发出声音...");
    }
}
class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("小狗汪汪叫!");
    }
}
class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("小猫喵喵叫!");
    }
}

public class Zoo {
    // 这个方法可以接收任何“动物”
    public static void hearSound(Animal animal) {
        animal.makeSound();
    }

    public static void main(String[] args) {
        // 父类引用 指向 子类对象
        Animal myDog = new Dog();
        Animal myCat = new Cat();

        myDog.makeSound(); // 虽然myDog是Animal类型,但它实际指向Dog,所以执行Dog的方法
        myCat.makeSound(); // 同理,执行Cat的方法

        System.out.println("--- 使用一个方法处理不同对象 ---");
        hearSound(new Dog()); // 传入狗对象
        hearSound(new Cat()); // 传入猫对象
    }
}

hearSound方法根本不关心传进来的是Dog还是Cat,它只知道这是个Animal,并且它有makeSound的功能。这就是多态的威力:屏蔽差异,统一接口。

【FTC机器人例子:统一控制所有部件】

我们的机器人上有很多可以“激活”的部件,比如爪子(Claw)、发射器(Launcher)。我们可以定义一个统一的Attachment(附件)父类。

/**
 * Attachment.java (父类/接口)
 * 代表所有可以被“激活”的机器人附件
 */
public class Attachment {
    public void activate() {
        System.out.println("一个附件被激活了...");
    }
}

/**
 * Claw.java (子类1:爪子)
 * @Override 注解告诉编译器,我们要重写父类的方法
 */
public class Claw extends Attachment {
    @Override
    public void activate() {
        System.out.println("爪子闭合,夹取成功!");
    }
}

/**
 * Launcher.java (子类2:发射器)
 */
public class Launcher extends Attachment {
    @Override
    public void activate() {
        System.out.println("发射器启动,发射像素!");
    }
}

/**
 * Test.java (万能遥控器)
 */
public class Test {
    public static void main(String[] args) {
        // 创建一个爪子和一个发射器
        Claw myClaw = new Claw();
        Launcher myLauncher = new Launcher();

        // --- 这就是多态的体现 ---
        // pressButton 方法接收的是通用的 Attachment 类型
        // 就像万能遥控器的“启动”按钮
        pressButton(myClaw);     // 传入爪子对象
        pressButton(myLauncher); // 传入发射器对象
    }

    /**
     * 这个方法就是我们的“万能遥控器按钮”
     * 它不关心具体是什么附件,只知道它是一个 Attachment,并且能被 activate()
     * @param attachment 任何继承了 Attachment 的对象
     */
    public static void pressButton(Attachment attachment) {
        System.out.print("按下按钮 -> ");
        attachment.activate();
    }
}

多态的核心价值

多态实现了接口的统一。pressButton方法不需要为Claw写一个,再为Launcher写一个。它只需要和通用的Attachment父类打交道,这让我们的程序拥有了极强的可扩展性。未来即使我们新增一个Drill(钻头) 附件,pressButton方法也无需任何修改就能直接使用它!

4.7 课堂练习​

练习 1:定义一个“手机”类 (巩固封装)​

  • 创建一个Phone类。

  • 为它添加私有属性:品牌 (brand, String)、价格 (price, double)。

  • 提供一个构造器,在创建对象时可以初始化品牌和价格。

  • 为这两个属性提供公共的getter和setter方法。

  • 添加一个公共方法call(String contact),调用时打印 "正在给 [联系人姓名] 打电话..."。

  • 创建一个测试类,创建Phone对象并测试其所有方法。

参考答案:

/**
 * Phone.java
 */
public class Phone {
    // 1. 私有属性
    private String brand;
    private double price;

    // 2. 构造器
    public Phone(String brand, double price) {
        this.brand = brand;
        this.price = price;
    }

    // 3. brand 的 getter 和 setter
    public String getBrand() {
        return this.brand;
    }
    public void setBrand(String brand) {
        this.brand = brand;
    }

    // 4. price 的 getter 和 setter
    public double getPrice() {
        return this.price;
    }
    public void setPrice(double price) {
        if (price > 0) { // setter 里可以加一些保护逻辑
            this.price = price;
        } else {
            System.out.println("价格无效!");
        }
    }

    // 5. 公共方法
    public void call(String contact) {
        System.out.println("正在给 " + contact + " 打电话...");
    }
}

/**
 * Test.java
 */
public class Test {
    public static void main(String[] args) {
        // 创建 Phone 对象
        Phone myPhone = new Phone("华为", 5999.0);

        // 测试 getter
        System.out.println("手机品牌:" + myPhone.getBrand() + ", 价格:" + myPhone.getPrice());

        // 测试 call 方法
        myPhone.call("张三");
    }
}

练习 2:FTC爪子控制器 (综合练习)​

  • 创建一个RobotClaw类。

  • 添加一个私有属性private boolean isOpen;来表示爪子的开合状态。

  • 创建一个构造器,在创建爪子对象时,让它默认为打开状态 (isOpen = true;)。

  • 创建两个公共方法:public void open()用来打开爪子,public void close()用来闭合爪子。

  • 创建一个公共方法public void printStatus(),根据isOpen的值,打印 "爪子状态:打开" 或 "爪子状态:关闭"。

  • 创建一个测试类,创建RobotClaw对象,并测试它的所有功能。

参考答案:

```java /* * RobotClaw.java / public class RobotClaw { // 1. 私有属性 private boolean isOpen;

// 2. 构造器
public RobotClaw() {
    this.isOpen = true; // 默认是打开状态
    System.out.println("爪子已创建,初始状态为:打开");
}

// 3. 打开方法
public void open() {
    this.isOpen = true;
    System.out.println("动作:打开爪子");
}

// 4. 关闭方法
public void close() {
    this.isOpen = false;
    System.out.println("动作:关闭爪子");
}

// 5. 打印状态方法
public void printStatus() {
    if (this.isOpen) {
        System.out.println("爪子当前状态:打开");
    } else {
        System.out.println("爪子当前状态:关闭");
    }
}

}

/* * Test.java / public class Test { public static void main(String[] args) { // 创建爪子对象 RobotClaw claw = new RobotClaw();

    // 测试初始状态
    claw.printStatus();

    // 测试关闭和打印状态
    claw.close();
    claw.printStatus();

    // 测试打开和打印状态
    claw.open();
    claw.printStatus();
}

} ```java

颜色主题调整