第3章 类的基础
3.1 类的基本概念
一种类的理解方式是函数的容器。例如在Java API的类Math,在类Math中,我们可以引用的方法是以public static
修饰的。
static
表示类方法,也叫静态方法。与类方法相对的是实例方法,实例方法没有static
修饰,必须通过实例或对象调用,而类方法可以直接通过类名进行调用,不需要创建实例。
public
表示方法是公开的,可以在任何地方被外部调用。
private
表示方法是私有的,这个方法只能在同一个类内被别的函数调用,而不能被外部类调用。
通过private封装和隐藏内部实现细节,避免误操作,是计算机程序的一种基本思维方式。
以下是类Math常用的方法。
方法 | 功能 |
---|---|
int round(float a) | |
double sqrt(double a) | |
double ceil(double a) | 向上取整 |
double floor(double a) | 向下取整 |
double pow(double a, double b) | |
int abs(int a) | |
int max(int a, int b) | |
double log(double a) | 取自然对数 |
double random() | 产生一个 ( 0 , 1 ) (0,1) (0,1)范围内的随机数 |
以下是一个和数组的操作相关的类Arrays常用方法。
方法 | 功能 |
---|---|
void sort(int[] a) | 按升序排序 |
void sort(double[] a) | |
int binarySearch(long[] a, long key) | 查找key在数组a中的位置 |
void fill(int[] a, int val) | 给数组所有元素赋相同的值 |
int[] copyOf(int[] originnal, int newLength) | 数组复制 |
boolean equals(char[] a1, char[] a2) | 判断两个数组是否相同 |
类事实上是一种自定义的数据类型。一个类型主要由4部分组成。
- 类型本身具有的属性,通过类变量(static修饰,也称静态变量或静态成员变量)来体现。
- 类型本身可以进行的操作,通过类方法(static修饰,也称静态方法或静态成员方法)体现。
- 类型实例具有的属性,通过实例变量(无static修饰,也称成员变量)来体现。
- 类型实例可以进行的操作,通过实例方法(无static修饰,也称成员方法)来体现。
我们可以使用final
来表示常量。
在类的定义中,类不可以使用private
来修饰,但可以没有修饰符,此时表示一种包的可见性。但在定义内部类时,可以使用private
修饰符。
和类变量类似,实例变量也有public
和private
修饰符,且不能使用static
修饰。
实例方法和类方法的区别如下。
- 类方法只能访问类变量,不能访问实例变量,可以调用其他类方法,当不能调用本类的实例方法。
- 实例方法既可以访问实例变量,也可以访问类变量。既可以调用类方法,也可以调用实例方法。
类的使用过程基本如下所示。
public class ChapterThree {
public static void main(String[] args) {
Point p = new Point();
p.x = 2;
p.y = 3;
System.out.println(p.distance());
}
}
其中,类的实例或对象的创建语句为Point p = new Point();
。其中,new Point();
是必须的。而在C++中,Point p
即可创建按一个类的实例或对象。但在Java中,Point p
只是创建了一个可以引用实例或对象的变量,即p
的值是对象或实例的实际存放地址。此时,new Point()
是为对象的实例分配内存,而赋值语句则把存放对象的内存的起始地址赋值给p
,使得p
可以引用这个实例。
在创建对象的实例时,所有的实例变量都会分配一个默认值,其中,数值类型变量的默认值是0,boolean为false,char是“\u0000”,引用类型变量是null。null是一个特殊的值,表示这个引用类型变量不指向任何对象。
不同于C++,Java可以在定义成员变量时指定初始值,无论成员变量是否为static final
。
Java中的构造方法可以通过this(...)
来调用。
每个类至少有一个构造方法,构造方法在通过new
创建对象的过程中会被调用。如果没有显示定义构造方法,Java编译器会自动生成一个默认构造方法。但显示定义了构造方法后,Java编译器不再生成默认构造方法。
构造方法可以使用private
来修饰。
类和对象的生命周期如下。
- 在程序第1次通过new创建一个类的对象时,或者直接通过类名访问类变量和类方法时,Java会将类加载进内存,为这个类分配一块空间。这个空间包括类的定义、成员变量和成员方法。
- 类加载进内存后,一般不会释放,直到程序结束。
- 每次做new操作时都会在内存中划分一块区域来存储对象,每个区域都对应一个独立的实例变量。
- 对象的释放是通过Java的垃圾自动回收机制管理的,当没有活跃变量引用对象时,对象对应的空间可能会被释放。
对象和数组一样,有两块内存,保存地址的部分分配在栈中,保存实际内容的部分分配在堆中。
3.2 类的组合
类的组合实际上是在类中使用另外一个类的对象。例如String
和Date
类。
每个类封装其内部细节,对外提供高层次的功能,使其他类在更高层次上考虑和解决问题,是程序设计的一种基本思维方式。
3.3 代码的组织机制
包类似于文件夹,类和接口放在包中。包的名字以.
分割,例如java.lang.String
,这个带完整包名的类名为完全限定名。Java API中所有的类和接口都位于包Java或javax下,Java是标准包,javax是扩展包。
在定义类的时候,应该使用关键字package
来声明包名,例如。
package shuo.laoma;
public class Hello{
}
包声明语句应该位于源代码的最前面,前面不能有注释外的其他语句。
注意,包名和文件目录必须匹配。如果存放源文件的根目录为E:\src
,上面Hello
类所在的文件为Hello.java
,其全路径应该是E:\src\shuo\laoma\Hello.java
。如果不匹配,Java会提示编译错误。
包的用途是避免命名冲突和方便组织代码。
同一个包下的类之间的相互引用是不需要包名的,可以直接使用。如果类不在同一个包内,则必须知道其所在的包。此时,有两种解决方式,一种是通过类的完全限定名,另一种是将用到的类引入到当前类中。特别地,java.lang
包下的类可以直接使用,不需要引入,也不需要完全限定名,例如String
,System
。
通过import
关键字可将一个类引入到当前类的文件中,例如。
package shuo.laoma;
import java.util.Arrays;
public class Hello{
}
做import
时,可以通过*
来将一个包下的所有类一次引入,例如
import java.util.*;
但这个引入不可以递归。
在一个类中,对其他类的引用必须时唯一确定的,不能有重名的类。如果有重名的类,只能通过import
引入其中一个类,其他同名的类需要通过完全限定名来访问。
import
语句应该放置在package
之后,类的定义之前。
有一种特殊类型的导入称为静态导入,其有一个static
关键字,可以直接导入类的公开静态方法或成员。但静态导入不应该过度使用,否则难以区分访问的是哪个类的代码。
我们可以将编译好的代码打包成一个文件,方便其他程序调用。
在Java中,编译后的一个或多个包的Java class可以打包成一个文件,命令为jar
,打包后的文件扩展名为.jar
,一般称之为jar包。
Java类库、第三方类库都是以jar包的形式提供的,将其加入到类路径classpath
即可使用。
从Java源代码到运行的程序,需要经过编译和链接两个步骤。编译是使用javac
命令将源代码文件编译扩展名为.class
的字节码。链接时使用java
命令解析.class
文件,转换为机器能识别的二进制代码,然后放到Java虚拟机上运行。
Java编译运行时需要以参数指定一个classpath,类路径。类路径可以有多个,对于直接的class文件,路径是class文件的根目录。对于jar包,路径是jar包的完整名称。
在Java源代码编译时,Java编译器会确定引用的每个类的完全限定名,确定的方式是根据import
语句和classpath
。Java运行时,会根据编译时确定下来的类的完全限定名寻找并加载类,寻找的方式就是在类路径中寻找。