Java基础:Day_08 继承与多态

一、继承思想

1.什么是继承关系:
基于某个父类对对象的定义加以拓展,而产生新的子类定义,子类可以继承父类原来的某些定义,也可以增加原来父类所没有的定义,或者覆写父类中的某些特性。例如下面代码:
在这里插入图片描述
我们可以看到,当在定义“老师类”、“学生类”、“员工类”的时候,他们都具有一些共同的属性和方法,当然也有不同的String。那么我们可以把它统统写成:
在这里插入图片描述

这样以来从面向对象的角度上说:继承是一种从一般到特殊的关系,是一种“is a”的关系,即子类是对父类的拓展。
语法格式: 在定义子类的时候来表明自己需要拓展于哪一个父类.

public class 子类名  extends 父类类名
{
         编写自己特有的状态和行为
}

在Java中,类和类之间的继承关系只允许单继承,不允许多继承。也就是说一个类A,只能有一个直接的父类,不能出现类A同时继承于类B和类C。但是,Java中允许多重继承(区分多继承和多重继承
多重继承例子:
动物有胎生动物和卵生动物之分,胎生动物有老虎,老虎又分华南虎,东北虎,孟加拉虎等。也就是A被B继承,B又被C继承。

在Java中除了Object类之外,每一个类都有一个直接的父类.
比如:class Student extends Person{}
我们就说此时Student的直接父类是Person.
问题: class Person{} ,此时Person的父类又是谁?
Object类是Java语言的根类(老祖宗,任何类都是Object的子类.)
class Person{} 等价于 class Person extends Object{}

继承关系的作用:
1):解决了代码的重复问题。
2):真正的作用,表示出一个体系。

先写父类还是先写子类:
一般的,我们在开发工程中先编写多个自定义类,写完之后,发现多个类之间存在共同的代码,此时可以抽去出一个父类。我们以后做开发,都是基于框架/组件来做的,我们是在别人的基础之上,继续做开发。好比,别人提供清水房,我们只需要在清水房的基础之上装修,就可以使用。以后,我们定义新的类去继承于框架中/组件中提供的父类。

子类继承父类之后,可以拥有父类的某一些状态和行为(子类复用了父类的功能或状态).
子类到底继承了父类的哪些成员(根据访问修饰符来判断):
1):如果父类中的成员使用public修饰,子类继承。
2):如果父类中的成员使用protected修饰,子类也继承,即使父类和子类不在同一个包中。
3):如果父类和子类在同一个包中,此时子类可有继承父类中 缺省修饰符的成员。
4):如果父类中的成员使用private修饰,子类打死都继承不到.因为private只能在本类中访问。
5):父类的构造器,子类也不能继承,因为构造器必须和当前的类名相同。

二、方法的覆写

1.方法覆写的定义
场景:
子类拓展了父类,可以获得父类的部分方法和成员变量。可是当父类的某个方法不适合于子类本身的特征时,此时怎么办?
如:企鹅(Penguin)和鸵鸟(Ostrich)是鸟类中的一个特殊品种,所以企鹅/鸵鸟类是鸟类的一个子类,但是鸟类有飞翔的功能,但是对应企鹅/鸵鸟,飞翔的行为显然不适合于它。
此时肿么办?

此时可以对方法进行覆写,效果如下:

扫描二维码关注公众号,回复: 6030384 查看本文章
class Bird
{
	public String name;
	public int age;
	
	public void fly()
	{
		System.out.println("我可以飞!");
	}
}

class Penguin extends Bird
{
	public void fly()	//@Override public void fly()
	{
		System.out.println("我飞不起来!");
	}
}

方法覆写的原则(一同两小一大):Override
一同:
①实例方法签名必须相同。 (方法签名= 方法名 + 方法的参数列表)

两小:
②子类方法的返回值类型是和父类方法的返回类型相同或者是其子类。子类可以返回一个更加具体的类.
③子类方法声明抛出的异常类型和父类方法声明抛出的异常类型相同或者是其子类。
子类方法中声明抛出的异常小于或等于父类方法声明抛出异常类型;
子类方法可以同时声明抛出多个属于父类方法声明抛出异常类的子类(RuntimeException类型除外);

一大:
④子类方法的访问权限比父类方法访问权限更大或相等。
private修饰的方法不能被子类所继承,也就不存在覆盖的概念.

判断是否是覆写方法的必杀技:@Override标签:若方法是覆写方法,在方法前或上贴上该标签, 编译通过,否则,编译出错。

只有方法存在覆盖的概念,字段没有覆盖。
方法覆盖解决的问题: 当父类的某一个行为不符合子类具体的特征的时候,此时子类需要重新定义父类的方法,并重写方法体.

2.方法重载和方法覆盖(方法重写)的区别:

方法重载: Overload
方法重写: Override
批判,本身二者一点关系都没有,仅仅只是因为名字很像.
方法重载: Overload
作用: 解决了同一个类中,相同功能的方法名不同的问题.
既然是相同的功能,那么方法的名字就应该相同.
规则: 两同一不同.
同类中,方法名相同,方法参数列表不同(参数类型,参数个数,参数顺序).
方法重写: Override
作用:解决子类继承父类之后,可能父类的某一个方法不满足子类的具体特征,此时需要重新在子类中定义该方法,并重写方法体.
规则: 一同两小,一大.
一同:父类和子类的方法签名是相同的,所以,建议:直接拷贝父类中方法的定义到子类中,再重写方法体,就OK了.

三、super关键字

当我们产生了一个这样的需求:在子类中的某一个方法中,去调用父类被覆盖的方法

class Bird
{
	public void fly()
	{
		System.out.println("飞翔");
	}
}

class Penguin extends Bird
{
	 public void fly()  
     {
		System.out.println("飞不起来!");			
     }

	 public void say()
	 {
		System.out.println("我要唱歌!!");
		fly();
		//以上是调用子类所覆盖的,其实是this.fly
		//如果要调用父类Bird的fly如下:
		super.fly();

	 }
}

//在子类方法中

class SuperDemo 
{
	public static void main(String[] args) 
	{
		Penguin p = new Penguin();
		p.say();
	}
}

什么是super:
this: 当前对象,谁调用this所在的方法,this就是哪一个对象.
super: 当前对象的父类对象.

子类初始化过程:创建子类对象的过程.
在创建子类对象之前,会先创建父类对象.
调用子类构造器之前,在子类构造器中会先调用父类的构造器,
默认调用的是父类无参数构造器…
1): 如果父类不存在可以被子类访问的构造器,则不能存在子类.
2):如果父类没有提供无参数构造器,此时子类必须显示通过super语句去调用父类带参数的构造器.

//在创建对象之前会先调用父类的构造器
class Animal
{
	private String name;
	private int age;

	Animal()
	{
		System.out.println("Animal的构造器");
	}
}

class Fish extends Animal
{
	private String color;

	Fish()
	{	
		//此处存在一个隐式的super();
		System.out.println("Fish的构造器");
	}
}

class Demo
{
	public static void main(String[] args)
	{
		Fish f = new Fish();
	}
}

输出结果为:

---------- 运行java ----------
Animal的构造器
Fish的构造器

如果父类构造器存在参数,那么将调用含参数的父类构造器,此时子类的构造器也必须先写入参数。

另外,super必须为构造器中的第一个语句!

//场景3
class Animal
{
	private String name;
	private int age;

	Animal(String name,int age)//接收子类
	{
		this.name = name;
		this.age = age;
	}

	public String getName()
	{
		return name;
	}
	public int  getAge()
	{
		return age;
	}
}

class Fish extends Animal
{
	private String color;

	Fish(String name,int age,String color)//问题来了,只有color是Fish私有的,而name和age是Animal私有的
	{	//假如此处用super.name = name 来接收传递过来的Nico,name是Animal的private类型。不可行
		//父类拥有其字段,只有让父类对其赋值
		super(name,age);//所以这句话的意思是:爸爸你来!
		this.color = color;
	}

	public void say()
	{
		System.out.println(getName()  + getAge() + color);//其实此处省略了super.getName + super.getName + this.color
	}
}

class Demo
{
	public static void main(String[] args)
	{
		Fish f = new Fish("Nico",5,"Yellow");
		f.say();
	}
}

3.隐藏(遮蔽)

super关键字的使用场景:
1):可以使用super解决子类隐藏了父类的字段情况.该情况,我们一般不讨论,因为破坏封装.
2):在子类方法中,调用父类被覆盖的方法,引出super的例子,此时必须使用super.
3):在子类构造器中,调用父类构造器,此时必须使用super语句:super([实参]).

//使用场景1
class SuperClass{
      public   String name ="superClass.name";
}

class SubClass extends SuperClass
{
     public   int name = 18; //隐藏了父类的name字段

     public void doWork()
     {        
         System.out.println(name);  
		 System.out.println(super.name);           
     }
}

class Demo
{
	public static void main(String[] args)
	{
		SubClass s = new SubClass();
		s.doWork();
	}
}

场景2参考方法的覆写。
场景3参考上面的场景3代码。

所谓隐藏就是“遮蔽”的意思。

① 满足继承的访问权限下,隐藏父类静态方法:若子类定义的静态方法的签名和超类中的静态方法签名相同,那么此时就是隐藏父类方法。注意:仅仅是静态方法,子类存在和父类一模一样的静态方法(static方法).

//方法的隐藏
class SuperClass
{
	public static void textMethod(){}
}
class SubClass extends SuperClass
{
	public static void textMethod(){}
}

② 满足继承的访问权限下,隐藏父类字段:若子类中定义的字段和超类中的字段名相同(不管类型),此时就是隐藏父类字段,此时只能通过super访问被隐藏的字段。

③ 隐藏本类字段:若本类中某局部变量名和字段名相同,此时就是隐藏本类字段,此时只能通过this访问被隐藏的字段。

static不能和super以及this共存!

四、Object类

Object类的常见方法:从
1): protected void finalize() :当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。垃圾回收器在回收某一个对象之前,会先调用该方法,做扫尾操作. 该方法我们不要去调用。
2): Class getClass() :返回当前对象的真实类型。
3): int hashCode(): 返回该对象的哈希码值,hashCode决定了对象再哈希表中的存储位置,不同对象的hashCode是不一样的.
4): boolean equals(Object obj) :拿当前对象(this)和参数obj做比较.
在Object类中的equals方法,本身和 “ == ”符号相同,都是比较对象的内存地址.
官方建议:每个类都应该覆盖equals方法,不要比较内存地址,而去比较我们关心的数据.,因为我们关系的是内容数据,而不是内存地址.
比如:两个学生对象,我们不管是如何new出来的,只要学号相同我就应该认为是同一个对象.
两个字符串,只要内容相同,我们就认为是同一个字符串.
5):String toString():表示把一个对象转换为字符串.
打印对象时,其实打印的就是对象的toString方法.
System.out.println(obj对象);等价于 System.out.println(obj对象.toString());
默认情况下打印对象,打印的是对象的十六进制的hashCode值,但是我们更关系对象中存储的数据.
官方建议我们:应该每个类都应该覆盖toString,返回我们关心的数据.

五、多态思想

1.多态问题的引出
学了继承关系,我们知道继承关系是一种”is A”的关系,也就说子类是父类的一种特殊情况
问题: 子类的对象是动物?
既然子类是一种特殊的父类,那么我们可不可以认为狗对象/猫对象就是动物类型的对象.
Animal d = new Dog(); //创建一只狗对象
Animal c = new Cat(); //创建一只猫对象
得到以下代码:
在这里插入图片描述
此时多态就产生了。

Animal a = new Dog();//编译类型为Animal,运行类型为Dog

对象(a)具有两种类型:

编译类型: 声明对象变量的类型,Animal,表示把对象看出什么类型.
运行类型: 对象的真实类型,Dog.运行类型—>对象的真实类型.
编译类型必须是运行类型的父类/或相同.
当编译类型和运行类型不同的时候,多态就出现了.
所谓多态: 对象具有多种形态,对象可以存在不同的形式.
Animal a = null;
a = new Dog(); //a此时表示Dog类型的形态
a = new Cat(); //a此时表示Cat类型的形态

多态的前提:可以是继承关系(类和类)/也可以是实现关系(接口和实现类),在开发中多态一般都指第二种.

我家里养了一只动物,名字叫”乖乖”,此时”乖乖”可以有多种形态;
乖乖 是狗, 乖乖的叫声: one one one.
乖乖 是猫, 乖乖的叫声: 喵 喵 喵.
多态的特点:
把子类对象赋给父类变量,在运行时期会表现出具体的子类特征(调用子类的方法).
Animal a = new Dog();

class Person
{
	private String name;
	private int    age;

	public void feed(Dog d)
	{
		System.out.println("喂养狗");
		d.eat();
	}

	public void feed(Cat c)
	{
		System.out.println("喂养猫");
		c.eat();
	}
}

class Animal
{
	public void eat()
	{
		System.out.println("吃食物");
	}
}
class Dog extends Animal
{
	public void eat()
	{
		System.out.println("吃狗粮");
	}
}


class Cat extends Animal
{
	public void eat()
	{
		System.out.println("吃猫粮");
	}
}

class Demo
{
	public static void main(String[] args)
	{
		Person p = new Person();
		Dog d = new Dog();
		p.feed(d);
		Cat c = new Cat();
		p.feed(c);	
	}
}

尽管上面这段代码没有语法问题,但是依然是十分臃肿的。每次喂养一个动物,就要创建一个喂养这种动物的方法,那么有没有方法来解决这个问题呢?
我们只需要把其中的Person类修改一下,代码如下:

class Person
{
	private String name;
	private int    age;

	public void feed(Animal a)
	{
		System.out.println("喂养");
		a.eat();
	}
}

此时,我们只需要定义一个方法feed,参数为其父类分别调用他们的eat方法就可以了。

2.多态方法的调用问题

前提:必须先存在多态情况:
存在父类:SuperClass,子类:SubClass,方法:doWork.
接下来用测试代码看一下执行情况:

SuperClass clz =  new SubClass();//多态
 clz.doWork();				     //输出什么结果
//情况一:doWork方法存在于SuperClass中,不存在于SubClass中.
class SuperClass
{
	public void doWork()
	{
		System.out.println("SuperClass");
	}
}

class SubClass extends SuperClass
{

}

class Demo
{
	public static void main(String[] args)
	{
		SuperClass clz = new SubClass();
		clz.doWork();
	}
}
//此时执行结果: SuperClass
 //应该先从SubClass类中去找doWork方法,找不到,再去父类SuperClass类中找.
//情况二:doWork方法存在于SubClass中,不存在于SuperClass中.
class SuperClass
{

}

class SubClass extends SuperClass
{
	public void doWork()
	{
		System.out.println("SubClass");
	}
}

class Demo
{
	public static void main(String[] args)
	{
		SuperClass clz = new SubClass();
		clz.doWork();
	}
}
// 错误: 找不到符号
/*编译时期,会去编译类型(SuperClass)中找是否有doWork方法:
                  找    到:编译通过.
                  找不到:编译报错.  
*/
//情况三: doWork方法存在于SuperClass和SubClass中.

class SuperClass
{
	public void doWork()
	{
		System.out.println("SuperClass");
	}
}

class SubClass extends SuperClass
{
	public void doWork()
	{
		System.out.println("SubClass");
	}
}

class Demo
{
	public static void main(String[] args)
	{
		SuperClass clz = new SubClass();
		clz.doWork();
	}
}
//SubClass
/*编译通过,执行SubClass的doWork方法.
      在运行时期,调用运行类型(SubClass)中的方法. 
*/
//情况四: doWork方法存在于SuperClass和SubClass中,但是doWork是静态方法.
class SuperClass
{
	public static void doWork()
	{
		System.out.println("SuperClass");
	}
}

class SubClass extends SuperClass
{
	public static void doWork()
	{
		System.out.println("SubClass");
	}
}

class Demo
{
	public static void main(String[] args)
	{
		SuperClass clz = new SubClass();
		clz.doWork();
	}
}
//	SuperClass

/*
	编译通过,执行SuperClass的doWork方法.
    静态方法的调用只需要类即可.
    如果使用对象来调用静态方法,其实使用的是对象的编译类型来调用静态方法.
    和对象没有关系.
*/

五、引用类型转换

引用类型的转换:
引用类型的大和小,指的是 父类 和子类的关系.
自动类型转换: 把子类对象赋给父类变量(多态).
Animal a = new Dog();
Object是所有类的根类:
Object obj = new Dog();
强制类型转换: 把父类类型对象赋给子类类型变量(当时该父类类型变量的真实类型应该是子类类型).
Animal a = new Dog();
Dog d = (Dog)a;

instanceof  运算符: 判断该对象是否是某一个类的实例.
语法格式:boolean b = 对象A   instanceof  类B;  // 判断 A对象是否是 B类的实例,如果是,返回true.

在开发中,有时候我们只需要关心它的真实类型的实例,而不是编译的实例。
在这里插入图片描述
六、组合关系

继承关系: 子类可以继承到父类中部分的成员,那么此时子类是可以修改到父类的信息的.
继承关系破坏封装,为了复用代码可能会让子类具有不该具有的功能.

为什么引入继承: 为了代码复用问题.
解决代码复用问题,不一定非要使用继承,也可以使用”包含关系”(has A).
我没钱,但是我想开豪车,我想吃火锅:
方式1: 任一个富豪干爹. 继承关系:
方式2: 把一个富豪绑架在我家里,挟天子以令诸侯! 组合关系/包含.

在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/PandaNotPan/article/details/89286579