20145326 《Java程序设计》第4周学习总结

  

20145326 《Java程序设计》第4周学习总结

教材学习内容总结

第六章

一、何谓继承

1.继承共同行为

面向对象中,子类继承父类,避免重复的行为定义。不过并非为了避免重复定义行为就使用继承,滥用继承而导致程序维护上的问题时有所闻。如何正确判断使用继承的时机,以及继承之后如何活用多态,才是学习继承的重点。以书上158页的例子来看,magician中粗体字部分与swordsman中相对应的程序代码重复了,重复在设计上就是不好的信号,如果我们想将name、level、blood改为其他名称,那就要修改swordsman与magician两个类,如果有更多类具有重复的程序代码,那就要修改更多类,造成维护上的不便。如果要改进,可以把相同的程序代码提升为父类(Role)。这里引入了一个新的关键字“extends”。它表示SwordsMan与Magician会扩充Role的行为,也就是继承Role的行为。SwordsMan与Magician不止可以继承Role的行为,还可以扩充原来Role没有的行为。继承就是为了避免多个类间重复而定义共同行为。继承的好处之一就是若你要将name、level、blood改为其他名称,那就只要修改Role.java就可以了,只要是继承Role的子类都无需修改。这里要注意一点,private成员会被继承,只不过子类无法直接存取,必须通过父类提供的方法来存取。

 

2.多态与is-a

在java中,子类只能继承一个父类,继承除了可避免类间重复的行为定义外,还有个重要的关系,那就是子类与父类间会有is-a的关系,中文称之为“是一种”的关系。用前面范例来说,SwordsMan继承了Role,所以SwordsMan是一种Role。但如果这样编写代码就无法通过:SwordsMan swordsman=new Role();编译程序是语法检查器,要知道程序片段为何可以通过编译,为何不可以通过编译,就是将自己当作编译程序,检查语法的逻辑是否正确,方式是从=号右边往左读。那翻译过来就是:“Role是一种SwordsMan”,这显然错误,导致编译失败。编译程序检查这类语法,一次只看一行。 若: Role role1=new SwordsMan(); SwordsMan swordsman=role1;这也会编译失败。第一行没问题,翻译过来是“SwordsMan是一种Role”,正确。第二行中,因为role1为Role声明的名称,所以第二行翻译过来是“Role是一种SwordsMan”,这不一定,所以编译失败。如果想让程序住嘴,可以这样修改: Role role1=new SwordsMan(); SwordsMan swordsman=(SwordsMan)role1; 相当于就是让Role扮演SwordsMan,这段程序代码是可以通过编译的。 但这种情况就不行: Role role2=new Magician(); SwordsMan swordsman=(SwordsMan)role2; 其中role2参考的是Magician,你要让魔术师假扮为剑士,这在执行上会是个错误。使用是一种(is-a)原则,就可以判断何时编译成功,何时编译失败,以及将扮演看作编译程序住嘴语法。书上165页的范例为我们展示了一种多态的写法,这样的写法好处何在?就算有100种角色,只要他们都是继承Role,都可以使用这个方法显示角色的血量,而不需要像前面重载的方式,多态的写法显然具有更高的可维护性。什么叫多态?多态就是使用单一接口操作多种类型的对象,注意这里的接口并不是专指java中的interface,而是指对象上可操作的方法。

3.重新定义行为

如果现在有个需求,请设计static()方法,可以播放角色攻击动画,我们首先想到的是用drawfight()方法。但是对于drawfight()方法而言,只知道传进来的会是一种Role对象,所以编译程序也只能检查你调用的方法,Role是不是有定义,显然目前的Role没有定义fight()方法,所以编译错误。仔细观察一下SwordsMan与Magician的fight()方法,它们的方法签署都是public void fight(),也就是说操作接口是相同的,只是操作内容不同,于是我们可以将fight()方法提升至Role类中定义。在Role类中定义了fight方法(),由于实际上角色如何攻击,只有子类才知道,所以这里的fight()方法内容是空的,没有任何程序代码执行,只有当SwordsMan继承Role之后,再对fight()进行定义。在继承父类后,定义与父亲中相同的方法部署,但执行内容不同,这称为重新定义。注意如果传入drawfight()的是SwordsMan,role参考的就是SwordsMan实例(参考书上167范例),操作的就是SwordsMan上的方法定义。如果父类中定义的是fight(),但子类中定义的是Fight(),这就不是重新定义fight()了,而是子类重新定义了一个Fight()方法,这是合法的方法定义,可以通过编译,只不过我们会发现为什么SwordsMan没有挥动剑。在JDK5之后支持标注,其中一个内建的标准标注就是@override,表示要求编译程序检查,该方法是不是真的重新定义了父类中的某个方法,如果不是,就会引起编译错误。

4.抽象方法,抽象类

现在有这么一个问题,你没有任何方式强迫或提示子类一定要操作fight()方法,只能口头或在文件上告知,不过如果有人没有传达到、没有看文件,或文件看漏了呢?如果某方法区块中真的没有任何程序代码操作,可以使用abstract表示该方法为抽象方法,该方法不用撰写{}块,直接“;”结束即可。类中意愿若有方法没有操作,并且标示为abstract,表示这个类定义不完整,定义不完整的类就不能用来生成实例。这就好比设计图不完整,不能用来生产成品一样。子类如果继承抽象类,对于抽象方法有两种做法,一种做法是继续标示该方法为abstract,另一种做法就是操作抽象方法,如果两种做法都没有实施,就会引发编译错误。 二、继承语法细节

1.protected成员

根据书上170页的例子,定义的toString()在取得名称、等级与血量时不是很方便,因为Role中的name、level与blood被定义为private,所以无法直接在子类中存取,只能通过getName()、getLevel()、getBlood()来取得。将Role中的name、level与blood定义为public,这又会完全开放name、level与blood的访问权限,如果不想这么做,只是想让子类可以直接存取name、level与blood的话,可以定义它们为protected。被声明为protected的成员,相同包的类可以直接存取,不同包中的类可以在继承后的子类直接存取。如果方法中没有同名参数,this可以省略,不过基于程序可读性,多打个this会比较清楚。java中有三个权限字,分别是public、protected、private,虽然只有三个权限字,但是有四个权限范围,因为没有定义权限关键字,默认值就是包范围。权限依小至大来区分,就是private、无关键字、protected、public,如果一开始不知道使用哪个权限,就先使用private,以后视要求再放开权限。

2.重新定义的细节

有时候重新定义方法时,并非完全不满意父类中的方法,只是希望在执行父类中方法的前、后做点加工。在java中,如果想取得父类中的方法定义,可以在调用方法前,加上super关键字。可以用super关键字调用的父类方法,不能定义为private!因为这就限定只能在类内使用。重新定义方法要注意,对于父类中的方法权限,只能扩大不能缩小,若原成员为public,子类中重新定义时不可为private或protected!在JDK5之前,重新定义方法时除了可以定义权限较大的关键字外,其他部分必须与父类中方法签署完全一致。在JDK5之后,重新定义方法时,如果返回类型是父亲中方法返回类型的子类,也是可以通过编译的。static方法属于类拥有,如果子类中定义了相同署名的static成员,该成员属于子类所有,而非重新定义,static方法也没有多态,因为对象不会个别用有static成员。

3.再看构造函数

如果类有继承关系,在创建子类实例后,会先进行父类定义的初始流程,再进行子类中定义的初始流程,也就是创建子类实例后会先执行父类构造函数定义的流程,再执行子类构造函数定义的流程。构造函数可以重载,父类中可重载多个构造函数,如果子类构造函数中没有指定执行父类中哪个构造函数,默认会调用父类中无参数构造函数(书上174范例)。这里要注意一点,this()与super()只能择一调用,而且一定要在构造函数第一行执行。如果定义了有参数的构造函数,也可以加入无参数构造函数,即使内容为空也无所谓,这是为了日后使用上的弹性。

4.再看final关键字

如果在指定变量值后就不想再改变变量值,可以在声明变量时加上final限定,如果后续撰写程序时,自己或别人不经意想修改final变量,就会出现编译错误。如果对象数据成员被声明为final,但没有明确使用=指定值,那表示延迟对象成员值的设定,在构造函数执行流程中,一定要有对该数据成员指定值的动作,否则编译错误。class前也可以加上final关键字,如果class前使用了final关键字定义,那么表示这个类是最后一个了,不会再有子类,也就是不能被继承。定义方法时,也可以限定该方法为final,这表示最后一次定义方法了,也就是子类不能重新定义final方法。

5.java.lang.Object

在java中,子类只能继承一个父类,如果定义类时没有使用extends关键字指定继承任何类,那一定是继承java.lang.Object。任何类,追溯至最上层父类,一定是java.lang.Object,也就是java中所有对象,一定“是一种”object。任何类型的对象,都可以使用Object声明的名称来参考。这有什么好处呢,如果有个需求是使用数组收集各种对象,那该声明为什么类型呢?答案是object[]。我们之前见到的toString()方法与equals()方法都是object上定义的方法。java.lang.Object是所有类的顶层父亲,这代表了object上定义的方法,所有对象都继承下来了,只要不是被定义为final方法,都可以重新定义。instanceof运算符,它可以用来判断对象是否由某个类创建,左操作数是对象,右操作数是类,在使用instanceof时,编译程序还会来帮点忙,会检查左操作数是否在右操作数类型的继承架构中,执行时期,并非只有左操作数对象为右操作数类直接实例化才返回true。只要左操作数类型是右操作数类型的子类型,instanceof也会返回true。

6.关于垃圾收集

创建对象会占据内存,如果程序执行流程中已无法再使用某个对象,该对象就只是损耗内存的垃圾。对于不再有用的对象,JVM有垃圾收集机制(GC),收集到的垃圾对象所占据的内存空间,会被垃圾收集器释放。执行流程中,无法通过变量参考的对象,就是GC认定的垃圾对象。执行流程具体来说就是线程,目前你唯一接触到的线程就是main()程序进入点开始之后的主线程,关于垃圾收集本身就很复杂,不同的需求也会有不同的方法。

7.再看抽象类

撰写程序常常有些看似不合理但又非得完成的需求。有些不合理的需求,本身确实不合理,但有些看似不合理的需求,其实是可以通过设计来解决的。以书上186页的3个例子来说,第一个例子中的类定义不完整,print()、println()与nextInt()都是抽象方法,因为老板还没决定在哪个环境下执行游戏,所以如何显示输出、取得用户输入就不能操作,但我们可以先操作猜数字的流程,虽然是抽象方法,但是go()方法中,还是可以调用。其实只要创建出ConsoleGame的实例,执行go()方法过程中调用到print()、println()与nextInt()等方法时,都是执行ConsoleGame中定义的流程,完整的猜数字游戏就这样操作出来了。

第七章

一、何谓接口

1.接口定义行为

以书上195页为例,老板今天想开发一个海洋乐园游戏,当中所有东西都会游泳,谈到会游泳的东西,首先就想到鱼,上一章又刚学过继承,也知道继承可以运用多态,所以就会定义Fish类中有个swim()行为。但实际上每种鱼游泳方式都不同,所以将swim()定义为abstract。老板又说为什么都是鱼,人也会游泳啊,于是就再定义Human类继承Fish,但是Human继承Fish?不会很奇怪吗,对,程序不会抱怨什么,就目前为止,程序也可以执行,但是逻辑或设计上有不合理的地方。java中只能继承一个父亲,所以更强化了“是一种”关系的限制性。“所有东西”都会“游泳”,代表了“游泳”这个“行为”可以被所有东西拥有,而不是“某种”东西专属。对于“定义行为”,在java中可以使用interface关键字定义。接口可以用于定义行为但不定义操作,在书上197页的例子中,swim()方法没有操作,直接标示为abstract,而且一定是public,对象若想拥有swimmer定义的行为,就必须操作swimmer接口。类要操作接口,必须使用implements关键字,操作某接口时,对接口中定义的方法有两种处理方式,一是操作接口中定义的方法,二是再度将该方法标示为abstract。以java的语意来说,继承会有“是一种”的关系,操作接口则表示“拥有行为”。

 

 

2.行为的多态

会使用接口定义行为之后,也要再来当编译程序,看看哪些是合法的多态语法。如:Swimmer swimmer1=new Shark(); 这段代码是对的。判断方式是“右边是不是拥有左边的行为”。所以翻译过来就是“鲨鱼会游泳。”完全正确。而对于如下这种情况: Swimmer swimmer1=new Shark(); Shark shark=(Shark)swimmer;若没有括号里的内容,编译则无法通过。因为有swimmer行为的对象不一定是Shark,所以无法通过编译。括号里的内容就是要让程序闭嘴,有swimmer行为的对象,不一定是Shark,不过我现在就是要让swimmer扮演Shark!,这样可以通过编译,执行时期 确实swimmer也是参考Shark实例,所以没有错误。但如果是这种情况,就不行:Swimmer swimmer1=new Human();Shark shark=(Shark)swimmer;第二行中,swimmer实际上参考的是Human实例,无法通过编译。在你的设计中,会游泳的东西都拥有swimmer的行为,都操作了swimmer接口,只要是操作swimmer接口的对象,都可以使用范例(201页)中的doswim()方法。可维护性显然提高很多。

3.解决需求变化

我们都知道写程序要有弹性,要有可维护性。不过这确实是个抽象的问题,这里从最简单的定义开始,如果增加新的需求,原有的程序无须修改,只需针对新需求撰写程序,那就是有弹性,有可维护性的程序。在java中,类可以操作两个以上的类,也就是拥有两种以上的行为。当然需求是无止境的,原有程序框架也许确实可满足某些需求,但有些需求也可能超过了原有架构预留的弹性,一开始要如何设计才会有弹性,则必须靠经验和分析判断。也许你预先假设会遇上某些需求而设计了一个接口,但从程序开发至生命周期结束,该接口从未被操作过,或者仅有一个类操作过该接口,那么该接口也许就不必存在,你事先的假设也许就是过度预测需求。在不好的架构下要修改程序的话,很容易牵一发而动全身。对了!在java中,接口可以继承自另一个接口。 需求不断变化,架构也有可能因此而改变。好的架构在修改时,其实也不会全部的程序代码都被牵动,这就是设计的重要性。

二、接口语法细节

1.接口的默认

在java中,可使用interface来定义抽象的行为与外观,如接口中的方法可声明为public abstract。如:public interface Swimmer{ public abstract void swim();} 接口中的方法没有操作时,一定得是公开且抽象,为了方便,你也可以省略public abstract,编译程序会自动帮你加上public abstract。书上208页下角有个范例特别好,这个代码肯定是无法编译成功的,因为Action中定义的execute()其实默认为public abstract,而some类在操作execute()方法时,没有撰写public,因此就是默认包权限,这等于是将Action中的public方法缩小为包权限,所以编译失败了,必须将some类的execute()改为public才通过编译。interface中还可以定义常数。如public interface Action{public static final int a=1;}java中经常见到在接口中定义这类常数,称为枚举常数。在interface中,也只能定义public static final的枚举常数。为了方便,撰写程序的时候可以省略,系统会自动帮你写上public static final,所以在接口中枚举常数,一定要使用=指定值,否则就会编译错误。要在类中定义枚举常数也是可以的,不过一定要明确写出public static final。类可以操作两个以上的接口,如果有两个接口都定义了某方法,而操作两个接口的类会怎样?程序面上来说,并不会有错误,照样通过编译。但还是要思考一下,如果表示不同的行为,那么在操作时,应该有不同的方法操作,some与other的execute()方法在名称上就得不同。如果表示相同的行为,那可以定义一个父接口,在当中定义execute()方法,而some与other继承该接口,各自定义自己的doSome()与doOther()方法(书上212有例子)。接口可以继承别的接口,也可以同时继承两个以上的接口,同样也是使用extends关键字,这代表了继承父接口的行为。在JDK8之后,如果父接口中定义的方法有操作,也代表了继承父接口的操作。

2.匿名内部类

经常会有临时继承某个类或操作某个接口并建立实例的需求,由于这类子类或接口操作类只使用一次,不需要为这些类定义名称,这个时候可以使用匿名内部类来解决这个需求,匿名内部类的方法为: new 父亲()|接口(){//类本体操作};JDK8出现前,如果要在匿名内部类中存取局部变量,则该局部变量必须是final,否则会发生编译错误。局部变量的生命周期往往比对象短,像是方法调用后返回对象,局部变量生命周期就结束了,此时再通过对象尝试存取局部变量会发生错误,java的做法是采用传值,实际上会在匿名内部类的实例中,建立新的变量参考原来的对象。

3.使用enum枚举常数

从JDK5之后,新增了enum语法,可用于定义枚举常数。根据书上217页的范例来看,使用enum定义枚举常数的过程中,enum实际上定义了特殊的类,继承java.lang.Enum,不过这是由编译程序处理,直接撰写程序继承Enum类会被编译程序拒绝。范例中的enum定义的Action实际上是个类,而enum中列举的STOP、RIGHT、LEFT、UP、DOWN常数实际是public static final,且为Action实例。你无法撰写程序直接实例化Action,因为构造函数权限设定为private,只有Action类中才可以实化。不过我们可以用这个Action来声明类型。play()方法中的action参数声明为Action类型,所以只接受Action.STOP、Action.RIGHT、Action.LEFT、Action.UP、Action.Down的传入,不像之前的play()方法,可以传入任何int值,case也只能列举Action实例,所以不用像之前范例,必须使用default在执行时检查,编译程序在编译时期会进行类型检查。

教材学习中的问题和解决过程

第六章主要讲的是继承与多态,内容其实挺简单,也没那么抽象,继承中的“父与子”的思想和“is-a”的概念我印象尤为深刻,这也是理解的突破口。代码编译也都挺顺利的。 多态就是使用单一接口操作多种类型的对象,注意这里的接口并不是专指java中的interface,而是指对象上可操作的方法。第七章主要讲的是接口,interface关键字是“定义行为”,而implements关键字是“拥有行为”的意思。通过接口拥有某种行为。 这两章概念都差不多掌握了,也将书上的代码都敲了一遍,感觉没什么较大问题,很开心~!代码已托管~

 

 

代码调试中的问题和解决过程

在敲代码的过程中,大部分都还是挺顺利的,有一小部分由于我的粗心,没有注意大小写等各种原因导致编译失败,但在经过修正后,也顺利通过编译了。将书上的代码都敲了一遍以后,我感觉对这两章的知识体会更深刻了。 代码全都托管成功~!

 

 

其他(感悟、思考等,可选)

其实java前三章的内容就是一些基本知识概念的介绍,起了个引入的作用。难度也都不是特别大。但从第四章就要开始接触java程序设计的核心知识了。上一周用了相当多的时间才圆满完成任务,虽然累,但很享受,很快乐。也给了我第四周学习的动力。第四周的任务是学习第六章和第七章,内容的难度和第四第五章其实差不多,看第一遍当然觉得有很多地方不懂,很陌生。这个时候就要静下心来,学java不是为了写一篇博客应付老师,完全没必要,老师和学生之间的关系应该是健身教练与健身学员之间的关系,要在学习中细心体会过程,寻找快乐,发现学习的真谛。最终促成一个良性循环那就再好不过了。这也是我最开始学java时所期待的效果。我现在就是先看一遍书,然后把不懂的都做上标记,然后看毕向东老师的视频,然后再看书,不断发现问题,不断解决问题,不断有新的感悟与收获,最终整理思绪,将其记录在博客上,与老师与同学共享。我想这也是娄嘉鹏老师的本意吧~ 这也是对我们的要求与期望! 学习的最高境界就是在快乐中学习,在学习中发现快乐。静下心来,无目的而和目的,继续努力吧!~世上无难事,是怕有心人。任何事情,万事开头难,但只要坚持下来了,一定会受益匪浅!!!

学习进度条

  代码行数(新增/累积) 博客量(新增/累积) 学习时间(新增/累积) 重要成长
目标 3500行 20篇 300小时  
第一周 120/120 1/1 14/14  
第二周 340/460 1/2 14/28  
第三周 200/660 1/3 14/42 学会了托管代码,学会了用构造模型的方法来了解类与对象这部分的知识。
第四周 320/980 1/4 14/56  

 

posted on 2016-03-23 23:55  20145326蔡馨熠  阅读(387)  评论(6编辑  收藏  举报

导航