`
liufei.fir
  • 浏览: 687527 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

java编程思想

阅读更多
Chap1 对象简介

       1.抽象的过程

       Alan Kay总结了Smalltalk的五项基本特征。这些特征代表了纯的面向对象的编程方法:

       (1).万物皆对象。将对象想成一种特殊的变量;它存储数据,而且还可以让你“提要求”,命令它进行某些操作。从理论上讲,你可以把所有待解决的问题中的概念性组件(狗,建筑,服务等)都标识成程序里的对象。

       (2).程序就是一组相互之间传递消息的对象。你只要向那个对象“发一个消息”,就能向它提出要求。更确切的说,你可以这样认为,消息是调用专属某个对象的方法的请求。

       (3).每个对象都利用别的对象来组建它自己的记忆。换言之,你通过将已有的对象打成一个包,来创建新的对象。由此,你可以将程序的复杂性,隐藏在对象的简单性之下。

       (4).对象都有类型。任何对象都是某个类的实例(instance of a class)。用以区分类的最突出的特点就是“你能传给它什么消息?”

       (5).所有属于同一类型的对象能接受相同的消息。这种互换性(substitutability)是OOP最强大的功能之一。

       Booch还给对象下了个更为简洁的定义:

       对象有状态,行为和标识。

       这就是说,对象可以有内部数据(状态),有方法(因而产生了行为),以及每个对象都能同其它对象区分开来--具体而言,每个对象在内存里都有唯一的地址。

       这句话或许有点太过了。因为对象还能存在于另一台及其上以及不同的内存空间中,此外还能保存在硬盘上。在这种情况下,对象的身份就不能用内存地址,而必须要用别的方法来确定。

     

       2.可凭借多态性相互替换的对象

       非OOP的编译器的做法称为前绑定(early binding)。编译器会产生那个名字的函数的调用,而连接器负责将这个调用解析成须执行的代码的绝对地址。在OOP中,不到运行的时候,程序没法确定代码的地址,所以向泛型对象发送一个消息的时候,就要用到一些特别的手段。

       OOP语言用了后绑定(late binding)的概念。当你向某个对象送了一个消息后,不到运行时,系统不能确定到底该调用哪段代码。编译器只保证这个方法存在,并且检查参数和返回值的类型(不这么做的语言属于弱类型weakly typed),但是它并不知道具体执行的是哪段代码。

       在有些语言中,你必须明确申明,某个方法要用到后绑定的灵活性(C++用virtual关键字)。在这些语言中,方法不是默认地动态绑定的。而动态绑定是 Java的缺省行为,因此无需添加什么额外的关键词就能获得多态性。

       将派生类当作它的基类来用的过程称为上传(upcast),反之称为下传(downcast)。下传所需的运行时检查会引起程序运行效率的降低,也加重了编程的负担。解决方案就是参数化类型(parameterized type)机制,即泛型。

     

       3.Collection和迭代器

       ArrayList和LinkedList,都是简单的线性序列,具有相同的接口和外部行为。对于ArrayList,随机访问是一种时间恒定的操作。然而对于LinkedList,随机访问和选取元素的代价会很大。另一方面,如果要在序列中插入元素,LinkedList的效率会比ArrayList的高出许多。

=============

Chap2 万物皆对象

       1.数据存在哪里

       数据可以存储在以下六个地方:

       (1).寄存器(registers)。这是反应最快的存储,因为它处在CPU里。但寄存器数量有限,由编译器分配,你不能直接控制。

       (2).栈(stack)。位于常规内存区里,CPU可以通过栈指针对它进行直接访问。栈指针下移就创建新的存储空间,上移就释放内存空间。这是仅次于寄存器的最快、最有效率的分配内存的方法。由于Java编译器必须生成能控制栈指针上下移的代码,所以程序编译的时候,那些将被存储在栈中的数据的大小和生命周期必须是已知的。Java把对象的reference存放在栈里。

       (3).堆(heap)。这是一段多用途的内存池,所有Java对象都保存在这里。在堆中分配空间时,编译器无需知道该分配多少空间,或数据会在堆里待多长时间。但是其速度比分配栈的慢些。

       (4).静态存储(static storage)。这里“静态”的意思是“在固定的位置”(尽管还是在RAM里面)。静态存储里面的数据在整个程序运行期间都能访问到。可以用 static关键词指明对象的某个元素是静态的,但是Java对象本身是决不会放到静态存储中去的。

       (5).固定存储(constant storage)。常量值通常直接放在程序里。有时常量还能为自己设置界限,这样在嵌入式系统中,就能选择是不是把它们放到ROM里面去。

       (6).非内存的存储(Non-RAM storage)。如果数据完全独立于程序,那么即使程序不运行,它也应该还在。对象被转化成某种能保存在其它介质上的东西,要用的时候,又能在内存里重建。Java提供了轻量级persistence的支持。

     

       特例:primitive类型

       primitive(原始)类型的变量直接保存值,并且存储在栈中。

     

       高精度的数值

       Java还包括两个能进行高精度算术运算的类:BigInteger和BigDecimal。

     

       作用域

       int x = 12;

       {

       int x = 100;//illegal

       }

     

       2.创建新的数据类型:类

       只有在“变量被用作类的成员”时,Java才能确保它获得默认值。本地变量,没有这种保障。

       不管在哪种情况下,Java在传递对象的时候,实际上是在传递reference。

     

     

     

============

Chap3 控制程序流程

       1.运算符

       逗号运算符

       Java里面,唯一一个把逗号当运算符用的地方是for循环。

     

       String的+运算符

       加号(+)用在String上的时候,如果表达式中有String,那么Java编译器会把其他的操作数都转换成String。

       Java没有sizeof

       C和C++的sizeof()用于获取数据要占用多少字节的内存,需要sizeof的主要原因是为了移植。相同的数据类型在不同的机器上占用的内存长度可能会不一样。

       Java没有移植的问题,因此不需要sizeof,所有数据类型在所有的机器上都是相同的。

       运算符的总结

       在进行数学运算或混和赋值的时候,char,byte,short,都会先进行提升,运算结果也是int。如果要把结果赋给原先那个变量,就必须明确地进行类型转换。

       除了boolean之外,所有的primitive类型都能被转换成其它的primitive类型。

     

       2.执行控制

       Java不允许把数字当作boolean用,尽管C和C++允许这么做(非零值表示true,零表示false)。

     

       Java里,唯一能放标签的地方,就是在循环语句的外面。而且必须直接放--在循环语句和标签之间不能有任何东西。而这么做的唯一理由就是,你会嵌套多层循环或选择。因为通常情况下break和continue关键词只会中断当前循环,而用了标签后,就会退到label所在的地方。

       label1:

       outer-iteration{

              inner-iteration{

                     break;//中断内循环,退到外循环

                     continue;//中断本次内循环,重新移到内循环开始处,执行下次内循环

                     continue label1;//中断本次外循环,移到外循环开始处,重新执行下次外循环

                     break label1;//退出外循环,执行循环以后的语句

              }

       }

       如果退出循环或选择的同时,还要退出方法,可以直接使用return。

       continue,break以及label的规则:

       (1).普通的continue会退到内部循环的最开始,然后继续执行内部循环。

       (2).带标签的continue会跳转到标签,并且重新进入直接跟在标签后面的循环。

       (3).break会从循环的“底部溜出去”。

       (4).带标签的break会从由这个标签标识的循环的“底部溜出去”。

       在Java里能使用标签的唯一理由就是,在嵌套循环的同时要用break和continue退出多层循环。

     

       3.switch

       switch会根据整数表达式的值(可以是char)决定应该运行哪些代码。

       找到匹配的值后,就会执行相应的case语句,不会再进行比较。通常case语句应该以break结束。否则会直接执行下一个case语句,而不会再次进行匹配。如果没有匹配的case,则执行default语句。

     

       计算细节

       将float或double转换成整数的时候,它总是将后面的小数截去。

       Math.random()会生成一个double,值域是[0,1)。

============

Chap4 初始化与清理

       1.用构造函数确保初始化

       构造函数的名字必须与类的名字大小写完全相同。构造函数本身没有返回值,虽然new表达式会返回新创建对象的reference。

     

       2.默认的构造函数

       如果你写了一个没有构造函数的类,编译器会自动创建一个默认的构造函数(不带参数)。但是,只要你定义了构造函数,不管带不带参数,编译器就不会再自动合成默认构造函数了。

     

       3.this关键字与构造函数

       在类的构造函数中,可以用this(arg)调用另一个构造函数,但是不能调用两个构造函数(即this()在构造函数中只能出现0或1次)。此外,必须在程序代码的最前面调用构造函数。在非构造函数的方法里,不能用this()调用同一个类的构造函数。

     

       4.static的含义

       类的static方法只能访问其他的static方法和static数据成员,不能在static方法调用非static方法,但是反过来是可以的。

       但是,如果可以传一个对象的reference给static方法,就可以通过这个reference调用非static方法和非static数据成员。但要达到这个目的,通常应该使用非static的方法。

     

       5.清理:finalize和垃圾回收

       java提供finalize()方法,垃圾回收器准备释放内存的时候,会先调用finalize()。

       (1).对象不一定会被回收。

       (2).垃圾回收不是拆构函数。

       (3).垃圾回收只与内存有关。

       (4).垃圾回收和finalize()都是靠不住的,只要JVM还没有快到耗尽内存的地步,它是不会浪费时间进行垃圾回收的。

     

       6.成员的初始化

       方法中的局部变量,使用前必须进行初始化,否则编译器会给出错误消息。

       类的成员数据会被自动初始化,对于primitive变量,会被清零(char型也为0);对于对象reference,会被赋值为null。

     

       7.指定初始化

       对类成员数据进行初始化时,可以调用方法获取初始值,该方法如果有参数,参数不能是类的其他尚未初始化的数据成员。可以这样:

       class Test{

              int i=f();

              int j=g(i);

       }

       不能这样:

       class Test{

              int j=g(i);//i尚未初始化

              int i=f();

       }

     

       8.初始化的顺序

       对类而言,初始化的顺序是由变量在类里定义的顺序决定的,变量的初始化会优先于任何方法,包括构造函数。

       创建对象的过程(小结,以Dog类为例)

       (1).第一次创建Dog类的对象(构造函数实际上是static方法),或者第一次访问Dog类的static的方法或字段的时候,Java解释器会搜寻classpath,找到Dog.class.

       (2).装载了Dog.class之后,(创建了Class对象之后),会对所有的static数据进行初始化。这样第一个装载Class对象的时候,会先进行static成员的初始化。

       (3).用new Dog()创建新对象的时候,Dog对象的构建进程会先在堆(heap)里为对象分配足够的内存。

       (4).这块内存先被清零,自动把Dog对象的primitive类型的成员赋缺省值(对于数字是零,或是相应的boolean和char),将 reference设成null。

       (5).执行定义成员数据时所作的初始化。

       (6).执行构造函数。可能牵涉到继承关系的很多活动。

     

       9.非静态的实例初始化

       实例初始化(instance initialization)语句,除了没有static关键字,其他与静态初始化没有两样。这种语法为匿名内部类(anonymous inner class)的初始化提供了必不可少的支持。

     

       10.数组的初始化

       Java可以将一个数组赋给另外一个,实际上是复制reference.

       可以用花括号括起来的对象列表初始化对象数组。有两种形式:

       Integer[] a = {

              new Integer(1),

              new Integer(2),

       };

       Integer[] b = new Integer[]{

              new Integer(1),

              new Integer(2),

       };

       列表的最后一个逗号是可选的,这个特性能使长列表的维护工作变得简单一些。  

     

     

     

===============  

Chap5 隐藏实现

       1.package:类库的单元

       Java的源代码文件通常被称为编译单元(compilation unit),必须是一个以.java结尾的文件,其中必须有一个与文件名相同的public类(大小写也必须相同,但不包括.java扩展名)。每个编译单元只能有一个public类。

       编译.java文件的时候,里面的每个类都会产生输出,文件名是类名,扩展名是.class。

       创建独一无二的package名字

       package语句必须是文件里的第一个非注释行。

       如果使用JAR文件打包package,必须把文件名放到CLASSPATH里面。

     

       冲突

       如果两个import语句所引入的类库都包含一个同名的类,只要不写会引起冲突的代码(不使用这个同名的类),就一切OK,否则编译器会报错。

       不论哪种对象,只要放进了String的表达式,就会被强制转化成该对象的String表示形式。如:

       System.out.print(""+100);//Force it to be a String

     

       2.Java的访问控制符

       Java的一个访问控制符只管它所定义的这一项,而C++的访问控制符会一直管下去,直到出现另一个。

       private:除非是用成员所在类的方法,否则一律不得访问。

       package访问权限(package access,有时也称为“friendly”),是默认的访问权限,没有关键词。同属一个package的类都能访问这个成员,但是对于不属于这个 package的类来说,这个成员就是private的。

       protected:继承的访问权限。protected也赋予成员package权限--同一个package里的其它类也可以访问protected 元素。除此之外,继承类(不管是否在同一个package)也可以访问protected成员。

       public:访问接口的权限,任何类都能访问。

     

       3.类的访问权限

       类只有两种访问控制权,public和package。(inner class 可以是private或protected)

       每个编译单元(文件)只能有一个public类,也可以没有public类(不常见)。public类的名字必须和编译单元文件名大小写完全相同。如果没有public类,就可以随意给编译单元文件起名。

       对于package权限的类,通常应该将方法也设成package权限。

       如果不写类的访问控制符,默认是package权限的。package中的任何一个类都能创建这个类的对象,但是package以外的类就不行了。但是,如果这个类有一个public 的static成员,那么即使客户程序员不能创建这个类的对象,他们也还可以访问这个static成员。

     

     

     

==============     

Chap6 复用类

       1.合成所使用的语法

       对象的reference作为类的成员时,会被自动初始化为null。此外可以在三个时间对类的reference成员进行初始化:

       (1)在定义对象reference的时候。这就意味着,在类的构造函数调用前,已经初始化完毕了。

       private String a=new String("hello"), b="world";

       (2)在类的构造函数。

       (3)在即将使用对象reference之前。这被称为lazy或delayed initialization,如果创建对象的代价很大,或不是每次都需要创建对象,这种做法就能降低程序开销。

     

       2.基类的初始化

       当你创建一个派生类对象的时候,这个对象里面还有一个基类的子对象(subobject)。

       对于默认构造函数,即无参构造函数,Java会让派生类的构造函数自动地调用基类的构造函数。基类会在派生类的构造函数调用它之前进行初始化。

       对于带参数的构造函数,必须用super关键字以及合适的参数明确地调用基类构造函数,super()语句必须是派生类构造函数的第一条语句。

     

       3.确保进行妥善的清理

       不要依赖垃圾回收器去做任何与内存回收无关的事情。如果要进行清理,一定要自己写清理方法,别去用finalize()。

       清理的顺序:先按照创建对象的相反顺序进行类的清理,然后调用基类的清理方法。

     

       4.名字的遮盖(重载方法的覆盖)

       如果Java的基类里有一个被重载了好几次的方法,那么在派生类里重新定义那个方法,是不会把基类里定义的任何一个给覆盖掉的。(在C++里,就会把基类方法全都隐藏起来)

     

       5.用合成还是继承

       合成用于新类要使用旧类的功能,而不是其接口的场合。继承则是要对已有的类做一番改造,以获得一个特殊版本,即将一个较为抽象的类改造成能适用于某些特定需求的类。

       合成要表达的是“有(has-a)”关系,继承要表达的是一种“是(is-a)”关系。

       判断该用合成还是继承时,可以问一下是不是会把新类上传给基类。如果必须上传,那么继承就是必须的。

     

       6.final关键词

       6.1 final的数据

       常量能用于两种情况:(1)编译时的常量(compile-time constant),这样就再也不能改了;(2)运行时初始化的值,这个值你以后不想改了。

       如果是编译时常量,编译器会把常量放到表达式中,可以降低运行时的开销。Java中这种常量必须是primitive的,要用final表示,这种常量的赋值必须在定义的时候进行。

       当final修饰对象的reference时,表示reference是常量,初始化的时候,一旦将reference指向了某个对象,那么它就再也不能指向别的对象了。但是这个对象本身是可以修改的。

       final int v1 = 10;//compile-time constants

       final int v2 = rand.nextInt(100);//

       final Value v3 = new Value();//v3的数据成员是可变的

       final int[] arr={1,2,3};//arr的元素是可变的,如a[i]++;

       空白的final数据(Blank finals),是指声明了final成员,却没有在声明时赋值。编译器会强制在构造函数中初始化final数据。通过使用带参数的构造函数,根据参数对空白的final数据进行初始化,可以在保持final数据不变性的同时,提供一定的灵活性。

       可以把方法的参数声明为final的。

       6.2 final方法

       使用final方法有两个目的。第一,可以禁止派生类修改方法。第二,效率。对于final方法,编译器会把调用转换成“内联(inline)”,即用方法本身的拷贝来代替方法的调用。但是,如果方法很大,程序会很快膨胀,于是内联也不会带来什么性能的改善。

       只有是基类接口里的东西才能被覆写。如果基类的方法是private的,那它就不属于基类的接口。即使在派生类里创建了一个同名的方法,它同基类中可能同名的private方法没有任何联系。

       6.3 final类

       把类定义成final的,可以禁止继承这个类。final类的数据可以是final的,也可以不是。final类的方法都隐含地变成final了。

     

       7.初始化与类的装载

       在传统的编程语言中,程序启动的时候都是一次装载所有的东西,然后进行初始化,接下来再开始执行。这些语言必须仔细控制初始化的顺序。

       Java采用了一种新的装载模式。编译之后每个类都保存在它自己的文件里。不到需要的时候,这个文件是不会装载的。即“类的代码会在它们第一次使用的时候装载”,第一次访问static成员或创建对象的时候。

       继承情况下的初始化=====

       首次使用类的时候,装载器(loader)就会寻找类的.class文件,转载过程中,会依次追溯装载基类(不管是否创建基类对象,这个过程都会发生)。下一步,会执行“根基类(root base class)”的static初始化,然后是下一个派生类的static初始化,以此类推。

       所有类都装载结束,就可以创建对象了。首先对象里所有成员数据会被初始化为缺省值,这个过程是一瞬间完成的,对象的内存会被设置成二进制0。然后开始构造基类,基类的构造过程及顺序与派生类相同,先对基类的变量按字面顺序进行初始化,再调用基类的构造函数(调用是自动发生的,但你可以用super关键字指定要调用基类的哪个构造函数)。之后会对派生类的变量按定义的顺序进行初始化,最后执行派生类构造函数其余代码。

     

     

=============

Chap7 多态性

       1.方法调用的绑定

       将方法的调用连到方法本身被称为“绑定(binding)”。当绑定发生在程序运行之前时(由编译器或连接器负责),被称作“前绑定(early binding)”。

       “后绑定(late binding)”指在程序运行时,根据对象类型决定该绑定哪个方法。后绑定也被称为“动态绑定(dynamic binding)”或“运行时绑定(run-time binding)”。

       除了static和final方法(private方法隐含有final的意思),Java的所有方法都采用后绑定。

     

       2.错误:“覆写”private的方法

       public class Parent{

              private void f(){

              //...

              }

              public static void main(String[] args){

                     Parent pa = new Child();

                     pa.f();//执行的是Parent.f()

              }

       }

       class Child extends Parent{

              public void f(){

              //...

              }

       }

       应该避免用基类的private的方法名去命名派生类中方法。

       只有非private的方法才能被覆写。

     

       3.继承与清理

       清理的顺序应该与初始化的顺序相反。对数据成员而言,清理顺序就应该与声明的顺序相反。

       在派生类的清理方法(比如dispose())的最后,应该调用基类的清理方法。

     

       4.多态方法在构造函数中的行为

       如果在构造函数里调用了动态绑定的方法,那么它会调用那个覆写后的版本。

       abstract class Glyph{

              abstract void 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

       上面程序中,Glyph的构造函数调用了draw(),而这个调用最后落到了RoundGlyph.draw()。而此时radius的值还没有被初始化为1,还是0。

     

       真正的对象初始化过程:

       (1)类加载结束,并且static初始化结束后,在进行其它工作之前,分配给这个对象的内存会被初始化为二进制的0。

       (2)构造基类,调用基类的构造函数。这时会调用被覆写的draw()方法(在调用RoundGlyph的构造函数之前),受第一步的影响,radius 的值还是0。

       (3)数据成员按照声明的顺序进行初始化。

       (4)调用派生类的构造函数的正文。

     

       5.用继承来进行设计

       一般准则:使用继承来表示不同的行为,使用成员数据(合成)来表示不同的状态。

     

       6.总结

       人们通常会把多态性同Java的那些非面向对象的特性相混淆,比如方法的重载,它常常会被当作面向对象的特性介绍给大家。千万别上当:不是后绑定的,就不是多态性。

     

     

     

===========

Chap8 接口与内部类

       1.接口(interface)

       interface也可以包含数据成员,但是它天生就是public,static和final的。

       interface默认是package权限的,只能用于同一个package。也可以加上public(只有保存在同名文件里的interface才可以加)。

       可以把interface里的方法声明成public的,但是即便不写public关键词,这些方法也自动是public的。当implements一个 interface的时候,必须把这个interface的方法定义成public的。

     

       2.Java的“多重继承”

       由于interface不带任何“实现”--也就是说interface和内存无关--因此不会有谁去阻挠interface之间的结合。

       到底是用interface,还是用abstract类?只要基类的设计里面可以不包括方法和成员变量的定义,就应该优先使用interface。只有在不得不定义方法或成员变量的情况下,才把它改成abstract类。

     

       3.合并接口时的名字冲突

       在要合并的接口里面放上同名的方法,通常会破坏程序的可读性,应该避免。

     

       4.用继承扩展interface

       可以用继承往interface里添加新的方法,也可以把多个interface合并成一个新的interface。使用extends关键词,多个“基接口(base interface)”之间用逗号分割。

       interface Vampire extends IMonster, IUndead{}

     

       5.常量的分组

       由于interface的数据成员自动就是public,static和final的,因此interface是一种非常方便的创建一组常量值的工具。这点同C和C++的enum很相似,但没有enum那样的类型安全。

     

       6.接口的嵌套

       接口既可以嵌套在类里,也可以嵌套在接口里面。

       实现接口的时候,不一定要实现嵌套在里面的接口。同样,private接口只能在定义它的类里实现。

     

       7.内部类(inner class)

       内部类与合成是截然不同的。

       除非是在“宿主类(outer class)”的非static方法里面,否则无论在哪里创建内部类的对象,都必须用OuterClassname.InnerClassName的形式来表示这个对象的类型。

     

       8.内部类与上传

       内部类可以被定义成private或protected,非内部类只可能是public或package权限的。

     

       9.在方法和作用域里的内部类

       在方法的某个作用域里定义的内部类,比如if语句里,并不意味着这个类的创建是有条件的--它会同别的东西一起编译。但是,这个类的访问范围仅限于定义它的那个作用域。除此之外,它同普通的类没有区别。

     

       10.匿名内部类

       public class Parcel6{

              public Contents cont(){

                     return new Contents(){

                            private int i = 11;

                            public value(){return i;}

                     };//Semicolon required

              }

       }

       上例中return new Contents(){...}语句表达的是:创建一个继承Contents的匿名类的对象。new语句所返回的reference对自动上传到 Contents。这个return语句是如下代码的简化形式:

       class MyContents implements Contents{

              private int i = 11;

              public int value(){return i;}

       }

       return new MyContents();

       这个匿名内部类是通过默认构造函数来创建Contents的,如果基类需要的是一个带参数的构造函数,可以直接将参数传给基类的构造函数,return new Contents(x){...}。

       如果在定义匿名内部类的时候,要用到外面的对象,编译器会要求把这个参数的reference声明成final的。

       public class Parcel8{

              //Argument must be final to use inside anonymous inner class

              public Destination dest(final String ds){

                     return new Destination(){

                            private String lbl = ds;

                            public String readLabel(){return lbl;}

                     };

              }

       }

       不能在匿名内部类里创建构造函数(因为它根本就没有名字),但是可以通过“实例初始化(instance initialization)”(与static初始化对应),进行一些类似构造函数的操作。实际上实例初始化过程就是匿名内部类的构造函数。但它的功能是有限的,由于不能重载实例初始化,因此只能有一个构造函数。

       public Base getBase(int i){

              return new Base(i){

                     {

                            System.out.println("Inside instance initializer.");

                     }

                     public void f(){

                            System.out.println("In anonymous f().");   

                     }

              };

       }

     

       11.与宿主类的关系

       内部类能访问宿主类的所有成员。内部类对象里存在一个隐蔽的指向宿主类对象的reference,由编译器处理。

     

       12.嵌套类(静态内部类)

       如果不需要这种“内部类对象和宿主类对象之间的”联系,可以把内部类定义成static的,通常被称作“嵌套类(nested class)”。嵌套类的意思是:

       (1)无需宿主类的对象就能创建嵌套类的对象。

       (2)不能在嵌套类的对象里面访问非static的宿主类对象。

       此外,嵌套类同普通的内部类还有一点不同。普通的内部类的成员数据和方法只能到类的外围这一层,因此普通的内部类里不能有static数据,static 数据成员或嵌套类。但是,这些东西在嵌套类里都可以有。

       嵌套类可以是interface的一部分。

       每个类可以带一个供测试的main()方法,但编译后,会产生额外的代码。可以考虑在嵌套类里创建供测试的main()。编译后会产生单独的名称如 Test$NestedTester.class的文件,发布的时候可以删除这个文件。

     

       13.引用宿主类的对象

       在内部类里通过宿主类名字后面加句点再加this来表示宿主类对象的reference。比如类Sequence.Selctor里,可以用 Sequence.this来获取它所保存的宿主类Sequence对象的reference。

       要在其他地方创建内部类的对象,就必须在new表达式里面给出宿主类对象的reference。如:

       Parcel11 p = new Parcel11();

       //Must use instance of outer class to create an instance of the inner class.

       Parcel11.Contents c = p.new Contents();

     

       14.在多层嵌套的类里向外访问

       内部类的嵌套层次不是问题--它可以透明地访问它的各级宿主类的成员。

       class MNA{

              private void f(){}

              class A{

                     private void g(){}

                     public class B{

                            void h(){

                                   g();

                                   f();

                            }

                     }

              }

       }

     

       public class MultiNestingAccess{

              public static void main(String[] args){

                     MNA mna = new MNA();

                     MNA.A mnaa = mna.new A();

                     MNA.A.B mnaab = mnaa.new B();

                     mnaab.h();

              }

       }

       ".new"语句指明了正确的作用域,因此无需在调用构造函数的语句里再限定类的名字了。

     

       15.继承内部类

       class WithInner(){

              class inner{}

       }

       public class InheritInner extends WithInner.Inner{

              //!InheritIner(){}//Won't compile

              InheritIner(WithInner wi){

                     wi.super();

              }

              public static void main(String[] args){

                     WithInner wi = new WithInner();

                     InheritInner ii = new InheritInner(wi);

              }   

       }

       InheritInner继承的只是内部类,默认的构造函数不能通过编译。必须传递宿主类对象的reference。此外,必须在构造函数里面使用这种语法:enclosingClassReference.super();这样才能提供那个必须的reference,才能编译通过。

     

       16.内部类可以被覆写吗

       像覆写宿主类的方法那样去“覆写”内部类,是不会有实际效果的。但是,可以在继承类的内部类明确继承基类的内部类。

       public class BigEgg2 extends Egg2{

              public class Yolk extends Egg2.Yolk{

              }

       }

     

       17.本地内部类(Local inner classes)

       本地内部类是在代码段,通常是方法的正文部分创建的。本地内部类不能有访问控制符,因为它并不属于宿主类,但它可访问当前代码段的final变量,以及宿主类的所有成员。

       由于本地内部类的名字在代码段(通常是一个方法)外面是没法访问的,因此选择本地内部类来代替匿名内部类的一个理由就是,需要一个有名字的构造函数,并要重载这个构造函数,因为匿名内部类只能进行实例初始化。另一个理由是,需要创建多个那种类的对象。

     

       18.内部类的标识符

       宿主类名字,加上"$",再加上内部类的名字。如果是匿名内部类,编译器会直接用数字来表示。

       如:OuterClass$InnerClass.class,OuterClass$2.class。

     

       19.为什么要有内部类?

       每个内部类都可以独立的继承某个“实现(implementation)”。内部类能在事实上继承多个实体类或abstract类。可以把它当作彻底解决多重继承问题的办法,接口部分地解决了这个问题。

     

     

     

     

========

Chap9     用异常来处理错误

       1.重抛异常

       如果直接重抛当前的异常,则printStackTrace()所打印出来的那些保存在异常对象里的信息,还会指向异常发生的地方,不会被指到重抛异常的地点。如果要装载新的栈轨迹信息,你可以调用fillInStackTrace()。

       try{

              f();

       } catch(Exception e){

              e.printStackTrace();

              //throw e;//17

              throw e.fillInStackTrace();//18

       }

     

       也可以抛出一个与捕捉到的异常不同的异常。这么做的效果同使用fillInstackTrace()的差不多--异常最初在哪里发生的信息被扔了,现在里面保存的时抛出新异常的地点。

     

       2.异常链

       异常链可以在捕捉到一个异常并且抛出另一个异常的时候,仍然保存前一个异常的信息。
分享到:
评论

相关推荐

    《Java编程思想》.pdf

    《Java编程思想》

    《java编程思想》_java编程思想_java编程思想_ThinkinginJava_mileefx_

    《Java编程思想》是Java程序员领域的一本经典之作,由Bruce Eckel撰写,以其深入浅出的讲解方式和丰富的实例闻名。这本书对于想要深入理解Java语言的人来说,是一份宝贵的资源。"Thinking in Java",直译为“思考...

    java编程思想第四版源代码.7z

    《Java编程思想》是 Bruce Eckel 的经典著作,第四版更是深入浅出地介绍了Java语言的核心概念和技术。这个压缩包包含的源代码是书中的示例程序,它们旨在帮助读者理解书中阐述的各种编程原理和实践。通过分析这些源...

    JAVA编程思想中文版.zip

    《JAVA编程思想》是 Bruce Eckel 的经典著作,中文版为国内Java开发者提供了深入理解Java语言的宝贵资源。这本书全面而深入地介绍了Java编程的核心概念和技术,是学习和提升Java编程技能的重要参考资料。 本书主要...

    java编程思想中用的额外的jar包

    java编程思想第四版用的额外的jar包,在http://www.mindviewinc.com/TIJ4/CodeInstructions.html中有详细的说明,可以自己一个个的去下,我这里都下好了,可以直接用,给个辛苦分,呵呵。

    java编程思想PPT

    文旦含有java编程思想一书中前9章的PPT 还附有几个基础型的编程代码 如乘法表: public class Chengfabiao { public static void main(String[] args) { for (int i = 1; i ; i++) {// 从1开始循环到9 for ...

    java编程思想4th版类库

    《Java编程思想》第四版是Java开发者必备的经典书籍之一,由Bruce Eckel撰写,深入浅出地介绍了Java语言的核心概念和技术。在这个压缩包文件中,包含的主要是与本书相关的类库资源,特别是针对"mindview.net"包的,...

    java编程思想(pdf格式)

    《Java编程思想》是Java开发领域的一本经典著作,它深入浅出地介绍了Java语言的核心概念和编程技术。这本书以其详尽的解释、丰富的实例和严谨的逻辑深受程序员喜爱。以下将围绕标题和描述中的知识点进行详细阐述: ...

    Java编程思想源代码及课后练习代码

    《Java编程思想》是一本由 Bruce Eckel 编著的经典Java教程,对于初学者和有经验的程序员来说,都是深入理解Java语言的重要参考书。这本书深入浅出地讲解了Java的核心概念,包括面向对象编程、泛型、并发、集合框架...

    Java编程思想源码关联jar包

    Java编程思想是深入理解并掌握Java这门编程语言的关键,其中源码的分析与学习尤为重要。这个压缩包包含了几个在Java编程中常见的关联库,这些库对于理解和实践Java编程思想有着重要作用。 首先,我们来看看`...

    java编程思想书本示例和习题答案

    《Java编程思想》是 Bruce Eckel 的经典著作,这本书深入浅出地介绍了Java语言的核心概念和技术,被广大Java程序员视为学习Java的必备参考书。这个压缩包包含了书中的实例代码和习题答案,对于读者理解和掌握Java...

    java 编程思想.txt

    根据提供的文件信息,我们可以深入探讨《Java编程思想》这一主题中的关键知识点,这些知识点主要集中在类、对象、封装性、继承、多态等核心概念上。 ### Java编程思想概述 《Java编程思想》这本书深入浅出地介绍了...

    java 编程思想中文版 及 习题

    《Java编程思想》是Java开发领域的一本经典著作,作者是Bruce Eckel。这本书深入浅出地介绍了Java语言的核心概念和技术,对初学者和有经验的开发者都极具价值。书中的内容涵盖了Java语言的各个方面,从基本语法到...

    Java编程思想参考答案代码及文档

    《Java编程思想》是 Bruce Eckel 的经典著作,它深入浅出地介绍了Java语言的核心概念和技术,对于初学者和有经验的程序员来说都是极好的学习资源。本压缩包包含两部分主要内容:Java编程思想的答案代码和英文版的...

Global site tag (gtag.js) - Google Analytics