java静态导入
在我以前的Java 101教程中,您学习了如何通过将引用类型(也称为类和接口)声明为其他引用类型和块的成员来更好地组织代码 。 我还向您展示了如何使用嵌套来避免嵌套引用类型和共享相同名称的顶级引用类型之间的名称冲突。
Java与嵌套一起使用包来解决顶级引用类型中的同名问题。 使用静态导入还可以简化对打包的顶级引用类型中的静态成员的访问。 静态导入将在您访问代码中的这些成员时为您节省击键,但是使用它们时需要注意一些事项。 在本教程中,我将向您介绍在Java程序中使用包和静态导入的方法。
由Jeff Friesen为JavaWorld创建。
包装参考类型
Java开发人员将相关的类和接口分组到包中。 使用包可以更轻松地查找和使用引用类型,避免同名类型之间的名称冲突,并控制对类型的访问。
在本节中,您将了解软件包。 您将了解什么是软件包,了解package
和import
语句,并探索受保护访问,JAR文件和类型搜索的其他主题。
什么是Java包?
在软件开发中,我们通常根据项目的层次关系来组织项目。 例如,在上一教程中,我向您展示了如何将类声明为其他类的成员。 我们还可以使用文件系统将目录嵌套在其他目录中。
使用这些层次结构将帮助您避免名称冲突。 例如,在非分层文件系统(单个目录)中,不可能为多个文件分配相同的名称。 相反,分层文件系统使同名文件存在于不同目录中。 同样,两个封闭类可以包含同名的嵌套类。 名称冲突不存在,因为项目被划分为不同的命名空间。
Java还允许我们将顶级(非嵌套)引用类型划分为多个命名空间,以便我们可以更好地组织这些类型并防止名称冲突。 在Java中,我们使用包语言功能将顶级引用类型划分为多个命名空间。 在这种情况下, 包是用于存储引用类型的唯一名称空间。 软件包可以存储类和接口以及子软件包,这些子软件包是嵌套在其他软件包中的软件包。
包具有名称,该名称必须是非保留标识符。 例如, java
。 成员访问运算符( .
)将包名与子包名分开,并将包或子包名与类型名分开。 例如, java.lang.System
的两个成员访问运算符将包名java
和lang
子包名分开,并将子包名lang
和System
类型名分开。
必须将引用类型声明为public
以便可以从其包外部进行访问。 这同样适用于必须可访问的任何常量,构造函数,方法或嵌套类型。 您将在本教程的后面部分看到这些示例。
包装说明
在Java中,我们使用package语句创建一个包。 该语句出现在源文件的顶部,并标识源文件类型所属的软件包。 它必须符合以下语法:
package identifier [. identifier ]*;
package identifier [. identifier ]*;
包语句以保留字package
开头,并以标识符开头,该标识符后面还可以有一个句点分隔的标识符序列(可选)。 分号( ;
)终止此语句。
第一个(最左侧)标识符为包命名,每个后续标识符为子包命名。 例如,在package ab;
,所有类型的源文件中声明属于b
的分装的a
包。
包/子包的命名约定
按照约定,我们用小写形式表示包或子包的名称。 当名称包含多个单词时,除第一个单词外,您可能希望将每个单词都大写; 例如, generalLedger
。
软件包名称的序列必须唯一,以避免编译问题。 例如,假设您创建了两个不同的graphics
包,并假定每个graphics
包都包含一个具有不同接口的Triangle
类。 当Java编译器遇到类似下面的内容时,它需要验证Triangle(int, int, int, int)
构造函数是否存在:
Triangle
t = new Triangle(1, 20, 30, 40);
Triangle
t = new Triangle(1, 20, 30, 40);
三角形边框
将Triangle
构造函数视为指定在其中绘制三角形的边界框。 前两个参数标识框的左上角,后两个参数定义框的范围。
编译器将搜索所有可访问的程序包,直到找到包含Triangle
类的graphics
程序包。 如果找到的包包括带有Triangle(int, int, int, int)
构造函数的相应Triangle
类,则一切正常。 否则,如果找到的Triangle
类没有Triangle(int, int, int, int)
构造函数,则编译器将报告错误。 (在本教程后面的内容中,我将详细介绍搜索算法。)
此方案说明选择唯一的程序包名称序列的重要性。 选择唯一名称序列的惯例是反转您的Internet域名,并将其用作序列的前缀。 例如,我将选择ca.javajeff
作为我的前缀,因为javajeff.ca
是我的域名。 然后,我将指定ca.javajeff.graphics.Triangle
来访问Triangle
。
域名组件和有效的软件包名称
域名组件并非始终是有效的程序包名称。 一个或多个组件名称可能以数字( 3D.com
) 3D.com
,包含连字符( -
)或另一个非法字符( ab-z.com
),或者是Java的保留字之一( short.com
)。 习惯上还是会把你前面加上下划线(数字com._3D
),替换用下划线(非法字符com.ab_z
),并用下划线(后缀保留字com.short_
)。
您需要遵循一些规则,以避免package语句出现其他问题:
- 您只能在源文件中声明一个package语句。
- 您不能在package语句之前加上注释。
之所以存在第一条规则是第二条规则的特例,是因为将引用类型存储在多个包中没有意义。 尽管一个包可以存储多个类型,但是一个类型只能属于一个包。
当源文件未声明package语句时,源文件的类型被称为未命名package 。 非平凡的引用类型通常存储在它们自己的包中,并避免使用未命名的包。
Java实现将程序包和子程序包名称映射到同名目录。 例如,一种实现会将graphics
映射到名为graphics
的目录。 在包装的情况下ab
,第一个字母 ,将映射到一个指定的目录a
和b将映射到一个b
的子目录a
。 编译器将实现包类型的类文件存储在相应目录中。 请注意,未命名的程序包对应于当前目录。
示例:使用Java打包音频库
一个实际的示例有助于完全掌握package
语句。 在本节中,我将在音频库的上下文中演示软件包,使您可以读取音频文件并获取音频数据。 为简便起见,我仅介绍该库的骨架版本。
音频库当前仅包含两个类: Audio
和WavReader
。 Audio
描述了一个音频剪辑,并且是图书馆的主要课程。 清单1给出了其源代码。
清单1.打包语句示例(Audio.java)
package ca.javajeff.audio;
public final class Audio
{
private int[] samples;
private int sampleRate;
Audio(int[] samples, int sampleRate)
{
this.samples = samples;
this.sampleRate = sampleRate;
}
public int[] getSamples()
{
return samples;
}
public int getSampleRate()
{
return sampleRate;
}
public static Audio newAudio(String filename)
{
if (filename.toLowerCase().endsWith(".wav"))
return WavReader.read(filename);
else
return null; // unsupported format
}
}
package ca.javajeff.audio;
public final class Audio
{
private int[] samples;
private int sampleRate;
Audio(int[] samples, int sampleRate)
{
this.samples = samples;
this.sampleRate = sampleRate;
}
public int[] getSamples()
{
return samples;
}
public int getSampleRate()
{
return sampleRate;
}
public static Audio newAudio(String filename)
{
if (filename.toLowerCase().endsWith(".wav"))
return WavReader.read(filename);
else
return null; // unsupported format
}
}
让我们逐步完成清单1。
- 清单1中的
Audio.java
文件存储Audio
类。 此清单以package语句开头,该语句将ca.javajeff.audio
标识为该类的包。 -
Audio
被声明为public
Audio
以便可以从其程序包外部引用它。 另外,它被声明为final
,因此不能扩展(意味着,子类化)。 -
Audio
声明private
samples
和sampleRate
字段以存储音频数据。 这些字段被初始化为传递给Audio
的构造函数的值。 -
Audio
的构造函数声明为package-private (意味着,构造函数未声明为public
,private
或protected
),因此无法从其包外部实例化此类。 -
Audio
提供了getSamples()
和getSampleRate()
方法,用于返回音频剪辑的样本和采样率。 每个方法都声明为public
方法,因此可以从Audio
的程序包外部调用它。 -
Audio
以public
和static
newAudio()
工厂方法结束,该方法用于返回与filename
参数相对应的Audio
对象。 如果无法获得音频剪辑,则返回null
。 -
newAudio()
将filename
的扩展名与.wav
进行比较(此示例仅支持WAV音频)。 如果它们匹配,则执行return WavReader.read(filename)
以返回带有基于WAV的音频数据的Audio
对象。
清单2描述了WavReader
。
清单2. WavReader帮助器类(WavReader.java)
package ca.javajeff.audio;
final class WavReader
{
static Audio read(String filename)
{
// Read the contents of filename's file and process it
// into an array of sample values and a sample rate
// value. If the file cannot be read, return null. For
// brevity (and because I've yet to discuss Java's
// file I/O APIs), I present only skeletal code that
// always returns an Audio object with default values.
return new Audio(new int[0], 0);
}
}
package ca.javajeff.audio;
final class WavReader
{
static Audio read(String filename)
{
// Read the contents of filename's file and process it
// into an array of sample values and a sample rate
// value. If the file cannot be read, return null. For
// brevity (and because I've yet to discuss Java's
// file I/O APIs), I present only skeletal code that
// always returns an Audio object with default values.
return new Audio(new int[0], 0);
}
}
WavReader
旨在将WAV文件的内容读取到Audio
对象中。 (该类最终将带有更多的private
字段和方法,从而变得WavReader
。)请注意,该类未声明为public
,这使WavReader
可以被Audio
访问,但不能在ca.javajeff.audio
包之外进行编码。 可以将WavReader
视为一个辅助类,其存在的唯一原因是为Audio
提供服务。
完成以下步骤来构建此库:
- 在文件系统中选择一个合适的位置作为当前目录。
- 在当前目录中创建一个
ca/javajeff/audio
子目录层次结构。 - 将清单1和2分别复制到文件
Audio.java
和WavReader.java
; 并将这些文件存储在audio
子目录中。 - 假设当前目录包含
ca
子目录,请执行javac ca/javajeff/audio/*.java
来编译ca/javajeff/audio
的两个源文件。 如果一切顺利,您应该在audio
子目录中发现Audio.class
和WavReader.class
文件。 (或者,对于本示例,您可以切换到audio
子目录并执行javac *.java
。)
现在,您已经创建了音频库,您将需要使用它。 很快,我们将看一个演示该库的小型Java应用程序。 首先,您需要了解import语句。
Java的导入语句
想象一下,必须为源代码中每次出现的Triangle
重复指定ca.javajeff.graphics.Triangle
。 Java提供了import语句,作为省略冗长的软件包详细信息的便捷选择。
import语句通过告诉编译器在编译过程中在何处查找不合格 (没有包前缀)的类型名称,从而从包中导入类型。 它显示在源文件顶部附近,并且必须符合以下语法:
import identifier [. identifier ]*.( typeName | *);
import identifier [. identifier ]*.( typeName | *);
导入语句以保留字import
开始,并以标识符开头,该标识符后面可以有句点分隔的标识符序列(可选)。 类型名称或星号( *
)后跟,并以分号终止此语句。
该语法揭示了import语句的两种形式。 首先,您可以导入单个类型名称,该类型名称通过typeName
标识。 其次,您可以导入所有通过星号标识的类型。
*
符号是一个通配符,代表所有不合格的类型名称。 它告诉编译器在import语句的程序包序列的最右边的程序包中查找此类名称,除非在先前搜索的程序包中找到类型名称。 请注意,使用通配符不会降低性能或导致代码膨胀。 但是,这可能导致名称冲突,您将看到。
例如, import ca.javajeff.graphics.Triangle;
告诉编译器ca.javajeff.graphics
软件包中存在不合格的Triangle
类。 同样,类似
import
ca.javajeff.graphics.*;
import
ca.javajeff.graphics.*;
告诉编译器在遇到Triangle
名称, Circle
名称甚至是Account
名称(如果尚未找到Account
)时,在此程序包中查找。
避免在多开发人员项目中使用*
在从事多开发人员项目时,请避免使用*
通配符,以便其他开发人员可以轻松查看源代码中使用了哪些类型。
翻译自: https://www.infoworld.com/article/3543349/packages-and-static-imports-in-java.html
java静态导入