4. 面向对象(第七天)

一   继承的概述

1.“继承”概念的引入

例如学生和工人,都有name和age,可以抽取出来,

将学生和工人的共性描述提取出来,单独进行描述,

只要让学生和工人与单独描述的这个类有关系,就可以了。

由此,抽取到class Person里面,(其实类本身就是从对象的特征中抽取共性出来的)。

class Student  extends Person (extends是继承的关键字)

学生就是子类,Person就是父类(或者超类)。

2.“继承”的好处

  • 提高代码的复用性
  • 让类与类之间产生了关系,有了这个关系才有了多态的作用


事物可以被描述,现实生活中,就有继承这种关系。

注意:千万不要为了获取其他类的功能,简化代码而继承。必须是类与类之间有所属关系才可以继承,所属关系是“is a”。

3.Java语言中“继承”的特点

(1) java只支持单继承,不支持多继承。

即一个类只能 extends 一个super类,不允许多个,因为容易产生安全隐患。

例如,A类和B类都有show()方法,分别打印“A”、“B”两个字母,

C如果同时继承了A类、B类,那么new C.show(); 要打印字母A还是B呢?因此,Java只支持单继承。

但是,Java也改良了多继承机制,用另一种方式体现,叫“多实现”。

(2)java支持多层继承(即可以构建一个继承体系)。

查阅父类:要使用一个继承体系的功能,可以先通过查阅体系中父类的描述。

          因为在父类中定义的是该体系共性的功能,以此了解该体系的基本功能。

创建子类:具体调用时,要创建最子类的对象。因为:

  1. 有可能父类不能创建对象,如抽象类;
  2. 创建子类对象可以使用更多功能,包括基本功能和特有功能。

总结起来就是简单的一句话:查阅父类功能,创建子类对象使用功能

二   聚集关系


程序反应的是现实中的关系,类与类之间不只有继承关系,也有聚集(包括聚合和组合)关系。

1、聚合关系 ,例如:球队由球员组成,球员是球队中的一个,球队中有球员。

2、组合关系 ,例如:手、脚、心脏是身体不可分割的一部分。

三   子父类中变量的特点


类中成员包含三个部分 :变量、函数、构造函数

1. 关于子父类中的变量

  • 子类中出现非私有的同名成员变量时,子类要访问本类中的变量,使用this(一般可以省略,因为默认是this);
  • 子类要访问父类中的同名变量,用super(不可以省略)。
  • super的使用和this的使用几乎一致,this代表的是本类对象的引用,super代表的是父类对象的引用。
  • 父类在内存中的情况:首先子类的对象都在堆内存中,new子类对象的时候,加载子类的class文件之前,会先加载父类的class文件。
  • 内存区中,会存在父类的变量和函数。(即使父类的num是private的,在内存中还是在子类对象里面。)
  • 子类继承了父类后,父类的变量就相当于子类的变量了

注意:

即使父类的成员变量是私有的,也可以继承,只是不能对外访问而已。

同名变量的情况比较少见,没必要重新定义。

其实更准确地表述,应该是:"this是当前对象在堆空间的引用地址,super是当前对象的父类 特征 的引用"

2. 示例代码

class Fu {
	int num = 1;
	int numFu = 1;
}

class Zi extends Fu {
	int num = 2;
	void show() {
		System.out.println(num); // 打印2,因为前面省略了this
		System.out.println(super.num); // 打印1,super代表父类对象的引用
		System.out.println(numFu); // 打印1
		System.out.println(this.numFu); // 打印1
		// 两种理解方式
	}
}

class Demo {
	public static void main(String[] args) {
		Zi z = new Zi();
		z.show();
		System.out.println(z.numFu + "..." + z.num); // 1...2
	}
}

依然可以运行,子类的堆内存中有父类的num,可以用this访问,也可以用super访问,都指向子类的对象。

四   子类父类中函数的特点 - 重写(覆盖)


1、当子类出现和父类一模一样的函数时,当子类对象调用该函数,

   会运行子类函数的内容,如同父类的函数被覆盖一样,

   这种情况是函数的另一个特性:重写(覆盖)

2、父类的方法依然在内存中,只不过没有运行 

3、当子类继承父类,沿袭了父类的功能,到子类中,但是子类虽具备该功能,但是该功能的内容却和父类不一致,

   这时,没有必要定义新功能,而是使用覆盖特殊,保留父类的功能定义,并重写功能内容(重写一定是有不同的内容,否则无须重写)。

   开发中,修改以前的代码是一种灾难。应该利用继承来扩展程序的功能,重写父类的功能。简化代码可以用super.方法名();。

4、子类如果要覆盖父类,必须保证子类权限大于等于父类权限,才可以覆盖,否则编译失败

   例如,父类方法为private,子类为默认权限,介于私有和公有之间。

   权限:public > 默认 > pirvate

   这种情况就可以覆盖。

5、静态只能覆盖静态,不能覆盖非静态,因为没有加载对象。很少用。

6、不指定权限,则使用默认权限,介于public 和 private

7、父类的方法如果是private 权限则无法覆盖。因为如果为 private 子类是不知道父类有该方法的,编译通过,但是执行失败。

8、子类如果要覆盖父类的函数,则 子类 和 父类 的函数,修饰符、返回值等等必须一模一样,否则 jvm 也不知道用谁。

    要么覆盖,要么不能重名,否则无法运行。例如,父类:int show();和子类:void show();

class Fu {
	private void show() {
		System.out.println("Fu show");
	}
	private void show1() {
		System.out.println("Fu show");
	}

}

class Zi extends Fu {
	void show() {
		System.out.println("Zi show");
	}
}

class Demo {
	public static void main(String[] args) {
		Zi z = new Zi();
		z.show(); // Zi show
		// z.show1();
	}
}

记住,重写和重载的比较:

重载

重写

相同

函数名称一样,内容不一样。

要点

只看同名函数的参数列表

子父类方法要完全一样,包括返回值

否则无法覆盖,也无法运行。

应用

一个类中

子父类中

权限

不影响

子类权限大于等于父类权限

五   子类父类中构造函数的特点 - 子类实例化过程


构造函数也是函数,也存在复写。

class Fu {
	Fu() {
		System.out.println("Fu");
	}
	Fu(int x) {
		System.out.println("Fu");
	}
}

class Zi extends Fu {
	Zi() {
		// super(); 隐含父类的构造函数
		System.out.println("Zi");
	}
	Zi(int x) {
		// super(); 隐含父类的构造函数
		System.out.println("Zi");
	}
}

class Demo {
	public static void main(String[] args) {
		Zi z1 = new Zi();
		Zi z2 = new Zi(4);
	}
}

输出:
Fu

Zi

Fu

Zi

在对子类对象进行初始化时,父类的构造函数也会运行。

那是因为子类的构造函数默认第一行,有一条隐式的语句 super();

super() 会访问父类中空参数的构造函数,而且子类中所有的构造函默认的第一行都是 super()


Q:为什么子类一定要访问父类中的构造函数?

A:因为父类中的数据,子类可以直接获取,所以子类在建立时,需要先查看父类是如何对这些数据进行初始化的。

   所以子类在对像初始化时,要先访问一下父类中的构造函数。

如果要访问父类中指定的构造函数,可以通过手动定义 super 语句的方式来指定。

子类中至少会有一个构造函数,会访问父类中的构造函数,这就是子类的实例化过程。

当然,最顶的父类也有 super();  它 extends Object。

class Fu {
	private String name;

	Fu(String name) {
		this.name = name;
	}
}

class Zi extends Fu {
	Zi(String name) {
		super(name);// 父类定义好的功能,直接用就可以了,没有必要重新定义
	}
}

class Fu {
	Fu() {
	}
}

class Zi extends Fu {
	Zi() {
	}

	Zi(String name) {
		this(); // 这个构造函数就没有隐含super();
	}
}

注意:super语句一定要定义在子类构造函数的第一行(还要注意参数列表)


结论:

子类的所有构造函数,默认都会访问父类中的空参数的构造函数

因为子类每一个构造函数内的第一行都有一句 隐式super()

当父类中没有空参数的构造函数时,子类必须手动通过 super 语句形式来指定要访问父类中的构造函数

当然,子类的构造函数第一行也可以手动指定this语句,来访问本类中的构造函数,

六   final关键字


final:最终的意思。final关键字作为修饰符,特点如下:

1. 可以修饰类、方法和变量。

2. 被final修饰的类不可以被继承。(为了并避免被继承,被子类复写功能)

继承有利有弊,弊端就是打破了封装性。

所以有些类不允许继承,内部的功能不运行复写。

要么直接用这个类,要么不用。

为了避免被继承、被子类复写功能,用final修饰,最终类。

3. 被final修饰的方法不可以被覆盖。只能另建新方法。强制某些方法不能被覆盖,保证使用。

4.final修饰的变量是一个常量,只能被赋值一次,和static不同,即使再次赋的值一样也不行。既可以修饰成员变量,也可以修饰局部变量(函数中)。

当在描述事物的时候,一些数据的出现值是固定的,这时为了增强阅读性,给这些值起个名字,方便阅读。

而这个值不需要改变,所以加上final修饰,锁住这个变量。

例如,比如 π值,finaldouble PI =3.14;固定数据,即使出现一次也可以用final修饰。

为了方便访问,可以这样:publicstatic final double PI = 3.14;全局常量。

5.常量的书写规范:作为常量,所有字母都大写,如果由多个单词组成,单词间通过 _ 连接

6.public static final修饰,一般成为全局常量


*内部类定义在类中的局部位置上时,只能访问该局部被final修饰的局部变量。

final class Demo1 { // 被final修饰的类不可以被继承

}

class Demo2 {
	final void show1() { // 被final修饰的方法不可以被覆盖
	}

	void show2() {
		final int x = 1; // final修饰的变量是一个常量,只能被赋值一次,即使再次赋的值一样也不行
		int y = 2;
	}
}

class Demo3 extends Demo2 {
	void show2() {
	}
}

class Demo {
	public static void main(String[] args) {
	}
}

现在为止学的类修饰符:public、final或者默认什么都不写

七   抽象类

1、引入抽象类

当多个类中出现了相同功能(函数声明)、但功能主体不同,这时也可以进行向上抽取,只抽取功能定义,而不抽取功能主体。

例如,void study()。对于这种表面上看不懂的函数,使用abstract抽象关键字,意为“笼统、模糊、看不懂”。

抽象方法调用没有意义,所以必须存放在抽象类中,也用abstract修饰。

不要创建抽象类的对象,没有意义。abstract只能修饰类和方法。

2、抽象类的特点

  • 抽象方法一定定义在抽象类中。
  • 抽象方法和抽象类都必须被abstract关键字修饰。
  • 抽象类不可以用new创建对象,虚拟机无法编译。因为调用抽象方法没有意义。但是可以创建引用。
  • 抽象类中的方法要被使用,必须由子类复写其所有的抽象方法后,建立子类对象调用。
  • 如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类。
  • 抽象类中既可以有抽象函数,也可以由非抽象函数,

3、示例代码

abstract class Student { // 学生抽象类
	abstract void study(); // 抽象方法,无方法体,记住

	void sleep() { // 可以邮费抽象方法
		System.out.println("Student sleep");
	}
}

class BaseStudent extends Student {
	void study() { // 必须重写抽象方法
		System.out.println("BaseStudent study");
	}
}

class AdvStudent extends Student {
	void study() {
		System.out.println("AdvStudent study");
	}
}

public class Demo {
	public static void main(String[] args) {
		BaseStudent bs = new BaseStudent();
		bs.study(); // 使用重写的抽象方法
		bs.sleep(); // 使用抽象类中,非抽象方法
	}
}

“抽象类可以强迫子类去做一些事情”

抽象类和一般类没有太大不同,该如何描述事物,就如何描述事物,只不过,该事物出现了一些看不懂的东西。

这些不确定的部分,也是该事物的功能,需要明确出现,但是无法定义主体。 通过抽象方法来表示。

抽象类比一般类多了抽像函数,就是在类中可以定义抽象方法

抽象类不可以实例化


特殊:抽象类中可以不定义抽象方法,这样做仅仅是不让该类建立对象

抽象类和一般类的比较

抽象类

一般类

相同

描述事物的方法依旧,都有一般方法。

不同

出现了看不懂的内容,即抽象方法。

无法定义主体。

有实在内容。内部方法都可以

直接使用,无需复写。

对象

无法建立,不可以实例化,但可以

建立引用。

可以建立对象。

   

抽象类练习

假如我们在开发一个系统时,需要对员工进行建模,

员工包含3个属性:姓名、工号、工资,经理也是员工,

除了含有员工的属性外,另外还有一个奖金属性,

请使用继承的思想设计出员工类,和经理类,要求类中提供必要的方法进行属性访问。

  • 员工类:name   id   pay
  • 经理类:继承了员工,并有自己特有的 bonus(奖金)

abstract class Employee {
	private String name;
	private String id;
	private double pay;
	Employee(String name, String id, double pay) {
		this.name = name;
		this.id = id;
		this.pay = pay;
	}
	public abstract void work();

}

class Manager extends Employee {
	private int bonus;
	Manager(String name, String id, double pay, int bonus) {
		super(name, id, pay);
		this.bonus = bonus;
	}

	public void work() {
		System.out.println("Manager work");
	}
}

class Pro extends Employee {
	Pro(String name, String id, double pay, int bonus) {
		super(name, id, pay);
	}

	public void work() {
		System.out.println("Pro work");
	}
}

八  模板方法模式


需求:获取一段程序运行的时间

原理:获取程序开始和结束的时间并相减

获取时间:System.currnetTimeMillis();→返回:当前时间与协调世界时 1970 年 1 月 1 日午夜之间的时间差(以毫秒为单位测量)。

模板方法设计模式:

在定义功能是,功能的一部分是确定的,但是有一部分是不确定的,而确定的部分在使用不确定的部分,

那么这时就将不确定的那部分暴露出去,有该类的子类去完成。这种方式,叫模板方法设计模式。

确定部分最好用final,使子类不能覆盖。不一定是抽象的,比如有默认的操作方式。

提高了扩展性、复用性。模板已经确定,但是具体内容不确定,由子类完成。

abstract class GetTime {
	public final void getTime() {
		long start = System.currentTimeMillis();// 暂时不需要理解
		runCode();
		long end = System.currentTimeMillis();
		System.out.println("毫秒:" + (end - start));
	}

	public abstract void runCode();

}

class SubTime extends GetTime {
	public void runCode() {
		for (int x = 0; x < 4000; x++) {
			System.out.print(" " + x);
		}
	}
}

public class Demo {
	public static void main(String[] args) {
		SubTime gt = new SubTime();
		gt.getTime();
	}
}

九   接口


1、初期理解:可以认为是一个特殊的抽象类,当抽象类中的方法都是抽象的,那么该类可以通过接口的形式来表示。
             class 用于定义类, interface 用于定义接口

2、重点在格式上,接口定义时的格式特点:

  • 接口中常见定义:常量、抽象方法
  • 接口中的成员都有固定修饰符
  • 常量:public static final
  • 方法:public abstract
  • 接口中的成员都是 public
  • 接口:是不可以创建对象的,因为有抽象方法。需要被子类实现,子类对接口中的抽象方法都覆盖后,子类才可以实例化,否则子类是一个抽象类

3、因为抽象类中可以有非抽象函数,所以要 extends ,但是接口中都为抽象,所以要用 implements

4、接口可以被类多实现。(虽然java不支持多继承,但支持多实现)

不可以多继承是因为,有可能多个父类中有同名的函数

接口可以多实现的原因是,接口中都为抽象方法,抽象方法都没有方法体,具体方法实现在子类中实现,不影响

5、一个类继承了一个类之后,还可以实现多个接口

6、接口和接口之间是继承关系

  • interface 类名 extends 类名
  • 接口之间可以多继承,因为没有方法体,interface 类名 extends 类名,类名
  • 但是函数名之间不能相同

7、接口的特点

  • 接口是对外暴露的规则
  • 接口是程序的功能扩展
  • 接口可以用来多实现
  • 类与接口之间是实现关系,而且类可以继承一个类的同时实现多个接口
  • 接口与接口之间可以有继承关系,多继承也可以

interface Inter {
	public static final int NUM = 3;

	public abstract void show();
}

interface InterTwo {
	// public static final int NUM = 4;
	public abstract void show();
}

interface InterThree extends Inter {
	public abstract void show();
}

class Test implements Inter, InterTwo {
	public void show() {
		System.out.println("Test implements Inter");
	}
}

public class Demo {
	public static void main(String[] args) {
		Test t = new Test();
		t.show(); // Test implements Inter
		System.out.println(t.NUM); // 3
		System.out.println(Test.NUM); // 3
		System.out.println(Inter.NUM); // 3
	}
}

interface A {
	void methodA();
}

interface B extends A {
	void methodB();
}

interface C extends B {
	void methodC();
}

interface D extends A, B, C {
}

class E implements C {
	@Override
	public void methodC() {
	}

	@Override
	public void methodB() {

	}

	@Override
	public void methodA() {

	}
}

十   接口的特点、举例体现

1、接口的特点

  • 接口是对外暴露的规则。
  • 接口是程序的功能扩展。体系以外的功能扩展。
  • 接口可以用来多实现,降低了耦合性。

例如,主板上的插槽就是对外暴露的规则,符合规则的内存条、CPU等都可以插上使用,扩展了主板的功能,

同时降低了CPU和主板的关系,即耦合性(紧密联系程度)只要符合就能一起工作,

同一个CPU可以插在不同的主板上使用。接口类似这些插槽。模块化开发。

  • 接口与类之间是实现关系;类可以继承一个类的同时再实现多个接口。
  • 接口与接口之间是继承关系。

2、使用思路

子类继承父类的全部功能;但是,有的子类需要某些功能,

有的子类不需要某些功能,可以将这些功能作为接口,需要的用,不需要的不用,

防止不需要的子类也带上这些功能,而且其他的子类也可以使用这些接口;

如果所有的子类都需要这些功能,但是具体的内容不同,可以使用抽象类,

子类自己进行覆盖,或者使用一般类,少部分的子类通过覆盖获得自己所需要的功能。

不同体系之间,基本功能和扩展功能有不同,例如,学生和运动员。基本功能定义在类中,扩展功能定义在接口中。

十一   接口举例体现


1、继承是属于这一类的,接口是类似

2、接口一般当做是功能的扩展,因为多实现

3、例子

abstract class Student {
	abstract void study();
	void sleep() {
		System.out.println("sleep");
	}
}

interface Smoking {
	void smoke();
}

class ZhangSan extends Student implements Smoking {
	void study() {
		System.out.println("study");
	}

	public void smoke() {
		System.out.println("smoke");
	}
}

public class Demo {
	public static void main(String[] args) {
		ZhangSan z = new ZhangSan();
		z.study();
		z.smoke();
	}
}

4、基本功能定义在类中,扩展工能定义在接口中

5、基本功能就是属于这个体系的,共通的,扩展功能是特殊的,个别的,额外的功能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值