一、包的概念

包内包含一组类,它们被组织在一个单独的命名空间(namespace)下。

例如,标准 Java 发布中有一个工具库,它被组织在 java.util 命名空间下。java.util 中含有一个类,叫做 ArrayList。使用 ArrayList 的一种方式是用其全名 java.util.ArrayList:

// hiding/FullQualification.javapublic class FullQualification {public static void main(String[] args) {java.util.ArrayList list = new java.util.ArrayList();}
}

这种方式使得程序冗长乏味,因此你可以换一种方式,使用 import 关键字。如果需要导入某个类,就需要在 import 语句中声明:

// hiding/SingleImport.java
import java.util.ArrayList;public class SingleImport {public static void main(String[] args) {ArrayList list = new ArrayList();}
}

现在你就可以不加限定词,直接使用 ArrayList 了。但是对于 java.util 包下的其他类,你还是不能用。要导入其中所有的类,需要使用 * ,就像本书中其他示例那样:

import java.util.*

之所以使用导入,是为了提供一种管理命名空间的机制。所有类名之间都是相互隔离的。类 A 中的方法 f() 不会与类 B 中具有相同签名的方法 f() 冲突。但是如果类名冲突呢?假设你创建了一个 Stack 类,打算安装在一台已经有别人所写的 Stack 类的机器上,该怎么办呢?这种类名的潜在冲突,正是我们需要在 Java 中对命名空间进行完全控制的原因。为了解决冲突,我们需要为每个类创建一个唯一标识符组合。

一个 Java 源代码文件称为一个编译单元(compilation unit)(有时也称翻译单元(translation unit))。每个编译单元的文件名后缀必须是 .java。在编译单元中可以有一个 public 类,它的类名必须与文件名相同(包括大小写,但不包括后缀名 .java)。每个编译单元中只能有一个 public 类,否则编译器不接受。如果这个编译单元中还有其他类,那么在包之外是无法访问到这些类的,因为它们不是 public 类,此时它们为主 public 类提供“支持”类 。

1.1 Java代码的组织形式

当编译一个 .java 文件时,.java 文件的每个类都会有一个输出文件。每个输出的文件名和 .java 文件中每个类的类名相同,只是后缀名是 .class。因此,在编译少量的 .java 文件后,会得到大量的 .class 文件。在 Java 中,可运行程序是一组 .class 文件,它们可以打包压缩成一个 Java 文档文件(JAR,使用 jar 文档生成器)。Java 解释器负责查找、加载和解释这些文件。

类库是一组类文件。每个源文件通常都含有一个 public 类和任意数量的非 public 类,因此每个文件都有一个 public 组件。如果把这些组件集中在一起,就需要使用关键字 package。

如果你使用了 package 语句,它必须是文件中除了注释之外的第一行代码。当你如下这样写:

package hiding;

意味着这个编译单元是一个名为 hiding 类库的一部分。换句话说,你正在声明的编译单元中的 public 类名称位于名为 hiding 的保护伞下。任何人想要使用该名称,必须指明完整的类名或者使用 import 关键字导入 hiding 。(注意,Java 包名按惯例一律小写,即使中间的单词也需要小写,与驼峰命名不同)

例如,假设文件名是 MyClass.java ,这意味着文件中只能有一个 public 类,且类名必须是 MyClass(大小写也与文件名相同):

// hiding/mypackage/MyClass.java
package hiding.mypackage;public class MyClass {// ...
}

现在,如果有人想使用 MyClass 或 hiding.mypackage 中的其他 public 类,就必须使用关键字 import 来使 hiding.mypackage 中的名称可用。还有一种选择是使用完整的名称:

// hiding/QualifiedMyClass.javapublic class QualifiedMyClass {public static void main(String[] args) {hiding.mypackage.MyClass m = new hiding.mypackage.MyClass();}
}

关键字 import 使之更简洁:

// hiding/ImportedMyClass.java
import hiding.mypackage.*;public class ImportedMyClass {public static void main(String[] args) {MyClass m = new MyClass();}
}    

package 和 import 这两个关键字将单一的全局命名空间分隔开,从而避免名称冲突。

1.2 创建独一无二的包名

注意,一个包从未真正被打包成单一的文件,它是由很多 .class 文件构成,为了让整个文件结构更整洁,一种合乎逻辑的做法是将特定包下的所有 .class 文件都放在一个目录下。这是 Java 解决Java代码组织问题的一种方式;稍后你还会在我们介绍 jar 工具时看到另一种方式。

将所有的文件放在一个子目录还解决了其他的两个问题:创建独一无二的包名和查找可能隐藏于目录结构某处的类。这是通过将 .class 文件所在的路径位置编码成 package 名称来实现的。按照惯例,package 名称是类的创建者的的 Internet 域名的反序。因为 Internet 域名是独一无二的,所以你的 package 名称也应该是独一无二的,不会发生名称冲突。如果你没有自己的域名,你就得构造一组不大可能与他人重复的组合(比如你的姓名),来创建独一无二的 package 名称。

当Java解释器要加载一个.class文件时,它需要能定位到这个.class文件所在的位置,首先,它找出环境变量 CLASSPATH(通过操作系统设置,有时也能通过 Java 的安装程序或基于 Java 的工具设置)。CLASSPATH 包含一个或多个目录,用作查找 .class 文件的根目录。从根目录开始,Java 解释器获取包名并将每个句点替换成反斜杠,生成一个基于根目录的路径名(取决于你的操作系统,包名 foo.bar.baz 变成 foo\bar\baz 或 foo/bar/baz 或其它)。然后这个路径与 CLASSPATH 的不同项连接,解释器就在这些目录中查找与你所创建的类名称相关的 .class 文件(解释器还会查找某些涉及 Java 解释器所在位置的标准目录)。

为了理解这点,比如说我的域名 MindviewInc.com,将之反转并全部改为小写后就是 com.mindviewinc,这将作为我创建的类的独一无二的全局名称。(com、edu、org等扩展名之前在 Java 包中都是大写,但是 Java 2 之后都统一用小写。)我决定再创建一个名为 simple 的类库,从而可以得到这样一个包:

package com.mindviewinc.simple;

这个包名可以用作下面两个创建在simple包下的文件的命名空间:

// com/mindviewinc/simple/Vector.java
// Creating a package
package com.mindviewinc.simple;public class Vector {public Vector() {System.out.println("com.mindviewinc.simple.Vector");}
}
// com/mindviewinc/simple/List.java
// Creating a package
package com.mindviewinc.simple;public class List {System.out.println("com.mindview.simple.List");
}

这两个文件都位于我机器上的子目录中,如下:

C:\DOC\Java\com\mindviewinc\simple

注意每个文件的第一行注释都指明了文件在源代码目录树中的位置,如果你回头看这个路径,会看到包名 com.mindviewinc.simple,但是路径的第一部分C:\DOC\Java呢?CLASSPATH 环境变量会处理它。我机器上的环境变量部分如下:

CLASSPATH=.;D:\JAVA\LIB;C:\DOC\Java

可以看到CLASSPATH包含了C:\DOC\Java,并且CLASSPATH还可以包含多个不同的搜索路径。

但是在使用 JAR 文件时,有点不一样。你必须在类路径写清楚 JAR 文件的实际名称,不能仅仅是 JAR 文件所在的目录。因此,对于一个名为 grape.jar 的 JAR 文件,类路径应包括:

CLASSPATH=.;D\JAVA\LIB;C:\flavors\grape.jar

设置好类路径后,可以进行如下的测试:

// hiding/LibTest.java
// Uses the library
import com.mindviewinc.simple.*;public class LibTest {public static void main(String[] args) {Vector v = new Vector();List l = new List();}
}

输出:

com.mindviewinc.simple.Vector
com.mindviewinc.simple.List

当编译器遇到导入 simple 库的 import 语句时,它首先会在 CLASSPATH 指定的目录中查找子目录 com/mindviewinc/simple,然后从已编译的文件中找出名称相符者(对 Vector 而言是 Vector.class,对 List 而言是 List.class)。注意,这两个类和其中要访问的方法都必须是 public 修饰的。

1.3 命名相同导致的冲突

当我们通过 * 导入了两个包含相同名字类名的类库,就会导致潜在的冲突,比如下面的例子:

import com.mindviewinc.simple.*;
import java.util.*;

两个类库下都包含有Vector类,如果我们不创建Vector类,就不会出现冲突,但是假如我们现在要创建一个Vector类,冲突就会出现:

Vector v = new Vector();

因为这里的Vector类,编译器无法判定是属于哪个类库的,所以需要我们强制指明,对于标准的Java类中的Vector,我们可以这样写:

java.util.Vector v = new java.util.Vector();

这种写法完全指明了 Vector 类的位置(配合 CLASSPATH),这样就避免了冲突,或者导入单个类以防冲突——只要不在同一个程序中使用有冲突的名字(若使用了有冲突的名字,必须明确指明全名)。

1.4 定制自己的工具库

一般来说,我们可以使用反转后的域名来命名要创建的工具包(库),比如 com.mindviewinc.util ,但为了简化描述,这里将工具包命名为 onjava。

比如下面的程序:

// onjava/Range.java
// Array creation methods that can be used without
// qualifiers, using static imports:
package onjava;public class Range {// Produce a sequence [0,n)public static int[] range(int n) {int[] result = new int[n];for (int i = 0; i < n; i++) {result[i] = i;}return result;}// Produce a sequence [start..end)public static int[] range(int start, int end) {int sz = end - start;int[] result = new int[sz];for (int i = 0; i < sz; i++) {result[i] = start + i;}return result;}// Produce sequence [start..end) incrementing by steppublic static int[] range(int start, int end, int step) {int sz = (end - start) / step;int[] result = new int[sz];for (int i = 0; i < sz; i++) {result[i] = start + (i * step);}return result;}
}

这个文件的位置一定是在某个以一个 CLASSPATH 位置开始,然后接着是 onjava 的目录下。编译完之后,就可以在系统的任何地方使用 import onjava 语句来使用这些方法了。

二、访问权限修饰符

Java 访问权限修饰符 public,protected 和 private 位于定义的类名,属性名和方法名之前。每个访问权限修饰符只能控制它所修饰的对象。如果不提供访问修饰符,就意味着"包访问权限"。所以无论如何,都存在着某种形式的访问控制权。

2.1 包访问权限

当我们没有使用修饰符时,我们所定义的类/属性/方法就是默认访问权限,(default access)。默认访问权限没有关键字,通常被称为包访问权限(package access)(有时也称为 friendly)。这意味着当前包中的所有其他类都可以访问那个成员。对于这个包之外的类,这个成员就被看作 private 的。由于一个编译单元(即一个文件)只能隶属于一个包,所以通过包访问权限,位于同一编译单元中的所有类彼此之间都是可访问的。

包访问权限可以把相关类聚到一个包下,以便它们能轻易地相互访问。包里面的类赋予了拥有包访问权限的成员之间相互访问的能力,这就相当于我们"拥有”了同一个包内的其他程序代码。构建包访问权限机制是将类聚集在包中的重要原因之一。在许多语言中,在文件中组织定义的方式是任意的,但是在 Java 中我们被强制以一种合理的方式组织它们。另外,我们可以将不应该对当前包中的类具有访问权限的类排除在包外。

类同样控制着哪些代码有权访问自己的成员,取得对成员的访问权的方式是:

  • 使成员成为 public。那么无论是谁,无论在哪,都可以访问它。
  • 赋予成员默认包访问权限,不用加任何访问修饰符,然后将其他类放在相同的包内。这样,其他类就可以访问该成员。
  • 在"复用"这一章你将看到,继承的类既可以访问 public 成员,也可以访问 protected 成员(但不能访问 private 成员)。但只有当两个类处于同一个包内,它才可以访问包访问权限的成员。
  • 提供访问器(accessor)和修改器(mutator)方法(有时也称为"get/set" 方法),从而读取和改变值。

2.2 public

当我们使用了关键字 public,就意味着紧随 public 后声明的成员对于每个人都是可用的。假设定义了一个包含下面编译单元的 dessert 包:

// hiding/dessert/Cookie.java
// Creates a library
package hiding.dessert;public class Cookie {public Cookie() {System.out.println("Cookie constructor");}void bite() {System.out.println("bite");}
}

记住,Cookie.java 文件产生的类文件必须位于名为 dessert 的子目录中,该子目录在 hiding下,hiding必须在 CLASSPATH 的几个目录之下。不要错误地认为 Java 总是会将当前目录视作查找行为的起点之一。如果你的 CLASSPATH 中没有 . 那么Java 就不会查找当前目录。

现在,使用 Cookie 创建一个程序:

// hiding/Dinner.java
// Uses the library
import hiding.dessert.*;public class Dinner {public static void main(String[] args) {Cookie x = new Cookie();// -x.bite(); // Can't access}
}

输出:

Cookie constructor

因为Cookie类以及构造器都是 public 的,所以我们可以创建一个额Cookie对象。但是,在 Dinner.java 中无法访问到 Cookie 对象中的 bite() 方法,因为 bite() 只提供了包访问权限,因而在 dessert 包之外无法访问,编译器禁止我们使用它。

2.3 默认包

看下如下的代码例子,这个代码是可以编译运行的:

// hiding/Cake.java
// Accesses a class in a separate compilation unit
class Cake {public static void main(String[] args) {Pie x = new Pie();x.f();}
}

输出:

Pie.f()

同一目录下的第二个文件:

// hiding/Pie.java
// The other class
class Pie {void f() {System.out.println("Pie.f()");}
}

最初看上去这两个文件毫不相关,但在 Cake 中可以创建一个 Pie 对象并调用它的 f() 方法(注意,你的 CLASSPATH 中一定得有 . 这样文件才能编译)。Cake.java 可以访问它们是因为它们在相同的目录中且没有在代码第一行给自己设定明确的包名。Java 把这样的文件看作是隶属于该目录的默认包中,因此它们为该目录中所有的其他文件都提供了包访问权限。

2.4 private

关键字 private 意味着除了包含该成员的类,其他任何类都无法访问这个成员。同一包中的其他类无法访问 private 成员,这实现了某种程度的隔离。在代码编写过程中,有可能出现多人合作创建一个包的情况,这时使用 private,我们可以自由地修改那个被修饰的成员,无需担心会影响同一包下的其他类。

默认的包访问权限通常提供了足够的隐藏措施;记住,使用类的客户端程序员无法访问包访问权限成员。这样做很好,因为默认访问权限是一种我们常用的权限(同时也是一种在忘记添加任何访问权限时自动得到的权限)。因此,通常考虑的是把哪些成员声明成 public 供客户端程序员使用。所以,最初不常使用关键字 private,因为程序没有它也可以照常工作。然而,使用 private 是非常重要的,尤其是在多线程环境中。(在"并发编程"一章中将看到)。

以下是一个使用 private 的例子:

// hiding/IceCream.java
// Demonstrates "private" keywordclass Sundae {private Sundae() {}static Sundae makeASundae() {return new Sundae();}
}public class IceCream {public static void main(String[] args) {//- Sundae x = new Sundae();Sundae x = Sundae.makeASundae();}
}

以上展示了 private 的用武之地:控制如何创建对象,防止别人直接访问某个特定的构造器(或全部构造器)。例子中,你无法通过构造器创建一个 Sundae 对象,而必须调用 makeASundae() 方法创建对象。

任何可以肯定只是该类的"助手"方法,都可以声明为 private,以确保不会在包中的其他地方误用它,也防止了你会去改变或删除它。将方法声明为 private 确保了你拥有这种选择权。

对于类中的 private 属性也是一样。除非必须公开底层实现(这种情况很少见),否则就将属性声明为 private。然而,不能因为类中某个对象的引用是 private,就认为其他对象也无法拥有该对象的 public 引用(参见附录:对象传递和返回)。

2.5 protected

要理解 protected 的访问权限,我们在内容上需要作一点跳跃。首先,在介绍本书"复用"章节前,你不必真正理解本节的内容。但为了内容的完整性,这里作了简要介绍,举了个使用 protected 的例子。

关键字 protected 处理的是继承的概念,通过继承可以利用一个现有的类——我们称之为基类,然后添加新成员到现有类中而不必碰基类。我们还可以在新类中改变基类的某些成员的行为。为了从一个类中继承,需要声明新类 extends 一个现有类,像这样:

class Foo extends Bar {}

如果我们创建了一个新包,并从另一个包中继承类,那么唯一能访问的就是被继承类的 public 成员。(如果在同一个包中继承,就可以操作所有的包访问权限的成员。)有时,基类的创建者会希望某个特定成员能被继承类访问,但不能被其他类访问。这时就可以使用 protected。注意protected 也提供包访问权限,也就是说,相同包内的其他类可以访问 protected 元素。

回顾下2.2节介绍的Cookie,如下定义的类不能调用拥有包访问权限的方法bite()

// hiding/ChocolateChip.java
// Can't use package-access member from another package
import hiding.dessert.*;public class ChocolateChip extends Cookie {public ChocolateChip() {System.out.println("ChocolateChip constructor");} public void chomp() {//- bite(); // Can't access bite}public static void main(String[] args) {ChocolateChip x = new ChocolateChip();x.chomp();}
}

输出:

Cookie constructor
ChocolateChip constructor

如果类 Cookie 中存在一个方法 bite(),那么它的任何子类中都存在 bite() 方法。但是因为 bite() 具有包访问权限并且位于另一个包中,所以在我们子类所在的包中无法使用它。你可以把它声明为 public,但这样一来每个人都能访问它,这可能也不是你想要的。如果你将 Cookie 改成如下这样:

// hiding/cookie2/Cookie.java
package hiding.cookie2;public class Cookie {public Cookie() {System.out.println("Cookie constructor");}protected void bite() {System.out.println("bite");}
}

这样,bite()对于所有继承Cookie的类,都是可访问的:

// hiding/ChocolateChip2.java
import hiding.cookie2.*;public class ChocolateChip2 extends Cookie {public ChocoalteChip2() {System.out.println("ChocolateChip2 constructor");}public void chomp() {bite(); // Protected method}public static void main(String[] args) {ChocolateChip2 x = new ChocolateChip2();x.chomp();}
}

输出:

Cookie constructor
ChocolateChip2 constructor
bite

2.6 包访问权限&Public构造器

当我们定义一个具有包访问权限的类时,可以在类中定义一个 public 构造器,编译器不会报错:

// hiding/packageaccess/PublicConstructor.java
package hiding.packageaccess;class PublicConstructor {public PublicConstructor() {}
}

有一个 Checkstyle 工具,你可以运行命令 gradlew hiding:checkstyleMain 使用它,它会指出这种写法是虚假的,而且从技术上来说是错误的。实际上你不能从包外访问到这个 public 构造器:

// hiding/CreatePackageAccessObject.java
// {WillNotCompile}
import hiding.packageaccess.*;public class CreatePackageAcessObject {public static void main(String[] args) {new PublicConstructor();}
}

如果你编译下这个类,会得到编译错误信息:

CreatePackageAccessObject.java:6:error:
PublicConstructor is not public in hiding.packageaccess;
cannot be accessed from outside package
new PublicConstructor();
^
1 error

因此,在一个具有包访问权限的类中定义一个 public 的构造器并不能真的使这个构造器成为 public,在声明的时候就应该标记为编译时错误。

7.1 封装(Java包(package)的概念+访问权限修饰符(public/private/protected/default))相关推荐

  1. java 权限修饰符大小_Java中访问权限修饰符public protected private, 缺省默认权限的用法总结...

    1.访问权限符: (1)public: 对于成员来说:任何其他类都可以访问它们,不管在同一个包中还是在另外的包中. 对于类来说: 也是一样. (2)friendly: 对于成员老说:如果一个类的成员没 ...

  2. Java——类成员的访问权限修饰符(private、protected、public、default)

    本文转载自:https://blog.csdn.net/qq_41420688/article/details/83348546 1. 作用 封装将数据和操作连接起来.封装的一个重要属性:访问控制. ...

  3. 2020.1.30 封装和隐藏(四种访问权限修饰符)、this、JavaBean、继承、方法的重写

    面对对象特征之一:封装和隐藏 Java中通过数据声明为私有的(private),再提供公共的(public)方法:getXxx()和setXxx()实现对属性的操作. 四种访问权限修饰符 在同一个ja ...

  4. java子承父业(继承,访问权限修饰符,super,重写,final)

    1.继承 :  子承父业 目的 : 提高代码的复用性         作用: 子类一旦继承父类,有权使用父类中的成员,可以在子类中定义子类独有的内容         定义:   子类  extends ...

  5. Java中四个访问修饰符public private protected 和默认(package-private)的用法详解

    访问级别 访问级别修饰符确定其他类是否可以使用特定字段或调用特定方法.有两个级别的访问控制: 1.在顶级 public或package-private(没有显式修饰符即默认权限). 类可以用修饰符pu ...

  6. java四种权限修饰符 (private 、default(缺省)、prote、public)适用范围

    验证测试 (1)定义一个order 类 ,其中包涵四种权限修饰符属性 以及方法 private int orderPrivate; int orderDefault; protected int or ...

  7. Java访问修饰符public,private,protected,以及不写(默认)时的区别?

    修饰符 当前类 同 包 子 类 其他包 public √ √ √ √ protected √ √ √ × default √ √ × × private √ × × × 类的成员不写访问修饰时默认为d ...

  8. java 的构造函数修饰符public private protected

    java中使用new关键字创建对象的时候,构造方法上的修饰符起什么作用? 1,限定可以创建对象的位置 使用public 修饰,new对象时,可以在任何包下的任何类中. 使用protected修饰,只能 ...

  9. Java 修饰符 public/private/protected/不写的区别

    修饰符 本类 同包 子孙类 其他包 public ✅ ✅ ✅ ✅ protected ✅ ✅ ✅ ❌ 默认 ✅ ✅ ❌ ❌ private ✅ ❌ ❌ ❌ public:public表明该数据成员.成 ...

  10. JAVASE零基础入门——package、封装、继承、访问权限修饰符、super、重写、Javabean 和 final 关键字

    目录 一.Package 二.封装 三.继承 四.访问权限修饰符 五.super 关键字 六.重写 6.1 重写 与 重载 之间的区别 : 6.2 重写的实现条件: 6.3 重写的需求 : 6.4 调 ...

最新文章

  1. 为什么你“越努力,越焦虑”?背后原因,99%的人都忽略了……
  2. TF:tensorflow框架中常用函数介绍—tf.Variable()和tf.get_variable()用法及其区别
  3. 无法获取签名信息,请上传有效包(110506)
  4. Eric6最简单的应用(创建一个项目-窗体-编译-运行)
  5. Idea代码统计工具
  6. html表格行数代码,HTML表格可选行Javascript包
  7. js2D物理引擎插件
  8. java操作seaweedfs
  9. 矢量、梯度——数学、物理不可混谈
  10. 邮箱如何设置smtp服务器端口,如何改变你的SMTP端口来允许发送电子邮件
  11. 主成分分析和因子分析的理论与速成应用丨R语言和SPSS比较案例
  12. js 剩余时间,天,小时,分钟,秒
  13. activemMQ启动失败 无法访问管理界面
  14. android读sdcard大文件系统,Android中使用SDcard读取文件
  15. CAD如何创建图层并绘制图形
  16. 打印机服务器虚拟端口,打印机服务器虚拟端口设置
  17. 轻量级开源聊天解决方案喧喧发布 1.0 版本
  18. XSS Phishing - 新式跨站脚本攻击方式
  19. PHP学习:PHP+Apache 安装/配置
  20. telnet 不是内部或外部命令,也不是可运行的程序解决方案:Windows 安装Telnet客户端

热门文章

  1. ajax的data数据无意间的致命错误
  2. php页面怎么改造mip,WordPress MIP 改造之 a 标签替换为 mip-link 跳转链接
  3. Spring读书笔记(一)
  4. Glide4.0源码全解析(一),GlideAPP和.with()方法背后的故事
  5. python中布尔值是什么意思_python中的布尔值是什么
  6. layui表格边框_layui怎么固定表格的表头
  7. 运算放大器权威指南_运算放大器科普文章
  8. vue点击按钮打开下拉菜单_vue 点击弹出下拉菜单 点击其他页面收回菜单
  9. android sqlitelog,如何解决Sqlitelog(13)语句中止在PhoneGap中的68错误android
  10. 学生选课系统代码-1start.py代码