*继承的由来
class Student {
String name;
int age;
void eat() {
System.out.println("eat");
}
void study() {
System.out.println("study");
}
}
class Worker {
String name;
int age;
void eat() {
System.out.println("eat");
}
void work() {
System.out.println("work");
}
}
// 通过代码的表述,发现两个类中有重复的成员变量和成员函数,我们就可以将这些重复的内容进行抽取
class Person {
String name;
int age;
void eat() {
System.out.println("eat");
}
}
class Student {
void study() {
System.out.println("study");
}
}
class Worker {
void work() {
System.out.println("work");
}
}
共同部分都在的Person类中,Person称之为Student和Worker的父类,Student和Worker就是Person的子类
class Person {
String name;
int age;
void eat() {
System.out.println("eat");
}
}
class Student extends Person {
void study(){
System.out.println("study");
}
}
class Worker extends Person {
void work() {
System.out.println("work");
}
}
循环 解决的是有逻辑性的重复代码
函数 解决的是有独立功能的重复代码
继承 解决的是多个类中重复的代码
public class ExtendsDemo01 {
public static void main(String[] args) {
Student s = new Student();
s.name = "张三";
s.age = 10;
s.eat();
s.study();
System.out.println(s.name);
System.out.println(s.age);
}
}
Student而言,虽然name和age没有定义在Student类中,但是Student继承自Person,所以name、age和
eat()都是从Person类中来的!
继承的作用:
提高代码的复用性,提高开发效率
为我们后续多态这个概念提供了前提
单继承和多继承
继承让我们类之间产生了一种子父关系,到底我们什么时候去使用继承呢?
一定是多个类之间有重复的代码时就用继承吗?使用继承,必须保证类与类之间有所属关系(is a关
系),即子类必须是父类当中的某一种,苹果是水果的一种,笔记本是电脑的一种,但不能说狗是人的一种!
class Dog extends Person { //业务逻辑上而言 狗不能算是人的一种!
void kanmen() {
System.out.println("kanmen");
}
}
既然每一个类只能有一个父类,而父类可以有多个子类的,所以既可以产生出继承体系
class A {}
class B extends A{}
class C extends B{}
class D extends C{}
List ----ArrayList ----ArrayStack ----ArrayQueue
不能为了专门获取某一个类的功能而随意的产生继承!
class Bird {
public void fly(){}
}
class Person extends Bird{}
下面是关于继承应该注意的几点:
1.继承和传统的理解有一些不太一样的地方,子类并不是父类的一个子集,实际上子类通常比父
类包含更多的信息和方法,子类是一种基于父类的进化
class OldPhone {
public void call(){}
public void message(){}
}
class NewPhone extends OldPhone{
public void has5G(){}
public void app(){}
public void pay(){}
}
2.不是所有的is a关系都适用于继承来建模,正方形是矩形的一种,对于矩形而言有宽和高,但是
对于正方形而言,只有一个边
class Rectangle {
int width;
int height;
}
class Square extends Rectangle {
int side;
}
3.父类中如果成员变量被私有化了,子类是继承不到的,也就意味着子类获取不到,如果父类为
私有成员变量提供了修改器和访问器的话,子类是可以用的
class Fu {
private int num = 10;
public void setNum(int num) {
this.num = num;
}
public int getNum() {
return num;
}
}
class Zi extends Fu {
}
Zi zi = new Zi();
//Error System.out.println(zi.num); 子类访问不到父类中的私有成员变量
zi.setNum(20);
System.out.println(zi.getNum());
子父类中成员变量静态变量的特点
public class ExtendsDemo03 {
public static void main(String[] args) {
Zi zi = new Zi();
System.out.println(zi.num);
System.out.println(zi.haha);
System.out.println(zi.lala);
zi.show();
}
}
class Fu {
int num = 10;
int haha = 20;
static int xixixixixix = 666;
}
class Zi extends Fu{
int haha = 30;
int lala = 40;
static int num = 888;
public void show() {
int num = 50;
//num在show函数中已有局部变量 所以优先打印局部变量
System.out.println(num); //50
//haha没在show函数中,不是局部变量,去对象中找 30
System.out.println(haha); //30
//直接标记用对象去找num 但是Zi对象中没有num,去父类找
System.out.println(this.num); //10
//如果Zi类中有num这个静态变量的话,打印上的就是静态num 888
//在成员函数中 调用一个变量顺序 1局部 2对象 3静态 4父类
//直接标记用对象去找haha Zi对象中有haha 所以30
System.out.println(this.haha); //30
//show函数中没有局部lala 去对象找 有 则40
System.out.println(lala); //40
//直接指定去父类中找变量
System.out.println(super.num); //10
System.out.println(super.haha); //20
System.out.println(xixixixixix);
}
}
如果父类成员变量是私有的,子类是不能直接访问的,除非父类提供了修改器和访问器
父类有非私有成员变量,子类没有,调用的是父类的
父类有非私有成员变量,子类也有同名非私有成员变量,调用的是子类的
父类没有非私有成员变量,子类有,调用的是子类的
在子类的成员函数中,如果直接去访问一个变量,访问的顺序是 1.局部 2.子类对象 3.子类静态 4.父
类 5.父类静态
在子类的成员函数中,如果通过this去访问一个变量,访问的顺序 1.子类对象 2.子类静态 3.父类 4.
父类静态
在子类的成员函数中,如果直接访问父类变量的话,加super关键字即可 super并不像this,
super不指向父类对象,指向父类空间
public class ExtendsDemo03 {
public static void main(String[] args) {
Zi zi = new Zi();
zi.show();
}
}
class Fu {
static int num2 = 5;
int num1 = 4;
}
class Zi extends Fu{
static int num2 = 3;
int num1 = 2;
public void show() {
int num1 = 1;
System.out.println(num1);
System.out.println(num2);
}
}
public class ExtendsDemo04 {
public static void main(String[] args) {
Zi zi = new Zi();
zi.show();
}
}
class Fu {
public void show() {
System.out.println("Fu show...");
}
}
class Zi extends Fu {
public void show() {
System.out.println("Zi show...");
}
}
如果父类中的函数为私有的,子类是继承不到的
如果父类中的函数不是私有,子类也没有的话,子类对象调用的是父类的
如果父类中的函数不是私有,子类也有同样的话,子类对象调用的子类自己的
特别的,对于第三种情况, 我们称之为是子类的函数重写了父类的函数
重载:在同一个类中,有多个函数的函数名是一样的,但是参数列表中,参数的数类型的排布和
参数的个数不一样
public void function(int a,int b){}
public int function(int a,int b){}//不是 重载只看参数类型的排列组合 和返回值没关系
public void function(int b,int a){}//不是 重载只看参数类型的排列组合 和参数名没关系
protected void function(int a,int b){}//不是 重载只看参数类型的排列组合 和权限也没有关系
private int function(double a,int b){}//算
double function(double a,double b){}//算
public void function(int a, double b, int c)//算
public void function(int a,int c,double b)//算
重写:在子父类中,子类的函数声明和父类的函数声明是一样的,我们称之为子类的函数重写了
父类的函数
子类的返回值类型和父类的返回值类型必须一样的(showA的问题)
子类的函数声明和父类的函数声明一样,但是参数列表不一样的话,本身是重载关系
(showB的问题)
从第二点就可以得到一个结论,但凡想重写,返回值必须一样,参数列表也必须一样
子类重写的函数权限要大于等于父类的函数,不能小于父类函数的权限(showC的问题)
父类的函数如果是private 如果子类出现同样的函数 但权限比private大 这个不叫重写!!!
showD其实就是子类自身的成员函数,特有函数,和父类没关系甚至包括重写(showD的问
题)
public class ExtendsDemo04 {
public static void main(String[] args) {
Zi zi = new Zi();
zi.show();
}
}
class Fu {
public void show() {
System.out.println("Fu show...");
}
public void showB(int a,int b) {
}
private void showD(){}
}
class Zi extends Fu {
public void show() {
System.out.println("Zi show...");
}
//此处的showB虽然没有报错 但不是重写父类的showB
//它与父类中的showwB原先是重载关系,只不过showB(int int)被抽走了,就不能说现在是重写
public void showB(int a,int b,int c) {
}
public void shwoD(){}
}
父类只是提供大多数类中最基本的操作,这些操作都是多个子类中共有的内容,当然对于每一个子类而言,其也有自己的一些额外的操作
public class ExtendsDemo05 {
public static void main(String[] args) {
郭麒麟 大林子 = new 郭麒麟();
大林子.表演();
}
}
class HUAWEI_P40 {
public void function() {
System.out.println("有4G");
System.out.println("骁龙996CPU");
System.out.println("8G内存");
System.out.println("216G硬盘");
}
}
class HUAWEI_P40_PRO extends HUAWEI_P40{
public void function() {
System.out.println("有5G");
System.out.println("海思CPU");
System.out.println("16G内存");
System.out.println("512G硬盘");
System.out.println("HonymonyOS");
}
}
class 郭德纲 {
public void 表演() {
System.out.println("说相声");
System.out.println("唱京剧");
}
}
class 郭麒麟 extends 郭德纲 {
public void 表演() {
super.表演();//至少继承了郭德纲表演的内容
//super不是指父类的对象 而是指父类的空间
System.out.println("演电视剧");
System.out.println("参加综艺");
System.out.println("开演唱会");
}
}
子父类中构造函数的特点
public class ExtendsDemo06 {
public static void main(String[] args) {
Zi zi = new Zi();
}
}
class Fu {
int num = 10;
public Fu() {
System.out.println("Fu show...");
}
}
class Zi extends Fu {
int num = 20;
public Zi() {
System.out.println("Zi show...");
}
}
发现在创建子类对象的时候,父类的构造函数也会执行了!为什么?
是因为,子类的构造函数中会先去调用父类的构造函数! 通过super(XXX)来调用
父类中不定义任何构造函数,会有一个默认无参的构造函数存在,子类构造函数中隐藏的第一句
super()是有用的
父类中定义了无参构造函数,那个默认无参的构造函数就不存在了,子类构造函数中隐藏的第一句
super()还是有用的
父类中定义了带参数的构造函数,那个默认无参的构造函数就不存在了,子类构造函数中隐藏的第
一句super()就用不了了,报错
怎么解决?要么显示的指明super(XXX),要么显示的保留父类中无参构造函数,让子类构造函数中
隐藏的第一句super()有用
一般会为父类提供无参的构造函数
class Fu {
int num = 10;
public Fu () {
System.out.println("Fu show... ");
}
public Fu(int num) {
this.num = num;
System.out.println("Fu show... 1");
}
}
class Zi extends Fu {
int num = 20;
public Zi() {
super();
System.out.println("Zi show...");
}
public Zi(int num) {
super(30);
this.num = num;
System.out.println("Zi show... 1");
}
}
我们之前在讲构造函数的时候,this(xxx)这一句也必须在第1句,和此处的super(xxx)冲突不?在于构造函
数之间是不能够相互调用的,那么也就是说,在所有的构造函数中,至少会有一个构造函数的第一句是
super(xxx)。
class DemoA {
public DemoA(){
System.out.println("DemoA ...");
}
}
class DemoB extends DemoA{
int a;
int b;
int c;
public DemoB() {
this(0,0,0);
System.out.println("DemoB ...");
}
public DemoB(int a) {
this(a,0,0);
this.a = a;
System.out.println("DemoB ... 1");
}
public DemoB(int a,int b) {
this(a,b,0);
this.a = a;
this.b = b;
System.out.println("DemoB ... 2");
}
public DemoB(int a,int b,int c) {
super();
this.a = a;
this.b = b;
this.c = c;
System.out.println("DemoB ... 3");
}
}
因为我们的父类要给子类留一些数据让子类继
承,所以在这里执行父类的构造函数并不代表创建了父类的对象,而是为了个父类中的数据进行初始化操作的!
完了之后这些数据才能被子类继承!
public class ExtendsDemo07 {
public static void main(String[] args) {
Zi zi1 = new Zi();
zi1.show();
Zi zi2 = new Zi(888);
zi2.show();
zi2.zhiyi();
}
}
class Fu {
int num = 10;
public Fu() {
//super();//有 只不过我们先不考虑
show();
}
public Fu(int num) {
//super();//有 只不过我们先不考虑
show();
this.num = num;//特别的,是一个坑,此处的this指的父类的 this不是指子类 父类
show();
}
public void show() {
System.out.println(num);
}
}
class Zi extends Fu {
int num = 20;
public Zi() {
super();
show();
}
public Zi (int num) {
super(666);
show();
this.num = num;
show();
}
public void show() {
System.out.println(num);
}
public void zhiyi() {
System.out.println(super.num);
}
}
final关键字
继承的出现提高了代码的复用性,并方便开发,但会随之而来一个问题,在有些类描述完成后,如果该
类不想被继承,或者该类当中有些方法是固定的,不想被子类去重写,一旦当我们子类继承了之后,就
可以对所有内容(只要不是私有的)都可以重写。
对于final关键字,它可以修饰变量、函数、类
final修饰变量(局部变量,成员变量,静态变量)
final修饰变量,表示变量空间中的数值不能被修改!如果是基本数据类型,那么表示变量存储的常量不
能被修改;如果是引用数据类型,则表示变量存储的对象的地址不能被修改(对象里面的属性是可以被
修改的)!
局部变量一旦定义的同时并赋值,且被final修饰的话,其值不能被修改
final int num = 6;
局部变量开始仅仅是定义并未赋值,且被final修饰的话,第一次赋值后,值不能被修改
final int num; num = 6;
局部变量是引用数据类型,且被final修饰的话,表示引用的地址不能改变,但是对应的对象中的数
据是可以改变的
final Person p = new Person("张三",18);
System.out.println(p);
p.name = "李四";
p.age = 30;
System.out.println(p);
Person p1 = new Person("王五",100);
p = p1; //不能修改p原先所指向的那个地址
成员变量被定义为final,但没有显示初始化的值的时候,那么针对性初始化为其最终的赋值,对象
创建之后,成员变量就不能修改了
class Person {
final String name;
final int age;
public Person(String name,int age) {
this.name = name;
this.age = age;
}
public String toString() {
return name + ":" + age;
}
}
Person p = new Person("张三",18);
p.name = "李四";//针对性初始化之后,修改的操作都报错
p.age = 30;
System.out.println(p);
成员变量被定义为final,但是有显示初始化的值的时候,那么显示初始化之后,其值就不能被修改
了
class Person {
final String name = "王五";
final int age = 100;
public Person(String name,int age) { //针对性初始化的操作就报错了
this.name = name;
this.age = age;
}
public String toString() {
return name + ":" + age;
}
}
Person p = new Person("张三",18);
System.out.println(p);
当成员变量被定义为final的时候,那么就忽略其默认初始化的值,后面显示初始化和针对性初始 化,谁先来,谁就是最终的赋值。
class Person {
public final static int num = 10;
}
Person p1 = new Person();
System.out.println(p1.num);
p1.num = 100;//Error 修改不了
Person p2 = new Person();
System.out.println(p2.num);
final修饰成员函数表示该成员函数不能被重写,final修饰一个类表示该类不能被继承
class DemoA {
public final void showA(){}
}
class DemoB extends DemoA{
public void showA(){}//DemoB中的showA()无法覆盖DemoA中的showA()
}
final class DemoA {
}
class DemoB extends DemoA{//无法从最终DemoA进行继承
}
抽象类
当我们编写一个类时,我们往往会为该类定义一些方法,这些方法是用来描述给类的行为方式,所以这
些方法一般都会有具体的执行内容也就是方法体。
但是有些时候,某个父类只是知道子类应该包含什么样的方法,但是无法得知子类的具体实现内容。相
当于我们代码中Dog的jiao是明确,Cat的jiao也是明确的,当我们把多个类中共同的部分向上抽取,抽取
出来的内容再被另外一个类封装,这个类我们称之为是父类Animal,但是对于Animal而言,jiao如何实
现呢?具体jiao的实现内容是由Animal具体的某一个子类来实现的,所以目前Animal对于jiao这个函数的
具体功能是不明确的,那么我们在定义这个函数的时候,就可以将该函数定义为抽象函数。包含了抽象
函数的类,这个类就必须是抽象类
abstract class Animal {
public abstract void jiao();
}
class Dog extends Animal{
public void kanmen() {
System.out.println("看门~");
}
}
class Cat extends Animal{
public void zhuolaoshu() {
System.out.println("捉老鼠~");
}
}
比如:每一个图形都能计算面积和周长,只不过对应到每一个具体的图形而言,计算面积和周长的方式
是不一样的,所以对于Shape而言,它只知道它的子类们都能有这俩功能,但是对于Shape自身而言,它
是不确定这两个功能是如何实现的,具体的实现就应该有具体的子类来完成,所以Circle和Reactangel在
继承Shape之后,还要去重写来自Shape的抽象函数(面积 周长),如果不重写的话,该子类还是包含
继承来的抽象函数,那么也必须定义为抽象类。
abstract class Shape {
public abstract double getArea();
public abstract double getPrimeter();
}
class Circle extends Shape {
double r;
public double getArea() {
return Math.PI * r * r;
}
public double getPrimeter() {
return 2 * Math.PI * r;
}
}
class Rectangle extends Shape {
double width;
double heigth;
public double getArea() {
return width * heigth;
}
public double getPrimeter() {
return 2 * (width + heigth);
}
}
抽象类的特点:
抽象类和抽象方法都需要被abstract关键字修饰,抽象方法一定在抽象类中
抽象类不可以创建对象的,因为你自身的成员函数都是不确定的,那么这个对象也就是不确定的
只要覆盖了重写了抽象类中所有的方法后,其子类才能被实例化(创建对象),否则该子类还是一
个抽象类
关于抽象类的几个小问题:
抽象类一定是父类?是的,抽象类本身就是由多个类不断向上抽取的结果,并且其抽象函数还需要
被具体的实现子类覆盖重写
抽象类是否有构造函数?有的,抽象类和普通类唯一的区别就在于我们的抽象类中有抽象函数而
已!
抽象类中的关键字abstract不能和哪些关键字同时存在?
final不让函数被重写,抽象函数就等着被子类重写!
private函数不能被子类继承,抽象函数就等着被子类继承且重写!
static修饰的静态函数是存储与方法区中,它既然存储在静态方法区中,就和对象没有关系
了,是直接通过类名调用,但是由于那时抽象的,没有具体的调用结果!所以不能和static同
时的。



