Java泛型与Object

Java动态数组(二)

  • 泛型
    • [1. 什么是泛型](#1. 什么是泛型)
    • [2. 定义泛型类](#2. 定义泛型类)
    • [3. 用 E 代替类型](#3. 用 E 代替类型)
    • [4. 不能用 E 来 new](#4. 不能用 E 来 new)
    • [5. 使用时指定类型](#5. 使用时指定类型)
      • [1. 数组引用](#1. 数组引用)
      • [2. 单个数据](#2. 单个数据)
      • [3. 构造方法参数](#3. 构造方法参数)
      • [4. 方法参数](#4. 方法参数)
      • [5. 返回值](#5. 返回值)
      • [6. 局部变量](#6. 局部变量)
    • 补充:三个关键字
  • Object

泛型

1. 什么是泛型

泛型,说白了就是一种「类型模板」。

写动态数组 ZArrayList 的时候,我们暂时还不知道将来要装什么类型的数据------可能是 Integer,可能是 String,也可能是自定义的 Student。于是先用一个占位字母(通常叫 E,即 Element 元素)顶替这个「还不知道的类型」。

2. 定义泛型类

java 复制代码
public class ZArrayList<E> {
    // 类体
}

来个最简单的对比,普通类和泛型类都实现同一个效果:

java 复制代码
// 普通类:类型固定为 String
public class NormalList {
    public void add(String s) { ... }
}
java 复制代码
// 泛型类:类型先用 E 占位,使用时再指定具体类型
public class ZArrayList<E> {
    public void add(E e) { ... }
}

可以看到,同样是 add 方法:普通类把参数类型固定成了 String,所以它只能添加 String 类型的数据;而泛型类里写的是 E,这个 E 暂时还没有确定类型,所以将来使用时,既可以装 String,也可以装 Integer、Student 等其他类型。

区别就是:普通类把类型固定,泛型类先用 E 占位,等使用的时候再确定具体类型。

  • E 叫「类型参数」,就是一个占位字母,你可以理解成"占位符";
  • 字母不一定是 E,写 T、K、V 都行,只是习惯上:
    • E → Element(元素)
    • T → Type(类型)
    • K / V → Key / Value(键和值)

3. 用 E 代替类型

java 复制代码
public class ZArrayList<E> {

    private E[] elements;              // 1. 成员变量:数组引用类型
    private E first;                   // 2. 成员变量:单个数据

    public ZArrayList(E first) {       // 3. 构造方法参数类型
        this.first = first;
    }

    public void add(E e) { ... }       // 4. 普通方法参数类型

    public E get(int index) { ... }    // 5. 方法返回值类型

    public void show() {
        E temp = first;                // 6. 局部变量类型
    }
}

在类里面,凡是需要写"数据类型"的位置,都可以直接用 E 占位。

但存在两个容易犯错的地方:

  • E[] 只能用来声明 数组引用,不能写成 new E[10],下一点细讲。
  • 静态方法(static)里不能直接用类的 E:因为静态方法属于类本身,还没和具体类型绑定。

4. 不能用 E 来 new

java 复制代码
E e = new E();        // ❌ 编译错误
E[] arr = new E[10];  // ❌ 编译错误

原因:编译阶段 Java 还不知道 E 到底是什么类型,没法给它分配确定的内存,所以不能用 E 去 new。这也是泛型最容易犯的错误。

5. 使用时指定类型

先看使用时的写法:

java 复制代码
// 创建对象时,把 E 换成具体类型
ZArrayList<String> list = new ZArrayList<>();

创建对象时,E 指定成什么类型,类里所有 E 就一起变成那个类型。指定成 String,这个数组就专门用来存 String;指定成 Integer,就专门用来存 Integer。

下面把第 3 节的 6 种 E 一个一个拆开讲,看看它们到使用阶段分别变成什么、有什么用。

1. 数组引用

原来的写法:

java 复制代码
private E[] elements;

指定 String 后变成:

java 复制代码
private String[] elements;

作用:这是数组本体,用来把多个数据存进对象里。

看到 private E[] elements; 可能会疑惑:这里并没有像方法参数 add(String s) 那样,在括号里看到有数据传进来啊。

其实,成员变量不是靠"传参"那一瞬间存数据的,它是对象内部的"仓库"。声明 private E[] elements; 只是先准备好一个数组引用,相当于先把货架搭好。

所以第 1 种 E 的意思就是:给这个仓库定一个"专门放什么"。

比如你写 ZArrayList<String>,就相当于给这个仓库贴了个标签:这里只能放 String 。于是 elements 这个数组就变成了"字符串数组"

2. 单个数据

原来的写法:

java 复制代码
private E first;

指定 String 后变成:

java 复制代码
private String first;

作用:单独保存一个数据,比如想记住第一个元素。

理解:它和数组一样都是"仓库",只不过数组是存一批,这个是存一个。

3. 构造方法参数

原来的写法:

java 复制代码
public ZArrayList(E first) {
    this.first = first;
}

指定 String 后变成:

java 复制代码
public ZArrayList(String first) {
    this.first = first;
}

作用:创建对象时,规定必须传入一个 String。

理解:new ZArrayList<>() 的括号里只能放 String:

java 复制代码
// 正确:
ZArrayList<String> list = new ZArrayList<>("hello");

// 错误,不能传 int:
// ZArrayList<String> list = new ZArrayList<>(100);

4. 方法参数

原来的写法:

java 复制代码
public void add(E e) { ... }

指定 String 后变成:

java 复制代码
public void add(String e) { ... }

作用:调用 add 时,只能往里面放 String。

java 复制代码
list.add("apple");   // 正确
// list.add(123);    // 编译报错

5. 返回值

原来的写法:

java 复制代码
public E get(int index) { ... }

指定 String 后变成:

java 复制代码
public String get(int index) { ... }

作用:从数组里取出数据时,直接就是 String。

理解:不用再自己强转:

java 复制代码
String s = list.get(0);   // 直接是 String

// 如果不用泛型,可能还得自己转一次:
// String s = (String) list.get(0);

6. 局部变量

原来的写法:

java 复制代码
public void show() {
    E temp = first;
}

指定 String 后变成:

java 复制代码
public void show() {
    String temp = first;
}

作用:方法内部临时保存数据时使用。

理解:临时变量也要和 E 保持一致,否则类型对不上会报错。

小结 :这 6 种 E 归根到底就是三件事------存数据 (第 1、2 种)、收数据 (第 3、4 种)、给数据 (第 5、6 种)。使用时指定 String,这三件事就都围绕 String 来运转。

补充:三个关键字

我们来简单区分这三个关键字:

  • private:私有,只能在这个类内部 使用。比如 private E[] elements 就是不想让外部随便乱动数据,从而更安全。
  • public:公共,类外面 也能访问。比如 public void add(E e),别人创建对象后,才能调用这个方法添加数据。
  • static:静态,属于类本身 ,不依赖某个具体的对象开放。因为没有和具体对象绑定,所以静态方法里不能用类的 E。

再补充一下 static:

static 表示"属于类本身,不属于某个具体对象"。类一加载,静态成员就已经存在了,不需要先 new 对象就能直接使用。

Java 的程序入口也和它有关。每个 Java 程序都要从一个特殊的 main 方法开始运行,最常用的主类写法是:

java 复制代码
public class Main {
    public static void main(String[] args) {
        // 所有代码从这里开始执行
    }
}

为什么 main 方法前面一定要写 static?因为程序刚启动时,JVM 还没有创建任何一个对象,它只能通过"类名.方法名"的方式直接调用 main。如果 main 不是 static,JVM 就找不到程序入口,程序根本没法运行。

竞赛中写主类,记住这个固定套路就行:

  • 类名一般写成 Main:很多 OJ(在线判题系统)会强制要求 public class Main。
  • main 方法必须完整写成 public static void main(String[] args),其中 static 不能漏。

所以可以这样理解:main 是一个静态方法,它属于 Main 类本身;程序一启动,还没创建任何对象,JVM 就能直接调用 Main.main() 开始运行。

Object

理解定义

Object 是 Java 里最基础的一个类,你可以把它理解成所有类的"老祖宗"。

在 Java 里,不管你写一个什么样的类,哪怕什么都没有,它都会自动继承 Object。

比如你写一个最简单的类:

java 复制代码
public class Student {
    // 里面什么都没写
}

其实它背后等价于:

java 复制代码
public class Student extends Object {
    // Java 自动帮你继承 Object
}

也就是说,Student、String、Integer,还有我们前面写的 ZArrayList,它们共同的祖先都是 Object。

重写方法

前面说过,Object 是所有类的"老祖宗",所以它会准备好一些通用方法,让每个类都能直接用。这些方法虽然能用,但往往只是"通用版",并不一定符合我们自己的需求。

比如 Object 给我们准备了一个 toString() 方法,作用是"把对象翻译成字符串"。但这个通用版没有太多信息,默认只会告诉我们:这是哪个类、以及一个看不懂的编号。

好在 Java 允许我们"重写":子类可以重新写一个名字相同、参数相同、返回值相同的方法,把从 Object 继承来的通用版本盖掉,换成更适合自己的版本。

重点记住一句话:继承来的方法,如果不满意,就可以重写它。

下面就用 toString() 来看具体怎么做。

默认效果

先写一个最普通的 Student,里面只放两个信息:

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

这时候我们没有重写任何方法,打印它:

java 复制代码
Student stu = new Student();
System.out.println(stu);

会看到:

text 复制代码
Student@1b6d3586

这其实是 Object 默认的 toString() 输出:类名@地址编号(哈希值)。它知道"这是一个 Student 对象",但不知道我们真正关心的是 name 和 age。

重写 toString()

现在要求变清楚:我们希望打印学生时,能直接看到姓名和年龄。于是我们在 Student 里重写 toString():

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

    @Override
    public String toString() {
        return "Student{name=" + name + ", age=" + age + "}";
    }
}

解释两点:

  • @Override:这是 Java 的标记,意思是"我是在重写从父类继承来的方法,不是新写一个方法"。
  • 方法名、参数、返回值都和 Object 里的 toString() 保持一致,才能真正覆盖它。

重写后,再打印同一个对象:

text 复制代码
Student{name=小明, age=18}

现在输出就是我们想要的信息了。

前后对比

  • 不重写:用 Object 给的通用版,只能看到 Student@1b6d3586。
  • 重写后:用 Student 自己定制的版本,能直接看到 name 和 age。

以后做动态数组时也会用同样的思路:给 ZArrayList 重写 toString(),让打印整个数组时显示的是里面的元素,而不是一串看不懂的编号。

相关推荐
惊讶的猫1 小时前
Tika文档解析
java
2401_872418781 小时前
互联网公开信息检索完全指南:Google、Yandex、Wayback Machine、DNS、WHOIS、Shodan 等 OSINT 工具大全
开发语言·php
HEJOO91 小时前
Java 类加载过程详解:从加载到初始化的完整链路
java·开发语言·firefox
hahaha60161 小时前
数字图像锐化
开发语言·python
科学熊1 小时前
Java 分布式理论与高并发编程
java·开发语言·分布式
普贤莲花1 小时前
先学Workbuddy还是“人工智能训练师”,或者python基础课?
开发语言·人工智能·python
砚底藏山河1 小时前
python量化实操:回测数据缓存与重放
java·python·金融·1024程序员节
星之擎1 小时前
Task 很优雅的取消方式是什么?C# 里面。
开发语言·c#
学长毕设2 小时前
基于springboot的农资管理系统-附源码
java·spring boot·后端