• xianaldai

    本期是Java开发教程系列的第三期,本期主要向各位介绍两个"组织代码"的基础知识——分包和注释.若你已经熟悉 package/import 和 javadoc,可以跳过本期内容.

    *每期叠甲*:

    时间仓促,水平有限,文中难免存在缺点和错误,恳请广大开发者和从业者指正.


    一、为什么需要包

    第 2 期里,我们的 Student 和 Main 都直接放在默认包里——只要不写 package 声明,类就属于默认包.练习时没问题,但真实项目会有两个麻烦:

    - 文件一多就没法找:几十上百个类全堆在一个文件夹里,想找"学生相关的"得来回翻

    - 类名会撞车:两个功能没法写相同的类名

    包(package)就是为了解决这两件事:把类按功能分到不同的"文件夹"里;同名的类只要在不同包里,就不冲突.


    二、声明包:package

    在 .java 文件的第一行写:```

    package com.mycodes.demo;

    public class Student {

    // ...

    }
    ``- package` 声明这个类属于哪个包,必须写在文件第一行,前面不能有其他代码(注释除外)

    - 包名对应磁盘上的目录结构:包 com.mycodes.demo 对应目录 com/mycodes/demo/,类文件就放在这个目录里

    - 包名规范:全小写,一般用反域名.比如你的网站是 mycodes.com,包名就用 com.mycodes.xxx.域名全球唯一,反着写就能避免全世界的类撞包名

    - 不写 package 就是默认包.默认包不能被 import,只适合练习用,正式项目不要用


    三、导入:import

    不同包里的类,要先 import 进来才能直接用:```

    import com.mycodes.demo.Student;

    public class Main {

    public static void main(String[] args) {

    Student s = new Student(); // 现在可以直接用 Student

    }

    }
    ``- 想一次性引入一个包里的一堆类,可以偷懒用通配符 *:``

    import java.util.\*; // 引入 java.util 包里的所有类
    ``但 *` 有局限,别依赖它:

    - 只管这一个包,不含子包:import java.util.* 只导入 java.util 包里直接定义的类,java.util.regex 这类子包里的类还是要单独 import——想用里面的 Pattern,得再写一行 import java.util.regex.Pattern; , * 帮不了你

    - 撞名时照样救不了:如果两个包都用 * 导入了,而它们各有一个同名类(比如 java.util.* 和 java.awt.* 里都有 List),直接写 List 照样编译报错——这时只能写全限定名,或者去掉一个 *、改成显式导入其中某个类

    - 看不清依赖:* 让读者看不出这个文件到底用了哪些类,代码量一大可读性就下降,很多工程规范直接禁止用 *

    所以结论:单个类就写单个 import;同一包要用的类确实很多时才考虑 *,并且时刻记得它只管当前这一层.

    - 同一个包里的类不需要 import,直接写类名就行

    - java.lang 包(里面有 String、System 等最常用的类)默认自动导入——所以前几期我们直接用 String、System.out,从没写过 import

    - 想不 import,也可以写全限定名(包名加类名,如 com.mycodes.demo.Student),但那样太啰嗦(这个其实在IDE可以快捷import,因此我们推荐写import)


    四、public 和默认访问

    跨包用别人的类,得知道一个前提:类里的东西默认只能被同一个包访问,被 public 修饰的东西才允许任何地方访问.```

    package com.mycodes.demo;

    public class Student {

    public String name; // public:谁都能访问

    String secret; // 默认:只有同包能访问

    public void sayHello() {

    System.out.println("你好,我是 " + name);

    }

    }
    ``- 类前面写了 public(public class Student`),别处的类才能 new 它

    - 方法前面写了 public,别处的类才能调用它

    - 什么都没写的字段/方法,只有同一个包里能用;跨包访问会编译报错

    本期只需要记住这一个规则就够用,完整的四种访问级别(public/protected/ package-private/private)下一期专门讲.


    五、注释与 javadoc

    注释是写给人看的,编译器会直接忽略.有三种:

    5.1 行注释 // xxxxx 和块注释 /* */```

    // 这是单行注释,直到这一行结束

    /*

    * 这是块注释,可以跨多行.

    * 一般用来给一段代码做说明.

    */
    ``**5.2 javadoc 注释 /** */**``

    /**

    * 这是 javadoc 注释.

    * 写在类或方法前面,描述它"是什么、怎么用".

    */

    public class Student {

    // ...

    }
    ```javadoc 比普通注释多两件事:

    - 可以在里面写 @param(参数说明)和 @return(返回值说明)

    - IDE 会在你鼠标悬停到类名或方法名上时,把 javadoc 显示成小提示——自己写过的代码,几个月后翻回来,悬停一下就能想起它是干嘛的(下图就是)

    图片

    给方法写 javadoc 的例子:```

    /**

    * 判断学生是否及格.

    *

    * @param threshold 及格线,低于它算不及格

    * @return 成绩大于等于及格线返回 true,否则返回 false

    */

    boolean passed(double threshold) {

    return score >= threshold;

    }
    ```写注释的三个建议:

    - 写"为什么这么做",而不是"做了什么"——i++ 谁都看得懂,但"为什么这里要加一"才值得写

    - 公开的类和方法,养成写 javadoc 的习惯

    - 注释不要太多太碎,否则和代码一样难读


    六、小项目:带包名的学生类

    把本期内容串起来,建一个真正带包的小项目.目录结构:```

    codes/

    └── src/

    ├── com/mycodes/demo/ ← 包 com.mycodes.demo

    │ └── Student.java

    └── com/mycodes/app/ ← 包 com.mycodes.app

    └── Main.java
    ``文件一 com/mycodes/demo/Student.java:``

    package com.mycodes.demo;

    public class Student {

    public String name;

    public double score;

    /**

    * 创建一个学生.

    *

    * @param name 学生姓名

    * @param score 学生成绩

    */

    public Student(String name, double score) {

    this.name = name;

    this.score = score;

    }

    /**

    * 判断是否及格.

    *

    * @return 成绩大于等于 60 返回 true,否则返回 false

    */

    public boolean passed() {

    return score >= 60;

    }

    }
    ``文件二 com/mycodes/app/Main.java:``

    package com.mycodes.app;

    import com.mycodes.demo.Student;

    public class Main {

    public static void main(String\[\] args) {

    Student s1 = new Student("小明", 92);

    Student s2 = new Student("小红", 58);

    System.out.println(s1.name + ":" + (s1.passed() ? "及格" : "不及格"));

    System.out.println(s2.name + ":" + (s2.passed() ? "及格" : "不及格"));

    }

    }
    ``运行结果:``

    小明:及格

    小红:不及格
    ```注意几个点:

    - Main 在 com.mycodes.app,Student 在 com.mycodes.demo,靠第一行的 import com.mycodes.demo.Student; 引入

    - 两个类都标了 public,所以能跨包使用

    - 每个类和方法前都写了 javadoc,鼠标悬停 new Student(...) 时,IDE 会显示"创建一个学生"的说明

    小知识:IDE(如 IntelliJ IDEA / Eclipse)建包时,会在磁盘上自动生成对应的目录;编译后类的"完整名字"是"包名 + 类名".


    朝花夕拾:终于看懂第 1 期的 import 了

    第 1 期我们用 ArrayList 和 HashMap 时,开头写过:```

    import java.util.ArrayList;

    import java.util.List;

    import java.util.HashMap;

    import java.util.Map;
    ``当时没说为什么.现在你知道了:ArrayList、HashMap 这些类并不在 java.lang 里,而在 java.util 包里,所以要先用 import 把它们引进来才能直接用.java.util 就是 JDK 自带的"工具类"包——Scanner、Random、Collections` 等等都在里面,后面会经常见到.


    总结

    本期学习了两个"组织代码"的基础知识:

    - 分包:package 声明类所属的包,包名对应目录结构,用反域名规范避免撞名;不同包的类用 import 引入

    - public 与默认访问:public 允许任何地方访问,什么都不写只允许同包访问(完整规则下一期讲)

    - 注释:行注释 //、块注释 /* */、javadoc /** */;用 @param/@return 写参数和返回值的说明

    下一期我们学习访问控制与封装——把四种访问级别讲完整,并学会用 private + getter/setter 把数据保护起来.

    主楼
  • xianaldai

    把markdown编辑器换成新编辑器被命中了

    #1
  • Neo-linuxdo

    点赞,看到这系列帖子,一下给我拉回了好多年前的感觉。

    #2

发表回复

登录后回复