第一章 对象导论
对象具有状态、行为和标识。这意味着每一个对象都可以拥有内部数据和方法,并且每一个对象都可以唯一地与其他对象区分开来。
每个对象都提供服务
访问控制的第一个存在原因是让客户端程序员无法触及他们不应该触及的部分
访问控制的第二个存在原因是允许库设计者可以改变类内部的工作方式而不用担心会影响到客户端程序员
包访问权限:类可以访问在同一个包(库构建)中的其他类的成员。但是在包之外,这些成员跟指定private一样。protected与private区别在于继承的类可以访问protected成员,但不能访问private成员
组合:使用现有的类合成新的类(has-a “拥有”关系)
继承应该只覆盖类的方法,而不添加在基类中没有的新方法吗?
如果基类与导出类具有完全的接口,称之为(is-a “是一个”关系);
如果导出类中添加了新的接口元素,这种情况下新的类型仍然可以替代基类,但是这种替代并不完美,因为基类无法访问新添加的方法,称之为(is-like-a “像是一个”关系)
前期绑定:编译器将产生对一个具体的函数名字的调用,而运行时将这个调用解析到将要被执行的代码的绝对地址。(非面向对象编程)
后期绑定:当向对象发送消息时,被调用的代码直到运行时才能确定。编译器确保被调用方法的存在,并对调用参数和返回值执行类型检查,但是并不知道将被执行的确切代码。
所有的类最终都继承自单一的基类 -- Object泛型:使用一对尖括号,中间包含类型信息,通过这些特征就可以识别对泛型的使用。
ArrayListshapes = new ArrayList();
第二章 一切都是对象
一切都视为对象,因此可采用单一固定的语法,尽管一切都看作对象,但操纵的标识符实际上是对象的一个“引用”。用户可以只创建引用,而不创建对象与它关联。但是更安全的做法是创建一个引用的同时便进行初始化。如:String s = "qwer";
存储数据的区域包括:
(1)寄存器。位于存储器内部,数量有限,需要根据需求进行分配
(2)堆栈。位于通用RAM(随机访问存储器)中,堆栈指针若向下移动,则分配新的内存;若向上移动,则释放这些内存。在创建程序时,Java系统必须知道存储在堆栈内的所有项的切确生命周期,以便上下移动堆栈指针,因此这个区域一般用于存储对象引用
(3)堆。一种通用的内存池(位于RAM),用于存放所有的Java对象
(4)常量存储。通常存放在程序代码内部,它们永远不会被改变
(5)非RAM存储。如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。例如流对象和持久化对象
特例:基本类型
不使用new来创建变量,而是创建一个并非是引用的“自动”变量,这个变量直接存储“值”,并置于堆栈中,因此更加高效。
*注意:若**类的某个成员**是基本数据类型,即使没有进行初始化,Java也会确保它获得一个默认值。然而确保初始化的方法并不适用于“局部”变量(即并非某个类的字段)。
static关键字:当声明一个事物是static时,就意味着这个域或者方法不会与包含它的那个类的任何对象实例关联在一起。所以,即使从未创建某个类的任何对象,也可以调用其static方法或访问其static域。
*静态成员和静态方法均可以通过特殊的方式直接调用:ClassName.i++ 、 ClassName.method();
第三章 操作符
赋值(“ = ”)
基本类型存储了实际的数值,而并非指向一个对象的引用,所以在为其赋值的时候,直接将一个地方的内容复制到另一个地方。当为对象“赋值”时,我们真正操作的是对象的引用,所以倘若“将一个对象赋值给另一个对象”,实际是将“引用”从一个地方复制到另一个地方。
自动递增和递减(++、--)
前缀式:先执行运算,再生成值
后缀式:先生成值,在执行运算
关系操作符
对象:== 和 != 比较的是对象的引用,equals()方法被覆盖时比较的是对象的值(否则equals()默认比较引用)
基本类型:使用 == 或 != 比较即可
*短路一旦能够明确无误地确定整个表达式的值,就不再计算剩余表达式的余下部分。因此,整个逻辑表达式靠后的部分有可能不会被运算。(利用短路特性,可以获得性能提升)
指数计数法1.39 x e^-43 在java里实际表示 1.39 x 10^-43
截尾和舍入
在将float或double转型为整型值时,总是对该数字进行截尾;如果想得到舍入的结果,请使用java.lang.Math中的round()方法
第四章 控制执行流程
break和continuebreak用于强行退出循环,不执行循环中剩余的语句;continue则停止执行当前的迭代,然后退回循环起始处,开始下一次迭代。
习题:吸血鬼数字如下的为吸血鬼数字:1260 = 21 * 60;1827 = 21 * 87;2187 = 27 * 81;找出四位数的所有吸血鬼数字?
第五章 初始化与清理
1.用构造器确保初始化
创建对象时,如果其类具有构造器,Java就会在用户有能力操作对象之前自动调用相应的构造器,从而保证初始化的进行。Java的做法是让构造器采用与类相同的名称。Java提供了默认构造器,它是不接受任何参数的构造器
2.方法重载
每个重载方法都必须有一个独一无二的参数类型列表(注意:参数顺序的不同也可以区分两个方法,但不建议这么使用)
注意基本类型的重载
如果传入的数据类型小于方法中声明的形式参数类型,实际数据类型就会被提升。char类型会被提升为int;如果传入的实际参数较大,就得通过类型转换来执行窄化转换,否则编译期会报错。
注意:根据方法的返回值来区分重载方法是行不通的。
3.默认构造器
如果你定义的类里面没有构造器,则编译器就会自动帮你创建一个默认构造器;如果已经定义了一个构造器(无论是否有参数),编译器就不会帮你自动创建默认构造器。
4.this关键字
只能在方法内部使用,表示对“调用方法的那个对象”的引用。但要注意,如果在方法内部调用同一个类的另一个方法,就不必使用this,直接调用即可。当前方法中的this引用会自动应用于同一类中的其他方法。可能为一个类写了多个构造器,有时可能想在一个构造器中调用另一个构造器,以避免重复代码,可以用this关键字做到这一点。
static的含义:在static方法的内部不能够调用非静态方法,反过来倒是可以的。而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。Java中禁止使用全局方法,但你在类中置入static方法就可以访问其他static方法和static域。
5.清理:终结处理和垃圾回收
finalize()方法
①对象可能不被垃圾回收
②垃圾回收不等于“析构”
③垃圾回收只与内存有关
构造器初始化
①初始化顺序
在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。
②静态数据的初始化
无论创建多少个对象,静态数据都只占用一份存储区域。初始化的顺序是先静态对象,而后是“非静态”对象
总结一下对象的构建过程(以一个名为Dog的类为例):
a.即使没有显式的使用static关键字,构造器实际上也是静态方法。因此,当首次创建类型为Dog的对象时,或者Dog类的静态方法/静态域首次被访问时,Java解释器必须查找类路径,以定位Dog.class文件。
b.然后载入Dog.class,有关静态初始化的所有动作都会执行。因此,静态初始化只在Class对象首次加载的时候进行一次。
c.当用new Dog()创建对象的时候,首先在堆上为Dog对象分配足够的存储空间。
d.这块存储空间会被清零,这就自动的将Dog对象中的所有基本类型数据都设置成了默认值,而引用被设置成了null。
e.执行所有出现于字段定义处的初始化动作。
f.执行构造器。
6.枚举类型
关键字enum,简单例子如下:
public enum Spiciness{NOT,MILD,MEDIUM,HOT,FLAMING}
第六章 访问权限控制
访问控制权限的等级,从最大权限到自小权限依次为:public、protected、包访问权限、private
*包访问权限
意味着当前包中的所有其他类对那个成员都有访问权限,但对于这个包之外的所有类,这个成员都是private。取得对某成员的访问权的唯一途径是:
①使该成员成为public
②通过不加访问权限修饰词并将其他类放置于同一个包内方式给成员赋予包访问权
③继承而来的类即可以访问public成员也可以访问protected成员,但是不能访问private
④提供访问器和变异器方法(也称get/set方法),以读取和改变数值
默认包
当两个Java文件处于相同的目录并且没有给自己设定任何包名称时,Java默认将它们看做隶属于该目录的默认包之中,于是他们为该目录中的所有其他文件都提供了包访问权限。
接口和实现
访问权限控制将权限的边界毁在了数据类型的内部:
第一个原因是要设定客户端程序员可以使用和不可以使用的界限。
第二个原因即将接口和具体实现进行分离。
第七章 复用类
组合:在新的类中产生现有类的对象。
继承:按照现有类的类型来创建新类,无需改变现有类的形式,采用现有类的形式并在其中添加新代码。
*注意:toString(),每一个非基本类型的对象都有一个toString()方法,而且当编译器需要一个String而你却只有一个对象时,该方法就会被调用。
编译器并不是简单的为每一个引用都创建默认对象,如果想初始化这些引用,可以再代码中的下列位置进行:
①在定义对象的地方。这意味着他们总是能够在构造器被调用之前被初始化
②在类的构造器中
③就在正要使用这些对象之前,这种方式成为惰性初始化
④使用实例初始化
初始化基类
在构造器中调用基类构造器来执行初始化,而基类构造器具有执行基类初始化所需要的所有内容。Java会自动在导出类的构造器中插入对基类构造器的调用。当存在继承关系时,构建过程是从基类“向外”扩散的,所以基类在导出类构造器可以访问它之前,已经完成了初始化。
*带参数的构造器
如果没有默认的基类构造器,或者想调用一个带参数的基类构造器,就必须用关键字super显式的编写调用基类构造器的语句。
代理
继承和组合之间的中庸之道,因为我们将一个成员对象置于所要构造的类中(就像组合),但此时我们在新类中又暴露了该成员对象的所有方法(就像继承)。
在组合和继承之间做出选择
组合技术通常用于想在新类中使用现有类的功能而非它的接口这种情形。即在新类中嵌入某个对象,让其实现所需要的功能,但新类的用户看到的只是为新类所定义的接口,而非所嵌入对象的接口。为取得此效果,需要在新类中嵌入一个现有类的private对象。
protected关键字
指明“就类用户而言,这是private的,但对于任何继承于此类的导出类或其他任何位于同一个包内的类来说,它却是可以访问的(提供了包访问权限)”
*向上转型
由导出类转型成基类,在继承图上是向上移动的,因此一般称为向上转型。由于向上转型是从一个较为专业的类型向较为通用的类型转换的,所以总是很安全的。
final关键字
可能使用到final的三种情况:
①final数据
一个永不改变的编译时常量一个在运行时被初始化的值,而你不希望它被改变当final用于对象引用时,表示引用恒定不变,一旦引用被初始化指向一个对象,就无法再把它改为指向另一个对象
创建一个含有static final域和final域的类,说明二者间的区别?
②final方法
第一个原因是把方法锁定,以防止任何继承类修改它的含义
第二个原因是效率
③final类
表明不打算继承该类,该类的设计永不需要做任何变动,该类没有任何子类。
初始化及类的加载
类的代码在初次使用时才加载,通常是指加载发生于创建类的第一个对象之时,但是当访问static域或static方法时,也会发生加载。
第八章 多态
方法调用绑定将一个方法调用同一个方法主体关联起来被称作绑定。若在程序执行前进行绑定,叫做前期绑定。
后期绑定:在运行时根据对象的类型进行绑定,也称为动态绑定或运行时绑定。
注意:Java中除了static方法和final方法(private方法属于final方法)之外,其他所有的方法都是后期绑定。
只有非private方法才可以被覆盖,因为private方法被默认为是final方法且对导出类是屏蔽的。在导出类中如果想覆盖基类中的private方法,最好采用不同的名字。
注意:如果你直接访问域,这个访问就将在编译期进行解析。如果某个方法是静态的,它的行为就不具有多态性。
对象调用构造器需要遵照下面的顺序:
①调用基类构造器。(这个步骤会不断地反复递归下去)
②按声明顺序调用成员的初始化方法
③调用导出类构造器的主体
*初始化的实际过程
①在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的零
②如之前描述那样调用基类构造器
③按照声明的顺序调用成员的初始化方法
④调用导出类的构造器主体
一条通用的准则:用继承表达行为间的差异,并用字段表达状态上的变化。
第九章 接口
1、抽象类和抽象方法
抽象方法:仅有声明而没有方法体。语法是 abstract void f();
抽象类:包含抽象方法的类
2、接口
关键字:interface
产生一个完全抽象的类,它根本就没有提供任何具体实现,它允许创建者确定方法名、参数列表和返回类型,但是没有任何的方法体。接口中可以包含域,但是这些域隐式地是static和final的。
3、完全解耦
策略模式:创建一个能够根据所传递的参数对象的不同而具有不同行为的方法。
适配器模式:接受拥有的接口,并产生需要的接口。
4、Java中的多重继承
在C++中组合多个类的接口行为被称为多重继承,但是由于每个类有一个具体实现,因此比较繁杂;在Java中,可以通过组合多个接口而只有一个类有具体实现的方式来避免这个问题。
5、通过继承来扩展接口
通过继承,可以很容易地在接口中添加新的方法声明,还可以通过继承在新接口中组合数个接口,这两种情况都可以获得新的接口。
*一般情况下,只可以将extends用于单一类,但是可以引用多个基类接口。
6、适配接口
接口最吸引人的地方就是允许同一个接口具有多个不同的具体实现。在简单情况下,它的体现形式通常是一个接受接口类型的方法,而该接口的实现和向该方法传递的对象则取决于方法的使用者。
7、接口中的域
因为接口中的任何域都自动是static和final的,所以接口成为了一种很便捷的用来创建常量组的工具。
初始化接口中的域:在接口中定义的域不能是“空final”,但是可以被非常量表达式初始化。既然域是static的,它们就可以在类第一次被加载时初始化,这发生在任何域首次比访问时。
8、嵌套接口
接口可以嵌套在类或其他接口中。
第十章 内部类
1、创建内部类
内部类:可以将一个类的定义放在另一个类的定义内部。如果想从外部类的非静态方法之外的任何位置创建某个内部类的对象,那么必须像在main()方法中那样,具体地指明这个对象的类型:OuterClassName.InnerClassName
2、链接到外部类
当生成一个内部类的对象时,此对象与制造它的外围对象之间就有了一种联系,所以它能访问其外围对象的所有成员,而不需要任何特殊条件。此外,内部类还拥有其外围类的所有元素的访问权。
3.使用.this与.new
如果你需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟.this。这样产生的引用自动地具有正确的类型,这一点在编译期就被知晓并受到检查,因为没有任何运行时开销。如果你想要告知某些其他对象,去创建某个内部类的对象,就需要使用.new语法来提供对外部类对象的引用。
*要想直接创建内部类的对象,不能直接去引用外部类的名字,而是必须使用外部类的对象来创建该内部类对象。
4.内部类与向上转型
当将内部类向上转型为其基类,尤其是转型为一个接口的时候,内部类就有了用武之地。因为内部类(某个接口的实现)能够完全不可见、并且不可用。所得到的只是指向基类或接口的引用,能够很方便地隐藏细节。
5.在方法和作用域内的类
也称之为局部内部类。这么做有两个理由:
①你实现了某类型的接口,于是可以创建并返回其引用。
②你要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但是又不希望这个类是公共可用的。
6.匿名内部类
在匿名内部类中不能有命名构造器,但通过实力初始化,就能够达到为匿名内部类创建一个构造器的效果。
7.嵌套类
如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为static,这通常称之为嵌套类。嵌套类意味着:
①要创建嵌套类的对象,并不需要其外围类的对象
②不能从嵌套类的对象中访问非静态的外围类对象
8.为什么需要内部类
使用内部类最吸引人的原因是:每个内部类都能独立地继承自一个接口的实现,所以无论外围类是否已经继承了某个接口的实现,对于内部类都没有影响。
内部类的其他特性:
①内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立。
②在单个外围类中,可以让多个内部类以不同的方式实现同一个接口或者继承同一个类。
③创建内部类对象的时刻并不依赖于外围类对象的创建。
④内部类并没有令人迷惑的“is-a”关系,它就是一个独立的实体。
9.内部类的继承
因为内部类的构造器必须连接到指向其外围类对象的引用,所以在继承内部类时,那个指向外围类对象的“秘密的”引用必须被初始化,而在导出类中不再存在可连接的默认对象。特殊语法:enclosingClassReference.super();
10.内部类可以被覆盖吗?
当继承了某个外围类的时候,内部类并没有发生什么特别神奇的变化。这两个内部类是完全独立的两个实体,各自在自己的命名空间内。当然,明确地继承某个内部类是可以的,这么做可以覆盖旧类的方法。
11.局部内部类
可以在代码块里创建内部类,典型的方式是在一个方法体的里面创建。局部内部类不能有访问说明符,因为他不是外围类的一部分,但是它可以访问当前代码块内的常量,以及此外围类所有成员。
为什么使用局比内部类而不是匿名内部类?
理由一是我们需要一个已命名的构造器或者需要重载构造器,而匿名内部类只能用于实例初始化。另一个理由是需要不止一个该内部类的对象。
12.内部类标识符
内部类文件的命名有严格的规则:外围类的名字 + “$” + 内部类的名字(LocalInnerClass$LocalCounter.class)
第11章 持有对象
1.泛型和类型安全的容器
通过指定容器实例可以保存的类型,可以在编译期防止将错误类型的对象放置到容器中。语法是:ArrayList
当你指定了某个类型作为泛型参数时,并不限于只能将该确切类型的对象放置到容器中,向上转型也可以像作用于其他类型一样作用于泛型。
2.基本概念
Java容器类库的作用是“保存对象”,可以划分为两大类:
①Collection
一个独立元素的序列,这些元素都服从一条或多条规则。
②Map
一组成对的“键值对”对象,允许你使用键来查找值。
3.List
有两种类型的List:
①ArrayList
长于随机访问元素,但是在List的中间插入和移除元素较慢
②LinkedList
进行插入和删除操作较快,但是在随机访问方面相对较慢。它的特性集较ArrayList更大
4.迭代器
迭代器是一个对象,它的工作是遍历并选择序列中的对象,而客户端程序员不必知道或关心该序列底层的结构。
Java中的Iterator对象只能单向移动,并且只能用来:
①使用方法iterator()要求容器返回一个Iterator。Iterator将准备好返回序列的第一个元素
②使用next()获得序列中的下一个元素
③使用hasNext()检查序列中是否还有元素
④使用remove()将迭代器新近返回的元素删除
第12章 通过异常处理错误
概念
用强制规定的形式来消除错误处理过程中随心所欲的因素。
基本异常
异常情形是指阻止当前方法或作用域继续执行的问题。对于异常情形,因为当前环境下无法获得必要的信息来解决问题,所以要从当前环境跳出,并且把问题提交给上一级环境。
捕获异常
try块:尝试各种可能产生异常的方法调用。
异常处理程序:通常紧跟在try块之后,以关键字catch表示。
创建自定义异常
必须从已有的异常类继承,最好选择意思相近的异常类继承。建立新的异常类型最简单的方法是让编译器为你产生默认构造器。
异常说明
使用附加的关键字throws,后面接一个所有潜在异常类型的列表
Java标准异常
Throwable类被用来表示任何可以作为一场被抛出的类。它又可以分为两大类:
①Error
用来表示编译时和系统错误
②Exception
可以被抛出的基本类型,在Java类库、用户方法以及运行时故障都可能抛出Exception异常
使用finally进行清理
对于一些代码,可能会希望无论try块中的异常是否抛出,它们都能得到执行。这种情况下,就要使用finally子句是否被抛出,finally子句总是执行
*在return中使用finally:因为finally子句总是会执行,所以在一个方法中,可以从多个点返回,并且可以保证重要的清理工作依然会执行
缺憾:异常丢失
异常作为程序出错的标志,决不应该被忽略,但它还是有可能被忽略,用某些特殊的方式使用finally子句,就会发生这种情况。(在finally中抛出新的异常覆盖原先抛出的异常)
异常匹配
抛出异常的时候,异常处理系统会按照代码的书写顺序找出“最近”的处理程序。找到匹配的处理程序之后,它就不会再继续查找。
注意:查找的时候并不要求抛出的异常同处理程序所声明的异常完全匹配,派生类的对象也可以匹配其基类的处理程序。如果把捕获基类的catch子句放在最前面,以此想把派生类的异常给“屏蔽”掉,这样编译器就会报告错误。
异常使用指南:
①在知道该如何处理的情况下捕获异常
②解决问题并且重新调用产生异常的方法
③进行少许修补,然后绕过异常发生的地方继续执行
④用别的数据进行计算,以代替方法预计会返回的值
⑤把当前运行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层
⑥把当前运行环境下能做的事情尽量做完,然后把不同的异常重抛到更高层
⑦终止程序
⑧进行简化
⑨让类库和程序更安全
第13章 字符串
1.不可变String
String对象是不可变的。String类中每一个看起来会修改String值的方法,实际上都是创建一个全新的String对象,以包含修改后的字符串内容。而最初的String对象纹丝不动。
2.重载“+”与StringBuilder
一个操作符在应用于特定的类时,被赋予了特殊的意义。操作符“+”可以用来连接String。使用StringBuilder不会生成多个String对象,并且提供了丰富而全面的方法,包括insert()、replace()。。。
3.格式化输出
①printf()
printf("Row 1: [%d %f]\n", x, y);%d和%f称为格式修饰符,它们不但说明插入数据的位置,同时还说明了将插入什么类型的变量。此处%d表示x是一个整数,%f表示y是一个浮点数
②System.out.format()
与printf()方法等价
③Formatter类
可以看成一个翻译器,它将你的格式化字符串与数据翻译成需要的结果,当你创建一个Fomatter对象的时候,需要向其构造器传递一些信息,告诉它最终的结果将向哪里输出。
4.正则表达式
String类自带了一个非常有用的正则表达式工具--split()方法,其功能是“将字符串从正则表达式匹配的地方切开”String类自带的另一个正则表达式工具是“替换”,你可以只替换正则表达式第一个匹配的子串(replaceFirst)或是替换所有匹配的地方(replaceAll)
第14章 类型信息
Java是如何让我们在运行时识别对象和类的信息的?
主要的方式有两种:
①“传统的”RTTI,它假定我们在编译时已经知道了所有的类型
②“反射”机制,它允许我们在运行时发现和使用类的信息
1.为什么需要RTTI
在Java中,所有的类型转换都是在运行时进行正确性检查的。这也是RTTI名字的含义:在运行时,识别一个对象的类型。
2.Class对象
类型信息在运行时是如何表示的?
这项工作是由称为Class对象的特殊对象完成的,它包含了与类相关的信息。每当编写并且编译一个新类,就会产生一个Class对象。为了生成这个类的对象,运行这个程序的Java虚拟机将使用被称为“类加载器”的子系统。
所有的类都是在对其第一次使用时,动态加载到JVM中的,当程序创建第一个对类的静态成员的引用时,就会加载这个类。这个证明构造器也是类的静态方法,即使构造器没有使用static关键字。因此,使用new操作符创建类的新对象也会被当做类的静态成员的引用。因此,Java程序在它开始运行之前并非被完全加载,其各个部分是在必需时才加载的。类加载器首先检查这个类的Class对象是否已经加载。如果没有加载,默认的类加载器就会根据类名查找.class文件,在这个类的字节码被加载时,它们会接受验证,保证其没有被破坏,并没有包含不良Java代码。一旦某个类的Class对象被载入内存,它就会用来创建这个类的所有对象。
getName()产生全限定的类名,getSimpleName()产生不含包名的类名,getCanonicalName()产生全限定的类名。getInterface方法返回一个Class对象,它表示Class对象中所包含的接口;getSuperclass方法查询其直接基类。
*类字面常量
如:FancyToy.class类字面常量不仅可以用于普通的类,也可应用于接口、数组以及基本数据类型。另外,用于基本类型的包装器类,还有一个标准字段TYPE。如Character.TYPE等价于char.class
*注意:有一点非常有趣,当使用“.class”来创建对Class对象的引用时,不会自动地初始化该Class对象。为了使用类而做的准备工作实际包含三个步骤:
①加载,这是由类加载器执行的,该步骤将查找字节码,并从这些字节码中创建一个Class对象;
②链接,在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必需的话,将解析这个类创建的对其他类的所有引用;
③初始化,如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。新的转型语法:cast()cast()方法接受参数对象,并将其转型为Class引用的类型
3.类型转换前先做检查
RTTI在Java中的第三种形式:关键字instanceof它返回一个布尔值,告诉我们对象是不是某个特定类型的实例。使用方法:if(x instanceof Dog)对instanceof有严格的限制:只可将其与命名类型进行比较,而不能与Class对象作比较。
动态的instanceof:Class.isInstance方法提供了一种动态地测试对象的途径。
4.instanceof和Class的等价性
在查询类型信息时,以instanceof的形式(包含instanceof和isInstance)与直接比较Class对象有一个重要的差别instanceof保持了类型的概念(比较类和它的派生类),而如果用==比较实际的Class对象,就没有考虑继承。
5.反射:运行时的类信息
在编译时,编译器必须知道所有要通过RTTI来处理的类。Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Field、Merhod以及Constructor类这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。
RTTI和反射之间真正的区别在于:对RTTI来说,编译器在编译时打开和检查.class文件;而对于反射来说,.class文件在编译时是不可获取的,所以在运行时打开和检查.class文件。
Class的getMethods()和getConstructors()方法分别返回Methods对象的数组和Constructor对象的数组。这两个类都提供了深层方法,用以解析其对象所代表的方法,并获取其名字、输入参数以及返回值。
6.动态代理
代理是基本的设计模式之一,它是你为了提供额外的或不同的操作,而插入的用来代替“实际”对象的对象。这些操作通常涉及与“实际”对象的通信,因此代理通常充当着中间人的角色。Java的动态代理比代理的思想更向前迈进一步,因为它可以动态地创建代理并动态地处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策
7.null对象
当你使用内置的null表示缺少对象时,在每次使用引用时都必须测试其是否为null。这显得很枯燥。空对象最有用之处在于它更靠近数据,因为对象表示的是问题空间内的实体。
8.接口与类型信息
inteface关键字的一种重要目标就是允许程序员隔离构件,进而降低耦合性。
第15章 泛型
泛型实现了参数化类型的概念,使代码可以应用于多种类型。
1.简单泛型
Java泛型的核心概念:告诉编译器想使用什么类型,然后编译器帮你处理一切细节。
2.泛型方法
目前,我们见到的泛型都是用于整个类上的,但同样可以在类中包含参数化方法,并且这个方法所在的类可以是泛型类,也可以不是泛型类。要定义泛型方法,只需将泛型参数列表置于返回值之前,如:publicvoid f(x){ };
3.问题
①任何基本类型都不能作为类型参数
②实现参数化接口:一个类不能实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会成为相同的接口
③转型和警告
④重载
⑤基类劫持了接口
第16章 数组
1.数组为什么特殊
数组与其他种类的容器之间的区别有三点:
①效率:在Java中,数组是一种效率最高的存储和随机访问对象引用序列的方式
②类型:数组是一个简单的线性序列,使得元素访问非常快速
③保存基本类型的能力:数组对象的大小被固定,并且在其生命周期中不可改变
2.数组是第一级对象
对象数组和基本类型数组在使用上几乎是相同的,唯一的区别就是对象数组保存的是引用,基本类型数组直接保存
基本类型的值。
3.返回一个数组
在Java中,可以直接“返回一个数组”,而无需担心要为数组负责 -- 只要你需要它,它就会一直存在,当你使用
完后,垃圾回收器会清理掉它。
4.多维数组
创建多维数组很方便。对于基本类型的多维数组,可以通过使用花括号将每个向量分隔开。
5.数组与泛型
通常,数组与泛型不能很好地结合。你不能实例化具有参数化类型的数组,擦除会移除参数类型信息,而数组必须
知道它们所持有的确切类型,以强制保证类型安全。但是,你可以参数化数组本身的类型。
6.Arrays实用功能
①复制数组
System.arraycopy()
②数组的比较
数组相等的条件是元素个数必须相等,并且对应位置的元素也相等,这可以通过对每一个元素使用equals()比较
来判断
③数组元素的比较
第一种是实现java.lang.Comparable接口,使你的类具有“天生”的比较能力,此接口比较简单,只有compareTo()
一个方法
第二种方式是创建一个实现了Comparator接口的单独的类
④数组排序
Java标准类库中的排序算法针对正排序的特殊类型进行了优化 -- 针对基本类型设计的“快速排序”以及针对对象
设计的“稳定归并排序”
⑤在已排序的数组中查找
如果数组已经排好序了,就可以使用Arrays.binarySearch()执行快速查找
第17章 容器深入研究
1.完整的容器分类法
包括了抽象类和遗留构件
JavaSE5新添加了:
①Queue接口及其实现PriorityQueue和各种风格的BlockingQueue
②ConcurrentMap接口及其实现ConcurrentHashMap,它们也是用于多线程机制的
③CopyOnWriteArrayList和CopyOnWriteArraySet,他们也是用于多线程机制的
④EnumSet和EnumMap,为使用enum而设计的Set和Map的特殊实现
⑤在Collection类中的多个便利方法
2.填充容器
两种用对单个对象的引用来填充Collection的方式,第一种是使用Collection.nCopies()创建传递给构造器的
List,这里填充的是ArrayList;第二种是使用Collection.fill()方法,但是它只能替换已经在List中存在的
元素,而不能添加新的元素。
3.Collection的功能方法
包含add(T)、addAll(...)、clear()等。
注意:其中不包括随机访问所选择元素的get()方法。因为Collection包含Set,而Set是自己维护内部顺序的,要
想检查Collection中的元素,就必须使用迭代器。
4.List的功能方法
基本的List很容易使用,大多数时候只是调用add()添加对象,使用get()一次取出一个元素,以及调用iterator()
获取用于该序列的Iterator。
其他方法包括:basicTest()中包含每个List都可以执行的操作;iteMotion()使用Iterator遍历元素;对应的
iterManipulation()使用Iterator修改元素;testVisual()用以查看List的操作效果等等。
5.Set和存储顺序
当你创建自己的类型时,要意识到Set需要一种方式来维护存储顺序,而存储顺序如何维护,则是在Set的不同实现
之间会有所变化的。
①HashSet*
为快速查找而设计的Set,存入HashSet的元素必须定义hashCode()
②TreeSet
保持次序的Set,底层是树结构。使用它可以从Set中提取有序的序列,元素必须实现Comparable接口
③LinkedHashSet
具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的顺序)。于是在使用迭代器遍历Set时,结果
会按照元素插入的次序显示。元素也必须定义hashCode()方法。
SortedSet:保证元素处于排序状态。
6.队列
除了并发应用,Queue在Java中仅有的两个实现是LinkedList和PriorityQueue,它们的差异在于排序行为而不是
性能。
7.理解Map
①性能
性能是映射表中一个重要问题,当在get()中使用线性搜索时,执行速度会相当地慢,而这正是HashMap提高速度的
地方。HashMap使用了特殊的值,称作散列码,来取代对键的缓慢搜索。散列码是“相对唯一”的,用以代表对象
的int值,它是通过将该对象的某些信息进行转换而生成的。
②SortedMap
使用SortedMap可以确保键处于排序状态。
③LinkedHashMap
为了提高速度,LinkedHashMap散列化所有的元素,但是在遍历键值对时,却又以元素的插入顺序返回键值对。
8.持有引用
SoftReference、WeakReference和PhantomReference由强到弱排列,对应不同级别的“可获得性”。
SoftReference用以实现内存敏感的高速缓存。WeakReference是为实现“规范映射”而设计的,它不妨碍垃圾
回收器回收映射的“键”或“值”。PhantomReference用以调度回收前的清理工作,它比Java终止机制更灵活。