为什么要使用“package”关键字和.h文件?

11
  1. 为什么在Java包中的文件里要写上“package”这个东西?难道如果它在目录里,就不间接地假定它已经在包里了吗?

  2. 我来自C++的世界。我总是从使用该类的其他文件中导入需要的类的.h文件(我的意思是,我只想“展示”头文件,而不是实现)。但是现在我对Java中的导入有点困惑。Java中如何实现这一点?

4个回答

7
  1. 不,这并不是默认假设。毕竟,我的包叫什么?com.mypackage.stuff?src.com.mypackage.stuff?myproject.com.mypackage.stuff?C.Users.makakko.workspace.myproject.src.com.mypackage.stuff?

    如果你只根据文件夹来确定包的位置,那么它是相对于驱动器根目录的吗?如果项目在另一台机器上的不同驱动器上开发,会怎样呢?它是相对于javac.exe的位置吗?再说了,如果安装目录不同呢?当运行javac时,工作目录是什么?但是你可以指定一个位置让javac来查找你的源文件。如果你想要编写一个简单的测试程序或者教一个从未编程过的人Java,你必须使用/解释整个包结构的概念吗?

    如果你省略package说明符,那么你仍然处于一个包中。它只是“默认包”,没有名称。

  2. 头文件更多地是C编译方式所需的产物,而不是实现信息隐藏的方法。在C中,必须先定义一个方法,然后才能引用它。如果你想要有几个相互引用的方法,你必须在使用任何一个方法之前定义所有方法,因此需要头文件。C++中的头文件继承了这一点,但是C++中的更改改变了头文件的必要性。

    在Java中,编译器将在执行任何需要方法/类的操作之前查看所有方法和类的签名。头文件的作用被放入了编译器本身。你不能依赖头文件来实现信息隐藏,因为

    1. 代码可以放在头文件中

    2. 除非你使用真正的信息隐藏(如单独的库),否则程序员可以找到与头文件匹配的c/cpp文件而没有问题

    同样,在Java中,只有通过删除源代码才能获得真正的信息隐藏。一旦你使源代码不可访问,你就会公开公共/受保护的类、枚举和接口的API。为了加分,为每个东西编写解释性的JavaDoc注释,并运行javadoc.exe对你的源代码进行分离的文档生成,以供使用你的包的人使用。


据我所知,包名总是多余的。只需尝试将 com.mypackage.stuff 更改为任何其他名称,并在不更改 CLASSPATH 的情况下编译即可。 - maaartinus

5

1)package声明必须与项目中的目录层次结构匹配。

如果我在Car.java中使用package com.stackoverflow.bakkal;,则应该遵循以下层次结构。

com/
|-- stackoverflow/
|   `-- bakkal/
|       |-- Car.java

2) 如果你想要隐藏一个实现,可以在Java中使用interface代替class。然后将实际的实现分发在例如.class文件或JARs中。

嗯,但是接口无法被实例化......

接口在某种程度上类似于C++中的原型。你有合同,然后实际的实现来自其他地方。

我想要实例化一个类,但是不给出实现,只给出原型

即使在C++中也不可能做到这一点,你如何实例化一个没有实际实现的东西呢?在C++中,你仍然需要链接到对象文件。在Java中,你使用.class文件。


1
1)我的问题是为什么它不被假定呢?:S 2)但是接口无法被实例化... 我想要实例化一个类,但是不给出具体实现,只有原型。 - makakko
只要.java文件可用,实现就始终可用。如果你想隐藏实现,只需提供用户/程序员/ET .class文件(或包含.class的.jar文件)。但一定要确保包含文档说明。 - Brian S
谢谢Brian,我明白了。另外,.class文件能以“好的”方式反转吗?我的意思是,能否恢复原始的“名称”变量/函数?因为我猜Java虚拟机不处理类成员或函数的“偏移量”... - makakko
1
@makkako:一个类文件可以被反编译。这并不意味着结果一定与原始源代码相同,因为反编译器必须根据字节码猜测原始代码。但这类似于检查已编译的C++程序的机器指令以猜测原始的C++代码。在Java中稍微容易一些,但这不是你应该浪费时间(而且确实是浪费时间)去担心的事情。 - Brian S
1
@makakko:Java反编译器可以恢复类和函数名称,但无法恢复局部变量的名称。 - dan04

3

Java的哲学是“显式优于隐式/假定”,因此不会假定包。它确实使你能够访问当前包中的任何内容,但需要明确导入任何外部内容。(我相信Java.lang是一个例外,因为它包含了很多基本功能,例如String,没有一个单独的包不会使用它)。

这也是为什么你倾向于看到:

import java.util.ArrayList;
import java.util.LinkedList;

改为:

import java.util.*;

这似乎很烦人,直到有一天你试着理解别人的代码时,你意识到如果隐藏或隐含某些内容,这将会更加困难。
如果你使用Eclipse、Netbeans或IntelliJ,你甚至不会注意到这一点,因为有两个功能。
首先,如果你在输入类名称时按下ctrl-space,它不仅会为你完成类名称,还会自动将其添加到导入列表中。
其次,如果你发现导入存在“错误”,或者你不使用ctrl-space扩展功能,可以键入ctrl-shift-o (eclipse)来进行“修复导入”。这将自动导入需要导入的内容,并删除你不再需要的导入。根据你的设置,它还会展开或收起*。
一旦你掌握了这个技巧,你就再也不用考虑导入的问题了。

1

该包指定了一个类的路径。它必须与磁盘或jar(zip)中的目录相匹配。位置是相对于类路径上的位置。受保护的访问仅限于同一包中的类。

.h文件中可能会做一些在类定义中完成的事情。常量属于一个类,可以公开可见。在.h中,常量必须公开可见。

导入与包含一个.h的效果相同,但帮助处理同名定义冲突的问题。您可以包含一个项目或从一个类中导入所有可见项目。也可以跳过导入,直接使用包名作为前缀来访问所需类。

通过导入不能真正看到实现(至少不超出编译后的类提供的范围)。可见的是可见接口的公共方法和数据。对于来自同一包的导入,未指定访问权限(public/protected/private)的方法和数据也是可见的。受保护的方法和变量对该类的子类可见。.h文件可以在不提供源代码或目标文件的情况下使用。导入要求提供指定的类。(一个类可能只由常量组成,尽管这样设计是不好的。)


protected 访问修饰符表示子类可以访问,但同一包中的其他类不能访问。没有指定任何访问修饰符(public/protected/private)时,使用的是包访问权限(也称为“默认访问权限”)。 - Brian S

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接