类和对象

Table of Contents

面向对象简介

面向对象简称 OO(Object Oriented),20 世纪 80 年代,有了面向对象分析(OOA)、面向对象设计(OOD)、面向对象程序设计(OOP)等新的系统开发方式模型的研究。

对象的概念

对 Java 语言来说,一切皆是对象,对象就是面向对象程序设计的核心。那么到底什么是对象呢?

所谓对象就是真实世界中的实体,对象与实体是一一对应的,也就是说现实世界中的每一个实体都是一个对象,它是一种具体的概念。对象有以下特点:

  • 对象具有属性和行为;
  • 对象具有变化的状态;
  • 对象具有唯一性;
  • 对象都是某个类别的实例。

面向对象开发模式,在具体的开发过程中便于程序的划分,方便程序员分工合作,提高开发效率。

Hmm... 对企业来说,效率是关键啊。

面向对象程序设计(OOP)有以下优点:

  • 可重用性:代码重复使用,减少代码量,提高开发效率。

面向对象的三大核心特性:继承、封装和多态,都是围绕这个核心。

  • 可扩展性:指新的功能可以很容易地加入到系统中来,便于软件的修改;
  • 可管理性:能够将功能与数据结合,方便管理。

该开发模式之所以使程序设计更加完善和强大,主要是因为面向对象具有继承、封装和多态 3 个核心特性。

1. 继承性

程序中的继承性,是指子类拥有父类的全部特征和行为,这是类之间的一种关系。

*注:C++ 支持多继承,即一个子类可有多个父类,多继承会引起很多冲突问题。Java 语言是单继承的,即一个子类只能有一个父类,但 Java 可以实现多个接口。

接口类似于类,但接口的成员没有执行体。

2. 封装性

封装是将代码及其处理的数据绑定在一起的一种编程机制,该机制保证了程序和数据都不受外部干扰且不被误用。

封装的目的在于保护信息。

使用它的主要优点如下:

  • 保护类中的信息,它可以阻止在外部定义的代码随意访问内部代码和数据;
  • 隐藏细节信息,用记只需要知道如何操作,不需要知道是如何运行的;
  • 有助于建立各个系统之间的松耦合关系,提高系统的使用;
  • 提高软件的复用率,降低成本。

Java 语言的基本封装单位是类。 由于类的用途是封装复杂性,所以类的内部有隐藏实现复杂性的机制。

Java 提供了私有和公有的访问模式,类的公有接口代表外部的用户应该知道或可以知道的每件东西,私有方法数据只能通过该类的成员代码来访问,这就可以确保不会发生不希望的事情。

3. 多态性

面向对象的多态性,即“一个接口,多个方法”。多态性体现在父类中定义的属性和方法被子类继承后,可以具有不同的属性或表现方式。 多态性允许一个接口被多个同类使用 ,弥补了单继承的不足。

认识类和对象

在面向对象中,类和对象是最基本、最重要的组成单元。

类实际上是表示一个客观世界某类群体的一些基本特征抽象,对象就是表示一个个具体的东西,所以说类是对象的抽象,对象是类的具体。

看,哲学小妞又来了。

类是概念模型,定义对象的所有特性和所需的操作,对象是真实的模型,是一个具体的实体。

由此可见,类是描述了一组有相同特性(属性)和相同行为(方法)的一组对象的集合。

类是构造面向对象程序的基本单位,是抽取了同类对象的共同属性和方法所形成的对象或实体的“模板”。而对象是现实世界中实体的描述,对象要创建才存在,有了对象才能对对象进行操作。类是对象的模板,对象是类的实例。

类是 Java 中的一种重要的引用数据类型,也是组成 Java 程序的基本要素,因为 Java 程序都是基于类的。

在 Java 中定义一个类,需要使用 class 关键字、一个自定义的类名和一对表示程序体的大括号。完整语法如下:

[public] [abstrct|final] class <class_name> [extends <class_name>] [implements <interface_name>] {
    // 定义属性部分
    <propery_type> <property1>;
    <propery_type> <property2>;
    <propery_type> <property3>;
    ...
    // 定义方法部分
    function1();
    function2();
    function3();
    ...
}

上述语法中各关键字的描述如下:

  • public :表示“共有”的意思;

如果使用 public 修饰,则可以被其他类和程序访问。每个 Java 程序的主类都必须是 public 类,作为公共工具供其他类和程序使用的类应定义为 public 类。

  • abstract :如果类被 abstract 修饰,则该类为抽象类;

抽象类不能被实例化,但抽象类中可以有抽象方法(使用 abstract 修饰的方法 )和具体方法(没有使用 abstract )修饰的方法。

*注:继承该抽象类的所有子类都必须实现该抽象类是的所有抽象方法(除非了类也是抽象类)。

  • final :如果类被 final 修饰,则不允许被继承;
  • class :声明类的关键字;
  • class_name :类的名称;
  • extends :表示继承其他类;
  • implements :表示实现某些接口;
  • property_type :表示成员变量的类型;
  • property :表示成员变量名称;
  • function() :表示成员方法名称。

Java 类名的命名规则:

  • 类名应该以下划线( _ )或字母开头,最好以字母开头;
  • 第一个字母最好大写,如果类名由多个单词组成,则每个单词的首字母最好都大写;
  • 类名不能为 Java 中的关键字,例如 boolean、this、int 等;
  • 类名不能包含任何嵌入的空格或点号以及除了下划线( _ )和美元符号( $ )字符之外的特殊字符。

来定义一个简单的 Person 类,如下:

1: public class Person {
2:     private String name;        // 姓名
3:     private int age;            // 年龄
4:     public void tell() {
5:         // 定义说话的方法
6:         System.out.println(name + "今年" + age + "岁!");
7:     }
8: }

成员变量

在 Java 中类的成员变量定义了类的属性。声明成员变量的语法如下:

[public|protected|private] [static] [final] <type> <variable_name>

各参数的含义如下:

  • public、protected、private :用于表示成员变量的访问权限;
  • static :表示该成员变量为类变量,也称为静态变量;
  • final :表示将该成员变量声明为常量,其值无法更改;
  • type :表示变量的类型;
  • variable_name :表示变量名称。

可以在声明成员变量的同时对其他进行初始化,如果声明成员变量时没有对其初始化,则系统会使用默认值初始化成员变量。

类型   默认值
整数型 byte、short、int、long 0
单精度浮点型 float 0.0f
双精度浮点型 double 0.0d
字符型 char '\u0000'
布尔型   false
引用类型   null

成员方法

声明成员方法可以定义类的行为,行为表示一个对象能够做的事情或者能够从一个对象取得的信息。类的各种功能操作都是用方法来实现的,属性只不过提供了相应的数据。

一个完整的方法通常包括:方法名称、方法主体、方法参数和方法返回值类型。

成员方法一旦被定义,便可以在程序中多次调用,提高了编程效率。声明成员方法的语法格式如下:

public class Test {
    [public|private|protected] [static] <void|return_type> <method_name> ([paramList]) {
        // 方法体
    }
}

上述代码中一个方法包含 4 部分:方法的返回值、方法名称、方法的参数和方法体。其中:

1. 返回类型

return_type 是方法返回值的数据类型,数据类型可以是原始的数据类型,也可以一个引用数据类型,如一个类、接口和数组等。除了这些,一个方法还可以没有返回值,即返回类型为 void

若方法有返回值,则在方法体中用 return 语句指明要返回的值,格式如下:

return 表达式
// OR
return (表达式)

其中,表达式可以是常量、变量、对象等。

*注:表达式的数据类型必须与声明成员方法时给出的返回值类型一致。

2. 参数列表

paramList 表示参数列表,这些变量都要有自己的数据类型,可以是原始类型,也可以是复杂数据类型,一个方法主要依靠参数来传递消息。方法主体是方法中执行功能操作的语句。

*注:形参是定义方法时参数列表中出现的参数,实参是调用方法时为方法传递的参数。

方法的形参和实参具有以下特点:

(1)形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只有在方法内部才有效,方法调用结束返回主调方法后则不能再使用该形参变量。

一切的机理,最终都要回到内存去解释,也只有如此,才能理解它的本质。

(2)实参可以是常量、变量、表达式、方法等,无论实参是何种类型的量,在时行方法调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此,应预先用赋值、输入等办法使实参获得确定的值。

(3)实参和形参在数量、类型和顺序上应严格一致,否则 会发生“类型不匹配”的错误。

(4)方法调用中发生的数据传送是单向的,即只能把实参的值传送给形参,而不能把形参的值反射地传送给实参。因此,在方法调用过程中,形参的值发生改变,而实参的值不会变化。

3. 修饰符

其他各修饰符的含义如下:

  • public、private、protected :表示成员方法的访问权限;
  • static :表示限定该成员方法为静态方法;
  • final :表示限定该成员方法不能被重写或重载;
  • abstract :表示限定该成员方法为抽象方法(不提供具体的实现,并且所属类型必须为抽象类)。

4.成员方法调用

在调用成员方法时应注意以下 4 点:

(1)对无参成员方法来说,是没有实际参数列表的,但方法名后的括号不能省略。

(2)对带参数的成员方法来说,实参的个数、顺序以及它们的数据类型必须与形式参数的个数、顺序以及它们的数据类型保持一致,各个实参间用逗号分隔,实参名与形参名可同可不同。

(3)实参也可以是表达式,此时一定要注意使表达式的数据类型与形参的数据类型相同,或者使表达式的类型按 Java 类型转换规则达到形参指明的数据类型。

(4)实参变量对形参变量的数据传递是“值传递”,程序在执行到调用成员方法时,Java 把实参值复制到一个临时的存储区(栈)中,形参有任何修改都在栈中进行,当退出该成员方法时,Java 自动清除栈中的内容。

5. 方法体中的局部变量

在方法体内可以定义本方法所使用的变量,称为局部变量。它的生存期与作用域是在本方法内,就开本方法则会被自动释放。

在方法体内定义变量时,变量前不能加修饰符。局部变量在使用前必须明确赋值,否则编译时会出错。

另外,在一个方法内部,可以在复合语句(把多个语句用括号 {} 括起来组成的一个语句称复合语句)中定义变量,这些变量只在复合语句中有效。

其实,说了那么多,说到底不过是一个作用域的问题,作用域的还是要回归到内存中去找寻终极答案。

this

this 关键字是 Java 常用的关键字,可用于任何实例方法内指向当前对象,也可指向对其调用当前方法的对象,或者在需要当前类型对象引用时使用。

1. this.属性名

大部分时候,普通方法访问其他方法、成员变量时无须使用 this 前缀,但如果方法里有个局部变量和成员变量同名,但程序又需要在该方法里访问这个被覆盖的成员变量,则必须使用 this 前缀。

来看个例子。

 1: public class Teacher {
 2:     private String name;
 3:     private double salary;
 4:     private int age;
 5: 
 6:     public Teacher(String name, double salary, int age) {
 7:         this.name = name;
 8:         this.salary = salary;
 9:         this.age = age;
10:     }
11: }

在 Teacher 类的构造方法中使用了 this 关键字对属性 name、salary、age 赋值, this 表示当前对象。如 this.name=name 语句表示一个赋值语句,等号左边的 this.name 是指当前对象具有的变量 name ,等号右边的 name 表示参数传递过来的数值。

*注:当一个类的属性(成员变量)名与访问该属性的方法参数名相同时,则需要使用 this 关键字来访问类中的属性,以区分类的属性和方法中的参数。

2. this.方法名

this 关键字最大的作用就是让类中一个方法,访问类里的另一个方法或实例变量。

this 可以代表任何对象,当 this 出现在某个方法中时,它所代表的对象是不确定的,但它的类型是确定的,它所代表的只能是当前类的实例。只有当这个方法被调用时,它所代表的对象被确定下来,谁在调用这个方法, this 就代表谁。

这话怎么读着有点抽象…… 来看一个具体的例子吧。很多时候都是这样,当你对所知理论感到困惑的时候,就回归到它所描述的具体的事物吧,因为那才是具体的、可感知的。

假设定义了一个 Dog 类,这个 Dog 对象的 run() 方法需要调用它的 jump() 方法,Dog 类的代码如下所示:

 1: /**
 2:  * 第一种定义 Dog 类方法
 3:  **/
 4: public class Dog {
 5:     // 定义一个 jump() 方法
 6:     public void jump() {
 7:         System.out.println("正在执行 jump 方法");
 8:     }
 9: 
10:     // 定义一个 run() 方法,run() 方法需要借助 jump() 方法
11:     public void run() {
12:         Dog d = new Dog();
13:         d.jump();
14:         System.out.println("正在执行 run 方法");
15:     }
16: }

像上面这种方式来定义这个 Dog 类,确实可以实现在 run() 方法中调用 jump() 方法。下面再提供一个程序来创建 Dog 对象,并调用该对象的 run() 方法。

1: public class DogTest {
2:     public static void main(String[] args) {
3:         // 创建 Dog 对象
4:         Dog dog = new Dog();
5:         // 调用 Dog 对象的 run() 方法
6:         dog.run();
7:     }
8: }

你看,在上面的程序中,一共产生了两个 Dog 对象,在 Dog 类的 run() 方法中,程序创建了一个 Dog 对象,并使用名为 d 的引用变量来指向该 Dog 对象。在 DogTest 的 main() 方法中,程序再次创建了一个 Dog 对象,并使用名为 dog 的引用变量来指向该 Dog 对象。

下面我们思考两个问题。

(1)在 run() 方法中调用 jump() 方法时是否一定需要一个 Dog 对象?

答案的肯定的,因为没有使用 static 修饰的成员变量和方法都必须使用对象来调用。

(2)是否一定需要重新创建一个 Dog 对象?

不一定,因为当程序调用 run() 方法时,一定会提供一个 Dog 对象,这样就可以直接使用这个已经存在的 Dog 对象,而无须重新创建新的 Dog 对象了。因此需要在 run() 方法中获得调用该方法的对象,通过 this 关键字就可以满足这个要求。

this 可以代表任何对象,当 this 出现在某个方法体中时,它所代表的对象是不确定的,但它的类型是确定的,它所代表的只能是当前类的实例。只有当这个方法被调用时,它所代表的对象才被确定下来,谁在调用这个方法,this 就代表谁。

现在再看这段话,是不是容易理解一些了呢?好的,让我们继续。

将前面的 Dog 类的 run() 方法改为如下形式会更加合适, run() 方法代码修改如下:

 1: /**
 2:  * 第二种定义 Dog 类方法
 3:  **/
 4: 
 5: // 定义一个 run() 方法,run() 方法需要借助 jump() 方法
 6: public void run() {
 7:     // 使用 this 引导调用 run() 方法的对象
 8:     this.jump();
 9:     System.out.println("正在执行 run 方法");
10: }

其实想一下,也不难明白,在第一种 Dog 类定义中,在 Dog 对象的 run() 方法内重新创建了一个新的 Dog 对象,并调用它的 jump() 方法,这意味着一个 Dog 对象的 run() 方法需要依赖于另一个 Dog 对象的 jump() 方法,这显然是不符合逻辑的。

第二种 Dog 类定义是当一个 Dog 对象调用 run() 方法时, run() 方法需要依赖它自己的 jump() 方法,与第一个定义类的方法相比,更符合实际情形。

在现实世界里,对象的一个方法依赖于另一个方法的情形很常见。例如,吃饭方法依赖于拿筷子方法,写程序方法依赖于敲键盘方法。这种依赖都是同一个对象两个方法之间的依赖。因此,Java 允许对象的一个成员直接调用另一个成员,可以省略 this 前缀。也就是说,将上面的 run() 方法必为如下形式也完全正确。

1: public void run() {
2:     jump();
3:     System.out.println("正在执行 run 方法");
4: }

*注:大部分时候,一个方法访问该类中的定义的其他方法、成员变量时加不加 this 前缀的效果的是完全一样的。

对于 static 修饰的方法而言,可以使用类来直接调用该方法,如果在 static 修饰的方法中使用 this 关键字,则这个关键字就无法指向合适的对象。所以, static 修饰的方法中不能使用 this 引用。 并且 Java 语法规定,静态成员不能直接访问非静态成员。

*注:省略 this 前缀只是一种假象,虽然程序员省略了调用 jump() 方法之前的 this ,但实际上这个 this 依然的存在的。

3. this() 访问构造方法

this() 用来访问本类的构造方法(构造方法是类的一种特殊方法,方法名称和类名相同,没有返回值),括号中可以有参数,如果有参数就是调用指定的有参构造方法。

*注: this() 不能在普通方法中使用,只能写在构造方法中;且在构造方法中使用时,必须是第一条语句。

 1: public class Student {
 2:     String name;
 3: 
 4:     // 无参构造方法
 5:     public Student() {
 6:         this("张三");
 7:     }
 8: 
 9:     // 有参构造方法
10:     public Student(String name) {
11:         this.name = name;
12:     }
13: 
14:     // 输出 name 和 age
15:     public void print() {
16:         System.out.println("姓名:" + name);
17:     }
18: 
19:     public static void main(String[] args) {
20:         Student stu = new Student();
21:         stu.print();
22:     }
23: }
24: // → 姓名:张三

对象

对象是对类的实例化。对象具有状态和行为,变量用来表明对象的状态,方法表明对象所具有的行为。Java 对象的生命周期包括创建、使用和清除。

对象创建

在 Java 语言中创建对象分显示创建与隐含创建两种情况。

1. 显示创建对象

对象的显示创建方式有 4 种。

1.1 使用 new 关键字创建对象

语法格式如下:

类名 对象名 = new 类名();

1.2 调用 java.lang.Class 或者 java.lang.reflect.Constructor 类的 newInstance() 实例方法

代码格式如下:

java.lang.Class Class类对象名称 = java.lang.Class.forName(要实例化的类全称);

类名 对象名 = (类名)Class类对象名称.newInstance();

调用 java.lang.Class 类中的 forName() 方法时,需要将要实例化的类的全称(比如 com.mxl.package.Student )作为参数传递过去,然后再调用 java.lang.Class 类对象的 newInstance() 方法创建对象。

1.3 调用对象的 clone() 方法

该方法不常用,使用该方法创建对象时,要实例化的类必须继承 java.lang.Cloneable 接口,语法格式如下:

类名对象名 = (类名)已创建好的类对象名.clone();

1.4 调用 java.io.ObjectInputStream 对旬的 readObject() 方法

创建方式千千万,掌握最基本的就可以了。
 1: public class Student implements Cloneable {
 2:     // 实现 Cloneable 接口
 3:     private String Name;
 4:     private int age;
 5: 
 6:     public Student(String name, int age) {
 7:         // 构造方法
 8:         this.Name = name;
 9:         this.age = age;
10:     }
11: 
12:     public Student() {
13:         this.Name = "name";
14:         this.age = 0;
15:     }
16: 
17:     public String toString() {
18:         return "学生名字:" + Name + ",年龄:" + age;
19:     }
20: 
21:     public static void main(String[] args) throws Exception {
22:         System.out.println("------ 使用 new 关键字创建对象 ------");
23:         Student student1 = new Student("小刘", 22);
24:         System.out.println(student1);
25: 
26:         System.out.println("------ 调用 java.lang.Class 的 newInstance() 方法创建对象 ------");
27:         Class c1 = Class.forName("Student");
28:         Student student2 = (Student)c1.newInstance();
29:         System.out.println(student2);
30: 
31:         System.out.println("------ 调用对象的 clone() 方法创建对象 ------");
32:         Student student3 = (Student)student2.clone();
33:         System.out.println(student3);
34:     }
35: }

我们对上述示例作一下说明:

(1)使用 new 关键字或 Class 对象的 newInstance() 方法创建对象时,都会调用类的构造方法。

(2)使用 Class 类的 newInstance() 方法创建对象时,会调用类的默认构造方法,即无参构造方法。

(3)使用 Object 类的 clone() 方法创建对象时,不会调用类的构造方法,它会创建一个复制的对象,这个对象和原来的对象具有不同的内存地址,但它们的属性值相同。

(4)如果类没有实现 Cloneable 接口,则 clone() 方法会抛出 java.lang.CloneNotSupportedException 异常。

上述程序执行结果如下:

------ 使用 new 关键字创建对象 -------
学生名字:小刘,年龄:22
------ 调用 java.lang.Class 的 newInstance() 方法创建对象 --------
学生名字:name,年龄:0
------ 调用对象的done()方法创建对象 -------
学生名字:name,年龄:0

2. 隐含创建对象

除了显示创建对象以外,在 Java 程序中还可以隐含地创建对象。如下面这几种情况:

(1) String strName = "strValue" ,其中的 strValue 就是一个 String 对象,由 Java 虚拟机隐含地创建。

(2)字符串的 + 运算符的结果为一个新的 String 对象。

1: String str1 = "Hello";
2: String str2 = "Java";
3: String str3 = str1 + str2;      // str3 引用一个新的 String 对象

(3)当 Java 虚拟机加载一个类时,会隐含地创建描述这个类的 Class 实例。

*注:类的加载是指把类的 .class 文件中的二进制数据读入内存中,把它存放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class 对象,用来封装类在方法区的数据结构。

无论采用哪种方式创建对象,Java 虚拟机在创建一个对象时都包含以下步骤:

  • 给对象分配内存;
  • 将对象的实例变量自动初始化为其变量类型的默认值;
  • 初始化对象,给实例变量赋予正确的初始值。

*注:每个对象都是相互独立的,在内存中占有独立的内存地址,并且每个对象都具有自己的生命周期,当一个对象的生命周期结束时,对象就变成了垃圾,由 Java 虚拟机自带的垃圾回收机制处理。

匿名对象

前面我们已经知道,创建对象的标准格式如下:

类名称 对象名 = new 类名称();

每次 new 都相当于开辟了一个新的对象,并开辟了一个新的物理空间。

匿名对象就是没有明确给出名字的对象,是对象的一种简写形式。一般匿名对象只使用一次,而且匿名对象只在堆内存中开辟空间,而不存在栈内存的引用,在使用一次后就等待被 GC(垃圾收集机制)回收。

匿名对象在实际开发中基本都是作为其他类实例化对象的参数传递的,它实际上就是个堆内存空间。对象不管匿名与否,都必须在开辟空间之后才可以使用。

属性行为

每个对象都有自己的属性和行为,这些属性和行为在类中体现为成员变量和成员方法,其中成员变量对应对象的属性,成员方法对应对象的行为。

在 Java 中,要引用对象的属性和行为,需要使用点( . )操作符来访问。对象名在圆点左边,而成员变量或成员方法的名称在圆点的右边。语法格式如下:

  对象名.属性(成员变量)    // 访问对象的属性
  对象名.成员方法名()      // 访问对象的方法

例如,定义一个 Student 类,创建该类的对象 stu ,再对该对象的属性赋值,代码如下:

1: tudent stu = new Student();    // 创建 Student 类的对象 stu
2: stu.Name = "李子文";           // 调用stu对象的Name属性并赋值
3: stu.Sex = true;                // 调用stu对象的Sex属性并赋值
4: stu.Age = 15;                  // 调用stu对象的Age属性并赋值

如果一个对象要被使用,则对象必须被实例化,如果一个对象没有被实例化而直接调用了对象中的属性或方法,如下代码所示:

1: tudent stu = null;
2: stu.Name = "李子文";
3: stu.Sex = true;
4: stu.Age = 15;

则程序运行时会出现以下异常:

Exception in thread "main" java.lang.NullPointerException

*注:此异常是开发中最常见的异常,也会始终伴随着每位开发人员,使用了未实例化的对象则肯定会出现此异常。

嗯,这件事还挺扯淡的…… ^_||

对象销毁

对象使用完之后需要对其进行清除(释放对象占用的内存)。在创建对象时,用户必须使用 new 操作符为对象分配内存,不过,Java 中在清除对象时,由系统自动进行回收,不需要用户额外处理。

Java 语言的内存自动回收称为 垃圾回收(Garbage Collection,GC)机制 ,是指 JVM 用于释放那些不再使用的对象所占用的内存。

其实,Java 语言并不要求 JVM 有 GC ,也没有规定 GC 如何工作,不过常用的 JVM 都有 GC,而且大多数 GC 都使用类似的算法管理内存和执行回收操作。

*注:C++ 语言对象是通过 delete 语句手动释放。如果回收内存的的任务由程序负责,也就是说必须在程序中显式地进行内存回收,这无疑增加程序员负担,而且存在很多弊端。Java 语言对象是由垃圾回收器收集然后释放,程序员不用关心释放的细节。自动内存管理是现代计算机语言发展的趋势,例如:C# 语言的垃圾回收,Object-c 和 Swift 语言的 ARC(内存自动引用计数管理)。

一个对象被当作垃圾回收的情况主要如下两种:

  • 对象的引用超过其作用范围;
  • 对象被赋值为 null

在 Java 的 Object 类中还提供了一个 protected 类型的 finalize() 方法,因此任何 Java 类都可以覆盖这个方法,在这个方法中进行释放对象所占有的相关资源的操作。

在 Java 虚拟机的堆区,每个对象都可能处于以下三种状态之一。

(1)可触及状态:当一个对象被创建后,只要程序中还有引用变量引用它,那么它就始终处于可触及状态。

(2)可复活状态:当程序不再有任何引用变量引用该对象时,该对象就进入可复活状态。在这个状态下,垃圾回收器会准备释放它所占用的内存,在释放之前,会调用它及其他处于可复活状态的对象的 finalize() 方法,这些 finalize() 方法有可能使该对象重新到可触及状态。

(3)不可触及状态:当 Java 虚拟机执行完可复活对象的 finalize() 方法后,如果这些方法都没有使该对象转到可触及状态,垃圾回收器才会真正回收它占用的内存。

*注:调用 System.gc() 或者 Runtime.gc() 方法也不能保证回收操作一定执行,它只是提高了 Java 垃圾回收器尽快回收垃圾的可能性。

TODO 注释(类、方法、字段)

访问控制修饰符

信息隐藏,是 OOP 最重要的功能之一,也是使用访问修饰符的原因。在编写程序时,有些核心数据往往不希望被用户调用,需要控制这些数据的访问。

访问控制符是一组限定类、属性或方法是否可以被程序里的其他部分访问和调用的修饰符,如下:

  • 类的控制符只能是空或者 public
  • 方法和属性的访问控制符有 4 个,分别是 public、private、protected、friendly

*注:其中 friendly 是一种没有定义专门的访问控制符的默认情况。

通过使用访问控制修饰符来限制对对象私有属性的访问,可以获得 3 个重要好处:

  • 防止对封装数据的未授权访问;
  • 有助于保证数据完整性;
  • 当类的私有实现细节发生改变时,可以限制发生整个应用程序中的“连锁反应”。
Table 1: 各种访问修饰符的可访问性
访问范围 private friendly(默认) protected public
同一个类
同一包中的其他类
不同包中的子类
不同包中的非子类

*注:类中被设定为 public 的方法是这个类对外的接口部分,避免了程序的其他部分直接去操作类内的数据,实际就是数据封装思想的体现。

所谓访问控制修饰符,说白了,就是禁止你使用业务以后可能需要修改的数据,否则就会造成“你已经用了,我改了,你的就可能出现异常”这种状况。

另外,所谓控制就是基于作用域的概念,从这个角度就很容易理解这些修饰符了。比如,private 就是类作用域;friendly 就是包作用域,之所以友好就是你不用去写它也可以(毕竟默认就是它);protected 就是继承链作用域了,俗称一条绳子上的蚂蚱;public 很容易理解了,就是公共的。

static

在类中,使用 static 修饰符修饰的属性(成员变量)称为静态变量,也可以称为类变量;常量称为静态常量;方法称为静态方法或类方法。它们统称为 静态成员 ,归整个类所有。

静态成员不依赖于类的特定实例,被类的所有实例共享,就是说 static 修饰的方法或者变量不需要依赖于对象来进行访问,只要整个类被加载,Java 虚拟机就可以根据类名找到它们。语法格式如下:

类名.静态成员

注意:

  • static 修饰的成员变量和方法,从属于类;
  • 普通变量和方法从属于对象;
  • 静态方法不能调用非静态成员,编译会报错。
为什么静态方法不能调用非静态成员呢?因为,非静态成员需要类实例化对象后才有效,而静态方法是可以通过类直接调用的。

1. 静态变量

类的成员变量可以分为两种:静态变量(AKA 类变量,被 static 修饰)和实例变量。

静态变量和实例变量的区别如下:

(1)静态变量:

  • 运行时,Java 虚拟机只为静态变量分配一次内存,在加载类的过程中完成静态变量的内存分配;
  • 在类的内部,可以在任何方法内直接访问静态变量;
  • 在其他类中,可经通过类名访问该类的静态变量。

(2)实例变量:

  • 每创建一个实例,Java 虚拟机就会为实例变量分配一次内存;
  • 在类的内部,可以在非静态方法中直接访问实例变量;
  • 在本类的静态方法或其他类中则需要通过类的实例对象进行访问。

不难看出,静态变量在类中的作用如下:

  • 静态变量可以被类的所有实例共享,因此静态变量可能作为实例之间的共享数据,增加实例之间的交互性;
  • 如果类的所有实例都包含一个相同的常量属性,则可以把这个属性定义为静态常量类型,从而节省内存空间。

2. 静态方法

与成员变量类似,成员方法也可以分为两种:静态方法(AKA 类方法,被 static 修饰)和实例方法。

静态方法与实例方法的区别如下:

(1)静态方法不需要通过它所属的类的任何实例就可以被调用,因此在静态方法中 不能使用 this 关键字 ,也不能直接访问所属类的实例变量和实例方法,但是可以直接访问所属类的静态变量和静态方法。另外,和 this 关键字一样, super 关键字也与类的特定实例相关,所以静态方法中也不能使用 super 关键字。

(2)在实例方法中可以直接访问所属类的静态变量、静态方法、实例变量和实例方法。

*注:在访问静态方法时,一般直接通过类名来访问,也可以通过实例化对象来访问(本质上还是通过类名来访问)。

3. 静态代码块

静态代码块指 Java 类中的 static{} 代码块,主要用于初始化类,为类的静态变量赋初始值,提升程序性能。

静态代码块的特点如下:

  • 静态代码块类似于一个方法,但它不可以存在于任何方法体中;
  • 静态代码可以置于类中的任何地方,类中可以有多个静态初始化块;
  • JVM 在加载类时执行静态代码块,所以很多时候会将一些只需要进行一次的初始化操作都放在 static 代码块中进行;
  • 如果类中包含多个静态代码块,则 JVN 将按它们在类中出现的顺序依次执行它们,每个静态代码块只会被执行一次;
  • 静态代码块与静态方法一样,不能直接访问类的实例变量和实例方法。

来看个例子吧。

 1: public class StaticCode {
 2:     public static int count = 0;
 3: 
 4:     {
 5:         count++;
 6:         System.out.println("非静态代码块 count=" + count);
 7:     }
 8: 
 9:     static {
10:         count++;
11:         System.out.println("静态代码块1 count=" + count);
12:     }
13: 
14:     static {
15:         count++;
16:         System.out.println("静态代码块2 count=" + count);
17:     }
18: 
19:     public static void main(String[] args) {
20:         System.out.println("*** StaticCode1 执行 ***");
21:         StaticCode sct1 = new StaticCode();
22:         System.out.println("*** StaticCode2 执行 ***");
23:         StaticCode sct2 = new StaticCode();
24:     }
25: }

执行结果如下:

静态代码块1 count=1
静态代码块2 count=2
***StaticCode1 执行***
非静态代码块 count=3
***StaticCode2 执行***
非静态代码块 count=4

上述代码中 { } 代码块为非静态代码块,非静态代码块是在创建对象时自动执行的代码,不创建对象不执行该类的非静态代码块。代码域中定义的变量都是局部的,只有域中的代码可以调用。

静态导入

在 JDK 1.5 之后增加了一种静态导入的语法,用于导入指定类的某个或全部静态成员变量、方法。如果一个类中的方法全部是使用 static 声明的静态方法,则在导入时就可以直接使用 import static 的方式导入。

静态导入使用 import static 语句,静态导入也有两种语法:

(1)导入指定类的单个静态成员变量、方法,语法格式如下:

import static package.ClassName.fieldName|methodName;

(2)导入指定类的全部静态就是、方法,语法格式如下:

import static package.ClassName.*;

*注: * 代表静态成员变量或方法名。

用一句话来归纳 importimport static 的作用,使用 import 可以省略写包名,而使用 import static 可以省略类名。

来看个例子吧,如下:

 1: import static java.lang.System.*;
 2: import static java.lang.Math.*;
 3: 
 4: public class StaticImportTest {
 5:     public static void main(String[] args) {
 6:         // out 是 java.lang.System 类的静态成员变量,代表标准输出
 7:         // PI 是 java.lang.Math 类的静态成员变量,表示 π 常量
 8:         out.println(PI);
 9:         // 直接调用 Math 类的静态方法
10:         out.println(sqrt(256));
11:     }
12: }

从上面程序不难看出, importimport static 的功能非常相似,只是它们导入的对象不一样而已。 import 语句和 import static 语句都是用于减少程序中代码编写量的。

final

final 应用于类、方法和变量时意义是不同的,但本质是一样的,都表示不可改变,类似 C# 里的 sealed 关键字。

(1) final 用在变量的前面表示变量的值不可以改变,此时该变量可以被称为常量。

(2) final 用在方法的前面表示方法不可以被重写。

*注:子类中如果创建了一个与父类中相同名称、相同返回类型、相同参数列表的方法,只是方法体中的实现不同,以实现不同于父类的功能,这种方式称为 方法重写 ,又称为方法覆盖。

(3) final 用在类的前面表示该类不能有子类,即该类不可以被继承。

1. final 修饰变量

final 修饰的变量即成为常量,只能赋值一次,但是 final 所修饰局部变量和成员变量有所不同,如下:

  • final 修饰的局部变量必须使用之前被赋值一次才能使用;
  • final 修饰的成员变量在声明时没有赋值的叫“空白 final 变量”,它必须在构造方法或静态代码块中初始化。
空白 final 实例变量在构造方法中初始化;空白 final 静态变量在静态代码块中初始化。

*注: final 修饰的变量不能被赋值这种说法是错误的,严格的说法是, final 修饰的变量不可被改变,一旦获得了初始值,该 final 变量的值就不能被重新赋值。

 1: public class FinalDemo {
 2:     void doSomething() {
 3:         // 没有在声明的同时赋值
 4:         final int e;
 5:         // 只能赋值一次
 6:         e = 100;
 7:         System.out.println(e);
 8:         // 声明的同时赋值
 9:         final int f = 200;
10:     }
11: 
12:     // 实例常量
13:     final int a = 5;            // 直接赋值
14:     final int b;                // 空白 final 变量
15:     // 静态常量
16:     final static int c = 12;    // 直接赋值
17:     final static int d;         // 空白 final 变量
18:     // 静态代码块
19:     static {
20:         // 初始化静态变量
21:         d = 32;
22:     }
23: 
24:     // 构造方法
25:     FinalDemo() {
26:         // 初始化实例变量
27:         b = 3;
28:         // 第二次赋值,会发生编译错误
29:         // b = 4;
30:     }
31: 
32: }

final 修饰基本类型变量和引用类型变量的区别

当使用 final 修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变。但对于引用类型变量而言,它保存的仅仅是一个引用, final 只保证这个引用类型变量所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变。

*注:在使用 final 声明变量时,要求全部的字母大写,这是一种好习惯。

2. final 修饰方法

final 修饰的方法不可被重写,如果出于某些原因,不希望任何类重写这个方法,则可以使用 final 修饰该方法。

Java 提供的 Object 类里就有一个 final 方法 getClass() ,因为 Java 不希望任何类重写这个方法,所以使用 final 把这个方法密封起来。但对于该类提供的 toString()equals() 方法,都允许子类重写,因此没有使用 final 修饰它们。

*注:试图重写 final 方法,会引发编译错误。

 1: public class FinalMethodTest {
 2:     public final void test() {
 3:     }
 4: }
 5: 
 6: class Sub extends FinalMethodTest {
 7:     // 下面方法定义将出现编译错误,不能重写 final 方法
 8:     public void test() {
 9:     }
10: }

对于一个 private 方法,因为它仅在当前类中可见,其子类无法访问该方法,所以子类无法重写该方法 – 如果子类中定义一个与父类 private 方法有相同方法名、相同形参列表、相同返回值类型的方法,也不是方法重写,只是重新定义了一个新方法。因此,即使使用 final 修饰一个 private 访问权限的方法,依然可以在子类中定义与该方法具有相同方法名、相同形参列表、相同返回值类型的方法。

final 修饰的方法仅仅是不能被重写,并不是不能被重载 ,如下:

1: public class FinalOverload {
2:     // final 修饰的方法只是不能被重写,完全可以被重载
3:     public final void test() {}
4:     public final void test(String arg) {}
5: }
方法重载 - 同类同名不同参,一般同返回;
方法重写 - 用在继承中,异类同名同参同返回。

final 修饰类中的方法,说明这种方法提供的功能已经满足当前要求,不需要进行扩展,并且也不允许从此类继承的类来重写这种方法,但是继承仍然可以继承这个方法,也就是说可以直接使用。在声明类中,一个 final 方法只被实现一次。

3. final 修饰类

final 修饰的类不能被继承,当子类继承父类时,将可以访问到父类内部数据,并可通过重写父类方法来改变父类方法的实现细节,这可能导致一些不安全的因素。

1: final class SuperClass {
2: }
3: 
4: class SubClass extends SuperClass { // 编译错误
5: }

final 修饰类是无法被任何其他类继承的,意味着此类在一个继承树中是一个叶子类,并且此类的设计已被认为很完美而不需要进行修改或扩展。

对于 final 类中的成员,可以定义其为 final ,也可以不是 final 。而对于方法,由于所属类为 final 关系,自然也就成了 final 型。当然,也可以明确地给 final 类中的方法加上一个 final ,但显然没有任何意义。

main()

在 Java 中, main() 方法是 Java 应用程序的入口方法,程序在运行的时候,第一个执行的方法就是 main() 方法,它和其他的方法有很大的不同。

1: public class HelloWorld {
2:     public static void main(String[] args) {
3:         System.out.println("Hello World!");
4:     }
5: }

其中,使用 main() 方法时应该注意如下几点:

  • 访问控制权限是公有的( public );
  • 该方法是静态的 ( static )。如果要在该方法中调用本类中的其他方法,则调用的其他方法也必须是静态的,否则需要先使用创建本类的实例对象,然后再通过对象调用成员方法;
  • 该方法没有返回值,只能使用 void
  • 该方法具有一个字符串参数,用来 接收执行 Java 程序的命令行参数 。命令行参数作为字符串,按照顺序依次对应字符串数组中的元素;
  • 字符串数组的(代码中的 args )可以任意设置,但一般约定为 args
  • 它的定义必须是 public static void main(String[] args)
  • 一个类只能有一个 main() 方法,这是一个常用于对类进行单元测试(对软件中的最小可测试单元进行检查和验证)的技巧。

下面我们来一个关于 args 的例子:

 1: public class TestMain {
 2:     public static void main(String[] args) {
 3:         int n = args.length;    // 获取参数数量
 4:         System.out.println("一共有 " + n + " 个参数");
 5:         if (n > 0) {
 6:             // 判断参数个数是否大于 0
 7:             for (int i = 0; i < n; i++) {
 8:                 System.out.println(args[i]);
 9:             }
10:         }
11:     }
12: }

假设我们编译上述程序后执行如下 Java 命令:

java TestMain                            // 一共有 0 个参数
java TestMain apple banana               // 一共有 2 个参数
java TestMain one two three for five six // 一共有 6 个参数

可见, main() 方法可以以字符串的形式接收命令行参数,然后在方法体内进行处理。

可变参数

在具体实际开发过程中,有时方法在参数的个数是不确定的,这时就需要可变参数了,其语法格式如下:

methodName({paramList}, paramType...paramName)

其中:

  • methodName 表示方法名称;
  • paramList 表示方法的固定参数列表;
  • paramType 表示可变参数的类型;
  • ... 是声明可变参数的标识;
  • paramName 表示可变参数名称。

*注:可变参数必须定义在参数列表的最后。

 1: public class StudentTestMethod {
 2:     public void print(String...names) {
 3:         int count = names.length;
 4:         System.out.println("本次参加考试的有 " + count + " 人,名单如下:");
 5:         for(int i = 0; i < names.length; i++) {
 6:             System.out.println(names[i]);
 7:         }
 8:     }
 9: 
10:     public static void main(String[] args) {
11:         StudentTestMethod student = new StudentTestMethod();
12:         student.print("Amy", "Lucy", "Jessica");
13:         student.print("小明", "小红");
14:     }
15: }
16: 

运行结果如下:

本次参加考试的有 3 人,名单如下:
Amy
Lucy
Jessica
本次参加考试的有 2 人,名单如下:
小明
小红

构造方法

构造方法是类的一种特殊方法,用来初始化类的一个新的对象,在创建对象( new 运算符 )之后自动调用。Java 中的每个类都有一个默认构造方法,并且可以有一个以上的构造方法。

Java 构造方法有以下特点:

  • 方法名必须与类名相同;
  • 可以有 0 个、1 个或多个参数;
  • 没有任何返回值,包括 void
  • 默认返回类型就是对象类型本身;
  • 只能与 new 运算符结合使用。

*注:如果为构造方法定义了返回值类型或使用 void 声明构造方法没有返回值,编译时不会出错,但 Java 会把这个所谓的构造方法当成普通方法来处理。

构造方法不是没有返回值吗?为什么不能用 void 声明呢?

简单地说,这是 Java 的 语法规定

实际上,类的构造方法是有返回值的,当使用 new 关键字来调用构造方法时,构造方法返回该类的实例,可以把这个类的实例当成构造器的返回值,因此构造器的返回值类型总是当前类,无须定义返回值类型。但必须注意不要在构造方法里使用 return 来返回当前类的对象,因为构造方法的返回值是隐式的。

构造方法不能被 staic、final、synchronized、abstractnative (类似于 abstract )修饰。

构造方法用于初始化一个新对象,所以用 static 修饰没有意义。构造方法不能被子类继承,所以用 finalabstract 修饰没有意义。多个线程不会同时创建内存地址相同的同一个对象,所以用 synchronized 修饰没有必要。

构造方法的语法格式如下:

public ClassName {
    public ClassName() {}            // 默认无参构造方法
    public ClassName([paramList]) {} // 定义有参构造方法
    ...
    // 类主体
}

*注:类的构造方法不是必须定义的。如果在类中没有定义任何一个构造方法,则 Java 会自动为该类生成一个默认的构造方法。默认的构造方法不包含任何参数,并且方法体为空。如果类中显示地定义了一个或多个构造方法,则 Java 不再提供默认构造方法。

*提示:无参数的构造方法也被称为 Nullary 构造方法。只有编译程序自动加入的构造方法,才称为默认构造函数。如果自行编写无参数、没有内容的构造函数,就不称为默认构造函数了(只是 Nullary 构造函数)。

要在不同的条件下使用不同的初始化行为创建类的对象,这时候就需要在一个类中创建多个构造方法。如下:

 1: public class Worker {
 2:     public String name;
 3:     private int age;
 4:     // 定义带有一个参数的构造方法
 5:     public Worker(String name) {
 6:         this.name = name;
 7:     }
 8:     // 定义带有两个参数的构造方法
 9:     public Worker(String name, int age) {
10:         this.name = name;
11:         this.age = age;
12:     }
13:     public String toString() {
14:         return "大家好!我叫" + name + ",今年" + age + "岁。";
15:     }
16: }

*提示:Object 类具有一个 toString() 方法,该方法是个特殊的方法,创建的每个类都会继承该方法,它返回一个 String 类型的字符串。如果一个类中定义了该方法,则在调用该类对象时,将会自动调用该类对象的 toString() 方法返回一个字符串,然后使用 System.out.println(对象名) 就可以将返回的字符串内容打印出来。

通过调用带参数的构造方法,在创建对象时,一并完成了对象成员的初始化工作,简化了对象初始化的代码。

析构方法

析构方法与构造方法相反,当对象脱离其作用域时(例如对象所在的方法已调用完毕),系统自动执行析构方法。析构方法往往用来做清理垃圾碎片的工作,例如在建立对象时用 new 开辟了一片内存空间,应退出前在析构方法中将其释放。

在 Java 的 Object 类中还提供了一个 protected 类型的 finalize() 方法,因此任何 Java 类都可以覆盖这个方法,在这个方法中进行释放对象所占有的相关资源的操作。

对象的 finalize() 方法具有如下特点:

  • 垃圾回收器是否会执行该方法以及何时执行该方法,都是不确定的;
  • finalize() 方法有可能使用对象复活,使对象恢复到可触及状态;
  • 垃圾回收器在执行 finalize() 方法时,如果出现异常,垃圾回收器不会报告异常,程序继续正常运行。

来看个例子吧,如下:

 1: public class Counter {
 2:     private static int count = 0; // 计数器变量
 3:     public Counter() {
 4:         // 构造方法
 5:         this.count++;             // 创建实例时增加值
 6:     }
 7:     public int getCount() {
 8:         return this.count;
 9:     }
10:     protected void finalize() {
11:         // 析构方法
12:         this.count--;             // 实例销毁时减少值
13:         System.out.println("对象销毁");
14:     }
15: }

下面创建一个对应的测试类:

 1: public class TestCounter {
 2:     public static void main(String[] args) {
 3:         Counter cnt1 = new Counter();
 4:         System.out.println(cnt1.getCount());     // → 1
 5:         Counter cnt2 = new Counter();
 6:         System.out.println(cnt2.getCount());     // → 2
 7:         cnt2 = null;                             // 销毁实例 cnt2
 8: 
 9:         try {
10:             System.gc();                         // 清理内存
11:             Thread.currentThread().sleep(1000);  // 延时 1000 ms
12:             System.out.println(cnt1.getCount()); // → 1
13:         } catch(InterruptedException e) {
14:             e.printStackTrace();
15:         }
16:     }
17: }

*注:由于 finalize() 方法的不确定性,所以在程序中可以调用 System.gc() 或者 Runtime.gc() 方法提示垃圾回收器尽快执行垃圾回收操作。

在编写 Java 程序时,随着程序架构越来越大,类的个数也越来越多。Java 引入了包(package)机制,提供了类的多层命名空间,用于解决类的命名冲突、类文件管理等问题。

包允许将类组合成较小的单元(类似于文件夹),它基本上隐藏了类,并避免名称上的冲突。包的 3 个作用如下:

  • 区分相同名称的类;
  • 能够较好地管理大量的类;
  • 控制访问范围。

1. 包定义

Java 中使用 package 语句定义包, package 语句应该放在源文件的第一行,在每个源文件中只能有一个包定义语句 ,并且 package 语句适用于所有类型(类、接口、枚举和注释)的文件。字义包的语法格式如下:

package 包名;

Java 包的命名规则如下:

  • 包名全部由小写字母(多个单词也全部小写);
  • 如果包名包含多个层次,每个层次用 . 分割;
  • 包名一般由倒置的域名开头,比如 com.baidu
  • 自定义包不能以 java 开头。

*注:如果源文件中没有定义包,那么类、接口、枚举和注释文件将会被放进一个无名的包中,也称为默认包。在实际企业开发中,通常不会把类放在默认包下。

2. 包导入

如果使用不同包中的其它类,需要使用该类的全名(包名+类名),代码如下:

1: example.Test test = new example.Test();

其中, example 是包名, Test 是包中的类名, test 是类的对象。

为了简化编程,Java 引入了 import 关键字, import 可以向某个 Java 文件中导入指定包层次下的某个类或全部类。 import 语句位于 package 语句之后,类定义之前。 一个 Java 源文件只能包含一个 package 语句,但可以包含多个 import 语句

使用 import 导入单个类的语法格式如下:

import 包名+类名;

import example.Test;            // 直接导入指定类
import example.*;               // 导入包下全部类

*注:上面 import 语句中的星号 * 只能代表类,不能代表包。

使用 * 可能会增加编译时间,特别是引入多个大包时,所以明确的导入你想要用到的类是一个好方法,需要注意的是使用星号对运行时间和类的大小没有影响。

通过使用 import 语句可以简化编程,但 import 语句并不是必需的,如果在类里使用其它类的全名,可以不使用 import 语句。

Java 默认为所有源文件导入 java.lang 包下的所有类,因此前面在 Java 程序中使用 String、System 类时都无须使用 import 语句来导入了。相比,前面介绍数组时提到的 Arrays 类,其位于 java.utl 包下,则必须使用 import 语句来导入。

在一些极端的情况下, import 语句也无能为力,只能在源文件中使用类全名。

例如,需要在程序中使用 java.sql 包下的类,也需要使用 java.util 包下的类,如下:

1: import java.util.*;
2: import java.sql.*;

如果接下来在程序中需要使用 Date 类,则会引起如下编译错误:

Test.java:25: 对 Date 的引用不明确,
Java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.Date 都匹配

import 语句导入的 java.sqljava.util 包下都包含了 Date 类,系统不知道使用哪个包下的 Date 类,此时只能使用该类的全名,如下:

1: java.sql.Date d = new java.sql.Date();

3. 系统包

Java SE 提供了一些系统包,其中包含了 Java 开发中常用的基础类。在 Java 语言中,开发人员可以自定义包,也可以使用系统包。

Table 2: Java 中常用的系统包
说明
java.lang Java 的核心类库,包含运行 Java 程序必不可少的系统类,如基本数据类型、基本数学函数、字符串处理、异常处理和线程等,系统默认加载这个包
java.io Java 语言的标准输入/输出类库,如基本输入/输出流、文件输入/输出、过滤输入/输出流等
java.util 包含如处理时间的 Date 类,处理动态数组的 Vector 类,以及 StackHashTable
java.awt 构建图于用户界面(GUI)的类库,低级绘图操作 Graphics 类、图形界面组件和布局管理(如 Checkbox 类、 Container 类、 LayoutManger 接口等),以及用户界面交互控制和事件响应(如 Event 类)
java.awt.image 处理和操作来处网上的图片的 Java 工具类库
java.awt.peer 很少在程序中直接使用到,使得同一个 Java 程序在不同的软硬件平台上运行
java.net 实现网络功能的类库有 Socket 类、 ServerSocket
java.lang.reflect 提供用于反射对象的工具
java.util.zip 实现文件压缩功能
java.awt.datatransfer 处理数据传输的工具类,包括剪贴板、字符串发送器等
java.sql 实现 JDBC 的类库
java.rmi 提供远程连接与载入的支持
java.security 提供安全性方面的有关支持

下面来看一个具体的例子吧。

(1)创建一个名为 com.dao 的包,向 com.dao 包中添加一个 Student 类,该类包含一个 String 类型数组的 GetAll() 方法,代码如下:

1: package com.dao;
2: 
3: public class Student {
4:     public static String[] GetAll() {
5:         String[] namelist = { "Amy", "Peny", "Leonard", "Sheldon", "Howard"};
6:         return namelist;
7:     }
8: }

(2)创建 com.test 包,在该包里创建带 main() 方法的 Test 类。在 main() 方法中遍历 Student 类的 GetAll() 方法中的元素内容,如下:

 1: package com.test;
 2: import com.dao.Student;
 3: 
 4: public class Test {
 5:     public static void main(String[] args) {
 6:         System.out.println("The members of TBBT: ");
 7:         for (String str:Student.GetAll()) {
 8:             System.out.println(str);
 9:             // → The members of TBBT: Amy Peny Leonard Sheldon Howard
10:         }
11:     }
12: }

TODO 递归算法

程序调用自身的编程技巧称为递归(recursion),它做为一种算法在程序设计语言中广泛应用。

Date: 2020-09-21 Mon 18:05

Author: Jack Liu

Created: 2020-09-26 Sat 11:26

Validate