第一篇:读《java编程思想》有感
编程
--------读《java编程思想》有
感
自从学电脑以来,我对于编程有了浓厚的兴趣,正好朋友有一本叫做《java编程思想》的书,我便借来研读,读完之后我深有体会,所以和大家分享一下。本书共22章,包括操作符、控制执行流程、访问权限控制、复用类、多态、接口、通过异常处理错误、字符串、泛型、数组、容器深入研究、Java I/O系统、枚举类型、并发以及图形化用户界面等内容。
一切皆是对象
在第二章中讲到,一切皆为对象,引起了我深度的思考,为何说一切都是对象呢?无论C++还是Java都属于杂合语言。但在Java中假定了我们只希望进行面向对象的程序设计。也就是说,正式用它设计之前,必须先将自己的思想转入一个面向对象的世界(除非早已习惯了这个世界的思维方式)。只有做好这个准备工作,与其他OOP语言相比,才能体会到Java的易学易用。多线程
看到十四章多线程时,我出现了一些困惑不明白何为多线程所以我上网找到了正解:线程是进程中一个任务控制流序列,由于进程的创建和销毁需要销毁大量的资源,而多个线程之间可以共享进程数据,因此多线程是并发编程的基础。多核心CPU可以真正实现多个任务并行执行,单核心CPU程序其实不是真正的并行运行,而是通过时间片切换来执行,由于时间片切换频繁,使用者感觉程序是在并行运行。读完这一章,我深刻的感受到java的强大之处,不管是不是单核,java都可以实现表面上的多线程。
设计范式
设计范式根据创建、结构、行为分为23个类,在这里我就不一一举例了。我们可将范式想象成一种特别聪明、能够自我适应的手法,它可以解决特定类型的问题。而我们需要做的就是学会这些范式后,去改进我们程序的效率去制造更多的类,变成我们的框架。
总结
当我看完这本书,合上最后一页是我的内心如释重负,闭上眼回忆这本书的内容感觉精彩无比,根据我的总结java不外乎分为:继承、封装、多态,只要好好学一定可以学会。说实在的我觉得这本书对于我来说越看越迷糊,所以我觉得这本书还是需要有点Java功力的人看的,初学者不妨选择一些较浅的书看着。
第二篇:Java编程思想读书笔记
这是一份试图提纲挈领的读书笔记,《java编程思想》这本八百多页的书娓娓道来地包含了太多细节,这对读者是非常贴心的,我也强烈建议细细读这本书,如果你希望在短时间内学会java这种语言,那么这本书不是最好的选择,你可以看看谭浩强系列。我把看这本书的过程中,个人觉得每一章中最重要的思想、用整理在这里,希望自己回顾的时候有所参照和提高。也希望大家带着同样的目的来看本篇读书笔记。
第一章 对象导论
比起过程型语言编写的程序,用面向对象语言编写的程序更加简单、易于理解、可复用。《c++编程思想》里也有这一章,是一个抛砖引自己的玉的章节,不明白可以跳过,回头再看。
第二章 一切都是对象
java语言里面,一切都是对象,并且程序员是通过引用来操纵对象。一个简单的例子,非常轻松地让读者进入java的世界。需要注意的是java数据会储存在5个不同的地方:寄存器、堆栈、堆、常量存储、非ram存储,用new创建的一般对象都放在堆中,而特殊的基本对象放在堆栈中,如果想把基本对象也放在堆中,需要包装基本类型。
第三章 操作符
java中的操作符语法类似于c,所以学习起来一点困难也没有。要特别注意两个比较大的整数相加或者相乘的时候的溢出问题,用long或者biginteger解决这个问题。
第四章 控制执行流程
我想起《pointer on c》这本书第一章就有这一句话,本书适合那些希望迅速学习一门新语言而不是被“为什么if和for很重要”的弱智问题耽搁进度的读者。呵呵,这一章很不厌其烦地介绍了运算、操作符优先级、类型转换、选择循环等基本特性,有c或者c++编程经验的读者可以大概浏览一下。
第五章 初始化和清理
关于初始化:
1.初始化很重要,一定不要忘记。而且java编译器会很好的防止使用未初始化数据的意外,这是比c和c++更优的地方。
2.编译器初始化的顺序为:
a.类首次加载的时候,有关静态初始化的所有动作都会执行。
a1.类的加载包括首次创建该类型的对象,或者该类的静态方法/静态域首次被访问
a2.静态域的初始化在一切初始化之前,即静态变量散布在代码不同的地方,它们也会在任何方法(包括构造器)调用之前被初始化
b.当用new calssname()创建对象的时候,会在堆上开辟足够的存储空间,这块存储空间被清零,然后执行字段的初始化动作。(这里的字段初始化都是非静态的,因为静态的变量已经在a中执行完毕,而且静态变量存储在不同的地方,静态数据只占用一份存储空间)
c.执行构造器
关于清理
c++关于清理的部分包含很大不确定性。目前需要知道的事情是,正常情况下,我们是不需要调用finalize方法的,而且垃圾回收区会自动回收不再使用的对象,同时我们需要自己注意一些需要关闭的文件。
需要注意的是,用=对数组进行“赋值”的时候,实际上是引用的传递,就是说,二者指向同一堆。
第六章 访问权限控制
关于包
你应该有一个自己的域名,这样发布你的java程序的时候,就可以将你的包名设置为你的域名倒转。想要正确让包工作,要正确设置classpath,对于新手来说,这的确是一个挑战。我当初就难到了。
关于访问权限修饰词
值得注意的是,如果两个编译单元放在同一个目录下并且都没有设置包名的话,他们对于对方都是拥有包访问权限的。访问权限修饰词是修饰方法和数据,而不是类。类只有两种访问权限,包访问权限或public访问权限。默认为包访问权限。如果不希望其它任何人对该类拥有访问权限,可以把所有的构造器设置为private。但是有一个例外,可以通过该类自己的static成员内部创建(于是就有了工厂设计模式和单例设计模式)。
第七章 复用类
有三种方法复用类:组合,继承,代理。
组合即是在新的类里面放上已经定义的类的对象,然后通过调用它的方法来实现自己的功能。
继承是通过extends关键词继承某一父类,这样就能访问父类的所有public方法(因此为了继承,一般的规则是将父类的所有数据成员都指定为private,将所有的方法都指定为public)。子类的初始化需要注意的是,(当创建了一个子类的对象时,该对象包含一个基类的子对象)java会在子类的构造器中插入对基类默认构造器的调用。但是如果没有默认的基类构造器,或者想调用一个带参数的基类构造器,就必须用关键词super显式地编写调用基类构造器的语句,并且配上适当的参数列表。
代理很有意思,(我们姑且使用导出类和基类这样的字眼,但要清楚我们不是在讨论继承里面的关键词)在导出类里保存一个基类的对象,然后用自己的方法对该基类的种种方法进行包装。
如何决定使用哪种方法复用类呢?is-a就继承,has-a就用组合。而且,组合比继承总体上使用更广泛、代价更小。
向上转型
这个就牛逼了,第八章,第九章,第十章都与此密切相关。看完本书之后印象最深的就是向上转型了。
使用final的原因有很多种,一定要弄清楚为什么使用final,是由于设计还是效率。
final作用于数据的时候:final作用在基本对象比如int上,该值就成为不可改变的,一旦被初始化就无法再被更改,但是作用在普通的对象引用的时候,final使引用恒定不变,但是引用指向的对象是可变的。编译器需要我们确保final对象一定要被初始化,我们可以通过在构造器中初始化他们,以达到相对自由的效果(称为空白final,我认为这个名字容易让人误解)。java允许在参数列表中以声明的方式将参数指明为final,这一特性主要用来向匿名内部类传递数据(这很重要)。
final作用于方法的时候,说明作者想保持该方法在继承的过程中不被改变,并且不被覆盖。同时,被final修饰的方法会被关闭“动态绑定”,这样编译器就会为final方法调用生成“有限”有效的代码。之所以说有限,是因为随着编译器的牛逼,它生成的代码越来越有效。
final作用于类的时候,即是作者声明对该类的设计不允许任何继承。
学习得更深入一些,可能对以下事实感到有兴趣:java中所有的事物都是对象,每个类的编译代码都存在于电脑中的文件夹里(文件夹的层次根据反转域名得到),该文件只有在需要使用程序代码时才被加载。具体的说,就是“类在其任何static成员函数(包括构造函数)被访问时加载”。第八章 多态
多态的重要基本原理就是向上转型:继承允许将对象视为它自己本身的类型或其基类型加以处处理。
将一个方法调用和一个方法主题关联起来称为绑定,java中所有的方法都是后期绑定(除了static方法和final方法),所以我们可以编写只与基类打交道的程序代码,并且这些代码对所有的导出类都可以正确运行。
(为什么static不动态绑定:因为static方法的主要用法就是用类名.方法名这样的方式来调用,不存在“发送消息给某个对象,让对象判断自己怎么做”这样的情况。
为什么final不动态绑定:这是早期final的一种用法,由程序员指定某方法为final,意味着程序员明了动态绑定的机制,并且声明该方法不需要动态绑定,这样可以获得更好的性能。这种用法已经很少使用了。)
初始化的时候,导出类的构造函数会自动调用基类的默认构造函数,此过程一直递归到最基本的基类。如果需要调用有参数的构造函数就需要手动执行。反过来,如果需要进行清理工作(大部分时候我们都不需要),务必手动执行基类的清理工作先。比如继承链的每个类都实现dispose()方法,那么执行某个类的清理工作的时候,需要手动调用super.dispose()。不过此种情况下,务必在执行super.dispose()之前释放成员对象,清理顺序与执行顺序是相反的。
此外,构造器方面有更加复杂的调用机制,我们不用理它,只需要知道一条有效的准则“用尽可能简单的方法使对象进入正常状态,如果可以的话避免调用其它方法”。
java编译器能够允许向上多态,就是因为java的机制能保存对象的类型信息,即rtti,正因为这种机制,java编译器也允许向下转型,以获得扩展类的“扩展出”的方法。(另,扩展类“扩展”了方法的这种继承不是“纯继承”,这样做好不好?用户自己度量)。向下转型失败的话会抛出一个classcastexception。
虽然这一章都是在讲多态,但是多态并不总是解决问题最好的方案,它有可能使事情不必要地复杂起来,我们应该总是优先考虑更加灵活的组合。
第九章 接口
一种专门提供“接口”的类叫抽象类,若含有至少一个abstract方法,该类就必须被声明为abstract的。抽象方法没有方法体,派生类必须实现它,否则派生类也必须被生命为抽象的。
interface关键词使抽象的概念更进了一步:1.这个“类”完全抽象。2.一个类可以向上转型为多种interface。要让一个类遵循某个特定接口,需要使用implement关键字。
在这一章中出现了“策略设计模式”这个词。创建一个能够根据所传递的参数对象的不同而具有不同行为的方法,被称为策略设计模式。
策略设计模式跟适配器设计模式联合使用可以提供非常强大的功能,比如我们遇到了无法更改的类(别人编写的),想要它满足我们的接口然后放到设计模式里面去(当然满足了接口之后的用法就不止如此了),就可以编写一个适配器,包装该类同时产生我所需要的接口。
使用抽象类和接口的两个原因是:1.在多重继承关系中(这真的很常见,看看java api就知道了),导出类可以被向上转型为每一个接口。2.防止客户端程序员创建该类的对象。那么我们该使用抽象类还是接口呢?事实上,如果知道某事物应该成为一个基类,那么第一选择应该是使它成为一个接口。
接口之间的继承能够形成很好的体系,更像我们的现实生活。但是要特别注意的是,在不同接口中使用相同的方法名通常会造成代码可读性的混乱,令人不快。
工厂方法设计模式是又一个重要的设计模式。我们在代码中增加额外的间接性,一个重要的原因是想要创建框架。
第三篇:Java编程思想第四版_读书笔记
一 基础知识点
1.面向对象程序设计(Object-oriented Programming OOP),UML(Unitied Modelling Language 统一建模语言)。将对象想像成“服务提供者”,它们看起来像什么?能够提供哪 些服务?需要哪些对象? 2.Java中动态绑定是默认行为。Java采用动态内存分配方式,通过new操作在堆(Heap)的内 存池中动态创建对象。Java存储结构类型:1)寄存器2)堆栈,主要存储对象引用3)堆,主要用于存放所有的Java对象4)常量存储,也就是程序代码区5)非RAM存储,如流对象 和持久化对象。基本类型不用new来创建变量,而且这个变量直接存储”值”,并置于堆栈中。3.BigInteger和BigDecimal的使用。当变量作为类的成员使用时 当变量作为类的成员使用时,Java才确保给定其默认初 当变量作为类的成员使用时 始值,但是在方法中定义的变量,它有可能是任意值。面向对象的程序设计可以归纳为“向 对象发送消息”。关键字Static。4.Javadoc只能为public和protected成员进行文档注释,但是也可以通过-private进行标记注 释。Javadoc常用方法: @see 引用其他类,link package.class#member label},{@ {@docRoot},{@inheritDoc},@version,@ author,@since,@param,@return,@throws,@deprecated。5.整数除法会直接去掉结果的小数位。基本类型的对象如果直接对它们赋值,对象指向同 一个常量存储区,但是如果通过对象来初始化则会指向不同的堆的存储区。如:
String st1 = new String(“A”);String st2 = new String(“A”);st1==st2 false String st1 = “A”;String st2 = “A”;st1==st2 true
6.逻辑操作符:与(&&)、或(||)、非(!),其中与(&&)、或(||)会产生短路现象。& |也支持逻辑 也支持逻辑
运算操作。运算操作
7.直接常量中L代表Long,F代表Float,D代表Double。显示二进制形式的话,可以通过Integer 和Long类的静态方法toBinaryString()。如:Long.toBinaryString(10L)。
8.在返回void的方法中没有return语句,那么在该方法的结尾处会有一个隐式的return。
一 般情况下每个方法都会有一个显示的return语句。9.Break用于强行退出循环,不执行循环中剩余的语句,而continue则停止执行当前的迭代,然后退回循环起始处,开始下一次迭代。goto仍是Java的一个保留字,但在语言中并未使用它。Break和continue与标签一起使用,可以中断循环,直到标签所在的地方。This用法: public class Leaf { int I = 0;Leaf increment(){ i++;return this;} }
10.回答一个新技术的问题大概思路和步骤是:我们想干什么,怎么干,干的过程中遇到了 什么问题,现在用什么方式来解决。答题时,先答是什么,再答有什么作用和要注意什么(这 部分最重要,展现自己的心得)。11.finalize的使用:垃圾回收只与内存有关,当“垃圾回收”时,finalize()得到调用。Java中 的对象都能被垃圾回收器回收,但是在“本地方法”的情况下,有可能在分配内存时采用类 似C语言的做法通过malloc()函数来分配存储空间时,这时只能通过free()函数来释放空间,而这些释放操作必须要放到finalize()方法中,垃圾回收器才能正确的释放内存。“垃圾回收” 都不保证一定会发生。
12.垃圾回收原理:引用记数 引用记数是一种简单但速度很慢的垃圾回收技术。每个对象都含有一个 引用记数
Java 编程思想第四版 读书笔记
引用记数器,当有引用连接至对象时,引用计数加1。当引用离开作用域或被置为null时,引用计数减1。垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象的引用计数为0 时,就立即释放其占用的空间。定位交互自引用的对象组所需的工作量极大,所以并没有被 应用于任何一种Java虚拟机中。Java虚拟机采用一种自适应 自适应的垃圾
回收技术,Java虚拟机会 自适应 进行监视,如果所有对象都很稳定,垃圾回收器的效率降低的话,就切换到“标记-清扫” 方式;同样,Java虚拟机会跟踪“标记-清扫”的效果,要是堆空间出现很多碎片,就会切 换回“停止-复制”方式。“停止-复制”,先暂停程序的运行,然后将所有存活 存活的对象从当前 存活 堆复制到另一个堆,没有被复制的全部都是垃圾。“标记-清扫”,从堆栈和静态存储区出发,遍历所有的引用,进而找出所有存活的对象,每当它找到一个存活对象,就会给对象设一个 标记,这个过程中不会回收任何对象,只有全部标记工作完成的时候,清理动作才会开始。在清理过程中,没有标记的对象将被释放,不会发生下任何复制动作。13.初始化顺序:先静态对象,后“非静态”对象,先变量,再构造函数,然后是方法。静态 初始化只有在必要时刻才会进行,如果不引用其对象,那么该对象中的静态成员将不会被创 建,而且它们只会在第一次被访问时进行初始化,其后不会再次被初始化。14.对象的创建过程:1)当首次创建对象时,或类的静态方法/静态域首次被访问时,Java 解释器必须查找类路径,以定位.class文件。2)载入.class,有关静态初始化的所有动作都会 执行。3)当用new 创建对象时,在堆上为对象分配存储空间,而且这块存储空间会被清零,也就是说它会自动地将对象中的所有基本类型数据都设置成默认值,而引用是被设置成 null。4)执行所有出现于字段定义处的初始化动作。5)执行构造器。15.数组:java.util.Arrays常用方法的使用。binarySearch(),copyOf(),asList(),copyOfRange(),equals(),fill(),sort(),toString(),hashCode()。可变 参数列表:void f(float i,Character„ args)。枚举类型:enum,它可以在switch语句内使用。16.类的结构依次为:1)包的注释2)package的设置3)import导入设置4)类的注释5)类的编 写。17.Java的访问权限:类的访问权限只有public和默认包访问权限,成员和方法有 public,protected,默认包访问权限和private。使用类的客户端程序是无法访问包访问权限成员 的。包访问权限的类的对象可以由包内任何其他类来创建和使用,但是包外则不行。18.为了继承,一般的规则是将所有的数据成员都指定为private,将所有的方法指定为public 或protected。Java会自动在导出类的构造器中插入对基类构造器的调用。调用基类的构造器 必须是你在导出类构造器中要做的第一件事。19.代理,它是继承与组合之间的中庸之道,因为我们将一个成员对象置于所要构造的类中(就像组合),但与此同时我们在新类中暴露了该成员对象的所有方法(就像继承)。20.清理方法的顺序:首先,执行类的所有特定的清理动作,其顺序同生成顺序相反;然后,调用基类的清理方法。除了内存之外,不能依赖垃圾回收器去做任何事,如果需要进行清理,最好是编写自己的清理方法,但不要使用finalize()。@Override注解可以防止在你不想重载 时而意外地进行了重载。21.组合与继承之间的选择:组合技术通常用于想在新类中使用现有类的功能而非它的接口,也就是在新类的嵌入某个对象,让其实现所需要的功能,但新类的用户看到的只是为新类所 定义的接口,而非所嵌入对象的接口,一般情况下会在新类中嵌入一个现有类的private对象。而继承,它是指使用某个现有类,并开发一个它的特殊版本。“is-a”(是一个)的关系是用 继承来表达的,而“has-a”(有一个)的关系则是用组合来表达的。22.final的用法:根据惯例,既是static又是final的域将用大写表示,并使用下划线分隔各个单 词。类中所有的private方法都隐式的指定为是final的。final类中所有的方法都隐式指定为是 final的。当前用HashMap替代了Hashtable,用ArrayList替代了Vector。
Java 编程思想第四版 读书笔记
23.Java中除了static方法和final方法(private方法属于final方法)之外,其他所有的方法都是 后期绑定。接口中的属性都是public static final的,方法都是public 24.多态:只有普通的方法调用可以是多态的。任何域访问操作都将由编译器解析,因此不 是多态的。如果某个方法是静态的,它的行为也不具有多态性。25.初始化的实际过程:1)在其他任
何事物发生之前,将分配给对象的存储空间初始化成二 进制的零。2)如前所述那样调用基类构造器,此时,调用被覆盖后的方法(要在调用子类 构造器之前调用)。3)按照声明的顺序调用成员的初始化方法。4)调用导出类的构造器主 体。编写构造器时有一条有效的准则: “用尽可能简单的方法使对象进入正常状态;如果可 以的话,避免调用其他方法”。在构造器内唯一能够安全调用的那些方法是基类中的final方 法(也适用于private方法,它们自动属于final方法)。这些方法不能被覆盖。26.一条通用的准则是: “用继承表达行为间的差异,并用字段表达状态上的变化”。27.一个内部类的对象能访问其外围对象的所有成员,还拥有其外围类的所有元素的访问权。在内部类中,如果你需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点和 this,(OuterClass out = OuterClass.this)。有时你可能想要告知某些其他对象,去创建其某个 内部类的对象,可以在new表达式中提供对其他外部类对象的引用,需要使用.new语法(OuterClass out = new OuterClass, OuterClass.InnerClass inner = out.new InnerClass())。在拥 有外部类对象之前是不可能创建内部类对象的,但是,嵌套类(静态内部类)除外。如果定 义一个匿名内部类,并希望它使用一个在其外部定义的对象,那么其参数引用必须是final 的。匿名类的实例初始化的实际效果就是构造器,而且你不能重载实例初始化方法,它可以 扩展类,也可以实现接口,但是实现接口,也只能实现一个接口。28.嵌套类(静态内部类):1)要创建嵌套类的对象,并不需要其外围类的对象;2)不能从嵌 套类的对象中访问非静态的外围类对象。30.为什么需要内部类:1)每个内部类都能独立继承自一个(接口的)实现,所以无论外围 类是否已经继承了某个(接口的)实现,对于内部类都没有影响。2)内部类可以更好的实 现“多重继承”。3)内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围 类对象的信息相互独立。4)在单个外围类中,可以让多个内部类以不同的方式实现同一个 接口或继承同一个类。5)创建内部类对象的时刻并不依赖于外围类对象的创建。
6)内部类 并没有令人迷惑的“is-a”关系,它是一个独立的实体。31.闭包:它是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过内部类提供闭包的功能是优良的解决方案。使用局部内部类而不使用匿名内部类的的理 由是需要不止一个该内部类的对象,或者需要一个已命名的构造器。32.内部类的继承:内部类的构造器必须连接到指向其外围类对象的引用,必须在构造器内 使用如下语法:enclosingClassReference.super();33.容器:List、Set、Query、Map。程序中不应该使用过时的Vector,Hashtable和Stack。常用的类有:Collection,Collections,Arrays,ArrayList,LinkedList,HashSet,TreeSet,LinkedHashSet, HashMap,TreeMap,LinkedHashMap,Query,Stack,PriorityQuery 迭代器:Iteratror,ListIterator3
Java 编程思想第四版 读书笔记
34.异常:把当前异常对象重新抛出时,printStackTrace()方法显示原来异常抛出点的调用栈 信息,要想更新这个信息,可以调用fillInStackTrace()方法。如: throw(Exception)e.fillInStackTrace()。35.异常链:在捕获一个异常后抛出另一个异常,而且希望把原始异常的信息保存下来。现 在所有的Throwable的子类在构造器中都可以接受一个cause(因由)对象作为参数。这个cause 就用来表示原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并抛 出了新的异常,也能通过这个异常链追踪到异常最初发生的位置。只有三种基本异常类(Error、Exception、RuntimeException)提供了带cause参数的构造器,其他的异常只能使用 initCause()方法。36.当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常,或者抛出的异 常的子类或者不抛出异常。37.Java标准异常: Throwable这个Java类被用来表示任何可以作为异常被抛出的类。Throwable 对象可分为两种类型(指从Throwable继承而得到的类型):Error用来表示编译时和系统错误(除
特殊情况外,一般不用你关心);Exception是可以被抛出的基本类型,在Java类库、用 户方法以及运行时故障中都可能抛出Exception型异常。所以Java程序员关心的基类型通常是 Exception。38.RuntimeException:Java运行时异常,它属于Java的标准运行时检测的一部分,它会自动被 Java虚拟机抛出,它也被称为“不受检查异常”,这种异常属于错误,将被自动捕获,不用 自己进行处理。除此之外的异常需要进行声明,并进行捕获或都向上抛出。只能在代码中忽 略RuntimeException(及其子类)类型的异常,其他类型异常的处理都是由编译器强制实施 的。RuntimeException代表的编程错误:1)无法预料的错误;2)作为程序同,应该在代码 中进行检查的错误。39.catch会捕获基类异常本身以及所有从它派生的异常,如果将基类异常放在前面,子类异 常放在后面的话,子类异常永远不会被捕获,编辑器会报错。Unreachable catch block for
RuntimeException.It is already handled by the catch block for Exception。
40.异常处理的一个重要目标就是把错误处理的代码同错误发生的地点分离。应该在下列情 况下使用异常:1)在恰当的级别处理问题。(在知道该如何处理的情况下捕获异常)
2)解 决问题并且重新调用产生异常的方法。3)进行少许修被,然后绕过异常发生的地方继续执
Java 编程思想第四版 读书笔记
行。4)用别的数据进行计算,以代替方法预计会返回的值。5)把当前运行环境下能做的事 情尽量做完,然后把相同的异常重抛到更高层。6)把当前运行环境下能做的事情尽量做完,然后把不同的异常抛到更高层。7)终止程序。8)进行简化。9)让类库和程序更安全。41.字符器:String,StringBuilder,Formatter.格式化语法: %[argument_index$][flags][width][.precision]conversion 在默认的情况下,数据是右对齐,不过可以通过使用“-”标志来改变对齐方向。42.正则表达式:意思是“我要插入一个正则表达式的反斜线,所以其后的字符具有特殊的 意义”。数字的正则表达式是:d,普通反斜线:,换行:n,制表符:t。要表示“一个 或多个之前的表达式”,使用+。“零个或多个”,使用?。(-|+)?:+在正则表达式中有特殊意 义,必须使用将其转义。String类有matches()和split()方法处理正则表达式。43.我们通过java.util.regex.Pattern类和Matcher类来构造正则表达式对象。导入java.util.regex 包,用static Pattern.compile()方法来编译正则表达式生成一个Pattern对象,再将你想要检索 的字符串传入Pattern对象的matcher()方法,它会生成一个Matcher对象,最后通过操作Matcher 对象来实现相关功能。还可以通过Scanner来完成正则表达式相关功能。44.在Java中,所有的类型转换都是在运行进进行正确性检查的。通过Class.forName()获得 Class对象的引用,也可以通过Object类的getClass()方法来获得。45.使用类的准备工作:1)加载,这是由类加载器执行,该步骤将查找字节码,并从这些字 节码中创建一个Class对象。2)链接,将验证类中的字节码,为静态域分配存储空间,并且 如果必需的话,将解析这个类创建的对其他类的所有引用。3)初始化,如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。(构造器隐式地是静态的)。46.RTTI和反射之间的区别:对RTTI来说,编译器在编译时打开和检查.class文件。而对于反 射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。47.泛型中T代表类型Class,要显式地指明类型,必须在点操作符与方法名之间插入尖括号,然后把类型置于尖括号中;如果是在定义该方法的类的内部,必须在点操作符之前使用this 关键字,如果是使用static的方法,必须在点操作符之前加上类名。在泛型代码内部,无法 获得任何有关泛型参数类型的信息。在泛型中创建数组,推荐使用Array.newInstance()。泛 型(
SuperClass> 48.任何基本类型都不能作为类型参数,但是可以使用它们的包装类,如不能使用 ArrayList,但可以使用ArrayList 49.异常的分类:1)Error:称为错误,由Java虚拟机生成并抛出,包括动态链接失败、虚拟 机错误等,程序对其不做处理;2)Exception:所有异常类的父类,其子类对应了各种各样可 能出现的异常事件,一般需要用户显示的声明或捕获;
3)Runtime Exception:一类特殊的异常,如被0除、数据下标超范围等,其产生比较频繁,处理麻烦,如果显式的声明或捕获将会对 程序可读性和运行效率影响很大。因此由系统自动检测并将它们交给缺省的异常处理程序(用户可不必对其处理)。50.使用自定义异常一般步骤:1)通过继承java.lang.Exception类声明自己的异常类;2)在 方法适当的位置生成自定义异常的实例,并用throw语句抛出; 在方法的声明部分用throws 3)语句声明该方法可能抛出的异常。
二 专项复习
1.容器 2.正则表达式
Java 编程思想第四版 读书笔记
3.设计模式 4.异常 5.泛型 6.反射 7.多线程 8.IO
常用包(加粗是抽象类,斜体是接口,普通是类)三 J2SE 常用包(加粗是抽象类,斜体是接口,普通是类)
1.java.lang 提供利用 Java 编程语言进行程序设计的基础类。Process、ProcessBuilder、Runtime、System、String、Object、Class、ClassLoader、Math、Compiler、Thread、ThreadGroup、Runnable、ThreadLocal、InheritableThreadLocal、Package 2.java.util ArrayList、Arrays、Collection、Collections、LinkedList、HashSet、TreeSet、Iterator、ListIterator、Map、HashMap、TreeMap、Comparator 历史遗留的类:Dictionary、Hashtable、Properties、Stack、Vector、Enumeration 使用迭代函数的步骤:1)通过调用类集的iterator()或listIterator()方法获得对类集头的迭代函 数;2)建立一个调用hasNext()方法的循环,只要hasNext()返回true,就进行循环迭代;3)在循环内部,通过调用next()方法来得到每一个元素。GregorianCalendar、TimeZone、SimpleTimeZone、Locale、DateFormat、BitSet、Calendar、SimpleDateFormat、Random、Observer、Observable、Timer、TimerTask GregorianCalendar定义了两个域:AD和BC。它们代表由公历定义的两个纪元。BC公元前,AD公元后 3.java.io InputStream、OutputStream Reader、Writer OutputStream、Reader Writer、FileInputStream、FileOutputStream、InputStream OutputStream Reader Writer ByteArrayInputStream、ByteArrayOutputStream、FilterOutputStream、FilterInputStream、BufferedInputStream、BufferedOutputStream、SequenceInputStream、PrintStream、RandomAccessFile FileReader、FileWriter、CharArrayReader、CharArrayWriter、BufferedReader、BufferedWriter、PrintWriter ObjectInputStream、ObjectOutputStream序列化的类必须实现java.io.Serializable 或 java.io.Externalizable 接口的对象才能从流读取。4.java.net InetAddress、URL、URLConnection Java中有两类TCP套接字。一种是服务器端的,另一种是客户端的。ServerSocket类设 计成在等待客户建立连接之前不做任何事的“监听器”。Socket类为建立连向服务器套接 字以及启动协议交换而设计。或 index.htm 文件。所以,http:/// 与http:///index.htm 是相同 的。Java通过两个类实现UDP协议顶层的数据报:DatagramPacket对象是数据容器,DatagramSocket是用来发送和接受DatagramPackets的机制。5.java.rmi
第四篇:JAVA编程心得体会
JAVA编程心得
计算机3班
窦金霞
20104773
最近几周一直在弄程序,说实话真的很累,但累中也有成功的快乐。我觉得学到了很多东西,这是只看课本知识所不能学到的。
说实话,以前我一直没学过JAVA虽然我也知道JAVA的重要性,可是即使上课听了,不实践还是掌握不了。因为种种原因,今年我没有买笔记本。没有机器,仅仅靠每周一次的上机练习是绝对不够的。所以我就插空调程序,在舍友们不用的时候自己再接她们的电脑调。
调上一个WEB版的通讯录程序时我已经感觉到学的很吃力,好多东西都不懂。这次做的这个学生成绩管理系统更复杂了,所以一开始调的时候感觉特别吃力.所以我告诉自己不能放弃,慢慢来,就这样我从最基本的sql语句session对象开始学起,我觉得我还有太多不懂得所以要比别人付出更多的努力。就这样我一点一点的学着„„
说心里话,在做上一个web版的通讯录时,我就感觉到成功的喜悦。好多地方我都是一点一点的问的,在问的过程中,我也学会了很多,像:Servlet和jsp之间跳不过去时有两种解决办法,一是关闭底层类中的db.close;二是将Servlet中的throws Exception改成try catch以捕捉异常;我还学到了集中查找错误的方法,可以加上两个双斜杠“//”将具体的方法屏蔽掉,一检查是方法错误还是Servlet错误,还有就是写上System.out.println()将获得的数据输出,用来检查数据传输过程有没有错误等等。
虽然在别人看来,这些方法可能都很常规,但是确实我自己学会的,我觉得很有成就感。我已经做好计划了,暑假的时候去买本本用自己的本本练习一下JAVA,虽然下学期不学JAVA了,但是我对JAVA的热情不会因为这个而削减的!
做完这个学生成绩管理系统后,我觉得我对JAVA的看法已经改变了。一前总以为JAVA很繁琐很难,听同学说JAVA不好学,开始又有一些听不懂,所以一直很畏惧JAVA。但真正做了这个系统以后我才感觉到其实任何事都没有难与不难之分,只要你肯努力的去做,世上无难事只怕有心人!
我现在对java学习充满了热情,我知道我还有很多的不足
还有很多需要努力的地方,所以我的JAVA之旅将继续进行„„
第五篇:Java编程思想重点笔记(Java开发必看)
Java编程思想,Java学习必读经典,不管是初学者还是大牛都值得一读,这里总结书中的重点知识,这些知识不仅经常出现在各大知名公司的笔试面试过程中,而且在大型项目开发中也是常用的知识,既有简单的概念理解题(比如is-a关系和has-a关系的区别),也有深入的涉及RTTI和JVM底层反编译知识。
1.Java中的多态性理解(注意与C++区分)
Java中除了static方法和final方法(private方法本质上属于final方法,因为不能被子类访问)之外,其它所有的方法都是动态绑定,这意味着通常情况下,我们不必判定是否应该进行动态绑定—它会自动发生。 final方法会使编译器生成更有效的代码,这也是为什么说声明为final方法能在一定程度上提高性能(效果不明显)。
如果某个方法是静态的,它的行为就不具有多态性:
class StaticSuper {
public static String staticGet(){
return “Base staticGet()”;
}
public String dynamicGet(){
return “Base dynamicGet()”;
} }
class StaticSub extends StaticSuper {
public static String staticGet(){
return “Derived staticGet()”;
}
public String dynamicGet(){
return “Derived dynamicGet()”;
} }
public class StaticPolymorphism {
public static void main(String[] args){ StaticSuper sup = new StaticSub();System.out.println(sup.staticGet());System.out.println(sup.dynamicGet());
}
}
输出:
Base staticGet()Derived dynamicGet() 构造函数并不具有多态性,它们实际上是static方法,只不过该static声明是隐式的。因此,构造函数不能够被override。
在父类构造函数内部调用具有多态行为的函数将导致无法预测的结果,因为此时子类对象还没初始化,此时调用子类方法不会得到我们想要的结果。
class Glyph {
void draw(){
System.out.println(“Glyph.draw()”);
}
Glyph(){
System.out.println(“Glyph()before draw()”);draw();
System.out.println(“Glyph()after draw()”);
} }
class RoundGlyph extends Glyph {
private int radius = 1;
RoundGlyph(int r){ radius = r;
System.out.println(“RoundGlyph.RoundGlyph().radius = ” + radius);
}
void draw(){
System.out.println(“RoundGlyph.draw().radius = ” + radius);
} }
public class PolyConstructors {
public static void main(String[] args){
new RoundGlyph(5);
}
}
输出: Glyph()before draw()RoundGlyph.draw().radius = 0 Glyph()after draw()RoundGlyph.RoundGlyph().radius = 5 为什么会这样输出?这就要明确掌握Java中构造函数的调用顺序:
(1)在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制0;
(2)调用基类构造函数。从根开始递归下去,因为多态性此时调用子类覆盖后的draw()方法(要在调用RoundGlyph构造函数之前调用),由于步骤1的缘故,我们此时会发现radius的值为0;
(3)按声明顺序调用成员的初始化方法;(4)最后调用子类的构造函数。
只有非private方法才可以被覆盖,但是还需要密切注意覆盖private方法的现象,这时虽然编译器不会报错,但是也不会按照我们所期望的来执行,即覆盖private方法对子类来说是一个新的方法而非重载方法。因此,在子类中,新方法名最好不要与基类的private方法采取同一名字(虽然没关系,但容易误解,以为能够覆盖基类的private方法)。
Java类中属性域的访问操作都由编译器解析,因此不是多态的。父类和子类的同名属性都会分配不同的存储空间,如下:
// Direct field access is determined at compile time.class Super {
public int field = 0;
public int getField(){
return field;
} }
class Sub extends Super {
public int field = 1;
public int getField(){
return field;
}
public int getSuperField(){
return super.field;
} }
public class FieldAccess {
public static void main(String[] args){ Super sup = new Sub();
System.out.println(“sup.filed = ” + sup.field +
“, sup.getField()= ” + sup.getField());Sub sub = new Sub();
System.out.println(“sub.filed = ” + sub.field + “, sub.getField()= ” + sub.getField()+ “, sub.getSuperField()= ” + sub.getSuperField());
}
}
输出:
sup.filed = 0, sup.getField()= 1 sub.filed = 1, sub.getField()= 1, sub.getSuperField()= 0 Sub子类实际上包含了两个称为field的域,然而在引用Sub中的field时所产生的默认域并非Super版本的field域,因此为了得到Super.field,必须显式地指明super.field。
2.is-a关系和is-like-a关系
is-a关系属于纯继承,即只有在基类中已经建立的方法才可以在子类中被覆盖,如下图所示:
基类和子类有着完全相同的接口,这样向上转型时永远不需要知道正在处理的对象的确切类型,这通过多态来实现。
is-like-a关系:子类扩展了基类接口。它有着相同的基本接口,但是他还具有由额外方法实现的其他特性。
缺点就是子类中接口的扩展部分不能被基类访问,因此一旦向上转型,就不能调用那些新方法。
3.运行时类型信息(RTTI + 反射)
概念
RTTI:运行时类型信息使得你可以在程序运行时发现和使用类型信息。 使用方式
Java是如何让我们在运行时识别对象和类的信息的,主要有两种方式(还有辅助的第三种方式,见下描述): 一种是“传统的”RTTI,它假定我们在编译时已经知道了所有的类型,比如Shape s =(Shape)s1; 另一种是“反射”机制,它运行我们在运行时发现和使用类的信息,即使用Class.forName()。
其实还有第三种形式,就是关键字instanceof,它返回一个bool值,它保持了类型的概念,它指的是“你是这个类吗?或者你是这个类的派生类吗?”。而如果用==或equals比较实际的Class对象,就没有考虑继承—它或者是这个确切的类型,或者不是。
工作原理
要理解RTTI在Java中的工作原理,首先必须知道类型信息在运行时是如何表示的,这项工作是由称为Class对象的特殊对象完成的,它包含了与类有关的信息。Java送Class对象来执行其RTTI,使用类加载器的子系统实现。
无论何时,只要你想在运行时使用类型信息,就必须首先获得对恰当的Class对象的引用,获取方式有三种:
(1)如果你没有持有该类型的对象,则Class.forName()就是实现此功能的便捷途,因为它不需要对象信息;
(2)如果你已经拥有了一个感兴趣的类型的对象,那就可以通过调用getClass()方法来获取Class引用了,它将返回表示该对象的实际类型的Class引用。Class包含很有有用的方法,比如: package rtti;
interface HasBatteries{} interface WaterProof{} interface Shoots{}
class Toy { Toy(){}
Toy(int i){} }
class FancyToy extends Toy implements HasBatteries, WaterProof, Shoots { FancyToy(){
super(1);
} }
public class RTTITest {
static void printInfo(Class cc){
System.out.println(“Class name: ” + cc.getName()+ “, is interface? [” + cc.isInterface()+ “]”);System.out.println(“Simple name: ” + cc.getSimpleName());System.out.println(“Canonical name: ” + cc.getCanonicalName());
}
public static void main(String[] args){ Class c = null;
try {
c = Class.forName(“rtti.FancyToy”);// 必须是全限定名(包名+类名)
} catch(ClassNotFoundException e){
System.out.println(“Can't find FancyToy”);System.exit(1);
}
printInfo(c);
for(Class face : c.getInterfaces()){ printInfo(face);
}
Class up = c.getSuperclass();Object obj = null;
try { // Requires default constructor.obj = up.newInstance();
} catch(InstantiationException e){
System.out.println(“Can't Instantiate”);System.exit(1);
} catch(IllegalAccessException e){ System.out.println(“Can't access”);System.exit(1);
}
printInfo(obj.getClass());
}
}
输出:
Class name: rtti.FancyToy, is interface? [false] Simple name: FancyToy Canonical name: rtti.FancyToy Class name: rtti.HasBatteries, is interface? [true] Simple name: HasBatteries Canonical name: rtti.HasBatteries Class name: rtti.WaterProof, is interface? [true] Simple name: WaterProof Canonical name: rtti.WaterProof Class name: rtti.Shoots, is interface? [true] Simple name: Shoots Canonical name: rtti.Shoots Class name: rtti.Toy, is interface? [false] Simple name: Toy Canonical name: rtti.Toy(3)Java还提供了另一种方法来生成对Class对象的引用,即使用类字面常量。比如上面的就像这样:FancyToy.class;来引用。
这样做不仅更简单,而且更安全,因为它在编译时就会受到检查(因此不需要置于try语句块中),并且它根除了对forName方法的引用,所以也更高效。类字面常量不仅可以应用于普通的类,也可以应用于接口、数组以及基本数据类型。
注意:当使用“.class”来创建对Class对象的引用时,不会自动地初始化该Class对象,初始化被延迟到了对静态方法(构造器隐式的是静态的)或者非final静态域(注意final静态域不会触发初始化操作)进行首次引用时才执行:。而使用Class.forName时会自动的初始化。
为了使用类而做的准备工作实际包含三个步骤:
-加载:由类加载器执行。查找字节码,并从这些字节码中创建一个Class对象
-链接:验证类中的字节码,为静态域分配存储空间,并且如果必需的话,将解析这个类创建的对其他类的所有引用。
-初始化:如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。这一点非常重要,下面通过一个实例来说明这两者的区别:
package rtti;
import java.util.Random;class Initable {
static final int staticFinal = 47;
static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
static {
System.out.println(“Initializing Initable”);
} }
class Initable2 {
static int staticNonFinal = 147;
static {
System.out.println(“Initializing Initable2”);
} }
class Initable3 {
static int staticNonFinal = 74;
static {
System.out.println(“Initializing Initable3”);
} }
public class ClassInitialization {
public static Random rand = new Random(47);
public static void main(String[] args){
// Does not trigger initialization Class initable = Initable.class;
System.out.println(“After creating Initable ref”);
// Does not trigger initialization
System.out.println(Initable.staticFinal);
// Does trigger initialization(rand()is static method)System.out.println(Initable.staticFinal2);
// Does trigger initialization(not final)
System.out.println(Initable2.staticNonFinal);
try {
Class initable3 = Class.forName(“rtti.Initable3”);
} catch(ClassNotFoundException e){
System.out.println(“Can't find Initable3”);System.exit(1);
}
System.out.println(“After creating Initable3 ref”);System.out.println(Initable3.staticNonFinal);
} }
输出:
After creating Initable ref 47 Initializing Initable 258 Initializing Initable2 147 Initializing Initable3 After creating Initable3 ref 74
RTTI的限制?如何突破? — 反射机制
如果不知道某个对象的确切类型,RTTI可以告诉你,但是有一个限制:这个类型在编译时必须已知,这样才能使用RTTI识别它,也就是在编译时,编译器必须知道所有要通过RTTI来处理的类。
可以突破这个限制吗?是的,突破它的就是反射机制。
Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Field、Method以及Constructor类(每个类都实现了Member接口)。这些类型的对
象是由JVM在运行时创建的,用以表示未知类里对应的成员。这样你就可以使用Constructor创建新的对象,用get()/set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。另外,还可以调用getFields()、getMethods()和getConstructors()等很便利的方法,以返回表示字段、方法以及构造器的对象的数组。这样,匿名对象的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情。
####反射与RTTI的区别
当通过反射与一个未知类型的对象打交道时,JVM只是简单地检查这个对象,看它属于哪个特定的类(就像RTTI那样),在用它做其他事情之前必须先加载那个类的Class对象,因此,那个类的.class文件对于JVM来说必须是可获取的:要么在本地机器上,要么可以通过网络取得。所以RTTI与反射之间真正的区别只在于:对RTTI来说,编译器在编译时打开和检查.class文件(也就是可以用普通方法调用对象的所有方法);而对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。下面的例子是用反射机制打印出一个类的所有方法(包括在基类中定义的方法):
package typeinfo;
import java.lang.reflect.Constructor;import java.lang.reflect.Method;import java.util.regex.Pattern;
// Using reflection to show all the methods of a class.// even if the methods are defined in the base class.public class ShowMethods { private static String usage =
“usage: n” +
“ShowMethods qualified.class.namen” +
“To show all methods in class or: n” +
“ShowMethods qualified.class.name wordn” +
“To search for methods involving 'word'”;
private static Pattern p = Pattern.compile(“w+.”);
public static void main(String[] args){ if(args.length < 1){ System.out.println(usage);System.exit(0);} int lines = 0;try { Class> c = Class.forName(args[0]);Method[] methods = c.getMethods();Constructor[] ctors = c.getConstructors();if(args.length == 1){ for(Method method : methods){
System.out.println(p.matcher(method.toString()).replaceAll(“"));} for(Constructor ctor : ctors){
System.out.println(p.matcher(ctor.toString()).replaceAll(”“));} lines = methods.length + ctors.length;} else { for(Method method : methods){ if(method.toString().indexOf(args[1])!=-1){
System.out.println(p.matcher(method.toString()).replaceAll(”“));lines++;} } for(Constructor ctor : ctors){ if(ctor.toString().indexOf(args[1])!=-1){
System.out.println(p.matcher(ctor.toString()).replaceAll(”“));lines++;} } } } catch(ClassNotFoundException e){ System.out.println(”No such Class: “ + e);}
} }
输出:
public static void main(String[])public final native void wait(long)throws InterruptedException public final void wait()throws InterruptedException public final void wait(long,int)throws InterruptedException public boolean equals(Object)public String toString()public native int hashCode()public final native Class getClass()public final native void notify()public final native void notifyAll()public ShowMethods()4.代理模式与Java中的动态代理
代理模式
在任何时刻,只要你想要将额外的操作从“实际”对象中分离到不同的地方,特别是当你希望能够很容易地做出修改,从没有使用额外操作转为使用这些操作,或者反过来时,代理就显得很有用(设计模式的关键是封装修改)。例如,如果你希望跟踪对某个类中方法的调用,或者希望度量这些调用的开销,那么你应该怎样做呢?这些代码肯定是你不希望将其合并到应用中的代码,因此代理使得你可以很容易地添加或移除它们。
interface Interface {
void doSomething();
void somethingElse(String arg); }
class RealObject implements Interface {
@Override public void doSomething(){
System.out.println(”doSomething.“);
}
@Override public void somethingElse(String arg){
System.out.println(”somethingElse “ + arg);
} }
class SimpleProxy implements Interface {
private Interface proxy;
public SimpleProxy(Interface proxy){
this.proxy = proxy;
}
@Override public void doSomething(){
System.out.println(”SimpleProxy doSomething.“);proxy.doSomething();
}
@Override public void somethingElse(String arg){
System.out.println(”SimpleProxy somethingElse “ + arg);
proxy.somethingElse(arg);
} }
public class SimpleProxyDemo {
public static void consumer(Interface iface){ iface.doSomething();
iface.somethingElse(”bonobo“);
} public static void main(String[] args){ consumer(new RealObject());
consumer(new SimpleProxy(new RealObject()));
}
}
输出:
doSomething.somethingElse bonobo SimpleProxy doSomething.doSomething.SimpleProxy somethingElse bonobo somethingElse bonobo 动态代理
Java的动态代理比代理的思想更向前迈进了一步,因为它可以动态地创建代理并动态地处理对所代理方法的调用。
import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;
class DynamicProxyHandler implements InvocationHandler {
private Object proxy;
public DynamicProxyHandler(Object proxy){
this.proxy = proxy;
}
@Override public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println(”*** proxy: “ + proxy.getClass()+
”.method: “ + method + ”.args: “ + args);
if(args!= null){
for(Object arg : args)
System.out.println(” “ + arg);
}
return method.invoke(this.proxy, args);
} }
public class SimpleDynamicProxy {
public static void consumer(Interface iface){ iface.doSomething();
iface.somethingElse(”bonobo“);
}
public static void main(String[] args){ RealObject real = new RealObject();consumer(real);
// insert a proxy and call again:
Interface proxy =(Interface)Proxy.newProxyInstance(Interface.class.getClassLoader(), new Class[]{ Interface.class },new DynamicProxyHandler(real));
consumer(proxy);
}
}
输出:
doSomething.somethingElse bonobo *** proxy: class typeinfo.$Proxy0.method: public abstract void typeinfo.Interface.doSomething().args: null doSomething.*** proxy: class typeinfo.$Proxy0.method: public abstract void typeinfo.Interface.somethingElse(java.lang.String).args: [Ljava.lang.Object;@6a8814e9 bonobo somethingElse bonobo 5.即时编译器技术 — JIT Java虚拟机中有许多附加技术用以提升速度,尤其是与加载器操作相关的,被称为“即时”(Just-In-Time,JIT)编译器的技术。这种技术可以把程序全部或部分翻译成本地机器码(这本来是JVM的工作),程序运行速度因此得以提升。当需要装载某个类时,编译器会先找到其.class文件,然后将该类的字节码装入内存。此时,有两种方案可供选择:
(1)一种就是让即时编译器编译所有代码。但这种做法有两个缺陷:这种加载动作散落在整个程序生命周期内,累加起来要花更多时间;并且会增加可执行代码的长度(字节码要比即时编译器展开后的本地机器码小很多),这将导致页面调度,从而降低程序速度。(2)另一种做法称为惰性评估(lazy evaluation),意思是即时编译器只在必要的时候才编译代码,这样,从不会被执行的代码也许就压根不会被JIT所编译。新版JDK中的Java HotSpot技术就采用了类似方法,代码每次被执行的时候都会做一些优化,所以执行的次数越多,它的速度就越快。
6.访问控制权限 Java访问权限修饰词:public、protected、包访问权限(默认访问权限,有时也称friendly)和private。
包访问权限:当前包中的所有其他类对那个成员具有访问权限,但对于这个包之外的所有类,这个成员却是private。
protected:继承访问权限。有时基类的创建者会希望有某个特定成员,把对它的访问权限赋予派生类而不是所有类。这就需要protected来完成这一工作。protected也提供包访问权限,也就是说,相同包内的其他类都可以访问protected元素。protected指明“就类用户而言,这是private的,但对于任何继承于此类的导出类或其他任何位于同一个包内的类来说,它却是可以访问的”。比如: 基类:
package access.cookie;public class Cookie {
public Cookie(){
System.out.println(”Cookie Constructor“);
}
void bite(){ // 包访问权限,其它包即使是子类也不能访问它 System.out.println(”bite“);
} }
子类:
package access.dessert;
import access.cookie.Cookie;
public class ChocolateChip extends Cookie {
public ChocolateChip(){
System.out.println(”ChocolateChip constructor“);
}
public void chomp(){
bite();// error, the method bite()from the type Cookie is not visible
} }
可以发现子类并不能访问基类的包访问权限方法。此时可以将Cookie中的bite指定为public,但这样做所有的人就都有了访问权限,为了只允许子类访问,可以将bite指定为protected即可。
7.组合和继承之间的选择
组合和继承都允许在新的类中放置子对象,组合是显式的这样做,而继承则是隐式的做。
组合技术通常用于想在新类中使用现有类的功能而非它的接口这种情形。即在新类中嵌入某个对象,让其实现所需要的功能,但新类的用户看到的只是为新类所定义的接口,而非所嵌入对象的接口。为取得此效果,需要在新类中嵌入一个现有类的private对象。但有时,允许类的用户直接访问新类中的组合成分是极具意义的,即将成员对象声明为public。如果成员对象自身都隐藏了具体实现,那么这种做法是安全的。当用户能够了解到你正在组装一组部件时,会使得端口更加易于理解。比如Car对象可由public的Engine对象、Wheel对象、Window对象和Door对象组合。但务必要记得这仅仅是一个特例,一般情况下应该使域成为private。在继承的时候,使用某个现有类,并开发一个它的特殊版本。通常,这意味着你在使用一个通用类,并为了某种特殊需要而将其特殊化。稍微思考一下就会发现,用一个“交通工具”对象来构成一部“车子”是毫无意义的,因为“车子”并不包含“交通工具”,它仅是一种交通工具(is-a关系)。
“is-a”(是一个)的关系是用继承来表达的,而“has-a”(有一个)的关系则是用组合来表达的。
到底是该用组合还是继承,一个最清晰的判断方法就是问一问自己是否需要从新类向基类进行向上转型,需要的话就用继承,不需要的话就用组合方式。
8.final关键字
对final关键字的误解
当final修饰的是基本数据类型时,它指的是数值恒定不变(就是编译期常量,如果是static final修饰,则强调只有一份),而对对象引用而不是基本类型运用final时,其含义会有一点令人迷惑,因为用于对象引用时,final使引用恒定不变,一旦引用被初始化指向一个对象,就无法再把它指向另一个对象。然而,对象其自身却是可以被修改的,Java并未提供使任何对象恒定不变的途径(但可以自己编写类以取得使对象恒定不变的效果),这一限制同样适用数组,它也是对象。 使用final方法真的可以提高程序效率吗?
将一个方法设成final后,编译器就可以把对那个方法的所有调用都置入“嵌入”调用里。只要编译器发现一个final方法调用,就会(根据它自己的判断)忽略为执行方法调用机制而采取的常规代码插入方法(将自变量压入堆栈;跳至方法代码并执行它;跳回来;清除堆栈自变量;最后对返回值进行处理)。相反,它会用方法主体内实际代码的一个副本来替换方法调用。这样做可避免方法调用时的系统开销。当然,若方法体积太大,那么程序也会变得雍肿,可能受到到不到嵌入代码所带来的任何性能提升。因为任何提升都被花在方法内部的时间抵消了。
在最近的Java版本中,虚拟机(特别是hotspot技术)能自动侦测这些情况,并颇为“明智”地决定是否嵌入一个final 方法。然而,最好还是不要完全相信编译器能正确地作出所有判断。通常,只有在方法的代码量非常少,或者想明确禁止方法被覆盖的时候,才应考虑将一个方法设为final。
类内所有private 方法都自动成为final。由于我们不能访问一个private 方法,所以它绝对不会被其他方法覆盖(若强行这样做,编译器会给出错误提示)。可为一个private方法添加final指示符,但却不能为那个方法提供任何额外的含义。
9.策略设计模式与适配器模式的区别
策略设计模式
创建一个能够根据所传递的参数对象的不同而具有不同行为的方法,被称为策略设 计模式,这类方法包含所要执行的算法中固定不变的部分,而“策略”包含变化的部分。策略就是传递进去的参数对象,它包含要执行的代码。适配器模式
在你无法修改你想要使用的类时,可以使用适配器模式,适配器中的代码将接受你所拥有的接口,并产生你所需要的接口。
内部类与组合是完全不同的概念,这一点很重要。
为什么需要内部类? — 主要是解决了多继承的问题,继承具体或抽象类 10.内部类
一般来说,内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外围类的对象。所以可以认为内部类提供了某种进入其外围类的窗口。内部类最吸引人的原因是:每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
如果没有内部类提供的、可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。从这个角度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效的实现了“多重继承”。也就是说,内部类允许继承多个非接口类型。
考虑这样一种情形:如果必须在一个类中以某种方式实现两个接口。由于接口的灵活性,你有两种选择:使用单一类或者使用内部类。但如果拥有的是抽象的类或具体的类,而不是接口,那就只能使用内部类才能实现多重继承。
使用内部类,还可以获得其他一些特性:创建内部类对象的时刻并不依赖于外围类对象的创建。
-内部类并没有令人迷惑的is-a关系,它就是一个独立的实体。
11.String类型 — 不可变
用于String的“+”与“+=”是Java中仅有的两个重载过的操作符,而Java并不允许程序员重载任何操作符。
考虑到效率因素,编译器会对String的多次+操作进行优化,优化使用StringBuilder操作(javap-c class字节码文件名 命令查看具体优化过程)。这让你觉得可以随意使用String对象,反正编译器会为你自动地优化性能。但编译器能优化到什么程度还不好说,不一定能优化到使用StringBuilder代替String相同的效果。比如:
public class WitherStringBuilder {
public String implicit(String[] fields){ String result = ”“;
for(int i = 0;i < fields.length;i++)result += fields[i];
return result;
}
public String explicit(String[] fields){
StringBuilder result = new StringBuilder();
for(int i = 0;i < fields.length;i++)result.append(fields[i]);
return result.toString();
} }
运行javap-c WitherStringBuilder,可以看到两个方法对应的字节码。implicit方法:
public java.lang.String implicit(java.lang.String[]);Code: 0: ldc #16 // String 2: astore_2 3: iconst_0 4: istore_3 5: goto 32 8: new #18 // class java/lang/StringBuilder 11: dup 12: aload_2 13: invokestatic #20 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;16: invokespecial #26 // Method java/lang/StringBuilder.”< init>”:(Ljava/lang/String;)V 19: aload_1 20: iload_3 21: aaload 22: invokevirtual #29 // Method java/lang/StringBuilder.ap pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;25: invokevirtual #33 // Method java/lang/StringBuilder.to String:()Ljava/lang/String;28: astore_2 29: iinc 3, 1 32: iload_3 33: aload_1 34: arraylength 35: if_icmplt 8 38: aload_2 39: areturn public java.lang.String implicit(java.lang.String[]);Code: 0: ldc #16 // String 2: astore_2 3: iconst_0 4: istore_3 5: goto 32 8: new #18 // class java/lang/StringBuilder 11: dup 12: aload_2 13: invokestatic #20 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;16: invokespecial #26 // Method java/lang/StringBuilder.”< init>”:(Ljava/lang/String;)V 19: aload_1 20: iload_3 21: aaload 22: invokevirtual #29 // Method java/lang/StringBuilder.ap pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;25: invokevirtual #33 // Method java/lang/StringBuilder.to String:()Ljava/lang/String;28: astore_2 29: iinc 3, 1 32: iload_3 33: aload_1 34: arraylength 35: if_icmplt 8 38: aload_2 39: areturn 可以发现,StringBuilder是在循环之内构造的,这意味着每经过循环一次,就会创建一个新的StringBuilder对象。
explicit方法:
public java.lang.String explicit(java.lang.String[]);Code: 0: new #18 // class java/lang/StringBuilder 3: dup 4: invokespecial #45 // Method java/lang/StringBuilder.”< init>”:()V 7: astore_2 8: iconst_0 9: istore_3 10: goto 24 13: aload_2 14: aload_1 15: iload_3 16: aaload 17: invokevirtual #29 // Method java/lang/StringBuilder.ap pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;20: pop 21: iinc 3, 1 24: iload_3 25: aload_1 26: arraylength 27: if_icmplt 13 30: aload_2 31: invokevirtual #33 // Method java/lang/StringBuilder.to String:()Ljava/lang/String;34: areturn } 可以看到,不仅循环部分的代码更简短、更简单,而且它只生成了一个StringBuilder对象。显式的创建StringBuilder还允许你预先为其指定大小。如果你已经知道最终的字符串大概有多长,那预先指定StringBuilder的大小可以避免多次重新分配缓冲。
####总结
因此,当你为一个类重写toString()方法时,如果字符串操作比较简单,那就可以信赖编译器,它会为你合理地构造最终的字符串结果。但是,如果你要在toString()方法中使用循环,那么最好自己创建一个StringBuilder对象,用它来构造最终的结果。
System.out.printf()和System.out.format()方法模仿自C的printf,可以格式化字符串,两者是完全等价的。
Java中,所有新的格式化功能都由java.util.Formatter类处理。
String.format()方法参考了C中的sprintf()方法,以生成格式化的String对象,是一个static方法,它接受与Formatter.format()方法一样的参数,但返回一个String对象。当你只需使用format()方法一次的时候,该方法很方便。
import java.util.Arrays;import java.util.Formatter;
public class SimpleFormat {
public static void main(String[] args){
int x = 5;
double y = 5.324667;
System.out.printf(”Row 1: [%d %f]n“, x, y);System.out.format(”Row 1: [%d %f]n“, x, y);
Formatter f = new Formatter(System.out);f.format(”Row 1: [%d %f]n“, x, y);
String str = String.format(”Row 1: [%d %f]n", x, y);System.out.println(str);Integer[][] a = {
{1, 2, 3}, {4, 5, 6},{7, 8, 3}, {9, 10, 6}
};
System.out.println(Arrays.deepToString(a));
} }
12.序列化控制
当我们对序列化进行控制时,可能某个特定子对象不想让Java序列化机制自动保存与恢复。如果子对象表示的是我们不希望将其序列化的敏感信息(如密码),通常会面临这种情况。即使对象中的这些信息是private属性,一经序列化处理,人们就可以通过读取文件或者拦截网络传输的方式来访问到它。有两种办法可以防止对象的敏感部分被序列化:
实现Externalizable代替实现Serializable接口来对序列化过程进行控制,Externalizable继承了Serializable接口,同时增添了两个方法:writeExternal()和readExternal()。
两者在反序列化时的区别:
-对Serializable对象反序列化时,由于Serializable对象完全以它存储的二进制位为基础来构造,因此并不会调用任何构造函数,因此Serializable类无需默认构造函数,但是当Serializable类的父类没有实现Serializable接口时,反序列化过程会调用父类的默认构造函数,因此该父类必需有默认构造函数,否则会抛异常。-对Externalizable对象反序列化时,会先调用类的不带参数的构造方法,这是有别于默认反序列方式的。如果把类的不带参数的构造方法删除,或者把该构造方法的访问权限设置为private、默认或protected级别,会抛出java.io.InvalidException: no valid constructor异常,因此Externalizable对象必须有默认构造函数,而且必需是public的。
-Externalizable的替代方法:如果不是特别坚持实现Externalizable接口,那么还有另一种方法。我们可以实现Serializable接口,并添加writeObject()和readObject()的方法。一旦对象被序列化或者重新装配,就会分别调用那两个方法。也就是说,只要提供了这两个方法,就会优先使用它们,而不考虑默认的序列化机制。
这些方法必须含有下列准确的签名:
private void writeObject(ObjectOutputStream stream)throws IOException;
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException
-可以用transient关键字逐个字段地关闭序列化,它的意思是“不用麻烦你保存或恢复数据—我自己会处理的”。由于Externalizable对象在默认情况下不保存它们的任何字段,所以transient关键字只能和Serializable对象一起使用。