☰
Java泛型与Object
2026/10/26 1:53:24 网站建设 项目流程

Java动态数组(二)

  • 泛型
    • 1. 什么是泛型
    • 2. 定义泛型类
    • 3. 用 E 代替类型
    • 4. 不能用 E 来 new
    • 5. 使用时指定类型
      • 1. 数组引用
      • 2. 单个数据
      • 3. 构造方法参数
      • 4. 方法参数
      • 5. 返回值
      • 6. 局部变量
    • 补充:三个关键字
  • Object
    • 理解定义
    • 重写方法
      • 默认效果
      • 重写 toString()
      • 前后对比

泛型

1. 什么是泛型

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

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

2. 定义泛型类

publicclassZArrayList<E>{// 类体}

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

// 普通类:类型固定为 StringpublicclassNormalList{publicvoidadd(Strings){...}}
// 泛型类:类型先用 E 占位,使用时再指定具体类型publicclassZArrayList<E>{publicvoidadd(Ee){...}}

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

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

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

3. 用 E 代替类型

publicclassZArrayList<E>{privateE[]elements;// 1. 成员变量:数组引用类型privateEfirst;// 2. 成员变量:单个数据publicZArrayList(Efirst){// 3. 构造方法参数类型this.first=first;}publicvoidadd(Ee){...}// 4. 普通方法参数类型publicEget(intindex){...}// 5. 方法返回值类型publicvoidshow(){Etemp=first;// 6. 局部变量类型}}

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

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

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

4. 不能用 E 来 new

Ee=newE();// ❌ 编译错误E[]arr=newE[10];// ❌ 编译错误

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

5. 使用时指定类型

先看使用时的写法:

// 创建对象时,把 E 换成具体类型ZArrayList<String>list=newZArrayList<>();

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

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

1. 数组引用

原来的写法:

privateE[]elements;

指定String后变成:

privateString[]elements;

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

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

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

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

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

2. 单个数据

原来的写法:

privateEfirst;

指定String后变成:

privateStringfirst;

作用:单独保存一个数据,比如想记住第一个元素。
理解:它和数组一样都是“仓库”,只不过数组是存一批,这个是存一个。

3. 构造方法参数

原来的写法:

publicZArrayList(Efirst){this.first=first;}

指定String后变成:

publicZArrayList(Stringfirst){this.first=first;}

作用:创建对象时,规定必须传入一个String。
理解:new ZArrayList<>()的括号里只能放String:

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

4. 方法参数

原来的写法:

publicvoidadd(Ee){...}

指定String后变成:

publicvoidadd(Stringe){...}

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

list.add("apple");// 正确// list.add(123); // 编译报错

5. 返回值

原来的写法:

publicEget(intindex){...}

指定String后变成:

publicStringget(intindex){...}

作用:从数组里取出数据时,直接就是String。
理解:不用再自己强转:

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

6. 局部变量

原来的写法:

publicvoidshow(){Etemp=first;}

指定String后变成:

publicvoidshow(){Stringtemp=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方法开始运行,最常用的主类写法是:

publicclassMain{publicstaticvoidmain(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。

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

publicclassStudent{// 里面什么都没写}

其实它背后等价于:

publicclassStudentextendsObject{// Java 自动帮你继承 Object}

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

重写方法

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

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

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

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

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

默认效果

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

publicclassStudent{Stringname;intage;}

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

Studentstu=newStudent();System.out.println(stu);

会看到:

Student@1b6d3586

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

重写 toString()

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

publicclassStudent{Stringname;intage;@OverridepublicStringtoString(){return"Student{name="+name+", age="+age+"}";}}

解释两点:

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

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

Student{name=小明, age=18}

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

前后对比

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询