Java基础9--继承--抽象类--接口

9-1,子父类的构造函数-子类的实例化过程

1,在子类构造对象的时候,发现访问子类构造函数时,父类的构造函数也运行了,这时为什么呢?

原因是:在自类的构造函数中第一行有一个默认的隐式语句,就是super()。

super()调用的就是父类中空参的构造函数。

示例:

class Fu{
	Fu() {
		super();//Fu继承自Object,也有一个super(),访问的是Object的空参构造函数
		System.out.println("Fu run ...");
	}
}
class Zi extends Fu {
	Zi() {
		super();//这里访问的是Fu的空参构造函数
		System.out.println("Zi run ...");
	}
}
class ExtendsDemo {
	public static void main(String[] args) {
		new Zi();
	}
}

运行结果是:

Fu run ...

Zi run ...

在本例中,如果Fu没有空参构造函数,如只有一个Fu(int x),则编译出错,在子类中必须显示的调用父类构造函数,并且也必须在Zi构造函数的第一行,比如super(1),即可解决。

继承时,构造函数不能被继承过来,也不能被覆盖。

 

2,子类的实例化过程

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

示例:

class Fu {
	Fu() {
		super();
		System.out.println("A Fu run ...");
	}
	Fu(int x) {
		super();
		System.out.println("B Fu run ..." + x);
	}
}
class Zi extends Fu {
	Zi() {
		super();
		System.out.println("C Zi run ...");
	}
	Zi(int x) {
		super();
		System.out.println("D Zi run ..." + x);
	}
}
class ExtendsDemo {
	public static void main(String[] args) {
		new Zi(6);
	}
}

程序结果:

A Fu run ...

D Zi run ...6

实例化过程为:

创建Zi对象,调用Zi的构造函数Zi(int x),在Zi(int x)中有默认的额super(),再调用Fu中的Fu(),Fu()中有super(),调用Object的Object(),这个函数啥都没做,直接return,返回到Fu()中,执行下一句打印,输出A Fu run ...,再返回Zi(int x)输出D Zi run ...6。

若在Zi(int x)中加入super(6),则先调用Fu中的Fu(int x),再输出Zi(intx)中的语句。

 

9-2,子父类中的构造函数-子类实例化过程细节

1,为什么子类实例化的时候要访问父类中的构造函数呢?

因为子类继承了父类,就取到了父类中的内容(属性),所以在使用父类的内容之前,要先看父类是如何对自己的内容进行初始化的。所以子类在构造对象时,必须访问父类中的构造函数,为了完成这个必须的动作,就在子类的构造函数的第一句中加入了super()语句,这是个默认的隐式语句,不写也会调用。

如果父类中没有定义空参的构造函数,那么子类的构造函数必须用super明确要调用父类中的那个构造函数。

注意:super语句必须定义在子类构造函数的第一行,因为父类的初始化动作要先完成。

子类构造函数中,如果使用this调用了本类的构造函数,那么super这个隐式语句就没有了,因为super和this都只能定义在第一行,所以二者只能存在一个。但是可以保证的是,子类中肯定会有其他的构造函数访问父类的构造函数。

 

2,示例:

class Demo extends Object {//java中所有的类都继承自Object
	/* 这是类中自带的默认的构造函数,自带有super
	Demo() {
		super();
		return;
	}
	*/
}

从这个例子中可以看出,我们写一个Demo类,在这个类什么都没写的时候,就已经具备注释中的内容了。

 

9-3,子父类构造函数-子类实例化过程-内存图解

1,一个对象的实例化过程:

以Person p = new Person();为例。

(1)JVM会读取指定路径(classpath)下的Person.class文件,并加载进内存,并会先加载Person的父类(如果有直接父类的情况下)。

(2)在堆内存中开辟空间,分配地址。

(3)并在对象空间中,对对象的属性进行默认的初始化。

(4)调用对应的函数进行初始化。

(5)在构造函数中,第一行会先调用父类中构造函数进行初始化。

(6)父类初始化完毕后,再对子类的属性进行显示初始化。

(7)再进行子类构造函数的特定初始化。

(8)初始化完毕后,将地址值赋给引用变量。

 

2,示例:

class Fu {
	Fu() {
		super();
		show();
		return;
	}
	void show() {
		System.out.println("fu show");
	}
}
class Zi extends Fu {
	int num = 8;
	Zi() {
		super();
		return;
	}
	void show() { //覆盖了Fu中的show
		System.out.println("zi show ..." + num);
	}
}
class ExtendsDemo{
	public static void main(String[] args) {
		Zi z = new Zi();
		z.show();
	}
}

运行结果:

zi show ... 0

zi show ... 8


步骤:

(1)ExtendsDemo类的方法加载进方法区。

(2)main方法加载进静态方法区。

(3)main进栈。

(4)Zi z = new Zi();开始加载Zi类,由于Zi类继承了Fu类,所以Fu类先加载进方法区,然后Zi类加载。

(5)z对象进栈。

(6)实例化z对象,再堆中开辟空间,分配地址,num默认初始化为0。

(7)Zi()构造方法进栈,自带this引用,并指向0X0045。

(8)Zi()中super()引用Fu的构造函数Fu(),Fu()构造方法进栈,自带this引用,指向0X0045,执行show方法,由于Zi类中重写了Fu类中的show方法,把Fu类中的show覆盖,Fu()指向Zi类对象z,调用z中的show方法,且这时num的值为0,故此时输出zi show...0。

(9)Fu()运行结束,弹栈。

(10)回到Zi()构造方法中,继续显示初始化num为8.

(11)Zi()运行结束,弹栈。

(12)实例化完毕,把0X0045赋给z,Zi指向堆内存。

(13)show方法进栈,自带this引用指向0X0045,输出zi show ...8。

(14)show运行完毕,弹栈。

 

9-4,final关键字

1,继承的弊端:打破了封装性。

2,final关键字:

(1)final是一个修饰符,可以修饰类、方法、变量。

(2)final修饰的类不可以被继承。

(3)final修饰的方法不可以被覆盖。

(4)final修饰的变量是一个常量,只能赋值一次。

3,为什么用final修饰变量?

在程序中,如果一个数据是固定不变的,那么直接使用这个数据就可以了,但是这样阅读性很差,所以应该给这个数据起一个名称,而且这个变量名称的值不能变化,所以加上final固定。若该数据是要被共享的,可以在加上static修饰符,可以直接被类名调用,若该常量在全局有效,可以加上public修饰,使全县最大化。

4,写法规范:

常量的所有字母都大写,多个单词之间用下划线连接。

 

9-5,抽象类-概述

1,例:

abstract class Person {//抽象类
	abstarct void show();//抽象方法
}

2,当遇到不具体的事物时就定义成抽象的,如犬科都有吼叫功能,狗和狼的叫声是不同的,但都具备吼叫功能,所以犬科的吼叫方法定义为抽象的,由于有不确定的方法,所以该类也是不确定的,该类也定义为抽象的。

3,示例:

abstarct class 犬科{
	abstract void 吼叫();
}
class 狗 extends 犬科 {
	void 吼叫() {
		System.out.println("汪汪");
	}
}
class 狼 extends 犬科 {
	void 吼叫() {
		System.out.println("嗷嗷");
	}
}

因为犬科中有一些是具体的方法,如睡觉功能,狗和狼的睡觉功能是一样的,所以向上提取出犬科类,而不在狗和狼类中直接写吼叫的方法。

 

9-6,抽象类-特点

特点:

(1)方法只有声明没有实现时,该方法就是抽象方法,需要被abstract修饰,抽象方法必须定义在抽象类中,该类必须也被abstract修饰。

(2)抽象类不可以被实例化,为什么?因为调用抽象方法没有意义。

(3)抽象类必须由其子类覆盖了所有的抽象方法后,该子类才可以被实例化,否则这个子类还是抽象类。

 

9-7,抽象类-细节

1,抽象类中有构造函数么?

有,用于给子类对象初始化。

2,抽象类可以不定义抽象方法么?

可以,但是很少见,目的就是不让这个类创建对象。AWT的适配器对象就是这种类。通常这个类中的方法有方法体,但是没有内容。

3,抽象关键字不可以和哪些关键字共存?

(1)private不行,因为抽象方法必须被子类覆盖并实现才能创建对象,若用private修饰,则子类不能覆盖这个抽象方法。

(2)static不行,用static修饰的方法可以不用对象调用,直接用类名调用,但是调用抽象方法是没有意义的,所以不兼容。

(3)final不行,抽象方法必须被覆盖,而加上final就不能被覆盖了,冲突。抽象类可以有子类,用final修饰则不能有子类,冲突。

4,抽象类和一般类的区别

相同点:抽象类和一般类都是用来描述事物的,都在内部定义了成员。

不同:

(1)一般类有足够的信息描述事物。

抽象类描述事物的信息有可能不足。

(2)一般类中不能定义抽象方法,只能定义非抽象方法。

抽象类中可以定义抽象方法,同时也可以定义非抽象方法。

(3)一般类可以被实例化。

抽象类不可以被实例化。

5,抽象类一定是一个父类么?

是的。因为需要子类覆盖其方法后才可以对子类实例化。

 

9-8,抽象类的一个小练习

需求:雇员示例:

公司中程序员有姓名,工号,薪水,工作内容。

项目经理除了有姓名,工号,薪水,还有奖金,工作内容。

对给出数据进行数据建模。

分析:

在这个领域中,先通过名词提炼找出涉及的对象。

程序员:

    属性:姓名、工号、薪水

    行为:工作

经理:

    属性:姓名、工号、薪水、奖金

    行为:工作

程序员和经理不存在直接继承的关系,但是程序员和经理有着共性的内容,可以进行抽取。因为他们都是公司的雇员。

可以将程序员和经理进行抽取,建立体系。

//描述雇员
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 Programmer extends Employee {
	Programmer(String name,String id,double pay) {
		super(name,id,pay);
	}
	public void work() {
		System.out.println("code...");
	}
}
//描述经理
class Manager extends Employee {
	private int bonus;
	Manager(String name,String id,double pay,int bouns) {
		super(name,id,pay);
		this.bonus = bonus;
	}
	public void work() {
		System.out.println("manage");
	}
}
class AbstractTest {
	public static void main(String[] args) {
		Programmer p = new Programmer("Peter","01001",6000.0);
		p.work();
		Manager m = new Manager("Tom","09001",12000.0,3000);
		m.work();
	}
}

9-9,接口interface-定义

1,当一个抽象类中的方法都是抽象的时候,这时可以将该抽象类用另一种形式定义和表示,就是接口interface。

定义接口使用的关键字不是class而是interface,但接口仍是一个类,编译完成后生成与接口同名的.class文件,如:interface Demo,则声称Demo.class。

2,接口当中常见的成员,这些成员都有固定的修饰符。

(1)全局常量:public static final

(2)抽象方法:public abstract

如:

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

接口中的成员必须这么定义。

可以得出一个结论,接口中的成员都是公共的权限。

3,由于接口中的成员的修饰符石固定的,所以若不写publc static final或是其中的某个修饰符,编译时会自动加上,但返回值类型必须写。

上述中的那段代码可以写成:

interface Demo {
	int NUM = 4;
	void show();
}

但一般不这么写,因为阅读性太差。若int NUM = 4;前面不加修饰符,很可能把它误以为是一个变量,而show()前不加修饰符,由于抽象方法是需要被覆盖的,而覆盖必须要了解抽象的权限,否则可能覆盖失败。

 

9-10,接口-实现implements

1,形式:

interface Demo {
	// code...
}
class DemoImpl implements Demo {
	// code...
}

2,类与类之间是继承关系,类与接口之间是实现关系。

接口不可以实例化,只能有实现了接口的子类并覆盖了接口中所有的抽象方法后,该子类才可以实例化,否则这个子类就是一个抽象类。

3,示例:

interface Demo {
	public static final int NUM = 4;
	public abstract void show();
}
class DemoImpl implements Demo {
	public static void main(String[] args) {
		DemoImpl d = new DemoImpl();
		System.out.println(d.NUM);//正确
		System.out.println(DemoImpl.NUM);//正确
		System.out.println(Demo.NUM);//正确
	}
}

但不能写d.NUM = 3;因为NUM为final的,其值不能改变。

 

9-11,接口-多实现

1,在Java中不直接支持多继承,因为会出现调用的不确定性。所以Java将多继承机制进行改良,在Java中变成了多实现。

一个类可以实现多个接口。

2,接口为什么不存在调用的不确定性?

之前说过,类之所以不支持多继承,是因为会出现方法调用的不确定性。接口为什么不存在这种情况呢?

因为接口中的方法都是抽象的,没有具体的实现方法,需要被覆盖才能实现,如果实现的两个接口中有相同的方法,则覆盖时把两个抽象方法都覆盖,所以实现的功能只有一个,不存在调用的不确定性。

注意:两个同名方法也需要相同的返回值类型。

 

9-12,接口-细节

1,一个类在继承另一个类的同时,还可以实现多个接口,接口的出现避免了单继承的局限性。

class Q {
	public void method() { ... }
}
class Test extends Q implements A,Z {
	...
}

Test类在继承Q的同时,可以实现A,Z等多个接口。

2,接口与接口之间是继承关系,而且接口可以多继承。

interface CC {
	void show();
}
interface MM {
	void method();
}
interface QQ extends CC,MM {
	void function();
}
class WW implements QQ {
	//需要覆盖三个方法
	public void show() { ... }
	public void method() { ... }
	public void function() { ... }
}

3,Java中有没有多继承?

有,只不过换了一种方式实现,即接口。多实现的方式,但接口之间可以进行多继承,原理就在于方法体是否存在。

 

9-13,接口-特点

特点:

(1)接口是对外暴露的原则。如:电脑的USB接口,对外暴露,给外界提供一个与自己连接的地方,而用不到接口的地方就封装在电脑内。

(2)接口是程序的功能扩展。USB接口不仅可以接鼠标,还可以接键盘、U盘等,提高了其功能。

(3)接口的出现降低了耦合性。即降低联系程度,我们可以在用鼠标的时候,把它接到电脑上,不用的时候拔掉,而不是将鼠标焊死在电脑上。

(4)接口可以用来多实现。

(5)类与接口之间是实现关系,而且类可以在继承一个类的同时实现多个接口。

(6)接口与接口之间可以有继承关系。

 

9-14,接口和抽象类的区别

1,异同点:

相同:

    都是不断向上抽取而来的。

不同:

(1)抽象类需要被继承,而且只能单继承。

    接口需要被实现,而且可以多实现。

(2)抽象类中可以定义抽象方法和非抽象方法,子类继承后,可以直接使用非抽象方法。

    接口中只能定义抽象方法,必须有子类实现。

(3)抽象类中的继承是is a关系,在定义该体系的基本共性内容。

    接口的实现是like a的关系,在定义体系的额外功能。

 

2,什么时候使用接口,什么时候使用抽象类呢?

Eg:犬按功能分有导盲犬、搜爆犬等。

/*
导盲犬和搜爆犬都是犬,具有吼叫,睡觉等共性功能,而二者可能吼叫的方式不同,故不提供实现方式,
但睡觉的方式是相同的,所以将犬类定义为abstract,定义一些共性的东西。
*/
abstract class 犬 {
	abstract void 吼叫();
	void 睡觉() {
		System.out.println("我睡...");
	}
}
/*
可能不只狗有导盲功能,还有导盲猪,这时就把导盲功能抽取出来,谁具有导盲功能,
谁来实现就可以了,而导盲的方式不同,所以不定义具体实现方法。
*/
interface 导盲 {
	abstract void 导盲();
}
interface 搜爆 {
	abstract void 搜爆();//谁具有搜爆功能,实现这个接口即可
}
/*
导盲是犬类的扩展性功能,需要实现导盲功能,实现导盲接口。
*/
class 导盲犬 extends 犬 implements 导盲 {
	//让导盲犬继承共性的犬,实现特有的导盲功能
	public void 吼叫() {
		System.out.println("汪汪");
	}
	public void 导盲() {
		System.out.println("狗的指引");
	}
}

因为犬类中有抽象的方法,还有一半方法,定义为抽象类;导盲中只有一个抽象方法,定义为接口。

在不同的问题领域中,有不同的分析方式。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值