包内含有一组类,它们在单一的名字空间下被组织在了一起。例如,在Java的标准发布中有一个工具库,它被组织在java.util名字空间下,如果要使用此名字空间下的ArrayList类,可以使用其命名的方式,如下:
public static void main(String[] args) {
java.util.List stringList = new java.util.ArrayList<>();
}
但这样就把程序变得的冗余太多了,为了简化代码则可以使用import关键字来导入某个包下的类,使用import的方式,如下:
import java.util.ArrayList;
import java.util.List;
public class ImportPackageDemo {
void example1(){
List stringList = new ArrayList<>();
}
}
如果要导入该包下的很多类,则可以使用通配符*来代替类名,即:import java.util.*
当编写一个Java源代码文件时,此文件通常被称为编译单元,每一个编译单元都必须要有一个后缀名.java,而在编译单元内则可以有一个public类,该类的名称必须与文件的名称相同(不包括后缀),每一个编译单元只能有一个public类,否则编译器就不会接受。如果该编译单元之中还有其他类的话,那么你只能在包内访问它,在包之外的世界是看不到这些类的,因此它们不是public类,它们只是为public类提供支持的一些类。
当编译一个.java文件时,在.java文件中的每个类都会有一个输出文件,而该输出文件的名称与.java文件中的每个类的文件相同,只是多了一个后缀名.class,因此,在编译少量.java文件之后,会出现大量的.class文件。Java可运行程序是一组可以打包并压缩为一个Java文档文件的.class文件。Java解释器负责这些文件的查找、装载和解释。
类库实际上是一组类文件,其中每个文件都有一个public类,以及任意数量的非public类。因此,每个文件都有一个构件,如果希望这些构件从属于同一个群组或者单元,就可以使用关键字package。如果使用package语句,它必须是文件中除注释以外的第一句程序代码。示例如下:
package com.thinkinjava.chapter6;
public class ImportPackageDemo {
}
使用package和import的目的,是为了将单一的全局名字空间分割开来,以免很多一起编写程序时出现名称冲突问题。按照惯例,package名称的第一部分是类的创建者的反顺序的Internet域名,然后是项目名或模块名以及其他标识符,按照这样的惯例,你就可以创建独一无二的包名了。
Java解释器的运行过程是:首先,找出环境变量CLASSPATH,CLASSPATH包含一个或多个目录,用作查找.class文件的根目录。从根目录开始,解释器获取包的名称并将每个句点替换成反斜杠,以从CLASSPATH中产生一个路径名称(如com.mengfei.test就变成了com\mengfei\test或com/mengfei/test)。得到的路径会与CLASSPATH中的名个不同的项相连接,解释器就在这些目录中查找与你所要创建的类名称相关的.class文件。
下面示例CLASSPATH的使用方法。
首先,在E盘创建一个Java目录,然后在Java目录下创建一个Demo.java文件,文件中的源代码如下:
public class Demo
{
public static void main(String[] args){
System.out.println("Hello World");
}
}
使用cmd命令,打开DOS窗口进入到Java目录, 使用javac Demo.java进行编译,然后再使用java Demo运行class文件,如下:
我们没有设置CLASSPATH,但是运行成功,这是因为CLASSPATH默认为当前路径,如果我们到E盘的根目录下执行相同的命令则会报:找不到或无法加载主类的错误,如下:
这时如果我们想在E盘的根目录下也可以运行Demo.class文件则需要设置CLASSPATH,有三种方式,示例如下:
① 设置CLASSPATH临时变量,作用只限于当前DOS命令窗口,窗口关闭失效
② 在使用命令时添加CLASSPATH参数,作用只限于当前运行class文件的命令
③ 在系统环境变量中加入CLASSPATH,永久有效
设置地址:我的电脑 → 属性 → 高级系统设置 → 环境变量 → 新建,然后输入变量名和变量值
我们先在类文件中添加一行打包的代码,即:
package test;
然后在DOS窗口执行javac -d Demo.java命令,注意:使用了package进行编译时,要在命令中加上-d .参数,目的是告诉编译器要在当前目录下自动生成与包同名的文件夹,并把类文件放到该文件夹下面。否则JVM无法在包文件夹下找到要加载的类文件。示例如下:
如果这时设置CLASSPATH并在根目录下执行相同的命令,结果是一样的,如下:
public、protected、private这几个权限访问修饰词是置于类中每个成员定义之前的,每个访问权限修饰词仅控制它所修饰的特定定义的访问权。如果不提供任何访问权限修饰词,则意味着它是包访问权限。如下图所示:
访问权限的控制通常被称为是具体实现的隐藏,把数据和方法包装进类中,以及具体实现的隐藏,一起被称为封装。其结果是一个同时带有特征和行为的数据类型。访问权限控制将权限的边界划在了数据类型的内部,主要原因有两点:
① 是要设定客户端程序员可以使用和不可以使用的界限。开发者可以在结构中建立自己的内部机制,而不必担心客户端程序员会偶然地将内部机制当作是他们可以使用的接口的一部分;
② 将接口和具体实现进行分离。客户端程序员除了可以向接口发送消息之外什么也不可以做,这样就可以更改所有不是public的东西,而不会破坏客户端代码。
对于类的权限只可使用包访问权限(即默认)或者public(内部类除外,它是一个特例),如果不希望其他人对该类拥有访问权限,则可以把所有的构造器都指定为private,从而阻止其他人创建该类的对象,但此时你依然可以在静态成员方法创建该类的对象,然后你可以决定是否将该方法置为public。
示例一:设置私有的构造方法,用静态成员方法返回类的实例,如下
public class VisitPermission {
public static void main(String[] args) {
Demo1 demo1 = Demo1.getInstance();
demo1.printSomething();
//打印结果:Hello Demo1
}
}
class Demo1{
private Demo1(){}
public static Demo1 getInstance(){
return new Demo1();
}
public void printSomething(){
System.out.println("Hello Demo1");
}
}
示例二:设置私有构造方法,用静态数据成员初始化类的实例,实现饿汉式单例模式,如下:
public class VisitPermission {
public static void main(String[] args) {
Demo2 demo2 = Demo2.getInstance();
demo2.printSomething();
//打印结果:Hello Demo2
}
}
class Demo2{
private Demo2(){}
private static Demo2 demo2 = new Demo2();
//由于静态数据成员只会初始化一次,所以这个方法返回的是类的单例,也就是说这个类永远只有一个实例
public static Demo2 getInstance(){
return demo2;
}
public void printSomething(){
System.out.println("Hello Demo2");
}
}