文章目录
1. 啥是继承?
- 继承是java面向对象核心思想之一,它可以 创建分等级层次的类。
- 那什么是继承? 类和类之间是有关联的,子类自动具备来自于父类的属性和行为。从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。
- 例如: 儿子,继承父亲的一些能力(方法),基本特征(属性),并且儿子可能会有自己的新能力(唱,跳 ,rap)。
- 代码分析:
class Student{ //继承只能继承 private 以外的东西 私有话只能本类。 String name; //先不要私有化属性。 int age; public void study(){ System.out.println("学习"); } class Teacher{ String name; int age; public void study(){ System.out.println("学习"); } } .... //还有 Worker ... 等等都具有名字和学习。 //这样的代码重复度很高。那么怎么解决这么多相似度的问题? // 所以,就抽取相同的属性和行为,向上抽象类,得到了Person类 class Person{ String name; int age; public void Study(){ System.out.println("学习"); } }
1.1 语法结构
-
使用关键字
extends
。//父类 public class Animal{ } //子类 public class Tiger{ } //继承语法结构 public class Tiger extends Animal{ }
-
父类是动物类,食肉动物和食草动物都具备动物的一些属性和特征,父类的特点是通用,子类特点是 更加具体。
- 是
is--a
的结构,继承关系。如:Tiger is a Animal。
- 但是子类会有父类的一般性,也会具有自身特性。
- 是
1.2 入门练习
- 继承的优点:提高代码的复用性(减少代码冗余,相同代码重复利用)。
- 使类与类之间产生了关系。复用和设计分离,即使以后有100个 只要继承父类就可以了。
- 思考一个问题: 不能使用
private
修饰的属性和方法;为什么?
-
Animal 类:
public class Animal { String food; public void call(){ System.out.println("发出声音"); } public void eat(){ System.out.println("吃 "+food); } }
-
EatMeat 类
public class EatMeat extends Animal{ int age; //自己的属性年龄 /* 介绍自己 */ public void introduce(){ System.out.println("我是食肉动物"); } }
-
Tiger 类
public class Tiger extends EatMeat{ }
-
测试类 Test_Extends
public class Test_Extends{ public static void main(String[] args) { Tiger t = new Tiger(); System.out.println(t.age);//拿到了父类EatMeat t.introduce(); //通过继承关系,拿到了食肉动物Animal属性和方法。 t.food="食草动物"; t.eat(); t.call(); } }
1.3 继承的特点
- 关键字
extends
相当于子类把父类的功能复制了一份。 - 继承可以传递 (动物,食肉动物, 老虎之间关系 Animal 3.0 )。
- 重点:java只支持单继承,不可以多继承。(可以多重继承,Animal3.0)
- 继承用于功能修改,子类可以拥有父类功能的同时,并进行功能拓展。
is-a关系 面向对象继承
例如:Tiger is a EatMeat , 体现了 狮子继承了食肉动物,二者有父子关系。
2. 继承类型关系图
- 继承也要灵活使用,千万不能为了继承而继承,就为了减少代码而继承,这就是错误的体现。
- 一定要,真正存在关系时而去使用继承。
2.1 继承关系案例
- 父类 又称之为 超类或者基类。
- 子类又叫做 派生类。
//1. 私有化属性,需要通过Get和Set访问。 public class Fu { //父类, (超类,基类) private String name; //姓名 public String getName() { return name; } public void setName(String name) { this.name = name; } //介绍自己的名字 public void speak(){ System.out.println("I am "+name); } }
-
子类 Zi 和 测试类:
//子类 public class Zi extends Fu{ //子类 (派生类) // 扩展自己特有的方法 public void say(){ System.out.println("My father is "+ getName());//调用父类方法,因为啥? } } // 测试类 class Demo{ public static void main(String[] args) { Zi zi = new Zi(); zi.speak(); // 来子父类的方法 //1. 父类私有属性,可以通过父类对外的访问方法设置。 zi.setName("张三"); zi.speak(); zi.say(); } }
-
输出结果:
I am null I am 张三 My father is张三
3. super 关键字
- 通过
super
关键字,可以使用父类内容。- 区分 父类与子类同名 变量。
- 调用 父类的构造函数。
super
代表父类的一个引用对象,如果使用必须放在第一位。
3.1 区别同名变量
super
关键字,在继承中区别同名变量的使用。
-
父类: Animal :
public class Animal { String name = "超类,基类"; }
-
子类: Cat:
public class Cat extends Animal { String name="派生类"; public void show (){ String name = "局部变量 name"; System.out.println(this.name); //使用谁的? 子类本身 //这里的super 我们可以理解为 Animal a = new Animal(); a相当于super System.out.println(super.name);// 使用的是谁的? 父类 System.out.println(name); // 使用谁的? 方法里受局部变量影响 } } // 测试类 Demo class Demo{ public static void main(String[] args) { // 匿名对象使用 ,只调用一次 。 new Cat().show(); } }
-
输出结果:
派生类 超类,基类 局部变量 name
3.2. 构造方法使用
- 创建子类的对象时,默认会先调用父类无参构造器。
- 父类的构造函数是不能够被继承。
- 为什么构造方法不能被继承? 因为构造方法必须与类名相同!
- 看下列图片,为什么会报错!?
- 因为子类继承父类之后,获取到了父类的内容(属性/字段),而这些内容在使用之前必须先初始化,所以 必须先调用父类的构造函数进行内容的初始化 。
- 解决上面图片的办法,如下面案例。
-
父类: Father
public class Father { // 父类 属性 String name; int age; // 无参数构造 TODO ... ... // public Father() {} // 构造函数 public Father(String name) { this.name = name; } // 构造函数 public Father(String name, int age) { this.name = name; this.age = age; } // 显示方法 public void show(){ System.out.println("name :"+name+"\n"+"age :"+age); } }
-
子类: Son
public class Son extends Father { String address; //自己属性 // 如果父类没有空参构造方法。子类必须调用父类构造方法,不管调用哪个 ,但必须调用一个。 public Son() { // super(); //默认调用父类空参构造(前提父类有空参构造) // super("你好"); super("jay",20); System.out.println("子类构造函数"); } //注意:如果非得,想使用子类构造方法 public Son(String address) { // super(); //第一行都是默认调用父类空参构造函数;(如果没有就必须调用别的。) super("张"); //如果没有空参构造,就必须调用含参数构造。 this.address = address; } } class Test{ public static void main(String[] args) { Son s = new Son(); s.show(); System.out.println("--------华丽的分割线--------------"); Son s2 = new Son(""); //调用子类含参构造 s2.show() } }
-
输出结果:
子类构造函数 name :jay age :20 --------华丽的分割线-------------- name :张 age :0
4. 方法重写
4.1 Override 重写
-
就是在子类中的把 父类方法重写一遍,前体是需要 修改或者扩展的方法,但是要遵循以下几点:
- 首先继承后,子类就拥有了父类的功能,如果子类方法名称和父类完全一样(包括方法的返回值,方法名和参数列表,完全一致),就会发生重写,标识符
Override
。 - 重写意义在于,外壳不变,核心重写, 可以添加子类特有的功能也可以修改父类的原有功能。
- 首先继承后,子类就拥有了父类的功能,如果子类方法名称和父类完全一样(包括方法的返回值,方法名和参数列表,完全一致),就会发生重写,标识符
-
方法重写时需要,参照以下几点:
- 当父类方法无法满足子类需求的时候,需要方法重写;
- 构造方法不能被重写;为什么?
- 父类的私有方法不能被重写; 即:
private 修饰符
- 子类重写父类方法时,修饰符要大于等于父类修饰符的权限。
private<默认(不写)<protected<public
4.2 重写 父类方法
- 古语有云:龙生九子,各有不同。
- 重写父类方法并扩展功能。
- 父类 Dragon
public class Dragon {
private String food; //属性
public String getFood() {
return food;
}
public void setFood(String food) {
this.food = food;
}
//1,权限protected
protected void function(){
System.out.println("控雨");
}
public void eat(){
System.out.println("喜欢吃"+food);
}
}
子类 Unicorn
public class Unicorn extends Dragon {
//1.重新父类方法要求完全一致
@Override
public void function() {
super.function();// 调用父类功能
//1.扩展自己的功能
System.out.println("喜欢和人类交朋友");
}
}
class Demo{
public static void main(String[] args) {
Unicorn u = new Unicorn();
u.setFood("玉米");
u.eat();
u.function();
}
}
-
输出结果:
喜欢吃玉米 控雨 喜欢和人类交朋友
5. 继承中的用法
5.1 成员变量的使用
- 就近原则 局部 < 成员< 父类。
public class Person {
int age = 30;
}
public class Teacher extends Person {
int age ; //子类属性
public void show (){
int age = 50; // 局部方法 age
System.out.println(age); //使用的局部age
// 想使用父类
System.out.println(super.age); // 如果想使用父类 age
System.out.println(this.age); // 如果想使用本类 age
}
}
class Demo{
public static void main(String[] args) {
int age = 40; // 没有一毛钱关系
Teacher teacher = new Teacher();
teacher.show();
}
}
输出代码:
50
30
0
5.2 成员方法的使用
- 继承,特有,重写方法
public class Teacher {
// 普通方法
public void show(){
System.out.println("自我介绍");
}
public void eat(){
System.out.println("印度美食,干净又卫生");
}
}
public class Students extends Teacher {
// 普通方法,或者 特有方法
public void showMe(){
super.show(); // 可以使用父类的介绍方法
System.out.println("唱,跳,rap");//加一些自己独特方法
}
// 如果想扩展自己的独特的吃饭,用到了重写
@Override
public void eat() {
//TODO 加一些逻辑上的处理
//... ...
System.out.println("老八秘制小汉堡!");
}
}
class Demo {
public static void main(String[] args) {
Students s = new Students();
s.showMe(); // 在父类方法上进行扩展
s.eat(); // 使用自己的方法
}
}
输出结果:
自我介绍
唱,跳,rap
老八秘制小汉堡!
5.3 构造方法的使用
- 子类创建对象时,默认去访问父类的无参数构造方法;
- 在子类所有构造方法中, 第一行都有默认的语句:
super();
写不写都存在! - 父类没有无参构造,可以用
super
调用其他构造方法,不然报错!~!
public class Person {
String name;
public Person(String name){
//sout(“”);可以什么都不输出!
System.out.println("父类:"+name);
}
// public Person(){
// System.out.println("Person");
// }
}
public class ZhangSan extends Person{
public ZhangSan(){
// 第一种:调用父类其他构造
super("周杰伦");
// this("许嵩"); //突发奇想,是否可以用this(参数),调用本类的带参数!?
System.out.println("ZhangSan");
}
public ZhangSan(String name){
//super(name); // 调用父类含参数 构造
this(); //调用本类
System.out.println(name);
}
}
class Demo2{
public static void main(String[] args) {
//new ZhangSan(); // 打印 Person ZhangSan?说明了有儿子的时候必须先有父亲
new ZhangSan("jjj");
}
}
- 提问: 如果父类中没有空参构造方法?子类继承会报错?如何解决?
6. 拓展
6.1 this 和 super 区别
- this代表 本类对象的引用,super代表 父类对象的引用。
- this用于区分 局部变量和成员变量。
this.成员变量 this.成员方法() this(【参数】)代表调用本类内容
- super用于区分 本类变量和父类变量。
super.成员变量 super.成员方法() super(【参数】),代表调用父类内容。
- this用于区分 局部变量和成员变量。
- super的前提条件式必须有继承, 不然没有super。
- this和super不可以同时出现在同一个构造方法里
- 因为语法要求this或者super都必须放在第一行,如果同时出现的话,到底第一行放谁呢。
6.2 重写 和 重载的区别
-
意义不同。
- 重写:是在建立了继承关系之后,如果子类想扩展父类方法,进行重写。在不修改源码的情况下,进行功能的修改与拓展。
- 重载:主要是方便程序调用,可以提供多种参数传递的选择。
-
创建语法结构不一样。
- 重写(
Override
):是指建立了继承关系以后,遵循方法名和返回值相同,权限大于重写父类权限原则。 - 重载(
OverLoad
):在一个类中 方法名必须相同 参数列表可以不相同(参数列表不同,参数个数,甚至顺序不同)则看作为重载!
- 重写(
-
注意: 重载对返回类型没有要求,可以相同也可以不同,对权限修饰也没有要求,但不能通过返回类型是否相同来判断重载。
public class Animal{
public void eat(Dog dog){
System.out.println("吃骨头");
}
public String eat(){
return "吃";
}
public void eat(Cat cat){
System.out.println("吃小鱼干");
}
}
7.简单介绍: 继承关系中的内存图
public class Fu {
int num = 10;
public Fu(){
System.out.println(num);
}
public void eat(){
System.out.println(" fu eat()");
}
}
public class Zi extends Fu {
int num = 20 ;
public Zi(){
super(); //1,初始化父类对象
System.out.println(num);
}
public void show(){
int num = 30;
System.out.println(num);
System.out.println(this.num);
System.out.println(super.num);
}
@Override
public void eat() {
System.out.println("zi eat()");
}
}
class Demo2{
// 所有的方法执行都是加载到栈内存中!
public static void main(String[] args) {
Zi zi = new Zi();// 第一步 做了什么? 10 20 ;
zi.show(); // 30 20 10 ;
zi.eat(); //重写了父类方法, 找的方式如同!
}
}