封装继承多态抽象接口汇编.docx
《封装继承多态抽象接口汇编.docx》由会员分享,可在线阅读,更多相关《封装继承多态抽象接口汇编.docx(46页珍藏版)》请在冰豆网上搜索。
封装继承多态抽象接口汇编
1.1封装
封装是面向对象编程的三大特征之一。
封装就是将通过抽象得到的属性和方法相结合,形成一个有机的整体——“类”。
封装的目的是增强数据安全性和简化编程,使用者不必了解具体的实现细节,所有对数据的访问和操作都必须通过特定的方法,否则便无法使用,从而达到数据隐藏的目的。
封装是面向对象编程语言对客观世界的模拟,客观世界的属性都是被隐藏在对象内部的,外界不能直接进行操作或者修改。
譬如:
常见的空调电视机等对象,这些对象都是封装好的,普通人只可以通过对小小的按钮操作来控制这些家电;不可以随意打开设备进行修改对象内容的配置。
但是专业人员可以修改这些家电,而我们就是要做这些“专家”;如下图所示。
图1.1.1封装对象
1.1.1为什么需要封装
通过第一阶段的学习,我们知道类由属性和方法组成,在类的外部通过本类的实例化对象可以自由访问和设置类中的属性信息,这样不利于属性信息的安全,示例1.1就是如此。
示例1.1
publicclassPerson{
publicStringname;
publicintage;
publicvoidsayHello(){
System.out.print("你好!
");
}
}
publicclassTest{
publicstaticvoidmain(String[]args){
Personp=newPerson();
p.name="皇帝";
p.age=1000;//属性信息可以直接设置
p.sayHello();
}
}
上述代码在第一阶段Java的课程中经常见到,大致一看没什么问题,但是仔细分析过之后会发现:
把年龄设置成1000合理吗?
由于Person类的属性都是公有的(public),那也就意味着在Person类的外部,通过Person类的实例化对象可以对这些公有属性任意修改,这就使得我们无法对类的属性进行有效的保护和控制。
这属于设计上的缺陷,那能不能避免这种情况呢?
这就需要用到下面的封装了。
1.1.2现实生活中的封装
现实生活中封装的例子随处可见,例如药店里出售的胶囊类药品,我们只需要知道这个胶囊有什么疗效,怎么服用就行了,根本不用关心也不可能去操作胶囊的药物成分和生产工艺。
再例如家家户户都用的电视机,我们只需要知道电视机能收看电视节目,知道怎么使用就行了,不用关心也不可能去搞清楚电视机内部都有哪些硬件以及是如何组装的。
这些都是现实生活中封装的例子。
在刚才的两个例子中,我们可以认为药物成分是胶囊的属性,但是用户不需要也不可能去操作它。
我们也可以认为内部硬件是电视机的属性,但是用户也不需要去操作它。
这就是现实生活中封装的特征,程序中的封装与此类似。
1.1.3程序中的封装
封装就是:
将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部的信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。
简而言之,封装就是将类的属性私有化,并提供公有方法访问私有属性的机制,我们看示例1.2。
示例1.2
publicclassPerson{
//将属性使用private修饰,从而隐藏起来
privateStringname;
privateintage;
publicvoidsayHello()
{
System.out.print("你好!
");
}
}
publicclassTest{
publicstaticvoidmain(String[]args){
Personp=newPerson();
p.name="杰克";//编译报错
p.age=1000;//编译报错
p.sayHello();
}
}
大致一看这段代码跟前面的代码没什么两样,其实只是做了一点改动,Person类中属性的访问修饰符由public改成了private,即属性私有化了。
这样一改就造成了main方法中的两行代码出现了编译错误,因为私有属性只能在其所在类的内部使用,在类的外部就无法访问了。
1.1.4如何实现封装
(1)使用private修饰符将类的属性私有化,如示例1.3所示。
使用private修饰的属性只能在类的内部调用,访问权限最小。
示例1.3
publicclassPerson{
privateStringname;
privateintage;
}
(2)属性被私有化之后在类的外部就不能访问了,我们需要给每个属性提供共有的
Getter/Setter方法,如示例1.4所示。
示例1.4
publicclassPerson
{
privateStringname;
privateintage;
publicStringgetName(){//获得name属性的getter方法
returnname;
}
publicvoidsetName(Stringname){//设置name属性的setter方法
this.name=name;
}
publicintgetAge(){//获得age属性的getter方法
returnage;
}
publicvoidsetAge(intage){//设置age属性的setter方法
this.age=age;
}
}
(3)在Getter/Setter方法中加入存取控制语句,如示例1.5所示。
示例1.5
publicvoidsetAge(intage){
if(age>0&&age<100)//存取限制
this.age=age;
else
System.out.println("年龄不规范!
");
}
上述代码中多次出现了this关键字,this表示当前类对象本身。
this.属性表示调用当前对象的属性,this.方法表示调用当前对象的方法。
在上面的Setter方法中,因为方法的参数和类的属性同名,所以需要使用this进行区别。
从以上示例可以看出,封装实现以下目的:
(1)隐藏类的实现细节。
(2)用户只能通过事先提供的共有的方法来访问属性,从而在该方法中加入控制逻辑,以限制对属性不合理的访问。
(3)可以进行对数据检查,有利于对象信息的完整性。
(4)便于后期的代码修改,有利于提高代码的安全性和可维护性。
1.1.5使用Eclipse生成Getter/Setter方法
Java类里的属性的Getter/Setter方法有非常重要的意义,例如某个类里包含了一个名为name的属性,则其对应的Getter/Setter方法名应为setName和getName(即将原属性名的首字母大写,并在前面分别加上get和set动词,这就变成Getter/Setter方法名)。
如果一个Java类的每个属性都被使用private修饰,并为每个属性提供了public修饰Getter/Setter方法,这个类就是一个符合Sun公司制定的JavaBean规范类。
因此,JavaBean总是一个封装良好的类。
编写类是程序人员日常工作中经常要做的事情,但是如果每一次都要手工编写Getter/Setter方法无疑会影响开发效率。
Eclipse充分考虑到了这一点并提供了快速生成Getter/Setter方法的功能。
在类中编写好私有属性后,在空白地方单击右键会弹出如图1.1.2所示的菜单。
图1.1.2生成Getter/Setter方法
依次单击【Source】|【GenerateGettersandSetters】菜单就弹出了如图1.1.3所示的对话框。
图1.1.3生成Getter/Setter方法
单击【SelectAll】按钮将所有的属性选中,然后单击【OK】按钮就可以看到Getter/Setter方法已经自动生成了。
1.2构造方法
1.2.1为什么需要构造方法
为什么需要构造方法,先看示例1.6
publicclassTest{
publicstaticvoidmain(String[]args){
Personp=newPerson();
System.out.print("姓名是:
"+p.getName()+"\t年龄是:
"+p.getAge());
}
}
上述代码是对经过封装的Person类进行测试,代码非常简单。
下面我们看一下这段代码的运行结果,如图1.1.4所示。
图1.1.4运行结果
上述代码的运行结果充分说明:
当我们使用new关键字创建对象时,属性都有默认值,例如String类型属性的默认值是null,int类型属性的默认值是0。
常见数据类型的默认值如下表所示。
表1-1-1常见数据类型的默认值
类型
缺省值
类型
缺省值
byte
(byte)0
char
'\u0000'
short
(short)0
float
0.0F
int
0
double
0.0D
long
0L
对象引用
null
boolean
false
但是,这些默认值毫无意义。
那有解决的办法吗?
能不能在创建对象时给属性初始化一些有意义的值呢?
我们看下面的代码。
示例1.7
publicclassPerson
{
privateStringname;
privateintage;
publicPerson(){
this.name="杰克";
this.age=28;
}
//省略Getter/Setter方法
}
我们在示例1.7的Person类中加入了一个特殊的方法Person(),然后再运行上面那个测试类,运行结果就变成了如图1.1.5所示的情况。
图1.1.5运行效果
上述代码的运行效果说明,那个特殊的Person()方法实现了对属性的初始化操作,而且这个方法无需我们显式调用。
Person()就是构造方法。
1.2.2什么是构造方法
构造方法所起的主要作用就是在使用new关键字创建对象时完成对属性的初始化操作。
构造方法有三个特征:
Ø没有返回类型。
Ø方法名和类名相同。
Ø无需显式调用。
另外需要注意的是:
如果一个类没有显式声明构造方法,那么这个类会有一个默认的构造方法,这个构造方法没有参数,方法体也为空。
但是如果显式地声明了构造方法,那么这个默认的无参构造方法就不存在了。
1.2.3有参的构造方法
通过无参构造方法可以很方便的实现对属性的初始化操作,但也存在一定的弊端:
就是通过无参构造方法所创建的对象的属性值都是一样的。
那能不能在创建对象时可以灵活得给属性赋不同的值呢?
这就需要有参的构造方法,我们看一下示例1.8。
示例1.8
publicclassPerson
{
privateStringname;
privateintage;
publicPerson(Stringname,intage){//有参的构造方法
this.name=name;
this.age=age;
}
}
publicclassTest{
publicstaticvoidmain(String[]args){
Personp1=newPerson("杰克",28);//给参数赋值
Personp2=newPerson("玛丽",24);//给参数赋值
}
}
构造方法和普通方法一样是可以带有参数的,通过带参的构造方法就可以灵活的创建对象,每个对象的属性值都不一样。
因为构造方法主要用于被其他方法来调用创建对象,并返回该类的实例,所以通常把构造方法设置成public访问权限,从而允许系统中任何位置的类都可以创建该类的对象。
除非在一些极端的情况下,我们需要限制创建该类的对象,可以把构造方法设置成其他访问权限:
例如设置为protected,主要用于被其子类调用(下一小节讲解),把其设置为private阻止其他类创建该类的实例。
1.3方法重载
在第一阶段的学习中我们已经在使用方法重载了,如示例1.9所示。
示例1.9
publicclassTest{
publicstaticvoidmain(String[]args){
System.out.println(100);//int类型
System.out.println(true);//boolean类型
System.out.println("我爱Java");//String类型
}
}
同样一个println方法却可以接收不同类型的参数,这是因为在一个类中存在多个println方法,但是这些println方法的参数是不同的,这其实就是方法重载。
当我们写的方法满足以下条件时就构成了方法重载:
Ø在同一个类中。
Ø方法名必须相同。
Ø方法参数不同,包括参数个数不同或参数类型不同或参数顺序不同。
Ø与方法的返回类型和访问修饰符没有任何关系。
有了方法重载,程序开发人员就不用显式的根据参数去判断该调用哪个方法了,而是由Java系统在运行时自动进行判断和调用。
下面以主人训练宠物的案例来说明方法重载的好处;请看示例1.10。
示例1.10
publicclassDog{//Dog类
privateStringcolor;//描述狗的颜色
privateStringname;//描述狗的昵称
privatefloatprice;//描述狗的价格
publicDog(Stringcolor,Stringname){
this.color=color;
this.name=name;
}
...省略Getter/Setter方法
publicclassMonkey{//Monkey类
privateStringcolor;//描述猴子的颜色
privateStringname;//描述猴子的昵称
privatefloatprice;//描述猴子的价格
publicMonkey(Stringcolor,Stringname){
this.color=color;
this.name=name;
}
...省略Getter/Setter方法
publicclassMaster{//主人类
privateStringname;
publicvoidtrain(Dogdog)//训练Dog
{
System.out.println(dog.getColor()+"的"+dog.getName()
+"向主人摇尾巴。
");
System.out.println(dog.getColor()+"的"+dog.getName()
+"接飞盘。
");
}
publicvoidtrain(Monkeymonkey)//训练Monkey
{
System.out.println(monkey.getColor()+"的"+monkey.getName()
+"向游人伸手要食物。
");
System.out.println(monkey.getColor()+"的"+monkey.getName()
+"在爬小树。
");
}
...省略Getter/Setter方法
publicclassTest{//测试类
publicstaticvoidmain(String[]args){
//创建Dog对象
Dogdog=newDog("黑色","小黑");
//创建Monkey对象
Monkeymonkey=newMonkey("黄色","阿黄");
//创建主人对象
Mastermaster=newMaster();
//调用训练Dog的方法
master.train(dog);
//调用训练Monkey的方法
master.train(monkey);
}
}
执行效果如图1.1.6所示。
图1.1.6运行效果
从上例执行的效果可以看出,在调用重载的方法时;只需把参数dog或者monkey传给train方法,Java系统在运行时会根据方法的类型执行对应的方法。
构造方法和普通方法一样也可以构成方法重载,示例1.11就是如此。
示例1.11
publicclassPerson
{
privateStringname;
privateintage;
publicPerson(){//无参构造方法
}
publicPerson(Stringname,intage){//有参构造方法
this.name=name;
this.age=age;
}
}
上述代码中有一个无参数的构造方法和有参的构造方法,完全符合方法重载的特征,所以这两个构造方法形成了方法重载。
构造方法重载的好处是完成多种初始化行为,因为有些时候在创建对象的时候,如果仅仅知道姓名属性的值可以调用Person(Stringname)构造方法;如果两个属性的值都知道则可以调用Person(Stringname,intage)构造方法。
构造方法重载提供了创建对象的灵活性;另外要注意的是通常建议保留类的无参数构造方法。
1.4继承
1.4.1为什么需要继承
我们现在封装了两个类:
Student和Teacher,示例1.12给出了这两个类的参考代码。
示例1.12
publicclassTeacher{//教员类
privateStringname;
privateintage;
publicTeacher(){
this.name="张老师";
this.age=28;
}
//此处省略getter/setter方法
publicvoidsayHello(){
System.out.print("你好!
");
}
}
publicclassStudent{//学生类
privateStringname;
privateintage;
publicStudent(){
this.name="张无忌";
this.age=24;
}
//此处省略getter/setter方法
publicvoidsayHello(){
System.out.print("你好!
");
}
publicvoidstudy(){
System.out.print("学习!
");
}
}
上述代码存在的主要问题是:
代码重复,还有上面的Dog类和Monkey类。
不管是教员类还是学生类,或者是工人类都需要姓名、年龄等属性,这就会出现很多重复代码。
如何有效的解决这个问题呢?
我们可以把Teacher类和Student类中相同的属性和方法提取出来放到另外一个单独的Person类中,然后让Teacher类和Student类继承Person类,同时保留自己独有的属性和方法,这就是Java中的继承,如示例1.13所示。
示例1.13
publicclassPerson{//人类
privateStringname;
privateintage;
//此处省略getter/setter方法
publicvoidsayHello(){
System.out.print("你好!
");
}
}
publicclassTeacherextendsPerson{//教员类
}
publicclassStudentextendsPerson{//学生类
publicvoidstudy(){//学生类独有的方法
System.out.print("学习!
");
}
}
在上述代码中,Teacher类没有定义任何属性和方法,Student类只定义了一个study方法,这样一来重复的代码的确没有了,但是会不会影响使用呢?
我们看示例1.14的测试代码。
示例1.14
publicclassTest{
publicstaticvoidmain(String[]args){
Teachert=newTeacher();//创建一个教员
t.setName("张三丰");
t.setAge(28);
t.sayHello();
Students=newStudent();//创建一个学生
t.setName("张无忌");
t.setAge(24);
t.sayHello();
}
}
上述代码经过测试是可以正常运行的,这充分说明继承不但解决了代码重复的问题,也保证了我们自定义的类是可以正常使用的。
1.4.2什么是继承
图1.1.7 现实中的继承
图1.1.7给出了一个现实生活中继承的例子,通过这个例子我们可以总结出继承具有如下特征。
Ø为什么说兔子和棉羊是食草动物,而不把兔子和狮子放在一起?
因为兔子和棉羊具
有相同的特征和行为。
这些相同的特征和行为可以抽象出一个父类――食草动物。
Ø继承具有树形结构,越顶层的越模糊、越通用,越底层的越具体、越个性。
Ø子类只能直接继承一个父类,一个父类可以有多个子类。
就像一个父亲可以有多个
儿子,但是一个儿子只能有一个父亲一样。
Java只支持单重继承。
Ø树形结构的顺序:
兔子是食草动物是正确的,但食草动物是兔子是不正确的。
这就是is-a关系。
这就是继承的特征,Java中的继承与之相同。
那么Java中的子类究竟可以从父类中继承哪些“财产”呢?
Ø子类可以继承父类中使用public和protected修饰的属性和方法,不论子类和父类是否在同一包中。
Ø子类可以继承父类中使用默认修饰符修饰的属性和方法,但子类和父类必须在同一个包中。
Ø子类无法继承父类中使用private修饰的属性和方法。
Ø子类无法继承父类的构造方法。
1.4.3如何实现继承
在Java中实现继承很简单,使用extends关键字即可,语法如下所示。
publicclassSubClassextendsSuperClass{//继承
}
SubClass称为子类,SuperClass称为父类、基类或者超类。
1.4.4类的祖先Object
在Java中,Object类是所有类的祖先,它处于继承关系的最顶层。
在定义一个类时,即使没有使用关键字extends继承Object类,系统也会默认去继承Object类。
其实我们在以前使用类时没有注意这一点,如图1.1.8所示。
图1.1.8Object类
在Teacher类中我们只定义了getter/setter方法和一个sayHello方法,但是在使用时却发现多出来很多其他的方法,例如:
toString()、wait