您的位置:首页 > 编程语言 > Java开发

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

2013-12-17 13:42 288 查看

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("狗的指引");
}
}

因为犬类中有抽象的方法,还有一半方法,定义为抽象类;导盲中只有一个抽象方法,定义为接口。
在不同的问题领域中,有不同的分析方式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: