图灵日记之java奇妙历险记--继承和多态
目录
继承
class xiaoming {
String name;
int age;
int height;
String play;
}
class zhangsan {
String name;
int age;
int height;
String study;
}
上述代码,两个类有相同的部分,面向对象思想提出继承的概念,将共同点共性抽取,实现代码复用
概念
继承机制: 是面向对象程序设计使代码可以复用的最重要的手段,他允许程序员在保持类原有类特性的基础上进行拓展,增加新功能,这样产生的新的类,叫派生类.继承主要解决的问题是:共性抽取,实现代码复用
利用继承的思想,将上述代码共性部分进行抽取,达到共用
继承语法
在java中如果要表示类之间的继承关系,需要借助extends关键字
利用继承,可以将刚才的代码改写为
class stu {
String name;
int age;
int height;
}
class xiaoming extends stu{
String play;
}
class zhangsan extends stu{
String study;
}
具体使用extends关键字如下:
修饰符 class 子类 extends 父类 {
// …
}
注意
子类会将父类中的成员变量或者成员方法继承到子类中了
父类成员访问
子类中访问父类的成员变量
在继承体系中,子类将父类中的方法和字段继承下来了
在子类方法中 或者 通过子类对象访问成员时:
如果访问的成员变量子类中有,优先访问自己的成员变量。
如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
如果访问的成员变量与父类中成员变量同名,则优先访问自己的。
成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。
class stu {
public String name;
public int age;
public int height;
}
class xiaoming extends stu{
public String play;
public void show(){
//没有同名的情况
System.out.println(name);
System.out.println(age);
System.out.println(height);
System.out.println(play);
}
}
如下代码:
class stu {
private String name;
public int age;
public int height;
}
class xiaoming extends stu{
public String play;
}
子类仍然可以继承这个父类的name变量,并获得该变量的存储空间。这并不是说子类可以直接访问该变量,但它在内部维护了对这个私有变量的引用。这意味着如果父类的这个私有变量发生改变,子类中的相应变量也会发生改变。
class big{
public int a = 1;
}
class small extends big{
public int a = 3;
public void show(){
System.out.println(a);
}
}
public class test {
public static void main(String[] args) {
new small().show();
}
}
照应上文,访问的成员变量与父类中成员变量同名,则优先访问自己的。
子类中访问父类的成员方法
成员方法没有同名时,在子类方法中或者通过子类对象访问方法时,则优先访问自己的,自己没有时 再到父类中找,如果父类中也没有则报错
通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到 则访问,否则编译报错。
通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用
方法适传递的参数选择合适的方法访问,如果没有则报错;
super关键字
class big{
public int a = 1;
}
class small extends big{
public int a = 3;
public void show(){
System.out.println(a);
}
}
子类方法或变量与父类同名时,直接访问只会访问子类,要想访问父类就要用到super关键字,该关键字主要作用:在子类方法中访问父类的成员。
class small extends big{
public int a = 3;
public void show(){
System.out.println(super.a);//访问父类
}
}
public class test {
public static void main(String[] args) {
new small().show();
}
}
class big{
public int a = 1;
}
class small extends big{
public int a = 3;
public void show(){
System.out.println(a);
System.out.println(this.a);
System.out.println(super.a);
}
}
public class test {
public static void main(String[] args) {
new small().show();
}
}
this包括父类和子类,依旧是先找子类再找父类
- 只能在非静态方法中使用
- 在子类方法中,访问父类的成员变量和方法。
子类构造方法
子类对象构造时,需要先调用基类(父类)构造方法,然后执行子类的构造方法
初始化父类要么就地初始化,要么默认初始化,或者调用构造方法
class cola{
int c;
public cola(int c) {
this.c = c;
}
}
class water extends cola{
int a;
int b;
}
如上代码,父类调用构造方法出现报错,为什么呢?在不创建构造方法的时候,会调用默认构造方法
(此处是默认构造方法的原代码)
-class cola{
int c;
}
class water extends cola{
int a;
int b;
}
默认构造方法显式化如下:
-class cola{
int c;
public cola(){
}
}
class water extends cola{
int a;
int b;
public water(){
super();
}
}
或许会有宝子说子类构造方法不加super不是没有报异常吗,像这样
class cola{
int c;
public cola(){
}
}
class water extends cola{
int a;
int b;
public water(){
}
}
这里还是把子类调用父类的构造方法给隐藏了,而非没有
class cola{
int c;
public cola(){
System.out.println("父类构造方法");
}
}
class water extends cola{
int a;
int b;
public water(){
super();
System.out.println("子类构造方法");
}
}
上述代码的结果可知,子类仍然会调用父类的构造方法
所以,现在我们可以解决一下这个报错的问题了
class cola{
int c;
public cola(int c) {
this.c = c;
}
}
class water extends cola{
int a;
int b;
}
父类的构造方法已经有了,不会调用默认构造方法但是子类的仍然会调用默认构造方法,如上代码的子类默认构造方法如下:
public water(){
super();
}
}
此处super调用父类的默认构造方法就与已经实现的父类的构造方法不同,父类的构造方法如上上次代码可知是这样的
public cola(int c) {
this.c = c;
}
}
默认构造方法有参数int c,但是super()并没有传参,所以不符,会报错异常
解决刚才的问题,如下解决:
class cola{
int c;
public cola(int c) {
this.c = c;
}
}
class water extends cola{
int a;
int b;
public water(){
super(1);//传参就ok了
}
}
class cola{
int c;
public cola(int c) {
this.c = c;
}
}
class water extends cola{
int a;
int b;
public water(int c){
//调用父类的构造方法,帮助初始化子类从父类继承的成员,而不会去实例化父类对象
super(c);
}
}
子类对象中成员是由两部分组成的,父类继承下来的以及子类新增加的部分.构造子类对象时候,先调用父类的构造方法,先将父类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整
注意
- 若父类显示定义无参或者默认构造方法,在子类构造方法第一行默认隐含super()调用,即调用基类构造方法
- 如果父类构造方法含参,此时需要子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败
- 在子类构造方法中,super()调用父类构造时,必须是子类构造函数的第一天语句
- super()只能在子类构造方法中出现一次,并且不能和this同时出现
super和this
相同点
- 都是java中的关键字
- 只能在类的非静态方法中使用,用来访问非静态成员和字段
- 在构造方法中调用时,必须是构造方法中的第一天语句,并且不能同时存在
不同点
4. this是当前对象的引用,当前对象即调用实例方法的对象,super相当于子类对象中从父类继承下来部分成员的引用
5. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
6. 在构造方法中:this()用于调用本类构造方法,super()用于调用父类构造方法,两种调用不能同时在构造方法中出现
7. 构造方法中一定会存在super()的调用,用户没有谢编译器也会自己000,但是this()用户不写则没有
初始化
class things{
static {
System.out.println("父类静态代码块");
}
{
System.out.println("父类实例代码块");
}
things(){
System.out.println("父类构造方法");
}
String name;
}
class building extends things{
static {
System.out.println("子类静态代码块");
}
{
System.out.println("子类实例代码块");
}
building(){
System.out.println("子类构造方法");
}
int height;
}
public class test {
public static void main(String[] args) {
building a = new building();
System.out.println("=============");
building b = new building();
}
}
如上代码可得.静态代码块先执行且只执行一次,在类加载阶段执行,当对象创建时,才会执行实例代码块,实例代码块执行完后,最后调用构造方法,父类优先于子类,父类实例代码块和父类构造方法紧接执行,子类同理
protected关键字
package AA;
public class A {
protected int a = 1;
}
package BB;
import AA.A;
public class B extends A{
public static void main(String[] args) {
A tem1 = new A();
B tem2 = new B();
System.out.println(tem2.a);
}
}
A类实例化出对象tem1,tem1无法访问是因为tem1是A的对象而非子类,protected修饰要满足之前图中的三个条件之一,tem2是B类的实例化对象,B类继承父类的成员,tem2实例化包括父类的成员,可直接访问
super不能在静态方法里使用,所以不能直接在main方法里使用super
利用如下代码来访问a
public class B extends A{
public void show(){
System.out.println(super.a);
}
public static void main(String[] args) {
B tem = new B();
tem.show();
}
}
继承方式
单继承
多层继承
不同类继承同一个类
多继承(不支持)
java不支持多继承,不要出现超过三层的继承关系,如果想从语法上进行限制继承, 就可以使用 final 关键字
final 关键字
- 修饰变量或者字段,表示常量,不可改变
- 修饰类:表示此类不能被继承
- 修饰方法:表示该方法不能被重写
继承与组合
和继承类似,组合也是一种表达类之间关系的方式,也是能够达到代码重用的效果。组合并没有涉及到特殊的语法,仅仅是将一个类的实例作为另一个类的字段
public class animal {
public dog d;
public cat c;
}
class dog{
}
class cat{
}
多态
条件
- 继承关系关系->向上转型
- 子类和父类有同名的覆盖/重写方法
- 通过父类对象的引用去调用这个重写的方法
多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法
向上转型
public class test extends test_big{
public int b = 2;
public test(int a, int b) {
super(a);
this.b = b;
}
public void func02(){
System.out.println(b);
}
public static void main(String[] args) {
test t01 = new test(1,2);
test_big t02 = t01;
}
}
class test_big{
public int a = 1;
public test_big(int a) {
this.a = a;
}
public void func01(){
System.out.println(a);
}
}
如上代码,子类test和父类test_big,其中代码
public static void main(String[] args) {
test t01 = new test(1,2);
test_big t02 = t01;
}
实例化子类创建一个t01的对象,把t01的引用给父类对象,可以简化为
public static void main(String[] args) {
test_big t02 = new test(1,2);
}
采用直接赋值的方式实现向上转型
向上转型:实际就是创建一个子类对象,将其当成父类对象来使用
语法格式:父类类型 对象名 = new 子类类型()
public static void fun04(test_big t) {
return;
}
public static void main(String[] args) {
test t = new test(1,2);
fun04(t);
}
如上:方法参数,传参的时候进行向上转型
如下返回值 向上转型
public static test_big func03(test t){
return t;
}
再来看
test_big t = new test(1,2);
子类对象给到了父类对象的引用
t是父类对象,如果你用t调用子类成员还是不行,因为那是子类特有的
如果子类和父类同时共有一个方法,方法名,参数列表,返回值都相同的情况下,如下
public class test extends test_big{
public void show(){
System.out.println("子类show");
}
public static void main(String[] args) {
test_big t = new test();
t.show();
}
}
class test_big{
public void show(){
System.out.println("父类show");
}
}
子类和父类都有一样的show方法
子类:
public void show(){
System.out.println("子类show");
}
父类:
public void show(){
System.out.println("父类show");
}
可是t刚才不是说是父类对象吗,不能有子类的特性,这里却调用方法时,显示的是子类的方法
这里就要提到重写了,如果在继承关系上,满足方法的返回值一样,方法名一样,方法的参数列表一样,那就可以说这两个方法之间的关系是重写,而程序运行的时候绑定到了子类的show方法,这个过程叫作动态绑定
重写
//注解,提示
@Override
实现重写:
-
返回值,参数列表,方法名必须一样
-
被重写的方法的访问修饰限定符在子类中必须大于等于父类的
private 默认值 protected public -
被private修饰的方法是不可以被重写的
-
被static修饰的方法是不可以被重写的
-
被final修饰的方法是不可以被重写
final修饰是常性的 -
被重写的方法的返回类型可以不同,但是必须是具有父子关系的,如下,又被称为协变类型:
public class Animal {
public Animal stay(){
return null;
}
}
class Dog extends Animal{
public Dog stay(){
return null;
}
}
重写和重载的区别
参数列表:
重写一定不能修改,重载必须修改
返回类型:
重写一定不能修改,除非构成父子类关系,重载可以修改
访问修饰限定符:
一定不能做更严格的限制,可以降低限制,重载可以修改
public class Animal {
@Override
public String toString() {
return "Animal{}";
}
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog);
}
}
class Dog extends Animal{
}
在Java中,Object 是所有类的直接或间接父类。每个类在Java都直接或间接地继承自Object类。这意味着,如果你创建一个类,而不明确指定它的父类,那么它将默认继承自Object类。
dog继承父类Animal,形参Dog类,实参Object类,向上转型,传参到valueOf方法,在valueOf方法里调用toString方法,因为子类Dog继承父类的toString方法,toString重写,obj调用子类的toString,打印Animal{}
动态绑定&&静态绑定
public class Animal {
public void fun1(){
System.out.println("父类");
}
public static void main(String[] args) {
Animal animal = new Dog();
animal.fun1();
}
}
class Dog extends Animal{
public void fun1(){
System.out.println("子类");
}
}
程序编译过程中,调用的是Animal的func1()
程序在运行过程中,调用Dog的func1方法
动态绑定->运行时绑定
静态绑定可以理解为在编译的时候已经确定要调用谁了
**静态绑定:**也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型确定了具体调用哪个方法.典型代表函数重载
**动态绑定:**也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能确定具体调用哪个类的方法
多态再理解
public class Animal {
public String name;
public void play(){
System.out.println("动物正在玩");
}
public Animal(String name) {
this.name = name;
}
public static void playAnimal(Animal animal){
animal.play();
}
public static void main(String[] args) {
Dog dog = new Dog("小狗");
playAnimal(dog);
Cat cat = new Cat("小猫");
playAnimal(cat);
}
}
class Dog extends Animal{
public Dog(String name) {
super(name);
}
@Override
public void play() {
System.out.println(name+"正在玩");
}
}
class Cat extends Animal{
public Cat(String name) {
super(name);
}
@Override
public void play() {
System.out.println(name+"正在玩");
}
}
当父类引用引用的子类对象不一样的时候,调用这个重写的方法,所表现出来的行为是不一样的,我们把这种思想叫作多态
向下转型
将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换。
Animal animal = new Dog("小狗");
这段代码向上转型可以理解为狗是一个动物,但是倒过来,动物不一定就是狗
Animal animal = new Dog("小狗");
Dog dog = animal;
所以如上代码会报错,需要通过强转来实现
Animal animal = new Dog("小狗");
Dog dog = (Dog) animal;
向下转型并不安全
public class Animal {
public String name;
public void play(){
System.out.println("动物正在玩");
}
public Animal(String name) {
this.name = name;
}
public static void playAnimal(Animal animal){
animal.play();
}
public static void main(String[] args) {
Animal animal = new Dog("小狗");
Cat cat = (Cat)animal;
cat.speak();
}
}
class Dog extends Animal{
public Dog(String name) {
super(name);
}
@Override
public void play() {
System.out.println(name+"正在玩");
}
}
class Cat extends Animal{
public Cat(String name) {
super(name);
}
public void speak(){
System.out.println("喵喵");
}
@Override
public void play() {
System.out.println(name+"正在玩");
}
}
在main方法里
public static void main(String[] args) {
Animal animal = new Dog("小狗");
Cat cat = (Cat)animal;
cat.speak();
}
当你运行会发现运行失败
可以加一个限制防止出现这种情况
public static void main(String[] args) {
Animal animal = new Dog("小狗");
//如果animal引用的对象是Cat对象的实例
if(animal instanceof Cat){
Cat cat = (Cat)animal;
cat.speak();
} else {
System.out.println("here");
}
}
再次强调一下,向下转型非常不安全
多态的优缺点
好处
- 能够降低代码的圈“圈复杂度”,避免使用大量if-else
圈复杂度是一种描述一段代码复杂程度的方式,一段代码如果平铺直叙,那么就比较容易理解,而如果有很多条件分支或循环语句,就认为理解起来更复杂
因此我们可以见到粗暴的计算一段代码中条件语句和循环语句出现的个数,这个个数称为“圈复杂度”,如果一个方法的圈复杂度太高,就需要考虑重构 - 可扩展能力更强
缺陷
代码运行效率降低
- 属性没有多态性
当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性 - 构造方法没有多态性
public class test {
public static void main(String[] args) {
Dog dog = new Dog();
}
}
class Dog extends Animal{
public void eat(){
System.out.println("dog");
}
}
class Animal{
public Animal(){
eat();
}
public void eat(){
System.out.println("Aniaml");
}
}
如上代码,实例化Dog类创建对象dog,先调用父类的构造方法,父类Animal的构造方法里调用eat方法,eat方法在子类和父类都有的情况下,调用子类的eat方法,发生了动态绑定
稍微改动一下
class Dog extends Animal{
private int d = 1;
public void eat(){
System.out.println("dog "+d);
}
}
对Dog类作出以前的改动,打印出来是
打印出来是0,而不是1,因为现在实例化子类的时候先调用父类的构造方法,构造方法里可以调用子类和父类重写的方法,调用子类的eat方法,而此时的d变量还未被子类所初始化,属于默认值,int的默认值是0,所以打印出0
所以在构造方法里,尽量避免使用实例方法,除了final和private方法,因为final和private方法无法被初始化
总结:用尽量简单的方式使对象进入工作状态,尽量不要在构造器中调用方法,如果这个方法被子类重写,就会触发动态绑定,但是此时子类对象还没构造完成,可能会出现一些隐藏的极难发现的问题
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!