栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java08—继承

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java08—继承

继承
*继承的由来
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同
时的。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/325351.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号