第4章 对象与类
对象与类
- 4.1 面向对象程序设计概述
- 4.1.1 类
- 4.1.2 对象
- 4.1.3识别类
- 4.1.4 类之间的关系
- 4.2 使用预定义类
- 4.2.1 对象与对象变量
- 4.2.2 Java类库中的 LocalDate 类
- 4.2.3 更改器方法与访问器方法
- 4.3自定义类
- 4.3.1 使用多个源文件
- 4.3.2 剖析 Employee类
- 4.3.3 构造器
- 4.3.4 var声明局部变量
- 3.4.5 null引用
- 3.4.6 隐式参数与显示参数
- 4.3.8 封装实例字段
- 4.3.9 基于类的访问权限
- 4.3.10 私有方法
- 4.3.11 final实例字段
- 4.4 静态字段与静态方法
- 4.4.1 静态字段
- 4.4.2 静态常量
- 4.4.3 静态方法
- 4.4.4 工厂方法
- 4.5 方法参数
- 4.6 对象构造
- 4.6.1对象的重载
- 4.6.2 默认字段初始化
- 4.6.3 无参构造器
- 4.6.4 显示字段初始化
- 4.6.5 参数名
- 4.6.6 调用另一个构造器
- 4.6.7 初始化块
- 4.6.8 对象析构与 finalize 方法
- 4.7 记录
- 4.7.1 记录概念
- 4.7.2 构造器:标准、自定义和简洁
- 4.8 包
- 4.8.1 包名
- 4.8.2 类的导入
- 4.8.3 静态导入
- 4.8.4 在包中增加类
- 4.8.5 包访问
- 4.8.6 类路径
- 4.8.7 设置类路径
- 4.9 JAR文件
- 4.9.1 创建 JAR文件
- 4.9.2 清单文件
- 4.9.3 可以行 JAR文件
- 4.9.4 多版本JAR文件
- 4.9.5 关于命令行选项的说明
- 4.10 文档注释
- 4.10.1 注释的插入
- 4.10.2 类注释
- 4.10.3 方法注释
- 4.10.4 字段注释
- 4.10.5 通用注释
- 4.10.6 包注释
- 4.10.7 注释提取
- 4.11 类设计技巧
4.1 面向对象程序设计概述
- 面向对象程序设计(Object-Oriented Programming,OOP)是当今主流的程序设计规范;Java是面向对象。
- 面向对象的程序是由对象组成的,每个对象包含对用户公开的特定功能的隐藏的实现。
- 传统的结构化程序设计通过一系列的过程(即算法)来求解问题。当时将算法是第一位,数据结构排在第二位;首先会确定操作数数据的过程,然后在决定如果组织数据数据的结果。而 OOP 调换的以次序,将数据方法在第一位,然后再考虑数据的算法。
- 对于一些规模较小的问题,将其分解为过程的做法合适,而对象个合适解决规模较大的问题。
4.1.1 类
- 类(class)指定了如何构建对象,由一个类构造(construct)对象的过程称为创建这个类的一个实例(instance)。
- 用Java编写的所有代码都在某个类中。
- 封装(encapsulation,有时称为信息隐藏)。从形式看,封装就是将数据和行为组合在一个包中,并对对象的使用者隐藏具体的实现细节。
- 对象中的数据称为实例字段(instance field),操作数据的过程成为方法(method)。
- 作为一个类的实例,一个特定对象有一组特定的实例字段值。这些值的集合就是这个对象的当前的撞他(state)。
- 只要在对象上调用一个方法,它的状态就有可能发生改变。
- 实现封装的关键在于,绝对不能让其他类中的方法直接访问这个类的实例字节。
- 程序只能通过对象的方法与对象数据进行交互。
- 封装为对象赋予了 “黑盒” 特征,这是提高重用性和可靠性的关键。
- OOP的另一个原则:可以通过扩展其他类来构建心类。
- 会让用户自定义Java类变得更为容易。
- Java提供了一个 “超类”,Object。其他类都扩展自这个Object类。
- 扩展一个已有的类是,这个类具有被扩展的那个类的全部属性和方法,称为继承(inheritance)。
4.1.2 对象
同一个类的所有实例对象都有一种家族相似性,它们支持相同的行为。
每个对象都会保存着描述当前状况的信息(状态)。一个对象的行为由所能调用的方法来定义,对象的状态可能会随着时间而发生改变,对象状态的改变必然是调用方法的结果(封装性)。
对象的状态不能完全描述一个对象,每个对象都有一个唯一标识(identity,或称身份)。
对象的关键特性会彼此相互影响。
在OOP中,对象的三个主要特征:
-
对象的行为(behavior):可以对这个对象做哪些操作(方法)。
-
对象的状态(state):调用方法时,对象会如何响应?
-
对象的标识(identity):如果区分可能有相同行为和状态的不同对象?
4.1.3识别类
传统过程式中,必须从最上面的main函数编写程序。设计面对对象系统是,没有所谓最上面。OOP,先从识别类开始,然后再为各个类添加方法。
4.1.4 类之间的关系
类之间常见的关系:
- 依赖(“users-a”):如果一个类的方法要使用或操作另一回类的对象,就说前一个类依赖于后一个类。
- 聚合(“has-a”):是一种 弱拥有关系;表示“类A 包含 类B”的实例,但类B可以独立于类A存在。
- 继承(“is-a”):表示一个更特殊的类与一个更一般的类之间的关系。
4.2 使用预定义类
在Java中,没有类就无法做任何事情。并不是所有的类都表现出面相对象的典型特征。
4.2.1 对象与对象变量
使用对象,先构建对象,并指定初始状态。后对对象应用方法。
在Java程序设计语言中,要使用构造器(constructor,或称构造函数)构造新实例。构造器是一种特殊的方法,其作用是构造并初始化对象。
构造器与类名同名。
//例:构造一个Date对象,需要在构造器前面加上 new 操作符
new Date();//表达式会构建一个新对象//还可以将对象传递给一个方法
System.out.println(new Date());//将构造对象存方法在一个变量中;
Date rightNow = new Date();
对象与对象变量之间的区别
Date startTime; // startTime doesn't refer to any ob (不指向任何一个对象)
//定义一个对象变量 startTime,它可以引用 Date 类型的对象。变量 startTime 不是一个对象。//两个变量引用一个对象
Date rightNow = new Date;
startTime = rightNow;
对象变量并不实际包含一个对象,它只是引用一个对象。
在Java中,任何对象变量的值都是一个引用,指向存储在另一个地方的某个对象。new操作符的返回值也是一个引用。
/**两部分:表达式 new Date()构造了一个Date类型的对象,它的值是新创建对象的一个引用。在将这个引用存储到 startTime 变量中。
**/
Date startTime = new Date;//可以显示地将对象变量设置为 null,指示这个对象变量目前没有引用任何对象。
startTime = null;
4.2.2 Java类库中的 LocalDate 类
Java标准类库中的Date类的实力有一个从状态(特定的时间点)。
时间 是用距离一个固定时间点的毫秒数(可正可付)表示的,这个时间点就是 纪元(epoch),它是 UTC 时间 1970年1月1日00:00:00。UTC就是 Coordinated Universal Time(国际协调时间),与 GMT(Greenwich Mean Time,格林尼治时间)一样,是一种实用的科学标准时间。
Date类对于处理人类记录日期的日历信息并不是很有用。
类库设计者将保存时间与给时间点命名分开。所以标准Java类库分别包含了两个类:一个用来表示时间点的Date类;另一个表示日历表示法的表示日期的LocalDate类。
不要使用构造器来构造 LocalDate类的对象,应当使用静态工厂方法(factory method),它会代表调用构造器。
//会构造一个新对象,表示构造这个对象时的日期。
LocalDate.now(); //提供年、月和日构造对应一个特定日期对象
LocalDate.of(1999,12,31);//将构造对象保存在一个对象变量中
LocalDate newYearsEve = LocalDate.of(1999,12,31);//用对象变量,分别获取对象中的 年、月、日
int year = newYearsEve.getYear();
int month = newYearsEve.getMonthValue();
int day = newYearsEve.getDayOfMonth();//新日期对象距离当前对对象指定天数的一个新日期
LocalDate aThousandDaysLater = newYearsEve.plusDays(1000);
4.2.3 更改器方法与访问器方法
- 访问器方法(Getter / Accessor)
- 用于获取对象的属性值
- 方法名通常以 get 开通
- 返回值类型与属性相同
- 没有参数
- 更改器方法(Setter / Mutator)
- 用于设置/修改对象的属性值
- 方法名通常以 set 开头
- 没有返回值(void)
- 接受一个参数,类型与属性相同
java.time.LocalDate 8
- static LocalDate now():构造一个表示当前日期的对象。
- static LocalDate of(int year, int month, int day):构造一个表示给定日期的对象。
- int getYear()
- int getMonthValue()
- int getDayOfMonth():得到当前日期的年、月和日。
- DayOfWeek getDayOfWeek():得到当前日期是星期几,作为DayOfWeek类的一个实例返回。在DayOfWeek实例上调用 getValue 来得到 1~7之间的一个数。
- LocalDate plusDays(int n)
- LocalDate minusDays(int n):生成当前日期之后或之前 n 天的日期。
4.3自定义类
主力类(workhorse class):通常没有 main 方法,而有自己的实例字段和实例方法。
构建一个完整的程序,会结合使用过个类,其中只有一个类有 main 方法。
//在Java中,最简单的类定义形式为:
class ClassName{field1field2...constructor1constructor2...method1method2...
}//例:Employee类
import java.time.LocalDate;class Employee {//instance fields 实例字段private String name;private double salary;private LocalDate hireDay;//constructor 构造函数public Employee(String n, double s, int year, int month, int day) {name = n;salary = s;hireDay = LocalDate.of(year, month, day)}//method 方法public String getName() {return name;}public double getSalary() {return salary;}public LocalDate getHireDay() {return hireDay;}public void raiseSalary(double byPercent) {double raise = salary * byPercent / 100;salary += raise;}
}
4.3.1 使用多个源文件
假如 EmployeeTest.java源码中构建了 Employee对象
执行 javac EmployeeTest:
- 当Java编译器发现EmployeeTest.java中用了 Employee
- 如果没有找到这个类文件,就会自动搜索 Employee.java 并编译文件。
- 如果Employee.java的版本已有 Employee.class文件版本更新,Java编译器就会自动地重新编译这个文件。
4.3.2 剖析 Employee类
- 该类包含了一个构造方法和4个方法
- 所有方法都标记为 public。关键字 public 表示任何类的任何方法都可以调用。
- Employee类的实力中有3个实例字段,用来存方法要操作的数据。
- 所有实例字段都标记为 private,关键字 private 确保只用本类的方法能构访问,任何其它类的方法都不能读写。
- name 和 hireDay是引用数据类型的变量。
4.3.3 构造器
Employee类的构造器:
public Employee(String n, double s, int year, int month, int day) {name = n;salary = s;hireDay = LocalDate.of(year, month, day)}
- 构造器与类名同名
- 构建 Employee类的对象时
- 构建器运行
- 将实例字段初始化为所希望的初始状态
- 构造器要结合 new操作符来调用。
- 不能对一个已经存在的对象调用构造器来重新设置实例字段
总结:
- 构造器与类同名
- 每个类可以有 0、1个或多个参数
- 构造器没有返回值
- 构造器总是结合 new操作符一起调用
4.3.4 var声明局部变量
在Java10中,如果可以从变量的初始值推导出它们的类型,可以用 var关键字声明局部变量,无序指定类型
//例
var harry = new Employee("Harry Hacker",500,1989,10,1);
- var 关键字只能用于方法中的局部变量。参数和字段的类型必须声明。
3.4.5 null引用
对象变量包含一个对象的引用,或者包含一个特殊值null(表示没有引用任何对象)。
对 null值 应用一个方法,会产生一个 NullPointerException异常。
3.4.6 隐式参数与显示参数
方法操作对象并访问它们的实例字段
//例:Employee 中的方法
public void raiseSalary(double byPercent) {double raise = salary * byPercent / 100;salary += raise;}//在另一个创建实例对象
Employee number = new Employee("Harry Hacker",500,1989,10,1);//调用 raiseSalary方法
number.raiseSalary(5);//将执行
double raise = salary * 5 / 100;number.salary += raise;
- raiseSalary方法有两个参数
- 第一个参数称为隐式(implicit)参数,定义方法的实例字段(double byPercent)
- 第二个参数是位于方法名后面括号中的数值,是显示(explicit)参数
可以使用关键字 this指示隐式参数,可以将实例字段与局部变量明显的区分开来
public void raiseSalary(double byPercent) {double raise = this.salary * byPercent / 100;this.salary += raise;}
4.3.8 封装实例字段
获取和设置实例字段的值:
- 一个私有的实例字段
- 一个公共的字段访问器方法
- 一个公共的字段更改器方法
警告: 不要编写返回可变对象引用的访问器方法
// 例
class Employee{private Date hirDay;public Date getHirDay(){return hireDay;//BAD}
}
LocalDate类没有跟改期方法,Date有一个更改器方法 setTime,可以设置毫秒数。
Date对象是可变的,这就破坏了封装性
Employee harry = new Employye();
Date d = harry.getHireDay();
d.setTime(d.getIime() - 10*365*25*25*60*60*1000 );
//let's give Harry ten years of added seniority
-
d和 harry.hireDay 引用同一个对象
-
对d调用更改器会自动改变 Employee 对象的私有状态
-
如果需要返回一个可变得对象引用,先对它进行克隆(clone)
- 对象克隆:存放在另一个新位置上的对象副本
//修改后的代码
class Employee{private Date hirDay;public Date getHirDay(){return (Date)hireDay.clone();}
}
4.3.9 基于类的访问权限
一个类的方法可以访问这个类的所有对象的私有数据。
//例
public class Employye{private String name;public boolean equals(Employye other){return name.equals(other.name);}
}//经典调用
if(harry.equals(boos)){};
//这个方法访问了 harry的私有字段,还访问了 boos的私有字段
4.3.10 私有方法
在java中,实现一个私有方法,将关键字 public 改为 private即可。
4.3.11 final实例字段
将实例字段定义为final后, 此字段必须在构造对象时初始化对象,并且以后不能再修改这个字段。
final在修饰引用数据类型时,只表示引用变量不会在改变,但是这个对象是可以更改。
private final StringBuilder evaluations = new StringBuilder();
//表示存储在 evaluations 变量中的对象引用不会再指向另一个不同的 StringBuilder 对象。不过这个对象是可以更改的。
4.4 静态字段与静态方法
4.4.1 静态字段
将一个字段定义为 static,这个字段并不出现在每个类的对象中。每个静态字段只有一个副本。
静态字段属于类,并不属于单个对象。
在一些面向对象程序设计语言中,静态字段称为 类字段。 术语 “静态” 只是沿用了 C++ 的叫法,并无实际意义。
//例
public class Employee{public static int nextId = 1;public int id;
}public class TestEmployee{Employye employeeDemo1 = new Employye();employyeeDemo1.id++;// id = 1employyeeDemo1.nextId++;// id == 2;Employye employeeDemo2 = new Employye();employyeeDemo2.id++;// id = 1employyeeDemo2.nextId++;// id == 3;
}
4.4.2 静态常量
在Math类中的 PI就是一个静态常量,不需要创建此对象调用,直接 类名.静态实例名。
public class Math{
…
public static final double PI = 3.14159265358979323846;
…
}
在 System类中的 out也是一个静态常量,out变量是公共字段加final的形式来进行,不允许重新赋值为另一个打印流。但是 System类中的setOut方法可以将System.out设置为不同的打印流。因为,setOut方法是原生方法,不是在java中实现的。原生方法可绕过java语言的访问控制机制。
public final class System {
…
public static final PrintStream out = null;
…
}
4.4.3 静态方法
静态方法是不操作对象的方法,静态方法中没有 this参数的方法啊(在非静态方法中,this参数指示这个方法的隐式函数)。
静态方法可以是用类名调用或者使用对象名调用,但是在使用对象名调用时,得出的结果与对象毫无关系。
以下两种情况可以使用静态方法:
- 方法不需要访问对象状态,因为它需要的所有参数都通过显示参数提供。
- 方法只需要访问类的静态字段。
4.4.4 工厂方法
静态方法的另一种常见的用途。如 LocalDate 和 NuberFormat 的类使用静态工厂方法(factory method)来构造对象,例如 LocalDate.now 和 LocalDate.of。
//例
public class TestNuberFormat{NumberFormat currencyInstance = NumberFormat.getCurrencyInstance();NumberFormat percentInstance = NumberFormat.getPercentInstance();double x = 0.1;System.out.println(currencyInstance.format(x));//¥0.10 q System.out.println(percentInstance.format(x));//10%
}
NumberFormat类不使用构造器创建对象
- 无法为构造器命名。够再起名字总是要与类型相同。但是,希望有两个不同的名字,分别得到不同的货币实例和百分比实例。
- 使用构造器时,无法改变所构造器对象的类型。而工厂方法实际上将放回 DecimalFormat类对象,这是继承 NumbFormat 的一个子类。
4.5 方法参数
将参数传递到方法(函数)中:
- 按值调用(call by value):表示方法接收的调用者的值。
- 按引用调用(call by reference):表示方法接收的调用者提供的变量位置(location)。
方法可以修改引用传递的变量值,不能修改按值传递的值。
“按 … 调用”(call by) 是标准的计算机科学术语,用来表述各种程序设计语言 中方法参数的行为。
Java程序设计语言总是采用按值调用,方法会得到所有参数的一个副本。方法不能传递给它的任何参数变量的内容。
//例:参数数据类型是基本数据类型
public class Test{public static void tripleValue(double x){x = x*3;}public static void main(String[] args) {double percent =10;tripleValue(percent);}
}
//例:参数数据类型是引用数据类型
public class Dome{double x =1;
}public class Test{public static void tripleValue(Dome dome){dome.x*=3;}public static void main(String[] args) {Dome dome = new Dome();tripleValue(dome);}
}
4.6 对象构造
4.6.1对象的重载
有的类有多个构造器方法,如 StringBuilder:
//创建一个空的对象
var messages = new StringBuilder();//指定一个初始字符串
var todoList = new StringBuilder("To do:\n");
这种功能叫重载。
重载:多个方法有相同的方法名,但有不同的参数。
- java允许重载任何方法
- 完整描述一个方法,需要指定方法名以及参数类型,叫方法的 签名(signature)
重载解析:
- 用各个方法首部中的参数类型与特定方法调用中所使用的值类型进行匹配 。
- 如编译器不匹配参数,产生编译时错误。
- 原因一:不存在的匹配
- 原因二:重载方法中没有一个相对更好的方法。
4.6.2 默认字段初始化
构造器中没有显示地为一个字段设置初始值,就会自动设置为默认值:
- 数值:0
- 布尔值:false
- 对象引用: null
4.6.3 无参构造器
-
一个类中如果没有构造器,就会自动提供一个无参构造器。
-
如果有设置构造器,就会不会自动提供无参构造器。
4.6.4 显示字段初始化
在执行构造器之前完成复制
//例
class Employee{private String name = "";. . .
}
4.6.5 参数名
如果方法的参数名与实例名相同,可以使用this指示访问实例字段。就是this指示隐式参数
java中方法调用分为两种参数:
- 显示参数(explicit parameters):在方法定义中看到的那些参数。
- 隐式参数(implicit parameter):指调用方法的对象本身。不在方法声明参数列表中,但每个实例方法都会自动接收到一个指向调用对象的引种——this。
//例
public class Employee{private String name;public Employee(String name){this.name = name;}
}
4.6.6 调用另一个构造器
this关键有两层含义:
- 指示一个方法的隐式参数。
- 在构造器中调用同一个类的另一个构造器。
- this()必须要第一行
- 有this()不能再有super();
- super后面在说
//例
public Employee{double s;public Employee(){this(1.1);System.out.println("s");}public Employee(double s){this.s = s;}
}
4.6.7 初始化块
初始化字段的方法:
- 在构造器中设置值
- 在声明中设置值
- 初始化块(initialization block)
- 在一个声明中,可以包含任意的代码块,构造这个类的对象时,就会执行这些块。
//例
class Employee{private static int nextId;private int id;{id = nextId;nextId++;}
}
初始化实例字段的多用途径:
- 如果构造器的第一行调用了另一个构造器,则基于所提供的构造器的参数执行第二个构造器
- 否则
- 所有字段初始化为期默认值(0,false 或 null)
- 按照在类声明中出现的顺序,执行所有字段初始化方法和初始化块
- 执行构造器主体代码。
//创建随机数
java.util.Random 1.0
- Random():构造一个新的随机数生成器。
java.util.random.RandomGenerator 17
- int nextInt(int n):返回一个 0~n-1之间的随机数
- static RandomGenerator of(String name):由给定算法名生成一个随机数生成器。
4.6.8 对象析构与 finalize 方法
有些面向对象的程序设计语言有显示的构造器的方法,其中放置一些清理代码,当对象不在使用需要执行这些清理代码。
在析构器中,最常见的操作是回收分配给对象的存储空间。
Java会完成自动的垃圾回收,不需要人工回收内存,所以Java不支持析构器。
4.7 记录
在JDK14引入了一个预览特性:“记录”,最终版在JDK16中发布。
4.7.1 记录概念
记录(record):一种特殊形式的类,其状态不可变,而且公共可读
//例
public record Point(double x ,double y) {}
-
在Java语言规范中,一个记录的实例字段称为组件(component)
-
private final double x; private final double y;
-
-
有一个构造器:
-
Point(double x ,double y);
-
-
访问器方法:
-
@Override public double x() {return x; } @Override public double y() {return y; }
-
-
总结
内容 | 是否需要手动写 | 备注 |
---|---|---|
字段 x , y | ❌ 不需要 | record 自动生成 |
构造函数 Point(double x, double y) | ❌ 不需要(除非想加逻辑) | 自动生成 |
x() 和 y() 方法 | ❌ 一般不需要 | 自动生成 |
4.7.2 构造器:标准、自定义和简洁
自动定义地设置所有实例字段的构造器称为标准构造器(canonical constructor)
自定义构造器(custom constructor)
-
第一句必须调用另一个构造器,所以最终会调用标准构造器。
-
public record Point(double x ,double y) {public Point(){this(0,0);} }
-
实现标准构造器的简洁(compact)形式:
- 不用指定参数列表
- 不能在简洁构造器的主体中读取或修改实例字段
public record Point(double x ,double y) {public Point{x= y;}
}
4.8 包
Java允许使用包(Package)将类组织在一个集合。借助包可以方便地组织代码,并将自己的代码与其他人的代码库分开。
4.8.1 包名
- 使用包名的的主要原因是确保类名的唯一性。
- 为了保证包名的绝对唯一性,可以使用一个因特网域名以逆序的形式作为包名
- 对于不同的项目使用不同的子包。
4.8.2 类的导入
一个类可以用使用所属包中的所有类,其他包中的公共类(public class)
采用两种方式访问另一个包中的公共类
-
方式一:使用完全限定名(fully qualified name)
-
java.util.Scanner scanner = new java.util.Scanner(java.lang.System.in);
-
-
方式二:使用import语句。
-
增加了import语句,在使用类是,就不必写出类的全名
-
可以使用import语句导入一个特定的类或者整个包。
-
//例 import java.time.*;
-
-
import语句位于源文件的顶部。
-
4.8.3 静态导入
一种import语句允许导入静态方法和静态字段,而不是类。
可以不必叫类名前缀
//例
import static java.lang.System.*;
public class Test {public static void main(String []args) {out.println("1123");}
}
4.8.4 在包中增加类
要想将类放在包中,必须将包名放在源文件的开头,即放在定义这个包中各个类的代码之前。
如果没有在在源文件中放置package语句,这个源文件中的类就属于无名包(unnamed package)
//例
package com.mohunhu.test;
public class Test {
}
4.8.5 包访问
- public:可以有任意类使用
- private:只能有自定义的类使用
- 没有指定修饰符:由同一个包中的所有方法访问
- protected:可以在同一个类中,同一个包中和通过继承在不同包中使用
4.8.6 类路径
类存储
- 方式一:在文件系统的子目录中
- 方式二:存储在JAR(Java归档)文件中。
- 在一个JAR文件中,可以包含多个压缩格式的类文件和目录
- 可以节省空间和改善性能
- JAR文件使用ZIP格式组织文件和子目录
- 可以使用任意ZIP工具查看JAR文件
使类能够被多个程序共享,需要做以下几点
- 把类文件放到一个目录中
- 将JAR文件放在一个目录中
- 设置类路径(class path),类路径是所有包含类文件的路径集合
javac编译器总是在当前目录中查找文件,但只有当路径中包含“.”目录时,java虚拟机才会查看当前目录。
如果没有指定类路径,默认的类路径会包含 “.”目录。
如果设置了类路径去忘记包含 "."目录,那么程序可以没有错误地通过编译,但不能运行。
4.8.7 设置类路径
最好使用 -classpath(或-cp,或 Java9 中的 --class-path)选项制定类路径:
java -classpath 路径/文件.jar
整个指令必须写在一行中。
还可以使用 设置 CLASSPATH环境变量来指定类路径。
在java9中,还可以从 模块路径加载类。
4.9 JAR文件
Java归档(JAR)文件:可以包含类文件,也可以包含诸多图像和声音等其他类型的文件;JAR文件的压缩是 ZIP压缩格式。
4.9.1 创建 JAR文件
可以使用 jar工具制作JAR文件(默认在,jar/bin目录中)
创建一个新JAR文件常用命令:
jar cvf jarFileName file1faile2. . .
jar程序选项
选项 | 说明 |
---|---|
c | 创建一个新的或空的存档并加入文件。如指定的是问价名目录,jar程序会进行递归处理 |
C | 临时改变目录 |
e | 在清单文件中创建一个入口点 |
f | 指定JAR文件名作为第二个命令行参数。如果没有此参数,jar命令会将结果写值标准数据或者从标准输入读取输入 |
i | 创建索引文件 |
m | 将一个清单文件添加到JAR文件中 |
t | 显示内容表 |
u | 跟新一个已有的JAR文件 |
v | 生成详细的输出 |
x | 解压文件。如果提供一个或多个文件名,只解压这些文件;否则,解压所有文件 |
0 | 存储,但不进行ZIP压缩 |
4.9.2 清单文件
除了类文件、图像和其他资源外,每个JAR文件还包含一个 清单文件(manifest),用于描述归档文件的特殊特性。
清单文件被命名为 MANIFEST.MF,位于JAR文件的一个特殊的META-INF子目录中。合法的最小清单文件极其加单,如 Manifest-Version:1.0。
复杂的清单文件可能包含更多条目。清单条目别分为多组多个节:
第一节被称为主机(main section):它作用于整个 JAR文件。
随后的条目可以指定命名实例的实现;如单个文件、包或者URL。
编辑清单文件:需将系统添加到清单文件放到文本文件中,然后运行 jar cfm jarFileName manifestFileName . . .
更新一个已有的JAR文件的清单,需要将增加的部分放置到一个文本文件中,然后执行 jar ufm MyArchive.jar manifest-additions.mf
4.9.3 可以行 JAR文件
使用jar命令中的 e 选项指定程序的入口点,即调用java执行程序时指定的类:
jar cvfe MyProgram.jar com.mycompany.mypkg.MainAppClass files to add
或,可以在清单文件中指定程序的主流,包括以下形式的语句:
Main-Class:com.mycompany.mypkg.MainAppClass
不能为 主类名扩展名 .class
清单文件中的最后一行必须以换行符结束。负责,将无法正确地读取清单文件。
使用简单的命令启动程序:
java -jar MyProgram.jar
4.9.4 多版本JAR文件
随着模块和包前置封装的引入,之前可以访问的一些内部API不再可用。
为此java9引入了多版本JAR(multi-release JAR)
-
为了保证向后兼容,特定版本类文件放在 META-INF/version 目录中
-
Java8完全不知道META-INF/version目录,它只会加载遗留的类。Java9读取JAR文件时,则会使用新版本。
-
增加不同版本的类文件,可以使用 --release标志:
- jar uf MyProgram.jar --release 9 Application.class
-
从头构建一个多版本JAR文件,可以使用-C选项,对应每个版本要切换到一个不同的类文件目录:
- jar cf MyProgram -C bin/8 . --release 9 0C bin/9 Application.class
-
面相不同版本编译时,使用 --release标志和 -d 标志来指定输出目录:
- javac -d bin/8 --release 8 . . .
-
Java9中,-d选项会创建这个目录(如原先该目录不存在)
4.9.5 关于命令行选项的说明
Java开发包(JDK)的命令行选项一直以来都是用单个短横多字母选项名的形式,如: java -jar …
jar命令是例外,此命令遵守 tar 命令 选项格式,而没有短横线:jar cvf …
在Java9开始,Java工具转向一种常用的选项格式,多字母选项目前面加两个短横线,另外对于常用的选项可以使用单字母快捷方式。
4.10 文档注释
JDK包含一个很有用的工具,javadoc;它可以由源文件生成一个HTML文档。
在源文件中添加特殊定界符/**开始注释,就可以生成一份文档。
4.10.1 注释的插入
javadoc工具从以下信息用抽取:
- 模块
- 包
- 公共类与接口
- 公共的和受保护的字段
- 公共的和受保护的构造及方法
为以上各个特性编写注释。各个注释放置在所描述特性的前面。注释以/**开始,并以*/结束。
- 每个 /** …*/文档注释包含标记以及之后紧跟着的自由格式文本(free-form text)。标记以 @可是,如@param
- 自由格式文本的第一句应是一个概要陈述。
- 在自由格式文本中,可以使用HTML修饰符。要键入等宽代码,需使用 {@code …}
- 文档中如果文件的链接,应该吧文件方法包含源文件的目录下的一个字符流doc-files中。
- javadoc工具将从原目录将 doc-files目录极其内容赋值到文档目录中。
4.10.2 类注释
类注释必须放在import语句之后,class定义之前
/*** A{@code Test} 这是一个测试类* 每一行的开始不添加*也是合法的。*/
public class Test {
}
4.10.3 方法注释
每个方法注释必须放在所描述的方法之前。
除通用标记之外,还可以使用下面的表示:
- @param variable description:这个标记将给当前方法的 “parameters” (参数)部分添加一个条目。此描述可以占据多行,并使用HTML标记。一个方法的所有@param标记必须方法在一起。
- @return description:这个标记将给当前方法添加 “returns”(返回)部分。此描述可以跨多行,并且可以使用HTML标记。
- @throws class description:这个标记将添加一个注释,表示这个方法可能抛出的异常。
/*** 测试方法* @param str 测试形参* @return 返回值* @throws NullPointerException 空指针异常*/public String test(String str) throws NullPointerException {return null;}
4.10.4 字段注释
只需要对公共字段(通常指静态常量)正价文档注释。
/*** 测试静态常量为1*/public static final int TEST = 1;
4.10.5 通用注释
标记@since text会建议一个 “since”(始于)条目。text(文本)可以是对引入这个特性的版本的描述。
类文档注释中可以使用下面的标记:
- @author name:标记将建立一个“author”(作者)条目。可以有过个@author标记,每个@author标记对应一个作者。
- @version text:标记将建议一个 “version”(版本)条目。text可以是对当前版本的任何描述。
- 通过@see和@link标记,可以使用超链接,链接到javadoc文档的相关部分或外部文档。
- 标记@see reference将在“see also”(参见)部分增加一个超链接。可以用于类和方法中。
- @see com.horstmann.corejava.Employee#raiseSalary(double):
- 建立一个超链接,链接到 om.horstmann.corejava.Employee类的raiseSalary(double)方法。
- @see <a href=“www.horstann.com/corejava.html”>The Core Java home page</a>
- 超链接到任何URL
- @see “Core Java 2 volume 2”
- 如@see标记后有一个双引号字符,文本就会显示在 “see also” 部分。
- {@link package.class#feature label}:
- 可以在任何文本注释中范式指向其他类或方法的超链接。
- Java9中,添加了 {@index entry}标记为搜索框增加一个条目。
- @see com.horstmann.corejava.Employee#raiseSalary(double):
- 标记@see reference将在“see also”(参见)部分增加一个超链接。可以用于类和方法中。
4.10.6 包注释
产生包注释,就需要再每个包目录中添加一个单独的文件,有以下选择:
- 提供一个名为 package-info.java 的java文件。
- 此文件必须包含一个初始的Javadoc注释,以/** 和 */定界,后面是package语句。
- 提供一个名为 package.html 的HTML文件,抽取标记 <body> … </body> 之间的文本。
4.10.7 注释提取
将HTML文件放在名为 docDirectory 的目录下。执行以下步骤:
- 切换到源文件目录,其中包含想要生成文档的源文件
- 运行命令
- 如果是一个包:javadoc -d docDirectory nameOfPackage
- 为多个包生成文档:javadoc -d docDirectory nameOfPackage1 nameOfPachage2 …
- 如果此文件在无名包中:javadoc -d docDirectory *.java
4.11 类设计技巧
- 一定要保证数据私有
- 一要初始化数据
- 不要在类中使用过多的基本类型
- 不是所有的字段都需要单独的字段访问器和更改器
- 分解有过多职责的类
- 类名和方法名要能够体现它们的职责
- 优先使用不可变的类