Java代码规约

规约条款级别

重要度 条目
⭐ ⭐ ⭐ ⭐ ⭐ 基本上无条件适用
⭐ ⭐ ⭐ ⭐ 无特别理由的话,都要适用
⭐ ⭐ ⭐ 根据项目,基本可以适用
⭐ ⭐ 不适用对全体也没有影响
⭐ 介绍一下这样的用法

代码的5条心得

  • 重视可读性
  • 命名要容易理解
  • Sample不能囫囵吞枣
  • 相同的代码不能写2次
  • 一个方法只能有1个作用

1.命名规约

1.1 概述

1.1.1 使用英语

⭐ ⭐ ⭐

【说明・动机】
  标识符声明时基本上全部使用**英语** 。
  这样统一的目的是让他人也**容易读懂**代码。

1
2
3
4
5
6
7
违反例
      public boolean hasZaiko(){//违反
      }

修正例
      public boolean hasStock(){//修正
      }

1.1.2 不要用大/小写字母来区别标识符的不同

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  Java的规范规定大写字母和小写字母为不同的字符,但请不要用这个不同来区分标识符。仅用大小写字母来区分的话,别人使用变量、方法、类等时,可能会发生简单的错误。
  这样统一的目的是让他人也容易读代码。

1
2
3
4
5
6
7
违反例
      private int number;
      private int Number;//违反

修正例
      private int lineNumber;
      private int trainNumber;//修正

1.2 包

1.2.1 包名统一使用小写字母

⭐ ⭐ ⭐ ⭐

【说明・动机】
  只要没有特殊规定的,请所有的包名都是统一的小写。
  这个是Java的一般的规约。

1
2
3
4
5
    违反例
      jp.co.isid.FrameWork.Banking.ACCOUNT//违反

    修正例
      jp.co.isid.framework.banking.account//修正

⭐ MobileOffice系统追加【包名规约】
  V系列的开发、根据【开发环境构筑指南】、Android SDK使用关于名称是Library系,Utility系,Application系的3个分类,分别适用以下的命名规则。

分类 名称
Library系 jp.casio.vx.framework.xxx
Utility系 jp.casio.vx.util.xxx
Application系 jp.casio.vx.app.xxx

1.2.2 包名的标识符要有意义

⭐ ⭐ ⭐

【说明・动机】
  包名的标识符要有意义,根据包名能很容易推测出这个包有什么样的类。
  包名通常使用功能ID(例如像ssau45690编号)或是连续的号码编号(连番)、新项目参与的成员和将来维护的人是很难理解的,不是上策。
  「包里包含了什么」要用心去想一个一眼看上就能够正确理解的标识符。

1
2
3
4
5
    违反例
    jp.co.isid.framework.banking.a00001//违反

    修正例
    jp.co.isid.framework.banking.account//修正

1.2.3 包名不要缩写

⭐ ⭐ ⭐ ⭐

【说明・动机】
  包名,即使稍微有点长,也尽可能的不要缩写,请赋予容易理解的标识符。
  (使用)缩写的场合、这个缩略语要全组人员都懂。

1
2
3
4
5
    违反例
    jp.co.isid.fw.bkg.acc//违反

    修正例
    jp.co.isid.framework.banking.account//修正

1.2.4 子包名可以相同

⭐ ⭐ ⭐ ⭐

【说明・动机】
  父包名不一样的情况下,子包名可以相同。

1
2
3
    sample
    jp.co.isid.framework.banking.entity //银行的账户
    jp.co.isid.framework.trading.entity //交易账户

1.2.5 包的详细信息要用package-info.java类来记载

⭐ ⭐ ⭐

【说明・动机】
  包里包含了什么样的类、类包含了什么样的作用等从Java5开始引用了package-info.java类的文献管理注解来记载。
  使用package-info,包内的信息要明确记载。还有子包名的概要信息也要记载、包构成的理解可以加深、代码的可维护性也提高了。请积极地使用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    sample
    /**
      * 这个包是XXX项目的业务YYY的の基础(根)包。
      * YYY是...
      *
      * YYY由以下要素构成。
      *  详细(信息)请参照以下包。
      * <ul>
      * <li>jp.co.isid.xxx.yyy.controller</li>
      * <li>jp.co.isid.xxx.yyy.entity</li>
      * <li>jp.co.isid.xxx.yyy.logic</li>
      * <li>jp.co.isid.xxx.yyy.util</li>
      * </ul>
      * 
      */
    package jp.co.gihyo.javarulebook.chapter1_2_5;

1.3 类・接口

1.3.1 类名的命名要表达类的角色和作用

⭐ ⭐ ⭐

【说明・动机】
  类名不要使用功能ID或者连番等很难想像的标识符。
  类名要赋予有意义的标识符,可以直观的理解类的内容,别人也容易读懂代码。

1
2
3
4
5
6
7
    违反例
    public class A00001 {//违反
    }

    修正例
    public class LoginAction {//修正
    }

1.3.2 类名的首字母要大写

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  类名、首字母要大写。类名有多个单词构成的场合、每个单词的首字母都要用大写字母来区分。这个是Java一般的规约。

1
2
3
4
5
6
7
    违反例
    public class sampleClass {//违反
    }

    修正例
    public class SampleClass {//修正
    }

1.3.3 异常类名的末尾要加上「Exception」

⭐ ⭐ ⭐ ⭐

【说明・动机】
  异常类名要在标识符的最后加上「Exception」。运行时异常在末尾加上「RuntimeException」会变得容易理解。
  这样统一的目的是为了让别人也能够容易理解「这个类是异常类」

1
2
3
4
5
6
    违反例
    public class Sample extends Exception {//违反
    }
    修正例
    public class SampleException extends Exception {//修正
    }

1.3.4 接口名和实现类名的对应关系要明确

⭐ ⭐ ⭐

【说明・动机】
  类名命名的规则、基本的接口(命名)也同样适用。例如接口名是Sample、实现类名就是SampleImpl。
  还有实现类是Sample、接口名的先头要赋予「I」即ISample、项目里要统一。这样统一的目的是让他人变得容易读懂代码。

1
2
3
    sample
    public class SampleImpl implements Sample {
    }

1.3.5 抽象类名和实现类名的对应关系要明确

⭐ ⭐ ⭐

【说明・动机】
  抽象类名和实现类名的对应关系在标识符上要表现出来。例如、如果类名是Sample的话、它的父抽象类名是AbstractSample。这样统一的目的是为了让别人也能够容易理解「这个类是抽象类」。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    sample
    public abstract class AbstractSample {
        public abstract String getName();
    }

    public class Sample extends AbstractSample {
    @Override
        public String getName() {
            return "sample";
        }
    }

1.3.6 表示能力的接口名要在末尾加“able”

⭐ ⭐

【说明・动机】
  对定义附加了有能力的类的接口(例Runnable,Cloneable,Closeable等)的场合,请在标识符加上这个表示能力的形容词(~able)。这样统一的目的是让他人容易读懂代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    sample
    public interface Movable {
      int moveLeft(int from, int dest);

      int moveRight(int from, int dest);

      int moveForward(int from, int dest);

      int moveBack(int from, int dest);
    }

    public class Car implements Movable {
    }

1.4 测试类

1.4.1 做单元测试时要使用JUnit等测试框架

⭐ ⭐ ⭐ ⭐

教育資料:[[テストコードの作成について]]

【说明・动机】
  做单元测试时,要使用Junit3或Junit4等的测试框架。单元测试框架,复数测试的自动执行和测试前后呼出处理等,单元测试提供辅助功能,体现单元测试的效率。
  下面是最经常使用的Junit3、下一个是JUnit4的例子。JUnit可以从以下下载。
http://junit.org/

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
sample
    //JUnit3.x的例子
public class EmployeeTest extends TestCase {
    public void testCheckAssign() throws Exception {
        Employee e = new Employee(123);
        e.assign(8990);
        asserttrue(e.isAssigned());
    }
}
// JUnit4.x的例子
public class EmployeeTest {
@test
public void checkAssign() {
    Employee e = new Employee(123);
    e.assign(8990);
    asserttrue(e.isAssigned());
}
}

1.4.2 测试类的命名:「测试对象类名+Test」

⭐ ⭐ ⭐ ⭐

【说明・动机】
  测试类名是「测试对象类名+Test」。这样统一的目的是让他人也能容易理解测试代码。

1
2
3
sample
public class SampleTest extends TestCase {
}

1.5 方法

1.5.1 方法名要以合适的动词开头

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  方法名要以合适的动词开头。例如、要从方法名能理解「这个方法是处理什么的」。方法是做什么的要明确记述。这样统一的目的是让他人容易读懂代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    违反例
    public class BadSample {
        protected int number;

        public BadSample() {
        }
        // 违反
        public void nnn(int number) {
            this.number = number;
        }
        public int getNumber() {
            return number;
        }
    }

    修正例
    public class GoodSample {
        protected int number;
        public GoodSample() {
        }
        // 修正
        public void setNumber(int number) {
            this.number = number;
        }
        public int getNumber() {
            return number;
        }
    }

1.5.2 方法名用大写字母来区隔多个单词

⭐ ⭐ ⭐ ⭐

【说明・动机】
  方法名只有一个单词的场合要用小写字母来记述。方法名有多个单词构成的场合、从第二个单词开始的首字母要用大写字母。
  这个是Java的一般规约。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    违反例
    public class BadSample {
        // 违反
        public int executesample() {
            return 0;
        }
    }

    修正例
    public class GoodSample {
        // 修正
        public int executeSample() {
            return 1;
        }
    }

1.5.3 生成对象的方法名用「create+对象名」命名

⭐ ⭐ ⭐

【说明・动机】
  生成对象的方法(工厂方法)的命名要用「create」开始、然后加上要生成的对象名。
  这样统一的目的是让他人容易读懂代码。

1
2
3
4
5
6
7
    sample
    public class SampleCreator {
        // 创建对象的工厂方法用create开始的方法名
        public static Sample createSample(int no, String name) {
            return new Sample(no, name);
        }
    }

1.5.4 类型转换的方法名用「to+要转换的对象名」或者「convert+要转换的对象名」命名

⭐ ⭐ ⭐

【说明・动机】
  一个对象转换成另一个对象(转换方法)是「to」或者「convert」开始、然后加上变换后的对象的名称。
  这样统一的目的是让他人容易读懂代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    sample
    public class DateUtil {
        // 变换为Date对象
        public static Date toDate(String s) {
            try {
                SimpleDateFormat formatter = new SimpleDateFormat("yyyy/mm/dd");
                return formatter.parse(s);
            } catch (ParseException e) {
                throw new ParseRuntimeException(e);
            }
        }
    }

1.5.5 返回值是布尔型(boolean)的方法名要明确true/false的状态

⭐ ⭐ ⭐

【说明・动机】
  返回boolean变量的方法名、要有一个能理解返回值是true/false的状态的标识符。记述形式以回答Yes或No的疑问文字形式为好。
  下面是一般的命名例。

命名
is+形容詞 isEmpty()
can+动词 canGet()
has+过去分词 hasChaged()
三元动词 contains()
三元动词+名詞 containsKey()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    sample
    public class Employee {
        // ……
        public boolean isAsleep() {
            return asleep;
        }
        public boolean canSpeakMultiLanguages() {
            return lauguages != null && 1 < lauguages.length;
        }
        public boolean hasExpired() {
            return expired == false;
        }
    }

1.5.6 要有用英语正反义词的意识

⭐ ⭐ ⭐

【说明・动机】
  成对表示作用、功能等的方法名要有使用英语正反义词的意识。下面是正反义词的例子。

send receive
start stop
register unregister
put get

这样做让他人也容易读懂代码。

1
2
3
4
5
6
    sample
    //send/receive成对(使用)
    public interface MessageHandler {
        boolean send(String message);
        String receive();
    }

1.6 参数

1.6.1 构造方法以及方法的参数名要赋予容易理解的标识符

⭐ ⭐ ⭐

【说明・动机】
  构造方法以及方法的参数名(形参)要赋予容易理解的标识符。
  参数名和成员变量名相同的场合、成员变量要使用this(没有this的话、就没有正确给成员变量设值)。
  这样做、哪个成员(变量)代入了哪个参数变得容易理解、别人也容易读懂代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    违反例
    public class BadSample {
        private String name;

        public BadSample(String str) {
            this.name = str; // 违反
        }

        public void setName(String str) {
            this.name = str; // 违反
        }

        public String getName() {
            return name;
        }
    }

    修正例
    public class GoodSample {
        private String name;

        public GoodSample(String name) {
            this.name = name; // 修正
        }

        public void setName(String name) {
            this.name = name; // 修正
        }

        public String getName() {
            return name;
        }
    }

1.7 变量概述

1.7.1 常量是不变的、全部是大写字母、用「_」区隔多个单词

⭐ ⭐ ⭐ ⭐

【说明・动机】
  常量或者枚举enum常量、都要用大写字母来记述。还有常量全部要用static final来声明。
  常量或者枚举常量有多个单词构成时、每个单词之间用「_」(下划线)来隔开。
  这样统一的目的是为了他人容易读懂代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    违反例
    public class BadSample {
        public static final int SampleValue = 1; // 违反

        public enum Language {
            japanese, english, chinaese; // 违反
        }

        public static void main(String[] args) {
            System.out.println(SampleValue);
            System.out.println(Language.english);
        }
    }

    修正例
    public class GoodSample {
        public static final int SAMPLE_VALUE = 1; // 修正

        public enum Language {
            JAPANES, ENGLISH, CHINAESE; // 修正
        }

        public static void main(String[] args) {
            System.out.println(SAMPLE_VALUE);
            System.out.println(Language.JAPANES);
        }
    }

1.7.2 变量名要反应变量的角色和作用

⭐ ⭐ ⭐

【说明・动机】
  要给变量赋予一个能反应它角色和作用的标识符。
  这样统一的目的是为了他人容易读懂代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    违反例
    public class BadSample {
        private String str1 = "server1"; // 违反
        private String str2 = "client1"; // 违反

        public static void main(String[] args) {
            BadSample sample = new BadSample();
            System.out.println(sample.str1);
            System.out.println(sample.str2);
        }
    }

    修正例
    public class GoodSample {
        private String serverName = "server1"; // 修正
        private String clientName = "client1"; // 修正

        public static void main(String[] args) {
            GoodSample sample = new GoodSample();
            System.out.println(sample.serverName);
            System.out.println(sample.clientName);
        }
    }

1.8 局部变量

1.8.1 可以缩写作用范围小的变量名

⭐

【说明・动机】
  一般来说变量名的标识符要反应变量的作用。但作用范围小的变量比较容易识别、类型名省略或使用省略的标识符也可以。
  省略的标识符用IDE来生成比较便利。例如在Eclipse里按照以下操作变量名可以自动指定。像这样的功能要好好利用。

  • Ctrl+space
  • Ctrl+1
  • Alt+Shift+L

但是、下面的情况,变量名最好不要省略。

  • 即使不省略变量名长度也适中
  • 以后有可能会修改变量作用范围
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
    违反例
    public class BadSample {
        public static void main(String[] args) {
            InputStream a = getInputStream(PATH); // 违反
            InputStreamReader isr = null; // 违反
            try {
                isr = new InputStreamReader(a);
                BufferedReader br = new BufferedReader(isr);
                // 作用范围小、省略名称也没问题
                for (String s = br.readLine(); s != null; s = br.readLine()) {
                    System.out.println(s);
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            } finally {
                // 结束处理
            }
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            InputStream inputStream = getInputStream(PATH); // 修正
            InputStreamReader reader = null; // 修正
            try {
                reader = new InputStreamReader(inputStream);
                BufferedReader br = new BufferedReader(reader);
                // 作用范围小、省略名称也没问题
                for (String s = br.readLine(); s != null; s = br.readLine()) {
                    System.out.println(s);
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            } finally {
                // 结束处理
            }
        }
    }

1.8.2 for循环的计数器、使用「i」「j」「k」...顺序嵌套

⭐ ⭐ ⭐

【说明・动机】
  for循环的计数器、每层请按照「i」「j」「k」的顺序(字母序列)使用。
  这样统一的目的是为了他人容易读懂代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    违反例
    public class BadSample {
        public static void main(String[] args) {
            for (int j = 0; j < 2; j++) {
                for (int n = 0; n < 3; n++) {
                    for (int t = 0; t < 5; t++) {
                        System.out.println(j + "," + n + "," + t);
                    }
                }
            } // 违反
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            for (int i = 0; i < 2; i++) {
                for (int j = 0; j < 3; j++) {
                    for (int k = 0; k < 5; k++) {
                        System.out.println(i + "," + j + "," + k);
                    }
                }
            } // 修正
        }
    }

1.9 资源文件

1.9.1 画像文件

⭐

【说明・动机】
  图标、背景画像等的画像文件容纳在res/drawable里。文件名遵照以下规则。
【使用文件】
  JPG,PNG
  图标画像、背景画像基本上使用PNG文件。
  (形式BMP,PNG,GIF,JPG)
【画像文件的命名规则】
  标识符①名前连番.扩展名
  标识符① = 画像分类的标识。根据需要复数组合。
  名前/连番 = 个别的图像判别的名字和连番。根据需要复数组合。
【标识符例】

标识符 内容
bg 背景画像
btn 按钮
ico 图标
img 画像全般

・[番号]
  番号用两位数字表示。
  另、一个页面内有唯一的画像(logo等)番号可以省略。

【按钮ON,OFF画像的切换】
  按钮的ON,OFF画像要切换的场合、画像文件要使用向下面的xml文件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!--     pressed -->
    <item
        android:state_pressed="true"
        android:drawable="@drawable/ico_back_on">
    <!--     released -->
    <item
        android:state_pressed="false"
        android:drawable="@drawable/ico_back_off">
    </selector>

这个时候、画像文件的名称、xml文件按照上面的规则命名、按照上面的规则:名称+(下划线)+on,off。
例)

意义 示例文件名
ON时的画像文件名 ico_back_on.png
OFF时的画像文件名 ico_back_off.png
xml文件名 ico_back.xml

2.程序设计规约/基础篇

2.1 概述

2.1.1 要使用接口引用对象

⭐ ⭐ ⭐

【说明・动机】
  引用对象时、用这个对象的实现类来声明。但是、对于实现类来说,存在适当的接口时、必须使用接口声明。
  使用接口的好处是,提高代码的灵活性。使用实现类声明的话、以后实现类有修改时、必须修改所有的引用。但如果使用接口声明,只要修改生成实例的地方,就可以了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
    违反例
    public class BadSample {
        public static void main(String[] args) {
            ArrayList<String> sampleList = new ArrayList<String>();// 违反
            sampleList.add("bad_sample");
            BadSample badSample = new BadSample();
            String ret = badSample.badMethod(sampleList);
            System.out.println(ret);

            // List实现被修改了
            LinkedList<String> otherList = new LinkedList<String>();
            // 这里编译错误
            // badSample.badMethod(otherList);
        }

        /**
         * 违反:如果调用实现类的场合更改为ArrayList以外的类,这个方法必须修改
         */
        private String badMethod(ArrayList<String> input) {
            return input.toString();
        }
    }

    修正例
    public class GoodSample {

        public static void main(String[] args) {
            List<String> sampleList = new ArrayList<String>();// 修正
            sampleList.add("good_sample");
            GoodSample sample = new GoodSample();
            String ret = sample.goodMethod(sampleList);
            System.out.println(ret);

            // List实现被修改了
            List<String> otherList = new LinkedList<String>();
            //方法用List接口作为参数的场合、没有影响
            String ret2=sample.goodMethod(otherList);
            System.out.println(ret2);
        }

        /**
         * 修正:如果调用实现类的场合更改为ArrayList以外的类,这个方法没有影响
         */
        private String goodMethod(List<String> input) {
            return input.toString();
        }

    }

2.1.2 不要使用不推荐的API

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  不要使用「不推荐」的类和方法。不推荐API可能在将来的版本中被删除、有不能再用的可能性。当需要这些功能时、在Javadoc中查找代替方案,并使用替代的功能。
  可以在Javadoc中判断出是否是不推荐的API、可参考以下的好方法。

  • 如果使用不推荐API的话、要使用IDE或工具来显示警告
  • 使用编译时输出的警告
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    违反例
    public class BadSample {
        public static void main(String[] args) {
            Date sampleDate=new Date();
            System.out.println(sampleDate.getYear());//违反
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            Calendar sampleCalendar=Calendar.getInstance();
            System.out.println(sampleCalendar.get(Calendar.YEAR));//修正
        }
    }

2.1.3 不用的代码不要写

⭐ ⭐ ⭐ ⭐

【说明・动机】
  如果存在不用的private方法或变量、或者局部变量、代码的可读性会下降,并会妨碍理解。检查代码是否正在使用、不用的话就删除。如果有代码是有必要存在的,就要把它变成使用中的方法。
  如果你想保持代码的理由是「将来有可能使用」、请在版本管理工具(git/svn)中保留履历、并在当前的代码中删除。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
    违反例
    public class BadSample {
        public static void main(String[] args) {
            new BadSample().usedMethod();
        }
        private void usedMethod(){
        }
        private void unusedMethod(){
            //违反  不使用的方法
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            new GoodSample().usedMethod();
        }
        private void usedMethod(){
        }
        //已经削除
    }

2.1.4 要使用适当的访问修饰符来声明(变量、方法、类...)

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  要充分理解private、public等访问修饰符的意义,要用适当的访问修饰符来声明类、方法、变量、常量等。
  通常、要使用Eclipse等IDE的代码协助功能、协助一览里仅仅表示类能访问的(类、方法...)范围。因此、使用适当的访问修饰符来声明、就能对类的使用者公开出想要让他访问的类、方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    违反例
    public class BadSample {
        public void execute() {
            internalMethod();
        }
        /**
         * 违反:仅仅是内部使用的方法用pubic来声明
         */
        public void internalMethod() {
        }
    }

    修正例
    public class GoodSample {
        public void execute() {
            internalMethod();
        }

        /**
         * 修正:仅仅内部使用的方法用private来声明、
         * 可以控制来自外部的访问
         */
        private void internalMethod() {
        }
    }
关键字 类本身 同一个包下 子类 别的包
public
protected ×
friendly × ×
private × × ×
  1. public 公开成员、所有的类和包都可以使用
  2. private 私有成员、只能在类的内部使用
  3. protected 自身和继承的子类,以及同一个包下的别的类中可以使用。(相当于 package public)
  4. friendly 只能在自身和同一个包下的别的类中使用。

2.1.5 要适当使用final

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  像下面的不会改变的/不想被改变的情况请用final来声明。要保证不会修改。

  • 不想被继承的类
  • 不想被重写的方法
  • 值不变的变量

另外、关于常量(基本型或者String型的变量、用final声明的变量)、编译时、使用这个常量的地方会用值直接替换。因此、一旦决定值不变,就要声明成常量。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    违反例
    public class BadSample {
         public static int DEFAULT_INTERVAL=60;
         //违反
    }
    修正例
    public class GoodSample {
         public static final int DEFAULT_INTERVAL=60;
         //修正
    }

2.1.6 要注意基本型和引用型的区别(定义篇)

⭐ ⭐ ⭐ ⭐ ⭐

教育資料:[[プリミティブ型と参照型の参数渡すの区別]]
【说明・动机】
  基本型和引用型、主要有以下3个不同点。

  • 实行速度和资源消耗
  • 默认值(实例变量声明时)
  • 数据构造

各自的差异如下。
实行速度和资源消耗

  • 基本型⇒使用的时候不需要生成新的对象
  • 引用型⇒使用的时候需要生成新的对象

使用基本型可以节约时间和资源,另一方面,引用型是作为对象进行处理,正好兼容Java的Collection等的API。
  默认值(实例变量声明时)

  • 基本型⇒默认值的不同取决于类型
  • 引用型 ⇒ null

「参考」基本型的默认值

基本型 默认值
boolean false
byte (byte)0
char '\u0000'
double 0.0d
float 0.0f
int 0
long 0L
short (short)0

数据构造
基本型和引用型的对应关系如下。

基本型 引用型
boolean Java.lang.Boolean
byte Java.lang.Byte
char Java.lang.Character
double Java.lang.Double
float Java.lang.Float
int Java.lang.Integer
long Java.lang.Long
short Java.lang.Short

2.1.7 要注意基本型和引用型的区别(参数編)

⭐ ⭐ ⭐ ⭐ ⭐

教育資料:[[プリミティブ型と参照型の参数渡すの区別]]
【说明・动机】
  方法传递的参数分别是基本型或引用型时、有以下区别:

  • 基本型场合= ⇒ 值传递
  • 引用型场合 ⇒ 实例化后的地址值传递

因此、多处引用这个实例的场合、在方法内部任意改变引用型参数的状态的话、会引起意料外的问题。在有引用型参数的方法里对传递的参数直接操作时要特别**注意**。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    sample
    import java.awt.Point;

    public class Sample {
        public static void main(String[] args) {
            int intValue = 0;
            Point point = new Point(0, 0);
            Sample sample = new Sample();
            sample.printValue(intValue,point);
            sample.modifyValue(intValue,point);
            sample.printValue(intValue,point);
        }
        private void modifyValue(int value,Point point) {
            //参数值改变
            value=10;
            point.setLocation(55, 77);
        }
        private void printValue(int value,Point point) {
            StringBuffer buffer=new StringBuffer();
            buffer.append(value);
            buffer.append(" : ");
            buffer.append(point);
            System.out.println(buffer);
        }
    }

【解说】
执行结果如下。
  0 : java.awt.Point[x=0,y=0]
  0 : java.awt.Point[x=55,y=77]
  上面的例子中、处理的变量intValue是基本型,变量point是引用型。正如所看到的执行结果,在modifyValue()方法执行前后,基本型intValue的值没有变换,而引用型point的值已经被改变。
  基本型(intValue)的参数作为值传递、只是复制值进行操作、对intValue自身并没有影响。另一方面、引用型(point)的参数是地址值进行传递、方法里对point的引用值进行操作从而影响了结果。

2.2 度量

【解说】
  这节定义的个数和行数只是大概的基准、遵守这个数值不是原始的目的。度量值是为了提高可读性和可维护性以及检查是否有超出度量值的部分(因为超出部分总有很多问题)
  因此、基于在本节中描述的内容,包、类或者方法的职责、还有考虑到PC的尺寸等【哪种程度的度量值合适】要根据每个产品・每个项目而决定。

2.2.1 1个方法的行数大约在20行以下

⭐ ⭐

【说明・动机】
  1个方法的行数、包含方法内的注释在20行左右是比较理想的。最多不要超过50行。
  超过上述行数的场合,考虑重新设计成多个方法等。
  1个方法的行数变少了,同时减少代码量,代码也容易把握。

2.2.2 1个类的行数大约在600行以下

⭐ ⭐

【说明・动机】
  1个类的行数包含注释在600行左右是理想的。最多不能超过1000行。超过上述行数的场合、考虑重新设计成多个类等。
  1个类的行数变少了,容易把握类的结构。

2.2.3 1个类里的public方法大约在20个以下

⭐ ⭐

【说明・动机】
  1个类里除了简单的可以访问的方法(如:setter、getter)、public方法要在20个以下。超过上述方法数时考虑重新设计成多个类
  public方法多的时候、使用类公开的部分变多了,变得难以把握。

2.2.4 1个包下的类的数量控制在20个一下

⭐ ⭐

【说明・动机】
  1个包里包含类的个数在20个左右是理想的。最多不能超过30个。超过时,可以考虑重新分割成多个包等。
  1个包里放入各种各样意义的类的时候,这个包里到底放了什么东西也不知道、包的意义也就没有了。要时刻检讨「放入包里的类是否真的有必要」。

2.2.5 要极力避免相互引用

⭐ ⭐ ⭐

【说明・动机】
  类之间的相互引用会使结构复杂、可读性和维护性都大大的下降。修改类时受影响的地方会变多,这也是导致bug的原因。包之间也是同样的,包之间相互引用的话,相互依存关系会导致复杂度提高,维护性也会下降。
  对相互引用要小心,没有必要的话尽量不要使用循环的结构。类或者包的相互引用,要尽量利用UML等的图示来把握。利用生成UML图的工具、可以有效的把握(类或包的)依存关系。
  还有类的内部也有这样的情况,如块的嵌套数、复杂的类/方法的调用、复合条件等的个数越来越多时,代码也会变得复杂。代码一复杂,可读性/维护性会降低,发生bug的可能性也就提高了。
  如果感到类或方法里代码复杂的话,请重新设计类或者方法。还有、如果发现类之间或包之间有相互引用的话,尽可能的不要着手,使用静态的解析工具来有效率的进行。

2.3 格式化

2.3.1 代码格式化是做个定义文件,并用IDE来进行格式化

⭐ ⭐ ⭐ ⭐

【说明・动机】
  像缩进源代码或添加空白等,要求代码格式的规则有很多,但最重要的是:「遵守约定的规则,无论谁写的代码格式都将相同」。
  最新的IDE、对于格式化能够做到细致的定义,能够整顿好统一化的格式。
  作成代码的格式化文件,通过IDE自动格式化的効率比告诉程序员代码格式,人工检查代码是否遵守规则要高。
  作成格式化用的模板,以后我们将一直依照这个模板进行格式化。
  但是,有文件保存时格式化这个功能时,保存时IDE也会自动的进行格式化。用这样的功能,可以轻松的进行格式化,应该积极的去用。

2.3.2 return返回值不要用括号

⭐ ⭐

【说明・动机】
  return返回值,请不要使用括号。Java中,只能够有一个返回对象。用括号有可能会使返回语句被误认为是某个方法,这样会降低代码的可读性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
    违反例
    public class BadSample {
        /**
         * 违反:返回值中存在运算,运算结果无法确认。
         */
        public int badSampleMethod1() {
            int a = 1;
            int b = 2;
            return (a + b);
        }

        /**
         * 违反:因为变量在括号中,有可能发生方法调用错误。
         */
        public int badSampleMethod2() {
            short a = 1;
            // 别的一些处理
            return (int) (a);
        }
    }

    修正例
    public class GoodSample {
        /**
         * 修正:返回值前,运算完成
         */
        public int goodSampleMethod1() {
            int a=1;
            int b=2;
            int c=a+b;
            return c;
        }
        /**
         * 修正:变量在括号外,容易理解
         */
        public int goodSampleMethod2() {
            short a=1;
            //别的一些处理
            return (int)a;
        }
    }

2.3.3 条件判断不要使用「!」

⭐ ⭐ ⭐

【说明・动机】
  条件分支是比较boolean型的条件时,不用「!」, 用true和false来比较。
  条件判断中调用的方法很长或者有多个条件,使用「!」的话,「!」就很容易会被忽视。
  而且,和true进行比较书写明确,提高了代码的可读性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    违反例
    /**
     * 违反:不用true/false比较,可读性差
     */
    public void badSampleMethod() {
        String name1="aaa";
        String name2="bbb";
        if (!StringUtil.isEmpty(name1)&& StringUtil.isEmpty(name2)) {
            //处理
        }
    }

    修正例
    /**
     * 修正:用true/false比较,容易理解
     */
    public void goodSampleMethod() {
        String name1="aaa";
        String name2="bbb";
        if (StringUtil.isEmpty(name1)==false &&
         StringUtil.isEmpty(name2)==true) {
            //处理
        }
    }

另外一些项目中boolean不要和true比较 (【Java代码推荐事項】)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    boolean isCheck=true;
    //×
    if(isCheck==true){
        System.out.println("OK");
        }
    //〇
    if(isCheck){
        System.out.println("OK");
        }
    }

    public void goodSampleMethod() {
        String name1="aaa";
        String name2="bbb";
        if ( StringUtils.isNotEmpty(name1) && StringUtils.isEmpty(name2)) {
            //处理
        }
    }

2.3.4 不等号的方向要统一向左("<"、"<=")

⭐ ⭐ ⭐

【说明・动机】
  不等号的方向要统一向左,在全体代码中都能确定「左边总是小数据」的规约的话,代码就更容易阅读。除了有特别用意的描述以外,方向请统一向左。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    违反例
    public void badSampleMethod() {
        int a = 1;
        int i = 2;
        if (a < i) {

        } else if (a > i) {// 违反

        }else{
        }
        if(a>0 && a<100){//违反
        }
    }

    修正例
    public void goodSampleMethod() {
        int a = 1;
        int i = 2;
        if (a < i) {

        } else if (i < a) {// 修正

        } else {

        }

        if (0 < a && a < 100) {// 修正

        }
    }

2.3.5 条件判断语句中,不要嵌套调用方法,用临时变量代替

⭐ ⭐ ⭐

【说明・动机】
  条件判断语句里有必要调用方法的情况下,禁止嵌套调用。不要持续的调用方法,把调用方法的结果用变量临时保存后,再将将变量代入条件语句中,这样能够清晰的描述条件语句的内容。
  嵌套调用方法时,加括号是难于理解的,也使可读性变差。而且调试的时候,中途的值不可能在调试器中自动取到,很难调试。
  尽可能的不要嵌套调用方法,使其更容易调试,更容易阅读。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    违反例
    public class BadSample {
        private int getNumber(){
            return 1;
        }
        private String getname(int i){
            return "Yonebayashi" + String.valueOf(i);
        }
        /**
         * 违反:因为条件判断语句里多次调用方法,所以很难检查值
         */
        public void badSampleMethod(){
            if (StringUtil.isEmpty(getName(getNumber()))==false) {
            }
        }
    }

    修正例
    public class GoodSample {
        private int getNumber(){
            return 1;
        }
        private String getname(int i){
            return "Yonebayashi" + String.valueOf(i);
        }
        /**
         * 修正:方法调用后的值被用在条件语句中,便于理解。
         */
        public void goodSampleMethod(){
            int number=getNumber();
            String name=getname(number);
            if (StringUtil.isEmpty(name)==false) {
                //修正
            }
        }
    }

2.3.6 使用IDE自动生成访问方法(get/set方法)

⭐ ⭐ ⭐ ⭐

【说明・动机】
  所谓的访问方法是get方法和set方法,它可以用IDE自动生成的。在JAVA中get,set方法是由IDE自动生成的,这样避免发生类型错误或代码错误。同时也会生成对应的注释。

2.4 文档注释

2.4.1 注释(Javadoc)中至少要有author,param,return,throws

⭐ ⭐ ⭐

【说明・动机】
  像下面这样记述类,方法,字段的文档注释。特别是用public声明的,必须要注释。下记以外的标签有必要的话,也要记述。
「注释的样式」

  • 以「/**」开始
  • 第二行以后以「*」开始,下面的javadoc的标签就在本内容里描述
  • 以「*/」结束

「类」

  • @author[作者]
    类的作者以及修改者的记述。
     有多个记述的情况,每人一个标签来记述。

「方法」

  • @param[标识符][说明]
     参数的标识符以及说明,按声明的顺序来记述
  • @return[说明]
     如果有返回值、记述返回值说明
  • @throws[标识符][说明]
     调用方法时,有可能抛出异常,记述异常的说明。

「字段」
 关于字段的说明
 尽可能用容易理解的标识符。只有标识符很难理解的情况下,用注释来记述标识符。

1
**注意:本书中例子的代码是以讲解规约为目的。为了进一步描述规约的要点,部分注释已经省略了。**
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    sample
    /**
     * sampleMethod的说明
     *
     * @param value
     * 参数的说明
     * @return 返回值的说明
     * @throws SampleException
     * 异常的说明
     */

    public String sampleMethod(String value) throws SampleException {
        String returnVaule = "SAMPLE";
        // 做一些处理
        return returnVaule;
    }

MEMO javadoc的列表显示
记述Javadoc时,别的一般标签如下:

标签 说明
@since 加入这个类或方法的版本号
@see 参考其他的类,方法,以及外部文献
@deprecated 已经废弃的类,方法
[@link] Javadoc里标签嵌入链接

2.4.2 注释应当尽量简洁

⭐ ⭐ ⭐ ⭐

【说明・动机】
  注释、真正需要的注释应尽可能的简洁。「真正需要的」最好的例子是代码的意图和目的。从代码中很难读取到意图和目的信息的。为什么需要这段代码,所以要详细的描述代码的意图和目的。
  此外,为了让注释便于理解,要用到以下三个方面。

  1. 字段声明时,清晰简易的命名就不需要注释了
  2. 修改者名,修改日等不要加(修正履歴的管理用版本管理工具来进行)
  3. (注释很长的场合)总结一句话的要点,使内容容易理解。

下面的违反例当中如果没有说明实际意义和目的的逻辑,将降低代码的可读性。注释冗余或者补充不完全整的情况,没有追加合适的变量名,方法名,以及类名。用以下的处理方式。

  • 追加合适的变量名,方法名,以及类名
  • 通过方法名很难推断出方法作用的这种情况发生的时候,对于补充的注释应该追加上去。

此外、例子里、类的注释省略了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
    违反例
    /**
     *  「(javadoc)应当尽量简洁 」 的不好的示范
     * 违反:不知道为什么不加数组最后的值
     */
    public class BadSample {
        /**
         * 求和方法
         *
         * @param elements
         *            求和的数值的数组
         * @return  参数数组的和
         */
        public int sum(int[] elements) {
            if (elements == null) {
                throw new IllegalArgumentException("要素はnullであってはいけません");
            }
            // 保存和的结果
            int sum = 0;
            // 循环计算数组的和,最后的值不计算
            for (int i = 0; i < elements.length - 1; i++) {
                // 每个数组元素取出来计算
                sum += elements[i];
            }
            // 返回结果
            return sum;
        }
    }

    修正例
    /**
     * 「(javadoc)应当尽量简洁 」 的好的示范
     *
     * 修正:知道了为什么不加数组最后的值
     *
     */
    public class GoodSample {

        /**
         * ×××业务用的数据元素,返回和
         *
         * ×××业务中,为了和即存系统的数据交互,数组的最后一个为假数据
         * 这个值不加入计算 参数的数组元素要素不能为null
         *
         * @param elements
         *            求和的数值的数组
         * @return  参数数组的和
         * @throws IllegalArgumentException
         * 参数的元素为null时,抛出异常
         */
        public int sum(int[] elements) {
            if (elements == null) {
                throw new IllegalArgumentException("元素不可为null");
            }
            int sum = 0;
            for (int i = 0; i < elements.length - 1; i++) {
                sum += elements[i];
            }
            return sum;
        }
    }

⭐ MobileOffice系统追加【注释的记述】

  • 所有文件的最前头首先要有版权内容。(Apache 2.0 Licence的时候,要有License许可)
  • 其次、author(作者)和类的概要也要记述。(author在eclipse里用Alt + Shift + J快捷键插入。)
  • 再往下就是记述更新者、更新履历、更新日期、更新内容。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    /*
     * Copyright (C) 2013 The CASIO Android Project
     *
     *
     */
    package jp.casio.vx.xxx.yyy;

    import java.util.List
    /**
     * @author 〇〇〇<br>
     *         概要   :XXXXXXXXXXXXXXXX(类的概要)<br>
     *         作成日付:9999/99/99<br>
     * <br>
     *         更新者  :XXXXXXXXXXXXXXX<br>
     *         更新履歴 :【1】<br>
     *         更新日付 :9999/99/99<br>
     *         更新内容 :XXXXXXXXXXXXXXXXXXXXXX<br>
     * 
     *         更新者  :XXXXXXXXXXXXXXX<br>
     *         更新履歴 :【2】<br>
     *         更新日付 :9999/99/99<br>
     *         更新内容 :XXXXXXXXXXXXXXXXXXXXXX<br>
     */
    public class PageLinkAdapter extends ArrayAdapter<Page>{
        ................
    }

2.5 包

2.5.1 不要导入java.lang包

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  java.lang包,即使没有写出来,它也是默认导入的。因此,开发人员没有必要明确的导入进来。

1
2
3
4
5
6
7
8
9
    违反例
    import java.lang.*;         //违反
    public class BadSample {
    }

    修正例
    //已修正
    public class GoodSample {
    }

2.5.2 不要在导入语句中使用星号

⭐ ⭐ ⭐ ⭐

【说明・动机】
  import如果带星,那个包内所有的类都将被导入。
  一看觉得很方便,但是多个包都用带星导入时,那么这个类中使用的类是从哪一个包导进来的,我们就不容易知道了,很难追溯代码。
  另外,如果导入类在当前的包中有同名类时,因为声明带星的,当前包的类将被优先使用,而有可能我们想要用的类并没有被利用。

因为这写原因,导入包时不要带*,一直要导到类名。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    违反例
    import javarulebook.data1.*;
    import javarulebook.data2.*;    //违反

    public class BadSample {
        public void badFunction(){
            //Hoge、Foo是哪个包的,一眼看不出来
            Hoge hoge = new Hoge();
            Foo hoge = new Foo();
        }
    }

    修正例
    import javarulebook.data1.Hoge;
    import javarulebook.data2.Foo;    //已修正

    public class GoodSample {
        public void goodFunction(){
            //Hoge、Foo是哪个包的,很容易看出来
            Hoge hoge = new Hoge();
            Foo hoge = new Foo();
        }
    }

2.5.3 用IDE管理导入语句

⭐ ⭐ ⭐ ⭐

【说明・动机】
  对于import语句、手动操作类的追加/删除以及修改,效率会很低。
  最新的IDE可以自动组织导入语句,所以要充分的使用IDE来完成。

2.6 类

2.6.1 不想被继承的类,用final来修饰

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  不想被继承的类请用final来声明。有以下的优点。

  • 因为它不会被继承是由编译保证,能够避免意外的继承。
  • 开发者可以明确指出「这个类不能继承」
1
2
3
4
5
6
    违反例
    public class BadSample {//违反
    }
    修正例
    public final class GoodSample {   //已修正
    }

2.6.2 按照public,protected,default,private的顺序声明变量

⭐ ⭐ ⭐

教育資料:[[オブジェクト指向について]]

【说明・动机】
  为了保证代码的可读性,字段属性的声明的访问修饰符要按照以下的顺序排列。

  • public
  • protected
  • default
  • private

但、static修饰符再上述之前优先声明。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
    违反例
    public class BadSample {
        //违反  字段的声明没有顺序
        int phoneNumber;
        public static final int NUMBER = 1;
        public String name;
        private int age;
        protected String nickName;
    }

    修正例
    public final class GoodSample {
        //已修正
        public static final int NUMBER = 1;
        public String name;
        protected String nickName;
        int phoneNumber;
        private int age;
    }

2.7 构造方法

2.7.1 不用public修饰的类,也不能有public权限的构造方法

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  不用public声明的类的构造方法,没有用public来声明吧?
  不用public声明的类的构造方法,并不是在任何地方都可以访问的。例如违反例的情况,这个构造方法它在别的包中可以调用但编译无法通过。在调用的地方,有公开的可以用但编译是错的,这样就很乱。
  为了防止这种情况,设计时请注意下面的情况。

  • 删除构造方法的public修饰符。
  • 给类加上public修饰符。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    违反例
    class BadSample { //非public的类
        public BadSample(){   //违反
        }
    }

    修正例
    class GoodSample {
        GoodSample(){   //已修正
        }
    }

2.7.2 构造方法要按照类的特性来作成

⭐ ⭐ ⭐

【说明・动机】
构造方法具有以下三个特性。

  • 只有实例化的时候才能用
  • 根据构造方法的访问权限,限定实例化的范围。
  • 创建参数不同的构造方法时,可以控制初始化的状态。

考虑到这些特点基础之上,再创建构造方法。
创建构造方法,大致有5种模式。
①实例化时,不需要处理参数
  这种情况,不需要实现构造方法。默认的构造方法会自动生成。
②不管有没有参数,构造方法都可以实例化。
  实例化时可以传递必要的值,或也可以用无参构造方法实例化后再通过设定方法等设定值的时候,有参和无参的构造方法都必须声明。
  只有一个构造方法被实例化时,默认的构造方法不会自动生成。出于这个原因,实现一个带参数的构造方法后,如果想实例化一个无参的构造方法就必须要把它明确的声明出来。
③实例化的时候要求有参数
  不允许实例化没有参数的构造方法。这个时候只要作成带参数的构造方法。不实现无参的构造方法时,没有参数的实例化也是不可以的。
④类想被继承,但不想被外部其他类实例化
  不想对子类以外的类公开时的构造方法,访问权限设定为protected,这时构造方法的可用范围就被限定在子类中。
⑤不想被实例化
  实用工具类等,不能实例化的情况。这种场合,作成无参的构造方法,而且构造方法的访问权限设定为private。

不是所有的类都适用这种构造方法,需要根据类的特性分别使用对应的构造方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    sample
    // 只有默认的构造方法,不要声明
    public class GoodSample1 {
    }

    // 有参数和没有参数两个构造方法都需要的时候、两个都要声明
    public class GoodSample2 {
        public GoodSample2(){
        }
        public GoodSample2(int value){
        }
    }

    // 只能实例化有参数的时候,只要声明有参数的
    public class GoodSample3 {
        public GoodSample3(int value){
        }
    }

    // 不想被外部其他类实例化的时候,用protected来修饰
    public class GoodSample4 {
        protected GoodSample4(int value){
        }
    }

    // 不想被实例化,用private来修饰
    public class GoodSample5 {
        private GoodSample5(){
        }
    }

2.8 方法

2.8.1 不想被重写的方法要用final修饰

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  一个方法被子类重写之后,有些情况下它的行为会发生很大的变化。不想被重写的方法要用final来声明。因此、方法不能被重写可以通过编译器来保证。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
违反例
    public class BadSample {
        // 可能会被重写
        public void badMethod() {
        }
    }
    修正例
    public class GoodSample {
        // 修正 用final来声明、不能被重写
        public final void goodMethod() {
        }
    }

2.8.2 返回值是数组或Collection的场合不要返回null

⭐ ⭐ ⭐ ⭐

【说明・动机】
  对于返回值是数组或Collection的方法,大多数情况下调用的地方会对这个方法的返回的数组或者Collection进行循环。像这种情况如果返回null的话,调用的地方必须要check返回的结果是否是null.
  因此、要返回长度是0的数组或空的Collection(Collections.emptyList())。这样的话,调用的地方就不需要check返回值是否是null。
  没有必要返回null的场合、null用长度是0的数组或空的Collection代替返回。
  如果一直使用这个策略的话,调用的地方就会变得容易。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    违反例
    public class BadSample {
        public String[] getArray(int value) {
            if (100 < value) {
                return null; // 返回null。 违反
            } else {
                // 实际做些处理后返回
                return new String[value];
            }
        }
        public List<String> getList(int value) {
            if (100 < value) {
                return null; // 返回null。 违反
            } else {
                // 实际做些处理后返回
                return new ArrayList<String>(value);
            }
        }
    }

    修正例
    public class GoodSample {
        public String[] getArray(int value) {
            if (100 < value) {
                return new String[0];
                // 修正.长度为0的数组。
            } else {
                // 实际做些处理后返回
                return new String[value];
            }
        }

        public List<String> getList(int value) {
            if (100 < value) {
                return Collections.emptyList();
                // 修正。用空的list代替null
            } else {
                // 实际做些处理后返回
                return new ArrayList<String>(value);
            }
        }
    }

2.8.3 设计public方法时,不要破坏类的整体性

⭐ ⭐ ⭐ ⭐

【说明・动机】
  设计public方法的时候、不要破坏类内部的整体性、请慎重的设计。

【解说】

在违反例的类里,stockRightSpeaker和stockLeftSpeaker是两个相同属性的数值。当我们想要「Speaker通常是左右两个一起卖」时、像下面的设计就会有问题。在这个类里、每个sell方法是通过外部调用来修改stockRightSpeaker和stockLeftSpeaker的值的。但是、万一仅仅只改变了其中一个的值、那么「Speaker左右一起卖」这个前提就容易崩溃。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    违反例
    public class BadSample {
        private int stockRightSpeaker;
        private int stockLeftSpeaker;
        //左右SET为前提,卖话筒的时候
        //如果单独调用方法的时候,就会破坏整合性
        public void sellRightSpeaker(int quantityRight){
            stockRightSpeaker = stockRightSpeaker - quantityRight;
        }
        public void sellLeftSpeaker(int quantityLeft){
            stockLeftSpeaker = stockLeftSpeaker - quantityLeft;
        }
    }

【解说】

在修正例里、stockRightSpeaker和stockLeftSpeaker不需要分别访问、在一个方法里一次性修改。
  这样的话、调用的地方只要访问sellSpeaker方法就可以了、「Speaker左右一起卖」这个前提就能维持了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    修正例
    public class GoodSample {
        private int stockRightSpeaker;
        private int stockLeftSpeaker;
        //从外部只能访问一对一起卖的方法。
        public void sellSpeaker(int quantity){
            stockRightSpeaker = stockRightSpeaker - quantity;
            stockLeftSpeaker = stockLeftSpeaker - quantity;
        }
    }

2.8.4 一个方法只做一件事

⭐ ⭐ ⭐ ⭐

【说明・动机】
  一个方法里处理多个不同内容的时候、代码的可读性・维护性・扩张型・再利用性等都会受到恶劣的影响。方法的功能要分割开。

【解说】

在违反例里、switchXandY方法是X和Y交换的功能,但是也放入了表示值的功能。
这个方法使用的时候、不要有显示值的功能、在调用switchXandY方法之后再进行值的显示。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    违反例
    import java.awt.Point;
    public class BadSample {
        private Point point;
        public static void main(String[] args) {
            Point point = new Point(55, 77);
            BadSample smpl = new BadSample(point);
            smpl.switchXandY();
        }
        public BadSample(Point point) {
            this.point = point;
        }
        //  违反 不仅做了值得变更,还做了值得显示
        private void switchXandY() {
            //  值的显示
            StringBuilder sb = new StringBuilder();
            sb.append("X is ");
            sb.append(point.getX());
            sb.append(" and Y is ");
            sb.append(point.getY());
            System.out.println(sb);
            // 值的修改
            double x = point.getX();
            double y = point.getY();
            point.setLocation(y, x);
            // 值的显示
            sb.setLength(0);
            sb.append("X is ");
            sb.append(point.getX());
            sb.append(" and Y is ");
            sb.append(point.getY());
            System.out.println(sb);
        }
    }

【解说】

在修正例里、X和Y的交换功能和値的显示功能分开了、每个方法的可读性提高了、重复的代码也没有了。另外、如果不要显示值的功能那就不要调用printValue()方法,仅仅调用想要功能就可以了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    修正例
    import java.awt.Point;

    public class GoodSample {
        private Point point;

        public static void main(String[] args) {
            Point point = new Point(55, 77);

            GoodSample smpl = new GoodSample(point);
            smpl.printValue();
            smpl.switchXandY();
            smpl.printValue();
        }

        public GoodSample(Point point) {
            this.point = point;
        }

        // 修正 交换X和Y
        private void switchXandY() {
            double x = point.getX();
            double y = point.getY();
            point.setLocation(y, x);
        }
        // 修正 值的显示方法
        private void printValue(){
            StringBuilder sb = new StringBuilder();
            sb.append("X is ");
            sb.append(point.getX());
            sb.append(" and Y is ");
            sb.append(point.getY());
            System.out.println(sb);
        }
    }

2.8.5 不要重载参数个数相同的方法

⭐ ⭐ ⭐ ⭐

【说明・动机】
  像下面的应用重载参数个数相同的方法是可以的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    public class ConvertUtils {
        public String toString(String s) {
            return s;
        }

        public String toString(int n) {
            String s = Integer.toString(n);
            return s;
        }
    }

但是、如果没有确定追加源代码参数的类型、就很难理解哪个方法被执行了。因此、像解说里记述的那样源代码的可读性就会下降。特别的、重载方法的参数有继承关系的场合、可能会调用到不想调用的方法、会导致意外的混乱。
实现的技巧是要使用重载时、要牢牢把握用法防止混乱。没必要时、请尽量不要重载参数个数相同的方法。

【解说】

在例子里、作成3个sampleMethod方法来处理异常对象。main方法里、生成实例后调用sampleMethod方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    sample
    public class BadSample {
        public static void main(String[] args) {
            Exception e1 = new Exception();
            IOException e2 = new IOException();
            // IOException的子类
            FileNotFoundException e3 = new FileNotFoundException();

            BadSample sample = new BadSample();
            sample.sampleMethod(e1);// ①
            sample.sampleMethod(e2);// ②
            sample.sampleMethod(e3);// ③
        }

        public void sampleMethod(Exception e) {
            System.out.println("Exception");
        }

        public void sampleMethod(IOException e) {
            System.out.println("IOException");
        }

        public void sampleMethod(Object e) {
            System.out.println("Object");
        }
    }

【解说】
执行结果、①,②,③对应的结果如下
 Exception
 IOException
 IOException
但是、只看①,②,③行、不知道哪个方法在实行。另外③的場合、FileNotFoundException对应哪个方法被调用就变得很难把握。

2.8.6 重写的方法要使用@Override注解

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  重写的方法必须使用@Override注解。下面是使用@Override的优点。

  • 编译时可以check方法名和参数、还可以避免不能被重写的方法被重写的错误。
  • 如果重写方法在父类中被修改的话编译时可以check出错误,这样做会比较安全
  • 一眼看上去就能够明白这是重写的方法
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    违反例
    public class SuperClass {
        public String sampleMethod() {
        }
    }
    public class BadSample extends SuperClass {

        // 违反、不知道是否是重写的方法
        // 不知道和父类的方法名称和签名是否不同
        public String sampleMethod() {
        }
    }

    修正例
    public class SuperClass {
        public String sampleMethod() {
        }
    }
    public class GoodSample extends SuperClass {

        // 修正、一看就知道是重写的方法
        // 和父类的方法名,签名不一样是就会报错
        @Override
        public String sampleMethod() {
        }
    }

2.8.7 static方法要用类名来调用

⭐ ⭐ ⭐ ⭐

【说明・动机】
  使用static方法的时候,用类的实例来调用过吗?只要使用类名,不要使用类的实例。另外、在调用端调用static方法会变得明了、可读性提高了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
    违反例
    public class BadSample {
        public static void sampleClassMethod() {
            // ...
        }

        public static void main(String[] args) {
            BadSample object = new BadSample();
            object.sampleClassMethod(); // 违反
        }
    }

    修正例
    public class GoodSample {
        public static void sampleClassMethod() {
            // ...
        }

        public static void main(String[] args) {
            GoodSample.sampleClassMethod(); // 修正
        }
    }

2.9 变量・数组

2.9.1 不要一次声明多个变量

⭐ ⭐ ⭐

【说明・动机】
  不要一次声明多个变量。按照「一次只声明一个变量」的记述来统一代码、使每个变量的类型明确、可以提高代码的可读性。
  一次只声明一个变量在追加注释的时候可以很明确的表示出这个变量的意图。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    违反例
    public class BadSample {
        private String firstName, lastName;    //违反
    }

    修正例
    public class GoodSample {
        private String firstName;    // 修正
        private String lastName;
    }

2.9.2 不要直接使用字面值,要定义成字面常量

⭐ ⭐ ⭐ ⭐

【说明・动机】
  字面值在代码中是数值或字符串作为常量的直接表现形式。直接使用字面值,降低了代码的可读性・维护性。特别是被称作「魔法数(magic number)」的值(不能够理解为什么利用这个字面值),大大妨碍了代码的理解。
  为了避免这种情况、请使用字面常量(static final字段)。使用字面常量、赋予适当的常量名、代码的意图明确了、也变得容易理解了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    违反例
    public class BadSample {
        private int[] sampleArray = new int[65535]; // 违反

        public void sampleMethod(String str) {
            if ("ZZZZ://".equals(str)) {
                // 处理
            }
        }
    }

    修正例
    public class GoodSample {
        private static final int MAX_MEMORY_SIZE = 65535; // 修正
        private int[] sampleArray = new int[MAX_MEMORY_SIZE]; //修正

        private static final String MY_PROTOCOL = "ZZZZ://"; // 修正
        public void sampleMethod(String str) {
            if (MY_PROTOCOL.equals(str)) {
                // 处理
            }
        }
    }

2.9.3 数组声明的格式:类型名[]

⭐ ⭐ ⭐ ⭐

【说明・动机】
  像「变量名[]」声明数组的形式是C语言遗留下来的痕迹、为了维持代码的一貫性、数组的声明形式在代码中要统一。
  这里推荐「类型名[]」的形式来声明数组

1
2
3
4
5
6
7
8
9
    违反例
    public class BadSample{
        private int sampleArray[];//违反
    }

    修正例
    public class GoodSample{
        private int[] sampleArray;//修正
    }

2.9.4 尽可能的使用局部变量

⭐ ⭐ ⭐

【说明・动机】
  对类变量(静态)或者实例变量频繁访问过吗?是不是真的有必要?
  对类变量或者实例变量频繁访问时,有可能会破坏对象的状态。使用局部变量、尽量只有在必要的情况下访问类变量或实例变量。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    违反例
    public class BadSample{
        private int result;
        public void addNumber(int[] numbers){
            if(numbers==null){
                throw new IllegalArgumentException
                ("参数的数值不能为null。" );
            }
            for (int n : numbers) {
                result +=n;//违反
            }
        }
    }

    修正例
    public class GoodSample{
        private int result;
        public void addNumber(int[] numbers){
            if(numbers==null){
                throw new IllegalArgumentException
                ("参数的数值不能为null。" );
            }
            //修正 一時なローカル変数
            int tempSum=0;
            for (int n : numbers) {
                tempSum +=n;
            }
            result=result+tempSum;
        }
    }

2.9.5 常量要用static final来声明

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  常量是在整个类里值不会发生变化的。常量用static final来声明、明确的表示了这个值是不可修改的、代码的可读性提高了。
  另外、Java5以后可以使用enum。表现Group功能时、请使用enum。详细请参照「2-13-8 Group固定时要使用enum」。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    违反例
    public class BadSample{
        private int constant=5;//违反
        private int getSize(int number){
            return number * constant;
        }
    }

    修正例
    public class GoodSample{
        private static final int CONSTANT=5;//修正
        private int getSize(int number){
                return number * CONSTANT;
        }
    }

2.9.6 局部变量和实例(成员)变量要区分使用

⭐ ⭐ ⭐

【说明・动机】
  有没有使用过足够的局部变量作为实例变量使用?(有没有某个局部函数内部使用的变量声明成了实例变量?)
  不必要的实例变量是导致代码性能和可读性下降的主要原因、处理多线程的时候有可能会导致不整合。实例变量的必要性要慎重的考虑。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    违反例
    public class BadSample{
        //违反 只有一个方法使用的变量
        private int value;
        //value只在这个方法里使用
        private int calcValue(SomeObj inValue){
            //没有必要用实例变量
            value=inValue.getData();
            for (int i = 0; i < value; i++) {
                //处理
            }
        }
    }

    修正例
    public class GoodSample{
        private int calcValue(SomeObj inValue){
            //修正 方法内声明
            int value=inValue.getData();
            for (int i = 0; i < value; i++) {
                //处理
            }
        }
    }

2.9.7 成员变量使用private或者protected修饰

⭐ ⭐ ⭐

【说明・动机】
  写过用public或default修饰的实例变量吗?
  根据面向对象封装性的思想、类内部状态不是任意类都可以访问的。请定义适当的访问方法,仅仅通过访问方法来访问实例变量。   另外、实例变量要用private或protected来修饰,要极力避免直接访问实例变数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    违反例
    public class BadObject {
        public int value = 10;// 违反
    }

    public class BadSample {
        public static void main(String[] args) {
            Bad0bject bj = new Bad0bject();
            obj.value += 10; //直接访问实例变数
            System.out.println(obj.value);
        }
    }

    修正例
    public class GoodObject {
        private int value = 10;// 修正
        public int getValue(){
            return value;
        }
    }

    public class GoodSample {
        public static void main(String[] args) {
            GoodObject sample = new GoodObject();
            int result = sample.getValue();
            result += 10;
            System.out.println(result);
        }
    }

2.9.8 不要使用public static final来声明数组

⭐ ⭐ ⭐

【说明・动机】
  你是不是认为用final声明的数组元素就不可以修改了吗?
  在违反例里,即使用final来声明数组,不变的只是数组的长度,数组的元素还是可以修改的。想要保持数组的元素也不可变时、请考虑下面的情况。
1. 不要使用数组,可以用Collections类的unmodifiableList()方法声明成不可修改的List或Set(数组变换成不可修改的List或Set时、为了避免影响原来的数组请clone数组)
1. 必须声明成数组的场合、不要使用public来修饰,要使用protected或者private修饰

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    违反例
    public class BadSample {
        public static final int[] SAMPLE_ARRAY=
            { 0, 1, 2, 3 };//违反
        public static void main(String[] args) {
            System.out.println(SAMPLE_ARRAY);
            SAMPLE_ARRAY[0]=1;//值可以变化
            System.out.println(SAMPLE_ARRAY[0]);
        }
    }

    修正例
    public class GoodSample {
        public static final List<Integer> IMMUTABLE_ARRAY = Collections
                .unmodifiableList(Arrays.asList(0, 1, 2, 3));// 修正
        // 变为private的,别人就不可以访问
        private static final Integer[] SAMPLE_ARRAY = { 0, 1, 2, 3 };
        // Clone一个数组,这样可以避免对原数组的影响
        public static final List<Integer> IMMUTALBE_ARRAY_ALT = Collections
                .unmodifiableList(Arrays.asList(SAMPLE_ARRAY.clone()));

        public static void main(String[] args) {
            System.out.println(IMMUTABLE_ARRAY.get(1));
            // 因为不可变,会触发UnsupportedOperationException异常
            IMMUTABLE_ARRAY.set(0, 123);
        }
    }

2.9.9 类变量使用类名调用

⭐ ⭐ ⭐ ⭐

【说明・动机】
  使用类变量的时候、有没有用过实例变量的引用来访问?
  使用类变量的时候、请使用类名。这样的话、调用的变量是否是类变量就变得很容易理解、代码的可读性也提高了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    违反例
    public class BadSample {
        public static final int  STATIC_VALUE=10;
        public void sampleMethod(){
            BadSample object=new BadSample();
            int localValue=object.STATIC_VALUE;//违反
            System.out.println(localValue);
        }
    }

    修正例
    public class GoodSample {
        public static final int STATIC_VALUE=10;
        public void sampleMethod(){
            int localValue=GoodSample.STATIC_VALUE;//修正
            System.out.println(localValue);
        }
    }

2.9.10 使用局部变量时,再声明

⭐ ⭐ ⭐ ⭐

【说明・动机】
  有看到过这样的要求吗:「局部变量要在代码的一开始就要立即声明」?
  声明局部变量的地方和使用它的地方离的很远的话、是降低代码的可读性和维护性的主要原因。为了避免这种情况的发生、请在使用之前声明局部变量。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
违反例
    public class BadSample {
        public static void main(String[] args){
            String localValue1="javarulebook";
            String localValue2="javaルールブック";
            String localValue3="全面改訂java6対応版";//违反
            LocalVarTarget target=new LocalVarTarget();
            target.execute1(localValue1);
            //...
            target.execute2(localValue2);
            //...
            target.execute3(localValue3);
            //...
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args){
            LocalVarTarget target=new LocalVarTarget();
            //使用变量之前再声明变量,更容易理解
            String localValue1="javarulebook";//修正
            target.execute1(localValue1);
            //...
            String localValue2="javaルールブック";//修正
            target.execute2(localValue2);
            //...
            String localValue3="全面改訂java6対応版";//修正
            target.execute3(localValue3);
            //...
        }
    }

2.9.11 不要轻易的重复使用局部变量・方法的参数

⭐ ⭐ ⭐ ⭐

【说明・动机】
  一旦局部变量声明了、可以轻易的另作他用吗?
  局部变量的作用范围小,它的作用在声明的时候就已经决定了。这样做,可以提高代码的可读性和维护性。同样的,方法的参数也要避免另作他用。
  另外、为了防止(局部变量/方法的参数)被再次利用、请用final来声明局部变量/方法参数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
    违反例
    public class BadSample {
        public static final int LENGTH1 = 10;
        public static final int LENGTH2 = 5;

        public void print(int no) {
            int i = 0;
            for (i = 0; i < LENGTH1; i++) {
                System.out.println("length1:" + no);
            }
            // 违反:再重复利用已经使用过的i
            for (i = 0; i < LENGTH2; i++) {
                System.out.println("length2:" + no);
            }
            // 违反:把参数no另作他用了
            no = LENGTH1 * LENGTH2;
            System.out.println("length1 * length2:" + no);
        }
    }

    修正例
    public class GoodSample1 {
        public static final int LENGTH1 = 10;
        public static final int LENGTH2 = 5;

        public void print(int no) {
            // for循环用的i
            for (int i = 0; i < LENGTH1; i++) {
                System.out.println("length1:" + no);
            }
            // 修正.重新定义for循环用的i
            for (int i = 0; i < LENGTH2; i++) {
                System.out.println("length2:" + no);
            }
            //修正.定义一个适合的名称
            int total = LENGTH1 * LENGTH2;
            System.out.println("length1 * length2:" + total);
        }
    }

    public class GoodSample2 {
        public static final int LENGTH1 = 10;
        public static final int LENGTH2 = 5;
        //final声明的话,没有重复利用的可能性
        public void print(final int no) {
            //for循环用的i
            for (int i = 0; i < LENGTH1; i++) {
                System.out.println("length1:" + no);
            }
            // 修正.重新定义for循环用的i
            for (int i = 0; i < LENGTH2; i++) {
                System.out.println("length2:" + no);
            }
            //修正.定义一个适合的名称
            final int total = LENGTH1 * LENGTH2;
            System.out.println("length1 * length2:" + total);
        }
    }

2.10 字符串操作

2.10.1 字符串文字的声明不用new

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  当生成String对象保存文字列字符串时、使用过new吗?
  没有必要使用new来生成字符串常量。用简单的字符串文字定义成常量。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
    违反例
    public class BadSample {
        protected String name = new String("YonebaYasrh");// 违反

        public void print() {
            System.out.println(this.nane);
        }
    }

    修正例
    public class GoodSample {
        protected String name = "YonebaYasrh";// 修正

        public void print() {
            System.out.println(this.nane);
        }
    }

2.10.2 字符串之间比较相同值时使用equals()方法

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  字符串之间比较时不要使用「」或「!=」操作符、请使用String类的equals()方法。
  使用这些操作符时、不能比较「是否是相同字符串」、而是check「是否是同一个实例」。因此、例如相同字符串使用「
」比较的结果是false、这也是产生bug的原因。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    违反例
    public class BadSample {
        public static void main(String[] args) {
            String str1 = "hoge";
            String str2 = "hoge";
            System.out.println(str1 == str2);
            // 违反:感觉好像是对的,实际上只是在比较实例的地址
            str1 = str1 + "fuga";// 生成一个String的新实例
            str2 = str2 + "fuga";// 生成一个String的新实例
            System.out.println(str1 == str2);
            // 违反:因为不是一个实例,即使内容一样也是返回false
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            String str1 = "hoge";
            String str2 = "hoge";
            System.out.println(str1.equals(str2));
            // 修正:内容进行比较
            str1 = str1 + "fuga";// 生成一个String的新实例
            str2 = str2 + "fuga";// 生成一个String的新实例
            System.out.println(str1.equals(str2));
            // 修正:内容进行比较
        }
    }

2.10.3 字符串的连接用StringBuilder类

⭐ ⭐ ⭐ ⭐

【说明・动机】
  字符串连接时、使用过操作符「+」吗?
  String是不可修改的对象、为了连接每次都是重新生成新的对象、性能就下降了。
  请用下面的方式。

  • 字符串连接之前定义StringBulider
  • 字符串连接时使用StringBulider的append()方法
  • 线程同步必要的字符串连接使用StringBuffer

另外、像「String name ="a"+"b"」场合、如果在一个声明中使用“+”操作符,性能不会降低。只有1个声明的场合、「+」操作符的可读性会更高、请使用操作符「+」。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    违反例
    public class BadSample {
        public static void main(String[] arqs) {
            String s = "";
            long start = System.currentTimeMillis();
            for (int i = 0; i < 10000; i++) {
                s += Integer.valueOf(i);// 违反
            }
            System.out.println("所要时间(微秒)):"
      + (System.currentTimeMillis() - start));
            System.out.println(s);
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] arqs) {
            StringBuilder builder = new StringBuilder();
            long start = System.currentTimeMillis();
            for (int i = 0; i < 10000; i++) {
                builder.append(i);// 修正
            }
            System.out.println("所要时间(微秒)):"
      + (System.currentTimeMillis() - start));
            System.out.println(new StringBuilder(builder));
        }
    }

2.10.4 字符串文字和变量比较时、要使用字符串文字的equals()方法

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  使用equals()方法比较字符串的场合、有下面2个方法。

  • String对象.equals([字符串文字])
  • [字符串文字].equals(String对象)

前面一个方法在调用equals()方法之前、必须要确认字符串不是null。后面一个方法没有必要确认字符串是否为null。因此、字符串的比较请使用[字符串文字].equals(string)。这样做如果代码中忘记check null的话也可以避免混合bug的风险、提高了代码的可读性和可读性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    违反例
    public class BadSample {
        public boolean validate(String userInput) {
            return (userInput != null && userInput.equals("y"));// 违反
        }
        public static void main(String[] args) {
            System.out.println("null的场合" + new BadSample().validate(null));
            System.out.println("不一致的场合" + new BadSample().validate("no"));
            System.out.println("一致的场合" + new BadSample().validate("y"));
        }
    }

    修正例
    public class GoodSample {
        public boolean validate(String userInput) {
            return "y".equals(userInput);// 修正
        }
        public static void main(String[] args) {
            System.out.println("null的场合" + new GoodSample().validate(null));
            System.out.println("不一致的场合" + new GoodSample().validate("no"));
            System.out.println("一致的场合" + new GoodSample().validate("y"));
        }
    }

2.10.5 基本型和String转换时,使用准备好的变换方法

⭐ ⭐ ⭐ ⭐

【说明・动机】
  String型和基本型的变换有各种各样的方法、使用准备好的变换方法是最容易理解的、处理也是有效的。尽可能的使用valueOf/parselnt等变换方法。
  还有、从String转换成int等使用valueOf/parselnt方法时、有可能不合适的值被当做数值型转换了。这种情况下、会发生NumberFormatExecption异常。因为NumberFormatExecption是运行时异常、本来是不能捕获(catch)的。但是、类型转换的时候,如果返回默认值的或者在log中把异常输出等,可以再捕获(Catch)时作适当的处理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
    违反例
    public class BadSample {
        public static void main(String[] args) {
            int i = 123;
            String s1 = "" + i;// 违反
            String s2 = new Integer(i).toString();// 违反
            System.out.println(s1);
            System.out.println(s2);

            int i1 = new Integer(s1).intValue();// 违反
            int i2 = Integer.valueOf(s2).intValue();// 违反
            System.out.println(i1);
            System.out.println(i2);
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            int i = 123;
            String s1 = String.valueOf(i);// 修正
            System.out.println(s1);

            try {
                Integer i1 = Integer.valueOf(s1);// 修正
                int i2 = Integer.parseInt(s1);// 修正
                System.out.println(i1);
                System.out.println(i2);
            } catch (NumberFormatException e) {
                /**
                 * NumberFormatException是运行时异常
                 * 但也有可能传入不适合数值型的值的时候就在这儿处理
                 */
                throw e;
            }
        }
    }

2.10.6 不要使用系统依存的符号(\n、\r等)

⭐ ⭐ ⭐ ⭐

【说明・动机】
  有没有在代码中直接使用过字面值「\n」和「\r」等的换行符呢?
  不同的OS、字符和字符串使用的换行符也是不同的。因此、如果在代码中使用「\n」或「\r」等换行符、可移植性就变得不好了(Java的特性是「Write Once,Run Anywhere」)。
  使用换行符或文件分割符时、不要使用系统固有的依存符号、使用System.getProperty()等方法取得系统对应的换行符或文件分隔符并使用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    违反例
    public class BadSample {
        public void displayMessage(String message) {
            StringBuilder result = new StringBuilder("Message:\n");// 违反
            result.append(message);
            System.out.println(result);
        }

        public static void main(String[] args) {
            new BadSample().displayMessage("Hello");
        }
    }

    修正例
    public class GoodSample {
        public void displayMessage(String message) {
            StringBuilder result = new StringBuilder("Message");// 修正
            String lineSeparator = System.getProperty("line.separator");
            result.append(lineSeparator);
            result.append(message);
            System.out.println(result);
        }

        public static void main(String[] args) {
            new GoodSample().displayMessage("Hello");
        }
    }

2.11 数值

2.11.1 如果想没有误差计算,可以使用BigDecimal类

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  利用科学计数法计算float、double等的浮点小数运算时、计算机内部是以2进制的形式保存的,不能正确计算出10进制小数。因此、数值的整数值和小数部分分开保存,使用「BigDecimal」类正确计算出10进制小数,适用于金额等的计算。
  另外、关于BigDecimal、不能使用参数是double型的构造方法。数値是double时、会产生误差。精度必要的情况下、请使用BigDecimal实例的参数是int、long、String、char[]型的构造方法来计算。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    违反例
    public class BadSample {
        public static void nain(String[] args) {
            double d = 0.0;
            for (int i = 0; i < 100; i++) {
                d += 0.1;
            }
            // 想象中是:10,但实际的值是:9.99999999999998
            System.out.println(" double:" + d);
        }
    }
    修正例
    public class GoodSample {
        public static void nain(String[] args) {
            BigDecimal value = new BigDecimal("0.0");
            BigDecimal addValue = new BigDecimal("0.1");
            for (int i = 0; i < 100; i++) {
                value = value.add(addValue);
            }
            // 正确的值10
            System.out.println(" BigDecimal:" + value.toPlainString());
        }
    }

2.11.2 数值的比较要注意精度

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  double或float浮点小数数值计算时,因为会四舍五入,因此会产生误差。因此、使用比较操作符比较数值相同的场合、不能保证是否能够得到想要的结果。
  应用中由于四舍五入产生误差的场合、请使用BigDecimal代替double或float。
  另外、容许一定程度的误差存在的场合,遇到修正例里的比较方法,在容许误差的范围里比较是否相等。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
    违反例
    public class BadSample {
        public static void main(String[] args) {
            new BadSample().sampleMethod();
        }

        private void sampleMethod() {
            double d = 0;
            // 违反.0.1累计加10回也不是1,就变成死循环了
            while (d != 1) {
                System.out.println("value:" + d);
                d += 0.1;
            }
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            new GoodSample().sampleMethod();
        }
        private void sampleMethod() {
            double d = 0;
            // 修正.允许误差:10e-8
            while (equalsDouble(d, 1, 10e-8) == false) {
                System.out.println("value:" + d);
                d += 0.1;
            }
        }
        private boolean equalsDouble(double value1, int value2, double delta) {
            double diff = Math.abs(value1 - value2);
            if (diff <= delta) {
                return true;
            } else {
                return false;
            }
        }
    }

2.11.3 要避免向低精度的基本型转换

⭐ ⭐ ⭐ ⭐

【说明・动机】
  表示数值的基本类型的变量定义了数值的精度。高精度的变量向低精度类型转换的时候,转换类型的精度值发生了改变。因此、计算的时候有可能会产生误差。请尽可能的避免向低精度的类型转换。

【解说】
下面的程序中,long型的数值转换成int型的数值。输出了下面的执行结果。
int > long

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    sample
    public class Sample {
        private void sampleMethod() {
            int inValue = 100;
            long longValue = 1000000000000L;
            int longToValue = (int) longValue;
            if (inValue > longToValue) {
                System.out.println("int > long");
            }
        }
    }

2.12 日期

2.12.1 Date和Calendar的转换使用long

⭐ ⭐

【说明・动机】
  想过「日期处理的时候,必须使用Calendar或Date类」吗?仅仅是处理单纯的日期的数据的情况下可以用long来代替。
  取得当前时间的场合,使用System#currentTimeMillis()(或者はSystem#nanoTime())、保持返回值是long是最省成本的。保持大量日期的场合、控制资源利用率的情况下、请考虑使用long。
  另外、Date或Calendar对象的值是可以修改的。若干个对象共用相同的Date或Calendar声明的日期实例时,请注意值的修改。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    sample
    public class GoodSample {
        private void sampleMethod() {
            // long->Calendar、Date的转换
            long longTime = System.currentTimeMillis();
            Calendar longToCalender = Calendar.getInstance();
            longToCalender.setTimeInMillis(longTime);
            Date longDate = new Date(longTime);

            // Calendar->long的转换
            Calendar calendar = Calendar.getInstance();
            long calendarToLongTime = calendar.getTimeInMillis();

            // Date->long的转换
            Date date = new Date();
            long dateToLongTime = date.getTime();
        }
    }

2.13 Java5以后追加的功能

2.13.1 尽可能使用扩张型for语句

⭐ ⭐ ⭐ ⭐

【说明・动机】
  Java5以后使用for语句的时候、尽可能的使用扩张型for语句。使用扩张型for语句的话、可以实现原来for语句简单的循环处理。例如根据索引值访问或者不使用迭代器的遍历(对象)、根据处理的内容使用专门的代码来记述。请积极地活用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
    违反例
    public class BadSample {
        public static void main(String[] args) {
            String[] strs = { "a", "b", "c", "d", "e" };
            // 违反.没有必要使用索引值。
            for (int i = 0; i < strs.length; i++) {
                String s = strs[i];
                System.out.println("array string:" + s);
            }

            List<String> list = new ArrayList<String>();
            list.add("a");
            list.add("b");
            list.add("c");
            list.add("d");
            list.add("e");
            //违反.没有必要使用索引值。 
            for (Iterator<String> itr= list.iterator(); itr.hasNext(); ) {
                String s=itr.next();
                System.out.println("interator string" + s);
            }

            Map<Integer, String> map = new HashMap<Integer, String>();
            map.put(1, "AAA");
            map.put(2, "BBB");
            map.put(3, "CCC");
            map.put(4, "DDD");
            //违反.没有必要使用索引值。
            Iterator<Integer> iterator= map.keySet().iterator();
            while (iterator.hasNext()) {
                int key = iterator.next();
                String value = map.get(key);
                System.out.println("key==" + key + "..value==" + value);
            }
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            String[] strs = { "a", "b", "c", "d", "e" };
            // 修正 尽量使用扩张型for语句。
            for (String s : strs) {
                System.out.println("array string:" + s);
            }

            List<String> list = new ArrayList<String>();
            list.add("a");
            list.add("b");
            list.add("c");
            list.add("d");
            list.add("e");

            // 修正 尽量使用扩张型for语句。
            for (String s : list) {
                System.out.println("interator string" + s);
            }


            Map<Integer, String> map = new HashMap<Integer, String>();
            map.put(1, "AAA");
            map.put(2, "BBB");
            map.put(3, "CCC");
            map.put(4, "DDD");
           //修正 尽量使用扩张型for语句。
            for (Entry<Integer, String> entry : map.entrySet()) {
                int key = entry.getKey();
                String value = entry.getValue();
                System.out.println("key==" + key + "..value==" + value);
            }
        }
    }

2.13.2 要积极使用Collection少用数组

⭐ ⭐ ⭐ ⭐

教育資料:[[Generics・ジェネリクス]]

【说明・动机】
  Java5以后使用for语句和泛型使代码容易理解,也提高了可维护性,可以写出简洁的代码。没有理由不使用。
  因此尽量不要使用数组,请使用集合类。因为数组没有使用泛型的效果。使用集合类、正确指定泛型的类型、请使用上述的扩张型for语句。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    违反例
    public class BadSample {
        public static void main(String[] args) {
            List<String> list1 = Arrays.asList("a", "b");
            List<String> list2 = Arrays.asList("c", "d");
            // 编译错误
            // List<String>[] allList = new List<String>[] { list1, list2 };
            List<?>[] allList = new List<?>[] { list1, list2 };
            for (List<?> list : allList) {
                for (Object object : list) {
                    // 泛型类型信息缺失了
                    System.out.println(object);
                }

            }
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            List<String> list1 = Arrays.asList("a", "b");
            List<String> list2 = Arrays.asList("c", "d");

            List<List<String>> allList = new ArrayList<List<String>>();
            allList.add(list1);
            allList.add(list2);

            for (List<String> list : allList) {
                for (String string : list) {
                    // 泛型的类型可以使用扩张型for语句
                    System.out.println(string);
                }
            }
        }
    }

2.13.3 添加注解的代码容易理解

⭐ ⭐ ⭐ ⭐

【说明・动机】
  Java5以后可以给代码添加注解。添加注解的场合、「这个代码是处理什么的」「(这段代码)有什么意义」都能够表现出来。注解自身是没有处理功能的、像下面的情况要活用。

  • 利用注解来执行程序
  • 指定注解的类型,可以检索出赋予注解的部分

还有、注解不但是程序,而且编译器、IDE也使用。
  例如标准的注解有@Override、在编译的时候能够正确的检查出方法是否是重写的。还有、有@Deprecated注解的变数・方法・类等不推荐,可以利用IDE直观的表现出来。
  (注解)对开发是有益的、请积极使用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
    sample
    public interface AnntationUseSample {
        String excute(String[] args);

        /**
         * 这个方法是废弃的。在版本×××中会被废弃。
         * 请使用{@link #excute(String[])} 代替
         */
        @Deprecated   // 标记清楚这个方法以后会被废弃
        String excuteOther(String arg);
    }
    public class AnnotationUseSampleImpl implements AnntationUseSample {
        @Override
        // 标记清楚(这个方法)重写接口中的方法
        public String excute(String[] args) {
            if (args == null) {
                return "";
            } else {
                StringBuilder builder = new StringBuilder();
                for (String s : args) {
                    builder.append(s);
                    builder.append(":");
                }
                if (0 < builder.length()) {
                    builder.setLength(builder.length() - 1);
                }
                return new String(builder);
            }
        }

        /**
         * 这个方法被废弃。版本ン×××中会被废弃。
         * 请使用{@link #excute(String[])} 代替
         */
        @Override
        @Deprecated
        public String excuteOther(String arg) {
            return excute(new String[]{arg});
        }
    }

2.13.4 要掌握注解的基本作成方法

⭐ ⭐

【说明・动机】
  Java5以后注解可以自己作成。基本的作法要学会、Java的API或者框架的自定义化处理时会很方便。要掌握Java提供的标准的注解的意义。

【解说】

Java中添加注解的时候对特别的接口的定义要使用@interface。 @interface与普通的接口有以下2点不同的特点。

  • 方法的返回值是String、基本型、Class、注解、enum或者1维数组
  • 使用注解的地方省略定义时的默认值是default。

例如下面是注解的定义

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
sample
    /**
     * 只能是类型的注解
     */
    @Target(ElementType.TYPE)
    /**
     * 运行时注解
     */
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AnnotationSample {

        /**
         * 值:必须指定
         */
        String value();

        /**
         * 没有指定priority是,用-1作为默认值
         */
        int priority() default -1;

        /**
         * targetType没有指定时,默认值是String
         */
        Class<?> targetType() default String.class;

        /**
         * LogOption没指定,默认值是DEBUG
         */
        LogOption logOption() default logOption().DEBUG;
    }

【解说】

另外,像下面这样调用。

1
2
3
4
    sample
    @AnnotationSample(value="注解的sample。", logOption=LonOption.FATAL);
    public class Hoge {
    }

【解说】

Jave提供了下面的标准的注解

  • 注解名说明

@Target : 给ElementType指定类和方法等的注解对象。
@Retention : RetentionPolicy指定注解的有效期间
@Inherited : 子类继承注解的效果
@DocumentedJavadoc : 在文档中维持注解的内容

指定注解的元素和设定有效期间的枚举,有一下的方法。

  • 类名的说明

指定ElementType注解的元素。
2.13.4-1
指定RetentionPolicy注解的有效期间。
2.13.4-2

2.13.5 List或Map要使用泛型

⭐ ⭐ ⭐ ⭐

【说明・动机】
  Java5以后使用泛型时要严格指定泛型的类型。使用泛型的话、会出现像下面的情况。

  • 可以避免类型转换时出错(修正例的1、3)
  • 可以降低填充值类型不同的风险(修正例的2)

首先、List或Map的类型要严格的声明,这样才能提高代码的维护性。List或Map等处理集合类的场合、要正确指定泛型的类型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
    违反例
    public class GenericsBadSample {
        public static void main(String[] args) {
            List list = new ArrayList();
            list.add("aaa");
            list.add("bbb");
            list.add("ccc");

            for (int i = 0; i < list.size(); i++) {
                // ClassCast风险
                Integer num = (Integer) list.get(i);
            }

            Map map = new HashMap();
            map.put("a", new Integer(1));
            map.put("b", "mogemoge");

            for (Iterator itr = map.keySet().iterator(); itr.hasNext();) {
                String key = (String) itr.next();
                // ClassCast风险
                System.out.println(key);
            }
        }
    }

    修正例
    public class GenericsSample {
        public static void main(String[] args) {
            List<String> list = new ArrayList<String>();
            list.add("aaa");
            list.add("bbb");
            list.add("ccc");

            for (int i = 0; i < list.size(); i++) {
                Integer num = (Integer) list.get(i);
                // 1.编译错误。在编译时出错
                String s = list.get(i);// 这个是正确的
            }

            Map<String, String> map = new HashMap<String, String>();
            map.put("a", new Integer(1));
            // 2.编译错误。编译时不能put
            map.put("b", "mogemoge");

            for (Iterator<String> itr = map.keySet().iterator();
                itr.hasNext();) {
                String key = (String) itr.next();
                Integer i = (Integer) map.get(key);
                // 3.编译错误。在编译时出错
                String s = map.get(key);
                System.out.println(s);
            }
        }
    }

2.13.6 不要使用自动装箱、必须进行null转换

⭐ ⭐ ⭐ ⭐

【说明・动机】
  Java5以后、基本型和包装型(引用型)利用自动装箱功能可以自动转换。基本型转换成包装型是装箱,反之,是拆箱。
  乍一看感觉非常的便利,但是包装型的值是null时自动拆箱转换成基本型时会出现问题。包装型的值是null时,自动拆箱的情况下会发生NullPointerExcepion异常,有可能是非常难以理解的bug。
  因此、自动装箱对所有对象不要使用,需要使用的话要确保通过Utility方法对null值进行转换。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
    违反例
    public class BadSample {
        public static void main(String[] args) {
            List<Integer> list = new ArrayList<Integer>();
            list.add(1);
            list.add(2);
            list.add(3);
            list.add(null);

            // 违反.自动装箱转换时有发生NullPointerException异常的风险
            for (int i : list) {
                System.out.println(i);
            }
        }
    }


    修正例
    public class GoodSample {
        public static void main(String[] args) {
            List<Integer> list = new ArrayList<Integer>();
            list.add(1);
            list.add(2);
            list.add(3);
            integer num = null;

            /*
             * 修正。
             *  自动装箱转换时的Null会发生异常。
             *  下面的应用方法设定了默认值,防止了异常的发生。
             */
            final int defaultValue = getDefaultValueIfNull(int.class, num);
            list.add(defaultValue);
            for (int i : list) {
                System.out.println(i);
            }
        }

        /**
             * 自动装箱时、包装型转换成基本型时、
             * null的场合、预先规定默认值
             * Utility例。
             *
             * @param <T>
             *            包装型的类型
             * @param type
             *            包装型的类型
             * @param value
             *            实际要变换成的值
             * @return 转换成基本型的值
             */
        @SuppressWarnings("unchecked")
        private static <T> T getDefaultValueIfNull(Class<T> type, T value) {
            if (value != null) {
                return value;
            }
            // 内容省略
        }
    }

2.13.7 泛型代入的场合要使用边界通配符

⭐ ⭐

【说明・动机】
  A类为B类的父类,A类的数组变量,也可以用B类的数组代入。例如Number是Double的父类、Number的数组变量,可以用Double的数组赋值。
  但是,在使用泛型时,有时这种赋值不起作用。
  例如,您不能将List<Double>分配给List<Number>List<Number>List<Double>作为不同类型处理。
  在这种情况下,请使用边界通配符。 边界通配符是一种以「?extends T」形式指定泛型类型的方法。 上面的示例是“如果是继承T类型的子类,都可以传入”。 所以,List<?extends Number>声明,是可以用List赋值的。还可以指定List<Integer>List<Long>赋值。
  还有一种「?super T」的声明也是可以的,但用的比较少,就不说明了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    违反例
    public class BadSample {
        public static void main(String[] args) {
            Double[] doubleArray = new Double[] { Double.valueOf(1.11),
            Double.valueOf(2.22) };
            //Number的数组可以用Double的数组来置换
            Number[] numberArray = doubleArray;

            List<Double> doubleList = new ArrayList<Double>();
            doubleList.add(Double.valueOf(1.11));
            doubleList.add(Double.valueOf(2.22));
            //下面编译出错
            List<Number> numberList = doubleList;
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            Double[] doubleArray = new Double[] { Double.valueOf(1.11),
            Double.valueOf(2.22) };
            // Number的数组可以用Double的数组来置换
            Number[] numberArray = doubleArray;

            List<Double> doubleList = new ArrayList<Double>();
            doubleList.add(Double.valueOf(1.11));
            doubleList.add(Double.valueOf(2.22));

            // 使用境界通配符
            List<? extends Number> numberList = doubleList;
        }
    }

2.13.8 表示一组固定值时使用枚举

⭐ ⭐ ⭐

教育資料:[[Enumの使い方]]

【说明・动机】
  Java5以后,表示一组固定值使用enum。可以简洁的表现出Group内包含的项目。有从Group中选择内容的情况时,请使用enum。
  另外、enum值追加或删除的时候、引用enum的部分必须重新再编译。这点要注意。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
    sample
    //职业种别的表示用enum
    public enum JobType {
        PROGRAMMER, ARCHITECT, SALES, STAFF, MANAGER, PRESIDENT;
    }
    //调用代码
    public class EnumSample {
        protected final JobType jobType;
        public EnumSample(JobType jobType) {
            this.jobType = jobType;
        }
        public JobType getJobType() {
            return this.jobType;
        }
        public static void main() {
            // 用枚举可以明确的把组内的分支项传过来
            EnumSample sample = new EnumSample(JobType.PROGRAMMER);
            JobType job = sample.getJobType();
            System.out.println(job);
            // valueOf()可以把字符串转为分支项
            JobType programmer = JobType.valueOf("PROGRAMMER");
            if (programmer == job) {
                System.out.println("JobType取得成功:" + job);
            }
            //Enum#values():可以enum的所有元素
            for (JobType type : JobType.values()) {
                printJobType(type);
            }
        }
        private static void printJobType(JobType job) {
            //enum也可以在switch语句中使用
            switch (job) {
            case ARCHITECT:
                System.out.println("架构师");
                break;
            case PROGRAMMER:
                System.out.println("程序员");
                break;
            case MANAGER:
                System.out.println("经理");
                break;
            case PRESIDENT:
                System.out.println("总裁");
                break;
            case SALES:
                System.out.println("销售员");
                break;
            case STAFF:
                System.out.println("员工");
                break;
            default:
                System.out.println("上面分支一个都不是的情况");
                break;
            }
        }
    }

2.13.9 枚举中不存在的值不能用调用Enum#valueOf

⭐ ⭐ ⭐

教育資料:[[Enumの使い方]]

【说明・动机】
  把字符串转换成枚举实例使用Enum#valueOf()比较方便。但是、valueOf方法有可能会代入enum中不存在的值吗?
  Enum#valueOf()里放入了不存在的字符串时,会抛出illegalArgumentException异常。例如从DB中pulldown的项目显示到画面上时、最好不要返回null。这种情况推荐自己写转换的逻辑。

  • enum中的值完全知道的场合 ⇒  Enum#valueOf()
  • 不知道enum中的值的场合⇒编写转换逻辑
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    违反例
    public class BadSample {
        public static void main(String[] args) {
            //从DB里取得的语言代码转换为枚举
            String s = getFromDatabase(-1);
            Lanuage lang = Language.valueOf(s);//违反
            System.out.println(lang);
        }

        (省略)
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            //从DB里取得的语言代码转换为枚举
            String s = getFromDatabase(-1);
            Lanuage lang = GoodSample.toEnum(s);//修正
            System.out.println(lang);
            Lanuage ja = toEnum("JAPANESE");
            System.out.println(ja);
        }

        /**
         * 为了避免IlleaglArgumentException异常、要自己写变换的逻辑
         */
        private static Lanuage toEnum(String s) {
            for (Lanuage lang : Lanuage.values()) {
                if (lang.name().equals(s)) {
                    return lang;
                }
            }
            return null;
        }
        (省略)
    }

2.13.10 尽可能不要使用可变长度的参数

⭐ ⭐ ⭐

【说明・动机】
  Java5以后引入了可变长度的参数、方法可以接受任意多个参数。例如hoge(String・・・args)方法可以接受0个到多个参数。内部编译时会转换成数组来处理。
乍一看感觉很方便,但是下面几种情况有可能会引起错误或bug。

  • 调用方难以理解到底调用的是哪个方法
  • 可变长度参数必须在方法参数的最后位置、限制了Java的情况、不是本来的方法设计。
  • 特别的、类型模糊的可变长度的参数有可能调用不到本来想要调用的方法。

因此、不要使用可变长度参数、使用集合类或者普通的数组。
(详细请参照「2-13-2 积极使用Collection,少用数组」)。集合类不能满足要求的情况下使用数组。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
    违反例
    public class BadSample {
        public static String append(String header, String... args) {
            StringBuilder builder = new StringBuilder();
            builder.append(header);
            builder.append("/");
            for (String s : args) {
                builder.append(s);
            }
            return new String(builder);
        }

        public static String append(String header) {
            throw new UnsupportedOperationException("还未实现");
        }

        public static void main(String[] args) {
            // 3个可变长参数调用append(String header, String... args)
            String ret = BadSample.append("HEADER", "a", "b", "c");
            System.out.println(ret);

            // 2个可变长参数调用append(String header, String... args)
            ret = BadSample.append("HEADER", "a", "b");
            System.out.println(ret);

            // 1个可变长参数调用append(String header, String... args)
            ret = BadSample.append("HEADER", "a");
            System.out.println(ret);

            // 没有可变长参数想调用append(String header, String... args)
            // 缺调用到append(String header)
            ret = BadSample.append("HEADER");//违反
            System.out.println(ret);
        }
    }

    修正例
    public class GoodSample {
        public static String append(String header, String[] args) {
            StringBuilder builder = new StringBuilder();
            builder.append(header);
            builder.append("/");
            if (args != null) {
                for (String s : args) {
                    builder.append(s);
                }
            }
            return new String(builder);
        }

        public static String append(String header) {
            throw new UnsupportedOperationException("还未实现");
        }

        public static void main(String[] args) {
            String ret = GoodSample.append("HEADER", new String[] { "a", "b", "c" });
            System.out.println(ret);

            ret = GoodSample.append("HEADER", new String[] { "a", "b" });
            System.out.println(ret);

            ret = GoodSample.append("HEADER", new String[] { "a" });
            System.out.println(ret);

            ret = GoodSample.append("HEADER",new String[0]);// 修正
            //但是,可以混合错误
            System.out.println(ret);
        }
    }

2.13.11 不推荐使用的类・方法・字段要赋予@Deprecated、并且要提示替代的方法

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  @Deprecated是Java提供的标准的注解,对不推荐的类・方法・字段要赋予@Deprecated。赋予了@Deprecated的类・方法・字段的代码,再编译的时候回给出警告。还有根据IDE、可以很直观的看出这个(方法、类、字段)是不推荐使用的。@deprecated和javadoc一同使用的时候、文献管理中要提示代替的方法。两者组合使用强调这个(方法、类、字段)是不推荐使用的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
    sample
    public class DeprecatedSample {
        /**
         * 设定宽度
         * @param width 宽度
         * @deprecated 这个方法被替换为
         * {@link #setSize(int, int)}
         */
        @Deprecated
        public void setWidth(int width) {
        }

        /**
         * 设定尺寸
         *
         * @param width 宽
         * @param height 高
         */
        public void setSize(int width, int height) {
        }
    }

2.13.12 使用辅助文字(增补字符)的场合、不要使用String#length()和String#charAt()

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  Java5以后支持Unicode4.0、可以使用辅助文字(SupplementaryCharacter)。
  辅助文字是Unicode规格定义的文字、代码的编码规格超过了U+FFFF。辅助文字是1个文字对应2个单位的字节。String#length()是数出String内有多少个字节的方法,是辅助文字的场合使用String#length()的返回值和文字数不一致。
  为了解决这个不一致,String类内追加了codePointCount()方法。要得到辅助文字数的场合要使用codePointCount()方法。
  另一方面、从辅助文字中取得String时用charAt()时、辅助文字是2个字节表现的,取不出来,得不到想要的char。这种情况下要使用codePointAt()方法。
  另外、使用codePointCount()方法时、结合字符(浊音或半浊音等文字、与前面的文字组合成的文字)会当做1个字符来处理。这里不做详细说明、记住这些、应用程序中会规定容许文字的范围,请适当的实现。
  辅助文字或者文字代码详情请参照「プログラマのための文字コード技術入門」(矢野啓介 著/技術評論社 刊)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
    //违反例
    public class BadSample {
        public static void main(String[] args) {
            String text = " ![2.13.12](/uploads/37c517f131a599df80f031317774becb/2.13.12.jpg) ";//U+200DB
            System.out.println(text.length());//显示2
            //显示?
            System.out.println(Character.toChars(text.charAt(0)));
        }

    //修正例
    public class GoodSample {
        public static void main(String[] args) {
            String text = " ![2.13.12](/uploads/37c517f131a599df80f031317774becb/2.13.12.jpg) ";// U+200DB
            // 显示1
            System.out.println(text.codePointCount(0, text.length()));
            //显示?
            System.out.println(Character.toChars(text.codePointAt(0)));
        }
    }

2.13.13 BigDecimal的格式化要使用はDecimalFormat或BigDecimal#toPlainString

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  Java5以后BigDecimal的toString()性能发生了变化、用原来的方法即使用toString方法也不是数字格式表示。这一点从JDK1.4迁移时有可能会存在bug。
  BigDecimal值格式的情况下两个选择。DecimalFormat、BigDecimal新追加了BigDecimal#toPlainString()。请区分以下面的方式。

  • 整数、固定小数値、百分数、金额等详细格式化的场合⇒DecimalFormat
  • 用简单的数值表示形式⇒toPlainString
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    违反例
    public class BadSample {
        public static void main(String[] args) {
            // 想显示1230000000但java5以后却不是
            BigDecimal decimal = new BigDecimal("1.23E+9");
            System.out.println(decimal.toString());// 违反

            // 想显示12500000但java5以后却不是
            BigDecimal decimal2 = new BigDecimal(new Double(12500000).toString());
            System.out.println(decimal2);// 违反
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) throws ParseException {
            DecimalFormat formatter = new DecimalFormat();
            formatter.applyPattern("############.######");

            // 显示的就是期待的1230000000
            BigDecimal decimal = new BigDecimal("1.23E+9");
            String formatResult = formatter.format(decimal);// 修正
            System.out.println("DecimalFormat的格式化结果:"+formatResult);
            String plainStringResult = decimal.toPlainString();// 修正
            System.out.println("BigDecimal#toPlainString的格式化结果:"+ plainStringResult);

            // 显示的就是期待的12500000
            BigDecimal decimal2 = new BigDecimal(new Double(12500000).toString());
            String formatResult2 = formatter.format(decimal2);// 修正
            System.out.println("DecimalFormat的格式化结果:"+formatResult2);
            String plainStringResult2 = decimal2.toPlainString();// 修正
            System.out.println("BigDecimal#toPlainString的格式化结果:"+ plainStringResult2);
        }
    }

2.13.14 多线程程序要使用Java5标准的Executor框架

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  Java5以后可以使用java.util.concurrent包和Executor框架、它可以更容易地控制多线程处理。java.util.concurrent.ExecutorService或java.util.concurrent.Executors中、提供了各种各样的功能。
  Executor框架可以自行处理线程再利用、线程数的管理、处理状态的监视、处理中断等等、用户可以专注于在线程执行的处理。
  线程再利用或线程管理等、比较好的运用是非常困难的。执行线程处理的场合、如果需要的功能java.util.concurrent包里没有提供的话、首先确认一下Executor框架是否能够实现。详细的技巧请参照「3.7 多线程」

1
2
shutdown()和shutdownNow()的区别:为了关闭在 ExecutorService 中的线程,你需要调用 shutdown() 方法。ExecutorService 并不会马上关闭,而是不再接收新的任务,一但所有的线程结束执行当前任务,ExecutorServie 才会真的关闭。所有在调用 shutdown() 方法之前提交到 ExecutorService 的任务都会执行。  
如果你希望立即关闭 ExecutorService,你可以调用 shutdownNow() 方法。这個方法会尝试马上关闭所有正在执行的任务,并且跳过所有已经提交但是还没有运行的任务。但是对于正在执行的任务,是否能够成功关闭它是无法保证的,有可能他们真的被关闭掉了,也有可能它会一直执行到任务结束。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
    sample
    public class ExecutorSample {
        public static void name(String[] args) {
            System.out.println("开始使用[" + Thread.currentThread().getName() + "] Executor的sample。");
            RunnableTask task1 = new RunnableTask();
            CallableTask task2 = new CallableTask();

            ExecutorService executor = Executors.newFixedThreadPool(2);

            // 执行线程的执行单位的Runnable
            executor.execute(task1);

            // 执行java5加入的Callable任务
            // 返回值是非同步处理的计算结果对象Future
            Future<String> future = executor.submit(task2);

            // 等待,知道从Future对象中取到结果为止
            String futureResult = null;
            while (futureResult == null) {
                try {
                    futureResult = future.get();
                } catch (InterruptedException e) {
                    System.out.println("线程被中断");
                } catch (ExecutionException e) {
                    System.out.println("执行时发生异常");
                }
            }
            System.out.println("Future的结果:" + futureResult);

            System.out.println("停止[" + Thread.currentThread().getName() + "] Executor。");

            // Executor的停止
            executor.shutdown();
            try {
                boolean awaitTermination = executor.awaitTermination(10, TimeUnit.SECONDS);
                if (awaitTermination == false) {
                    executor.shutdownNow();
                }
            } catch (InterruptedException e) {
                System.out.println("线程被中断");
            }

            System.out.println("停止[" + Thread.currentThread().getName() + "] Executor。");
        }
    }

    public class RunnableTask implements Runnable {

        @Override
        public void run() {
            try {
                System.out.println("开始[" + Thread.currentThread().getName() + "] GoodTask1的处理。");
                TimeUnit.SECONDS.sleep(2);
                System.out.println("结束[" + Thread.currentThread().getName() + "] GoodTask1的处理。");
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

    }

    public class CallableTask implements Callable<String> {
        @Override
        public String call() {
            String ret = "FAIL";
            try {
                System.out.println("开始[" + Thread.currentThread().getName() + "] GoodTask2的处理。");
                TimeUnit.SECONDS.sleep(3);
                System.out.println("结束[" + Thread.currentThread().getName() + "] GoodTask2的处理。");
                ret = "SUCCESS";
            } catch (Exception e) {
                e.printStackTrace();
            }
            return ret;
        }
    }

2.13.15 时间的单位转换要使用TimeUnit

⭐ ⭐

【说明・动机】
  Java5引入了TimeUnit(java.util.concurrent.TimeUnit)类、提供了很多很多方便的时间单位转换的方法。
  时间单位的转化适当的值进行乘算或除算是必要的,例如,把秒换算成毫秒就有弄错的可能。如果使用TimeUnit的toXXX方法(XXX是变换后的时间单位)的话、就能很容易理解的进行时间单位的变换。另外、TimeUnit类的各个常量里、定义了sleep方法、调用Thread.sleep()时可以指定时间单位。这些请活用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    违反例
    public class BadSample {
        public static void name(String[] args) throws Exception {
            // 转化成毫秒
            long millis = 24 * 60 * 60 * 1000;
            System.out.println("24时间は" + millis + "msです");

            long start = System.currentTimeMillis();
            // 停止10秒。Sleep的参数转换成毫秒
            Thread.sleep(10 * 1000);
            long end = System.currentTimeMillis();
            long timeSec = (end - start) / 1000;

            System.out.println("处理时间" + timeSec + "秒");
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) throws Exception {
            // 60秒转换为微秒
            long millis = TimeUnit.SECONDS.toMillis(60);
            System.out.println("60秒是" + millis + "ms");

            long start = System.currentTimeMillis();
            // 10秒停止
            TimeUnit.SECONDS.sleep(10);
            long end = System.currentTimeMillis();
            long timeSec = TimeUnit.MILLISECONDS.toSeconds(end - start);

            System.out.println("处理时间" + timeSec + "秒");
        }
    }

2.13.16 是否导入static(类、方法、变量)要统一

⭐ ⭐ ⭐

【说明・动机】
  Java5以后可以static导入,虽然调用static方法很简单,但有以下缺点。

  • 一眼看上去不知道调用了哪个方法
  • 和原来的调用方法(类名・static变量・方法)混在一起会使可读性下降。

因此、在项目开发中按照下面任意一条规约来规定。

  • 不要使用static导入
  • 只能在测试类里使用(Junit的Assert类的每个方法)
  • 只有常量可以使用
  • 整体都用static导入
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
    违反例
    public class StaticClass {
        public static final int STATIC_NO = 1;
        public static final String STATIC_TEXT = "text";

        public static void staticMethod(int n, String t) {
        }
    }

    import static sample.StaticClass.STATIC_NO;
    import static sample.StaticClass.staticMethod;

    public class BadSample {
        public void sampleMethod() {
            int n = STATIC_NO;
            // 只有这部分没用static导入
            String s = StaticClass.STATIC_TEXT;
            staticMethod(n, s);
        }
    }
    修正例
    public class StaticClass {
        public static final int STATIC_NO = 1;
        public static final String STATIC_TEXT = "text";

        public static void staticMethod(int n, String t) {
        }
    }

    import static sample.StaticClass.STATIC_NO;
    import static sample.StaticClass.STATIC_TEXT;
    import static sample.StaticClass.staticMethod;

    public class GoodSample {
        public void sampleMethod() {
            int n = STATIC_NO;
            // 修正
            String s = STATIC_TEXT;
            staticMethod(n, s);
        }
    }

3.程序设计规约/技术篇

3.1 继承

3.1.1 继承前请检讨继承的必要性

⭐ ⭐ ⭐

【说明・动机】
  多个类有相同处理的时候可以轻易的使用继承吗?
  父类不是放置公用方法和公有字段的场所。有相同功能的场合,首先要检讨是否有继承的必要性。一般的、伴随着继承关系的复杂化、在哪儿做了什么处理也就不知道了、这样很容易产生bug。如果使用类实例化话、就可以避免继承结构复杂。
  另外、在Java里,使用extends只能继承1个父类。因此、只有本来就有继承关系的才使用extends实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    违反例
    public abstract class AbstractProducerLogicBase {
        // ...
    }

    public abstract class AbstractProducerLogicExtBase extends
            AbstractProducerLogicBase {
        // ...
    }

    // 违反、容易将继承关系变得复杂
    public class BadProducerLogicImpl extends AbstractProducerLogicExtBase {
        // ...
    }

    修正例
    public class GoodProducerLogicImpl extends AbstractProducerLogicBase {
        protected AbstractProducerLogicExtBase delegate;

        public GoodProducerLogicImpl(AbstractProducerLogicExtBase delegate) {
            this.delegate = delegate;
        }
        @Override
        public Status execute(String[] args) {
            // 修正、正确的使用了继承
            Status status = this.delegate.execute(args);
        }
    }

3.1.2 在子类中不要声明与父类的成员变量相同的变量名

⭐ ⭐ ⭐ ⭐

教育資料:[[オブジェクト指向について]]

【说明・动机】
  已经在父类中声明的成员变量,在子类中也声明相同的变量,父类的变量会被子类的变量覆盖。变量被覆盖的话,会引起「本打算访问相同的变量,但实际上访问的是不同的变量」的情况,这样容易产生错误。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
    违反例
    public class BadSample {
        protected int number = 4;

        public void print() {
            System.out.println("BadSample の number変数の値は、" + number);
        }
    }

    public class ChildBadSample extends BadSample {
        protected int number = 5; // 违反

        @Override
        public void print() {
            super.print();
            System.out.println("ChildBadSample の number変数の値は、" + number);
        }

        public static void main(String[] args) {
            new ChildBadSample().print();
        }
    }

【解说】

上面的程序执行结果如下。
BadSampleのnumber 变量值为4
ChildBadSampleのnumber 变量值为5
本来打算访问同一个变量、但是实际上访问的是父类和子类不同的变量(BadSample的number变量的值是4、ChildBadSample 的number变量的值是5)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
    修正例
    public class GoodSample {
        protected int number = 4;

        public void print() {
            System.out.println("number is " + number);
        }
    }
    public class ChildGoodSample extends GoodSample {
        protected int childNumber = 5; // 修正
        @Override
        public void print() {
            super.print();
            System.out.println("number is " + number);
            System.out.println("childNumber is " + childNumber);
        }
        public static void main(String[] args) {
            new ChildGoodSample().print();
        }
    }

【解说】

上面的程序执行的结果如下。
GoodSampleのnumber 变量值为4
ChildGoodSampleのnumber 变量值为4
ChildGoodSampleのchildNumber 变量值为5
父类和子类赋予不同的变量名的场合、访问的是哪一个变量能够很清楚的明白。

3.1.3 用IDE自动生成equals()方法和hashCode()方法

⭐ ⭐ ⭐ ⭐

【说明・动机】
  在需要重写equals()方法的场合,也要重写hashCode()方法。equals()相等时2个对象的hashCode()也必须相等、反之也成立。为了防止遗漏,尽量不要手动操作。活用Eclipse等的IDE来自动生成equals()和hashCode()方法。在Eclipse里、生成equals()的同时hashCode()也生成了、遗漏的可能性就变少了。重写equals()而没有重写hashCode()的场合、就不能正确使用基于hash(哈希)的所有集合类的功能,包括HashMap、HashSet、HashTable。
  无论如何也要手动操作的场合、equals()和hashCode()中任意一个重写的、另一个也必须重写。

【解说】

下面使用IDNumber类的例子,来实现equals()和hashCode()方法。以下的equals()方法是id的值相同就返回true。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
    sample
    public class IDNumber {
        final int num;
        public IDNumber(int num) {
            this.num = num;
        }

        @Override
        public boolean equals(Object object) {
            boolean isEqual = false;
            if (object == this) {
                isEqual = true;
            } else if (object instanceof IDNumber) {
                IDNumber idNum = (IDNumber) object;
                if (idNum.num == this.num) {
                    isEqual = true;
                }
            }
            return isEqual;
        }
    }

【解说】

上面IDNumber类的例子还没有实现hashCode()方法。这种情况下,尝试使用一下HashMap。

1
2
    Map<IDNumber,String> map = new HashMap<IDNumber,String>();
    map.put(new IDNumber(123),"Hanako");

【解说】

乍看之下,对于这个HashMap,我们使用map.get(new IDNumber(123) ),应该能得到"Hanako",但是返回值却是null。

在hashCode()方法还是默认值的情况下,即使id的数值,新的实例生成的时候,新的实例会被给予一个新的哈希值。上面的例子中,使用HashMap的get()方法时,因为又新生成了一个实例,所以即使id的值相同,但是给予了另外的哈希值,因此认为他们是不同的IDNumber实例。 为了避免上面的情况,就必须要重写hashCode()方法。
另外,单独重写hashCode()方法时,要慎重的操作。保证同一个实例返回同样的哈希值。 下面是这个例子的基础上,使用Eclipse自动生成的hashCode()。

1
2
3
4
5
6
7
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + num;
        return result;
    }

这里虽然介绍了实现hashCode()的方法,但是其他也有很多方法。这样重写了hashCode()方法,使得在使用基于哈希值的集合类时,能得到正确的结果。

3.1.4 尽可能的实现toString()方法

⭐ ⭐ ⭐

【说明・动机】
  为了容易Debug、尽可能的实现toString()方法。默认的Object#toString()方法返回的仅仅是hashCode()方法返回的值。为了输出一个比hash code 容易理解的值一定要重写这个方法。
  另外、在JUnit中、对象之间比较时如果assert失败的话、在toString()方法里可以取得对象比较抛出的异常信息,失败的原因也就能够很容易的理解。从测试的角度来看的话、实现toString()方法是最理想的。
  Eclipse等的IDE中提供了自动生成toString()的功能,请积极地正确地使用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    违反例
    public class BadSample {
        private int sampleValue;

        public BadSample(int value) {
            this.sampleValue = value;
        }

        public static void main(String[] args) {
            System.out.println(new BadSample(100));
        }
    }

【解说】

运行上面的例子,可能会得到下面这样的结果 BadSample@ca0b6

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    修正例
    public class GoodSample {
        private int sampleValue;
        private String hoge;

        public GoodSample(int value, String hoge) {
            this.sampleValue = value;
            this.hoge = hoge;
        }

        // 用eclipse自动生成的toString()
        @Override
        public String toString() {
            return "GoodSample [hoge=" + hoge + ",sampleValue=" 
                                     + sampleValue + "]";
        }

        public static void main(String[] args) {
            // 修正。实例中也可以很轻松的使用、
            // debug也很方便。
            System.out.println(new GoodSample(100, "hello"));
        }
    }

【解说】
   运行上面重写toString()的例子,结果如下。对象内部状态正确的表示出来,也非常容易和标准进行比较。      GoodSample [hoge=hello,sampleValue=100]

3.1.5 不要使用Cloneable#clone、使用自己定义的拷贝方法

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  对象的拷贝的场合可以使用Cloneable#clone方法吗?   使用Cloneable#clone方法是单纯的拷贝,关于对象拷贝的只是地址并没有拷贝内容(浅拷贝),如果地址是共有的、就会产生意外的错误。如果想拷贝对象的内容(深拷贝),不要使用Cloneable#clone方法,请自己准备拷贝方法、正确的拷贝内容。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
    违反例
    public class BadSample implements Cloneable {
        private String name;

        private List<String> addresses;

        public BadSample(String name, List<String> addresses) {
            this.name = name;
            this.addresses = addresses;
        }

        public String getName() {
            return name;
        }

        public List<String> getAddresses() {
            return addresses;
        }

        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }

        public static void main(String[] args) 
                                  throws CloneNotSupportedException {
            BadSample sample = new BadSample("Yone", Arrays.asList("Tokyo",
                    "Minato-ku", "Hoge"));
            BadSample clone = (BadSample) sample.clone();// 违反

            System.out.println("clone前の住所:" + clone.getAddresses());

            //  因为元对象和复制对象共用List内对象的地址、
            // 所以元对象变更后,复制对象也变更了。
            sample.getAddresses().set(0, "Toyama");
            System.out.println("オリジナルに修正を入れると、
                                 クローンの中身も変わってしまっている");
            System.out.println("clone後の住所(オリジナル):"
                                     + sample.getAddresses());
            System.out.println("clone後の住所(コピー):" + clone.getAddresses());
        }
    }

    修正例
    public class GoodSample {
        private String name;

        private List<String> addresses;

        public GoodSample(String name, List<String> addresses) {
            this.name = name;
            this.addresses = addresses;
        }

        public String getName() {
            return name;
        }

        public List<String> getAddresses() {
            return addresses;
        }

        public static GoodSample copy(GoodSample original) {
            return new GoodSample(original.name, new ArrayList<String>(original.addresses));
        }

        public static void main(String[] args)
                     throws CloneNotSupportedException {
            GoodSample sample = new GoodSample("Yone", Arrays.asList("Tokyo",
                    "Minato-ku", "Hoge"));
            GoodSample copy = GoodSample.copy(sample);// 修正

            System.out.println("clone前の住所:" + copy.getAddresses());

            sample.getAddresses().set(0, "Toyama");
            System.out.println("オリジナルに修正を入れても、
                                         コピーの中身は変わっていない。");
            System.out.println("clone後の住所(オリジナル):" 
                                    + sample.getAddresses());
            System.out.println("clone後の住所(コピー):" + copy.getAddresses());
        }
    }

【解说】

违反例的运行结果如下。
  clone前の住所:[Tokyo, Minato-ku, Hoge]
  元对象修改后,复制对象也变更了。
  clone後の住所(オリジナル):[Toyama, Minato-ku, Hoge]
  clone後の住所(コピー):[Toyama, Minato-ku, Hoge]
修正例的运行结果如下。
  clone前の住所:[Tokyo, Minato-ku, Hoge]
  元对象修改后,复制对象没有变更。
  clone後の住所(オリジナル):[Toyama, Minato-ku, Hoge]
  clone後の住所(コピー):[Tokyo, Minato-ku, Hoge]

3.2 实例化

3.2.1 注意对象(实例)之间的比较方法的不同

⭐ ⭐ ⭐ ⭐

【说明・动机】
  对象(实例)之间的比较有下面两个方法。
 ・使用「
 ・使用equals方法
  使用「
」的场合、比较的是「是否是同一个实例」。
  另一方面、使用equals方法比较的场合、是对象之间比较。equals()方法默认实现普通的「」比较、大多各自的对象等值比较时合适的实现就可以了。例如、String类的equals()方法实现了「String内包含的字符串是否相等」
  实现的时候、要明确是「想比较是否是相同的实例」还是「想比较是否是具有相同值的实例」,「
」和equals方法请正确的使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    sample
    public class SampleBean {
        private int code;
        public SampleBean(int code) {
            this.code = code;
        }
        public int getCode() {
            return code;
        }
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof SampleBean) {
                SampleBean bean = (SampleBean) obj;
                // code相同就视为相等
                if (this.code == bean.getCode()) {
                    return true;
                }
            }
            return false;
        }
    }

    public class Sample {
        public static void main(String[] args) {
            SampleBean bean1 = new SampleBean(100);
            SampleBean bean2 = new SampleBean(100);

            // 因为‘==’是比较实例是同一个所以返回false
            System.out.println(bean1 == bean2);
            // 因为equals是比较内部值是否相等所以返回true
            System.out.println(bean1.equals(bean2));
        }
    }

3.2.2 比较Class对象的场合、不要使用字符串比较

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  比较Class对象的场合、使用类名的字符串比较的时候、如果类名的字符串弄错了或者类名修改的话等很容易产生错误、不是适当的方法。还有比较的时候如果容许子类的场合,实现起来也比较困难。
  类比较的场合、请使用下面的方法。
  ・完全一致的场合 ⇒ 使用equals()或==
  ・子类也容许的场合 ⇒ 使用isAssignableFrom()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
违反例
    public class BadSample {
        /**
         * 传入的参数是否是ArrayList类
         */
        public boolean isArrayList(Class<?> list) {
            String listName = list.getName();
            // 违反 字符串可能会出错
            boolean result = listName.equals("java.util.ArrayList");
            return result;
        }

        /**
         * 传入的参数是否是List类的子类
         */
        public boolean isList(Class<?> list) {
            String listName = list.getName();
            // 违反 ArrayList以外都返回false
            boolean result = listName.equals("java.util.ArrayList");
            return result;
        }
    }

    修正例
    public class GoodSample {
        /**
         * 传入的参数是否是ArrayList类
         */
        public boolean isArrayList(Class<?> list) {
            String listName = list.getName();
            // 修正 和类比较
            boolean result = (list == ArrayList.class);
            return result;
        }
        /**
         * 传入的参数是否是List类的子类
         */
        public boolean isList(Class<?> list) {
            // 修正
            boolean result = List.class.isAssignableFrom(listCandidate);
            return result;
        }
    }

3.2.3 角色转换处理要用instanceof方法进行判断

⭐ ⭐ ⭐ ⭐

【说明・动机】
  角色转换处理只能在有继承关系的类之间实行。没有继承关系的类之间进行角色转换处理时,必须使用instanceof来check,泛型里对象的参数的类型也同样适用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
    违反例
    public class BadSample {
        public void executeBadSampleMethod(Object o) {
            /**
             * 违反 没有用instanceo或者Class#isInstance方法来check
             */
            Employee employee = (Employee) o;
            System.out.println(employee.getName());
        }
        ...
    }

    修正例
    public class GoodSample {
        public void executeGoodSampleMethod(Object o) {
            // 修正。 实例的类型先做check
            if (o instanceof Employee) {
                Employee employee = (Employee) o;
                System.out.println(employee.getName());
            } else {
                throw new IllegalArgumentException(
                       "参数の型はEmployeeかその子类である必要があります。");
            }
        }
    }

    /**
     * 「围绕instanceof的角色转换处理」的例子.<br/>
     * <br/>
     * 根据泛型,传入适当的参数。
     */
    public class GoodSample2<T extends Employee> {
        public void executeGoodSampleMethod(T o) {
                Employee employee = (Employee) o;
                System.out.println(employee.getName());
        }
        public static void main(String[] args){
            GoodSample2<Engineer> goodSample = new GoodSample2<Engineer>();
            Engineer e= new Engineer("Shinpei Ohtani",198111);
            goodSample.executeGoodSampleMethod(e);
            // 编译错误
            // Object o = "ジェネリクスによる対処";
            // goodSample.executeGoodSampleMethod(o);
        }
    }

3.3 控制结构

3.3.1 控制语句(if,else,while,for,do-while)的{}不要省略

⭐ ⭐ ⭐ ⭐

【说明・动机】
  控制语句的{}省略过没有?   即时只有1行处理也不要省略{}。如果省略{}的话,控制语句在什么地方结束会变得难以理解,有新功能追加的时候如果忘了添加{}有可能会产生bug。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    违反例
    public class BadSample {
        public void badSampleMethod(boolean flag){
            if(flag)//违反
                System.out.println("ifの中");
                System.out.println("ifの外");
            }
    }

    修正例
    public class GoodSample {
        public void goodSampleMethod(boolean flag){
            if(flag){//修正
                System.out.println("ifの中");
                System.out.println("ifの中");
            }
        }
    }

3.3.2 不要使用空的{}块

⭐ ⭐ ⭐ ⭐

【说明・动机】
  在if语句和for语句里避免适用空的{}块。
  有空的{}块存在的场合、是「因为不需要的代码而使用空的{}」还是「因为开发途中还没有书写的代码而使用空的{}」就很难理解。通过下面的做法可以变得容易理解。

  • 在开发途中使用空{}的场合=>在Eclipse里添加「TODO」注释
  • 不需要的代码而使用空的{}=>添加不要的原因注释
1
2
3
4
5
6
7
8
    违反例
    for (int i = 0; i < 10; i++) {
    }

    修正例
    for (int i = 0; i < 10; i++) {
        //TODO处理を実装する。
    }

3.3.3 要灵活运用for和while语句

⭐ ⭐ ⭐

【说明・动机】
  有没有意识到for和while语句不同?
  根据程序员的喜好,从数组或集合类等取出多个要素,处理每个要素的时候,一般的都会使用for语句。尤其Java5以后可以使用扩张型for语句、首先使用的就是for语句(详细请参照2-13-1尽量使用扩张型for语句」)。
  下面的情况通常使用for语句。通常for语句的括弧中要记述下面3个条件。

  • 计数器的声明
  • 循环条件
  • 下一个计数器的表达式

这些条件省略的时候或者没有必要的场合可以使用while语句。根据具体情况,从扩张型for语句、for语句、while语句中选择并使用适当的循环结构,可以不断增加代码的可读性。

【解说】

遍历元素的集合,处理每个元素的场合、原则上不使用while语句。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    违反例
    public class BadSample{
        void method(String[] lines){
            //违反
            //处理数组的每个要素应当使用for语句
            int i=0;
            while (i<lines.length) {
                //处理
                i++;
            }
        }
    }

【解说】

遍历元素的集合,处理每个元素的场合、原则上使用for语句。

1
2
3
4
5
6
7
8
    修正例
    public class GoodSample{
        void method(String[] lines){
            for (String line : lines) {
                //处理
            }
        }
    }

【解说】

不能使用扩张型for语句的场合使用一般的for语句。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    sample
    public class ForSample{
        void method(String[] lines){
            StringBuilder sb =new StringBuilder();
            for (int i = 0; i < lines.length; i++) {
                if(i>0){
                    sb.append(", ");
                }
                sb.append(lines[i]);
            }
        }
    }

【解说】

像下面这样什么都不需要的场合使用while循环。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    sample
    public class WhileSample{
        void method(BufferedReader reader) throws
        IOException{
            String line;
            while ((line=reader.readLine())!=null) {
                System.out.println(line);
            }
        }
    }

3.3.4 使用for语句不要改变在迭代过程中循环变量的值

⭐ ⭐ ⭐

【说明・动机】
  for语句的计数器可以在循环内修改值吗?
  for语句的计数器不应该在非条件式进行操作。控制结构很难理解且容易出错,而且错误的场合要找出错误就会变得很难。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    违反例
    public class BadSample{
        int badSampleMethod(){
            int result=0;
            for (int i = 0; i < 100; i++) {
                i+=1;//违反
                result+=i;
            }
            return result;
        }
    }

    修正例
    public class GoodSample{
        int goodSampleMethod(){
            int result=0;
            //修正
            for (int i = 0; i < 50; i++) {
                result+=(2*i-1);
            }
            return result;
        }
    }

3.3.5 for语句的计数器要从0开始

⭐ ⭐ ⭐ ⭐

【说明・动机】
  for语句内使用的计数器的初期値在没有特殊理由的话要从0开始。违反例里、for语句里使用计数器访问数组的要素的场合计数器使用0以外的值开始了,代码的可读性降低了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
    违反例
    public class BadSample{
        public static void main(String[] args){
            int[] testArray=new int[10];
            for (int i = 1; i < 10; i++) {//违反
                testArray[i-1]=i;
            }
        }
    }

    修正例
    public class GoodSample{
        public static void main(String[] args){
            int[] testArray=new int[10];
            for (int i = 0; i < 10; i++) {//修正
                testArray[i]=i+1;
            }
        }
    }

3.3.6 不要随意使用break或continue

⭐ ⭐ ⭐

【说明・动机】
  循环内的处理控制随意使用break或continue的话,会使控制结构复杂化、代码的可读性也降低了。像下面做法比较好。

  • 循环的主要的继续条件在while或for语句中记述
  • break或continue仅仅控制异常的处理流
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
    违反例
    public class BadSample{
        public static void main(String[] args){
            BadSample sample=new BadSample();
            sample.useContinue();
        }
        private void useContinue(){
            for (int i = 0; i < 10; i++) {
                if((i+1)%2==0){
                    System.out.println(
                            (i+1)+"は偶数です。");
                    continue;//违反
                }
                System.out.println(
                        (i+1)+"は奇数です。");
            }
        }
    }

    修正例
    public class GoodSample{
        public static void main(String[] args){
            GoodSample sample=new GoodSample();
            sample.unUseContinue();
        }
        private void unUseContinue(){
            for (int i = 0; i < 10; i++) {
                if((i+1)%2==0){//修正
                    System.out.println(
                            (i+1)+"は偶数です。");
                }else{
                    System.out.println(
                            (i+1)+"は奇数です。");
                }
            }
        }
    }

3.3.7 简单的数组拷贝时使用既存的clone()等既存方法

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  简单的数组拷贝时,要特意抒写循环语句等来处理吗?
  根据场所的不同使用Java的便利的方法。

  • 重新拷贝作成的场合=>使用原来的数组对象的clone()方法
  • 想要复制到不同大小的数组的场合=>使用Arrays.copyOf()方法或(Java6以上)System.arraycopy()方法
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    违反例
    public class BadSample{
        String[] copy(String[] orgArray){
            if(orgArray==null){
                return null;
            }
            int length=orgArray.length;
            String[] copyArray=new String[length];
            for (int i = 0; i < length; i++) {
                copyArray[i]=orgArray[i];//违反
            }
            return copyArray;
        }
    }

    修正例
    public class GoodSample{
        String[] copy(String[] orgArray){
            if(orgArray==null){
                return null;
            }
            return orgArray.clone();//修正
        }
    }

    public class GoodSample2{
        String[] copy(String[] orgArray){
            if(orgArray==null){
                return null;
            }
            return Arrays.copyOf(
                    orgArray, orgArray.length)//修正
        }
    }

3.3.8 循环处理中要最小限度的生成对象

⭐ ⭐ ⭐

【说明・动机】
  因为对象的生成要花费时间,所以在重复次数多的循环内部生成对象时会让性能大大降低。对性能的要求是可预知的情况下、循环处理中要最小限度的生成对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    违反例
    public class BadSample{
        public void badSampleMethod(String[] array){
            for (int i = 0; i < array.length; i++) {
                StringBuilder sb=new StringBuilder();
                //违反
                sb.append("log: ");
                sb.append(array[i]);
                System.out.println(sb.toString());
            }
        }
    }

    修正例
    public class GoodSample{
        public void goodSampleMethod(String[] array){
            StringBuilder sb=new StringBuilder();
            //修正
            for (int i = 0; i < array.length; i++) {
                sb.append("log: ");
                sb.append(array[i]);
                System.out.println(sb.toString());
                sb.setLength(0);//回到初始化状态
            }
        }
    }

3.3.9 尽量避免在循环里使用if/else语句和switch语句,使用面向对象

⭐ ⭐ ⭐

【说明・动机】
  方法中可以包含很多分支吗?
  Switch语句或if/else语句重复多次会使代码可读性下降,性能也会受到影响。 这是设计问题。使用多态性或统一的接口设计,减少使用分支。

【解说】

下面的例子没有使用多态性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    违反例
    public class BadSample{
        public static void main(String[] args){
            Object[] animals=new Object[2];
            animals[0]=new BadDog();
            animals[1]=new BadCat();
            int size=animals.length;
            for (int i = 0; i < size; i++) {
                //不得不使用if语句来区分
                //需要check的类型增加,if语句也会增加
                if(animals[i] instanceof BadDog){
                    ((BadDog)animals[i]).bark();
                }else if(animals[i] instanceof BadCat){
                    ((BadCat)animals[i]).meow();
                }
            }
        }
    }

    public class BadDog{
        //BadDog类,独自的叫的方法
        public void bark(){
            System.out.println("わんわん");
        }
    }

    public class BadCat{
        //BadCat类,独自的叫的方法
        public void meow(){
            System.out.println("にゃー");
        }
    }

【解说】

上面的例子使用多态性来修正。代码变得简单了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    修正例
    public class GoodSample{
        public static void main(String[] args){
            Animal[] animals=new Animal[2];
            animals[0]=new Dog();
            animals[1]=new Cat();
            int size=animals.length;
            for (int i = 0; i < size; i++) {
                //不需要判断实例的区别。
                animals[i].cry();
            }
        }
    }

    public interface Animal{
        //共同的叫的方法
        void cry();
    }

    public class Dog implements Animal{
        //实现Animal中的方法
        public void cry(){
            System.out.println("わんわん");
        }
    }

    public class Cat implements Animal{
        //实现Animal中的方法
        public void meow(){
            System.out.println("にゃー");
        }
    }

3.3.10 循环处理的内部、不要使用try/catch块

⭐ ⭐ ⭐

【说明・动机】
  循环中尽量不要放置try/catch块。性能变得低下,循环内的处理难看明白。没有特殊理由的场合在循环外面使用try/catch块。
  作为例外情况、即时在途中发生Exception、做了适当的异常处理之后也想让循环处理到最后的情况,就不受这种限制(可以在循环里使用try/catch块)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
    违反例
    public class BadSample {
        public void method(String[] str){
            int size=str.length;
            //违反,循环内部有try/catch块
            for (int i = 0; i < size; i++) {
                try {
                    int num=Integer.parseInt(str[i]);
                    someOtherMethod(num);
                } catch (NumberFormatException e) {
                    //不是数值的文字列情况发生
                    throw new IllegalArgumentExctption("不正な値が見つかりました",e);
                }
            }
        }
        private void someOtherMethod(int i){
            //处理
        }
    }

    修正例
    public class GoodSample {
        public void method(String[] str){
            int size=str.length;
            //修正.循环外部有try/catch块
            try {
                for (int i = 0; i < size; i++) {
                    int num=Integer.parseInt(str[i]);
                    someOtherMethod(num);
                }
            } catch (NumberFormatException e) {
                //不是数值的文字列情况发生
                throw new IllegalArgumentExctption("不正な値が見つかりました",e);
            }
        }
        private void someOtherMethod(int i){
            //处理
        }
    }

3.3.11 在if/while的条件中不要使用「=」

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  if或while语句的条件内使用代入操作符(=)情况是除了代入方法的返回值的情况,其它情况几乎不考虑。
  大多数的情况是下面的某一种。

  • 「=」和「==」是不一样的
  • 在条件文内部代入了在条件文外部执行的语句

另外、「=」如果代入的变量是boolean型的话是可以通过编译的,其它以外的类型编译时会check出错误。下面的违反例里、不管terminate的值都用terminate是true代入的话,条件的结果一直都会是true。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    违反例
    public class BadSample {
        public String execute(boolean terminate) {
            String value = "none";
            if (terminate = true) {// 违反。有可能产生bug
                value = "terminate";
            }
            return value;
        }

        public static void main(String[] args) {
            String s = new BadSample().execute(true);
            System.out.println(s);

            String s2 = new BadSample().execute(false);
            System.out.println(s2);
        }
    }
    修正例
    public class GoodSample {
        public String execute(boolean terminate) {
            String value = "none";
            if (terminate == true) {// 修正
                value = "terminate";
            }
            return value;
        }

        public static void main(String[] args) {
            String s = new GoodSample().execute(true);
            System.out.println(s);

            String s2 = new GoodSample().execute(false);
            System.out.println(s2);
        }
    }

3.4 集合

3.4.1 Java2以后使用集合类

⭐ ⭐ ⭐ ⭐

【说明・动机】
  Vector类、Hashtable类、Enumeration类可以使用吗?
  如果没有使用这些的特殊理由的话,为了统一使用接口的目的、请使用List(ArrayList类)、Map(HashMap类)、Iterator代替。使用List等接口时利用Java2整理的容易理解的方法,根据接口的特性调用元是不可修改的,实现类是可以修改的。
  另外、自从Java5引入了和泛型一起使用、根据类型可以写出安全的容易维护的代码。(详细请参照「2.-13-5 List或Map要使用泛型」)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
    违反例
    import java.util.Enumeration;
    import java.util.Vector;
    public class BadSample {
        public static void main(String[] args) {
            Vector<String> sampleVector = new Vector<String>();// 违反
            sampleVector.addElement("1");
            sampleVector.addElement("2");
            sampleVector.addElement("3");

            Enumeration<String> sampleEnumeration = sampleVector.elements();
            while (sampleEnumeration.hasMoreElements()) {
                System.out.println(sampleEnumeration.nextElement());
            }
            sampleVector.removeElement("1");
            int length = sampleVector.size();
            for (int i = 0; i < length; i++) {
                System.out.println(sampleVector.elementAt(i));
            }
        }
    }

    修正例
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;

    public class GoodSample {
        public static void main(String[] args) {
            List<String> sampleList = new ArrayList<String>();// 修正
            sampleList.add("1");
            sampleList.add("2");
            sampleList.add("3");

            Iterator<String> sampleIterator = sampleList.iterator();
            while (sampleIterator.hasNext()) {
                System.out.println(sampleIterator.next());
            }
            sampleList.remove("1");
            int length = sampleList.size();
            for (int i = 0; i < length; i++) {
                System.out.println(sampleList.get(i));
            }
        }
    }

3.5 終了(结束)处理

3.5.1 使用API处理资源(消耗)时,在finally块中处理

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
  资源被打开了、这个资源关闭的方法有没有确实执行了?为了规避资源的泄露不许执行支援的关闭。   例如(输入/输出)流的场合、途中发生异常的场合,资源必须被关闭、close()方法在finally块中记述。(输入/输出)流以外的、从Java5追加了java.io.Closeable接口实现了类的对象的close()方法、资源需要关闭的场所不要忘记调用。

【解说】

在违反例里异常发生的时候(输入/输出)流没有关闭。因此、发生了资源的泄露、有可能会引起故障。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
    违反例
    public class BadSample {
        public void badSampleMethod(File file) {
            if (file == null) {
                throw new IllegalArgumentException("ファイルはnullであってはいけません。");
            }
            Reader reader = null;
            try {
                reader = new FileReader(file);
                BufferedReader bufReader = new BufferedReader(reader);
                // ……Other to do
                bufReader.close();
                //违反。资源有泄露的危险性。
            } catch (Exception ioe) {
                throw new RuntimeException(ioe);
            }
        }

        // ……Other to do
    }

【解说】

如果再finally块中执行close()方法的话,异常发生的时候可以确保关闭(输入/输出)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    修正例
    public class GoodSample {
        public void goodSampleMethod(File file) {
            if (file == null) {
                throw new IllegalArgumentException("ファイルはnullであってはいけません。");
            }
            Reader reader = null;
            try {
                reader = new FileReader(file);
                BufferedReader bufReader = new BufferedReader(reader);
                // ……Other to do
                bufReader.close();
            } catch (Exception ioe1) {
                throw new RuntimeException(ioe1);
            } finally {
                if (reader != null) {

                    try {
                        reader.close();
                        // 修正。关闭处理确实执行了。
                    } catch (Exception ioe2) {
                        System.out.println("クローズ時に例外発生:"+
                             ioe2.getMessage());
                    }
                }
            }
        }

        // ……Other to do
    }

3.6 异常处理

3.6.1 使用catch语句抓到异常而不处理的情况下,要给予合适的错误信息

⭐ ⭐ ⭐ ⭐

【说明・动机】
catch语句抓到异常而不处理的情况下,是否可以直接用throw语句抛出呢?   在发生异常的地方就直接用throw语句抛出的话,处理失败的时候要调查错误的原因就变得很困难。 抓到异常而不处理,就直接用throw抛出的情况下,为了知道发生什么样的错误要给予合适的错误信息

【解说】

例如,发生的错误可以在log中显示出来。在违反例中,configure()的内部的parseDate()发生错误时,错误log先在ParseException输出,即使这样在仅看了错误log时也不知道在什么语句发生了错误。如果仔细看错误log就会知道configure()处理发生了错误,但是这样需要花费间去调查。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
违反例
    public class BadSample {
        public void configure(Properties prop) throws ParseException {// 违反
            Date date = parseDate(prop.getProperty("processStartDate"));
            // ……other to do
            System.out.println("開始日付をセットしました:" + date.toString());
        }

        private Date parseDate(String dateString) throws ParseException {
            return new SimpleDateFormat("yyyy-MM-dd").parse(dateString);
        }

        public static void main(String[] args) {
            BadSample badSample = new BadSample();
            Properties prop = new Properties();
            prop.setProperty("processStartDate", "no_such_date");
            try {
                badSample.configure(prop);
            } catch (ParseException e) {
                e.printStackTrace();
                System.err.println(e);
            }
        }
    }

【解说】

修正例中,因为错误log事先独自在异常类ConfigurationException中作成,一眼就能看出在什么地方出现了配置错误。 另外,从ConfigurationException的错误信息中,在处理开始时间的语句中只管的看出出错的地方。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    修正例
    public class GoodSample {
        public void configure(Properties prop) throws ConfigurationException {// 修正
            Date date = null;
            try {
                date = parseDate(prop.getProperty("processStartDate"));
            } catch (Exception ex) {
                // 因为给异常给予了合适的错误信息并返回
                // 更容易知道发生了什么。
                throw new ConfigurationException("開始日付のパースで失敗しました:", ex);
            }
            // ……other to do
            System.out.println("開始日付をセットしました:" + date.toString());
        }

        private Date parseDate(String dateString) throws ParseException {
            return new SimpleDateFormat("yyyy-MM-dd").parse(dateString);
        }

        public static void main(String[] args) {
            GoodSample goodSample = new GoodSample();
            Properties prop = new Properties();
            prop.setProperty("processStartDate", "no_such_date");
            try {
                goodSample.configure(prop);
            } catch (ConfigurationException e) {
            //根据异常信息也能容易的知道发生的地方
                e.printStackTrace();
                System.err.println(e);
            }
        }
    }

3.6.2 再次抛出异常的时候、原本的异常也要传入抛出的异常。

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
异常在check后再次throw的情况,请将check后的异常传入throw的异常。check后的异常没有传入throw时,再次throw前的stacktrace会消失,错误发生时就不能追溯原因。
另外,Java类库的一部分异常,元异常不能传入constructor中,这种情况下,要使用initCause方法来配置元异常。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
    违反例
    public class BadSample {
        public void sampleMethod1(String s) {
            try {
                printMethod(s);
            } catch (Exception e) {
                // 违反 没有传入check后的异常
                throw new RuntimeException("Error");
            }
        }

        private void printMethod(String s) throws Exception {
            // 这样会发生NullPointerException错误
            String message = s.toLowerCase() + "message";
            System.out.println(message);
        }

        public static void main(String[] args) {
            BadSample sample = new BadSample();
            try {
                sample.sampleMethod1(null);
            } catch (Exception e) {
                // 因为没有传入原来的异常
                // 就不知道在printMethod方法发生异常
                e.printStackTrace();
            }
        }
    }

    修正例
    public class GoodSample {
        public void sampleMethod1(String s) {
            try {
                printMethod(s);
            } catch (Exception e) {
                // 修正 传入check后的异常
                throw new RuntimeException("Error", e);
            }
        }

        private void printMethod(String s) throws Exception {
            // 这样会发生NullPointerException错误
            String message = s.toLowerCase() + "message";
            System.out.println(message);
        }

        public static void main(String[] args) {
            GoodSample sample = new GoodSample();
            try {
                sample.sampleMethod1(null);
            } catch (Exception e) {
                // 知道在printMethod方法发生异常
                e.printStackTrace();
            }
        }
    }

【解说】

违反例的结果中,只能知道异常的发生,从stacktrace中无法知道之前是什么引起的。 这是因为在抛出RuntimeException时候,没有传入发生异常的类。 违反例的运行结果如下:
 Java.lang.RuntimeException:Error
 at jp.co.gihyo.javarulebook.chater3_6_2.
 BadSample.sampleMethod1(BadSmaple.java:15)
 at jp.co.giho.javarulebook.chapter3_6_2.
 BadSample.sampleMethod1(BadSmaple.java:28)
在看了修正例的运行结果后,因为在RuntimeException中恰当的传入了发生的异常,在异常stacktrace中追溯也变得容易 这个例子中NullPointerException就是产生异常的原因 修正例の运行结果如下:  Java.lang.RuntimeException:Error
 at jp.co.gihyo.javarulebook.chater3_6_2.
 GoodSample.sampleMethod1(GoodSmaple.java:15)
 Java.lang.RuntimeException:Error
 at jp.co.gihyo.javarulebook.chater3_6_2.
 GoodSample.sampleMethod1(GoodSmaple.java:28)
 Caused by:java.lang.NullPointerExcetion
 at jp.co.gihyo.javarulebook.chater3_6_2.
 GoodSample.sampleMethod1(GoodSmaple.java:21)
 at jp.co.gihyo.javarulebook.chater3_6_2.
 GoodSample.sampleMethod1(GoodSmaple.java:12)
 ……1 more
由此,要很容易的从stacktrace上追溯问题发生的原因就非常的重要。

3.6.3不要抛出Exception类生成的对象

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
   可以使用Exception类的对象,抛出异常吗? Exception类是所有异常的父类。用Exception类的实例作为异常抛出时,就不能从抛出的异常来判断发生了什么异常。 为了对应发生的异常,请抛出适当的异常类对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    违反例
    public class BadSample {
        public void badSmapleMethod() throws Exception {// 违反
            throw new Exception();// 违反
        }
    }

    修正例
    public class GoodSample {
        public void goodSmapleMethod() throws NoSuchMethodException {// 修正
            throw new NoSuchMethodException();// 修正
        }
    }

3.6.4 catch块必须做处理

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
catch块不能为空,必须对异常做出处理。 如果catch块什么也不做,就不能掌握发生了什么,也会引起严重的bug。如果不能做出处理,也需要用log来输出,好让之后发生异常时能够确认。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
    违反例
    public class BadSample {
        public void badSmapleMethod(File file) {
            BufferedReader reader = null;
            try {
                reader = new BufferedReader(new FileReader(file));
                reader.read();
            } catch (FileNotFoundException fnfe) {
                // 违反。使用者不知道发生了什么
            } catch (IOException ioe1) {
                // 违反。使用者不知道发生了什么
            } finally {
                try {
                    if (reader != null) {
                        reader.close();
                    }
                } catch (IOException ioe2) {
                    // 违反。使用者不知道发生了什么
                }
            }
        }

        public static void main(String[] args) {
            BadSample badSample = new BadSample();
            File file = new File("no_such_file");
            badSample.badSmapleMethod(file);
            System.out.println("例外が発生せず、何が起きているがわからない。");
        }
    }

    修正例
    public class GoodSample {
        private Logger logger = Logger.getLogger(getClass().getName());

        public void goodSampleMethod(File file) {
            BufferedReader reader = null;
            try {
                reader = new BufferedReader(new FileReader(file));
                reader.read();
            } catch (FileNotFoundException fnfe) {
                // 修正。最少也需要用log输出
                logger.log(Level.WARNING, "[SKIP]File not found", fnfe);
            } catch (IOException ioe1) {
                // 修正。最少也需要用log输出。
                logger.log(Level.WARNING, "[SKIP]I/O error", ioe1);
            } finally {
                try {
                    if (reader != null) {
                        reader.close();
                    }
                } catch (IOException ioe2) {
                    // 修正。最少也需要用log输出。
                    logger.log(Level.WARNING, "Cannot close", ioe2);
                }
            }
        }

        public static void main(String[] args) {
            GoodSample goodSample = new GoodSample();
            File file = new File("no_such_file");
            goodSample.goodSampleMethod(file);
        }
    }

3.6.5 不要继承Error、Throwwable类

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
Error类是程序必须catch的表示重大问题的异常类。因此在程序中,不能定义Error类的子类。程序定义的异常请继承Exception类。 另外也不能定义Throwable的子类。Throwable类是Exception和Error的父类,如果程序的异常继承了Throwable,错误和异常就会分不清楚。   在程序中定义的异常请继承Exception类。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    违反例
    public class BadSampleException
                    extends Error {// 违反
    }


    public class AnotherBadSampleException
                    extends Throwable {// 违反
    }

    修正例
    public class GoodSampleException
                    extends Exception {// 修正
    }

3.6.6 在finally块不要写对返回值有影响的语句。

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
finally块的内容,是无论之前是否有异常发生,都必须运行的。finally块中,像关闭流这种必须的操作,是必须写在其中的。
相反的finally块中如果写有return语句,不能写对方法的返回值有影响的语句。例如下面的违反例,finally中有return语句,返回值必定是2。。 这个内容和「3.5.1 使用API处理资源(消耗)时,在finally块中处理」,「3.6.7 不要在finally中抛出异常」总结起来,finally块的使用规则如下:

  • 关闭流之类的操作必须写在其中
  • 不要有对方法返回值有影响的操作
  • 因为有可能会覆盖try语句中发生异常,finally块中不要引起异常
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    违反例
    public class BadSample {
        public int sampleMethod(int value) {
            int a = value;
            try {
                // ……
                if (a == 1) {
                    return a;// 这里无论如何都会返回1。
                }
            } catch (SampleException e) {
                throw new RuntimeException(e);
            } finally {
                a = 2;
                return a;// 违反:这个方法的返回值必定为2。
            }
        }
    }

    修正例
    public class GoodSample {
        public int sampleMethod(int value) {
            int a = value;
            try {
                // ……
                if (a == 1) {
                    return a;
                }
            } catch (SampleException e) {
                throw new RuntimeException(e);
            }
            a = 2;
            return a;// 修正
        }
    }

3.6.7 不要从finally中抛出异常

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
设计时不要在finally语句中抛出异常。 在运行try语句中发生异常时,finally语句中发生的异常会覆盖之前的异常,结果就不知道本来应该捕捉的异常。 因此,就会引起不知道是什么样的异常引起的危险性。 违反例中,不管原来是FileNotFoundException异常,finally语句里抛出的异常将原来的异常覆盖。 正因为这样,本来check发生的异常的方法就没有了,也不知道是什么引起的异常。有必要理解finally语句是catch块抛出异常后仍然需要运行的前提之上实现的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
    违反例
    public class BadSample {
        public void badSampleMethod(File file) {
            Reader r = null;
            try {
                r = new FileReader(file);
                r.read();
            } catch (IOException ioe1) {
                // 原来是想抛出这个异常
                throw new RuntimeException(ioe1);
            } finally {
                // 在finally块中抛出异常的话
                // 原来发生的异常就被覆盖了
                throw new RuntimeException(
                "finallyで例外を投げでしまうと、
                             本来発生した例外は上書きされてしまう。");
            }
        }
        public static void main(String[] x) {
            BadSample sample = new BadSample();
            // 想要发生FileNotFoundException异常
            sample.badSampleMethod(new File("/no_such_file"));
        }
    }

    修正例
    public class GoodSample {
        public void goodSampleMethod(File file) {
            Reader r = null;
            try {
                r = new FileReader(file);
                r.read();
            } catch (IOException ioe1) {
                throw new RuntimeException(ioe1);
            } finally {
                try {
                    if (r != null) {
                        r.close();
                    }
                } catch (Exception e) {
                    //本来在日志里收集合适的log信息
                    e.printStackTrace();
                    System.out.println(e.getMessage());
                }
            }
        }

        public static void main(String[] x) {
            GoodSample sample = new GoodSample();
            // 想要发生FileNotFoundException异常
            sample.goodSampleMethod(new File("/no_such_file"));
        }
    }

3.7 多线程

3.7.1 实现Runnable或者Callable接口

⭐ ⭐ ⭐ ⭐

【说明・动机】
有以下3中方法来实现线程并行处理的类的作成

  1. 处理类作为Thread的子类来作成
  2. 处理类实现Runnable接口
  3. 处理类实现Callable接口(java5以后)

    如果要实现多线程处理,为了明确各个类的分工,请使用2和3的方法。1方法中,虽然重写run方法也可以实现多线程处理,但是在没有必要扩展或者利用run方法以外的Thread类的方法时,没有必要继承Thread类。 相反的在继承了Thread的地方使用了不必要的方法,方法可能会被重写,所以不推荐。 实现Runnable接口还是Callable接口,需要根据线程处理是否需要返回值来变化。

  4. 不需要返回值⇒Runnable接口

  5. 需要返回值⇒Callable接口

    另外Java5以后,为了代替直接使用Thread类,使用java.util.concurrent.Executor类能更简单的实现并行处理,请同时使用这几种方法(Executor的詳細参照「2-13-14 マ多线程程序要使用Java5标准的Executor框架)。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
    违反例
    //Thread的代码
    public class BadSampleController extends Thread {// 违反

        private Car car;

        public BadSampleController(Car car) {
            this.car = car;
        }

        @Override
        public void run() {
            CarState state = car.startEngine;
            if (state == CarState.START_ENGINE) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                throw new RuntimeException("error");
            }
        }
    }

    public class BadSample {
        public static void main(String[] args) {
            Car car = new Car("Red car");
            BadSampleController badSampleController = new BadSampleController(car);
            System.out.println("[" + Thread.currentThread().getName() + "] スレッド開始。");

            badSampleController.start();
            while (badSampleController.isAlive()) {
                System.out.println("¥tBadSampleController スレッドの停止を待っています。");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("[" + Thread.currentThread().getName() + "] スレッド終了。");
        }
    }

    修正例
    /**
     * 在线程编程中实现Runnable或者Callable接口为原则的例子
     *
     * 不是直接继承{@link Thread}
     * 继承{@link Runnable}接口
     *
     */
    public class GoodSampleController implements Runnable {// 修正

        private Car car;

        public GoodSampleController(Car car) {
            this.car = car;
        }

        @Override
        public void run() {
            CarState state = car.startEngine;
            if (state == CarState.START_ENGINE) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                throw new RuntimeException("error");
            }
        }
    }

    /**
     * 在线程编程中实现Runnable或者Callable接口为原则的例子
     *
     * 不是直接继承{@link Thread}、 从Java5导入
     * 继承{@link Runnable}接口
     *
     */
    public class GoodSampleController2 implements Callable<CarState> {// 修正
        private Car car;

        public GoodSampleController2(Car car) {
            this.car = car;
        }

        @Override
        public CarState call() throws Exception {
            CarState state = car.startEngine;
            if (state == CarState.START_ENGINE) {
                try {
                    TimeUnit.MILLISECONDS.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                throw new RuntimeException("error");
            }
            return state;
        }
    }

    /**
     * 在线程编程中实现Runnable或者Callable接口为原则的例子
     *
     * 从Java5导入的并行处理的框架api
     * 使用{@link ExecutorService},减少bug的产生。
     *
     */
    public class GoodSample {
        public static void main(String[] args) {
            //有ThreadPool的ExecutorService生成
            ExecutorService executor = Executors.newFixedThreadPool(2);
            //运行实现Runnable的实例
            Car car = new Car("Red car");
            executor.execute(new GoodSampleController(car));
            /运行实现Callable的实例
            Car car2 = new Car("Blue car");
            Future<Carstate> returnState = executor.submit(new GoodSampleController2(car2));
            while (true) {
                try {
                    //等待Callable的返回值
                    CarState carstate = returnState.get();
                    System.out.println("["+ Thread.currentThread().getName()+"] CallableからCarStateの取得に成功:"+ carstate);
                    break;
                } catch (InterruptedException e) {
                    System.out.println("スレッドがインタラプトされました。" + e);
                } catch (ExecutionException e) {
                    System.out.println("スレッド実行時にエラーが発生しました。" + e);
                }
            }
            //Executor结束
            System.out.println("["+ Thread.currentThread().getName() +"] Executorを停止します。");
            executor.shutdown();
            try {
                boolean awaitTermination = executor.awaitTermination(2,TimeUnit.SECONDS);
                if (awaitTermination==false) {
                    executor.shutdownNow();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("["+ Thread.currentThread().getName() + "] 全スレッド停止しました。");
        }
    }

3.7.2 使用notifyAll()方法来再次开启等待中的线程

⭐ ⭐ ⭐ ⭐

【说明・动机】
再次开启等待中的线程,请使用notifyAll()方法,而不是notify()方法。notify()方法是同一个对象中,开启多个等待线程中的一个,开启的线程无法预测。为了避免想要开启的线程没有开启的危险,除了以下的场合,请使用notifyAll()方法   1. 等待状态的线程只有1个 1. 开启任意1个线程都没有影响

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    违反例
    public class BadSample {
        // ...
        public void refuel(int fuel) {
            synchronized (car) {
                this.fuel = fuel;
                car.notify();// 违反
            }
            // ...
        }
    }

    修正例
    public class GoodSample {
        // ...
        public void refuel(int fuel) {
            synchronized (car) {
                this.fuel = fuel;
                car.notifyAll();// 修正
            }
            // ...
        }
    }

3.7.3 想要切换线程的时候,不要使用Thread类的yield()方法

⭐ ⭐ ⭐ ⭐

【说明・动机】
  Thread.yield()方法说到底只是促进线程的切换,不能保证线程实际切换   想要转移到其他线程的情况下,不要使用Thread.yield()。 另外yield()方法中,没有解放线程的锁。有锁的状态下使用yield()方法,会导致预期外的情况发生。有锁的状态下想要切换线程,请使用锁定对象的wait方法。 wait的使用方法,请参照3.7.5「 wait()、notify()、notifyAll()方法,在synchronized块中使用」和3.7.6「wait()方法执行后,再次确定前提条件」

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    违反例
    public class BadSample {
        private Object lock;
        public void longProcess() {
            synchronized (lock) {
                Thread.yield();// 违反
            }
        }
    }

    修正例
    public class GoodSample {
        private Object lock;
        public void longProcess() {
            synchronized (lock) {
                try {
                    lock.wait();// 修正
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3.7.4不要从synchronized块中呼出synchronized块中的方法

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
有没有在synchronized块中呼出synchronized块里有的方法(包含synchronized声明的方法)呢?有没有考虑过这样会发生死锁的可能性?死锁在满足以下条件时可能会发生
1. 有多个有锁对象 2. 刚取得某个对象的锁,又取得了其他对象的锁的代码 3. 对象的锁的顺序没有决定 因此不仅要严格的决定锁对象资源的锁顺序,也请不要从synchronized块中呼出synchronized块中的方法

【解说】

死锁是2个线程中,都为了取得对方的锁,而等待对象再次开启的状态。例如,有两个厨师。想要把所有东西切碎,需要有菜刀和案板,菜刀和案板缺一不可。厨师A取得菜刀,厨师B取得案板,双方都因为没有同时拥有菜刀和案板而不能工作。由此,A在等着B将案板放手,B在等着A将菜刀放手,互相等待,结果什么事也做不成。 以下是线程A(厨师A)和线程B (厨师B)对于X(菜刀)和Y(案板)的资源开放而互相等待的示意图。 3.7.4-1

这个问题,只要事先给予锁对象连接顺序就能解决。例如,菜刀和案板中规定「必须菜刀先解放」,厨师A先取得菜刀的情况下,厨师B必须要等A的工作完成菜刀解放。厨师A的工作结束后,厨师B才能使用菜刀,就不会产生死锁。

3.7.5 wait()、notify()、notifyAll()方法,在synchronized块中使用

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
wait()、notify()、notifyAll()方法,请在获得对象的锁的状态下使用。 如果没有获得锁就使用的,会产出「java.lang.illegalMonitorStateException」异常

【解说】

运行违反例,会发生以下的异常。使用notify()、notifyAll()也会有相同的结果。 Exception in thread "main" java.lang.IllegalMnitorStateException:current thread not owner
修正例就没有异常发生。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    违反例
    public class BadSample {
        public static void main(String[] args) {
            Test test = new Test();
            try {
                test.wait(); // 违反
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            Test test = new Test();
            synchronized (test) {// 修正
                try {
                    test.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3.7.6 wait()方法执行后,再次确定前提条件

⭐ ⭐ ⭐ ⭐ ⭐

【说明・动机】
使用wait(),很多情况下是等待条件成立的场合。但是wait()方法运行后, 不能保证条件成立。条件是否成立,需要在wait()运行后再次确定。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    违反例
    synchronized (car) {
        if (furl == null) {// 违反
            try {
                test.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        car.drive();// 处理
    }

【解说】
car对象(车)在没有fuel(燃料)的情况下不能工作。因此fuel为null的情况下,线程在等待中。 这样的处理一眼看上去很正常,但是发生了下面的情况会怎样呢?  

  1. 故意或者误操作,使用了notifyAll(),无论fuel是否是null,线程都再次开启了。
  2. 和这个处理没有关系的地方使用了notifyAll(),线程再次开启。
  3. fuel补充后,使用notifyAll(),但其他的线程在之前已经获得了car的锁,fuel的状态改变了
  4. VM的任意操作,线程再次开启(这个现象叫「スプリアスウェイタアップ」)

上面这些情况发生的情况下,不能保证「fuel不为null的时,car.drive()会正常运行(车会走)」。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    修正例
    synchronized (car) {
        while (furl == null) {// 修正
            try {
                test.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        car.drive();          // 处理
    }

【解说】

违反例中,就是用if替换了while。 if语句中,语句只会运行一次,就移动到下面的操作了。while语句会再次确定语句中的条件。只有结果为true的时候,才会运行内部的处理,当结果为false,就会跳过while语句进行下面的操作。 3.7.6-1

因此,fuel是null的状态下再次开启线程,也需要对条件进行判断。fuel如果是null的情况线程等待,就可以保证car.drive()运行时fuel不为null。

3.7.7 处理的等待不要使用循环定时器,要使用CountDownLatch之类已经存在的结构

⭐ ⭐ ⭐ ⭐

【说明・动机】
在其他线程在中,循环定时器作为等待运行中的处理的方法。循环定时器是,「某个条件满足,继续处理的场合下,自身要访问这个条件是否满足」。因为循环定时器的频繁访问,消耗了大量时间做循环处理。另外间隔一定时间访问的场合,一次访问后,即使条件满足,直到下一次访问开始之前,处理都不会进行,程序占有时间过长   相反的,条件满足时如果向对方通知,就能防止无效的资源耗费。Java5导入的协同api,java.util.concurrent的CountDownLatch,准备了以下两种方法

  • 处理结束时通知countDown方法
  • 直到countDown调用,保持await方法

    例如,条件满足的地方,用await方法,保持当前状态,其他的线程条件完整之后,调用countDown方法,等待的地方处理就会再次进行。这样做的话,就没有不必要的访问,变得非常智能。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
    违反例
    //违反 使用循环定时器的Runnable
    public class BadRobot implements Runnable {

        private byte[] commands;

        private RobotController controller;

        public BadRobot(RobotController controller) {
            this.controller = controller;
        }

        @Override
        public void run() {
            long start = System.currentTimeMillis();
            synchronized (controller) {
                while (commands == null) {
                    try {
                        System.out.println("waiting...");
                        TimeUnit.SECONDS.sleep(10);// 违反
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("go!");
                int size = commands.length;
                for (int i = 0; i < size; i++) {
                    this.processCommand(commands[i]);
                }
                commands = null;
            }
            long end = System.currentTimeMillis();
            System.out.println("所要时间:" + (end - start) + "msec");
        }
        public synchronized void storeCommands(byte[] commands){
            this.commands=commands;
        }
        private void processCommand(byte[] command){
            controller.execute(command);
        }
    }

    //运行代码
    public class BadSample {
        public static void main(String[] args) {
            BadRobot robot=new BadRobot(new RobotController);
            new Thread(robot).start();
            try {
                //wait a bit
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (Exception e) {
                e.printStackTrace();
            }
            robot.storeCommands(new byte[]{1,2,3});
        }
    }

【解说】

违反例的while语句,运行以下的处理,并且满足向下次处理进行的条件。  badRobot.storeCommands(commands)
但是,这个处理一直到运行,用了10000微秒(10秒),每次都运行了sleep()。另外即使条件满足,直到sleep()结束,才会进行下面的处理。如果使用 java.util.concurrent.CountDownLatch,就能像以下一样简单的实现访问处理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
    修正例
    //修正 使用latch并实现
    public class BadRobot implements Runnable {
        private byte[] commands;
        private RobotController controller;
        private CountDownLatch startLatch;
        private CountDownLatch endLatch;
        public GoodRobot(RobotController controller,
                               CountDownLatch startLatch,
                CountDownLatch endLatch) {
            this.controller = controller;
            this.startLatch = startLatch;
            this.endLatch = endLatch;
        }

        @Override
        public void run() {
            long start = System.currentTimeMillis();
            System.out.println("waiting...");
            try {
                startLatch.await();
            } catch (Exception e) {
                e.printStackTrace();
            }

            synchronized (controller) {
                System.out.println("go!");
                int size = commands.length;
                for (int i = 0; i < size; i++) {
                    this.processCommand(commands[i]);
                }
                commands = null;
            }
            long end = System.currentTimeMillis();
            System.out.println("所要时间:" + (end - start) + "msec");

            // 終了のパリアを外す。
            endLatch.countDown();
        }

        public synchronized void storeCommands(byte[] commands) {
            this.commands = commands;
            //ラッチをカウントダウンし、パリアを外す。
            startLatch.countDown();
        }

        private void processCommand(byte[] command) {
            this.controller.execute(command);
        }
    }

【解说】

调用的地方要像下面这样。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    sample
    public class GoodSample {

        public static void main(String[] args) {
            RobotController controller = new RobotController();

            //直到开始条件满足时都在等待的Latch。
            CountDownLatch startLatch = new CountDownLatch(1);

            //直到结束条件满足时都在等待的Latch。
            CountDownLatch endLatch = new CountDownLatch(1);
            GoodRobot goodRobot = new GoodRobot(controller, startLatch, endLatch);
            new Thread(goodRobot).start();

            // コマンドの設定時にGoodRobotrunで
            // startLatchで実現したパリアを外す。
            goodRobot.storeCommands(new byte[] { 7, 8, 9 });

            // 終了のバリア
            try {
                endLatch.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("終了。");
        }
    }

3.7.8 同步(synchronized)只适用于必要的部分

⭐ ⭐ ⭐ ⭐

【说明・动机】
要使用同步,有以下两种方法。

  1. 方法用synchronized声明
  2. 利用synchronized块

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    ●1
    synchronized void method () {
        ...
    }
    ●2
    void method () {
      synchronized(<インスタンス>){
        ...
      }
    }
2中的synchronized块的<インスタンス>部分是取得锁的实例。那么1中的synchronized方法如何取得锁呢?这个方法如下:

1
2
3
4
5
    void method () {
       synchronized(this){
            ...
       }
    }
像看到的一样,synchronized方法通过this来获得锁(static synchronized方法的情况下,他的类java.lang.Class实例是被锁的)。被锁的对象不能从其他地方和synchronized连接,对于this锁定的情况,获得锁的时候(方法运行时),不能使用同一个synchronized对象的方法。   这就是线程安全,这个方法不是同步被调用,或者是锁解除前一直在等待。对于用this来获取锁带来的影响要放在心上。 因此,要按下面的做法: + 首先synchronized块要考虑能否运行部分的锁 + 只有在所有方法都需要判断是否要同步的情况下,方法用synchronized声明

要使用synchronized块的场合,要遵守同步的范围要尽量小(不得不同步的地方)的原则。synchronized的适用范围越小,就能减少以下列出的情况的发生。 + 死锁发生的可能性 + 性能低下 + 死锁发生时的debug成本(低可读性)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    违反例
    public class GoodSample {

        private Map<String, String> names = new Map<String, String>();
        private Map<String, Long> birthdays = new Map<String, Long>();

        public synchronized void setName(String user, String name) {//违反
            names.put(user, name);
        }

        public synchronized void setBirthDay(String user, Lond birthday) {//违反
            birthdays.put(user, birthday);
        }

        // 超出了必须同步的范围,setName()方法运行并没有关系
        // 也不要使用setBirthDay()
    }
    修正例
    public class GoodSample {

        private Map<String, String> names = new Map<String, String>();
        private Map<String, Long> birthdays = new Map<String, Long>();

        public void setName(String user, String name) {// 修正
            synchronized (names) {// 修正
                names.put(user, name);
            }
        }

        public void setBirthDay(String user, Lond birthday) {// 修正
            synchronized (birthdays) {// 修正
                birthdays.put(user, birthday);
            }
        }
    }

3.7.9 多线程环境下使用ConcurrentHashMap或者CopyOnWriteArrayList

⭐ ⭐ ⭐ ⭐

【说明・动机】
写多线程环境下运行的程序,要HashMap或是ArryList同步,不使用Collections#synchronizedMap()#Collections#synchronizedList(),而是使用Java5以后追加的ConcurrentHashMap或者CopyOnWriteArrayList。Java5后追加的这些类,使用方便程度几乎没有变,并且有更好的并行连接性,性能也提高了  

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    违反例
    public class BadSample {
        public static void main(String[] args){
            Map<String, String> map=
                Collections.synchronizedMap(new HashMap<String,String>);//违反 全部的方法都被同步
            map.put("日本", "Japan");
            map.put("アメリカ", "US");
            map.put("ヨーロッパ", "EU");
            System.out.println(map);
            List<String> list=
                Collections.synchronizedList(new ArrayList<String>());//违反 全部的方法都被同步
            list.add("Tokyo");
            list.add("Kanagawa");
            list.add("Chiba");
            System.out.println(list);
        }
    }

    修正例
    public class GoodSample {
        public static void main(String[] args) {
            ConcurrentHashMap<String, String> map = new ConcurrentHashMap<String, String>();// 修正
            map.putIfAbsent("日本", "Japan");// 新api方法
            map.put("アメリカ", "US");
            map.put("ヨーロッパ", "EU");
            System.out.println(map);

            CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();//修正
            list.addIfAbsent("Tokyo");
            list.add("Kanagawa");
            list.add("Chiba");
            System.out.println(list);

        }
    }

3.8 Garbage Collection垃圾回收机制

3.8.1 不要重写finalize()

⭐ ⭐ ⭐ ⭐

【说明・动机】
  重写了finalize方法,他的实例在回收的时候也能被调用。但是调用的时间或是线程不受控制。另外不是回收对象的时候,不自己运行,实现调用finalize的代码,会产生预期外的bug。 因此,例如在db连接或是file关闭处理,公开对象不要实现finalize,资源或是对象不要的时候再进行处理。 finalize方法, 在没有其他代替手段,只有finalize可以处理的场合下才重写。几乎所有的情况都不需要在程序中使用finalize处理。   

【MEMEO】使用结构管理工具
不局限于java,多人开发的时候资源代码的结构管理就变得很重要。为了管理而使用的工具就是结构管理工具。经常使用的有SVN或是CVS。最近一个叫git的工具也被广泛使用 用什么工具要根据项目的情况和个人的喜欢,要努力使用结构管理工具来适当的管理资源代码。 目前在Eclipse上的支持来考虑,CVS是以Eclipse中的CVSclient为标准的。SVN也是,提供Subversive或是Subclipse插件,是强力的支持。   另外git的支持是刚刚开始的。 EGit作为Eclipse插件,也是充满了期待。 ※1、Subversion http://subversion.apache.org
※2、CVS http://www.nongnu.org/cvs/
※3、Git http://git-scm.com/
※4、Subversive http://www.eclipse.org/subversive
※5、Subclipse http:/subclipse.tigris.org
※6、EGit http:///www.eclipse.org/egit/

3.9 其他

3.9.1 传递匿名类的参数时,使用final修饰

⭐ ⭐ ⭐ ⭐

【说明・动机】
对于匿名类传递参数的场合,不要使用成员变量或者临时变量,方法自己的变量用final修饰。可以增加可读性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
    违反例
    public class BadSample {
        private String tempValue1;

        public Runnable sampleMethod(String value1, String value2) {
            tempValue1 = value1;

            // 违反 使用成员变量或者临时变量传值
            final String tempValue2 = value2;

            Runnable runnable = new Runnable() {

                @Override
                public void run() {
                    System.out.println(tempValue1 + tempValue2);
                }
            };
            return runnable;
        }
    }

    修正例
    public class GoodSample {

        private String tempValue1;

        public Runnable sampleMethod(final String value1, final String value2) {
            //修正sample
            Runnable runnable = new Runnable() {

                @Override
                public void run() {
                    System.out.println(value1 + value2);
                }
            };
            return runnable;
        }
    }

【MEMO】 状況を理解して使うツール・戦略を変える
本书记述了一些java项目里比较一般的工具,和不太容易引起错误的Tips。尽量使用一些通用的规则。但是所有情况下,这个规则并不是绝对唯一的。规则本来就是根据原来的状况使用并且不断修正的。 说道具体的例子,说是Java也可以说是移动应用程序,最近,对于Android开发,本书的规则也有可能有些地方不适用。那是因为Android程序对资源的约束比较严格,根据android框架的约束本书记载的机能不能使用等情况,使用方法会产生区别。 这样,项目采用的技术或是项目自身的情况,考虑到这些,对于一个健全的开发项目来说,有必要制作适合规则。本书能在这样的场合下起到作用就太好了。 ※ google开发的,基本是面向移动的平台。
http://www.android.com/

3.9.2 返回值有多个的场合下,不要使用数组或是Map

⭐ ⭐ ⭐ ⭐

【说明・动机】
方法的返回值有多个的场合下,不要轻易的使用数组或是Map。不知道返回值放在哪放了什么,可读性降低,也容易发生bug。特别注意在返回计算结果或是db得到数值的情况。   多个返回值的场合下,生成一个返回值的类,返回这个类的实例。   複数の値をまとめて返す場合には、类を使って適切なデータ構造を作成して、どのようなデータを持っているのか、どうやってデータを取り出すのかを、コードの利用者にも出来るだけわかりやすくしてください。
集中多个值并返回的情况,使用类来生成合适的数据,要用怎样的数据,怎么将数据取出,代码的使用者也能很容易的理解。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
    违反例
    public class BadSample {
        public String[] createPersonArray(String name, String address) {
            String[] result = { name, address };
            return result;
        }

        public Map<String, String> getPersonMap(String name, String address) {
            Map<String, String> person = new HashMap<String, String>();
            person.put("name", name);
            person.put("address", address);

            return person;
        }

        public static void main(String[] args) {
            final String name = "山田";
            final String address = "東京都";
            BadSample sample = new BadSample();

            //违反 第0个是名字,第1个是住址。
            //调用方法的地方并不知道
            String[] personArry = sample.createPersonArray(name, address);
            System.out.println("配列の型ではどこに何が入っているかわからない"+ personArry);

            //违反 调用方法的地方不知道名字和住址。
            Map<String, String> personMap = sample.getPersonMap(name, address);
            System.out.println("Mapでもどのキーで値が入っているかわからない。"+ personArry);
        }
    }

    修正例
    public class GoodSample {
        public Person createPerson(String name, String address) {
            Person person=new Person(name,address);
            return Person;
        }

        public static void main(String[] args) {
            final String name = "山田";
            final String address = "東京都";
            GoodSample sample = new GoodSample();

            //修正 使用合适的数据结构类来集中数据值
            Person person =sample.createPerson(name, address);

            //修正  使用IDE等,不论有什么样的项目都可以补充。
            System.out.println("名前は" + person.getName());
            System.out.println("住所は" + person.getAddress());
        }
    }

4.补充

4.1 命名总结

变量/方法

  • 小写字母开头,后面各个单词首字母大写
    • lineNumber
    • radioGroupIPAddress
    • doSomeAction() (方法:动词开始)
    • executeSample()
    • createObjectName() 生成对象的方法 「 Dir 」
  • 使用 Class[File] 定义变量的时候,是文件夹的场合下,后面追加「 Dir 」
    • imagesDir
    • resDirなど

类/接口/enum类

  • 大写字母开头,后面各个单词首字母大写
    • SampleCalss
    • LoginAction
  • 异常类: -Exception
    • SampleException   (异常类)
  • 测试类: -Test
    • SampleTest   (测试类)
  • 抽象类: Abstract-
    • AbstractSampleClass (抽象类)

  • 全部小写字母
    • jp.co.casio.caios.xxx.xxx

常量/enum常量

  • 全部大写字母
  • 常量全部用static final声明
  • 区分:「_」(下划线)
1
2
3
4
5
6
    public static final int SAMPLE_VALUE = 1;
    public enum MultiLanguage{
        JAPANESE,
        ENGLISH,
        CHINESE;
    }

资源文件名

  • 全部小写字母
  • 用「_」区分
1
2
3
4
5
    main.xml
    row_item.xml
    table_name_list.xml
    strings.xml
    style.xml

资源/控制器

  • 和①一样,小写字母开头,后面各个单词首字母大写
  • 不要省略控制器的默认名,后面再追加作用的描述。
  • 在Java source内和资源关联时,控制器的名称要和资源一致。
1
2
3
    android:id="@+id/textViewTableTitle" 
    android:id="@+id/listViewTablesName" 
    android:id="@+id/gridViewTable" 
1
    ListView listViewTablesName = (ListView) findViewById(R.id.listViewTablesName);

资源(控制器以外)

  • 全部小写字母
  • 用[_]区分
1
2
    <string name="button_cancel">いいえ</string>
    <string name="dialog_message_unchecked_confirm">ファイルを選んでください</string>

在Strings.xml定义资源的时候,基本上要使用下列的单词作为开头

  • dialog_title_
  • dialog_message_
  • title_
  • headline_
  • text_
  • button_
1
2
3
    <string name="diaog_title_unchecked_confirm_title">確認</string>
    <string name="button_record_add">追加</string>
    <string name="button_record_modify">修正</string>

Style资源的名称基本上以「style」开头

1
2
3
4
5
6
7
8
    <style name="style_text">
            <item name="android:layout_width">wrap_content</item>
           ……
     </style>
    <style name="style_button">
            <item name="android:layout_height">36px</item>
           ……
    </style>

事件方法的名称

  • 基本上各个控制器的事件都在类里实现
  • 同一个类中有多个可以连接事件方法的控制器时,要用控制器的资源ID区分
  • 对于各个控制器的事件方法,要在中扩充其他方法时,名称中要有控制器的名称。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    onClick(View ){ 
    switch(v.getId()){
     case buttonAppend:
               onButtonAppendClick();
     case buttonRemove:
               onButtonRemoveClick();
     case buttonClose:
               onButtonCloseClick();
    }
    onKey(...){
               ...
               onEnterKey();
               onXxxxKey();
    }
其他的事件方法参照上面的例子定义

方法名称和顺序

Activity的方法的名称定义规则和顺序

  1. receiveParameter()
  2. initialize()
  3. onLoad()
  4. onClick()
  5. onItemClick()
  6. onAction()
  7. changeFunctionButtonStatus()
  8. showDialog()
  9. onDialogResultClick()
  10. call○○Activity()
  11. onActivityResult()
  12. update()

Activity类的名称

Activity类的名称后要加「Activity」 MainActivity
CustomerBasicActivity

颜色的使用

在xml文件和java文件中使用颜色的场合,使用Android系统的颜色,不要使用color.xml xml文件:

1
2
    android:textColor="@android:color/white" 
    android:textColor="@android:color/black" 

Java文件:

1
2
    Color.BLACK
    Color.WHITE

名称定义的简洁化

1.名称定义要尽量简洁,尽量的容易理解

  • LinearLayout
  • RelativeLayout
  • AbsoluteLayout
    • +id/layoutXxx
  • FinderHorizontalScrollView
  • FinderScrollView
    • +id/scrollViewXxx
  • PageImageView
    • +id/imageViewXxx
  • IconButton
    • +id/buttonXxx

2.控制器的名称定义时,不必要反映layout的文件名,为了简洁,不用追加

page.xml    relativeLayoutPageFunction -> layoutFunction    buttonPageCancel  -> buttonCancel    buttonPageShowMode -> buttonShowMode list.xml buttonListCancel    -> buttonCancel

3.根据控制器的功能,来决定名称的开始。
有背景图片的情况下,比如按钮,想要用透明的TextView,Layout来实现,这种情况下,根据textView○○,layout○○的规则,不要定义容易理解的名字,而是根据功能来命名。上面的情况下都请使用「button~」

Activity的方法的命名规则和顺序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    receiveParameter();
    initialize()
    initButton()
    initTextView()
        initListView()
        ・・
    onLoad()
    onClick()
    onButtonXxxxClick()
     ・・・
    onItemClick()
    onXxxxxItemClick()
     ・・・
    onItemSelected()
    onScrollChanged()
    onScroll()
    onTouch()
    ・・
    onAction()
    onActionAdd()
    onActionModify()
    onActionDelete()
    onActionShow()
    onActionEdit()
    ・・・
    changeFunctionButtonStatus()
    changeFunctionButtonVisibleStatus()
    changeFunctionButtonEnableStatus()
    changeFunctionButtonBackgroundColorStatus()
    changeFunctionButton○○Status()
    ・・・
    showDialog()
    showXxxxDialog()
    ・・・
    onDialogResultClick()
    onXxxxxDialogResult()
    callXxxxxActivity()
    onActivityResult)
    update()
        //update(int id,string text);
        //update(int id);//R.id.listViewLeft
    updateXxxxx()

4.2 其他总结

2个数值的比较

使用Math.max和Math.min ・两个数值进行比较,取得较大数的场合下,使用Math.max ・两个数值进行比较,取得较小数的场合下,使用Math.min

1
2
3
4
    int a=10;
    int b=12;
    //Math.max(a,b)返回值12
    //Math.min(a,b)返回值10

连接字符串

3个以上的字符串连接,请使用StringBuilder

1
2
3
    StringBuilder stringBuilderSql = new StringBuilder();
    stringBuilderSql.append("SELECT ").append(TABLE_CMA001)
    //……

Map遍历

○○.使用entrySet()

1
2
3
4
    KeyValues kvs = new KeyValues(keyValueMap.size());
    for (Map.Entry<String, String> entry : keyValueMap.entrySet()) {
       kvs.put(entry.getKey(), entry.getValue());
    }

在使用KeyValues、ContentValues的场合,获取值

○○.不要用get(key)方法,请使用下列的方法:

  • ○○.getAsString(key)
  • ○○.getAsBoolean(key)
1
2
3
4
    ContentValues cvs = new ContentValues();
    String contentValue=cvs.getAsString(key);
    KeyValues kvs = new KeyValues();
    String keyValue=kvs.getAsString(key);

数组拷贝

想要拷贝数组时,使用System.arraycopy

1
2
    columns = new String[columns.length];
    System.arraycopy(columns, 0, queryColumns, 0, columns.length);
領域 类本身 同一个包内 子类 其他包
public
protected ×
friendly × ×
private × × ×
  1. public 公开成员,所有类和包都能使用
  2. private 私有成员,类本身能使用
  3. protected 类本身或是被继承的类或是同一个包内可以使用
  4. friendly 类本身和同一个包内可以使用