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

📅 2026/8/5 10:10:35
JavaSE 基础语法 - 类和对象 - ⑤
接上节 JavaSE 基础语法 - 类和对象 - ④ 的内容。26、Object 类简介我们先来看一个简单的问题。定义一个学生类publicclassStudent{Stringname;intage;}测试代码publicclassTest{publicstaticvoidmain(String[]args){StudentstunewStudent();System.out.println(stu);}}运行结果Student3b07d329大家有没有想过Student 类里面明明什么方法都没有为什么还能直接打印对象再比如StudentstunewStudent();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){StudentstunewStudent();System.out.println(stu);}}输出Student3b07d329很多认为这是对象地址。其实严格来说并不是对象地址。而是类名 对象哈希值十六进制为什么下面继续分析。System.out.println() 底层做了什么平常经常会写System.out.println(stu);实际上println() 方法存在多个重载。例如println(intx)println(doublex)println(charx)println(Objectobj)当传入对象时调用的是println(Objectobj)它的内部源码如下publicvoidprintln(Objectx){StringsString.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(objnull)?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.Student3b07d329如果类没有包那么输出Student3b07d329所以很多人误认为 3b07d329 是对象地址。实际上它只是对象的哈希值转换成十六进制后的结果。注意哈希值并不等于对象在内存中的地址只是在默认实现中通常与对象身份相关。JVM 并没有规定 hashCode() 必须返回对象地址。27.3 为什么要重写 toString()默认输出 Student3b07d329 这一串对于我们来说几乎没有任何意义。例如我们真正想看到的是Student{name张三,age18}或者姓名张三 年龄18因此在开发中都会重写toString()让对象能够按照我们希望的格式输出。重写 toString()StudentpublicclassStudent{privateStringname;privateintage;publicStudent(Stringname,intage){this.namename;this.ageage;}OverridepublicStringtoString(){returnStudent{namename, ageage};}}测试StudentstunewStudent(张三,18);System.out.println(stu);输出Student{name张三,age18}这样输出就比原来的更直观。27.4 Override 注解在重写方法时前面都会写Override例如OverridepublicStringtoString(){return...;}那么它有什么作用什么是注解注解Annotation可以理解为写给编译器看的说明信息。它不会直接改变程序的运行结果但可以帮助编译器检查代码。Override 的作用Override 表示当前方法是重写Override父类的方法。例如OverridepublicStringtoString(){returnStudent{namename, ageage};}编译器会检查父类中是否真的存在 toString() 方法方法名、参数列表、返回值类型是否符合重写规则。如果写错了例如OverridepublicStringtostring(){returnStudent;}由于方法名写成了 tostring小写 s编译器会立即报错帮助我们及时发现问题。如果没有 OverridepublicStringtostring(){returnStudent;}程序虽然能够编译通过但这并不是重写而是定义了一个新的方法。此时调用System.out.println(stu);仍然会执行 Object 类中的 toString()输出依旧是Student3b07d329因此建议在所有重写方法前都加上 Override 注解它不仅能提高代码可读性还能帮助编译器进行检查减少低级错误。28、对象数组一个学生需要创建一个对象那么多个学生就要创建多个对象。如果现在班级只有一名学生这样写完全没有问题。但是如果班级有50名学生呢。难道要写Studentstu1newStudent(张三,18);Studentstu2newStudent(李四,19);Studentstu3newStudent(王五,20);......Studentstu50newStudent(赵六,18);很明显这种方式既麻烦又不利于后续管理。那么有没有一种方式能够统一管理多个 Student 对象答案就是对象数组。28.1 为什么需要对象数组之前学习整数数组int[]nums{10,20,30,40,50};数组中存放的是1020304050那么Student 能不能也放到数组中当然可以。例如Student[]studentsnewStudent[50];这样就可以保存 50 个 Student 对象。因此对象数组就是元素类型为引用数据类型对象的数组。它最大的作用就是统一管理多个对象。例如学校Student[]↓ 张三 李四 王五 赵六以后学习 ArrayList 的时候其实底层思想也是类似的。28.2 对象数组的定义对象数组定义方式与普通数组完全一致。语法格式类名[]数组名;例如Student[]students;也可以Studentstudents[];但是推荐Student[]students;这样符合 Java 官方规范。创建对象数组使用new创建。例如Student[]studentsnewStudent[5];表示创建一个长度为 5 的 Student 数组。注意这里创建的是数组对象。不是Student 对象。这一点非常重要。28.3 对象数组初始化对象数组有三种初始化方式。第一种动态初始化例如Student[]studentsnewStudent[3];运行后数组有三个元素每个元素都为 null。那为什么不是三个 Student 对象而是 null 。原因new Student[3] 只是创建了数组没有创建 Student 对象。因此数组中的每一个元素默认值都是null因为 Student 属于引用类型。引用类型默认值就是null第二种静态初始化例如Students1newStudent(张三,18);Students2newStudent(李四,20);Student[]students{s1,s2};或者Student[]students{newStudent(张三,18),newStudent(李四,20),newStudent(王五,19)};创建数组同时创建对象。第三种先创建数组再存放对象例如Student[]studentsnewStudent[3];students[0]newStudent(张三,18);students[1]newStudent(李四,20);students[2]newStudent(王五,19);这种方式是比较常用的。28.4 对象数组访问访问方式与普通数组完全一致。例如System.out.println(students[0]);输出Student{name张三,age18}访问成员System.out.println(students[0].getName());输出张三修改对象students[0].setAge(20);再次打印System.out.println(students[0]);输出Student{name张三,age20}说明数组保存的是对象引用。28.5 对象数组遍历遍历方式与普通数组一样。例如for(inti0;istudents.length;i){System.out.println(students[i]);}增强 forfor(Studentstu:students){System.out.println(stu);}输出Student{name张三,age18}Student{name李四,age20}Student{name王五,age19}28.6 JVM 内存分析下面我们来分析一下对象数组在内存中到底是怎么存储的。来看Student[]studentsnewStudent[3];JVM 内存可以看到数组里面没有 Student只有三个 null继续students[0]newStudent(张三,18);内存数组里面保存的是Student对象地址而不是对象本身。继续students[1]newStudent(李四,20);students[2]newStudent(王五,19);最终因此对象数组保存的是对象引用地址真正的对象依然存放在堆内存中。