三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

JavaSE 基础语法 - 类和对象 - ⑤

JavaSE 基础语法 - 类和对象 - ⑤

接上节 JavaSE 基础语法 - 类和对象 - ④ 的内容。

26、Object 类简介

我们先来看一个简单的问题。

定义一个学生类:

publicclassStudent{Stringname;intage;}

测试代码:

publicclassTest{publicstaticvoidmain(String[]args){Studentstu=newStudent();System.out.println(stu);}}

运行结果:

Student@3b07d329

大家有没有想过,Student 类里面明明什么方法都没有,为什么还能直接打印对象?

再比如:

Studentstu=newStudent();stu.toString();

代码同样可以正常运行。

可是我们从来没有写过 toString() 方法。

那么这个 toString() 方法到底是从哪里来的?

解:所有 Java 类都会默认继承 Object 类。

26.1 什么是 Object 类

Object类位于:java.lang.Object它是 Java 中最顶层(根)的类。

也就是说:普通类都直接或间接继承 Object(数组类型也是 Object 的子类)。

例如:

我们定义:

publicclassStudent{}

实际上编译器会自动理解成:

publicclassStudentextendsObject{}

只是:

extendsObject

被 Java 自动省略了。

因此 Student 类实际上拥有 Object 类中的所有非私有成员。

26.2 Object 在继承体系中的地位

Java 的继承关系可以理解为一棵树。

Object为树顶,其他的都是它的分支。

像 String 、 ArrayList 、 TreeSet 这种,最终都会继承 Object。

因此:Object 被称为所有类的父类(Root Class)。

26.3 为什么所有类都继承 Object?

为什么 Java 要设计一个 Object 类?

原因很简单:现实生活中,不同的对象虽然类型不同,但它们都有一些共同的行为。

例如:

学生:姓名、年龄 汽车:品牌、价格 手机:品牌、颜色

虽然数据不同。

但是它们都可以:

  • 比较
  • 打印
  • 获取类型
  • 获取哈希值

因此:

Java 将这些所有对象都具有的公共能力,统一放到了Object类中。

这样所有类都自动拥有这些能力。

这也是继承的思想。

27、对象打印

在Object 类中定义了:

publicStringtoString()

下面重点说一下:为什么打印对象会得到一串奇怪的内容。

27.1 为什么打印对象得到的是地址

来看下面代码。

publicclassStudent{Stringname="张三";}

测试:

publicclassTest{publicstaticvoidmain(String[]args){Studentstu=newStudent();System.out.println(stu);}}

输出:

Student@3b07d329

很多认为:这是对象地址。其实严格来说,并不是对象地址。

而是:类名 + @ + 对象哈希值(十六进制)

为什么?下面继续分析。

System.out.println() 底层做了什么?

平常经常会写:

System.out.println(stu);

实际上,println() 方法存在多个重载。

例如:

println(intx)println(doublex)println(charx)println(Objectobj)

当传入对象时调用的是:

println(Objectobj)

它的内部源码如下:

publicvoidprintln(Objectx){Strings=String.valueOf(x);if(getClass()==PrintStream.class){// need to apply String.valueOf again since first invocation// might return nullwriteln(String.valueOf(s));}else{synchronized(this){print(s);newLine();}}}

继续进入:String.valueOf 的源码:

publicstaticStringvalueOf(Objectobj){return(obj==null)?"null":obj.toString();}

看到这里就明白了。

也就是说:

System.out.println(stu);

底层实际上等价于:

System.out.println(stu.toString());

因此真正执行的是:toString()

27.2 Object 中的 toString()

Object 中默认的:toString() 到底返回什么?

源码如下:

publicStringtoString(){returngetClass().getName()+"@"+Integer.toHexString(hashCode());}

它由三部分组成:类全限定名 + @ + 对象哈希值(16进制)

例如:

com.demo.Student@3b07d329

如果类没有包,那么输出:

Student@3b07d329

所以很多人误认为 3b07d329 是对象地址。实际上它只是对象的哈希值转换成十六进制后的结果。

注意:哈希值并不等于对象在内存中的地址,只是在默认实现中通常与对象身份相关。JVM 并没有规定 hashCode() 必须返回对象地址。

27.3 为什么要重写 toString()

默认输出 Student@3b07d329 这一串,对于我们来说几乎没有任何意义。

例如我们真正想看到的是:

Student{name='张三',age=18}

或者:

姓名:张三 年龄:18

因此在开发中,都会重写:toString(),让对象能够按照我们希望的格式输出。

重写 toString()

Student:

publicclassStudent{privateStringname;privateintage;publicStudent(Stringname,intage){this.name=name;this.age=age;}@OverridepublicStringtoString(){return"Student{name='"+name+"', age="+age+"}";}}

测试:

Studentstu=newStudent("张三",18);System.out.println(stu);

输出:

Student{name='张三',age=18}

这样输出就比原来的更直观。

27.4 @Override 注解

在重写方法时,前面都会写:@Override

例如:

@OverridepublicStringtoString(){return"...";}

那么,它有什么作用?

什么是注解?

注解(Annotation)可以理解为:写给编译器看的说明信息

它不会直接改变程序的运行结果,但可以帮助编译器检查代码。

@Override 的作用

@Override 表示:当前方法是重写(Override)父类的方法

例如:

@OverridepublicStringtoString(){return"Student{name='"+name+"', age="+age+"}";}

编译器会检查:

  • 父类中是否真的存在 toString() 方法;
  • 方法名、参数列表、返回值类型是否符合重写规则。

如果写错了,例如:

@OverridepublicStringtostring(){return"Student";}

由于方法名写成了 tostring(小写 s),编译器会立即报错,帮助我们及时发现问题。

如果没有 @Override:

publicStringtostring(){return"Student";}

程序虽然能够编译通过,但这并不是重写,而是定义了一个新的方法。此时调用:

System.out.println(stu);

仍然会执行 Object 类中的 toString(),输出依旧是:

Student@3b07d329

因此:建议在所有重写方法前都加上 @Override 注解,它不仅能提高代码可读性,还能帮助编译器进行检查,减少低级错误

28、对象数组

一个学生需要创建一个对象,那么多个学生就要创建多个对象。

如果现在班级只有一名学生,这样写完全没有问题。

但是,如果班级有50名学生呢。

难道要写:

Studentstu1=newStudent("张三",18);Studentstu2=newStudent("李四",19);Studentstu3=newStudent("王五",20);......Studentstu50=newStudent("赵六",18);

很明显,这种方式既麻烦,又不利于后续管理。

那么,有没有一种方式能够统一管理多个 Student 对象?

答案就是:对象数组

28.1 为什么需要对象数组

之前学习整数数组:

int[]nums={10,20,30,40,50};

数组中存放的是:

1020304050

那么,Student 能不能也放到数组中?

当然可以。例如:

Student[]students=newStudent[50];

这样就可以保存 50 个 Student 对象。

因此:对象数组就是元素类型为引用数据类型(对象)的数组

它最大的作用就是:统一管理多个对象

例如:

学校:

Student[]↓ 张三 李四 王五 赵六

以后学习 ArrayList 的时候,其实底层思想也是类似的。

28.2 对象数组的定义

对象数组定义方式与普通数组完全一致。

语法格式:

类名[]数组名;

例如:

Student[]students;

也可以:

Studentstudents[];

但是推荐:

Student[]students;

这样符合 Java 官方规范。

创建对象数组

使用new创建。

例如:

Student[]students=newStudent[5];

表示创建一个长度为 5 的 Student 数组。

注意:这里创建的是数组对象。不是Student 对象。

这一点非常重要。

28.3 对象数组初始化

对象数组有三种初始化方式。

第一种:动态初始化

例如:

Student[]students=newStudent[3];

运行后,数组有三个元素,每个元素都为 null。

那为什么不是三个 Student 对象,而是 null 。

原因:new Student[3] 只是创建了数组,没有创建 Student 对象

因此数组中的每一个元素默认值都是null,因为 Student 属于引用类型。

引用类型默认值就是:null

第二种:静态初始化

例如:

Students1=newStudent("张三",18);Students2=newStudent("李四",20);Student[]students={s1,s2};

或者:

Student[]students={newStudent("张三",18),newStudent("李四",20),newStudent("王五",19)};

创建数组同时,创建对象。

第三种:先创建数组,再存放对象

例如:

Student[]students=newStudent[3];students[0]=newStudent("张三",18);students[1]=newStudent("李四",20);students[2]=newStudent("王五",19);

这种方式是比较常用的。

28.4 对象数组访问

访问方式与普通数组完全一致。

例如:

System.out.println(students[0]);

输出:

Student{name='张三',age=18}

访问成员:

System.out.println(students[0].getName());

输出:

张三

修改对象:

students[0].setAge(20);

再次打印:

System.out.println(students[0]);

输出:

Student{name='张三',age=20}

说明:数组保存的是对象引用。

28.5 对象数组遍历

遍历方式与普通数组一样。

例如:

for(inti=0;i<students.length;i++){System.out.println(students[i]);}

增强 for:

for(Studentstu:students){System.out.println(stu);}

输出:

Student{name='张三',age=18}Student{name='李四',age=20}Student{name='王五',age=19}

28.6 JVM 内存分析

下面我们来分析一下对象数组在内存中到底是怎么存储的。

来看:

Student[]students=newStudent[3];

JVM 内存:

可以看到,数组里面没有 Student,只有三个 null

继续:

students[0]=newStudent("张三",18);

内存:

数组里面保存的是:Student对象地址,而不是对象本身。

继续:

students[1]=newStudent("李四",20);students[2]=newStudent("王五",19);

最终:

因此:对象数组保存的是对象引用(地址),真正的对象依然存放在堆内存中。

← 返回列表