← 返回首页
Java学习

3.Java数组与面向对象基础

2026-09-19·6 次浏览
3.Java数组与面向对象基础

Java 数组与面向对象基础

前面用一个变量保存一个值,现在我们想保存一组成绩,就需要数组。再往后,我们把一个学生的数据和行为放在一起,逐步认识类、对象、封装、继承和多态。

1. 一维数组

数组用来存放一组相同类型的数据。每个位置有一个下标,下标从 0 开始。

记录三名学生的成绩,不必分别写 score1、score2、score3,用一个数组就能统一保存和遍历。

创建数组,通过下标取出或修改元素。

java 复制代码
int[] scores = {80, 90, 100};
System.out.println(scores[0]); // 80

// 下标从 0 开始,下标 1 对应第二个成绩。
scores[1] = 95;
System.out.println(scores[1]); // 95
System.out.println(scores.length); // 3

for (int score : scores) {
    System.out.println(score);
}

这里共有 3 个元素,合法下标是 0、1、2。访问 scores[3] 会越界。

另一种创建方式是 new int[3],表示先创建 3 个位置,初始值都是 0。引用类型数组的元素初始值是 null。

数组长度创建后不能改变。Object[] 能容纳不同对象,是因为这些对象都能作为 Object 使用;它仍然是一个元素类型为 Object 的数组。

2. 求最大值和最小值

求最大值,就是遍历数组,每次留下较大的数;最小值同理。

例如一组数据里有 12、35、1、4,我们想知道最大、最小各是多少。先拿第一个数作为起点,就不需要猜一个初始值。

先只看最大值的过程,本例假定数组不为空:

java 复制代码
int[] arr = {12, 35, 1, 4};
// 先用数组里的真实值作起点,数组全是负数时也能正确比较。
int max = arr[0];
for (int number : arr) {
    if (number > max) {
        // 遇到更大的数,就替换当前记录。
        max = number;
    }
}
System.out.println(max); // 35

比较完四个数,max 留下 35。最小值只需要把比较方向换成 <:

java 复制代码
int[] arr = {12, 35, 1, 4};
// 先拿第一个数作起点,后面只留下更小的数。
int min = arr[0];
for (int number : arr) {
    if (number < min) {
        min = number;
    }
}
System.out.println(min); // 1

两个例子分开运行。不要把 max 随便初始化成 0,否则数组全部是负数时,就可能错误地留下 0。

3. 在数组中插入一个数

插入元素,就是把新值放在指定位置,原来这个位置及后面的值向后挪。

数组长度不能改变。想在下标 3 的位置插入 56,就要创建长一位的 arr1,把旧数据复制进去。

java 复制代码
import java.util.Arrays;

int[] arr = {12, 35, 1, 4};
// 原数组不能变长,要插入一个数就创建多一个位置的新数组。
int[] arr1 = new int[arr.length + 1];
for (int i = 0; i < arr1.length; i++) {
    if (i < 3) {
        // 插入位置前面的数,仍放在原来的下标。
        arr1[i] = arr[i];
    } else if (i == 3) {
        // 下标 3 是第四个位置,这里放新插入的值。
        arr1[i] = 56;
    } else {
        // 后面的数向右挪一格,所以从原数组的前一个下标取值。
        arr1[i] = arr[i - 1];
    }
}
System.out.println(Arrays.toString(arr1)); // [12, 35, 1, 56, 4]

前三个数照搬,下标 3 放 56,之后从原数组的前一个下标取值。新数组多了一个位置,原来的 arr 没有改变。

这里的下标 3 就是第四个位置。一般情况下,插入下标可以从 0 到原数组长度,最后一个位置表示追加。

4. 冒泡排序

比较相邻两个数,顺序不对就交换。一轮结束后,当前最大的数会到右边。

通过这个小算法理解比较、交换和嵌套循环。

外层控制轮数,内层比较相邻元素。

java 复制代码
import java.util.Arrays;

int[] numbers = {12, 35, 1, 4};

// 每轮把还没排好的部分中的最大值移到右边。
for (int round = 0; round < numbers.length - 1; round++) {
    // 右边已经排好的 round 个数,不必再比较。
    for (int i = 0; i < numbers.length - 1 - round; i++) {
        if (numbers[i] > numbers[i + 1]) {
            // 先暂存左边的数,避免交换时把它覆盖掉。
            int temp = numbers[i];
            numbers[i] = numbers[i + 1];
            numbers[i + 1] = temp;
        }
    }
}
System.out.println(Arrays.toString(numbers)); // [1, 4, 12, 35]

相邻比较才是这里演示的冒泡排序。拿某个位置依次和后面所有位置比较,也可能排好序,但不是同一套过程。

实际开发中通常直接使用 Arrays.sort(numbers)。这里手写是为了学习过程,不是推荐所有项目都自己实现排序。

5. Arrays 工具类

Java 提供的数组工具,有打印、排序、复制等方法。

常见操作已经写好,不必每次重复编写循环。

通过类名调用工具方法。

java 复制代码
import java.util.Arrays;

int[] numbers = {3, 1, 2};
Arrays.sort(numbers);
System.out.println(Arrays.toString(numbers)); // [1, 2, 3]

// 复制出一个新数组,修改 copy 的元素不会改变 numbers。
int[] copy = Arrays.copyOf(numbers, numbers.length);
copy[0] = 99;
System.out.println(numbers[0]); // 1

这里复制出了另一个数组。若数组里放的是对象,复制数组后,里面的对象引用仍可能指向同一个对象。

6. 二维数组

数组的元素还是数组,可以把它理解为多行数据。

例如用行和列表示一张表。

第一个下标选行,第二个下标选这一行中的元素。

java 复制代码
int[][] table = {{1, 2}, {3, 4}};
System.out.println(table[1][0]); // 3

// 外层先取一行,内层再取这一行里的每个数。
for (int[] row : table) {
    for (int number : row) {
        System.out.print(number + " ");
    }
    System.out.println();
}

Java 的二维数组每行长度可以不同,不要求一定是规则矩形。

7. 面向对象、类和对象

把某个角色的数据和行为放在一起,就是面向对象的一种基本做法。类是描述,对象是根据类创建的具体实例。

学生有姓名,也会学习。把它们放在 Student 中,比到处维护零散的变量和函数更容易组织。

先定义类,再用 new 创建对象,最后访问属性、调用方法。

java 复制代码
public class Student {
    public String name;

    public void study() {
        System.out.println(name + "正在学习");
    }
}

接着调用:

java 复制代码
Student student = new Student();
student.name = "张三";
student.study(); // 张三正在学习

先定义学生是什么样子,再创建一个学生让它学习。后面会继续用小例子逐步补上构造方法和封装。

8. 构造方法:创建对象时给它初始值

构造方法与类同名,没有返回类型。使用 new 创建对象时,会执行它。

创建学生时就设置姓名,不必创建后再补一次赋值。

写一个有参构造方法,再把初始数据传进去。

java 复制代码
public class Student {
    public String name;

    public Student() {
        this.name = "未命名";
    }

    public Student(String name) {
        // this.name 是对象字段,右边 name 是传进来的参数。
        this.name = name;
    }
}

接着调用:

java 复制代码
Student first = new Student();
Student second = new Student("李四");
System.out.println(first.name);  // 未命名
System.out.println(second.name); // 李四

如果一个类完全没有写构造方法,编译器才会提供默认无参构造方法。自己写过有参构造后,不能假定无参构造仍然存在。

9. this:当前对象

this 表示当前正在使用的这个对象。

构造方法参数和字段都叫 name 时,要区分它们。

看上一节的 this.name = name:左边是对象的字段,右边是传入的参数。

调用 new Student("李四") 时,就把“李四”放进新对象的 name 字段。

10. 方法重载

方法名相同,参数列表不同。可以是参数数量不同,也可以是参数类型不同。

同一种操作可以接收不同输入,例如两个整数相加,或者三个整数相加。

保留相同方法名,写不同参数。

java 复制代码
public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }

    // 同名方法增加了一个参数,这就是重载。
    public int add(int a, int b, int c) {
        return a + b + c;
    }
}

接着调用:

java 复制代码
Calculator calculator = new Calculator();
System.out.println(calculator.add(1, 2));    // 3
System.out.println(calculator.add(1, 2, 3)); // 6

只改返回类型,不能构成重载。

11. 成员变量和局部变量

成员变量写在类里、方法外;局部变量写在方法或代码块里。

对象需要长期保存的数据放在字段里,某次计算临时使用的数据放在方法里。

前面的 Student.name 是成员变量;调用代码中的 first、second 是局部变量。

成员变量有默认值,例如 int 默认是 0,引用默认是 null。局部变量使用前必须自己赋值。

12. 封装

把字段藏在类内部,通过方法读取或修改。

不让外部随便写入错误数据,例如年龄不能是负数。

字段写成 private,在修改方法里检查数据。

java 复制代码
public class Student {
    private int age;

    public void setAge(int age) {
        // 先检查再赋值,不让错误年龄进入对象。
        if (age < 0) {
            throw new IllegalArgumentException("年龄不能为负数");
        }
        this.age = age;
    }

    public int getAge() {
        return age;
    }
}

接着调用:

java 复制代码
Student student = new Student();
student.setAge(18);
System.out.println(student.getAge()); // 18

throw 表示拒绝错误数据,第五篇会详细解释。封装不是机械地给所有字段加 getter、setter,而是提供需要的操作。

13. static

static 成员属于类,而不是某一个对象。

某个数据或操作由这个类统一提供,不需要为每个对象单独准备一份。

通过 类名.成员名 使用。

java 复制代码
public class School {
    // 这个字段属于 School 类,不需要先创建 School 对象。
    public static String name = "第一中学";

    public static void showName() {
        System.out.println(name);
    }
}

接着调用:

java 复制代码
School.showName(); // 第一中学
School.name = "第二中学";
School.showName(); // 第二中学

静态方法里没有“当前对象”,所以不能直接使用 this 或直接访问实例字段。

14. 继承与方法重写

继承让子类在父类的基础上增加或修改行为。子类用 extends 指定父类,用重写方法完成自己的实现。

使用学生和老师的例子:他们都是人,但介绍身份时说的内容不同。把共同的角色放在 People 中,让学生、老师分别重写 role()。

先定义父类:

java 复制代码
public class People {
    public void role() {
        System.out.println("我是一个人");
    }
}

学生重写这个方法:

java 复制代码
public class Student extends People {
    // 重写父类的 role(),改成学生自己的身份介绍。
    @Override
    public void role() {
        System.out.println("我是学生");
    }
}

老师也重写它:

java 复制代码
public class Teacher extends People {
    // 同一个方法在老师类中有不同的实现。
    @Override
    public void role() {
        System.out.println("我是老师");
    }
}

创建对象并调用:

java 复制代码
Student student = new Student();
Teacher teacher = new Teacher();
student.role(); // 我是学生
teacher.role(); // 我是老师

子类把父类已有的方法换成了自己的实现。@Override 让编译器检查是否真的重写成功,能帮助发现方法名或参数写错的问题。

子类构造方法中,super(...) 用来调用父类构造方法;普通方法中,super.role() 可以调用父类的版本。初始化父类部分,不是额外创建一个独立的父类对象。

重写时不能降低父类方法的访问权限。常见权限先这样理解:

修饰符 入门理解
public 对外公开,仍受类型和模块可见性约束
protected 同包可用,跨包子类按继承规则使用
不写 同一个包中可用
private 限于声明类内部使用,嵌套类有相关语言规则

15. 多态

多态是用父类类型接收子类对象,调用重写方法时,执行实际对象的版本。

还是刚才的学生和老师。如果有一个介绍身份的方法,不想为学生、老师各写一份,就可以让参数统一使用 People。

先看最直接的调用:

java 复制代码
// 变量类型是 People,实际对象是 Student,调用 role() 时执行学生的版本。
People first = new Student();
People second = new Teacher();
first.role();  // 我是学生
second.role(); // 我是老师

变量类型都是 People,实际对象不同,执行的 role() 就不同。

参数写父类,传入子类对象

使用 who(People people),它只调用 role():

java 复制代码
public class RoleDemo {
    public static void who(People people) {
        // 接收任意 People 子类对象,由实际对象决定执行哪个 role()。
        people.role();
    }
}

传入哪种对象,就介绍哪种身份:

java 复制代码
RoleDemo.who(new Student()); // 我是学生
RoleDemo.who(new Teacher()); // 我是老师

返回类型写父类,返回子类对象

也可以通过名称选择身份,让方法返回 People:

java 复制代码
public class RoleFactory {
    public static People who(String name) {
        if ("student".equals(name)) {
            // 返回类型写 People,也可以返回它的子类 Student。
            return new Student();
        }
        return new Teacher();
    }
}

使用返回的对象:

java 复制代码
People student = RoleFactory.who("student");
People teacher = RoleFactory.who("teacher");
student.role(); // 我是学生
teacher.role(); // 我是老师

直接打印对象容易看到默认的对象文字;调用 role(),才能观察实际执行的是哪种身份介绍。本例只演示两种选择,约定其他名称走老师分支。

变量类型决定能直接调用哪些成员;实际对象类型决定重写方法执行哪个实现。多态也不会让父类变量直接访问子类新增的方法。

16. 抽象类与抽象方法

抽象方法只声明要做什么,不写方法体;包含抽象方法的类,必须声明为抽象类。

使用会员买书的例子:普通会员打九折,超级会员打八折。父类只要求“会员能买书”,具体优惠由子类说明。父类没有统一实现,就可以把 buybook() 写成抽象方法。

先定义抽象父类:

java 复制代码
public abstract class Member {
    // 只要求会员具备买书行为,优惠细节交给子类实现。
    public abstract void buybook();
}

普通会员实现九折:

java 复制代码
public class OrdinaryMember extends Member {
    @Override
    public void buybook() {
        System.out.println("普通会员打九折");
    }
}

超级会员实现八折:

java 复制代码
public class SuperMember extends Member {
    @Override
    public void buybook() {
        System.out.println("超级会员打八折");
    }
}

通过父类类型调用:

java 复制代码
// 统一用 Member 类型调用,不同会员执行各自的优惠说明。
Member first = new OrdinaryMember();
Member second = new SuperMember();
first.buybook();  // 普通会员打九折
second.buybook(); // 超级会员打八折

抽象类不能直接 new,但可以有字段、构造方法和普通方法。普通子类必须实现继承的抽象方法,否则也要声明为抽象类。

补充:常见误区和官方资料
  • 数组初始化不要求写在物理上的一行;先声明后赋值时可用 new int[]{...}。
  • super(...) 在 Java 17 示例中应放在构造方法的第一条语句。没有明确写时通常尝试调用父类无参构造,但该构造必须存在且可访问。
  • 重载按参数选择,重写按实际对象选择。重载也可能由继承的方法共同组成。
  • 跨包 protected 不是“子类可以通过任意父类对象访问”的意思,要按访问表达式的类型判断。
  • 类加载不等于类初始化,静态代码块在类初始化时执行。
  • Java 传递参数时传的是值;传对象时,复制的是引用值。

数组规范 · 类与成员规范 · Arrays API

── 完 ──

评论

还没有评论,来说点什么
发表评论
0 / 1000