您的位置:首页 > 职场人生

黑马程序员-面向对象(继承与多态)

2013-10-30 21:42 316 查看
----------------------
ASP.Net+Android+IOS开发、.Net培训、期待与您交流!----------------------

java的封装原因和好处

classPerson{
privateintage;

publicvoidsetAge(inta){
if(a>0&&a<130){
age=a;
speak();
}else
System.out.println("feifaage");
}

publicintgetAge(){
returnage;
}

privatevoidspeak(){
System.out.println("age="+age);
}
}

classPersonDemo{
publicstaticvoidmain(String[]args){
Personp=newPerson();

//p.age=-20;
p.setAge(-40);
//p.speak();
}
}



在PersonDemo类当中,直接操作new了一个Person,那么可以直接通过对象名.属性去给这个对象赋值,但是为什么这样不好,需要把类内部的属性封装起来,不然外部直接操作呢。这点一直不太理解,总有多此一举的感觉。因为封装了,通过set或get也一样外部能够操作和更改值,而且这样还比较麻烦。之前看视频的时候,说原因是可以添加检查,排除一些非法的输入,不怎么理解,现在理解了,如果设置一个set方法的话,那么方法体里面可以增加一些if语句去检查输入是否合乎常理,避免出现非法数据,这样程序就没意义,而暴露在外部的话,if语句不能添加这样的功能,区别就在这。

主函数的定义:
public:代表着该函数访问权限是最大的。
static:代表主函数在类的加载前就已经存在了。
void:主函数没有具体的返回值。
main:不是关键字,但是是一个特殊的单词,可以被jvm识别。
(String[]arr):函数的参数,参数类型是一个数组,该数组中的元素是字符串。字符串类型的数组。
jvm在调用主函数时,传入的是newString[0];
主函数是固定格式的:jvm识别。(除开args这个变量可以不一样之外,其余都必须一样)

classMainDemo{
publicstaticvoidmain(String[]args){
main(1);
}

publicstaticvoidmain(intargs)

{
System.out.println(args);
}
}


这个算不算重载呢,或者会不会发生重载?

那么到底虚拟机运行的时候会执行哪一个
主函数的格式是固定的,所以在一个类当中,虚拟机首先会查找存不存在main(String[]args),如果不存在,则不会启动这个类,上面的代码提示,,所以不算重载,程序也不能运行

当句子执行时候,虚拟机的内部的运行执行机制是如何的?

例:Personp1=newPerson("zhangsan",17);
运行时,
(1)从左到右执行,先在栈内存中为变量p1开辟一个空间
(2)因为new用到了person.class,所以虚拟机会先找到person.class这个文件加载进来
(3)首先进行的是静态初始化,为类进行初始化
(4)然后为对象在堆内存分配相应的空间,分配内存地址
(5)在堆内存中建立对象的特有属性,然后进行默认初始化
(6)对象内部的成员属性开始显式初始化
(7)对象开始执行构造代码块,为对象初始化
(8)构造函数开始执行,为特定对象初始化
(9)把该对象的堆内存地址传给栈内存中的p1

继承extends

被final修饰的类不能被继承

不是父类中所有方法都被继承,构造方法不能继承

父类的私有属性不能被继承(实际上是继承了,只是不能被子类直接使用)

如果子类中存在与父类同名的属性或者方法时,在子类创建对象的时候,默认调用的是子类中的属性和方法,但是如果想要调用父类的属性或者方法,可以通过super()关键字

Instancedof关键字:

判断一个对象到底属于哪个类的实例

我的理解:我是不是你产生的

格式:对象instanceof类返回值是boolean型

假设存在classA,存在classBextendsA

即A是B的父类

Aa1=newB();

System.out.println(a1instanceofA);

System.out.println(a1instanceofB);

Aa2=newA();

System.out.println(a2instanceofA);

System.out.println(a2instanceofB);

输出为:

True

True

False

False

(1)多态的体现
父类的引用指向了自己的子类对象
换句话说,父类的引用也可以接收自己的子类对象
语法实现形式:Animala=newcat();
(2)
多态的前提,什么时候使用多态
必须是类与类之间有关系,要么继承,要么实现,通常可以把多个类的共同属性封装好,然后再去进行继承,继而实现多态
还有一个大前提,就是覆盖

classCat
{
publicvoideat()
{
System.out.println("吃鱼");
}
publicvoidcatchMouse()
{
System.out.println("抓老鼠");
}
}

classDog
{
publicvoideat()
{
System.out.println("吃骨头");
}
publicvoidkanJia()
{
System.out.println("看家");
}
}
//观察以上两个类,作为动物,都具有吃的本能,所以这个功能可以抽取出来,封装成抽象类,然后再进行继承
abstractclassclassAnimal
{
abstractvoideat();
}


(3)多态的好处
多态的出现大大提高了程序的扩展性
(4)多态的应用

(5)多态的弊端:多态有好处也有局限性
提高了扩展性的同时,弊端在于只能使用父类的引用访问父类中的成员
abstractclassAnimal

{

abstractvoideat();//抽象方法不用实现,也没有大括号

}

抽象类作为父类,里面只有一个成员函数,局限性就在此体现,不能为别的动物提供特有的个性化的成员函数
可以说优点因为这个,缺点也是因为这个

类里面的方法如果不确定的话,就定义为抽象类,由使用者自己去覆写实现

多态的编译和运行的结果情况分析

classFu
{
staticintnum=5;
staticvoidmethod1()
{
System.out.println("fumethod_1");
}
staticvoidmethod2()
{
System.out.println("fumethod_2");
}
staticvoidmethod4()
{
System.out.println("fumethod_4");
}
}
classZiextendsFu{
staticintnum=8;
staticvoidmethod1()
{
System.out.println("zimethod_1");
}
staticvoidmethod3()
{
System.out.println("zimethod_3");
}
staticvoidmethod4()
{
System.out.println("zimethod_4");
}
}

classDuoTaiDemo4
{
publicstaticvoidmain(String[]args)
{
Fuf=newZi();//用父类的引用接收子类对象
f.method1();
f.method2();
f.method3();
}
}


此时编译应该是不通过的,提示错误f.method3();找不到

那是为什么会出现这个情况呢(重点理解记忆)

应该要从编译的机制解释,java虚拟机编译的时候,语法检查首先会在f他的Fu类中查找method1();method2();method3();这三个方法,如果都存在,并且语法没有错误,则编译通过,但是在实际运行时,再检查类中的方法有没有被static修饰,如果被static修饰,(假设编译通过的情况下)调用的是父类中的method1();method3();,如果没有被static修饰,调用的是子类Zi中的method1();method3();而不是父类中的
所以这一点应该是有所区别的,我们在思考的时候应该注意区分编译和运行时代码的情况

重点注意,关于向上转型和向下转型:

在对对象发生向下转型的时候,必须是已经发生了向上转型的前提下,才可以向下转型,否则编译会报错。打个比方,一个对象好比一个人,转型就好比上楼梯,刚开始都是在平地上,我必须上了一级楼梯,我才能往下走一级楼梯,在平地上是不能再往下的了

错误的写法:

假设A是B的父类

Aa=newA();

Bb=(B)a;

此时编译报错

正确的写法:

Aa=newB();//必须先存在向上转型,才可以向下转型

Bb=(B)a;

在多态中成员函数的特点:(需要区分静态函数和非静态函数)

在编译时期:参阅引用型变量所属的类中是否有调用的方法如果有,编译通过,如果没有编译失败。
在运行时期:参阅对象所属的类中是否有调用的方法
简单总结就是:静态成员函数在多态调用时,编译看左边,运行时看左边。
非静态成员函数在多态调用时,编译看左边,运行时看右边。

以上总结只正对类中的方法来说,如果是针对属性变量,下面再讲解

classFu
{
staticintnum=5;
}
classZiextendsFu
{
staticintnum=8;
}
classDuoTaiDemo4
{
publicstaticvoidmain(String[]args)
{
Fuf=newZi();//父类引用接收子类对象
System.out.println(f.num);//输出结果为父类的nun值
Ziz=newZi();
System.out.println(z.num);
}
}


这个的输出是:
5
8

其实这里是多态需要注意一下的地方

准确来说,继承后子类实现要分情况讨论,一般是指没有发生转型的情况下,子类引用接收子类对象,如今发生了向上转型,父类引用接收子类对象,那么在编译和运行的时候,因为左右不统一了,所以要参考左边的类所属变量,这个是比较特殊的情况

结论:

在多态中,成员变量的特点:
无论编译和运行,都参考左边(其实静态和非静态都适用)。

在多态中,静态成员函数的特点:(区分静态和非静态)
静态:编译时期,检查参考左边引用变量的类,在运行阶段,还是访问左边
非静态:编译时期,检查参考左边引用变量的类,但在运行阶段,访问的实际上是右边对象中的方法

java中的继承中的问题,不支持多继承,但是支持多层继承,两者有什么区别呢,下面代码表示的是多继承,但是会出问题

classA
{
voidshow()
{
System.out.println("a");
}
}
classB
{
voidshow()
{
System.out.println("b");
}
}
classCextendsA,B
{}

Cc=newC();
c.show();
//如果java允许支持这个多继承的话,上面的代码会让虚拟机出错,因为并不知道C中的show()是继承哪一个类,所以会报错
//下面说到多层继承,把以上代码修改一下
classA
{
voidshow()
{
System.out.println("a");
}
}
classBextendsA
{
voidmianPrint()
{
System.out.println("b");
}
}
classCextendsB
{}

Cc=newC();
c.show();


这个比较好理解,B继承了A,那么B里面具有A的功能,接着C继承了B,那么C里面也就有了B的内容,那么这个继承里面就相当于有了三层,可以理解为C直接继承了B间接继承了A
这个举个例子会比较好理解,一个人只能有一个生物学父亲,不可能同时有几个生物学上的父亲,但是,你父亲也有自己的父亲,也就是你爷爷,你父亲继承你爷爷,然后你继承你父亲,这就是多层继承

事物间的关系

除开继承之外,还有聚合和组合
聚合:学生是班级的一部分
组合:手是身体的一部分
两者在逻辑上并没有严格的区分,但是两者在联系程度上组合比聚合要紧密地多,有不可分割的意思

重写(覆盖)

当子类继承父类,沿袭了父类的功能,到子类中,
但是子类虽具备该功能,但是功能的内容却和父类不一致,
这时,没有必要定义新功能,而是使用覆盖特殊,保留父类的功能定义,并重写功能内容。

覆盖:
(1)子类覆盖父类,必须保证子类权限大于等于父类权限,才可以覆盖,否则编译失败。
(2)静态只能覆盖静态,

重载:只看同名函数的参数列表。
重写:子父类方法要一模一样。(除开方法体内容以外)

子父类中的构造函数。
在对子类对象进行初始化时,会先行自动调用父类的构造函数,然后再调用子类的构造函数
那是因为子类的构造函数默认第一行有一条隐式的语句super();
super():会访问父类中空参数的构造函数。
而且子类中所有的构造函数默认第一行都是super();
所以有个建议,如果要自定义一个父类,这个父类的构造函数是带参数的,然后用子类继承,那么最好给父类手动加上一个空参数的构造函数,否则,子类在初始化的时候可能会报错,因为子类在实例化时会自动执行父类中的空参构造函数

示例:

classCar

{

StringMyColor;

Car(){}//此处可能需要定义一个空参的构造方法,否则下面子类创建对象时可能会编译不通过

Car(StringMyColor)//有参数的构造方法

{

this.MyColor=MyColor;

}

publicvoidrun()

{

System.out.println("模型开动");

}

}

classBaoMaCarextendsCar

{

BaoMaCar(StringMyColor)

{

this.MyColor=MyColor;//如果用这一句,则对象初始化的时候会调用父类的空参构造方法,因为父类中已经定义了有参的构造方法,所以在编译的时候,编译器不会自动添加空参构造方法,所以必须要给父类定义一个空参的构造方法,否则编译报错

//super(MyColor);//可以如果用这一句,父类中可以没有空参的构造方法

}

publicvoidrun()

{

System.out.println("宝马开动");

}

}

classMyCarDemo

{

publicstaticvoidmain(String[]args)

{

Carc=retCar(1);

c.run();

}

publicstaticCarretCar(intnum)

{

if(num==1)

returnnewBaoMaCar("白色宝马");

if(num==2)

returnnewBaoShiJieCar("红色保时捷");

elsereturnnewCar("白色模型车");

}

}


为什么子类一定要访问父类中的构造函数。
因为父类中的数据子类可以直接获取。所以子类对象在建立时,需要先查看父类是如何对这些数据进行初始化的。
所以子类在对象初始化时,要先访问一下父类中的构造函数。
如果要访问父类中指定的构造函数,可以通过手动定义super语句的方式来指定。

注意:super语句一定定义在子类构造函数的第一行。

结论:
子类的所有的构造函数,默认都会访问父类中空参数的构造函数。
因为子类每一个构造函数内的第一行都有一句隐式super();

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

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

----------------------
ASP.Net+Android+IOS开发、.Net培训、期待与您交流!----------------------详细请查看:http://edu.csdn.net
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐