OCA/OCP Java Note (1): Java Building Blocks (1)

1. Understanding the Java Class Structure

1.1. Comments

  注释有以下三种形式,其中最后一种用于Javadoc:

// comment until end of line

/* Multiple
 * line comment
 */

 /**
 * Javadoc multiple-line comment
 * @author Jeanne and Scott
 */

  对于多行注释,在一组 /*  和 */ 之间的多行内容都会被作为注释,要小心 /* */ 不匹配的情况,如下面的代码无法编译:

/*
 * /* ferret */
 */

第一行的 /* 和第二行末尾的 */ 配对,之间的 * /* ferret 成为注释(注意这里第一个*/之间有一个空格),而第三行的 */ 没有与之相对的 /* ,导致编译错误。

1.2. Classes vs. Files

  多数情况下,Java类都被定义在自己的同名*.java文件中,且通常都是public的,表明任何代码都可以调用它。但Java并不强制要求每一个类都是public,如:

class Animal {
    String name;
}

  也可以在同一个文件中定义多个类,但其中最多只能有一个类是public的:

public class Animal {
    private String name;
}
class Animal2 { 
}

  对于public的类,它的类名必须与文件名匹配,上面的例子在一个文件中定义了两个类,其中Animal为public,则这个文件的文件名必须为Animal.java。

2. Writing a main() Method

  一个典型的main()函数如下:

public class Main {
    public static void main(String[] args) {

    }
}

  其参数列表为一个java.lang.String对象的列表,可以写成数组String[] args 、String args[] 或可变参数String… args 的形式。

  下面的mian()函数会将其前两个参数打印出来:

public class Main {
    public static void main(String[] args) {
        System.out.println(args[0]);
        System.out.println(args[1]);
    }
}

编译并使用如下参数运行:

$ javac Zoo.java 
$ java Zoo A1 A2

得到的打印为:

A1
A2

  两个参数用空格区分,如果想在一个参数中使用空格,需要将参数用双引号“” 括起来:

$ java Zoo "A1 A2" A3

得到的打印为:

A1 A2
A3

  通过命令行传入的参数都被作为String对象:

$ java Zoo A1 2

得到的打印为:

A1
2

这里的2 为String而不是int。

  如果通过命令行给出的参数数量少于main()函数访问的参数,则在main()函数访问到命令行未给出的参数时,会发生java.lang.ArrayIndexOutOfBoundsException。

3. Understanding Package Declarations and Imports

3.1. Wildcards

  通配符(wildcard)用于一次性导入一个包中的所有类,如:

import java.util.*;

这里的* 作为通配符,将导入java.util包中的所有类,包括java.util.Random等等,但不会导入子包(child packages)、字段(fields)或是方法(methods)。之后会涉及到静态导入(static import),允许导入其他类型。

3.2. Redundant Imports

  之前的例子在使用String时,并没有导入String所在的包java.lang,这是因为java.lang包会被自动导入,其余的包都需要手动导入。

  下面的例子使用了Files和Paths两个类:

public class InputImports {
    public void read(Files files) {
        Paths.get("name");
    }
}

Files和Path都位于java.nio.file包中,可以使用通配符导入:

import java.nio.file.*;

也可以使用类名导入:

import java.nio.file.Files;
import java.nio.file.Paths;

  但是,下面的导入方法都是不正确的:

import java.nio.*;    // NO GOOD – 通配符只能匹配类名,导入当前包下的所有类,
                      // 不会包括java.nio下的file包,不会导入Files和Paths两个类
import java.nio.*.*;    // NO GOOD – 最多只能使用一个通配符,且通配符必须在末尾
import java.nio.files.Paths.*;    // NO GOOD – 不能导入方法

3.3. Naming Conflicts

  不同的包下面可以存在具有相同名称的类,导入时需要注意避免命名冲突。下面的例子希望使用 java.util.Date:

public class Conflicts {
  Date date;
  // some more code
}

与之前类似,导入java.util.Date可以使用如下两种方式:

import java.util.*;

或:

import java.util.Date;

  有很多包中都有名叫Date的类,当这个类同时存在于多个被导入的包中时,会导致编译错误:

import java.util.*;
import java.sql.*; // DOES NOT COMPILE

java.util和java.sql包中都有一个名叫Date的类,上面第二行在导入java.sql时会发生编译错误:The type Date is ambiguous。如果想同时使用java.util.Date和java.sql中的其他类,可以使用如下的方式:

import java.util.Date;
import java.sql.*;

第一行显式地指明了要导入java.util.Date,这种方式的优先级高于通配符。如果以相同的优先级导入两个命名冲突的类,同样会导致编译错误:

import java.util.Date;
import java.sql.Date; // DOES NOT COMPILE

如果一定要同时使用java.util.Date和java.sql.Date,解决办法是只导入其中之一,另一个使用完整名称(包名.类名):

import java.util.Date;
public class Conflicts {
    Date date;
    java.sql.Date sqlDate;
}

或者二者都不导入,只使用完整名称:

public class Conflicts {
   java.util.Date date;
   java.sql.Date sqlDate;
}

3.4. Creating a New Package

  包名和路径相关联,如有位于packagea的ClassA在C:\temp\packagea\ClassA.java,有packageb的ClassB在C:\temp\packageb\ClassB.java:

package packagea;
    public class ClassA {
}

package packageb;
import packagea.ClassA;
public class ClassB {
    public static void main(String[] args) {
      ClassA a;
      System.out.println("Got it");
    }
}

在Windows下进行编译和运行的方法如下:

cd C:\temp
javac packagea/ClassA.java packageb/ClassB.java
java packageb.ClassB

packagea和packageb都位于temp文件夹下,进入temp文件夹后,可以直接使用javac进行编译。此外,还可以通过class path来指定其他路径下的文件:

java -cp ".;C:\temp\someOtherLocation;c:\temp\myJar.jar" myPackage.MyClass

也可以使用通配符来包含class path下的所有jar:

java -cp "C:\temp\directoryWithJars\*" myPackage.MyClass

3.5. Code Formatting on the Exam

  如果考试中的问题不涉及import,则题目的代码中会省略import的部分,此时代码段的行号不会从1开始,下面的代码段从第6行开始,说明省略了import的部分:

6: public void method(ArrayList list) {
7:  if (list.isEmpty()) { System.out.println("e");
8:  } else { System.out.println("n");
9: }  }

  而对于下面的代码段,行号从1开始,而又没有import ArrayList,则认为该段代码无法编译:

1: public class LineNumbers {
2: public void method(ArrayList list) {
3:  if (list.isEmpty()) { System.out.println("e");
4:  } else { System.out.println("n");
5: }  } }