Java提供了两个用于高精度技术的类:BigInteger和BigDecimal。虽然它们大体上属于"包装器类"的范畴,但二者都没有对应的基本类型。
这两个类包含的方法,提供的操作与对基本类型所能执行的操作相似。也就是说,能作用于int或float的操作,也同样能作用于BigInteger
和BigDecimal。只不过必须以方法调用的方式取代运算符方式来实现。由于这么做复杂了许多,所以运算速度会比较慢。
以速度换取了精度。
BigInteger支持任意精度的整数。也就是说,在运算中,可以精确地表示任何大小的整数值,而不会丢失任何信息。
BigDecimal支持任何精度的定点数。例如,可以用它进行精确的货币计算。
自动递增和递减
对于前缀递增和前缀递减(如++a 或 --a),会先执行运算,再生成值。而对于后缀递增和后缀递减(如a++或a--),
会先生成值,再执行运算。
public class Demo001 { public static void main(String[] args) { int i = 1; System.out.println(i);//1 System.out.println(++i);//2 System.out.println(i++);//2 System.out.println(i);//3 System.out.println(--i);//2 System.out.println(i--);//2 System.out.println(i);//1 } }
大多数Java类库都实现了equals()方法,以便用来比较对象的内容,而非比较对象的引用。
当使用逻辑操作符时,我们会遇到一种"短路"现象。即一旦能够明确无误地确定整个表达式的值,就不再计算表达式余下
部分了。因此,整个逻辑表达式靠后的部分有可能不会被运算。
对于布尔值,按位操作符具有与逻辑操作符相同的效果,只是它们不会中途"短路"。
return
如果在返回void的方法中没有return语句。那么在该方法的结尾处会有一个隐式的return,因此
在方法中并非总是必须要有一个return语句。但是,如果一个方法声明它将返回void之外的其它东西,
那么必须确保每一条代码路径都将返回一个值。
break和continue
在任何迭代语句的主体部分,都可用break和continue控制循环的流程。
switch
switch语句是实现多路选择的一种干净利落的方法。但它要求使用一个选择因子,并且必须是int或char
那样的整数值。例如,假如将一个字符串或者浮点数作为选择因子使用,那么它们在switch语句里是不会
工作的。对于非整数类型,则必须使用一系列if语句。Java SE5的新特性enum可以帮助我们减弱这种限制,
因为enum可以和switch协调工作。
在case语句中,使用单引号引起的字符也会产生用于比较的整数值。
尽管可以用this调用一个构造器,但却不能调用两个。此外,必须将构造器调用置于最起始处,
否则编译器会报错。除构造器外,编译器禁止在其他任何方法中调用构造器。
再论组合与继承
在面向对象编程中,生成和使用程序代码最有可能采用的方法就是直接将数据和方法包装进一个类中,并使
该类的对象。也可以运用组合技术使用现有类来开发新的类;而继承技术其实是不太常用的。因此,尽管在
教授OOP的过程中我们多次强调继承,但这并不意味着要尽可能使用它。相反,应当慎用这一技术,其使用
场合仅限于你确信使用该技术确实有效的情况。到底是该用组合还是继承,一个最清晰的判断办法就是问一问
自己是否需要从新类向基类进行向上转型。如果必须向上转型,则继承是必要的;但如果不需要,则应当好好
考虑自己是否需要向上转型。
编写构造器时有一条有效的准则:"用尽可能简单的方法使对象进入正常状态;如果可以的话,避免调用其他
方法"。在构造器内唯一能够安全调用的那些方法是基类中的final方法(也适用于private方法,它们自动属
于final方法)。这些方法不能被覆盖,因此也就不会出现上述令人惊讶的问题。
使用接口的核心原因:为了能够向上转型为多个(多实现)基类型(以及由此而带来的灵活性),使用接口的
第二个原因却是与使用抽象基类相同:防止客户端程序员创建该类的对象,并确保这仅仅是建立一个接口。
接口与工厂
接口是实现多重继承的途径,而生成遵循某个接口的对象的典型方式就是工厂方法设计模式。
这与直接调用构造器不同,我们在工厂对象上调用的是创建方法,而该工厂对象就生成接口的某个实现
的对象。
HashSet、TreeSet和LinkedHashSet都是Set类型,输出显示在Set中,每个相同的项只有保存一次,但是
输出也显示了不同的Set实现存储元素的方式也不同。HashSet使用的是相当复杂的方式来存储元素的,
。如果存储顺序很重要,那么可以使用TreeSet,它按照比较结果的升序保存结果;或者使用LinkedHashSet,
它按照被添加的顺序保存对象。
键和值在Map中的保存顺序并不是它们的插入顺序,因为HashMap实现使用的是一种非常快的算法来控制顺序。
本例使用了三种基本风格的Map:HashMap、TreeMap和LinkedHashMap。与HashSet一样,HashMap也提供了最快
的查找技术,也没有按照任何明显的顺序来保存其元素。TreeMap按照比较结果的升序保存键,而LinkedHashMap
则按照插入顺序保存键,同时还保留了HashMap的查询速度。
HashSet输出的顺序没有任何规律可循,这是因为出于速度原因的考虑,HashSet使用了散列。
HashSet所维护的顺序与TreeSet或LinkedHashSet都不同,因为它们的实现具有不同的元素存储方式。
TreeSet将元素存储在红-黑树数据结构中,而HashSet使用的是散列函数。LinkedHashSet因为查询
速度的原因也使用了散列,但是看起来它使用了链表来维护元素的插入顺序。
PriorityQueue
Integer、String和Character可以与PriorityQueue一起工作,因为这些类已经内建了自然排序。
如果你想在PriorityQueue中使用自己的类,就必须包括额外的功能以产生自然排序,或者必须
提供自己的Comparator。
Java SE5引入了新的被称为Iterable的接口,该接口包含一个能够产生Iterator的iterator()
方法,并且Iterable接口被foreach用来在序列中移动。因此若果你创建了任何实现Iterable的
类,都可以将它用于foreach语句中,实例如下所示:
public class IterableClass implements Iterable<String>{ protected String[] words = ("And that is how" + "we know the Earth to be banana-shaped.").split(" "); @Override public Iterator<String> iterator() { // TODO Auto-generated method stub return new Iterator<String>(){ private int index = 0; @Override public boolean hasNext() { return index < words.length; } @Override public String next() { return words[index++]; } @Override public void remove() { } }; } public static void main(String[] args) { for(String s : new IterableClass()){ System.out.println(s + " "); } } }容器不能持有基本类型,但是自动包装机制会仔细地执行基本类型到容器中所持有的包装器类型
之间的双向转换。
各种Queue以及栈的行为,由LinkedList提供支持。
Map是一种将对象(而非数字)与对象相关联的设计。HashMap设计用来快速访问;而TreeMap保持
"键"始终处于排序状态,所以没有HashMap快。LinkedHashMap保持元素插入的顺序,但是也通过
散列提供了快速访问能力。
Set不接受重复元素。HashSet提供最快的查询速度,而TreeSet保持元素处于排序状态。LinkedHashSet
以插入顺序保存元素。
显式地创建StringBuilder还允许你预先为其指定大小。如果你已经知道最终的字符串大概有多长,
那预先指定StringBuilder的大小可以避免多次重新分配缓冲。
如果在其他语言中使用过正则表达式,那你立刻就能发现Java对反斜线\的不同处理。在其他语言中,\\表示
"我想要正则表达式中插入一个普通的(字面上)反斜线,请不要给它任何特殊的意义。"而在Java中,\\的
意思是"我要插入一个正则表达式的反斜线,所以其后的字符具有特殊的意义。" 例如,如果你想表示一位数字,
那么正则表达式应该是\\d。如果你想插入一个普通的反斜线,则应该这样\\\\。不过换行和制表符之类的东西
只需要使用单反斜线:\n\t。
String类还自带了一个非常有用的正则表达式工具 split()方法,其功能是"将字符串从正则表达式匹配的
地方切开"
String.split()还有一个重载的版本,它允许你限制字符串分割的次数。
String类自带的最后的一个正则表达式工具是"替换"。你可以只替换正则表达式第一个匹配的子串,或是
替换所有匹配的地方。
所有的类都是在对其第一次使用时,动态加载到JVM中的。类加载器首先检查这个
类的Class对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找
.class文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,
并且不包含不良Java代码(这是Java中用于安全防范目的的措施之一)
类字面常量
Java还提供了另一种方法来生成对Class对象的引用,即使用类字面常量。
FancyToy.class:这样做不仅更简单,而且更安全,因为它在编译时就会受到检查
(因此不需要置于try语句块中)。并且它根除了forName()方法的调用,所以也
更高效。
初始化有效地实现了尽可能的"惰性"。从对initable引用的创建中可以看到,仅使用
.class语法来获得对类的引用不会引发初始化。但是,为了产生Class引用,Class.forName()
立即就进行了初始化。
如果一个static final值是"编译期常量",那么这个值不需要进行初始化就可以被读取。但是,
如果只是将一个域设置为static或final的,还不足以确保这种行为。