多态与继承
1.继承:
1.1继承的概念
- 概念
一个类继承另一个类,则称继承的类为子类,被继承的类为父类。 - 主要解决的问题
共性的抽取,实现代码复用
1.共性:都具备的特性。例如猫,狗都属于动物类
2.java中不支持多层继承 建议不超过3层继承 而且只能继承一个类
3.父类=基类 子类=派生类
1.2继承的语法
java中表示类之间继承关系,用extend关键字来实现,具体如下;
修饰符 class 子类 extends 父类{
//代码的实现
}
例:
//父类->动物类
class Animal{
String name;
int age;
public void eat(){
System.out.println(name + "正在吃饭");
}
public void sleep(){
System.out.println(name + "正在睡觉");
}
}
//子类->猫类 继承 动物类
class Cat extends Animal{
//mew是Cat类独有的方法
void mew(){
System.out.println(name + "喵喵喵~~~");
}
}
//子类->狗类 继承 动物类
class Dog extends Animal{
//bark是Dog类独有的方法
void bark(){
System.out.println(name + "汪汪汪~~~");
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
// Dog类中并没有定义任何成员变量,name和age属性肯定是从父类Animal中继承下来的
System.out.println(dog.name);
System.out.println(dog.age);
//Dog中没有eat()和sleep()方法,因为继承父类,所以可以调用父类的eat()和sleep()方法
dog.eat();
dog.sleep();
//bark是Dog类独有的方法
dog.bark();
}
}
注:
1.子类继承父类,会把父类的成员变量和方法继承到子类。
2.子类要有独有的方法,否则继承无意义。
1.3父类成员的调用
1.3.1子类调用父类的成员变量
1.子类与父类的成员变量不同名
public class Base {
int a;
int b;
}
public class Derived extends Base{
int c;
public void method(){
a = 10; // 访问从父类中继承下来的a 并修改
b = 20; // 访问从父类中继承下来的b 并修改
c = 30; // 访问子类自己的c 并修改
}
}
2.子类与父类的成员变量同名
class Base {
int a;
int b;
int c;
}
class Derived extends Base{
int a; // 与父类中成员a同名,且类型相同
char b; // 与父类中成员b同名,但类型不同
public void method(){
a = 100; // 访问父类继承的a,还是子类自己新增的a?
b = 101; // 访问父类继承的b,还是子类自己新增的b?
c = 102; // 子类没有c,访问的肯定是从父类继承下来的c
// d = 103; // 编译失败,因为父类和子类都没有定义成员变量d
}
}
在子类方法中 或者 通过子类对象访问成员时:
- 如果访问的成员变量子类中有,优先访问自己的成员变量。
- 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
- 如果访问的成员变量与父类中成员变量同名,则优先访问自己的。
- 成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。
1.3.2子类中访问父类的成员方法
1.成员方法名字不同
class Base {
public void methodA(){
System.out.println("Base中的methodA()");
}
}
class Derived extends Base{
public void methodB(){
System.out.println("Derived中的methodB()方法");
}
public void methodC(){
methodB(); // 访问子类自己的methodB()
methodA(); // 访问父类继承的methodA()
// methodD(); // 编译失败,在整个继承体系中没有发现方法methodD()
}
总结:成员方法没有同名时,在子类方法中或者通过子类对象访问方法时,则优先访问自己的,
自己没有时再到父类中找,如果父类中也没有则报错。
1.成员方法名字相同
class Base {
public void methodA(){
System.out.println("Base中的methodA()");
}
public void methodB(){
System.out.println("Base中的methodB()");
}
}
class Derived extends Base{
public void methodA(int a) {
System.out.println("Derived中的method(int)方法");
}
public void methodB(){
System.out.println("Derived中的methodB()方法");
}
public void methodC(){
methodA(); // 没有传参,访问父类中的methodA()
methodA(20); // 传递int参数,访问子类中的methodA(int)
methodB(); // 直接访问,则永远访问到的都是子类中的methodB(),基类的无法访问到
}
}
【说明】
派生类(子类):在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。
通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法适传递的参数选择合适的方法访问,如果没有则报错
1.4supe和this关键字
- super作用
在子类方法中访问父类的成员变量和方法
super.变量名:访问父类的普通成员变量
super.方法名();调用父类的的普通成员方法
super():调用父类的构造方法 - this作用
this是当前对象的引用,当前对象即调用实例方法的对象
注:不能利用super调用父类的静态方法
this 会优先范围跟子类自己的,如果子类没有,访问的是父类的
class Base {
int a;
int b;
public void methodA(){
System.out.println("Base中的methodA()");
}
public void methodB(){
System.out.println("Base中的methodB()");
}
}
class Derived extends Base{
int a; // 与父类中成员变量同名且类型相同
char b; // 与父类中成员变量同名但类型不同
// 与父类中methodA()构成重载
public void methodA(int a) {
System.out.println("Derived中的method()方法");
} // 与父类中methodB()构成重写(即原型一致)
public void methodB(){
System.out.println("Derived中的methodB()方法");
}
public void methodC(){
// 对于同名的成员变量,直接访问时,访问的都是子类的->就近原则
a = 100; // 等价于: this.a = 100;
b = 101; // 等价于: this.b = 101;
// 注意:this是当前对象的引用
// 访问父类的成员变量时,需要借助super关键字
// super是获取到子类对象中从父类继承下来的部分
super.a = 200;//修改的父类成员变量数据
super.b = 201;//修改的父类成员变量数据
// 父类和子类中构成重载的方法,直接可以通过参数列表区分清访问父类还是子类方法
methodA(); // 没有传参,访问父类中的methodA()
methodA(20); // 传递int参数,访问子类中的methodA(int)
// 如果在子类中要访问重写的基类方法,则需要借助super关键字
methodB(); // 直接访问,则永远访问到的都是子类中的methodA(),父类类的无法访问到
super.methodB(); // 访问父类的methodB()
}
}
注:
- 只能在非静态方法中使用
- 在子类方法中,访问父类的成员变量和方法。
1.5super和this的区别
- this是当前对象的引用,当前对象即调用实例方法的对象
- super相当于是子类对象中从父类继承下来部分成员的引用
- 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
- 在构造方法中:this()用来访问本类构造方法,super()用于调用父类构造方法,两种调用不能同时在构造方法中出现
- 构造方法中一定会存在super()的调用,用户没有写编译器也会增加,但事实this()用户不写则没有
- super this不能同时出现,只能出现一次(在一个方法中) super this 必须写在方法的第一行
- 当子类构造完成之前,先帮助父类完成初始化
1.5.1代码块的执行顺序
静态代码块>实例代码块>构造代码块
1:父类静态代码块优先于子类代码块执行,且最早执行
2:父类实例代码块和父类构造方法紧接着再执行
3:子类的实例代码块和子类的构造方法紧接着再执行
4:第二次实例化子类对象时,父类和子类的静态代码块都不会再执行
1.6子类构造方法
public class Base {
public Base(){
System.out.println("Base()");
}
}
public class Derived extends Base{
public Derived(){
// super(); // 注意子类构造方法中默认会调用基类的无参构造方法:super(),
// 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句,
// 并且只能出现一次
System.out.println("Derived()");
}
}
public class Test {
public static void main(String[] args) {
Derived d = new Derived();
}
}
结果打印:
Base()
Derived()
释:
在子类构造方法中,并没有写任何关于基类构造的代码,但是在构造子类对象时,先执行基类的构造方法,然后执行子类的构造方法,因为:子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。父子父子肯定是先有父再有子,所以在构造子类对象时,先要调用基类的构造方法,将从基类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整 。
注:
- 在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法。
- 在子类构造方法中,super(…)调用父类构造时,必须是子类构造函数中第一条语句。
- super(…)只能在子类构造方法中出现一次,并且不能和this同时出现
1.7访问修饰符
2.多态
2.1多态的概念
- 概念
- 多态的概念:指多种形态,就是去完成某个行为,当不同的对象去完成时会产生出不同 的状态。
例如:狗和猫都去吃饭,狗吃狗粮,猫吃猫粮。
2.2多态的实现条件
1. 必须在继承体系下
2. 子类必须要对父类中方法进行重写
3. 通过父类的引用调用重写的方法
例:
class Animal {
String name;
int age;
public Animal(String name, int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(name + "吃饭");
}
}
class Cat extends Animal{
public Cat(String name, int age){
super(name, age);
}
public void eat(){
System.out.println(name+"吃小鱼干");
}
}
public class Dog extends Animal {
public Dog(String name, int age){
super(name, age);
}
public void eat(){
System.out.println(name+"吃狼骨头");
}
}
public class Test {
// 编译器在编译代码时,并不知道要调用Dog 还是 Cat 中eat的方法
// 等程序运行起来后,形参a引用的具体对象确定后,才知道调用那个方法
// 注意:此处的形参类型必须时父类类型才可以
public static void eat(Animal a){
a.eat();
}
public static void main(String[] args) {
Cat cat = new Cat("元宝",2);//传入参数
Dog dog = new Dog("小七", 1);//传入参数
eat(cat);
eat(dog);
}
}
运行结果:
元宝吃小鱼干
元宝正在睡觉
小七吃狼骨头
小七正在睡觉
2.3重写(发生再继承)
2.3.1重写的概念
- 概念
重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程
进行重新编写, 返回值和形参都不能改变。即**外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
2.3.2重写的规则
- 子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致
- 被重写的方法返回值类型可以不同,但是必须是具有父子关系的。即继承关系
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为 protected
- 父类被static、private修饰的方法、构造方法都不能被重写。
-重写的方法,可以使用@Override 注解来显式指定,有了这个注解能帮我们进行一些合法性校验。 例如不小心将方法名字拼写错了 (比如写成 aet),那么此时编译器就会发现父类中没有 aet方法,就会编译报错,提示无法构成重写。
2.3.3重写和重载的区别
重写:
1:方法名称相同
2:返回值相同//具有父子关系的可以不相同
3:参数列表相同(数据类型、个数、顺序)
- 子类的访问权限要>=父类的权限
- private修饰的方法、final修饰的方法、static修饰的方法 不可以重写
- 当在父类中调用子类重写方法的时候 此时会调用子类重写的方法
重载:
1:方法名称相同
2:参数列表不同数据类型、个数、顺序)
3:返回值没有要求
重写和重载的区别
区别点 | 重写 | 重载 |
---|---|---|
参数列表 | 不能修改 | 必须修改 |
返回类型 | 不能修改(除非父子关系) | 可以修改 |
访问修饰符 | 可以降低限制 | 可以修改 |
2.4向上、向下转型
2.4.1向上转型
- 概念
创建一个子类对象,将其当成父类对象来使用。 - 语法格式
父类类型 对象名= new 子类类型();
例:Animal animal = new Cat();
class Animal {
String name;
int age;
public void eat(){
System.out.println(name + "吃饭");
}
}
class Cat extends Animal{
public void eat(){
System.out.println(name + "吃小鱼干");
}
}
class Dog extends Animal{
public void eat(){
System.out.println(name + "吃狗粮");
}
}
public class Test {
// 2. 方法传参:形参为父类型引用,可以接收任意子类的对象
public static void eatFood(Animal a){
a.eat();
}
// 3. 作返回值:返回任意子类对象
public static Animal buyAnimal(String var){
if("狗".equals(var) ){
return new Dog("狗狗",1);
}else if("猫" .equals(var)){
return new Cat("猫猫", 1);
}else{
return null;
}
}
public static void main(String[] args) {
Animal cat = new Cat("元宝",2); // 1. 直接赋值:子类对象赋值给父类对象
Dog dog = new Dog("小七", 1);
eatFood(cat);
eatFood(dog);
Animal animal = buyAnimal("狗");
animal.eat();
animal = buyAnimal("猫");
animal.eat();
}
}
优缺点:
向上转型的优点:让代码实现更简单灵活。
向上转型的缺陷:不能调用到子类特有的方法。
2.4.2向下转型
- 概念
为了重新获得因为向上转型而丢失的子类独有的特性 - 格式
父类类型 父类对象 = 子类实例;
子类类型 子类对象 = (子类)父类对象;
例 Animal a = new Dog();
Dog b = (Dog) a;
释:
向下转型不安全,用的比较少,一般情况用instanceof关键字来提高向下转型的安全
2.4.3 instanceof
- 功能
判断一个对象是否是某个类(或接口)的实例
例 对象 instanceof 类(接口)
//如果成立返回 true 否则 flase
if(对象 instanceof 类(接口)){
//运行这里的代码
}