计算机程序的思维逻辑 (22) - 代码的组织机制

使用任何语言进行编程都有一个类似的问题,那就是如何组织代码,具体来说,如何避免命名冲突?如何合理组织各种源文件?如何使用第三方库?各种代码和依赖库如何编译连接为一个完整的程序?

本节就来讨论Java中的解决机制,具体包括包、jar包、程序的编译与连接,从包开始。

包的概念

使用任何语言进行编程都有一个相同的问题,就是命名冲突,程序一般不全是一个人写的,会调用系统提供的代码、第三方库中的代码、项目中其他人写的代码等,不同的人就不同的目的可能定义同样的类名/接口名,Java中解决这个问题的方法就是包。

即使代码都是一个人写的,将很多个关系不太大的类和接口都放在一起,也不便于理解和维护,Java中组织类和接口的方式也是包。

包是一个比较容易理解的概念,类似于电脑中的文件夹,正如我们在电脑中管理文件,文件放在文件夹中一样,类和接口放在包中,为便于组织,文件夹一般是一个层次结构,包也类似。

包有包名,这个名称以逗号分隔表示层次结构。比如说,我们之前常用的String类,就位于包java.lang下,其中java是上层包名, lang是下层包名,带完整包名的类名称为其完全限定名,比如String类的完全限定名为java.lang.String。Java API中所有的类和接口都位于包java或javax下,java是标准包,javax是扩展包。

接下来,我们讨论包的细节,从声明类所在的包开始。

声明类所在的包

语法

我们之前定义类的时候没有定义其所在的包,默认情况下,类位于默认包下,使用默认包是不建议的,文章中使用默认包只是简单起见。

定义类的时候,应该先使用关键字package,声明其包名,如下所示:

package shuo.laoma;

public class Hello {
    //类的定义
}

以上声明类Hello的包名为shuo.laoma,包声明语句应该位于源代码的最前面,前面不能有注释外的其他语句。

包名和文件目录结构必须匹配,如果源文件的根目录为 E:\src\,则上面的Hello类对应的文件Hello.java,其全路径就应该是E:\src\shuo\laoma\Hello.java。如果不匹配,Java会提示编译错误。

命名冲突

为避免命名冲突,Java中命名包名的一个惯例是使用域名作为前缀,因为域名是唯一的,一般按照域名的反序来定义包名,比如,域名是:apache.org,包名就以org.apache开头。

没有域名的,也没关系,使用一个其他代码不太会用的包名即可,比如本文使用的"shuo.laoma",表示"老马说编程"中的例子。

如果代码需要公开给其他人用,最好有一个域名以确保唯一性,如果只是内部使用,则确保内部没有其他代码使用该包名即可。

组织代码

除了避免命名冲突,包也是一种方便组织代码的机制,一般而言,同一个项目下的所有代码,都有一个相同的包前缀,这个前缀是唯一的,不会与其他代码重名,在项目内部,根据不同目的再细分为子包,子包可能又会分为子包,形成层次结构,内部实现一般位于比较底层的包。

包可以方便模块化开发,不同功能可以位于不同包内,不同开发人员负责不同的包。包也可以方便封装,供外部使用的类可以放在包的上层,而内部的实现细节则可以放在比较底层的子包内。

通过包使用类
同一个包下的类之间互相引用是不需要包名的,可以直接使用。但如果类不在同一个包内,则必须要知道其所在的包,使用有两种方式,一种是通过类的完全限定名,另外一种是将用到的类引入到当前类。

只有一个例外,java.lang包下的类可以直接使用,不需要引入,也不需要使用完全限定名,比如String类,System类,其他包内的类则不行。

比如说,使用Arrays类中的sort方法,通过完全限定名,可以这样使用:

int[] arr = new int[]{1,4,2,3};
java.util.Arrays.sort(arr);
System.out.println(java.util.Arrays.toString(arr));

显然,这样比较啰嗦,另外一种就是将该类引入到当前类,引入的关键字是import,import需要放在package定义之后,类定义之前,如下所示:

package shuo.laoma;
import java.util.Arrays;

public class Hello {
    public static void main(String[] args) {
        int[] arr = new int[]{1,4,2,3};
        Arrays.sort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

import时,可以一次将某个包下的所有类引入,语法是使用.*,比如,将java.util包下的所有类引入,语法是:import java.util.*,需要注意的是,这个引入不能递归,它只会引入java.util包下的直接类,而不会引入java.util下嵌套包内的类,比如,不会引入包java.util.zip下面的类。试图嵌套引入的形式也是无效的,如import java.util.*.*。

在一个类内,对其他类的引用必须是唯一确定的,不能有重名的类,如果有,则通过import只能引入其中的一个类,其他同名的类则必须要使用完全限定名。

引入类是一个比较繁琐的工作,不过,大多数Java开发环境都提供工具自动做这件事,比如,在Eclipse中,通过菜单"Source->Organize Imports"或对应的快捷键ctrl+shift+O就可以自动管理引入类。

包范围可见性

前面几节我们介绍过,对于类、变量和方法,都可以有一个可见性修饰符,public/private/protected,而上节,我们提到可以不写修饰符。如果什么修饰符都不写,它的可见性范围就是同一个包内,同一个包内的其他类可以访问,而其他包内的类则不可以访问。

需要说明的是,同一个包指的是同一个直接包,子包下的类并不能访问,比如说,类shuo.laoma.Hello和shuo.laoma.inner.Test,其所在的包shuo.laoma和shuo.laoma.inner是两个完全独立的包,并没有逻辑上的联系,Hello类和Test类不能互相访问对方的包可见性方法和属性。

另外,需要说明的是protected修饰符,protected可见性包括包可见性,也就是说,声明为protected,不仅表明子类可以访问,还表明同一个包内的其他类可以访问,即使这些类不是子类也可以。

总结来说,可见性范围从小到大是:

private < 默认(包) < protected < public

jar包

为方便使用第三方代码,也为了方便我们写的代码给其他人使用,各种程序语言大多有打包的概念,打包的一般不是源代码,而是编译后的代码,打包将多个编译后的文件打包为一个文件,方便其他程序调用。

在Java中,编译后的一个或多个包的Java class文件可以打包为一个文件,Java中打包命令为jar,打包后的文件后缀为.jar,一般称之为jar包。

可以使用如下方式打包,首先到编译后的java class文件根目录,然后运行如下命令打包:

jar -cvf <包名>.jar <最上层包名>

比如,对前面介绍的类打包,如果Hello.class位于E:\bin\shuo\laoma\Hello.class,则可以到目录 E:\bin下,然后运行:

jar -cvf hello.jar shuo

hello.jar就是jar包,jar包其实就是一个压缩文件,可以使用解压缩工具打开。

Java类库、第三方类库都是以jar包形式提供的。如何使用jar包呢?将其加入到类路径(classpath)中即可。类路径是什么呢?

程序的编译与连接

从Java源代码到运行的程序,有编译和连接两个步骤。编译是将源代码文件变成一种字节码,后缀是.class的文件,这个工作一般是由javac这个命令完成的。连接是在运行时动态执行的,.class文件不能直接运行,运行的是Java虚拟机,虚拟机听起来比较抽象,执行的就是java这个命令,这个命令解析.class文件,转换为机器能识别的二进制代码,然后运行,所谓连接就是根据引用到的类加载相应的字节码并执行。

Java编译和运行时,都需要以参数指定一个classpath,即类路径。类路径可以有多个,对于直接的class文件,路径是class文件的根目录,对于jar包,路径是jar包的完整名称(包括路径和jar包名),在Windows系统中,多个路径用分号;分隔,在其他系统中,以冒号:分隔。

在Java源代码编译时,Java编译器会确定引用的每个类的完全限定名,确定的方式是根据import语句和classpath。如果import的是完全限定类名,则可以直接比较并确定。如果是模糊导入(import带.*),则根据classpath找对应父包,再在父包下寻找是否有对应的类。如果多个模糊导入的包下都有同样的类名,则Java会提示编译错误,此时应该明确指定import哪个类。

Java运行时,会根据类的完全限定名寻找并加载类,寻找的方式就是在类路径中寻找,如果是class文件的根目录,则直接查看是否有对应的子目录及文件,如果是jar文件,则首先在内存中解压文件,然后再查看是否有对应的类。

总结来说,import是编译时概念,用于确定完全限定名,在运行时,只根据完全限定名寻找并加载类,编译和运行时都依赖类路径,类路径中的jar文件会被解压缩用于寻找和加载类。

小结

本节介绍了Java中代码组织的机制,包和jar包,以及程序的编译和连接。将类和接口放在合适的具有层次结构的包内,避免命名冲突,代码可以更为清晰,便于实现封装和模块化开发,通过jar包使用第三方代码,将自身代码打包为jar包供其他程序使用,这些都是解决复杂问题所必需的。

我们一直在说,程序主要就是对数据的操作,为表示和操作数据,我们介绍了基本类型,类以及接口,下节,我们介绍Java中表示和操作一种特殊数据的机制 - 枚举。

----------------

未完待续,查看最新文章,敬请关注微信公众号“老马说编程”(扫描下方二维码),从入门到高级,深入浅出,老马和你一起探索Java编程及计算机技术的本质。用心写作,原创文章,保留所有版权。

-----------

更多相关原创文章

计算机程序的思维逻辑 (21) - 内部类的本质

计算机程序的思维逻辑 (20) - 为什么要有抽象类?

计算机程序的思维逻辑 (19) - 接口的本质

计算机程序的思维逻辑 (18) - 为什么说继承是把双刃剑

计算机程序的思维逻辑 (17) - 继承实现的基本原理

计算机程序的思维逻辑 (16) - 继承的细节

计算机程序的思维逻辑 (15) - 初识继承和多态

计算机程序的思维逻辑 (14) - 类的组合

计算机程序的思维逻辑 (13) - 类

时间: 2024-12-26 16:40:54

计算机程序的思维逻辑 (22) - 代码的组织机制的相关文章

计算机程序的思维逻辑 (21) - 内部类的本质

内部类 之前我们所说的类都对应于一个独立的Java源文件,但一个类还可以放在另一个类的内部,称之为内部类,相对而言,包含它的类称之为外部类. 为什么要放到别的类内部呢?一般而言,内部类与包含它的外部类有比较密切的关系,而与其他类关系不大,定义在类内部,可以实现对外部完全隐藏,可以有更好的封装性,代码实现上也往往更为简洁. 不过,内部类只是Java编译器的概念,对于Java虚拟机而言,它是不知道内部类这回事的, 每个内部类最后都会被编译为一个独立的类,生成一个独立的字节码文件. 也就是说,每个内部

计算机程序的思维逻辑 (23) - 枚举的本质

前面系列,我们介绍了Java中表示和操作数据的基本数据类型.类和接口,本节探讨Java中的枚举类型. 所谓枚举,是一种特殊的数据,它的取值是有限的,可以枚举出来的,比如说一年就是有四季.一周有七天,虽然使用类也可以处理这种数据,但枚举类型更为简洁.安全和方便. 下面我们就来介绍枚举的使用,同时介绍其实现原理. 基础 基本用法 定义和使用基本的枚举是比较简单的,我们来看个例子,为表示衣服的尺寸,我们定义一个枚举类型Size,包括三个尺寸,小/中/大,代码如下: public enum Size {

计算机程序的思维逻辑 (29) - 剖析String

上节介绍了单个字符的封装类Character,本节介绍字符串类.字符串操作大概是计算机程序中最常见的操作了,Java中表示字符串的类是String,本节就来详细介绍String. 字符串的基本使用是比较简单直接的,我们来看下. 基本用法 可以通过常量定义String变量 String name = "老马说编程"; 也可以通过new创建String String name = new String("老马说编程"); String可以直接使用+和+=运算符,如: S

计算机程序的思维逻辑 (28) - 剖析包装类 (下)

本节探讨Character类,它的基本用法我们在包装类第一节已经介绍了,本节不再赘述.Character类除了封装了一个char外,还有什么可介绍的呢?它有很多静态方法,封装了Unicode字符级别的各种操作,是Java文本处理的基础,注意不是char级别,Unicode字符并不等同于char,本节详细介绍这些方法以及相关的Unicode知识. 在介绍这些方法之前,我们需要回顾一下字符在Java中的表示方法,我们在第六节.第七节.第八节介绍过编码.Unicode.char等知识,我们先简要回顾一

计算机程序的思维逻辑 (25) - 异常 (下)

上节我们介绍了异常的基本概念和异常类,本节我们进一步介绍对异常的处理,我们先来看Java语言对异常处理的支持,然后探讨在实际中到底应该如何处理异常. 异常处理 catch匹配 上节简单介绍了使用try/catch捕获异常,其中catch只有一条,其实,catch还可以有多条,每条对应一个异常类型,比如说: try{ //可能触发异常的代码 }catch(NumberFormatException e){ System.out.println("not valid number"); }

计算机程序的思维逻辑 (40) - 剖析HashMap

前面两节介绍了ArrayList和LinkedList,它们的一个共同特点是,查找元素的效率都比较低,都需要逐个进行比较,本节介绍HashMap,它的查找效率则要高的多,HashMap是什么?怎么用?是如何实现的?本节详细介绍. 字面上看,HashMap由两个单词组成,Hash和Map,这里Map不是地图的意思,而是表示映射关系,是一个接口,实现Map接口有多种方式,HashMap实现的方式利用了Hash. 下面,我们先来看Map接口,接着看如何使用HashMap,然后看实现原理,最后我们总结分

计算机程序的思维逻辑 (13) - 类【转】

类 上节我们介绍了函数调用的基本原理,本节和接下来几节,我们探索类的世界. 程序主要就是数据以及对数据的操作,为方便理解和操作,高级语言使用数据类型这个概念,不同的数据类型有不同的特征和操作,Java定义了八种基本数据类型,其中,四种整形byte/short/int/long,两种浮点类型float/double,一种真假类型boolean,一种字符类型char,其他类型的数据都用类这个概念表达. 前两节我们暂时将类看做函数的容器,在某些情况下,类也确实基本上只是函数的容器,但类更多表示的是自定

计算机程序的思维逻辑 (14) - 类的组合【转】

正所谓,道生一,一生二,二生三,三生万物,如果将二进制表示和运算看做一,将基本数据类型看做二,基本数据类型形成的类看做三,那么,类的组合以及下节介绍的继承则使得三生万物. 上节我们通过类Point介绍了类的一些基本概念和语法,类Point中只有基本数据类型,但类中的成员变量的类型也可以是别的类,通过类的组合可以表达更为复杂的概念. 程序是用来解决现实问题的,将现实中的概念映射为程序中的概念,是初学编程过程中的一步跨越.本节通过一些例子来演示,如何将一些现实概念和问题,通过类以及类的组合来表示和处

计算机程序的思维逻辑 (56) - 文件概述

我们在日常电脑操作中,接触和处理最多的,除了上网,大概就是各种各样的文件了,从本节开始,我们就来探讨文件处理,本节主要介绍文件有关的一些基本概念和常识,Java中处理文件的基本思路和类结构,以及接来下章节的安排思路. 基本概念和常识 二进制思维 为了透彻理解文件,我们首先要有一个二进制思维.所有文件,不论是可执行文件.图片文件.视频文件.Word文件.压缩文件.txt文件,都没什么可神秘的,它们都是以0和1的二进制形式保存的.我们所看到的图片.视频.文本,都是应用程序对这些二进制的解析结果. 作