一、方法重写/覆盖(override)
基本介绍
方法覆盖(重写)就是子类有一个方法,和父类的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类的方法。
使用案例
public class Animal {
public void cry() {
System.out.println("动物叫唤..");
}
}
public class Dog extends Animal{
//1. 因为 Dog 是 Animal 子类
//2. Dog 的 cry 方法和 Animal 的 cry 定义形式一样(名称、返回类型、参数)
//3. 这时我们就说 Dog 的 cry 方法,重写了 Animal 的 cry 方法
public void cry() {
System.out.println("小狗汪汪叫..");
}
}
注意事项
1)子类的方法的方法名称,形参列表,要和父类方法的方法名称,形参列表完全一样。
2)子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类
比如:父类返回类型为Object,子类返回类型是String
Public Object getInfo() { }
public String getInfo() { }
3)子类方法不能缩小父类方法的访问权限
public > protected > 默认 > private
重写(override)和重载(overload)的区别
名称 | 发生范围 | 方法名 | 形参列表 | 返回类型 | 修饰符 |
---|---|---|---|---|---|
重载(overload) | 本类 | 必须一样 | 类型、个数或者顺序至少有一个不同 | 无要求 | 无要求 |
重写(override) | 父子类 | 必须一样 | 相同 | 子类方法不能缩小父类方法的访问范围 |
二、面向对象编程-多态
基本介绍
方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
多态的具体体现
1)方法的多态
public class PloyMethod {
public static void main(String[] args) {
//方法重载体现多态
A a = new A();
//这里我们传入不同的参数,就会调用不同 sum 方法,就体现多态
System.out.println(a.sum(10, 20));
System.out.println(a.sum(10, 20, 30));
//方法重写体现多态
B b = new B();
a.say();
b.say();
}
}
class B { //父类
public void say() {
System.out.println("B say() 方法被调用...");
}
}
class A extends B {//子类
public int sum(int n1, int n2){
//和下面 sum 构成重载
return n1 + n2;
}
public int sum(int n1, int n2, int n3){
return n1 + n2 + n3;
}
public void say() {
System.out.println("Asay() 方法被调用...");
}
}
//结论:重写和重载就体现多态
2)对象的多态
1 - 一个对象的编译类型和运行类型可以不一致
2 - 编译类型在定义对象时,就确定了,不能被改变
3 - 运行类型是可以变化的
4 - 编译类型看定义时 =号的左边,运行类型看 =号的右边
比如:
Animal animal = new Dog();
//animal的编译类型是Animal,运行类型是Dog
animal = new Cat();
//animal的运行类型变成了Cat,编译类型仍然是Animal
补充:编译类型和运行类型
例如:Person person = new Student();
这行代码将会生成一个person变量,该变量的编译时类型是Person,运行时类型是Student
类在继承时,子类会覆盖与父类相同的属性
总结一点就是:对象访问变量看声明,访问方法看实际对象类型(new出来的类型)
java允许把一个子类对象直接赋值给一个父类引用变量,无须任何类型转换
或者被称为向上转型,由系统自动完成
//例: 在一个主人类master中的喂食方法
Public void feed(Dog dog, Bone bone) {
System.out.println(this.name + “给” + dog.getName() + “eat” + bone.getName());
}
Public void feed(Cat cat, Fish fish) {
System.out.println(this.name + “给” + cat.getName() + “eat” + fish.getName());
}
//为了提高代码的复用性,减少冗余代码量,可使用以下方法代替:
Public void feed(Animal animal, Food food) {
System.out.println(this.name + “给” + animal.getName() + “eat” + food.getName());
}
//利用多态的原理:
//animal变量的编译类型是Animal,可以指向(接收)Animal子类的对象
//food变量的编译类型是Food,可以指向(接收)Food子类的对象
多态注意事项
多态的前提是:两个对象(类)存在继承关系
-
多态的向上转型
1)本质:父类的引用指向了子类的对象 2)语法: 父类类型 引用名 = new 子类类型(); 3)特点:编译类型看左边,运行类型看右边。
//向上转型: 父类的引用 指向了 子类的对象
//语法:父类类型 引用名 = new 子类类型();
Animal animal = new Cat();
Object obj = new Cat();// Object 也是 Cat 的父类
//向上转型调用方法的规则如下:
//(1) 可以调用父类中的所有成员(需遵守访问权限)
//(2) 但是不能调用子类的特有的成员
//(3) 因为在编译阶段,能调用哪些成员,是由编译类型来决定的 //animal.catchMouse();错误
//(4) 最终运行效果看子类(运行类型)的具体实现,即调用方法时,按照从子类(运行类型)开始查找方法 //,然后调用,规则与前面讲的方法调用规则一致。
-
多态的向下转型
1)语法: 子类类型 引用名 = (子类类型)父类引用; 2)只能强转父类的引用,不能强转父类的对象 3)要求父类的引用必须指向的是当前目标类型的对象 4)当向下转型后,可以调用子类类型中所有的成员
//多态的向下转型
//(1) 语法:子类类型 引用名 =(子类类型)父类引用;
//(2) 要求父类的引用必须指向的是当前目标类型的对象
//cat 的编译类型 Cat,运行类型是 Cat
Animal animal1 = new Cat();
Cat cat = (Cat) animal1;
//dog 的编译类型 Dog,运行类型是 Dog
Animal animal2 = new Dog();
Dog dog = (Dog) animal2;
-
补充:
属性没有重写之说!属性的值看编译类型 instanceOf 比较操作符,用于判断对象的运行类型是否为 XX 类型 或 XX 类型的子类型
class AA {} //父类
class BB extends AA {}//子类
BB bb = new BB();
System.out.println(bb instanceof BB);// true
System.out.println(bb instanceof AA);// true
//aa 编译类型 AA, 运行类型是 BB
//BB 是 AA 子类
AA aa = new BB();
System.out.println(aa instanceof AA); // true
System.out.println(aa instanceof BB); // true
Object obj = new Object();
System.out.println(obj instanceof AA);//false
String str = "hello";
//System.out.println(str instanceof AA);
System.out.println(str instanceof Object);//true
Java 的动态绑定机制
1)调用对象方法,方法会和该对象的 运行类型 / 内存地址 绑定.
2)调用对象属性,没有动态绑定机制,哪里声明,使用哪里的属性。
多态的应用
- 多态数组
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
// 应用实例:现有一个继承结构如下:要求创建 1 个 Person 对象、
// 让 Student 和 Teacher 都继承 Person。
// 2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组中,并调用每个对象 say 方法
Person[] persons = new Person[5];
persons[0] = new Person("jack", 20);
persons[1] = new Student("mary", 18, 100);
persons[2] = new Student("smith", 19, 30.1);
persons[3] = new Teacher("scott", 30, 20000);
persons[4] = new Teacher("king", 50, 25000);
- 多态参数
方法定义的形参类型为父类类型,实参允许为子类类型。(后面我们使用接口编程,一般都是子类类型)
三、Object 类详解
1. equals 方法
equals 方法
== 和 equals 的对比
== 是一个比较运算符
1)既可以判断基本类型,又可以判断引用类型
2)如果判断基本类型,判断的是 值是否相等
示例:
int i = 10; double d = 10.0;
3)如果判断引用类型,判断的是 地址是否相等
即判断是不是同一个对象
equals 是 Object类中的方法
1)equals 只能判断引用类型
2)默认判断的是地址是否相等,子类中往往重写该方法
用于判断内容是否相等
比如Integer,String
String类 重写equals方法源码
/*
//看看 Jdk 的源码,String 类的 equals 方法
//把 Object 的 equals 方法重写了,变成了比较两个字符串值是否相同
*/
public boolean equals(Object anObject) {
if (this == anObject) { //如果是同一个对象
return true;//返回 true
}
if (anObject instanceof String) { //判断类型
String anotherString = (String)anObject; //向下转型
int n = value.length;
if (n == anotherString.value.length) { //如果长度相同
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) { //然后一个一个的比较字符
if (v1[i] != v2[i])
return false;
i++;
}
return true; //如果两个字符串的所有字符都相等,则返回 true
}
}
return false; //如果比较的不是字符串,则直接返回 false
}
Object类 equals方法源码
//即 Object类的 equals 方法默认就是比较对象地址是否相同
//也就是判断两个对象是不是同一个对象.
public boolean equals(Object obj) {
return (this == obj);
}
Integer类 重写equals方法源码
//从源码可以看到 Integer类 也重写了 Object类的 equals 方法
//变成了判断两个值是否相同
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
尝试自己重写equals
假如我们有一个Person对象,有name,age,gender属性,只需要这三个值相同,那么我们就认为这个对象相等。
//重写 Object 的 equals 方法
public boolean equals(Object obj) {
//判断如果比较的两个对象是同一个对象,则直接返回 true
if(this == obj) {
return true;
}
//类型判断
if(obj instanceof Person) {//是 Person,我们才比较
//进行 向下转型, 因为我需要得到 obj 的 各个属性
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age && this.gender == p.gender;
}
//如果不是 Person ,则直接返回 false
return false;
}
2. hashCode 方法
基本介绍
1)提高具有哈希结构的容器的效率。
2)两个引用,如果指向的是同一个对象,则哈希值肯定是一样的。
3)两个引用,如果指向的是不同对象,则哈希值是不一样的。
4)哈希值主要根据地址号来的!,但不能完全将哈希值等价于地址。
5)后面在集合中,如果需要 hashCode 的话,也会重写。
public class HashCode_ {
public static void main(String[] args) {
AA aa = new AA();
AA aa2 = new AA();
AA aa3 = aa;
System.out.println("aa.hashCode()=" + aa.hashCode());
System.out.println("aa2.hashCode()=" + aa2.hashCode());
System.out.println("aa3.hashCode()=" + aa3.hashCode());
}
}
class AA {}
3. toString 方法
基本介绍
1)默认返回:全类名+@+哈希值的十六进制
【查看 Object 的 toString 方法】
2)子类往往重写 toString 方法,用于返回对象的属性信息。
3)重写 toString 方法,打印对象或拼接对象时,都会自动调用该对象的 toString 形式。
4)当直接输出一个对象时,toString 方法会被默认的调用。
比如System.out.println(monster); 就会默认调用 monster.toString()
Object 的 toString() 源码
//(1)getClass().getName() 类的全类名(包名+类名)
//(2)Integer.toHexString(hashCode()) 将对象的 hashCode 值转成 16 进制字符串
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
4. finalize 方法
基本介绍
当对象被回收时,系统自动调用该对象的 finalize 方法。子类可以重写该方法,做一些释放资源的操作。
什么时候被回收:当某个对象没有任何引用时,则 jvm 就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来 销毁该对象,在销毁该对象前,会先调用 finalize 方法。
垃圾回收机制的调用,是由系统来决定(即有自己的 GC 算法), 也可以通过 System.gc() 主动触发垃圾回收机制。
//重写 finalize
@Override
protected void finalize() throws Throwable {
System.out.println("我们销毁 汽车" + name);
System.out.println("释放了某些资源...");
}
注:本博客引用韩顺平老师Java课程