目录
1. Object类是Java中所有类的默认父类,无须使用extends来定义。class声明的类都有一个父类,Object类。
Java中引用数据类型之间的相等比较使用equlas方法。不能使用"==",其比较的是地址。
3. Object不仅是所有类(class)的父类,JDK对Object类做了扩展。
Object类
Java中所有类的共同父类——Object类
全名称:包名.类名——java.lang.Object;
1. Object类是Java中所有类的默认父类,无须使用extends来定义。class声明的类都有一个父类,Object类。
因为Object类是所有类的父类,使用Object引用来接所有的类型,参数最高统—化。
package object_test; import animal.Dog; import preson.Person; public class ObjectTest { public static void main(String[] args) { // object是所有类(包括JDK本身的类、自定义的类)都是object的子类 Object obj1 = "abc"; Object obj2 = new Dog(); Object obj3 = new Person(); } }
Java中所有类都是Object的子类,故所有类型都可以发生向上转型变为Object类型。
2. Object类中的所有方法子类全都可以继承
之所以System.out.println(任意的引用类型),就能输出对应的值,是因为默认都调用该类型的toString()方法,Object类存在toString()方法。
package object_test; import supertest.China; public class ObjectTest { public static void main(String[] args) { Dog dog = new Dog(); System.out.println(dog); } } //输出结果 animal.Dog@1b6d3586
Object类存在toString()方法
如果想输出当前类中的属性值我们就"覆写"toString方法。
package animal; public class Dog extends Animal { @Override public String toString() { //复写Dog类的toString() return "Dog类的toString()方法"; } } package object_test; import animal.Dog; import preson.Person; public class ObjectTest { public static void main(String[] args) { Object obj2 = new Dog(); System.out.println(obj2); } } //输出结果 Dog类的toString()方法
Java中引用数据类型之间的相等比较使用equlas方法。不能使用"==",其比较的是地址。
package object_test; public class ObjectTest { public static void main(String[] args) { Student stu1 = new Student("张三", 88); Student stu2 = new Student("李四", 90); Student stu3 = new Student("李四", 90); // "==" 比较的是数值,对于引用类型来讲,保存的内容就是一个地址 // 比地址 //虽然stu2和stu3属性相等,输出依然false System.out.println(stu1 == stu2); System.out.println(stu2 == stu3); } } class Student { private String name; private int score; public Student(String name, int score) { this.name = name; this.score = score; } } //输出结果 false false
Object默认的equals()方法
此时若需要比较两个对象的属性值是否相同,就需要按照比较规则覆写equals方法。
public class ObjectTest { public static void main(String[] args) { Student stu1 = new Student("张三", 88); Student stu2 = new Student("李四", 90); Student stu3 = new Student("李四", 90); // 此时若需要比较两个对象的属性值是否相同,就需要按照比较规则覆写equals方法 System.out.println(stu2.equals(stu3)); } } class Student { private String name; private int score; public Student(String name, int score) { this.name = name; this.score = score; } @Override public boolean equals(Object obj) { //覆写equals()方法,实现比较对象属性 // 1.若当前对象就是obj if (this == obj) { return true; } // 2.此时当前对象和obj指向的对象确实不是一个地址 // 若此时obj指向的对象和Student压根没关系。 // 例如:obj指向一个Dog对象,没有可比性,直接返回false if (obj instanceof Student) { // 3.obj这个引用指向的对象确实是Student类的对象且和当前对象不是一个对象 // Object obj = new Student(); Student stu = (Student) obj; return this.score == stu.score && this.name.equals(stu.name); } return false; } } //输出结果 true
此处向下转型的目的:
obj是Object类的对象,但它实际指向了一个Student类的对象。我们需要比较的是Student类中不同对象的name与score,但Object类中没有name和score属性,故不能用obj比较。需要将obj向下转型还原成Student类型。
Student stu = (Student) obj;
用到向下转型的时机:
当一个父类引用实际上指向了一个子类实例时,我们需要调用子类独有的属性或方法时才会用到向下转型。
例如:Person per = new China);
per是指向China对象的Person类型,如果需要用到China独有的属性或方法,就需要把per还原为China类型。
3. Object不仅是所有类(class)的父类,JDK对Object类做了扩展。
Object类可以接收所有引用数据类型的对象(接口,数组,类)
package object_test; interface ITest{ void test(); } class TestImpl implements ITest{ @Override public void test() { System.out.println("TestImpl"); } } public class ObjectTest { public static void main(String[] args) { //接口引用 ITest i1 = new TestImpl(); //整型数组引用 int[] date = new int[10]; //只要是引用类型,都可以用Object接收 Object obj1 = i1; Object obj2 = date; }
因此在Java中,若一个方法参数或者返回值是Object类型,说明该参数或者返回值可以是任意引用数据类型(数组,类,接口)
除了8大基本类型没法用Object类来接收以外,所有类型都能使用Object来接收。
JDK中的常用接口
接口优先原则
当一个场景既可以使用抽象类也可以使用接口定义时,优先考虑使用接口(更灵活)。
1. java.lang.Comparable;比较接口
当一个类实现了Comparable接口,表示该类具备了可比较的能力。
package object_test; import java.util.Arrays; public class ComparableTest { public static void main(String[] args) { //date整型数组,创建了3个整型变量 int[] date = new int[]{ 8, 3, 5 }; Arrays.sort(date); System.out.println(Arrays.toString(date)); //对象数组 //per是Person类型的数组,创建了三个Person类型的变量 Person[] per = new Person[]{ new Person("张三", 30), new Person("李四", 18), new Person("王五", 20) }; Arrays.sort(per); //报错 System.out.println(Arrays.toString(per)); } } class Person { String name; int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
由于Person这个类型是自定义类型,对于编译器来说,不像int这样大小关系一目了然。到底哪个Person对象大,哪个Person对象小,编译器无从得知。
要让Person这个类型具备可比较的能力,即让JDK知道Person对象"谁大谁小",就需要让Person类实现Compareable接口,覆写抽象方法compareTo。
覆写抽象方法compareTo
此处int返回值
>0 表示当前对象this > 传入对象o
=0 表示当前对象this = 传入对象o
<0 表示当前对象this < 传入对象o@Override public int compareTo(Object o) { if (this == o) { return 0; } if (o instanceof Person) { //当前传入的o就是Person类型的引用,向下转型还原为Person //要比较Person对象的大小关系,就要用到Person独有的属性,向下转型 Person per = (Person) o; //此时根据年龄大小比较 return this.age - per.age; } //报错,抛出异常 throw new IllegalArgumentException("不是Person类型,无法比较!"); }
Arrays.sort()方法的内部实际上就是根据Compable接口的compareTo方法的返回值进行比较。
覆写sort()方法,升序排序对象数组
public static void sort(Comparable[] arr) { for (int i = 0; i < arr.length - 1; i++) { for (int j = 0; j < arr.length - i - 1; j++) { if (arr[j].compareTo(arr[j + 1]) > 0) { //前一个大于后一个,交换j、j+1 Comparable tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; } } } }
当arr[j].compareTo(arr[j +1]) > 0; 我就认为arr[j] ">" arr[j + 1]。
返回值>0表示当前对象(this)"大于"传入对象(o)。
整体代码
package object_test; import java.util.Arrays; public class ComparableTest { public static void sort(Comparable[] arr) { for (int i = 0; i < arr.length - 1; i++) { for (int j = 0; j < arr.length - i - 1; j++) { if (arr[j].compareTo(arr[j + 1]) > 0) { //前一个大于后一个,交换j、j+1 Comparable tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; } } } } public static void main(String[] args) { //date整型数组,创建了3个整型变量 int[] date = new int[]{ 8, 3, 5 }; Arrays.sort(date); System.out.println(Arrays.toString(date)); //对象数组 //per是Person类型的数组,创建了三个Person类型的变量 Person[] per = new Person[]{ new Person("张三", 30), new Person("李四", 18), new Person("王五", 20) }; sort(per); System.out.println(Arrays.toString(per)); } } class Person implements Comparable { String name; int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } @Override public int compareTo(Object o) { if (this == o) { return 0; } if (o instanceof Person) { //当前传入的o就是Person类型的引用,向下转型还原为Person //要比较Person对象的大小关系,就要用到Person独有的属性,向下转型 Person per = (Person) o; //此时根据年龄大小比较 return this.age - per.age; } //报错,抛出异常 throw new IllegalArgumentException("不是Person类型,无法比较!"); } }
2. java.lang.Cloneable; 克隆接口
原对象和新产生对象确实是两个独立的对象,新产生的对象是通过原对象"拷贝"而来的,属性值和原对象完全一致。
要让一个类具备可复制的能力,实现Cloneable接口,覆写clone方法 。
package object_test; public class CloneableTest { public static void main(String[] args) { } } class Animal implements Cloneable{ String name; public Animal(String name) { this.name = name; } }
此时普通子类Animal并未覆写父类接口Cloneable的抽象方法,原因是父类Cloneable中就没有抽象方法。
类似Cloneable接口,称之为"标记"接口,这个接口本身内部没有任何抽象方法,只有打上这个标记的子类才具备克隆的能力。
JVM在运行时会检查所有实现了Cloneable接口的子类,赋予其克隆的能力。clone方法是Object提供的方法。
覆写clone方法
public Animal Clone(){ Animal newAnimal = null; try { newAnimal = (Animal) super.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } return newAnimal; }
覆写Object类的clone()方法,返回值为Animal类,最后返回Animal类型的newAniaml。避免类型转换。
整体代码
package object_test; public class CloneableTest { public static void main(String[] args) { Animal animal1 = new Animal("test"); // animal2是通过animal1克隆复制来的,确实是两个独立的对象,属性值完全相同 Animal animal2 = animal1.Clone(); System.out.println(animal1 == animal2); System.out.println(animal2.name); } } class Animal implements Cloneable { String name; public Animal(String name) { this.name = name; } public Animal Clone() { Animal newAnimal = null; try { newAnimal = (Animal) super.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } return newAnimal; } }
false说明animal1与animal2地址不同;输出test表示,animal1的name属性成功克隆给animal2的name属性了。
若此时,新创建animal3,Animal animal3 = new Animal("test");
animal2和animal3到底有何不—样?
animal1和animal3彼此之间没有任何关系,只是产生animal3的时候属性值恰好等于animal1而已。
animal2的产生是依赖于animal1而产生,animal3的产生和animal1没任何关系。
问题:clone方法产生的对象是否调用构造方法?
答:不会。
在Java中产生—个新对象有两种方式
1. 最常见的通过构造方法产生对象
new 类();——>当有new关键字,就在堆上开辟该类相应属性的空间,给属性赋默认值。
2. 通过clone()方法产生对象。调用clone()方法时,JVM会开辟与原对象内存大小完全相同的新空间,并将对象中属性的值从原对象中复制—份。(不推荐,Java之父都不推荐使用这种方式产生对象)
深浅拷贝(了解概念)
浅拷贝
浅拷贝,最外层对象确实是由原对象的clone方法产生的新对象,属性值与原对象保持一致,原对象和新对象的内部若包含其他类的引用,这些引用指向的对象是相同的,并没有产生新对象。
克隆的对象和原对象确实是两个独立的对象——b1和b2。
对象b1的内部若包含了其他类的引用a,克隆后的b2包含的其他类的引用并没有产生新的对象——>浅拷贝,b1.a和b2.a指向相同的A对象。浅拷贝就是克隆的对象和原对象内部包含的其他对象是一个东西。
深拷贝
深拷贝,最外层对象确实是由原对象的clone方法产生的新对象,属性值与原对象保持一致,原对象和新对象的内部若包含其他类的引用,这些引用指向的对象也是新对象。
b2克隆对象内部包含的其他类的引用也产生了新的对象——>深拷贝,b1.a和b2.a指向不同的a对象。
深拷贝就是克隆对象和原对象内部包含的其他对象也是克隆出来的。
Java中深拷贝的实现方式:
1.递归使用clone方法。
2.序列化。(json字符串)