------- android培训、java培训、期待与您交流! ----------
大家在实际写代码过程中,会通过编译形成很多字节码文件(“.class”文件),如果同名字节码文件存在于同一个文件夹下,新文件将会覆盖旧文件,而这一特性可能会造成一些数据的丢失。通常,在windows操作系统下通过文件夹来管理文件。而Java语言中的“包(package)”正是起到了文件的作用。
1. 包的作用
1) 对类文件进行分类管理。比如A、B、C类文件是为某个程序服务的,就将A、B、C类文件保存在同一个包(文件夹)内,这样做不仅方便查找,而且便于管理,不会造成混乱。
2) 通过包名来区分同名类,给类提供多层命名空间。如果出现了前述定义两个同名但不同功能的类文件的情况,并且需要在同一段代码中使用,这时就可以将两个类文件分别保存在两个包中,并通过包名对两个类进行区分。此外,需要注意的是,定义了包名以后,类名的全称就变为了:包名.类名。
3) 将字节码文件和源代码文件进行分离。如果没有定义包,字节码文件和源代码文件就将默认的存储在一个目录下。包机制的出现,将字节码文件集中保存于包中,在查阅源代码和调用类文件的过程中不会出现混乱。
2. 包的定义
1) 格式
定义包需要用到关键字“package”,格式如下:
package 包名;
2) 规则
(a) 定义包名的语句一定要定义在java源代码的第一行。
(b) 包名的所有字母都要小写。
(c) 可以建立多层包目录,格式为:
package 包名1.包名2.包名3;
我们用下面的代码作为示例:
代码1:
//定义包名
package pack;
class PackageDemo
{
public static void main(String[] args)
{
System.out.println("HelloWorld!");
}
}
3. 编译时的注意事项
我们以代码1为例,说明几个错误现象。
问题一:当我们在java源代码文件中定义了包,并对该源代码进行编译时,如果在控制台中输入“javacPackageDemo.java”命令进行编译,然后输入“java PackageDemo”命令运行,会报出异常——类名错误。这是因为定义了包名以后,类名的全称是“包名.类名”,而不仅是类名了。
问题二:针对问题一我们在运行时输入“java pack.PackageDemo”命令,强调PackageDemo类的所属,但依旧会报出错误提示——未能找到PackageDemo类,这是因为我们在编译的时候没有创建“pack”包,当然也就无法在“pack”目录下找到PackageDemo类了。
问题三:针对问题二,正确的编译命令应是:
javac –d 包所在路径 字节码文件
其中“-d”是javac命令的参数,表示执行包所在路径。其中,包所在路径如果是当前目录(源代码文件所在目录,下同)就可以使用“.”代替,方便书写。对应到代码1命令就变为:“javac–d . PackageDemo.java”,这样就会在当前目录下创建pack包(文件夹),并在该包内创建PackageDemo字节码文件。最后通过运行命令“java pack.PackageDemo”运行该字节码文件。当然,包所在路径也可以指定为其他任意路径,但要注意运行前要重新设置classpath为指定路径,否则提示当前目录下没有指定包。
小知识点1:
如果没有定义包的话,编译器就将源代码(“.java”文件)所在目录——当前目录作为默认的包进行编译。
以上是包的基本使用方法,可以说包也是一种封装形式,它是将多个类封装到包内。那么这也再次印证了:封装不等同与私有。
4. 包与包之间的访问
1) 不同包之间的访问
我们通过下面的例子进行说明。
代码2:
package pack1;
/*
从外部包pack访问本包pack1的类
需将该类定义为公有
*/
public class Super
{
//从外部包只能访问到公有类的公有成员
public void f()
{
System.out.println("Superf() run");
}
}
代码3:
package pack;
class PackageDemo
{
public static void main(String[] args)
{
//书写完整的类名,否则编译失败:找不到指定类
pack1.Super s = new pack1.Super ();
s.f();
}
}
代码2和代码3中的两个类Super和PackageDemo分别定义在两个源代码文件中—Super.java和PackageDemo.java中。先后编译Super.java和PackageDemo.java文件,然后执行PackageDemo类,打印结果为:
Super f() run
本例着重需要注意的是:
1) 包的存在,使类名发生了变化,完整的类名是:包名.类名。这也是前面已经强调过的。
2) 如果在编译时指定了当前目录以外的路径,那么首先需要设置classpath为指定目录,否则编译失败,提示在当前目录下找不到指定包。
3) PackageDemo类定义在了pack包,而Super类定义在了pack1包内,不同包内类互相访问,就需要将被访问的类(上例中的Super类)的访问权限定义为公有,否则提示从外部软件包无法访问指定类,编译失败,并且也只能调用到该类的公有方法。
2) 不同包之间类的继承
我们保留代码2的Super类,在新的包pack2中定义Super的子类Sub,并对代码3中的PackageDemo类做一定的修改。
代码4:
package pack1;
public class Super
{
public void f()
{
System.out.println("Superf() run");
}
}
代码5:
package pack2;
//不同包之间的类之间可以建立继承关系,注意书写完整的类名
public class Sub extends pack1.Super
{
public void m()
{
System.out.println("Subm() run");
//调用父类的方法f()
f();
}
}
代码6:
package pack;
class PackageDemo2
{
public static void main(String[] args)
{
//创建子类对象,调用子类m()方法
pack2.Sub sub= new pack2.Sub();
sub.m();
System.out.println();
//创建父类对象,调用父类f()方法
pack1.Super sup = new pack1.Super();
sup.f();
}
运行结果为:
Sub m() run
Super f() run
Super f() run
3) protected访问权限
protected访问权限的作用:不同包内的类之间形成继承关系时,子类可以访问父类中被protected访问权限修饰的成员(可以被复写,也可以被子类对象调用),相仿,如果是包外其他类只能访问public成员。以上述Super、Sub和PackageDemo2类为例,假如Super类的f()方法被protected类修饰,那么该方法只能被Sub类访问到,而不能像代码6中那样被PackageDemo2类访问。如果我们对代码4中的Super类进行修改,将f()方法的访问权限修改为protected,如代码7所示,
代码7:
package pack1;
public class Super
{
//将f()方法的访问权限修改为protected
protected void f()
{
System.out.println("Superf() run");
}
}
然后,编译PackageDemo2.java时编译失败,提示为:
PackageDemo2.java:14: 错误: f()可以在Super中访问protected
这一提示也就体现了protected的作用。如果此时,将代码6中创建父类对象并调用其f()方法的语句注释掉,是可以通过编译并运行的,大家可以自行尝试。
protected的意思是“被保护的”,该访问权限是继public、默认(包访问权限)和private权限以外的第四个可以用于修饰成员的权限修饰符(也是最后一个)。
小知识点2:
至此我们学习了所有四种成员访问修饰符,他们分别是:public、protected、默认(包访问权限)和private。
我们把这四种访问权限的访问范围总结成下表,供大家参考:
|
不同包 |
子类(可以不同包) |
同一个包内 |
同一个类内 |
public |
yes |
yes |
yes |
yes |
protected |
no |
yes |
yes |
yes |
默认 |
no |
no |
yes |
yes |
private |
no |
no |
no |
yes |
小知识点3:
一个Java源代码文件中(.java文件)只能有一个公有类,并且该类的类名必须与文件名相同。
5. import——导入
前面说到可以通过类似“packagepack1.pack2.pack3;”这样的命令创建多层包目录。然而,当我们需要从pack1包外访问pack3子包内的类时,可能会遇到这样一个问题:假设我们要访问pack3子包内的Demo类,创建该类的对象,并调用它的方法。由于此时Demo类的全称是“pack1.pack2.pack3.Demo”,这就会造成在书写代码上极大的不便,阅读性也非常差。为了解决这个问题,我们在此引入新的关键字——import,意思是“导入”。
1) 类导入格式
我们就以上述Demo类为例,格式如下:
importpack1.pack2.pack3.Demo;
通过这一语句将Demo类“导入”以后,在下面的代码中访问Demo类就无需再在类名前添加包名了,而且阅读性也比较好,可谓一劳永逸。
注意:
(a) 如果需要导入两个包中的同名类,还是要在类名前添加包名以示区分,否则编译失败。
(b) 导包语句一定要定义在包定义语句之后,类定义代码之前。
2) 多个类的导入——通配符
如果在pack3包内有多个类,并且在本代码内都需要对其进行访问和调用,这时我们没有必要一一导入,而是使用通配符——“*”,使用格式如下:
importpack1.pack2.pack3.*;
“*”的作用是:导入某个包内所有的类。
注意:
(a) “*”仅仅作用于包中的类,如果某个包内还有子包是无法通过通配符导入的。
(b) 在实际开发中不建议使用通配符,因为,如果仅仅为了使用某个包内的某一个类就将其余类全部导入,是比较浪费内存空间的。最好的习惯还是用哪个导入哪个类。
3) 包名的定义习惯
前面说到为了区分不同包内的同名类,需要添加包名以示区分,而如果包名相同的话,就彻底无法区分了,因此通常在实际开发中,习惯使用倒写域名进行包名的定义,比如黑马程序员官网网址为:www.itheima.com,以此定义包名就是:
packagecom.itheima.xxx;
xxx代表存储某一类类文件的包。由于域名是不能重复的,因此,这样定义就杜绝了重复包名的出现。Java标准类库实际也是这么做的,比如“java.lang.*”。