最近看新项目的时候,很多代码看得迷迷糊糊,觉得自己基础有些差,所以开始啃这本Think in Java。本文仅作为笔记记录。
1.面向对象程序设计方式中涉及到的基本特性:
>.万物皆为对象。 它可以存储数据,也可以在自身上执行操作。
>.程序是对象的集合。它们通过发送消息来告知彼此所要做的。可以把消息想象成 对某个特定对象的方法的调用请求。
>.每个对象都有自己的由其他对象所构成的存储。 可以通过创建包含现有对象的包的方式来创建新类型的对象。(疑惑)
>.每个对象都拥有其类型。 每个对象都是一个类的实例。
>.某个特定类型的所有对象都可以接收同样的消息。 例如:圆形的对象也是几何形的对象。这种可替代性是OOP中最强有力的概念之一。
2.类描述了具有相同特性(数据元素)和行为(功能)的对象集合。一个类实际就是一个数据类型。
3.Java的三个访问限定关键字:public、private、protected
>.public表示紧随其后的元素对任何人都是可用的。
>.private关键字表示除 类型创建者 和 类型的内部方法 之外的任何人都不能访问的元素。
>.proteced关键字与private作用相当,差别仅在于继承的类可以访问protected成员,但是不能访问private成员。
>.Java还有一种默认的访问权限,没有使用限定词时,发挥作用。这种权限被称为:包访问权限。这种权限下,类可以访问在同一个包中的其他类的成员,但是在包之外作用和private一样。
4.使用现有的类合成新的类,这种概念叫做:组合(composition),如果组合是动态发生的,那么它通常被称为聚合。组合经常被视为“has-a”(拥有关系)。
>.聚合:整体和部分可以分离,它们可以有各自的生命周期。 例如: 雁群和孤雁
>.组合:整体和部分不可以分离,成员必须依赖整体才有意义。 例如: 燕子和它的翅膀
5.继承使用过多会导致难以使用并过分复杂的设计。建立新类时,首先考虑组合,因为它更加简单灵活。
6.由于基类和子类具有相同的基础接口,所以伴随此接口必定有某些具体实现。也就是说,当对象接收到特定消息时,必须有某些代码去执行。
7.两种方法可以使基类和子类产生差异:
>.直接在子类中添加新的方法,这些新方法不是基类接口的一部分。这意味着基类不能满足你的所有需求。
>.改变基类的方法的行为,称为重写(override)
8.把将子类看做是它的基类的过程称为向上转型。 (多态相关)
9.单继承使得垃圾回收器的实现变得更加容易, 垃圾回收器是Java相对C++的重要改进。由于所有对象都保证具有其类型信息,因此不会因为无法确定对象的类型而陷入僵局。这个系统级操作(例如异常处理)显得尤为重要。
10.Java完全采用了动态内存分配方式:每当想要创建新的对象时,就要使用new关键字来构建此对象的动态实例。
11.在堆中分配存储空间所需的时间 可能远远大于在堆栈中创建存储空间的时间。(在堆栈中创建存储空间和释放存储空间通常各需要一条汇编指令,分别对应将栈顶指针向下移动和将栈顶指针向上移动)
12.允许在堆栈上创建对象的语言,编译器可以确定对象存活的时间,并可以自动销毁它。但是在堆上创建对象,编译器会对它的生命周期一无所知。
13.把问题切分成多个可独立运行的部分(任务),从而提高程序的响应能力。在程序中,这些彼此独立运行的部分称之为线程。同一个cpu上“同时”(看起来是同时,其实不是的,cpu在各个程序之间切换)运行多个程序。
1.将一切都看作对象,但是操纵的标识符其实是一个“引用”(reference)。引用可以独立存在。最好创建引用的同时对引用进行初始化(这样比较安全,否则调用引用的方法时会报空指针异常)。
2.五个不同的存储数据的地方:
>.寄存器:这是最快的存储区,它位于处理器内部(其他四种在处理器外部)。但是寄存器的数量极其有限,所以寄存器根据需求进行分配,不能直接控制。
总结:速度最快、位于处理器内部、数量有限、我们控制不了。
>.堆栈:位于通用RAM(随机访问存储器)中,但通过堆栈指针可以从处理器那获得直接支持。堆栈指针若向下移动,则分配新的内存;若向上移动,则释放那些内存。这是一种快速有效的分配存储方法,仅次于寄存器。创建程序时,Java系统必须知道存储在堆栈内所有项的确切声明周期,以便上下移动堆栈指针。这一约束限制了程序的灵活性。对象的引用存储在堆栈中。
总结:位于RAM中;通过堆栈指针和处理器关联;快速有效,速度仅次于寄存器;系统必须知道堆栈中所有项的生命周期,导致不够灵活
注意:内存中的堆栈和数据结构中的栈不同。这里的堆栈对应linux内存中的user stack区域(这个区域向下生长),因此指针向下移动分配新内存。查看的博客地址:[原文](https://www.cnblogs.com/mingmingcome/p/8256921.html#commentform)
>.堆:一种通用的内存池(也位于RAM中),用于存放所有的Java对象。和堆栈不同的是:编译器不需要知道存储的数据在堆里存活多长时间。因此,在堆里分配存储有很大的灵活性。这种灵活性的代价是:用堆进行存储分配和清理可能比用堆栈进行存储分配需要更多的时间。
总结:位于RAM中,存放所有Java对象;比较灵活;进行存储分配和清理可能比用堆栈需要更多时间;
>.常量存储:常量值通常直接存储在程序代码内部,这样做是安全的,因为它们永远不会被改变。有时,在嵌入式系统中,常量本身会和其他部分隔离开,所以这种情况下可以选择将其存放在ROM(只读存储器)中。
>.非RAM存储: 如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。其中两个基本的例子是流对象和持久化对象。在流对象中,对象转化成字节流,通常被发送给另一台机器。在“持久化对象”中,对象被存放在磁盘上,因此,即使程序终止,它们仍可以保持自己的状态。这种存储方式的技巧在于:把对象转化成可以存放在其他媒介上的事物,在需要时,可恢复成常规的,基于RAM的对象。
总结:数据不受程序的任何控制,可以存活在程序之外; 把对象转化成可以存放在其他媒介上的事物,需要时可恢复成基于RAM的对象。
3.基本类型创建时,变量直接存储“值”,并且放在堆栈中,比较高效。Java要确定每种基本类型所占存储空间的大小。Java中基本类型的大小不随机器硬件架构的变化而变化。 这种所占存储空间大小的不变性是Java程序比其他大多数语言编写的程序更具有可移植性的原因之一。
4.所有数值类型都有正负号,没有无符号数据类型。boolean类型所占存储空间的大小没有明确指定,仅定义为能够取字面值 true 和 false;
byte 1字节
char short 2字节
int float 4字节
long double 8字节
5.高精度数字:Java提供了两个用于高精度计算的类:BigInteger 和 BigDecimal 它们没有对应的基本类型。
BigInteger支持任意精度的整数,也就是说,在运算中,可以准确地表达任何大小的整数值,而不会丢失任何信息。
BigDecimal支持任何精度的定点数,例如,可以进行精确的货币计算。
6.Java确保数组会被初始化,而且不能在它的范围之外被访问。 这种范围检查是以每个数组上少量的内存开销及运行时的下表检查为代价的,但是换来的是安全性和效率的提高(有时可以优化这些操作)。
创建一个数组对象时,实际创建了一个引用数组,并且每个引用都会自动被初始化为一个特定值,该值拥有自己的关键字null,在使用任何引用之前,必须为其指定一个对象。
int[] arr = new int[10]; 每一个默认值为0
short[] 默认值为0
byte[] 默认值为0
double[] 默认值为0.0
float[] 默认值为0.0
char[] 默认值为0对应的字符 是一个控制字符
boolean[] 默认值为false
String[] 默认值为null
7.Java是一种自由格式(free-form)的语言,所以空格、制表符、换行都不会影响程序的执行结果。
8.类中包含两种类型的元素: 成员和方法。
成员可以是任意类型的对象,可以通过其引用与其通信;也可以是基本类型的一种。 如果字段是对某个对象的引用,那么必须初始化该引用,以便使其与一个实际对象相关联。
若类的某个成员是基本数据类型,即使没有初始化,Java也会确保它获得一个默认值。
方法的基本组成部分包括:名称、参数、返回值和方法体。方法名和参数列表唯一标识一个方法。Java中方法只能作为类的一部分来创建,方法只有通过对象才能被调用(标注:static类型的方法针对类调用,不依赖于对象)。
在参数列表中必须指定每个所传递对象的类型及名字,Java中任何传递对象的场合中,传递的实际上是引用。但是如果参数被设置为String类型,则必须传递一个String对象,否则编译器将抛出错误。
方法的返回类型如果是void,return关键字的作用只是用来退出方法。但是如果返回类型不是void,无论在什么地方返回,编译器都会强制返回一个正确类型的返回值。
9.static关键字:当声明一个事物是static时,意味着这个域或方法不会与包含它的那个类的任何对象实例关联在一起。非静态方法可以调用静态方法并访问静态变量,但是静态方法不能调用非静态方法。
10.Java lang包是每个java文件中都自动导入的一个包。
1.在最底层,Java中的数据是通过使用操作符来操作的。
2.“+”操作符有时意味着字符串连接,并且如果必要,它还要执行“字符串转换”,试着将其他类型元素转换为String
3.Random类可以生成随机数。 如果创建过程中没有传递任何参数,那么Java就会将当前时间作为随机数生成器的种子(seed参数),并由此在程序每一次执行时都要产生不同的输出。
通过在创建Random对象时提供种子(用于随机数生成器的初始化值,随机数生成器对于特定的种子值总是产生相同的随机数序列),就可以在每一次执行程序时都生成相同的随机数。 例如new Random(1).nextFloat();
4. i++ 和 ++i 的区别
i++ 在表达式运算完再对 i 进行加一操作(运算过程中,使用的是i的值)
++i 在表达式运算之前,先对i进行加一操作
i++ 举例:
int i = 0;
print(i++ + 1); //输出1
print(i); //输出1
++i 举例:
int i = 0;
print(++i + 1); //输出2
print(i); //输出1
5. == 和 != 比较的是对象的引用, .equals()方法比较的是对象的实际内容,但是.equals()方法不适用于基本类型。基本类型直接使用 == 和 !=
6.自己定义的类中,如果不重写equals()方法,equals()默认会比较两个对象的引用。大多数Java类库都实现了equals方法,以便于来比较对象的内容。
7.逻辑操作符中的“短路”现象:一旦能够明确无误地确定整个表达式的值,就不再计算表达式的剩余部分。
8.按位操作符用来操作整数基本数据类型中的单个“比特”,即二进制位。按位操作符会对两个参数中对应的位执行布尔代数运算,并最终生成一个结果。
9.异或运算:个人理解: 两位(bit)只能有一个为真或者假 例如:真假美猴王??? (不存在两个真猴王 所以都是1的时候,1^1 = 0)
10.移位操作符操作的运算对象也是二进制的“位”。移位操作符只能用来处理整数类型。(还没看完)
11.三元操作符:也称为条件操作符,属于操作符的一种。
12.将 Float 或 Double 类型转换为整型值时,总是对该数字进行截尾。如果想实现数学中四舍五入的效果,调用Math.round()方法。
1.在Java的控制语句中,涉及到的关键字包括:if-else、while、do while、for、return、break以及选择语句switch。 Java中不支持goto语句,但是可以进行类似goto那样的跳转,比起典型的goto,有了更多的限制。
2.while 和 do while 的唯一区别就是do while中的语句至少会执行一次,即便第一次表达式就被计算为false。 而在while循环结构中,如果条件第一次就为false,那么其中的语句根本不会执行。实际应用中,while 比 do while 更常用一些。
3.逗号操作符:(注意不是逗号分隔符,逗号用作分隔符时用来分隔函数的不同参数),Java里唯一用到逗号操作符的地方就是 for 循环的控制表达式。在控制表达式的初始化和步进控制部分,可以使用一系列由逗号分隔的语句,而且那些语句均会独立执行。
实例:
for (int i = 0, j = i + 1; i < 5; i++ , j = i + 1){
print(i + " " + j)
}
4.Foreach 语法:Java SE5引入的一种新的更加简洁的for语法,用于数组和容器。foreach还可以用于任何 Iterable 对象。
5.return 关键字有两方面的用途:一方面指定一个方法返回什么值(假设它没有Void返回值),另一方面它会导致当前的方法退出,并返回那个值。
6.break 和 continue :在任何循环语句的主体部分,都可用 break 和 continue 控制循环流程。其中,break 用于强行退出循环,不执行循环中剩余的语句。而 continue 则停止执行当前的循环,然后退回循环起始处,开始下一次循环。
7. while(true) 和 for(;;) 是一回事,都表示无穷循环。
8.break label; continue label; 能起到goto的效果。
实例:
label: //后边不能加表达式 只在循环语句之前起作用
outer-Iteration{
inner-Iteration{
//...
break; (1)
//...
continue; (2)
//...
continue label; (3)
//...
break label; (4)
}
}
(1)处,break 中断内部循环,回到外部循环。
(2)处,continue 使执行点回到内部循环的起始处,继续执行内部循环。
(3)处,continue label 同时中断内部循环和外部循环,直接转到label处,随后继续执行循环过程,但是是从外部循环开始。
(4)处,break label 会中断所有循环,并回到label处,但是不会重新执行循环。实际上是完全终止了两个循环。
9.在Java中需要使用标签的唯一理由是有循环嵌套存在,而且想从多层嵌套中break 或者 continue。
10.Java中,switch语句的case后, 在JDK1.7 之前只能是int char类型的, JDK 1.7 之后,case后可以跟String类型。 (1.7之后已写代码验证,可以接String类型)。 但是case关键字后不能跟Float类型的值。
1.构造器是一个在创建对象时被自动调用的特殊方法,Java中才用了构造器,并且提供了垃圾回收器。对于不再使用的资源,垃圾回收器能自动将其释放。
2.创建对象时,如果类具有构造器,Java就会在对象可以被使用之前自动调用相应的构造器,从而保证了初始化的进行。
3.由于构造器名称必须和类名完全相同,所以“每个方法的首字母小写”的编码风格不适用于构造器。
4.构造器是一种特殊类型的方法,它没有返回值。 与返回值为void不同。对于空(void)返回值,尽管方法本身不会自动返回什么,但仍可以选择让它返回别的东西,构造器则不会返回任何东西。
5.每个重载的方法都必须有一个独一无二的参数类型列表。如果两个参数的类型不同,那它们的顺序不同也属于重载,但是一般情况下不这么做,这会使代码难以维护。
方法的返回值不能作为重载的依据。
6.默认构造器(无参构造器)是没有形参的构造器,它的作用是创建一个默认对象,如果类中没有构造器,则编译器会自动帮你创建一个默认构造器。
7.调用一个方法时,编译器做了一些幕后的工作。它把“所操作对象的引用”作为第一个参数传给方法(内部发生)。由于这个引用是编译器偷偷传进去的,所以没有标识符可用。但是有个专门的关键字this,this关键字只能在方法内部使用,表示对“调用方法的对象”的引用。 注意:如果在方法内部调用同一个类的另一个方法,不必使用this,直接调用即可。
8.static方法就是没有this,在static方法内部不能调用非静态方法,但是非静态方法可以调用静态方法。
9.假设对象获得了一块“特殊”的内存区域(不是通过new获得),由于垃圾回收器只知道释放那些由new分配的内存,所以它不知道如何释放对象的这块“特殊”内存。为了应对这种情况,Java允许在类中定义一个名为finalize()的方法。
工作原理(假定是这样):一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。
10.Java中的对象可能 不 被垃圾回收,垃圾回收只与内存有关。 也就是说使用垃圾回收器的唯一原因是为了回收程序不再使用的内存。所以对于垃圾回收有关的任何行为来说(尤其是finalize()方法),都必须和内存及其回收有关。不要过多地使用finalize()
11.无论是“垃圾回收”还是“终结”,都不保证一定发生。如果Java虚拟机(JVM)并未面临内存耗尽的情形,它是不会浪费时间去执行垃圾回收以恢复内存。
12.Java中方法内部的局部变量,Java以编译时错误的形式来保证变量被初始化。但是类的数据成员(成员变量),如果是基本类型的话,会保证有一个默认的初始值。在类里定义一个对象引用时,如果不将其初始化,此引用会获得一个特殊值null。
13.在类的内部,变量定义的先后顺序决定了初始化的顺序。成员变量会在任何方法(包括构造器)被调用之前得到初始化。
14.无论创建多少个对象,静态数据都只占用一份存储区域。static不能应用于局部变量,因此它只能作用于域。如果一个域是静态的基本类型的域,且也没有对它进行初始化,那么它就会获得基本类型的标准初值;如果它是一个对象引用,那么它的默认初始值是null。
15.静态初始化只有在必要时刻才会进行。初始化的顺序是先静态对象,而后是“非静态”对象。
16.Java允许将多个静态初始化动作组织成一个特殊的“静态子句”(有时也叫“静态块”)。与其他静态初始化动作一样,静态块仅执行一次:当首次生成这个类的一个对象时,或者首次访问属于那个类的静态数据成员时。
17.枚举(enum)类型的实例是常量,因此按照命名习惯它们都用大写字母表示(如果一个名字中有多个单词,用下划线将它们隔开)。
18.在创建enum时,编译器会自动添加一些有用的特性。例如,会创建toString()方法,以便可以很方便地显示某个enum实例的名字,编译器还会创建ordinal()方法,用来表示某个特定enum常量的声明顺序,以及static values()方法,用来按照enum常量的声明顺序,产生由这些常量值构成的数组。enum是类,并且有自己的方法。
1.当编写一个Java源代码文件时,此文件通常被称为编译单元,每个编译单元必须有一个相同的后缀名.Java,而在编译单元内则可以有一个public类,该类的名称必须与文件的名称相同(包括大小写,但是不包括文件的后缀名),每个编译单元只能有一个public类。
2.默认访问权限没有任何关键字,但通常是指包访问权限(有时也表示为friendly)。这就意味着当前的包中的所有其他类,对那个成员都有访问权限,但对于这个包之外的所有类,这个成员却是private。
3.包访问权限允许将包内所有相关的类组合起来,以使它们彼此之间可以轻松地相互作用。当把类组织起来放进一个包内之时,也就给它们的包访问权限的成员赋予了相互访问的权限。
4.取得对某成员的访问权的唯一途径:
>.使该成员为public,无论是谁,无论在哪,都可以访问该成员。
>.通过不加访问权限修饰词并将其他类放置于同一个包内的方式给成员赋予包访问权。于是包内的其他类就可以访问该成员了。
>.继承来的类既可以访问public成员,又可以访问protected成员(但是不能访问private成员)。
>.提供访问器和编译器(get/set方法),以读取和改变数值。
5.关键字private的意思是,除了包含该成员的类之外,其他任何类都无法访问这个成员。
6.protected修饰的成员,在同一个包中,相当于默认包权限。在不同包下protected成员对其他非继承类不可见。 继承类可以访问 protected 成员和 public 成员。(书中原话:如果创建了一个新包,并自另一个包中继承类,那么唯一可以访问的成员就是源包中的public成员。 这个地方有些疑问???)
7.访问权限的控制常被称为是 具体实现的隐藏。把数据和方法包装进类中,以及具体实现的隐藏,常共同被称作是“封装”。其结果是一个同时带有特征和行为的数据类型。
8.访问权限控制将权限的边界划在了数据类型的内部。其中有两个原因:
>. 第一个原因是要设定客户端程序员可以使用和不可以使用的界限。可以在结构中建立自己的内部机制,而不必担心客户端程序员会偶然地将内部机制当作是他们可以使用的接口的一部分。
>. 第二个原因是将接口和具体实现进行分离。如果结构是用于一组程序之中,而客户端程序员除了可以向接口发送信息之外(调用方法)什么也不可以做的话,那么就可以随意更改不是public的东西(例如有包访问权限,protect-ed和private成员),而不会破坏客户端代码。
9.为了控制某个类的访问权限,修饰词必须出现在class关键字之前。
>.每个编译单元(.java文件)只能有一个public类。这表示每个编译单元都有单一的公共接口,用public类来表现。
>.public类的名称必须完全与含有该编译单元的文件名相匹配,包括大小写。
>.编译单元内完全不带public类也是可能的。这种情况下可以随意对文件命名。
10.通常情况下,类的访问权限仅有 包访问权限 或 public。 但是特殊情况下(例如内部类可以是private或者protected,但是这是特例)。如果不希望其他任何人对该类拥有访问权限,可以把所有的构造器都指定为private。从而阻止任何人创建该类的对象。但是有一个例外,可以在该类的static成员内部进行创建。
11.如果我们自己编写了构造器,类就不会再自动创建构造器了。
1.复用类的两种方法:
>.第一种方法只需在新的类中产生现有类的对象,由于新的类是由现有类的对象组成,所以这种方法称为组合。
>.第二种方法按照现有类的类型来创建新类。无需改变现有类的形式,采用现有类的形式并在其中添加新代码。这种方式称为继承。继承是面向对象程序设计的基石之一。
2.每一个非基本类型的对象都有一个toString()方法。
3.在初始化对象引用时,可以在代码的下列位置进行:
>.在定义对象的地方。这意味着它们总能在构造器被调用之前被初始化。
>.在类的构造器中。
>.在使用这些对象之前,这种方式称为“惰性初始化”。在生成对象不必每次都生成对象的情况下,这种方式可以减少额外的负担。
>.使用实例初始化。(实力初始化在代码中表现为:{}包裹的代码块在静态块之后,构造方法之前被调用)
4.继承并不只是复制基类的接口,当创建了一个子类的对象时,该对象包含了一个基类的子对象。这个子对象和用基类直接创建的对象时一样的。基类的子对象被包装在子类对象内部。
5.编译器会默认为一个类提供无参构造方法。创建子类时,会先调用父类的构造方法。如果基类没有默认的构造器或者想要调用一个带参数的基类构造器,必须用关键字super显式地编写调用基类构造器的语句,并且加上适当的参数。注意:super()语句必须放在子类构造函数的第一行。
6.除了内存以外,不能依赖垃圾回收器去做任何事情。如果需要清理,最好是编写自己的清理方法,但不要使用finalize().
7.组合技术通常用于想在新的类中使用现有类的功能而非它的接口这种情形。实现方式是在新的类中嵌入一个现有类的private对象。 用户只能调用新类的方法,但是在新类的方法内部实际上调用了现有类的方法来实现某些功能。
示例:
现有类 class A{
public void doSth(){};
}
新类: class B{
private A a = new A();
public bMethod(){
a.doSth();
}
}
8.继承技术中最重要的方面是用来表现子类和基类之间的关系。概括为:子类是父类的一种类型。
9.由子类转型成基类,一般称为“向上转型”。向上转型是从一个较专用类型向通用类型转换,所以总是安全的。子类可能比基类含有更多的方法,但是它必须至少具有基类中所含有的方法。
10.final关键字在定义基本类型变量时表示基本类型的数值恒定不变,当final修饰对象引用时,表示对象引用恒定不变。一旦引用被初始化指向一个对象,就无法再把它改为指向另一个对象。但是对象自身是可以被修改的。
11.一个既是static又是final的域只占据一段不能改变的存储空间。
12.final修饰的方法在子类中可以使用,但是不可以被重写。类中所有的private方法都隐式地指定为是final。由于子类无法访问基类的private方法,所以也就无法重写基类的private方法。
13.final修饰的类不能够被继承。final类中的所有方法都隐式地指定为是final的。
1.将一个方法调用同一个方法主体关联起来被称作绑定。 如果在程序执行前进行绑定,叫做前期绑定。当编译器只有一个基类的引用时,无法确定调用哪个方法。
2.后期绑定: 运行时根据对象的类型进行绑定。后期绑定也叫作动态绑定或者运行时绑定。Java中除了static方法和final方法(private方法属于隐式final方法)之外,其他方法都是后期绑定。
3.如果某个方法是静态的,那么它的行为不具有多态性。子类的静态变量和父类的静态变量是占据不同存储空间的。
4.
1.抽象方法仅有声明,没有方法体。包含抽象方法的类叫做抽象类,如果一个类包含一个或多个抽象方法,该类必须被限定为抽象的。
2.如果从一个抽象类继承,子类有两种处理方式:
>.实现基类中所有的抽象方法
>.如果没有实现基类所有的抽象方法,子类也需要定义为抽象类。
3.使某个类成为抽象类不需要所有的方法都是抽象的,只需要将某些方法声明为抽象即可。
4.抽象类中可以没有抽象方法,并且抽象类中的非抽象方法可以有方法体。
5.interface关键字产生一个完全抽象的类,类中的方法没有任何的具体实现。它允许创建者确定方法名、参数列表和返回类型,但是没有任何方法体。接口只提供形式,不提供具体实现。
6.当要实现一个接口时,在接口中被定义的方法必须被定义为public。
7.使用String类型的split()方法,可以作为一种创建数组的快捷方法。
实例:
String word = "Hello,world,Hello,kotlin";
String[] arr = word.split(",");
8.Java中接口是多继承,需要将所有接口名都置于implements关键字后,用逗号将他们一一隔开。可以继承任意多个接口,并可以向上转型为每个接口,因为每个接口都是一个独立类型。
9.使用接口的核心原因:为了能够向上转型为多个基类型(以及由此带来的灵活性)。使用接口的第二个原因和使用抽象类相同:防止客户端程序员创建该类的对象,并确保这仅仅是建立一个接口。
10.通过继承,可以很容易地在接口中添加新的方法声明,还可以通过继承在新接口中组合多个接口。
11.接口中定义的成员变量自动是static和final的(也是public的),所以接口成为了一种很便捷地创建常量组的工具。在Java SE5之前,这是产生和enum具有相同效果的类型的唯一途径。
12.接口中定义的域不能是“空final”,但是可以被非常量表达式初始化。例如:int RANDOM_INT = new Random().nextInt();
13.接口中的域不是接口的一部分,它们的值被存储在该接口的静态存储区内。
14.接口可以嵌套在其他类或者其他接口中。在类中嵌套接口可以拥有public和“包访问”两种可视性。
15.接口也可以被实现为private的,private接口不能在定义它的类之外被实现。
1.可以将一个类的定义放在另一个类的定义内部,这就是内部类。内部类是一种非常有用的特性,它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可视性。 内部类和组合是完全不同的概念。
2.如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象,那么必须具体地指明这个对象的类型。例如:OuterClassName.InnerClassName
3.内部类可以访问外围对象的所有成员,不需要任何特殊条件。内部类还拥有外围类的所有元素的访问权。
4.如果在内部类中想生成对外部类对象的引用,可以使用外部类的名字后面加.this。 例如Outer.this
5.要想直接创建内部类的对象,必须使用外部类的对象进行创建。在外部类对象创建之前不可能创建内部类(非静态)的对象。如果创建的是嵌套类(静态内部类),则不需要对外部类对象的引用。
6.内部类使用场景:当将内部类向上转型为其基类,尤其是转型为一个接口的时候。(从实现了某个接口的对象,得到对此接口的引用,与向上转型为这个对象的基类,实质上效果是一样的。) 这样可以使某个接口的实现完全不可见,并且不可用。所得到的只是指向接口或基类的引用,能够方便地隐藏实现细节。
7.如果希望一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用时final类型的。
8.匿名类和正规的继承相比有些受限,匿名类既可以扩展类,也可以实现接口,但是不能同时进行。并且如果实现接口,只能实现一个。
9.嵌套类:static修饰的内部类。此时,内部类和外部类的对象之间没有联系。
>.要创建嵌套类的对象,不需要外部类的对象。
>.不能从嵌套类的对象中访问非静态的外部类对象。
嵌套类和普通内部类的另一个区别:普通内部类的字段和方法,只能放在类的外部层次上,普通内部类不能有static数据和static字段,也不能包含嵌套类。但是嵌套类可以包含这些东西。
普通内部类不能包含static数据和方法的原因:可以把普通内部类看做外部类的非静态成员,它的初始化必须在外部类对象创建以后才进行,所以要加载内部类,需要先实例化外部类对象。 而Java虚拟机要求所有静态变量必须在对象创建完成之前完成,有矛盾。
普通内部类可以包含 static final 类型的常量:常量存放在内存中的常量池。它的机制和变量不同,加载常量不需要加载类。
10.嵌套类可以作为接口的一部分。放到接口中的任何类都自动是public和static的。
11.一个内部类被嵌套多少层不重要,它能透明地访问所有它嵌入的外围类的所有成员。
12.使用内部类最吸引人的原因是:每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的实现),对于内部类都没有影响。
13.内部类的一些特性:
>.内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外部类对象的信息相互独立。
>.在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。
>.创建内部类对象的时刻并不依赖于外围类对象的创建。
>.内部类是一个独立的个体。
14.局部内部类不能有访问说明符,它不是外围类的一部分,但是它可以访问当前代码块内的常量,以及此外围类的所有成员。
1.通常,程序根据运行时的条件才创建新的对象,但是有时不知道对象的数量及类型。Java中提供了容器类解决这个问题。
2.容器的基本类型: List 、Set、 Map、 Queue。容器类可以自动地调整自己的尺寸。
3.Java容器类类库的用途是“保存对象”,并且分为两个不同的概念:
>.Collection,一个独立元素的序列,这些元素都服从一条或多条规则。List必须按照插入的顺序保存元素,Set不能有重复的元素,Queue按照排队规则来确定对象产生的顺序。
>.Map,一组成对的“键值对”对象,允许使用键来查找值。
4.Arrays.asList()方法接受一个 数组 或是 一个用逗号分隔的元素列表(使用可变参数),并将其转换成一个List对象。 Collections.addAll()方法接受一个Collection对象,以及一个数组或者一个用逗号分隔的列表,将元素添加到Collection中。
5.Collection.addAll()方法只能接受一个Collection对象作为参数(结尾没有s)。因此没有Arrays.asList()或者Collections.addAll()灵活,这两个方法都是用的可变参数列表。
6.List以特定的顺序保存一组元素。Set中元素不能重复,Queue只允许在容器的一端插入对象,并从另一端移除对象。Map中保存的是键值对。
7.List分为两种: ArrayList 和 LinkedList
>.ArrayList 擅长随机访问元素,但是在ArrayList中间插入和删除元素时较慢。
>.LinkedList 擅长在List中间的插入和删除,但是在随机访问时相对较慢。
8.迭代器是一个对象,它的作用是遍历并选择序列中的对象,而使用者不需要知道序列底层的结构。 迭代器通常也被称为轻量级对象:创建它的代价小。因此,对迭代器会有一些限制:
>.Java的Iterator只能单向移动,这个Iterator只能用来:
>.使用iterator()要求容器返回一个Iterator
>.使用next()获得序列中的下一个元素。
>.使用hasNext()检查序列中是否还有元素。
>.使用remove()方法将迭代器返回的元素删除。
9.Iterator可以移除由next()产生的最后一个元素,这意味着在调用remove()方法之前必须先调用next()方法。
10.ListIterator 是Iterator的子类型,它只能用于各种List类的访问。ListIterator可以双向移动,它还可以产生相对于迭代器在列表中指向的当前位置的前一个和后一个元素的索引,并且可以使用set()方法替换它访问过的最后一个元素。
11.“栈”通常是指“后进先出”(LIFO)的容器。有时栈也被称为叠加栈,因为最后一个压入栈的元素第一个弹出栈。Stack是用LinkedList实现的。
12.Set不保存重复的元素,Set最常被使用的是测试 “归属性”,可以很容易地查询某个对象是否在某个Set中。 查找是Set中最重要的操作,HashSet对快速查找进行了优化。
13.Queue是一个典型的先进先出(FIFO)的容器,即从容器的一端放入事物,从另一端取出,并且放入容器的顺序和取出的顺序是相同的。 队列常被当成一种可靠的将对象从程序的某个区域传递到另一个区域的途径。
LinkedList提供了方法以支持队列的行为,并且它实现了Collection接口, 因此LinkedList可以用作Queue的一种实现。
14.PriorityQueue 优先级队列声明下一个弹出元素是最需要的元素(具有最高的优先级)。当在PriorityQueue上调用offer()方法插入一个对象时,这个对象会在队列中被排序。默认的排序将使用对象在队列中的自然顺序,但是可以通过提供自己的Comparator来修改这个顺序。PriorityQueue可以确保获取的元素是队列中优先级最高的元素。
15.PriorityQueue中允许重复,最小的值拥有最高的优先级(如果是String,空格也可以算值,并且比字母的优先级高)。
1.异常情形是指阻止当前方法或作用域继续执行的问题。 与普通问题的区别:普通问题是指,在当前环境下能得到足够的信息,总能处理这个错误。而异常情形,不能继续下去,因为在当前环境无法获得必要的信息来解决问题。 能做的事情是 抛出异常(从当前环境跳出,把问题交给上一级环境)。
2.抛出异常后,将会使用new在堆上创建异常对象。然后当前的执行路径被终止 ,并且从当前环境中弹出对异常对象的引用。此时异常处理机制接管程序,并且寻找一个恰当的地方继续执行程序。这个恰当的地方就是异常处理程序,它的任务是将程序从错误状态中恢复,以使程序要么换一种方式运行,要么继续运行下去。
3.能够抛出任意类型的Throwable对象,它是异常类型的根类。通常,对于不同类型的错误,要抛出相应的异常。错误信息可以保存在异常对象内部或者用异常类的名称来暗示,上一层环境通过这些信息来决定如何处理异常。
4.try块:如果在方法内部抛出了异常(或者方法内部调用的其他方法抛出了异常),这个方法将在抛出异常的过程中结束。要是不希望方法就此结束,可以在方法中设置一个特殊的块来捕获异常。
5.异常处理程序紧跟在try块后,以关键字catch表示。当异常被抛出时,异常处理机制负责搜寻参数与异常类型相匹配的第一个处理程序。然后进入catch语句执行,一旦catch子句结束,则处理程序的查找过程结束。
6.可以只写一个异常处理程序来捕获所有类型的异常。通过捕获异常类型的基类Exception,就可以做到这一点。这将捕获所有异常,所以最好把Exception基类放到处理程序列表的末尾,防止它抢在其他处理程序之前把异常捕获。
7.printStackTrace()方法所提供的信息可以通过getStackTrace()方法来直接访问,这个方法将返回一个由栈轨迹中的元素所构成的数组,其中每个元素都表示栈中的一帧。元素0是栈顶元素,并且是调用序列中的最后一个方法调用(这个Throwable被创建和抛出之处)。数组中的最后一个元素和栈底是调用序列中的第一个方法调用。
8.重新抛出异常会把异常抛给上一级环境中的异常处理程序,同一个try块和后续catch语句会被忽略。此外,异常对象的所有信息都得以保持,所以高一级环境中捕获此异常的处理程序可以从这个异常对象中获取所有信息。
1.String对象是不可变的,每一个看起来会修改String值的方法,实际上都是创建了一个新的String对象,用来包含修改后的字符串内容。
2.String对象具有只读特性,所以指向它的任何引用都不能改变它的值,因此也不会对其他引用造成影响。
3.用于String的 “+” 和 “+=”是Java中仅有的两个重载过的操作符,Java不允许程序员重载任何操作符。
4.StringBuilder允许指定大小,预先指定StringBuilder的大小可以避免多次重新分配缓冲。当为一个类编写toString()方法时,如果字符串操作比较简单,使用String。但是如果要在toString()方法中使用循环,最好创建一个StringBuilder对象。
5.StringBuilder是非线程安全的,StringBuffer是线程安全的。
6. 正则表达式(略过,一块大肉)
1.面向对象编程中的基本目的是:让代码只操纵基类的引用。这样,如果添加一个新类来扩展程序,就不会影响到原来的代码。子类中重写的方法是被动态绑定的,所以即使通过父类的引用来调用,也可以产生正确的行为,这就是多态。
2.RTTI(Run-Time Type Information):运行时类型信息。在Java中,所有的类型转换都是在运行时进行正确性检查的。
3.类型信息在运行时的表示工作由Class对象的特殊对象完成,它包含了与类有关的信息。
4.类是程序的一部分,每个类都有一个Class对象。每当编写并编译了一个新类,就会产生一个Class对象(确切的说,是被保存在一个同名的.class文件中)。为了生成这个类的对象,运行这个程序的Java虚拟机(JVM)将使用被称为“类加载器”的子系统。
5.类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器,它是JVM实现的一部分。原生类加载器加载的是可信类,包括Java API类,它们通常是在本地加载的。
6.所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类。这个证明构造器也是类的静态方法,即使在构造器之前并没有使用static关键字。因此,使用new 操作符创建类的新对象也会被当做类的静态成员的引用。
7.Java程序在它开始运行之前并非被完全加载,其各个部分是在必需时才加载的。类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找.class文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良Java代码(这也是Java中用于安全防范目的的措施之一)。
8.Class对象仅在需要的时候才被加载,static初始化是在类加载时进行的。
9.Class.forName("")方法。 forName()方法是取得Class对象的引用的一种方法,返回一个Class对象的引用。调用forName()方法时候,如果类还没有进行加载,就会加载类(并且会初始化对象)。getClass()方法可以获取Class引用。注意:在传递给forName()的字符串中,必须使用全限定名(包含包名的类名)。
10.为了使用类做的三个准备工作:
>.加载,由类加载器执行。该步骤将查找字节码(通常在classpath所指定的路径中查找,但这是非必需的),并从这些字节码中创建一个class对象。
>.链接。在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必须的话,将解析这个类创建的对其他类的所有引用。
>.初始化。如果该类具有父类,则对父类初始化,执行静态初始化器和静态初始化块。当前类的初始化被延迟到了对静态方法或者非常数静态域进行首次引用时才执行。
11.Java提供了另一种方法来生成对Class对象的引用,即使用类字面常量。例如A.class。 这样做不仅简单,而且更安全,在编译时就会受到检查。也更高效。 类字面常量不仅可以应用于普通的类,还可以应用于接口、数组以及基本数据类型。 对于基本数据类型的包装器类,还有一个标准字段TYPE。TYPE字段是一个引用,指向对应的基本类型的Class对象。例如 Byte.TYPE 注意: 当使用.class来创建对象的引用时,不会自动初始化该Class对象。
12.目前已知的RTTI形式包括:
>.传统的类型转换,由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换,会抛出ClassCastException异常。
>.代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需的信息。
>.RTTI 在 Java中的第三种形式:关键字instanceOf,返回一个布尔值,告诉我们对象是不是某个特定类型的实例。instanceOf只可以将引用与命名类型比较,不能与Class对象比较。
13.interface关键字的一种重要目标就是允许程序员隔离构件,进而降低耦合性。
1.泛型的主要目的之一就是用来指定容器中要持有什么类型的对象,而且由编译器来保证类型的正确性。
2.类型参数:用尖括号括住,放在类名后边。在使用这个类的时候用实际的类型替换此类型参数。
3.元组:将一组对象直接打包存储于其中的一个单一对象,这个容器对象允许读取其中元素,但是不允许向其中放入新的对象。(这个概念也称为数据传送对象)。通常,元组可以具有任意长度,同时,元组中的对象可以是任意不同的类型。注意:元组隐含地保持了其中元素的次序。
4.泛型也可以应用于接口。例如 生成器,这是一种专门负责创建对象的类。实际上这是工厂方法设计模式的一种应用。不过,当使用生成器创建新的对象时,它不需要任何参数,而工厂方法一般需要参数。
5.Java泛型中的一个局限性:基类类型无法作为类型参数。不过Java SE5 具备了自动打包和拆包的功能,可以很方便的在基本类型和其相应的包装器类之间进行转换。
6.泛型方法所在的类可以是泛型类,也可以不是泛型类。是否拥有泛型方法,与其所在的类是否是泛型没有关系。 如果使用泛型方法可以取代将整个类泛型化,那么就应该只使用泛型方法。 对于一个static方法,无法访问泛型方法的类型参数,如果static方法需要使用泛型的类型参数,就必须使其成为泛型方法。
7.由于擦除的原因,将泛型应用于异常时非常受限的。catch语句不能捕获泛型类型的异常,因为在编译期和运行时都必须知道异常的确切类型。泛型类也不能直接或间接继承自Throwable。但是,类型参数可能会在一个方法的throws语句中用到,使得可以编写随检查型异常的类型而发生变化的泛型代码。
1.数组可以通过整型索引值访问它们的元素,并且它们的尺寸不能改变。
2.数组和其他种类的容器之间有三个区别:效率、类型和保存基本类型的能力。在Java中数组是一种效率最高的存储和随机访问对象引用序列的方式。数组就是一个简单的线性序列,这使得元素访问非常快速,付出的代价是数组大小固定,并且在生命周期中不可变。 ArrayList的效率比数组低很多。
3.无论使用哪种类型的数组,数组标识符其实只是一个引用,指向在堆中创建的一个真实对象,这个数组对象用以保存指向其他对象的引用。可以作为数组初始化语法的一部分隐式地创建此对象,或者用new表达式显示地创建。只读成员length是数组对象的一部分(事实上,这是数组对象唯一可访问的字段或者方法),表示此数组对象可以存储多少元素。"[]"语法是访问数组对象的唯一方式。
4.新生成一个数组对象时,其中所有的引用被自动初始化为null,所以检查其中的引用是否为null,即可知道数组的某个位置是否有对象。 基本类型的数组如果是数值型的,就被自动初始化为0,如果是字符型的,就被自动初始化为(char)0,如果是布尔类型的,就被初始化为false。
5.基本类型数组和对象数组一样,只不过基本类型的数组直接存储基本类型数据的值。
6.JavaSe5中,Arrays.deepToString()方法,它可以将多维数组转换为多个String。
7.通常,数组与泛型不能很好地结合。不能实例化具有参数化类型的数组。擦除会移除参数类型信息,而数组必须知道它们所持有的确切类型,以强制保证类型安全。
8.使用Arrays.fill()可以填充整个数组,或者只填充数组的某个区域。但是由于只能用单一的值来调用Arrays.fill(),因此所产生的结果并非特别有用。
9.Arrays的实用方法: 其中六个基本方法:equals()用于比较两个数组是否相等(deepEquals()用于多维数组)。fill()用于填充数组,sort()用于对数组排序。binarySearch()用于在已排序的数组中查找元素,toString()产生数组的String表示,hashCode()产生数组的散列码,所有这些方法对各种基本类型和Object类重载过。Arrays.asList()接受任意的序列或数组作为其参数,并将其转换为List容器。
10.Java标准类库中提供有static方法System.arraycopy(),用它复制数组比用for循环要快很多。System.arraycopy()针对所有类型做了重载。 System.arraycopy()不会执行自动包装和自动拆包。
11.基本类型数组和对象数组都可以复制。如果复制对象数组,那么只是复制了对象的引用——而不是对象本身的拷贝。这被称作浅复制(shallow copy)。
12.Arrays类提供了重载后的equals()方法,用来比较整个数组。同样此方法针对所有基本类型与Object都做了重载。数组相等的条件是元素个数必须相等,并且对应位置的元素也相等。
13.程序设计的基本目标是“将保持不变的事物与会发生改变的事物相分离”,通过使用“策略”,可以将“会发生变化的代码”封装在单独的类中(策略对象),你可以将策略对象传递给总是相同的代码,这些代码将使用策略来完成其算法。
14.Java有两种方式来提供比较功能。第一种是实现java.lang.Comparable接口,使类具有“天生的”比较能力,这个接口只有一个compareTo()方法。这个方法接收一个Object类型的参数,如果当前对象小于参数则返回负值,如果相等则返回0,如果当前对象大于参数则返回正数。
15.数组排序:使用内置的排序算法,就可以对任意的基本类型数组排序,也可以对任意的对象数组进行排序,只要该对象实现了Comparable接口或具有相关联的Comparator。
16.String排序算法依据词典编排顺序排序,所以大写字母开头的词都放在前面输出,然后才是小写字母开头的词。如果想忽略大小写字母将单词都放在一起排序,那么可以使用String.CASE_INSENSTIVE_ORDER.
17.Java标准类库中的排序算法针对正排序的特殊类型进行了优化——针对基本类型设计的“快速排序”,以及针对对象设计的“稳定归并排序”,所以无需担心排序的性能,除非你可以证明排序部分的确是程序效率的瓶颈。
1.Set是自己维护内部顺序的(这使得随机访问变得没有意义),如果想检查Collection中的元素,必须使用迭代器。
2.如果一个操作是未获支持的,那么在实现接口的时候可能导致UnsupportedOperationException异常,而不是将产品程序交给客户以后才出现异常。
3.Set:存入Set的每个元素必须是唯一的,因为Set不保存重复元素。加入Set的元素必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口,Set接口不保证维护元素的次序。
4.HashSet:为快速查找设计的Set,存入HashSet的元素必须定义hashCode()。 如果没有其他限制,应该默认选择使用HashSet,它对速度进行了优化。
5.TreeSet:保持次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。元素必须实现Comparable接口。
6.LinkedHashSet:具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历时,结果会按元素插入的次序显示。元素也必须定义hashCode()方法。
7.必须为散列存储和树型存储都创建一个equals()方法,但是hashCode()只有在这个类将会被置为HashSet或者LinkedHashSet中时才是必须的。对于良好的编程风格而言,应该在重写equals()方法时,总是同时重写hashCode()方法。
8.SortedSet中的元素可以保证处于排序状态。Comparator comparator()返回当前Set使用的Comparator,或者返回null,表示以自然方式排序。
>.Object first() 返回容器的第一个元素。
>.Object last() 返回容器的最末一个元素。
>.SortedSet subSet(fromElement, toElement) 生成此Set的子集,范围从fromElement(包含)到toElement(不包含)。
>.SortedSet headSet(toElement) 生成此Set的子集,由小于toElement的元素组成。
>.SortedSet tailSet(fromElement) 生成此Set的子集,由大于或等于fromElement的元素组成。
9.除了并发应用,Queue在JavaSe 5中仅有的两个实现是LinkedList和PriorityQueue,它们的差异在于排序行为而不是性能。 除了优先级队列,Queue将精确地按照元素被置于Queue中的顺序产生它们。
10.双向队列允许在任何一端添加或移除元素。
11.Map中的基本方法是put()和get(),但是为了容易显示,toString()方法被重写为可以打印键值对。get()方法使用的可能是能想象到的效率最差的方法来定位值,从数组的头部开始,使用equals()方法依次比较键。但是这里的关键是简单。
12.性能是Map中的一个重要问题,当在get()中使用线性搜索时,执行速度会相当的慢,而这正是HashMap提高速度的地方。HashMap使用了特殊的值,称作散列码,来取代对键的缓慢搜索。散列码是“相对唯一的”、用以代表对象的int值,它是通过将该对象的某些信息进行转换而生成的。hashCode()是根类Object中的方法,因此所有Java对象都能产生散列码,HashMap就是使用对象的hashCode()进行快速查询的,这个方法能够快速提高性能。
13.HashMap :Map基于散列表的实现(它取代了HashTable)。插入和查询“键值对”的开销是固定的。可以通过构造器设置容量和负载因子,以调整容器的性能。
14.LinkedHashMap: 类似于HashMap,但是迭代遍历时,取得“键值对”的顺序是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一点,而在迭代访问时更快,因为它使用链表维护内部次序。
15.TreeMap: 基于红黑树的实现。 查看“键”或“键值对”时,它们会被排序,(次序由Comparable或Comparator决定)。TreeMap的特点在于,所得到的结果是经过排序的。TreeMap是唯一带有subMap()方法的Map,它可以返回一个子树。
16.WeakHashMap: 弱键(weak key)映射,允许释放映射所指向的对象;这是为解决某类特殊问题而设计的。如果映射之外没有引用指向某个“键”,则此“键”可以被垃圾回收器回收。
17.ConcurrentHashMap: 一种线程安全的Map ,它不涉及同步加锁。
18.IdentityHashMap : 使用 == 代替equals()对键进行比较的散列映射。散列是映射中存储元素时最常用的方式。
19.SortedMap:使用SortedMap(TreeMap是其现阶段的唯一实现),可以确保键处于排序状态。这使得它具有额外的功能,这些功能由SortedMap接口中的下列方法提供:
>.Comparator comparator(): 返回当前Map使用的Comparator 或者 返回 null,表示以自然方式排序。
>.T firstKey() 返回Map中的第一个键。
>.T lastKey() 返回Map中的最末一个键。
>.SortedMap subMap(fromKey, toKey) 生成此Map的子集,范围由fromKey(包含),到toKey(不包含)的键确定。
>.SortedMap headMap(toKey)生成此Map的子集,由键小于toKey的所有键值对组成。
>.SortedMap tailMap(fromKey) 生成此Map的子集,由键大于或等于fromKey的所有键值对组成。
20.为了提高速度,LinkedHashMap散列化所有的元素,但是在遍历键值对时,却又以元素的插入顺序返回键值对。此外,可以在构造器中设定LinkedHashMap,使之采用基于访问的最近最少使用(LRU)算法,于是没有被访问过的(可被看做要删除的)元素就会出现在队列的前面。对于需要定期清理元素以节省空间的程序来说,此功能使得程序很容易得以实现。
21.正确的equals()方法必须满足五个条件:
>.自反性。 对任意x, x.equals(x) 一定返回true。
>.对称性。 对任意x和y ,如果y.equals(x)返回true,则x.equals(y)也返回true。
>.传递性。对任意x、y、z 如果x.equals(y)返回true,y.equals(z) 返回true, 则 x.equals(z)一定返回true。
>.一致性。对任意x和y,如果对象中用于等价比较的信息没有改变,那么无论调用x.equals(y)多少次,返回的结果应该保持一致,要么一致是true,要么一直是false。
>.对任何不是null的x,x.equals(null)一定返回false。
22.使用散列的目的在于:想要使用一个对象来查找另一个对象。不过使用TreeMap或者自己实现的Map也可以达到这个目的。
23.Map.entrySet()方法必须产生一个Map.Entry对象集。但是Map.Entry是一个接口,用来描述依赖于实现的结构,因此如果想要创建自己的Map类型,必须同时定义Map.Entry实现。entrySet()使用了HashSet来保存键值对。
24.散列的价值在于速度,散列使得查询得以快速进行。由于瓶颈位于键的查询速度,因此解决方案之一就是保持键的排序状态,然后使用Collections.binarySearch()进行查询。
25.散列将键保存在某处,以便能够很快找到。存储一组元素最快的数据结构是数组,所以使用它来表示键的信息(注意是键的信息,而不是键本身)。但是因为数组不能调整容量,因此就有一个问题:我们希望在Map中保存数量不确定的值,但是如果键的数量被数组容量限制的话,解决方法是:数组并不保存键本身。而是通过键对象生成一个数字,将其作为数组的下标。这个数字叫做散列码。
26. ArrayList底层由数组支持,LinkedList是由双向链表实现的,其中的每个对象包含数据的同时还包含指向链表中前一个与后一个元素的引用。如果经常在表中插入或删除元素,LinkedList比较合适。否则应该使用ArrayList。
27.可以根据需要选择TreeSet、HashSet或者LinkedHashSet。 HashSet 的性能基本上总是比TreeSet好,特别是在添加和查询元素时,而这两个操作也是最重要的操作。TreeSet存在的唯一原因是它可以维持元素的排序状态。所以,只有当需要一个排好序的Set时,才应该使用TreeSet。因为其内部结构支持排序,并且因为迭代是我们更有可能执行的操作,所以用TreeSet迭代通常比HashSet更快。
28.除了IdentityHashMap,所有的Map实现的插入操作都会随Map尺寸的变大而明显变慢。但是查找的代价通常要比插入小得多。 TreeMap通常要比HashMap慢。与使用TreeSet一样,TreeMap是一种创建有序列表的方式。树的行为是:总是保证有序,并且不必进行特殊的排序。一旦填充了一个TreeMap,就可以调用keySet()方法来获取键的Set视图,然后调用toArray()来产生由这些键构成的数组。
29.当使用Map时,第一选择应该是HashMap,只有在你要求Map始终保持有序时,才需要使用TreeMap。
30.LinkedHashMap在插入时比HashMap慢一点,因为它维护散列数据结构的同时还要维护链表(以保持插入顺序)。正是由于这个链表,使得其迭代速度更快。
31.HashMap的性能因子: 我们可以通过手工调整HashMap来提高其性能,从而满足我们特定应用的需求。涉及到以下一些术语:
>.容量:表中的桶位数。
>.初始容量:表在创建时所拥有的桶位数。HashMap和HashSet都具有允许指定初始容量的构造器。
>.尺寸:表中当前存储的项数。
>.负载因子:尺寸/容量 空表的负载因子是0,半满表的负载因子是0.5,负载轻的表产生冲突的可能性比较小,因此对于插入和查找都是最理想的。HashMap和HashSet都具有允许指定负载因子的构造器,表示当负载情况达到该负载因子的水平时,容器将自动增加其容量(桶位数),实现方式是使容量大致加倍,并重新将现有对象分布到新的桶位中(再散列)。
>.HashMap使用的默认负载因子是0.75(只有当表达到3/4满时,才进行再散列),这个因子在时间和控件代价之间达到了平衡。更高的负载因子可以降低表所需的空间,但是会增加查找代价,查找是我们大多数时间做的操作。
32.对象是可获得的,是指此对象可在程序中的某处找到。如果一个对象是可获得的,垃圾回收器就不能释放它,因为它仍然为你的程序所用。如果一个对象不是“可获得”的,那么你的程序将无法使用它,所以将其回收是安全的。
33.如果想继续持有某个对象的引用,希望以后还能访问到该对象,但是也希望能够允许垃圾回收器释放它,这时应该使用Reference对象。这样,你可以继续使用该对象,而在内存耗尽的时候又允许释放该对象。以Reference对象作为你和普通引用之间的媒介(代理),一定不能有普通的引用指向那个对象,这样就能达到上述目的。普通引用是指没有经过Reference对象包装过的引用。
SoftReference、WeakReference和PhantomReference由强到弱排列,对应不同级别的“可获得性”。SoftReference用以实现内存敏感的高速缓存。 WeakReference是为实现“规范映射”而设计的,它不妨碍垃圾回收器回收映射的“键”或“值”。“规范映射”中对象的实例可以在程序的多处被同时使用,以节省存储空间。Plantomreference用以调度回收前的清理工作,它比Java终止机制更灵活。
使用SoftReference和WeakReference时,可以选择是否将它们放入ReferenceQueue(用作回收前清理工作的工具)。而PhantomReference只能依赖于ReferenceQueue。
34.Vector是唯一可以自我扩展的序列,在Java1.0/1.1中被大量使用。Enumeration接口比Iterator小。
1.File(文件)既能代表一个特定文件的名称,又能代表一个目录下的一组文件的名称。如果它指的是一个文件集,我们就可以对此集合调用list()方法,这个方法会返回一个字符数组。
2.Arrays.sort(arr,String.CASE_INSENSITIVE_ORDER),将数组按字母顺序排序,忽略大小写。
3.File类不仅仅只代表存在的文件或者目录。也可以用File对象来创建新的目录或者尚不存在的整个目录路径。我们还可以查看文件的特性(例如:大小、最后修改日期、读/写),检查某个File对象代表的是一个文件还是一个目录,并可以删除文件。
4.流代表任何有能力产出数据的数据源对象或者是有能力接收数据的接收端对象。“流”屏蔽了实际的I/O设备中处理数据的细节。Java中I/O类分成输入和输出两部分,通过继承,任何自InputStream或Reader派生而来的类都含有名为read()的基本方法,用于读取单个字节或者字节数组。 任何自OutputStream 或 Writer派生而来的类都含有名为write()的基本方法,用于写单个字节或者字节数组。 但是通常我们不会用到这些方法,它们之所以存在是因为别的类可以使用它们,以便提供更有用的接口。
5.在Java1.0中,类库的设计者限定与输入有关的类都应该从InputStream继承,与输出有关的类都应该从OutputStream继承。
6.InputStream的作用是用来表示那些从不同的数据源产生输入的类。 这些数据源包括:字节数组、String对象、文件、“管道”(工作方式与实际管道相似,从一端输入,从另一端输出)、一个由其他种类的流组成的序列(以便我们可以将它们收集合并到一个流内)、其他数据源(如Internet连接等)。
每一种数据源都有相应的InputStream子类。FilterInputStream也属于一种InputStream,为“装饰器”类提供基类,其中,装饰器可以把属性或有用的接口与输入流连接在一起。
7.OutputStream类型的类决定了输出所要去往的目标:字节数组(但不是String)、文件或管道。
8.InputStream和OutputStream是面向字节形式的I/O的。Reader和Writer提供兼容Unicode与面向字符的I/O功能。
9.有时我们必须把来自于“字节”层次结构中的类和“字符”层次结构中的类结合起来使用。为了实现这个目的,要用到“适配器”类:InputStreamReader可以把InputStream转换为Reader, OutputStreamWriter 可以把OutputStream转换为Writer。
10.无论我们何时使用readLine(),都不应该使用DataInputStream(编译器会报错),而应该使用BufferedReader。除了这一点,DataInputStream仍是I/O类库中的首选成员。
11.FileWriter对象可以向文件写入数据。创建与指定文件连接的FileWriter,我们通常会用BufferedWriter将其包装起来用以缓冲输出(尝试移除此包装来感受对性能的影响——缓冲往往能显著地增加I/O操作的性能)。
12.System.out事先被包装成printStream对象,System.err也被包装成printStream对象,System.in是一个没有被包装过的未经加工的InputStream。
13.Java的对象序列化将那些实现了Serializable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。利用对象序列化可以实现轻量级持久性。
14.对象序列化的概念主要是为了支持两种主要特性:
>.Java的远程方法调用,它使存活于其他计算机上的对象使用起来就像是存活在本机上一样。当向远程对象发送消息时,需要通过对象序列化来传输参数和返回值。
>.对Java Bean来说,对象的序列化也是必须的,使用一个bean时,一般情况下是在设计阶段对它的状态信息进行配置。这种状态信息必须保存下来,并在程序启动时进行后期恢复。这种具体工作就是由序列化完成的。
1.关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用这是一种非常有用的功能。
2.调用enum的values()方法,可以遍历enum实例。values()方法返回enum实例的数组,而且该数组中的元素严格保持其在enum中声明时的顺序,因此可以在循环中使用values()返回的数组。
3.调用enum的ordinal()方法返回一个int值,这是每个enum实例在声明时的次序,从0开始。可以使用==来比较enum实例,编译器会自动提供equals()方法和hashCode()方法。Enum类实现了Comparable接口,所以它具有compareTo()方法。同时,它还实现了Serializable接口。
4.除了不能继承自一个enum之外,我们基本上可以将enum看作一个常规的类。也就是说,我们可以向enum中添加方法,enum中甚至可以有main方法。
5.在switch语句中使用enum,是enum提供的一项非常便利的功能。在switch中一般只能使用整数值(JDK1.7之后,可以跟String类型),而枚举实例天生就具备整数值的次序,并且可以通过ordinal()方法取得其次序(编译器帮我们做了类似的工作),因此我们可以在switch语句中使用enum。
6.enum中values()方法是由编译器添加的static方法。
7.所有的enum都是继承自java.lang.Enum类,Java不支持多继承,所以,enum不能再继承其他类,但是在创建新的enum时,可以同时实现多个接口。
8.JavaSE 5中引入了EnumSet,是为了通过enum创建一种替代品,以替代传统的基于int的“位标志”。这种标志可以用来表示某种“开关”信息,不过,使用这种标志,我们最终操作的只是一些bit,而不是这些bit要表达的概念,因此很容易写出让人难理解的代码。使用EnumSet的优点是:它在说明一个二进制位是否存在时,具有更好的表达能力,并且无需担心性能。
9.EnumSet的基础是long,一个long值有64位,而一个enum实例只需一位bit表示其是否存在,也就是说,在不超过一个long的表达能力的情况下,EnumSet最多可以应用于不超过64个元素的enum。 当EnumSet应用于多过64个元素的enum时,必要时会增加一个long。
10.EnumMap是一种特殊的Map,它要求其中的键(key)必须来自一个enum,由于enum本身的限制,所以EnumMap内部可以由数组实现。因此EnumMap的速度很快, 我们可以放心地使用enum实例在EnumMap中进行查找操作。但是我们只能将enum的实例作为键来调用put()方法,其他操作和一般Map差不多。
11.枚举类型非常适合用来创建状态机。一个状态机可以具有有限个特定的状态,它通常根据输入,从一个状态转移到下一个状态,不过也可能存在瞬时状态,而一旦任务执行结束,状态机就会立刻离开瞬时状态。
1.注解(也称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据。
2.注解的语法比较简单,除了@符号的使用之外,它基本与Java固有的语法一致。JavaSE5中内置了三种,定义在java.lang中的注解:
>.Override,表示当前的方法定义将覆盖父类中的方法。如果不小心拼写错误,或者方法签名对不上被覆盖的方法,编译器会发出错误提示。
>.Deprecated,如果程序员使用了注解为它的元素,那么编译器会发出警告信息。
>.SuppressWarnings,关闭不当的编译器警告信息。在Java SE5之前的版本中也可以使用该注解,不过会被忽略不起作用。
3.注解的定义看起来像接口的定义,注解也会编译成class文件。 除了@符号以外,注解的定义很像一个空的接口。定义注解时,会需要一些元注解,如@Target 和 @Retention。 @Target用来定义注解将用于什么地方。@Retention用来定义该注解在哪一个级别可以用(在源代码中 SOURCE、类文件中 CLASS、运行时 RUNTIME)。 注解的元素看起来像接口的方法,唯一区别就是可以指定默认值。 没有元素的注解称为标记注解。
4.注解的元素在使用时表现为 名 —— 值 对的形式,并需要置于注解声明之后的括号内。
5.Java目前只内置了三种标准注解,以及四种元注解。元注解专职负责注解其他的注解。
>.@Target :表示该注解可以用于什么地方。可能的ElementType参数包括:CONSTRUCTOR:构造器的声明、 FIELD:域声明(包括enum实例)、LOCAL_VARIALBE:局部变量声明、METHOD:方法声明、PACKAGE:包声明、PARAMETER:参数声明、TYPE:类/接口/enum声明
>.@Retention :表示需要什么级别保存该注解信息。 可选的RetentionPolicy参数包括: SOURCE、CLASS、RUNTIME。
>.@Documented : 将此注解包含在Javadoc中
>.Inherited : 允许子类继承父类中的注解。
6.使用注解的过程中,很重要的一个部分就是创建与使用注解处理器。
7.注解元素可用的类型包括: 所有基本类型、String、Class、enum、Annotation、以上类型的数组。
8.注解不支持继承。
9.单元测试是对类中的每个方法提供一个或多个测试的一种实践,其目的是为了有规律地测试一个类的各个部分是否具备正确的行为。
1.如果想要一个程序运行得更快,那么可以将其断开为多个片段,在单独的处理器上运行每个片段。并发是用于多处理器编程的基本工具。并发通常是提高运行在单处理器上的程序的性能。
2.如果程序中的某个任务因为该程序控制范围之外的某些条件(通常是I/O)而导致不能继续执行,那么我们就说这个任务或线程阻塞了。如果没有并发,则整个程序都将停止下来,直至外部条件发生变化。
3.实现并发最直接的方式是在操作系统级别使用进程。进程是运行在它自己的地址空间内的自包容程序。多任务操作系统可以通过周期性地将cpu从一个进程切换到另一个进程来实现同时运行多个(进程)程序。操作系统通常会将进程互相分隔开,因此它们不会彼此干涉,这使得进程编程相对容易一些。
4.Java的线程机制是抢占式的,这表示调度机制会周期性地中断线程,将上下文切换到另一个线程,从而为每个线程都提供时间片,使得每个线程都会分配到数量合理的时间去驱动它的任务。
5.并发编程使我们可以将程序划分为多个分离的、独立运行的任务。通过使用多线程机制,这些独立任务(也被称为子任务)中的每一个都将由执行线程来驱动。一个线程就是在进程中的一个单一的顺序控制流,因此,单个进程可以拥有多个并发执行的任务,但是你的程序使得每个任务都好像有自己的CPU一样,底层机制是切分CPU时间。
6.线程可以驱动任务,因此需要一种描述任务的方式,这可以由Runnable接口来提供。要想定义任务,只需实现Runnable接口并编写run()方法,使得该任务可以执行命令。 任务的run方法通常总会有某种形式的循环,使得任务一直运行下去直到不再需要,所以要设定跳出循环的条件(有一种选择是直接从run()返回)。通常,run()被写成无限循环的形式,这意味着,除非有某个条件使得run()终止,否则它将永远运行下去。 在run()方法中对静态方法Thread.yield()的调用是对线程调度器(Java线程机制的一部分,可以将CPU从一个线程转移到另一个线程)的一种建议。 当从Runnable导出一个类时,它必须具有run()方法,但是这个方法并无特殊之处——它不会产生任何内在的线程能力,要实现线程行为,必须显式地将一个任务附着到线程上。
7.Thread构造器只需要一个Runnable对象。调用Thread的start()方法为该线程执行必须的初始化操作,然后调用Runnable的run()方法,以便在这个新线程中启动该任务。
8.线程的优先级将该线程的重要性传递给了调度器。尽管CPU处理现有线程集的顺序是不确定的,但是调度器将倾向于让优先权高的线程先执行(优先权不会导致死锁)。优先级较低的线程仅仅是执行的频率比较低。
9.在使用并发时,将域设置为private是非常重要的,否则,sychronized关键字不能防止其他任务直接访问域,这样就会产生冲突。
10.原子操作是不能被线程调度机制中断的操作,一旦操作开始,那么它一定可以在可能发生的“上下文切换”之前(切换到其他线程)执行完毕。
11.如果将一个域声明为violatile的,那么只要对这个域产生了写操作,那么所有的读操作就都可以看到这个修改。volatile域会立即被写入到主存中。
12.一个线程可以处于以下四种状态之一:
>.新建(new):当线程被创建时,它只会短暂的处于这种状态。此时它已经分配了必需的系统资源,并执行了初始化。此刻线程已经有资格获得CPU时间了,之后调度器将把这个线程转变为可运行状态或阻塞状态。
>.就绪(Runnable):在这种状态下,只要调度器把时间片分配给线程,线程就可以运行。也就是说,在任意时刻,线程可以运行也可以不运行。只要调度器能分配时间片给线程,它就可以运行。
>.阻塞(Blocked):线程能够运行,但有某个条件阻止它的运行。当线程处于阻塞状态时,调度器将忽略线程,不会分配给线程任何CPU时间。直到线程重新进入就绪状态,它才有可能执行操作。
>.死亡(Dead):处于死亡或终止状态的线程将不再是可调度的,并且再也不会得到CPU的时间,它的任务已结束,或不再是可运行的。任务死亡的通常方式是从run()方法返回,但是任务的线程还可以被中断。
13.一个线程进入阻塞状态可能有以下原因:
>.通过调用sleep() 使任务进入休眠状态,这种情况下,任务在指定的时间内不会运行。
>.通过调用wait()使线程挂起。直到线程得到了notify()或notifyAll()消息,线程才会进入就绪状态。
>.任务在等待某个输入/输出完成。
>.任务试图在某个对象上调用其同步控制方法,但是对象锁不可用,因为另一个任务已经获得了锁。
14.Thread类包含interrupt()方法,因此可以终止被阻塞的任务,这个方法将设置线程的中断状态。
15.Sleep()、yield() 调用的时候,锁并没有释放,wait()方法调用时,线程的执行被挂起,对象上的锁被释放。