继承这一机制机制是面向对象这一设计的,为了使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。
比如一个实体猫和狗他们的共性都是动物,然后我们就可以设置动物这个类将其共同的特点放到里面,以便接下来猫和狗这些实体的使用。
继承的语法格式
修饰符 class 子类 extends 父类{
.
}
eg
class Animal{ String name; int age; public void eat(){ System.out.println(name + "正在吃饭");} public void sleep(){ System.out.println(name + "正在睡觉");} } class Dog extends Animal{ void bark(){ System.out.println(name +"汪汪"); } } public class Cat extends Animal{ void mew(){ System.out.println(name +"喵喵"); } } public class Main { public static void main(String[] args) { Dog dog = new Dog(); System.out.println(dog.name); System.out.println(dog.age); Cat cat = new Cat(); System.out.println(cat.age); System.out.println(cat.name); } }
从上述例子可以看出继承了类的类将会少写一部分代码,实现了代码的复用,增加了简洁性
注
java中只能继承一个类,不能进行多继承。
还有关键字 extends的使用。
子类会将父类的成员变量和成员方法继承到子类中
子类继承父类之后,必须加一些自己特有的成员变量或者成员方法否则没有继承的必要性
子类访问父类成员变量或者成员方法
因为在继承中子类将父类的一切都继承下来,那么子类能否直接访问父类中继承下来的成员。
子类访问父类中继承成员变量(子类新创建成员变量与父类不相同)
class Base { int a = 10; int b =20; } class Derived extends Base{ int c; public int get_A(){ return a; } public int get_B(){ return b; } public int get_C(){ return c; } public void method(){ System.out.println(get_A()+"a的值"); System.out.println(get_B()+"b的值"); System.out.println(get_C()+"c的值"); } } public class Main { public static void main(String[] args) { Derived derived = new Derived(); derived.method(); } }
子类访问父类中继承下来的成员变量(子类新创建的成员变量与父类相同)
class Base { int a = 10; int b =20; } class Derived extends Base{ int c; int a = 30; int b =40; public int get_A(){ return a; } public int get_B(){ return b; } public int get_C(){ return c; } public void method(){ System.out.println(get_A()+"a的值"); System.out.println(get_B()+"b的值"); System.out.println(get_C()+"c的值"); } } public class Main { public static void main(String[] args) { Derived derived = new Derived(); derived.method(); } }
注
子类在访问父类继承下来的成员变量时,访问的成员变量自己有那就访问自己的,没有就访问父类的父类没有报错,也就是自己有优先访问自己,没有访问父类。
super关键字
由于有时需要在子类中访问父类同名的成员方法或者成员变量时不能访问使用。java创建了super,可以直接访问父类的同名方法或者成员变量。
class Base{ int a = 10; int b = 20; public void a(){ System.out.println("base a"); } public void b(){ System.out.println("base b"); } } class Derived extends Base{ int a = 30; int b= 40; @Override public void a() { System.out.println("derived a"); System.out.println(a); System.out.println(super.a); } @Override public void b() { System.out.println("derived b"); System.out.println(b); System.out.println(super.b); } } public class Main { public static void main(String[] args){ Derived derived = new Derived(); derived.a(); derived.b(); } }
输出结果
在子类方法中想要访问父类的方法或者变量使用super
注
只能在非静态方法中使用,只能在子类方法中使用。
子类的构造方法
子类方法构造时,先会构造父类的构造方法,在构造子类的构造方法。
class Base{ int a = 10; int b = 20; public Base(){ System.out.println("base"); } public void b(){ System.out.println("base b"); } } class Derived extends Base{ int a = 30; int b= 4; public Derived(){ System.out.println("derived"); } @Override public void b() { System.out.println("derived b"); System.out.println(b); System.out.println(super.b); } } public class Main { public static void main(String[] args){ Derived derived = new Derived(); } }
从运行结果也可以进一步证明是先构造父类在构造子类。
注
若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即隐式调用父类构造方法
如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句。super(...)只能在子类构造方法中出现一次,并且不能和this同时出现
super和this关键字的区别
相同点:
都是Java中的关键字
只能在类的非静态方法中使用,用来访问非静态成员方法和字段
3在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
不同点:
this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用
在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
在构造方法中: this用于调用本类构造方法,super用于调用父类构造方法,两种调用不能同时在构造方法中出现
构造方法中一定会存在super的调用,用户没有写编译器也会增加,但是this用户不写则没有
final关键字
final关键字可以修饰变量成员方法以及类。
修饰变量时,将会使其变为常量(不能被修改)
修饰类表示类不能被继承
修饰方法不能被重写
多态
就是使同一件事情,发生在不同对象身上,就会产生不同的结果。比如动物吃饭不同的动物吃不同的饭。
多态产生的条件
必须在继承体系下
子类必须要对父类中方法进行重写
通过父类的引用调用重写的方法
class Animal{ public void eat(){ } } class Cat extends Animal{ @Override public void eat() {//重写 System.out.println("猫吃鱼"); } } class Dog extends Animal{ @Override public void eat() {//重写 System.out.println("狗吃骨头"); } } public class Main { public static void main(String[] args){ Dog dog = new Dog(); Cat cat = new Cat(); dog.eat(); cat.eat(); } }
这就是一个简单的多态猫和狗都是动物,但是他们吃的食物不同。
并且我们实现这种功能就用到了重写。
重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写,返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。也就是说子类能够根据需要实现父类的方法。
重写的规则:
子类在重写父类的方法时,一般必须与父类方法原型一致:返回值类型方法名(参数列表)要完全一致被重写的方法返回值类型可以不同,但是必须是具有父子关系的
访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为protected
父类被static、private修饰的方法、构造方法都不能被重写。
重写的方法,可以使用@Override注解来显式指定.有了这个注解能帮我们进行一些合法性校验.例如不小心将方法名字拼写错了(比如写成aet),那么此时编译器就会发现父类中没有aet方法,就会编译报错,提示无法构成重写。
重载与重写的区别
体现出方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
重写时需要注意:
对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容,并且添加或者改动新的内容。
例如:若干年前的手机,只能打电话,发短信,来电显示只能显示号码,而今天的手机在来电显示的时候,不仅仅可以显示号码,还可以显示头像,地区等。在这个过程当中,我们不应该在原来老的类上进行修改,因为原来的类,可能还在有用户使用,正确做法是:新建一个新手机的类,对来电显示这个方法重写就好了,这样就达到了我们当今的需求了。(如现在的智能手机和老年机)
向上转型和向下转型
静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。
动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。
向上转型
向上转型就是创建一个子类对象,以供父类使用。
父类类型 父类对象 = new 子类类型();
Animal 是父类类型,但这个Dog是一个子类类型,将子类类型转化为父类,这是小范围转化为大范围。
直接赋值
将子类对象值赋值给父类对象。
方法传参
形参是父类对象引用,实参是可以接受任意的子类对象
方法返回
将子类对象返回赋值给父类。
注
向上转型的特点:代码更加灵活(使父类对象可以直接调用和子类对象相同的成员属性或者成员方法),不能调用到子类特有的方法。
向下转型
将父类对象赋值给子类对象。
方法传参
形参是子类实参是父类
方法返回
将父类作为对象返回赋值给子类
注
向下引用必须经常用强制转化否则会报错,并且向下转型是一个大范围向小范围的值转化,很容易发生错误。java中为了确保向下转型安全引用 instanceof关键字其左边的对象是否是它右边的类所创建的对象。
总结如下:
多态的优缺点
能够降低代码的"圈复杂度",避免使用大量的if - else
什么叫"圈复杂度" ?
圈复杂度是一种描述一段代码复杂程度的方式.一段代码如果平铺直叙,那么就比较简单容易理解.而如果有很多的条件分支或者循环语句,就认为理解起来更复杂.
因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数,这个个数就称为"圈复杂度".如果一个方法的圈复杂度太高,就需要考虑重构.
可扩展性强
代码运行效率低