封装是把过程和数据包围起来,对数据的访问只能通过已定义的接口。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。封装是一种信息隐藏技术,在java中通过关键字private实现封装。什么是封装?封装把对像的所有组成部分组合在一起,封装定义程序如何引用对象的数据,封装实际上使用方法将类的数据隐藏起来,控制用户对类的修改和访问数据的程度。
什么是多态
下面是多态存在的三个必要条件,要求大家做梦时都能背出来!
多态存在的三个必要条件一、要有继承;二、要有重写;三、父类引用指向子类对象。
多态的好处:
1.可替换性(substitutability)。多态对已存在代码具有可替换性。例如,多态对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作。2.可扩充性(extensibility)。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。3.接口性(interface-ability)。多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。如图8.3所示。图中超类Shape规定了两个实现多态的接口方法,computeArea()以及computeVolume()。子类,如Circle和Sphere为了实现多态,完善或者覆盖这两个接口方法。4.灵活性(flexibility)。它在应用中体现了灵活多样的操作,提高了使用效率。5.简化性(simplicity)。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。
Java中多态的实现方式:接口实现,继承父类进行方法重写,同一个类中进行方法重载。
Java中的继承、封装、多态
继承的理解:
1、继承是面向对象的三大特征之一,也是实现代码复用的重要手段。Java的继承具有单继承的特点,每个子类只有一个直接父类。
2、Java的继承通过extends关键字来实现,实现继承的类被称为子类,被继承的类称为父类(有的也称其为基类、超类),父类和子类的关系,是一种一般和特殊的关系。就像是水果和苹果的关系,苹果继承了水果,苹果是水果的子类,水果是苹果的父类,则苹果是一种特殊的水果。
3、Java使用extends作为继承的关键字,extends关键字在英文是扩展的意思,而不是继承。为什么国内把extends翻译成继承呢?除了与历史原因有关外,把extends翻译成为继承也是有其道理的:子类扩展父类,将可以获得父类的全部属性和方法,这与汉语中得继承(子辈从父辈那里获得一笔财富成为继承)具有很好的类似性。值得指出的是:Java的子类不能获得父类的构造器。
4、实例:
classBaseClass{
publicdoubleweight;
publicvoidinfo(){
System.out.println("我的体重是"+weight+"千克");
}
publicclassExtendsDemo001extendsBaseClass{
publicstaticvoidmain(String[]args){
//创建ExtendsDemo001对象
ExtendsDemo001ed=newExtendsDemo001();
//ExtendsDemo001本身没有weight属性,但是ExtendsDemo001的父类有weight属性,也可以访问ExtendsDemo001对象的属性
ed.weight=56;
//调用ExtendsDemo001对象的info()方法
ed.info();
打印结果为:我的体重是56.0千克
5、Java类只能有一个父类。这句话是错误的,应该这样说:Java类只能有一个直接父类,可以有无限多个间接父类,如:
classFruitextendsPlant{…….}
classAppleextendsFruit{…….}
重写父类的方法:
1、大部分的时候,子类总是以父类为基础,额外添加新的属性和方法。但有一种情况例外:子类需要重写父类的方法。例如鸟类都包含了飞翔的方法,其中鸵鸟是一种特殊的鸟类,因此鸵鸟也是鸟的子类,因此它也将从鸟类获得飞翔方法,但这个飞翔方法明显不适合鸵鸟,为此,鸵鸟需要重写鸟类的方法。
2、如下代码可以帮助理解重写:
1)classBird{
2)//Bird类的fly()方法
3)privatevoidfly(){
4)System.out.println("我要在天空中飞翔");
5)}
6)}
7)publicclassOcerrideTestextendsBird{
8)//重写Bird类的fly()方法
9)publicvoidfly(){
10)System.out.println("我只能在地上奔跑");
11)}
12)publicstaticvoidmain(String[]args){
13)//创建OcerrideTest对象
14)OcerrideTestot=newOcerrideTest();
15)ot.fly();
16)}
17)}
打印结果为:我只能在地上奔跑
这种子类包含父类同名方法的现象被称为方法重写,也被称为方法覆盖(Override)。
方法的重写要遵循“两同两小一大”规则:
⑴“两同”:方法名相同;形参列表相同。
⑶子类方法的访问权限应比父类方法更大或相等
尤其需要指出的是:覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法,不能一个是类方法,一个是实例方法,例如下面的代码将会有编译错误:
ClassBaseClass{
publicstaticvoidtest(){…….}
ClassSubClassextendsBaseClass{
publicvoidtest(){………….}
若想调用父类中的fly()方法,则只需在子类中fly()方法中加上如下代码即可:
super.fly();
注意:super和this一样,都不能出现在static的方法中
调用父类构造器
1、看如下程序定义的BaseheSub类,其中Sub类是Base类的子类,程序在Sub类的构造器中使用super来调用Base构造器里的初始化代码。
classBase{
publicdoublesize;
publicStringname;
publicBase(doublesize,Stringname){
this.size=size;
this.name=name;
publicclassSubextendsBase{
publicStringcolor;
publicSub(doublesize,Stringname,Stringcolor){
//在子类构造器中调用父类构造器,使用super调用来实现
super(size,name);
this.color=color;
Subs=newSub(5.6,"测试对象","红色");
System.out.println(s.size+"------"+s.name+"------"+s.color);
打印结果为:5.6------测试对象------红色
静态初始化块
封装的理解:
1、封装(Encapsulation)是面向对象的三大特征之一,它指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。
2、掌握了访问控制符的用法之后,下面通过使用合理的访问控制来定义一个Person类,这个Person类就实现了良好的封装。代码如下:
publicclassPerson{
Personp=newPerson();
p.setAge(10);
System.out.println(p.getAge());
//将属性使用private修饰,将这些属性隐藏起来
privateStringname;
privateintage;
//提供方法来操作name属性
publicvoidsetName(Stringname){
//对姓名执行合理的校验
if(name.length()>6||name.length()<2){
System.out.println("您的姓名不符合要求");
}else{
publicStringgetName(){
returnthis.name;
//提供方法来操作age属性
publicvoidsetAge(intage){
if(age>100||age<0){
System.out.println("您的年龄必须要在0~100之间");
this.age=age;
publicintgetAge(){
returnthis.age;
运行结果为:10
多态的理解:
1、多态(Polymorphism)是面向对象的三大特征之一。
先看下面的程序:
classSuperClass{
publicintbook=6;
publicvoidbase(){
System.out.println("父类的普通方法base()");
publicvoidtest(){
System.out.println("父类中北覆盖的方法");
publicclassPloymorphismTest001extendsSuperClass{
//重新定义一个book实例属性,覆盖父类的book实例属性
publicStringbook="Java疯狂讲义";
System.out.println("子类中覆盖父类的方法");
privatevoidDmeo(){
System.out.println("子类中普通的方法");
//主方法
//下面编译时类型和运行时类型完全一样,因此不存在多态
SuperClasssc=newSuperClass();
System.out.println("book1="+sc.book);//打印结果为:6
//下面两次调用将执行SuperClass的方法
sc.base();
sc.test();
PloymorphismTest001pt=newPloymorphismTest001();
System.out.println("book2="+pt.book);//打印结果为:Java疯狂讲义
//下面调用将执行从父类继承到的base方法
pt.base();
//下面调用将执行当前类的test方法
pt.test();
//下面编译时类型和运行时类型不一样,多态发生
SuperClasssscc=newPloymorphismTest001();
//结果表明访问的是父类属性
System.out.println("book3="+sscc.book);//打印结果为:6
//下面调用将执行从父类继承到得base方法
sscc.base();
sscc.test();
//因为sscc的编译类型是SuperClass,SuperClass类没有提供Demo()方法
//所以下面代码编译时会出现错误
//sscc.Demo();
程序运行结果为:
book1=6
父类的普通方法base()
父类中北覆盖的方法
book2=Java疯狂讲义
子类中覆盖父类的方法
book3=6
上面程序的main方法中显示创建而来3个引用变量,对于前两个引用变量sc和pt,它们编译时类型和运行时类型完全相同,因此调用它们的属性和方法非常正常,完全没有问题。但第三个引用变量sscc,则比较特殊,它编译时类型是SuperClass,而运行时类型是PloymorphismTest001,当调用该引用变量的test方法时,实际上是执行PloymorphismTest001类中覆盖后的test方法,这就是多态。
当把一个子类对象直接赋给父类引用变量,例如上面的SuperClasssscc=newPloymorphismTest001();这个sscc引用变量的编译时类型是SuperClass,而运行时类型是PloymorphismTest001,当运行时调用该引用变量的方法时,其方法行为总是像子类方法的行为,而不是像父类方法行为,这将出现相同类型的变量、执行同一个方法时呈现出不同的行为特征,这就是多态。
特别提醒:
与方法不同的是,对象的属性则不具备多态性,如上面的sscc引用变量,程序中输出它的book属性时,并不是输出PloymorphismTest001类里定义的实例属性,而是输出SuperClass类的实例属性。
注意:
我们通过Objectp=newPerson()代码定义一个变量p,则这个p只能调用Object类的方法,而不能调用Person类里定义的方法。
在没有好好地研习面向对象设计的设计模式之前,我对Java接口和Java抽象类的认识还是很模糊,很不可理解。
刚学Java语言时,就很难理解为什么要有接口这个概念,虽说是可以实现所谓的多继承,可一个只有方法名,没有方法体的东西,我实现它又有什么用呢?我从它那什么也得不到,除了一些方法名,我直接在具体类里加入这些方法不就行了吗?
为什么一定要有抽象类这个概念?为什么就不能把这个父类写成一个具体的类,子类再继承它不就可以了吗?何必弄一个抽象类出来,还要弄一些没有方法体的抽象方法,弄得又象接口又象类的,让人捉摸不定。
当我开始学习java设计模式,真正走进面向对象设计的大门之后,我才发现,自己对面向对象设计的理解原来是那么的片面,那么的肤浅,根本就没有真正理解面向对象思想的精髓,在某一种程度上还受着面向过程的影响,以为弄出了一个个类,就算是面向对象了,而其实还是被过程所驱使着。
我还是说说我现在对面向对象思想的理解吧,不一定正确全面,但我想应该还算是比以前略有进步吧。
面向对象思想,我觉得最关键的就是抽象。
一个软件设计的好坏,我想很大程度上取决于它的整体架构,而这个整体架构其实就是你对整个宏观商业业务的抽象框架,当代表业务逻辑的高层抽象层结构合理时,你底层的具体实现需要考虑的就仅仅是一些算法和一些具体的业务实现了。当你需要再开发另一个相近的项目时,你以前的抽象层说不定还可以再次利用呢,面对对象的设计,复用的重点其实应该是抽象层的复用,而不是具体某一个代码块的复用,是不是一下子感觉自己对复用理解的高度又上升了一层?^_^
说到了抽象,我就不能不提到曾让我头痛的Java接口和Java抽象类了,这也是本文我想说的重点。
既然面向对象设计的重点在于抽象,那Java接口和Java抽象类就有它存在的必然性了。
Java接口和Java抽象类代表的就是抽象类型,就是我们需要提出的抽象层的具体表现。OOP面向对象的编程,如果要提高程序的复用率,增加程序的可维护性,可扩展性,就必须是面向接口的编程,面向抽象的编程,正确地使用接口、抽象类这些太有用的抽象类型做为你结构层次上的顶层。
Java接口和Java抽象类有太多相似的地方,又有太多特别的地方,究竟在什么地方,才是它们的最佳位置呢?把它们比较一下,你就可以发现了。
1、Java接口和Java抽象类最大的一个区别,就在于Java抽象类可以提供某些方法的部分实现,而Java接口不可以,这大概就是Java抽象类唯一的优点吧,但这个优点非常有用。如果向一个抽象类里加入一个新的具体方法时,那么它所有的子类都一下子都得到了这个新方法,而Java接口做不到这一点,如果向一个Java接口里加入一个新方法,所有实现这个接口的类就无法成功通过编译了,因为你必须让每一个类都再实现这个方法才行,这显然是Java接口的缺点。
2、一个抽象类的实现只能由这个抽象类的子类给出,也就是说,这个实现处在抽象类所定义出的继承的等级结构中,而由于Java语言的单继承性,所以抽象类作为类型定义工具的效能大打折扣。在这一点上,Java接口的优势就出来了,任何一个实现了一个Java接口所规定的方法的类都可以具有这个接口的类型,而一个类可以实现任意多个Java接口,从而这个类就有了多种类型。
3、从第2点不难看出,Java接口是定义混合类型的理想工具,混合类表明一个类不仅仅具有某个主类型的行为,而且具有其他的次要行为。
Java接口和Java抽象类的存在就是为了用于具体类的实现和继承的,如果你准备写一个具体类去继承另一个具体类的话,那你的设计就有很大问题了。Java抽象类就是为了继承而存在的,它的抽象方法就是为了强制子类必须去实现的。
我想,如果你编的代码里面连一个接口和抽象类都没有的话,也许我可以说你根本没有用到任何设计模式,任何一个设计模式都是和抽象分不开的,而抽象与Java接口和抽象Java类又是分不开的。
理解抽象,理解Java接口和抽象Java类,我想就应该是真正开始用面向对象的思想去分析问题,解决问题了吧。