Java继承
2021/11/13 20:40:18
本文主要是介绍Java继承,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
继承
- 1 继承的含义
- 2 继承的特点
- 3 继承中成员变量的关系
- 4 继承中构造方法的关系
- 5 继承中成员方法的关系
- 6 继承中静态成员的关系
- 7 this、super的比较和重载、重写的比较
- 8 练习(针对以上内容)
- 9 final关键字
1 继承的含义
继承:(界门纲目科属种) 格式 class 子类名 extends 父类名 {} 子类可以有自己的新特性(属性, 行为) 术语: 父类, 超类, 基类 子类, 衍生类 继承的好处: 1. 提高了代码的复用性 2. 提高了代码的维护性(分情况而定) 3. 让类与类之间产生了关系,是多态的前提 其实这也是继承的一个弊端:类的耦合性增强 设计原则: 高内聚, 低耦合 内聚: 就是自己完成某件事情的能力。 耦合: 就是类与类之间的关系。 举例:人的职责(分清自己的职责),不要多做事,也不要少做事。
ExtendsDemo1:
class People { String name; int age; public void eat() { System.out.println("我要吃小龙虾"); } public void sleep() { System.out.println("我要睡觉,不要打扰我"); } } class Student extends People { public void study() { System.out.println("我爱学习,学习爱我"); } } class Teacher extends People { public void teach() { System.out.println("我爱学生,学生爱我"); } } public class ExtendsDemo1 { public static void main(String[] args) { Student s = new Student(); s.name = "Henson_z"; s.age = 18; System.out.println(s.name + " " + s.age); // Henson_z 18 s.eat(); // 我要吃小龙虾 s.sleep(); // 我要睡觉,不要打扰我 System.out.println("-----------------------"); Teacher t = new Teacher(); t.name = "刘亦菲"; t.age = 16; System.out.println(t.name + " " + t.age); // 刘亦菲 16 t.eat(); // 我要吃小龙虾 t.sleep(); // 我要睡觉,不要打扰我 // 以上打印的结果都是从父类继承而来 } }
2 继承的特点
java中继承的特点: a. 只支持单继承,不支持多继承(C++),即一个类最多只能有一个父类,不能有多个父类; b. 但是支持多层继承。 注意事项: a. 子类只能继承父类的非私有成员(实际上私有成员被隐式继承了,即子类中不能访问,在后面的反射中会介绍),打破了封装性(继承不能有private,打破封装)。 b. 子类不能继承父类的构造方法(如果可以,则子类中有public Father(){...},构造方法命名没有和类名Son相同,也没有返回值,所以不是成员方法,所以在语法上就错误),但是可以通过关键字super去访问父类的构造方法。 c. 不要为了部分功能而去使用继承,比如: class A { m1(); m2(); m3(); m4(); } 现在需要一个类B,具有以下功能:m1(), m2(), m3(), m5(),如果使用继承: class B extends A { m5(); } 但其实这样是不推荐的,因为B中也会继承m4(),这对B来说是多余的,所以此时不能使用继承,但可以使用复合。 d. 什么时候使用继承呢? 继承中体现的关系:"is a"的关系 人: 学生,老师 水果: 香蕉,水果姐(x)
ExtendsDemo2:
class Super1 { } class Super2 extends Super1 { private int a = 10; int b = 20; Super2() { System.out.println("无参构造方法"); } Super2(int a, int b) { this.a = a; this.b = b; } } // 报错,因为java中只能单继承 // class Sub extends Super1, Super2 { // } class Sub extends Super2 { // Sub继承Super2,Super2继承Super1,支持多层继承。 } public class ExtendsDemo2 extends Super2 { public static void main(String[] args) { ExtendsDemo2 demo = new ExtendsDemo2(); // System.out.println(demo.a); // 报错,私有成员a被隐式继承,子类不能访问。 // ExtendsDemo2 demo2 = new ExtendsDemo2(1, 2); // 子类不能继承父类的构造方法,所以子类中没有有参构造方法,所以报错。 System.out.println(demo.b); // 20,非私有成员可以被正常继承。 } }
3 继承中成员变量的关系
继承中成员变量的关系: 私有成员变量被隐式继承,其它成员变量都被正常继承。 子类的成员变量隐藏了父类的成员变量:就近原则 此时可通过super关键字访问父类的成员变量。 super关键字是一片内存空间的表示,不能表示父类对象。
ExtendsDemo3:
class Father { int a = 10; int b = 20; } class Son extends Father { int a = 100; int b = 200; public void show() { System.out.println(a); System.out.println(b); System.out.println(super.a); System.out.println(super.b); } } public class ExtendsDemo3 { public static void main(String[] args) { Son son = new Son(); son.show(); // 运行结果:100, 200, 10, 20 } }
内存图分析:
**总结:**this和super均可以表示一片内存空间的标识;this还可以表示当前对象,但是super不可以表示父类对象。
4 继承中构造方法的关系
继承中构造方法的关系: a.子类不会继承父类的构造方法; b.子类中所有的构造方法默认都会访问父类的无参构造方法。 为什么子类的构造方法要访问父类的构造方法? 父类中定义的成员变量,子类会继承,因此需要调用父类的构造方法,对这些数据进行初始化。 如果父类中没有无参构造方法呢? 1.为父类添加一个无参构造方法; 2.子类显示调用父类的有参构造方法,通过super关键字; 3.子类通过this关键字调用本类中定义的其它构造方法,但是其它构造方法最终也会调用父类构造方法。 this(); // 调用本类的构造方法 super(); // 调用父类的构造方法 注意事项: a.创建子类对象时,必须要先访问父类的构造方法(有参、无参均可),默认访问的是父类无参构造方法,如果访问不到,则报错。 b.this(), super()调用构造方法只能放在构造器的第一行,所以this()和super()不能同时出现。
ExtendsDemo4:
class Father3 { // 1.为父类添加一个无参构造方法 /*Father3() { System.out.println("Father"); }*/ Father3(int a) { System.out.println("Father: " + a); } } class Son3 extends Father3 { Son3() { // 3.通过this关键字调用本类中定义的其它构造方法,但是其它构造方法最终也会调用父类构造方法。 this("深蓝"); System.out.println("Son"); } Son3(String name) { // 2.通过super关键字手动调用父类构造方法 super(100); System.out.println("Son: " + name); } } public class ExtendsDemo4 { public static void main(String[] args) { // 默认会访问父类的无参构造方法 new Son3(); // 运行结果:Father, Son new Son3("Heson_z"); // Father, Son: Heson_z // 如果把父类无参构造方法注释,则静态编译会出错,因为父类没有提供默认的无参构造方法,子类的构造方法也没有手动调用父类的构造方法。 // 添加this()和super()之后,上面运行的结果: // Father: 100, Son: 深蓝, Son // new Son3()运行结果 // Father: 100, Son: Heson_z // new Son3("Heson_z")运行结果 } }
5 继承中成员方法的关系
继承中成员方法的关系: 当子类中定义了一个方法签名一模一样的方法时,就会隐藏父类中定义的方法。 通过super.method() 去访问父类中定义的方法 方法的重载:overload 同一个类中, 方法名字一样, 参数列表不一样(方法签名不同), 和返回值类型无关。 方法的重写:override 在子父类中, 方法签名一样, 返回值类型: 基本数据类型必须一致 引用数据类型:父类的类型必须兼容子类的类型(父类的类型必须大于等于子类的类型) 访问权限: 子类的方法的访问权限必须大于等于父类方法的访问权限 注意事项: a. 父类中的私有方法不能被重写,因为方法重写的前提是继承,私有方法被隐式继承(或者说不能被继承),所以不能被重写。 b. 静态方法按照重写的形式,但不能算作方法重写,且不能添加@Override注解,在多态中会讲解。 引入一个注解:@Override @Override: 能够判断一个方法是不是重写父类中定义的方法。 提醒注意: 这是在重写父类中定义的方法。 方法重写有什么意义? 父类中提供方法, 相当于默认行为。父类的默认行为不能满足子类的需求,就要进行方法的重写。
ExtendsDemo5:
class Father4 { Father4 show() { System.out.println("Father.show()"); return new Father4(); } private void show1() { System.out.println("Father.show1()"); } public static void show2() { System.out.println("Father.show2()"); } } class Son4 extends Father4 { // 子类中自己定义的方法 public void method() { System.out.println("Son.method()"); show(); // 访问自己的show()方法 super.show(); // 通过super访问父类的show()方法 } @Override // 对父类方法的重写,返回值为引用数据类型需要被父类兼容(返回值为基本数据类型,需要和父类一致),访问权限大于等于父类。 public Son4 show() { System.out.println("Son.show()"); return new Son4(); } // @Override // 私有方法不能被重写,此时子类中的show1()方法相当于子类中自己定义的方法,不是继承的,所以不是重写。 void show1() { System.out.println("Son.show1()"); } // @Override // 静态方法即使按照重写的形式,添加@Override时会报错,所以不是方法的重写,在多态中讲解。 public static void show2() { System.out.println("Son.show2()"); } } public class ExtendsDemo5 { public static void main(String[] args) { Son4 son = new Son4(); son.method(); // 运行结果:Son.method(), Son.show(), Father.show() son.show1(); // Son.show1() } }
6 继承中静态成员的关系
继承中静态成员(静态变量、静态方法)的关系: a.静态成员也会被继承。 b.如果出现同名的静态成员,子类静态成员会隐藏父类静态成员,那么父类中定义的静态成员可以通过类名来访问,也可以通过父类对象来访问。 c.静态方法中不能使用this, super关键字,因为这两个关键字都是非静态的。 d.静态方法即使按照重写形式,也不能算作方法的重写,添加@Override会出错。
ExtendsDemo6:
class Father2 { static int a = 10; static int b = 20; } class Son2 extends Father2 { static int a = 100; static int b = 200; public static void show() { System.out.println(a); System.out.println(b); // 静态方法中不能使用this, super关键字 // System.out.println(super.a); // 报错 // System.out.println(super.b); // 报错 System.out.println(Father2.a); System.out.println(Father2.b); } } public class ExtendsDemo6 { public static void main(String[] args) { Son2.show(); // 运行结果:100, 200, 10, 20 } }
ExtendsDemo7:
class Father5 { public static Father5 show1() { System.out.println("Father.show1()"); return new Father5(); } } class Son5 extends Father5 { // @Override // 静态方法按照重写形式,不是方法的重写 public static Father5 show1() { System.out.println("Son.show1()"); return new Father5(); } public static void method() { show1(); // super.show1(); // 静态方法中不能使用非静态的super Father5.show1(); } } public class ExtendsDemo7 { public static void main(String[] args) { Son5.method(); // 运行结果:Son.show1(), Father.show1() } }
7 this、super的比较和重载、重写的比较
this和super的用法: a.访问成员变量 this.成员变量 super.成员变量 b.访问成员方法 this.成员方法() super.成员方法() c.方法构造方法 this() super() this: 1.代表当前对象 (上面的用法a, b) 2.代表一片内存空间的标识 (上面的用法c) super: 只能代表一片内存空间的标识,不能代表父类对象。(上面的用法a, b, c) 方法重写和重载的区别?方法重载能改变返回值类型吗? 方法重载: 同一个类中,方法名字相同,但是形参的类型或个数不同,与返回值无关。 方法重写: 在子父类中,方法名字相同,形参也相同(方法签名相同),但是实现不同。 返回值类型: 如果是基本数据类型,则必须一样; 如果是引用数据类型,则父类的返回值类型必须要大于等于(兼容)子类的返回值类型。 访问权限: 子类方法的访问权限大于等于父类方法的访问权限。 方法重载可以改变返回值类型。
8 练习(针对以上内容)
练习一:看程序写结果 (Test1)
class Father { int a = 10; } class Son extends Father { int a = 20; public void show() { int a = 30; System.out.println(a); // 30 System.out.println(this.a); // 20 System.out.println(super.a); // 10 } } public class Test1 { public static void main(String[] args) { Son son = new Son(); son.show(); // 30 20 10 } }
练习二:看程序写结果 (Test2)
class Fu { static { System.out.println("静态代码块Fu"); } // 构造代码块只是一个语法糖, 反编译之后会放到构造方法前面 { System.out.println("构造代码块Fu"); } public Fu() { System.out.println("构造方法Fu"); } } class Zi extends Fu { static { System.out.println("静态代码块Zi"); } { System.out.println("构造代码块Zi"); } public Zi() { System.out.println("构造方法Zi"); } } public class Test2 { public static void main(String[] args) { new Zi(); // 运行结果: /* 静态代码块Fu 静态代码块Zi 构造代码块Fu 构造方法Fu 构造代码块Zi 构造方法Zi*/ } }
总结:Java中如果有继承关系,对象的初始化是分层初始化 1.父类按照层次依次加载,然后再加载子类。 2.成员变量是按照层次进行初始化的,先初始化父类中定义的数据,再初始化子类中定义的数据。
练习三:看程序写结果 (Test3)
class X { Y b = new Y(); X() { System.out.print("X"); } } class Y { Y() { System.out.print("Y"); } } class Z extends X { Y y = new Y(); Z() { System.out.print("Z"); } } public class Test3 { public static void main(String[] args) { new Z(); // YXYZ } }
总结: 父类的显示初始化 --> 父类的构造方法 --> 子类的显示初始化 --> 子类的构造方法
练习四:(Test4)
手机类和新式手机类。(加入好听的彩铃功能,基本功能由手机提供) 手机: 属性:品牌, 价格, 颜色... 行为:打电话, 发短信, 玩游戏... 新式手机: 属性:品牌, 价格, 颜色... 行为:打电话(有彩铃), 发短信, 玩游戏... 重写可以加强父类的方法, 也可以重新写一个完全不一样的方法。
class Phone { String brand; double price; String color; public void call(String name) { System.out.println("给" + name + "打电话"); } public void sendMessage(String name) { System.out.println("给" + name + "发短信"); } public void play(String game) { System.out.println("玩" + game + "游戏"); } } class NewPhone extends Phone { @Override // 重写加强父类的方法 public void call(String name) { System.out.println("彩铃播放中..."); // System.out.println("给" + name + "打电话"); super.call(name); } } public class Test4 { public static void main(String[] args) { NewPhone phone = new NewPhone(); phone.call("刘亦菲"); // 彩铃播放中... 给刘亦菲打电话 phone.sendMessage("Henson_z"); // 给Henson_z发短信 phone.play("王者荣耀"); // 玩王者荣耀游戏 } }
练习五:猫狗案例 (Test5)
现实生活中: 猫: 属性:brand, 颜色, 年龄 行为:叫, 吃, 抓老鼠... 狗: 属性:brand, 颜色, 年龄 行为:叫, 吃, 看门... 动物: 属性:brand, 颜色, 年龄 行为:叫, 吃 代码世界: Animal: Field: brand, color, age Method: speak(), eat() Cat extends Animal Field: Method: catchMouse() Dog extends Animal Field: Method: watchHouse()
class Animal { String brand; String color; int age; public Animal() { } public Animal(String brand, String color, int age) { this.brand = brand; this.color = color; this.age = age; } public void speak() { // 不同的动物叫法不同,这里不提供实现 } public void eat() { System.out.println("朕要吃好吃的"); } public void show() { System.out.println("brand: " + brand + ", color: " + color + ", age: " + age); } } class Cat extends Animal { public Cat() { } public Cat(String brand, String color, int age) { super(brand, color, age); } @Override public void speak() { System.out.println("喵喵喵"); } public void catchMouse() { System.out.println("抓老鼠"); } } class Dog extends Animal { public Dog() { } public Dog(String brand, String color, int age) { super(brand, color, age); } @Override public void speak() { System.out.println("汪汪汪"); } public void watchHouse() { System.out.println("看门狗"); } } public class Test5 { public static void main(String[] args) { Cat cat = new Cat("橘猫", "橘色", 3); cat.show(); // brand: 橘猫, color: 橘色, age: 3 cat.eat(); // 朕要吃好吃的 cat.speak(); // 喵喵喵 cat.catchMouse(); // 抓老鼠 System.out.println("----------------------"); Dog dog = new Dog("哈士奇", "黑白", 2); dog.show(); // brand: 哈士奇, color: 黑白, age: 2 dog.eat(); // 朕要吃好吃的 dog.speak(); // 汪汪汪 dog.watchHouse(); // 看门狗 } }
9 final关键字
如果对继承不加以限制,可能会导致一些严重的事故发生。
Demo:
class A { public void func() { System.out.println("这是绝密文件"); } } class B extends A { @Override public void func() { System.out.println("这是毫无价值的文件"); // 通过继承来重写方法 } } public class Demo { public void show(A a) { a.func(); } public static void main(String[] args) { Demo demo = new Demo(); B b = new B(); demo.show(b); // 这是毫无价值的文件 (涉及到多态知识) } }
final关键字:
final: 最终的。 修饰类:该类就不能被继承 (做了绝育手术) 修饰方法:该方法不能被重写, (禁止子类重写该方法) 修饰变量:自定义常量 注意事项: a.修饰变量时,该变量有且只能被赋值一次。 b.对于成员变量来说, 即使常量为默认的0值,也必须得赋值一次。
class AA { public void func() { System.out.println("这是绝密文件"); } } class BB extends AA { @Override public void func() { System.out.println("这是毫无价值的垃圾"); } } public class FinalDemo1 { // final int num; // 错误 final int num = 0; // b.对于成员变量来说, 即使常量为默认的0值,也必须得赋值一次。上面的写法错误。 public void show(A a) { a.func(); } public static void main(String[] args) { final int a = 10; System.out.println("a = " + a); // 10 // a = 20; // a.修饰变量时,该变量有且只能被赋值一次。 // a = 10; // 同上,即使值不变也报错。 FinalDemo1 demo = new FinalDemo1(); System.out.println(demo.num); // 0 // demo.num = 20; // 同上 } }
练习六:final修饰基本数据类型与引用数据类型的区别
final修饰: a.基本数据类型:值不能发生改变 b.引用数据类型:引用地址不能发生改变(只能指向同一个对象)
class Animal { int a; int b; Animal(int a, int b) { this.a = a; this.b = b; } } public class Test6 { public static void main(String[] args) { final int i = 100; System.out.println("i = " + i); // i = 100 // i = 200; // a.基本数据类型:值不能发生改变 final Animal animal = new Animal(1, 2); System.out.println(animal.a + " " + animal.b); // 1 2 // animal的值始终没有发生变化,所以正确。 animal.a = 10; animal.b = 20; System.out.println(animal.a + " " + animal.b); // 10 20 // animal = new Animal(10, 20); // 出错 b.引用数据类型:引用地址不能发生改变(只能指向同一个对象) } }
这篇关于Java继承的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-30java最新版本是什么,有什么特性?-icode9专业技术文章分享
- 2024-11-30[开源]27.8K star!这款 Postman 替代工具太火了!
- 2024-11-30Gzip 压缩入门教程:轻松掌握文件压缩技巧
- 2024-11-29开源工具的魅力:让文档管理更“聪明”
- 2024-11-29Release-it开发入门教程
- 2024-11-29Rollup 插件入门教程:轻松掌握模块打包
- 2024-11-29从零到一,产品经理如何玩转项目管理和团队协作
- 2024-11-29如何通过精益生产管理工具帮助项目团队实现精准进度控制?
- 2024-11-29低代码应用开发课程:新手入门与基础教程
- 2024-11-29入门指南:全栈低代码开发课程