如何编写高质量代码,从而提升系统性能.想必是很多程序员都非常注意的地方,最近总结了一些要点,特此记录在案. 所谓代码高可读性和高可维护性,其实就是应该有着规范的Java程序设计风格,在开发程序过程中,从近期目标看是应该着眼于功能实现,但是只能解一时之渴,而不思长远之计,确不可取,一个杂乱无序的代码让人看后有一种不解其意,心绪烦乱的感觉.所以,作为一名合格的程序员,一定要确定一个观点就是你编写出来的代码不只是给你一个人看的,还是给别人看的,所以在开发过程中文件注释头,java源文件编排格式,方法体的具体业务含义的注释都是必须的. 如程序注释就分为块注释与行注释 .例如块注释为
/**
* @param
@return
*/
行注释
/** **/或者 //
再者就是方法的命名也需要多加斟酌,一个业务方法,如果取最能体现体现该业务的名字,这样读者几乎不需要看代码便可以知道该方法具体用途. 高质量的代码其实很多时候都在一些小细节中体现,对于每个程序员来说一个for循环都会写,可是却有很多人没有能在代码中体现出高效性来,今天这一节我用简单的一个例子来说明:一个ArrayList需要遍历. 一般人会写成for(int i=0;ilist.size();i++) 这有问题吗?没有问题,能够完成程序员的意图的功能.
可是它高效吗?你有注意到这点吗?其实问题就出现在list.size()方法,这个方法是计算一个list的大小,本身它不会存在任何问题,可是将它放在了一个for()循环中的话,就很有问题了,因为如果一个N次的for循环,这个方法就需要被执行N次,这样的代码就造成计算机花很多的时间去做没有意义的事情,而本来这个list.size()方法只需要计算一次的就可以了,所以我们把计算list大小的方法放在for循环外面去定义的话,效率就可以得到提高
如: int size = list.size();
for(int i=0;isize;i++)
关于For循环还有一个要注意的地方,就是在for循环里面去New一个新对象.如:
for(int i=0;i10;i++){
A a = new A();
}
是不是怎么看都不会有问题,是的在语法上.或者是执行业务处理逻辑的时候,它都是没有任何问题的,可是这是从语言级别去看待问题,没有深入到它的实现机制上去看待问题.
在介绍这个问题的之前我想先简单说下关于java内存的机制:java是如何在内存中保存对象,我们回到A a=new A()在内存中是怎么分配这个问题来,
首先在栈中开辟一段空间存放引用a,然后在堆中开辟一段空间存放新建的A对象,同时引用a指向堆中新建得A对象,a所指代的对象地址为A在堆中地址.根据javaGC机制,只有对象已经没有引用指向它的时候,才有可能被GC给回收,所以基于这种机制的话,上述的一个For循环就会存在很大的效率问题了,如果循环有1000次,在内存中栈会有1000个引用,而堆中也会有1000个新生成的对象,同时1000个引用会相应指向1000个新生成的对象,即使这个for循环结束,也不会有任何改变.但是实际上1000个引用的生成完全是没有必要的,如果有着编写高性能代码的想法的话:像这样的for循环完全可以这样写:
A a = null;
for(int i=0;i1000;i++){
a = new A();
...
这样的代码在内存中便只会在栈中生成一个指向,每当一个for循环结束后,这个指向会指向下一个新生成的对象,前面生成的对象就会失去指向,这样GC就有可能更加快速的回收这些已经失去功能的对象. 在java中其实new 一个对象是非常耗费时间的,特别是重量级对象,所以每次在new 对象的时候一定需要考虑清楚是不是非的生成一个对象才能完成我的业务需求呢?都说到这里了大家应该明白能够根据实际情况,然后举一反三的话,我相信大家编写出来的代码就会更加高效了.
其次是针对同步的慎重考虑,因为我们一旦用了synchronized这个关键字后,就很可能丧失了并行的功效,所以在开发的过程中需要注意到线程是不是会对共有的资源进行处理,然后在慎重选择Synchronized 关键字,其实大家可以考虑用ThreadLocal这个类,它的优点是既保证同步的情况下仍然能保证并行,缺点是会占用更多的空间去换取换取时间.
(1)在搜索子句的列名边要避免函数、算术操作符和其它的表达式,除非建立基于函数的索引
like "sm%"可以转换成 ="sm" and "sn"
(10)在子查询,exists和in要比not exists和not in执行得快,因为对于exists和in,优化器只要找到一条记录就可以返回TRUE,而对于not exists和not in则必须检查所有的值.
以上10条总结如果在编写sql的时候能注意到的话,将会在一定程度上提高java跟数据交互的性能.
那么除了在SQL上下功夫来提高性能之外,编写合适的事务处理也将带来一些性能提高.我们都知道事务具有:原子性,隔离性,一致性,持久性,所以在使用事务的时候肯定是牺牲并发性能为代价的.特别是一个涉及update的事务处理的时候,数据库会在表上加上排他锁,而一个数据资源只要被一个事务放置了排他锁,其他事务将不能放上排他锁,一定要一直等到事务结束后才释放.所以在这种情况下的,并发性就会被抹杀掉了.我们不能改变这种加锁的机制,但是我们可以用另外一种方式来达到一定程度的性能提升,那就是根据实际情况将一个大事务分解成小事务来处理.简而言之就是减低事务放置排他锁和释放排他锁的时间间隔,这样可以让其他的事务能更快的访问到数据资源.而关于大事务分解一定要小心使用,如果使用不恰当的话很可能会产生意想不到的数据不一致错误.
如何写出好的Java代码
①.. 优雅需要付出代价.
从短期利益来看,对某个问题提出优雅的解决方法,似乎可能花你更多的时间.但当它终于能够正确执行并可轻易套用于新案例中,不需要花上数以时计,甚至以天计或以月计的辛苦代价时,你会看得到先前所花功夫的回报(即使没有人可以衡量这一点).这不仅给你一个可更容易开发和调试的程序,也更易于理解和维护.这正是它在金钱上的价值所在.这一点有赖某种人生经验才能够了解,因为当你努力让某一段程序代码变得比较优雅时,你并不是处于一种具生产力的状态下.但是,请抗拒那些催促你赶工的人们,因为那么做只会减缓你的速度罢了.
即使你已确定某段程序代码极为重要,而且是系统的重要瓶颈,这个准则依然成立.尽可能简化设计,让系统能够先正确动作.如果程序的执行不够快,再量测其效能.几乎你总是会发现,你所认为的"瓶颈"其实都不是问题所在.把你的时间花在刀口上吧.
如果你所探讨的问题过于混杂,试着想像该问题的基本动作会是什么,并假设这一小块东西能够神奇地处理掉最难的部分.这"一小块"东西其实就是对象–请撰写运用该对象的程序代码,然后检视对象,并将其中困难的部分再包装成其他对象,依此类推.
Class 使用者扮演着"客户"角色,不需要(也不知道)class的底层运作方式.Class开发者必须是class设计专家,并撰写class,使它能够尽可能被大多数新手程序员所用,而且在程序中能够稳当执行.一套程序库只有在具备通透性的情况下,使用起来才会容易.
如果你的设计所得结果更甚于此,请问问自己,是否其中每一样东西在整个程序生命期中都饶富价值?如果并非如此,那么,维护它们会使你付出代价.开发团队的成员都有不维护"无益于生产力提升"的任何东西的倾向;这是许多设计方法无法解释的现象.
通过这种方式,只要执行测试程序,所有的程序变动就可以自动获得验证,而且可以立即发现错误.由于你知道的测试架构所具备的安全性,所以当你发现新的需求时,你会更勇于进行全面修改.请记住,程序语言最大的改进,是来自型别检查、异常处理等机制所赋予的内置测试动作.但这些功能只能协助你到达某种程度.开发一个稳固系统时,你得自己验证自己的classes或程序的性质.
这里所指的意义可以像"将共用程序代码置于惟一函数"这么简单.如果你加入的间接层(或抽象化、或封装等等)不具意义,它可能就和没有适当的间接层一样糟糕.
①.1. 让class尽可能微小而无法切割(atomic).
赋予每个class单一而清楚的用途.如果你的classes或你的系统成长得过于复杂,请将复杂的classes切割成比较简单的几个classes.最明显的一个判断指针就是class的大小:如果它很大,那么它工作量过多的机会就可能很高,那就应该被切割.重新设计class的建议线索是:
①.) 复杂的switch语句:请考虑运用多态(Polymorphism).
冗长的引数列会使函数的调用动作不易撰写、阅读、维护.你应该试着将函数搬移到更适当的class中,并尽量以对象为引数.
如果某段程序代码不断出现于许多derived class函数中,请将该段程序代码置于某个base class 函数内,然后在derived class函数中调用.这么做不仅可以省下程序代码空间,也可以让修改该段程序代码动作更易于进行.有时候找出此种共通程序代码还可以为接口增加实用功能.
通常这种情况代表所谓的"type-check coding".也就是说究竟会执行哪一段程序代码,乃是依据某种型别信息来做抉择(最初,确切型别可能不十分明显).你通常可以使用继承和多态来取代此类程序代码;Polymorphical method (多态函数)的调用会自动执行此类型别检验,并提供更可靠更容易的扩充性.
如果某个接口元素对class而言极重要,它应该被放在base class 里头,而不是直到衍生(derivation)时才被加入.如果你在继承过程中加入了函数,或许你应该重新思考整个设计.
从class 的最小接口开始妨展,尽可能在解决问题的前提下让它保持既小又单纯.不要预先考量你的class被使用的所有可能方式.一旦class被实际运用,你自然会知道你得如何扩充接口.不过,一旦class被使用后,你就无法在不影响客户程序代码的情况下缩减其接口.如果你要加入更多函数倒是没有问题–不会影响既有的客户程序代码,它们只需重新编译即可.但即使新函数取代了旧函数的功能,也请你保留既有接口.如果你得通过"加入更多引数"的方式来扩充既有函数的接口,请你以新引数写出一个重载化的函数;通过 这种方式就不会影响既有函数的任何客户了.
请base class和derived class 之间的关系是"is-a"(是一种),让class和成员对象之间的关系是"has-a"(有一个).
如果不需要,请优先选择合成(也就是是使用成员对象).这种作法可以消除"过多基础型别".如果你采用继承,使用者会认为他们应该可以向上转型.
也就是说,如果你找到了某个 class, 带有一些状态变量,而其函数会依据这些变量值切换不同的行为,那么你或许就应该重新设计,在subclasses 和覆写后的函数(overrided methods)中展现行为止的差异.
函数不应该依据引数值条件式地选择执行某一段程序代码.这种情况下你应该撰写两个或更多个重载函数(overloaded methods)
飞机上的"旅客舒适系统"包括数个分离的元素:座椅、空调、视讯设备等等,你会需要在飞机上产生许多这样的东西.你会将它们声明为Private成员并开发出一个全新的接口吗?不会的,在这个例子中,元素也是Public接口的一部分,所以仍然是安全的.当然啦,简单聚合并不是一个常被运用的解法,但有时候的确是.
你的class应该设计得尽可能容易使用.你应该预先考量可能性有的变动,并针对这些 可能的变动进行设计,使这些变动日后可轻易完成.
这往往是刚踏OOP领域的过程式(procedural)程序员的一个苦恼,因为他们往往最终还是写出一个过程式程序,并将它们摆放到一个或两个巨大对象中.注意,除了application framework (应用程序框架,译注:一种很特殊的、大型OO程序库,帮你架构程序本体)之外,对象代表的是程序中的观念,而不是程序本身.
对象也应该具有定义明确界限清楚的行为.有时候使用"数据对象"是适当的,但只有在通用形容器不适用时,才适合刻意以数据对象来包装、传输一群数据项.
你应该只在必要时才使用继承.如果在组合适用之处你却选择了继承,你的设计就渗杂了非必要的复杂性.
这句话的极端例子,就是继承出不同的classes表现各种不同的颜色,而不使用"color"field.
语意相异的两个对象拥有相同的动作(或说责任)是可能的.OO世界中存在着一种天生的引诱,让人想要从某个class继承出另一个subclass,为的是获得继承带来的福利.这便是所谓"变异性".但是,没有任何正当理由足以让我们强迫制造出某个其实并不存在的superclass/subclass关系.比较好的解决方式是写出一个共用的base class,它为两个derived classes制作出共用接口–这种方式会耗用更多空间,但你可以如你所盼望地从继承机制获得好处,而且或许能够在设计上获得重大发现.
最清晰易懂的设计是将功能加到继承得来的class里头;继承过程中拿掉旧功能(而非增加新功能)则是一种可疑的设计.不过,规则可以打破.如果你所处理的是旧有的class程序库,那么在某个class的subclass限制功能,可能会比重新制定整个结构(俾使新class得以良好地相称于旧 class)有效率得多.
举个例子,如果你的class只应该产出惟一一个对象,那么请不要以加思索毫无设计的手法来完成它,然后撰写"只该产生一份对象"这样的注解就拍拍屁股走人.请将它包装成singleton(译注:一个有名的设计模式,可译为"单件").如果主程序中有多而混乱的"用以产生对象"的程序代码,请找出类似 factory method这样的生成模式(creational patterns),使价钱可用以封装生成动作减少"赤裸裸无加掩饰的机能"(naked functionality)不仅可以让你的程序更易理解和维护,也可以阻止出于好意却带来意外的维护者.
请记住,你往往必须在获得所有信息之前让项目继续前进.而且理解未知部分的最好也最快的方式,通常就是实际前进一步而不只是纸上谈兵.除非找到解决办法,否则无法知道解决办法.Java拥有内置的防火墙,请让它们发挥作用.你在单一class或一组classes中所犯的错误,并不会伤害整个系统的完整性.
将团队以外的某些人带进来-他不必非得是个顾问不可,他可以是公司其他团队的成员.请那个人以新鲜的姿态审视你们的成果,这样可以在尚可轻易修改的阶段找出问题,其收获会比因演练而付出的时间和金钱代价来得高.实现 (Implementation)
价钱可以在以下网址找到相关文档:java.sun.com/docs/codeconv/idex.html.本书尽可能遵守这些习惯.众多Java程序员看到的程序代码,都有是由这些习惯构成的.如果你固执地停留在过去的编写风格中,你的(程序代码)读者会比较辛苦.不论你决定采用什么编写习惯,请在整个程序中保持一致.你可以在home.wtal.de/software-solutions/jindent上找到一个用来重排Java程序的免费工具.
通常这种的形式是在最前端加上底线和其他字符,匈牙利命名法(Hungarian notation)是其中最差的示范.在这种命名法中,你得加入额外字符来表示数据的型别、用途、位置等等.仿佛你用的是汇编语言(assembly language)而编译器没有提供任何协肋似的.这样的命名方式容易让人混淆又难以阅读,也不易推行和维护.就让classes和packages来进行"名称上的范
围制定(name scoping)"吧.
包括equals( )、hashCode( )、clone( ) ( 实现出Cloneable),并实现出Comparable和Serialiable等等.
你不需要移除该测试就可将程序纳入项目.而且如果有所变动,你可以轻易重新执行测试.这段程序代码也可以做为class的使用示例.
这可能会引发对多重基类(multiple base types)的认识需求.如果你不需要向上转型,你可以先衍生新的class发便执行protected访问动作,然后在"需要用到上述 protected成员"的所有classes中,将新class声明为成员对象,而非直接继承.
只有在程序能动但执行不够快时,而且效能量测工具(profiler)显示某个函数的调用动作成为瓶颈时,才使用final函数.
这不仅强调二者间的关联,也是通过"将class名称嵌套置于另一个class 内"而使同一个class 名称在单一Package中可被重复使用.Java 容器库在每个容器类中都定义了一个内隐的(inner)Iterator class,因而能够提供容器一份共通接口.运用内隐类的另一个原因是让它成为private实现物的一部分.今天这一节,内隐类会为信息隐藏带来好处,而不是对上述的class关联性提供肋益,也不是为了防止命名空间污染问题(namespace pollution).
那会让人神经错乱.尤其在系统建构初期,先别烦恼究竟要不要撰写(或避免)原生函数(native methods)、要不要将某些数声明为final、要不要调校程序代码效率等等.你的主要问题应该是先证明设计的正确性,除非设计本身需要某种程度的效率.
这种作法可降低"对象被用于错误场所,因而隐藏难以察觉的臭虫"的机会.假设你有个容器,以及一段走访该容器的程序片段.如果你复制该段程序代码,将它用于新的容器身上,你可能会不小心以旧容器的大小做为新容器的走访上限值.如果旧容器已不在访问范围内,那么编译期便可找出这样的错误.
请熟悉他们的用法.你将所以呢大幅提升你的生产力.请优先选择ArrayList来处理序列(sequences),选择HashSet来处理集合(sets)、选择HashMap来处理关联式数组(associative arrays),选择Linkedlist (而不是Stack) 来处理 shacks和queues.
请在你所撰写的每个class中运用Java 提供的所有强固提升工具:访问权限、异常、型别检验等等.通过这种方式,你可以在建构系统时安全地移往抽象化的下一个层次.
试着在最靠近问题发生点的地方处理问题.请优先在"掷出异常之处"处理问题,并在拥有足够信息以处理异常的最接近处理函数(handler)中捕捉异常.请进行现阶段你能够对该异常所做的处理;如果你无法解决问题,应该再次掷出异常.
函数应该是一种简短的、"描述并实现class接口中某个可分离部分"的功能单元.过长且复杂的函数不仅难以维护,维护代价也高.或许它尝试做太多事情了.如果你发现这一类函数,代表它应该被切割成多相函数.这种函数也提醒你或许得撰写新的class.小型函数同样能够在你的class中被重复运用.(有时候函数必须很大才行,但它们应该只做一件事情.)
一旦你对外公开了程序库的概况(method、Class 或field).你便再也无法移除它们.因为如果移除它们,便会破坏某个现有的程序代码,使得它们必须重新被编写或重新设计.如果你只公开必要部分,那么你便可以改变其他东西而不造成伤害.设计总是会演化,所以这是个十分重要的自由度.通过这种方式,实现码的更动对derived class 造成的冲击会降最低.在多线程环境下,私密性格外重要-只有private数据可受保护而不被un-synchronized(未受同步控制)的运用所破坏.
不过注解应该赋予程序代码真正的意义;如果只是重申程序代码已经明确表示的内容,那是很烦人的.请注意,通常Java class和其函数的名称都很长,为的便是降低注解量.
次佳情境下,class 只继承自(或合成自)强固的(robust)classes,所以如有任何异常被掷出,并不需要清理.其他情况下,你就得在finally子句清理合成后的classes.如果某个构造函数一定会失败,适当的动作就是掷出异常,使调用者不至于盲目认为对象已被正确产生而继续执行.
特殊情况下可能需要释放一些不会被垃圾回收的内存.因为垃圾回收器可能不会被唤起处理你的对象,所以你无法使用finalize()执行必要的清理动作.基于这个原因,你得拟定自己的"清理用"函数.在class finalize()中,请检查确认对象的确已被清理,并在对象尚未被清理时,掷出衍生自Runtime Exception 的异常.使用这种架构前,请先确认finalize()在你的系统上可正常动作(这可能需要调用System.gc()来确认).
但如果你的"直接上一层superclass"是Object,,就不需要这个动作.你应该让super.finalize() 成为被覆写(overridden)之finalize()的最后一个动作而不是第一个动作,用以确保base class的组件在你需要它们的时候仍然可用.
通过这种方式,你可以获得数组的"编译期型别检验"的好处,而且数组接收者可能不需要"先将数组中的对象加以转型"便能加以使用.请注意,容器库的base class (Java. Util. Collection) 具有两个toArray(),能够达到这个目的.
如果你覆写base class 函数时没有正确拼写其名称,那么便会增加一个新的函数,而不是覆写原有的函数.但是情况完全合法,所以你不会从编译器或执行期系统得到任何错误消息–你的程序代码只是无法正确作用,如此而已.
先让程序动起来,再让它快–但只有在你必须(也就是说只有在程序被证明在某段程序代码上遭遇效能瓶颈)时才这么做.除非你已经使用效能量测工具(profiler)找出瓶颈所在,否则你可能性只是在浪费你的时间.效能调校的"隐藏成本"便是让你的程序代码变得更不可读、更难维持.
清晰的设计能够制作出去易懂的程序.注解、细节说明、示例都是无价的.这些东西能够帮助你和你的后继者.如果没有其他信息,那么Java 线上文档找出一些有用的信息时,你所遭遇的挫败应该足以让你相信这一点.
NewPhone类
package?com.baidu.question;
public?class?NewPhone?extends?Phone?{
private?boolean?mute?=?true;
public?void?call()?{
if(mute){
super.call();
}else{
System.out.println("语音已关闭");
//这里是直接设置
public?void?setMute(boolean?mute){
this.mute=mute;
//担心你的题目是要求两种方法,写的第二种,下面两个方法负责开关
public?void?openMute(){
this.mute=true;
/*
*?也可以这样写
*?setMute(true);
*?下边的方法一样
*?*/
public?void?closeMute(){
this.mute?=?false;
Phone类
public?class?Phone?{
public?void?call(){
System.out.println("打电话");
测试类
public?class?PhoneTest?{
public?static?void?main(String[]?args)?{
Phone?phone?=?new?Phone();
phone.call();
NewPhone?newPhone?=?new?NewPhone();
newPhone.call();
newPhone.setMute(false);
newPhone.openMute();
newPhone.closeMute();
测试结果
打电话
语音已关闭