Java 包
文章目录
意义
-
为了更好地组织类,Java提供了包机制,用于区别类名的命名空间
-
包语句的语法格式
package pkg1[.pkg2[.pkg3…]];
-
// 例如,Something.java 文件 package net.java.util; public class Something{} // 路径是 net/java/util/Something.java
-
-
包(package)可以定义为一组相互联系的类型
-
类、接口、枚举和注释
-
为这些类型提供访问保护和命名空间管理的功能
-
// 部分系统包 java.lang // 打包基础的类 java.io // 含输入输出功能的函数
-
-
开发者可以自定义把一组类和接口等打包,并定义自己的包
- 在实际开发中是值得提倡的
- 完成类的实现之后,将相关的类分组,可以让他人更容易地确定哪些类、接口、枚举和注释等是相关的
-
包创建了新的命名空间,所以不会跟其他包中的任何名字产生命名冲突
- 使用包这种机制,更容易实现访问控制,并且让定位相关类更加简单
创建
- 创建包时要取一个合适的名字
- 若其他的源文件包含了该包提供的类、接口、枚举或者注释类型时,都必须将这个包的声明放在这个源文件的开头(导包)
- 包声明应该在源文件的第一行,每个源文件只能有一个包声明,文件中的每个类型都应用于它
- 若源文件没有使用包声明,其中的类,函数,枚举,注释等将被放在一个无名的包(unnamed package)
/* 文件名: Animal.java */
package animals; // 包声明
interface Animal { // 在 animals 包中定义接口 Animal
public void eat();
public void travel();
}
// 在同一个包中加入该接口的实现
package animals;
public class MammalInt implements Animal{ // 在 animals 包中定义 MammalInt 类实现接口 Animal
public void eat(){
System.out.println("Mammal eats");
}
public void travel(){
System.out.println("Mammal travels");
}
public static void main(String args[]){
MammalInt m = new MammalInt();
m.eat();
m.travel();
}
}
// 编译两个文件,它们在同一个命名为animals的子目录中
运行结果:
Mammal eats
Mammal travels
作用
使用包(package)机制
- 防止命名冲突
- 进行访问控制
- 提供搜索和定位 类、接口、枚举 和 注释 等
实现方式
- 功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用
- 采用了树形目录的存储方式;同一个包中的类名字是不同的
- 不同的包中的类的名字是可以相同的
- 同时调用两个不同包中相同类名的类时,应该加上包名加以区别
- 因此包可以避免名字冲突
- 限定了访问权限,拥有包访问权限的类才能访问某个包中的类
import
-
为了能够使用某一个包的成员,需要在 Java 程序中明确导入该包
- 使用
import
语句可完成此功能 import
语句应位于package
语句之后,所有类的定义之前- 可以有 0 或 多个
import package1[.package2…].(classname|*); // 语法格式
- 使用
-
在同一个的类想使用另一个类,该包名可以省略
-
例如:
payroll
包已包含Employee
类- 在
payroll
包中定义Boss
类 Boss
类引用Employee
类的时无需导入payroll
包
package payroll; public class Boss{ public void payEmployee(Employee e){ // 同一个包下直接使用无需导包 e.mailCheck(); } }
- 在
-
-
不在一个包中
-
引用其他包的类需要使用类全名描述
-
用
import
关键字引入- 使用类全限定名
- 或使用通配符
*
引入包中所有类:
import payroll.*; // 通配符引入包中所有类 import payroll.Employee; // 或明确引入 Employee 类(类全限定名) // 注意:类文件中可以包含任意数量的 import 声明;但import 声明必须在包声明之后,类声明之前
-
目录结构
类放在包中会有两种主要的结果
-
包名成为类名的一部分
-
包名必须与相应的字节码所在的目录结构相吻合
-
// 包式管理 java 中文件的一种简单方式 // 类、接口等类型的源码放在一个文本中,文本名就是源码中文件名,并以.java作为扩展名 // 文件名 : Car.java package vehicle; public class Car { } ....\vehicle\Car.java // 把源文件放在一个目录中,目录要对应类所在包的名字 // 正确的类名和路径如下 类名 -> vehicle.Car 路径名 -> vehicle\Car.java (在 windows 系统中)
-
-
通常公司使用其互联网域名的颠倒形式来作为包名
-
例如:互联网域名是
runoob.com
-
所有的包名都以
com.runoob
开头 -
包名中的每一个部分对应一个子目录
// com.runoob.test 包中有 Runoob.java 源文件,则文件路径为 ....\com\runoob\test\Runoob.java
-
-
-
// 文件名: Runoob.java package com.runoob.test; public class Runoob {} class Google {} // 编译后的文件路径 .\com\runoob\test\Runoob.class; .\com\runoob\test\Google.class; // 通配符导入所有 \com\runoob\test\ 中定义的类、接口等 import com.runoob.test.*;
-
编译时,编译器为包中定义的每个类、接口等类型各创建一个不同的输出文件
- 输出文件的名字就是这个类型的名字,并加上 .class 作为扩展后缀
-
编译后的
.class
文件应该和.java
源文件一样-
放置的目录应该和包的名字对应
-
但并不要求
.class
文件和.java
的路径一样 -
可以分开安排源码和类的目录
-
<path-one>\sources\com\runoob\test\Runoob.java <path-two>\classes\com\runoob\test\Google.class
- 将类目录分享给他人不用透露源码
- 这种方法管理源码和类文件可以让编译器和java 虚拟机(JVM)可以找到程序中使用的所有类型
-
-
-
-
class path
:类目录的绝对路径-
系统变量
CLASSPATH
中编译器和JVM
将 包名追加到class path
之后构造.class
文件的路径<path- two>\classes
:类目录绝对路径class path
- 包名:
com.runoob.test
- 编译器和 JVM 会在
<path-two>\classes\com\runoob\test
中找.class
文件
-
class path
可多个路径,多路径应用分隔符分开- 默认情况下,编译器和 JVM 查找当前目录
JAR
文件包含Java
平台相关的类,所以默认放在了class path
中
-
类编译与调试
带包(创建及引用)的l类的编译与调试;使用 dos 命令
- 命令的参数是
类的全限定名
,而不是 文件名 - 打包编译时,会自动创建包目录,不需手动创建包名文件夹
- 当前目录有多个 java 文件需要编译或打包编译
javac -d .\*.java
指令:当前目录下的所有 java 文件根据程序中是否有包声明进行编译或打包编译
- 类路径不在当前目录下时
- 用到
java -cp ...
指令指定类路径 - 如:
java -cp F:/javaweb2班/20160531mypack1.java
- 用到
- 清楚 java 虚拟机根据包声明包导入执行字节码文件的流程。
CLASSPATH
使用 dos 窗口
显示
显示当前 CLASSPATH
-
Windows 平台(DOS 命令行)
-
C:\> set CLASSPATH
-
-
UNIX 平台(Bourne shell )
-
#echo $CLASSPATH
-
删除
删除当前 CLASSPATH
-
Windows 平台(DOS 命令行)
-
C:\> set CLASSPATH=
-
-
UNIX 平台(Bourne shell)
-
#unset CLASSPATH; export CLASSPATH
-
设置
设置CLASSPATH变量
-
Windows 平台(DOS 命令行)
-
C:\> set CLASSPATH=C:\users\jack\java\classes
-
-
UNIX 平台(Bourne shell)
-
#CLASSPATH=/home/jack/java/classes; export CLASSPATH
-
Object 类
定义
-
Java 中
Object
类是所有类的父类- 所有类都继承了 Object,子类可以使用 Object 的所有方法
-
Object
类位于java.lang
包,编译时会自动导入- 定义类时如果没有明确继承,会自动继承
Object
,成为Object
的子类
// 显式继承 public class Runoob extends Object{} // 隐式继承 public class Runoob {} // 两种方式都是继承了 Object
- 定义类时如果没有明确继承,会自动继承
构造函数
Object():构造一个新对象
类的方法
方法列表
方法 | 描述 |
---|---|
protected Object clone() | 创建并返回一个对象的拷贝 |
boolean equals(Object obj) | 比较两个对象是否相等 |
protected void finalize() | 当 GC (垃圾回收器)确定不存在对该对象的有更多引用时,由对象的垃圾回收器调用此方法 |
Class getClass() | 获取对象的运行时对象的类 |
int hashCode() | 获取对象的 hash 值 |
void notify() | 唤醒在该对象上等待的某个线程(随机唤醒) |
void notifyAll() | 唤醒在该对象上等待的所有线程 |
String toString() | 返回对象的字符串表示形式 |
void wait() | 当前线程进入等待状态;直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法 |
void wait(long timeout) | 让当前线程处于等待(阻塞)状态,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过参数设置的timeout 超时时间 |
void wait(long timeout, int nanos) | 与 wait(long timeout) 方法类似,多了一个 nanos 参数,参数表示额外时间(以纳秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 纳秒 |
clone()
注意事项
-
对象克隆:对象的复制(全新复制)
- 使用已有对象内容创建新的对象
-
通过
Object
类的clone()
方法实现protected Object clone() throws CloneNotSuppertedException;
- 所有类都继承
Object
类,即所有类都有clone()
方法 - 接收对象必须强转
-
实现克隆需要对象所在类实现
Cloneable
接口Object
类本身没有实现Cloneable
接口- 不重写
clone()
方法直接进行调用会发生CloneNotSupportedException
异常
- 不重写
- 该接口没有任何方法提供,描述的是一种能力
- 接口可以定义标准 或 描述一种能力的标识;作为标识接口
-
使用规则
-
如果覆盖了非final类中的clone方法,则应该返回一个通过调用
super.clone()
而得到的对象 -
例如
class A implements Cloneable{ // 实现 Cloneable 接口表明此类可以克隆 public A clone() { return new A(); // 直接调用构造器 } } class B extends A{ public B clone() { try{ return (B) super.clone(); // 得到错误对象,super.clon() 返回 A 的对象 }catch (CloneNotSupportedException e){ throw new AssertionError(); } } } // 正确重写 clone() public A clone(){ return (A)super.clone(); // 调用父类 clone() 方法,强转为本类类型 }
-
浅克隆
-
成员变量是值类型,复制一份给克隆对象
-
成员变量是引用类型,则将对象的地址复制一份给克隆对象
- 即原型对象和克隆对象的成员变量指向相同的内存地址
-
浅克隆当对象被复制时只复制它本身和其中包含的值类型的成员变量
- 引用类型的成员对象并没有复制
-
可以重写 clone() 方法,直接赋值引用等实现
深克隆
-
成员变量是值类型或引用类型,都复制一份给克隆对象
- 深克隆将对象本身和其所包含的所有成员变量都复制
- 深克隆将对象本身和其所包含的所有成员变量都复制
-
实现方法
-
重写
clone()
方法-
原型对象的引用类型属性成员变量类也需要实现
Cloneable
接口对clone()
方法进行重写 -
在原型对象的
clone()
方法中同时克隆引用类型成员变量public class User implements Cloneable{ // 原型类型 private String userName; // 值类型 private HavingDinner havingDinner; // 引用类型对象 // 其他 set、get 方法略 public void setHavingDinner(HavingDinner havingDinner) { this.havingDinner = havingDinner; } @Override protected Object clone() throws CloneNotSupportedException{ // 重写 clone 方法 User user = (User)super.clone(); // 进行浅克隆 user.setHavingDinner((HavingDinner)getHavingDinner().clone()); // 对引用类型成员变量克隆,实现深克隆 return user; } } public class HavingDinner implements Cloneable{ // 引用类型成员变量的类 private String foodName; @Override protected Object clone() throws CloneNotSupportedException{ // 该类也实现克隆 return super.clone(); } }
-
-
序列化、反序列化
- 把对象序列化输出到流里面,然后把流里面的数据反序列化出来,得到一个新的对象
-
equals()
-
Object
类equals()
方法public boolean equals(Object obj) { return (this == obj); }
- 默认使用
==
判断对象- 判断两个对象引用指向的是同一个对象
- 即比较两个对象的内存地址是否相等
- 默认使用
-
应该重写该方法自定义判断两个对象相等的逻辑
-
两个对象地址相等则一定相等
- 否则进一步判断
-
另一个对象为
null
或两个对象的类型不同则一定不相等 -
逐个对对象的属性进行判断是否相等
- 都相等时判断两个对象相等
@Override public boolean equals(Object o) { // 重写 equals 方法 if(this == o) // 两个对象地址相等返回 true return true; if(o == null || getClass() != o.getClass()) // 比较对象为 null 或两个对象运行类型不同返回 false return false; Demo demo = (Demo)o; // 将比较对象强转为当前对象编译类型 // 逐项比较两个对象的属性并返回结果 return id == demo.id && age == demo.age && Objects.equals(name, demo.name) && Objects.equals(date, demo.date); }
-
notify()
- 随机唤醒正在等待的线程
- 只能被作为此对象监视器的所有者的线程调用
- 成为对象监视器的所有者,三种方法
- 执行对象的同步实例方法
- 使用 synchronized 内置锁
- 对于 Class 类型的对象,执行同步静态方法
- 如果当前线程不是此对象监视器的所有者
- 抛出
IllegalMonitorStateException
异常
- 抛出
- 成为对象监视器的所有者,三种方法
- 所有线程都在当前对象等待时,只会选择一个线程
- 选择是任意性的,并在对实现做出决定时发生
- 一次只能有一个线程拥有对象的监视器
- 线程在对象监视器上等待可以调用
wait()
方法
notifyAll()
- 唤醒所有正在等待的线程
notifyAll()
和notify()
notifyAll()
:唤醒此对象监视器上等待的所有线程notify()
:随即唤醒一个线程
- 当前线程不是对象监视器的所有者
- 调用方法同样发生
IllegalMonitorStateException
异常
- 调用方法同样发生
wait() 、wait(long timeout)
-
线程等待、超时等待;直到被
notify
方法唤醒 -
当前线程必须是此对象的监视器所有者
- 否则发生
IllegalMonitorStateException
异常
- 否则发生
-
当前线程在等待之前或在等待时被任何线程中断抛出
InterruptedException
异常 -
wait(long timeout)
- 传递的参数不合法,抛出
IllegalArgumentException
异常 timeout
参数为 0,则不会超时,一直进行等待,类似于wait()
方法
- 传递的参数不合法,抛出