老夫一个寒假就干了这么点事(王老师:怪我咯……
3 Java 的基本程序设计架构
- 整形(int)的范围与机器无关,都是 32 位、4 字节、-20 亿~+20 亿。解决了移植性的问题。
- long 的后缀是 l,16 进制前缀是 0x,8 进制前缀 0(少用),2 进制前缀 0b,并且从 Java7 开始可以在数字中加入下划线,增强易读性。
- Java 没有无符号类型。
- float,占 4 个字节,后缀 F,单精度数,少用。double,占 8 个字节,后缀 D,双精度数,常用。
- 在数值计算中若要不含任何误差,应该使用 BigDecimal 类。
- strictfp 关键字表征了在该方法体中所有浮点运算都将使用严格的浮点计算。
- 与运算在第一个表达式为假时不会继续计算后续的表达式;或运算在第一个表达式为真的时候不会继续后续的表达式。
- &、| 运算法作用于布尔值时,同 &&、||,只是没有短路的说法,会计算完所有的表达式。
- 移位运算中,若移位位数多于该数的总位数,会循环移位,因此需要在移位运算中对于移位数进行模运算。
- String 是不可修改其中某一位的。是不可变字符串。只能修改对于该字符串的引用,变为对于另外字符串的引用。这样的好处是可以将相同的字符串进行共享。虚拟机一般情况下只将常量字符串共享,拼接(+、substring)得到的字符串将不会共享。当然可以设置虚拟机将所有的字符串共享。
- Scanner 是一个标准的读取输入方法,用法为:
1 | Scanner in = new Scanner(System.in); |
- Console 也是一个标准的读取输入方法。用法为:
1 | Console con = System.console(); |
- 在使用 break、continue 语句时,最好在 break、continue 跳出、重复的循环体之前加上标签。
1 | read_data: |
#4 对象与类
类与类之间的关系有:
- 依赖(Use-a)如果一个类的方法操纵另一个类的对象,则称一个类依赖于另一个类。我们应该尽量使类之间的相互依赖减到最小,如果 A 与 B 之间没有依赖,那么意味着 A、B 的改变不会造成任何 Bug,即让类之间的耦合度最小。
- 聚合(Has-a)类 A 的对象中包含了类 B 的对象。
- 继承(Is-a)
Java 中虽有的对象变量都是对堆中的对象的引用。
不要编写返回可变对象的访问器方法。如果需要返回一个可变对象的引用,应该首先对他进行 clone。
一个方法可以访问所属类的所有对象的私有数据。如类 A 的对象 a 中的方法可以访问同为类 A 的另一个对象 b 的私有数据。
所有类的对象会共享同一个静态引用。
在类中添加 Main 方法,是一个进行单元测试的好方法。
传入方法的参数总是按值调用。即,方法得到的是所有参数值的一个拷贝,方法不能修改传递给他的任何参数变量的内容。但是方法的传入参数包括了基本数据类型和对象引用,当传入参数是对象引用的时候,虽然该引用地址不能被更改,但是该引用的对象是可以被修改的。
实际上对象引用进行的依旧是值传递,我们可以操作的是传入的对象引用指向的对象,而不能操作该对象引用。
数据域若是没有被构造器赋值,那么会被默认的将数值置 0,布尔置 false,对象引用值 null。
当且仅当类中没有提供任何构造器时,系统才会默认的提供一个没有输入参数的构造器。若有,那么系统将不会提供 0 参数的构造器,若在这个时候调用 0 参数的构造器,将会报错。
在类中的构造器调用之前,会先对预设的值进行赋值。
Java 中又三种初始化数据域的方法:
- 在声明中初始化
- 在初始化块中初始化
- 在构造函数中初始化
从上到下依次执行。
可以使用静态代码块对静态变量进行初始化。
静态导入可以方便的使用某个类中的静态方法。如 Math 类。
5 继承
- Object…args 等同于 Object [] args,在输入任意多参数的时候会被自动装箱成 Object [] args。
- 枚举类的 toString 方法可以得到该枚举对象的 String,反过来,通过枚举对象的 String,可以通过 Enum.valueOf (枚举类.class, 枚举字符串) 得到对应的枚举对象。
- 枚举对象的 ordinal () 方法可以得到该枚举常量的位置。
6 接口与内部类
- 虽然不能 new 一个接口对象,但是可以用一个接口变量引用任何实现了该接口的类对象。
- 一样的可以同 instanceof 来检查某个对象是否实现了某个接口;接口依然可以被继承;接口的定义中虽然不能实现具体的方法,但是可以定义常量(static final)
- 抽象类与接口最大的区别在于,一个子类只能继承于一个抽象类,却可以实现数个接口。(Java 是没有多继承的)
- 即使默认的 clone 已经满足要求,也应该实现 Clonable 接口,将 clone 定义为 publi,并调用 super.clone()实现浅拷贝,若要进行深拷贝,则需要在浅拷贝的基础上,对于拷贝出来的对象中每一个引用对象进行深拷贝。
- 所有的数组类型均包含一个 public 的克隆方法。可以方便的进行数组对象的克隆。
- 内部类可以访问自身的数据域,月可以访问创建它的外围类对象的数据域。内部类中总是有一个隐式引用,指向其外部类。
11 异常、断言、日志与调试
由程序导致的是 runtimeException,由 IO 错误导致的错误会返回 ioException。
对于所有可能抛出异常、会被别人调用的方法,在声明的时候,最好都要加上 throw 语句以提示使用者做出反应。
子类中不能抛出比超类中更普适的异常。
throws 用于方法名声明时,throw 用于抛出具体错误
Java 中,throw 只能抛出 Throwable 对象,而不像在 cpp 中可以抛出任何值。
对于自己定义的 Throwable 类型,需要实现两个构造器,其中一个是默认构造器,另一个是带有描述信息的构造器,通过调用 super 的构造器将这些信息打印出来。
在进行错误的处理的时候,最好的策略是,对于知道如何处理的异常进行捕获;对于不知道该如何处理的异常进行传递,将该异常抛给调用这个方法的人,再上一层进行异常处理。
异常传递的时候,可以在新异常中使用 initCause 方法将原异常作为原因传递给新异常,那么在新异常抛出的时候就可以获取到原始愿意。
在 try/finally 语句块中使用 return 需要注意:在 try 中的 return 调用之前,会调用 finally 中的语句块,如果 finally 语句块中也有 return,那么会出现一些很奇妙的错误。
建议独立使用 try/finally 语句块和 try/catch 语句块,例如在 try/catch 中放进 try/finlly 语句块,这样,外层的 catch 语句就可以保证捕捉到 try 语句和 finally 语句中的异常,并且 finally 也能被正确的执行。
使用异常的技巧:
- 只在异常情况下使用异常。使用异常进行测试是非常耗时间的。
- 不要过分细化异常。尽量使用多个 catch 对可能的数个异常进行捕获。
- 利用异常层次结构。不要只抛出 runtimeException,不要只捕获 throwable 异常。
- 早抛出,晚捕获。早抛出异常可以更加明显的得到异常的类型及信息;晚捕获可以让高层的用户得知发生了错误并做出更加正确的处理动作。
断言机制允许在测试期间向代码中插入一些检查语句,当代码发布的时候,这些语句将被移除。
断言会对给定的条件进行检查,检查结果如果为 false,将会抛出一个 AssertionError 的对象。assert 语句有两种形式:
assert 条件;
assert 条件:表达式;
其中,表达式的作用仅仅是产生一个消息字符串,传入 AssertionError 构造器中,转换成一个消息字符串。
使用以下语句启用 \ 禁用断言:
java -enableassertions MyApp
断言失败是致命的,是不可回复的错误;断言一般只用于开发、检测阶段。
12 泛型程序设计
泛型程序设计意味着编写的代码可以被很多不同类型的对象所重用。
泛型类可以看做普通类的工厂,使用需要的数据类型对泛型类进行初始化,即完成了私有类的生成。
泛型方法定义如下:
public static
T method(T… a) 调用时,将类名放入尖括号即可。在定义泛型方法的时候,可能需要对于泛型进行限定:
public static <T extends Comparable & Serializable> T method(T… a)
这样的限定相当于限定了该泛型必须调用哪些方法。
13 集合
- Java 中对于数据结构的类库是将接口与实现分离的,每一个实现都可以通过一个实现了某一个数据类型的接口类型进行标示。对其进行引用的时候,只需要使用接口作为引用变量,引用任何一种实现了该接口的数据结构类型即可。
- 在 API 中也有很多以 Abstract 开头的类,在自己想要实现某种类型的数据结构的时候,继承这些类比直接实现某个接口来的方便很多(虽然基本不会用到)
- Collection 是基本的集合接口,其中有两个基本方法:add 和 iterator。add 用于添加一个元素,成功返回 true,失败返回 false;iterator 用于返回一个迭代器,迭代器是一个实现了 Iterator 接口的对象,其中包括三个方法:next,hasNext,remove。迭代器用于遍历 collection,在 Java SE 5.0 之后可以优雅的以 foreach 完成。
- foreach 在遍历 arrayList 的时候是顺序访问,在遍历 HashSet 的时候是以一种随机的方式进行访问。
- iterator 中的 remove 是用于删除上次调用 next 方法时返回的元素。在调用 remove 之前必须调用 next,否则将出错。迭代器所处的位置是两个元素之间。
- Java 中的链表(LinkedList)都是双向链表。
- 调用链表的迭代器中的 remove 需要注意,当先调用 next 的时候,将会删除左侧节点,当调用 previous 的时候,将删除右侧节点,即 remove 总是删除刚刚经历过的节点。
- vector 所有的方法都是同步的,而 Arraylist 的方法不是。因此在不需要同步的时候使用 ArrayList 以节省时间,只在需要同步的时候使用 Vector。
- Java 中 hashTable 用链表数组实现,每个列表被称为【桶】,桶的个数为总数的 75%~150%,最好将桶数设置为一个素数。在插入元素的时候,首先对该元素计算 HashCode,然后使用桶数对其取余,得到的数就是插入位置,如果该位置已经有元素,则称发生了【散列冲突】,此时将比较该桶中所有元素,查看是否有重复,若无则插入在桶中最后一个元素的后面。当该散列表中,一定比例的桶已经填充完毕,则需要进行【再散列】,决定是否进行再散列的因子被称为【装填因子】,一般取 75%。
- TreeSet 使用红黑树实现有顺序的散列表,每一次将元素添加到树中的时候,都会被放置到正确的位置上。
- 优先级队列使用堆完成,堆是一个可以自我调整的二叉树,可以自动的使最小的元素移动到根,而不用花时间排序。
- Java 中对于映射表也有两个实现:HashMap 和 TreeMap,均实现了 Map 接口。此两者均为对键值的操作,并不会按照关联的值进行操作。HashMap 对于键值进行散列;TreeMap 对于键值进行顺序排列,同样的,hash 会比 tree 更快一些。
14 多线程
多线程:一个程序同时执行多个任务。每一个任务称为一个线程(Thread)。
多线程与多进程的区别在于:每个进程拥有自己的一整套变量,而线程之间则共享数据。
synchronized 关键字可以防止代码块收到并发访问的干扰。
也可以使用 ReentrantLock 保护代码块:
1
2
3
4
5
6
7myLock.lock();
try{
//code here
}
finally{
myLock.unlock;//放在finally中是很重要的事,否则一旦出错,其他线程将永远被阻塞下去。
}当使用锁的时候,不能使用带资源的 try 语句。
从 Java1.0 开始,加入了 synchronized 关键字,起作用等同于对于整块代码进行保护的进程锁,免去了显式的对方法进行进程锁。同样的,在 synchronized 标示的方法中,同样可以使用条件锁,响应的语句为 wait 和 notifyAll。
v1.5.2