这时候猛地一想回过头读读《Java编程规范》(作者Gosling)。
链接在:http://pan.baidu.com/share/link?shareid=523214&uk=3962180550
这是一部非常好的关于Java学习的教材,我会选择其中可能会有疏漏的地方写下来,也作为一个个人的读书笔记。下面是checklist。
- Unicode 完成
- 七种变量
class Point {
static int numPoints; // numPoints is a class variable
int x, y; // x and y are instance variables
int[] w = new int[10]; // w[0] is an array component
int setX(int x) { // x is a method parameter
int oldx = this.x; // oldx is a local variable
this.x = x;
return oldx;
}
}
这里因为加了static,所以numPoints是类变量;而x,y是需要实例化的,所以是实例变量。- 关于final
用final修饰的class是不能有subclass的。
用volatile修饰的变量不能同时用final修饰。
- Gneric Class
- Inner class和Inner interface
Any local variable, formal method para meter or exception handler parameter used but not declared in an inner class must be declared final. Any local vari-able, used but not declared in an inner class must be definitely assignedbefore the body of the inner class.
class Outer {
int i = 100;
static void classMethod() {
final int l = 200;
class LocalInStaticContext{
int k = i; // compile-time error
int m = l; // ok
}
}
void foo() {
class Local {
// a local class
int j = i;
}
}
}
Inner class只能访问用final修饰的变量,而且因为是final修饰,便必须要在声名时赋值。
class WithDeepNesting{
boolean toBe;
WithDeepNesting(boolean b) {
toBe = b;
}
class Nested {
boolean theQuestion;
class DeeplyNested {
DeeplyNested(){
theQuestion = toBe || !toBe;
}
}
}
}
Here, every instance of WithDeepNesting.Nested.DeeplyNested has an enclosing instance of class WithDeepNesting.Nested (its immediately enclos-ing instance) and an enclosing instance of class WithDeepNesting (its 2nd lexi-cally enclosing instance).
意即,对于内部类,每次实例化都会带着一个外部类的实例,层层包裹。
- 接口
interface Fish {
int getNumberOfScales();
}
interface Piano {
int getNumberOfScales();
}
class Tuna implements Fish, Piano {
// You can tune a piano, but can you tuna fish?
int getNumberOfScales() {
return 91;
}
}
但如下的代码却不能通过编译:interface Fish {
int getNumberOfScales();
}
interface StringBass {
double getNumberOfScales();
}
class Bass implements Fish, StringBass {
// This declaration cannot be correct, no matter what type is used.
public ??? getNumberOfScales() {
return 91;
}
}
下面看一个因为package不同而在重写方法中编译不过的例子:
package points;
public class Point {
int x, y;
public void move(int dx, int dy) {
x += dx; y += dy;
}
}
package points;
public class Point3d extends Point {
int z;
public void move(int dx, int dy, int dz) {
x += dx; y += dy; z += dz;
}
}
import points.Point3d;
class Point4d extends Point3d {
int w;
public void move(int dx, int dy, int dz, int dw) {
x += dx; y += dy; z += dz; w += dw; // compile-time errors
}
}
这时候就会出错,因为Point4d不在package中。更好的做法是:import points.Point3d;
class Point4d extends Point3d {
int w;
public void move(int dx, int dy, int dz, int dw) {
super.move(dx, dy, dz); w += dw;
}
}
从这里也可以看出,默认修饰的权限范围是package level。
即,package也是权限控制的一种方式。且子类继承后的权限应该小于等于父类。
- Volatile
class Test {
static int i = 0, j = 0;
static void one() {
i++; j++;
}
static void two() {
System.out.println("i=" + i + " j=" + j);
}
}
假如有两个线程,一个在不停地调用one,另一个在不停地调用two。那么two就有可能看到i和j不一致的情况。(有可能i比j大,或j比i大)
一种解决方法是给这两个方法加上同步:
class Test {
static int i = 0, j = 0;
static synchronized void one() {
i++; j++;
}
static synchronized void two() {
System.out.println("i=" + i + " j=" + j);
}
}
但是这种方法阻止了两个线程同步进行。另一种方法是加上volatile:
class Test {
static volatile int i = 0, j = 0;
static void one() {
i++; j++;
}
static void two() {
System.out.println("i=" + i + " j=" + j);
}
}
- volatile适用的两个场合:修改值的操作是原子的且不依赖原来的值,读的操作大于写的操作
- 在类变量(static类型)的初始化过程中,不能使用this和super关键字: 因为没有instance
class Test {
Test() {
k = 2;
}
int j = 1;
int i = j;
int k;
}
上面这样是可以的,尽管Test的初始化中的k是在后面声明的class Z {
static int peek() {
return j;
}
static int i = peek();
static int j = 1;
}
class Test {
public static void main(String[] args) {
System.out.println(Z.i);
}
}
以上代码会输出0,是因为当调用peek方法的时候,会返回j的值,可是这个时候j还没有被初始化,所以class UseBeforeDeclaration {
static {
x = 100; // ok - assignment
int y = x + 1; // error - read before declaration
int v = x = 3; // ok - x at left hand side of assignment
int z = UseBeforeDeclaration.x * 2;
// ok - not accessed via simple name
Object o = new Object(){
void foo(){
x++;
}
// ok - occurs in a different class
{
x++;
}
// ok - occurs in a different class
}
;
}
{
j = 200; // ok - assignment
j = j + 1; // error - right hand side reads before declaration
int k = j = j + 1;
int n = j = 300; // ok - j at left hand side of assignment
int h = j++; // error - read before declaration
int l = this.j * 3; // ok - not accessed via simple name
Object o = new Object(){
void foo(){
j++;
}
// ok - occurs in a different class
{
j = j + 1;
}
// ok - occurs in a different class
}
;
}
int w = x = 3; // ok - x at left hand side of assignment
int p = x; // ok - instance initializers may access static fields
static int u = (new Object(){
int bar(){
return x;
}
}
).bar();
// ok - occurs in a different class
static int x;
int m = j = 4; // ok - j at left hand side of assignment
int o = (new Object(){
int bar(){
return j;
}
}
).bar();
// ok - occurs in a different class
int j;
}
这段说明了初始化变量的用法- 隐藏变量(包括类变量和实例变量)
class Point {
int x = 2;
}
class Test extends Point {
double x = 4.7;
void printBoth() {
System.out.println(x + " " + super.x);
}
public static void main(String[] args) {
Test sample = new Test();
sample.printBoth();
System.out.println(sample.x + " " +
((Point)sample).x);
}
}
在上面的代码中,Test继承了Point,并且隐藏了Point中的x变量。于是对于每一个Test实例,都有两个x,一个是int类型,一个是double类型。但是在Test中的x都是自己的double类型,只有显式地用super.x才能指明是Point中的int类型的x。而当强转成了父类Point后,x就成了point中int类型的x。如果把Test中的成员变量的声明删掉,那所有的x就只有Point中的x了。- 继承的时候可以从父类或者接口(或者父类的接口)继承相同名字的变量,但是在本类的方法中要显式地指出用的是哪个变量。
interface Frob {
float v = 2.0f;
}
class SuperTest {
int v = 3;
}
class Test extends SuperTest implements Frob {
public static void main(String[] args) {
new Test().printV();
}
void printV() {
System.out.println(v);
}
}
上面的代码会出现问题,因为printV方法不知道v是哪个v。修改成下面的代码,使用SuperTest.v或者Frob.v就可以指明是哪个v。interface Frob {
float v = 2.0f;
}
class SuperTest {
int v = 3;
}
class Test extends SuperTest implements Frob {
public static void main(String[] args) {
new Test().printV();
}
void printV() {
System.out.println((super.v + Frob.v)/2);
}
}
- 下面一个例子说明:即使使用继承的具有歧义的同名变量是具有相同的类型和相同的值,编译还是会出错。
interface Color {
int RED=0, GREEN=1, BLUE=2;
}
interface TrafficLight {
int RED=0, YELLOW=1, GREEN=2;
}
class Test implements Color, TrafficLight {
public static void main(String[] args) {
System.out.println(GREEN); // compile-time error
System.out.println(RED); // compile-time error
}
}
上面的代码中RED变量就是例子。- It is a compile-time error for a private method to be declared abstract .
- It is a compile-time error for a static method to be declared abstract .
- It is a compile-time error for a final method to be declared abstract .
- 子类可以重写父类的方法并改成abstract,但是这样的话,子类的子类就不能调用super.abstractMethod(...)了,因为它的父类的这个方法是abstract的。总之在通过super调用的方法不能是abstract的
- 静态方法中不能调用this,super,以及泛型类型
- 如果一个class被final修饰,那么相当于它里面的所有方法都加上了final
- final在处理过程中会被inline处理
- 被native修饰的方法不能被abstract修饰
- 方法中如果有throw就可以不用return
- 实例方法不能重写静态方法, 但隐藏静态变量是可以的.『静态方法与静态变量的不同之处』
- 同样,静态方法不能隐藏实例方法『但是静态变量可以隐藏实例变量』
- 如果父类的方法被重写,可以在子类通过super来调用它
- strictfp对于重写没有影响,也就是non-strictfp的可以重写strictfp的,strictfp也可以重写non-strictfp的
- 如果重写的方法返回的类型R1不是被重写的方法的返回类型R2的子类型,则警告。『重写后类型变成子类型』
- 如果被重写的方法是protected,那么重写后应该是protected或者public;如果被重写的方法是public,那么重写后就是public;如果是private,则不可重写
package p1;
public class Outer {
protected class Inner{
}
}
package p2;
class SonOfOuter extends p1.Outer {
void foo() {
new Inner(); // compile-time access error
}
}
- 上面这个例子会有编译错误,虽然Inner是protected的,意味着SonOfOuter可以访问Inner类,可以继承它,但是:不意味着Inner的构造方法能够被SonOfOuter访问。
- 控制构造函数的访问权限可以控制构造的范围,甚至防止构造,例如:
class ClassOnly {
private ClassOnly() {
}
static String just = "only the lonely";
}
以上的代码中,ClassOnly就永远不会给构造。- 显式地初始化一个枚举类型,会导致编译错误
- 枚举类型的clone方法是被final修饰的,这就意味着枚举常量永远不会被clone
- 序列化过程中的一些特殊过程,保证了反序列化的时候枚举类型永远不会被复制。即使使用反射也是被禁止的。
- 枚举类型不能被abstract修饰
- 枚举类型无须定义为final,它被隐式地定义为了final,除非:枚举常量中有class
- 嵌套的枚举类型被隐式地修饰为了static,显式地把嵌套的枚举类型修饰为static是允许的
enum Color {
RED, GREEN, BLUE;
static final Map <String,Color> colorMap =
new HashMap<String,Color>();
Color() {
colorMap.put(toString(), this);
}
}
- 以上代码在会出错,因为Color的构造函数在执行的时候,colorMap这个HashMap还没有被构造起来,会抛出NullPointerException。
- EnumSet类中有很多静态方法,用来处理枚举类型
- 枚举类型中可以给常量成员添加一些“行为”,如以下的代码:
import java.util.*;
public enum Operation {
PLUS {
double eval(double x, double y) {
return x + y;
}
}
,
MINUS {
double eval(double x, double y) {
return x - y;
}
}
,
TIMES {
double eval(double x, double y) {
return x * y;
}
}
,
DIVIDED_BY {
double eval(double x, double y) {
return x / y;
}
}
;
// Perform the arithmetic operation represented by this constant
// abstract double eval(double x, double y);
public static void main(String args[]) {
double x = Double.parseDouble(args[0]);
double y = Double.parseDouble(args[1]);
for (Operation op : Operation.values())
System.out.println(x + " " + op + " " + y + " = " +
op.eval(x, y));
}
}
上面的代码展示了“加减乘除”的四个操作的四种行为,有点switch-case的味道。- 所有的接口被隐式地定义为public
- 接口定义的成员相当于被public static修饰
- 接口属于类变量,所以this和super也是不允许出现的
- Annotation类型不可以是泛型
- Annotation类型不能继承,默认是继承自annotation.Annotation
方法也不能有任何类型参数
方法的声明也不能出现throws句型
class Point {
int x, y;
}
class ColoredPoint extends Point {
int color;
}
class Test {
public static void main(String[] args) {
ColoredPoint[] cpa = new ColoredPoint[10];
Point[] pa = cpa;
System.out.println(pa[1] == null);
try {
pa[0] = new Point();
}
catch (ArrayStoreException e) {
System.out.println(e);
}
}
}
- 上面的代码会抛出java.lang.ArrayStoreException异常,是因为pa引用指向的实际上是一个ColoredPoint的数组,这样的话在每次存储的时候java都会检查存储的是不是ColoredPoint类型。
- 初始化class会要求先初始化父class,但是初始化interface不会要求先初始化父class
- finalize的过程与gc收集,强引用,弱引用,软引用,幽灵引用有关
- finalize的过程不能保证顺序,有可能同时进行(循环引用的object)
- 通过重写finalize方法可以使得上述的object按照顺序进行(从无序变有序)
- 如果一个class有静态变量或者静态初始化过程,那么它就不应该被reload
- 在一个object的构造方法完成以前,不要让其他线程有看到这个object的引用的机会。这样可以保证:一个object可以被多个线程正确地看到
- 如果在构造方法中有对final变量的赋值,那么在构造方法结束以前,这个final变量对于其他线程都是不可见的
- 通常final修饰的是不可修改的,但是出于历史原因,被final static修饰的System.in, System.out, System.err可以被修改。(通过System.setIn, System.setOut, System.setErr方法来修改)这些域会被编译器作特殊地处理:比如,对于这些域的读操作是“对同步免疫的”,锁或者是volatile读不需要关心从这些域中读到什么值,这些write-protected的域可以被看到发生了改变,那么它们就需要一个同步操作。而write-protected的意思就是只有protected作用域内的代码才能执行写操作。
- 关于Word Tearing:
public class WordTearing extends Thread {
static final int LENGTH = 8;
static final int ITERS = 1000000;
static byte[] counts = new byte[LENGTH];
static Thread[] threads = new Thread[LENGTH];
final int id;
WordTearing(int i) {
id = i;
}
public void run() {
byte v = 0;
for (int i = 0; i < ITERS; i++) {
byte v2 = counts[id];
if (v != v2) {
System.err.println("Word-Tearing found: " +
"counts[" + id
+ "] = " + v2 + ", should be " + v);
return;
}
v++;
counts[id] = v;
}
}
public static void main(String[] args) {
for (int i = 0; i < LENGTH; ++i)
(threads[i] = new WordTearing(i)).start();
}
}
以上的代码说明:数组中的任意成员都会被当成不同的值来处理,即使它们同时被不同的线程读写。代码中的System.err不会输出任何东西。- 对于double和long类型的非原子写操作:因为double和long是64个bit的,所以对于double和long的一个写操作由两次组成,每次各写32bit。这样的话就有可能造成多个线程之间读到的值的不一致。所以虚拟机尽量避免分割开64bit的数据。而且鼓励程序员尽量把共享的64bit数据声明为volatile(保证读一致性),或者在使用的时候进行同步操作。
- 无论是Thread.sleep方法还是Thread.yield方法,都不含同步的语义。也就是说,在调用这两个方法之前,编译器不需要把寄存器中的写cache操作,给flush到共享内存中。以为这下面这段代码永远不会退出while循环:
while (!this.done) Thread.sleep(1000);
- while循环在判断this.done的时候cache了这个变量,在这之后每次读取都会读到cache的值,即使其他线程修改了this.done。但是前提是,this.done是non-volatile的,比如final修饰的。
没有评论:
发表评论