Java类和对象

2023-05-16

文章目录

    • 本章学习要点
  • Java面向对象:对象的概念及面向对象的三个基本特征
    • 对象的概念
    • 面向对象的三大核心特性
        • 继承性
        • 封装性
        • 多态性
  • Java认识类和对象
  • Java类的定义及定义类时可用的关键字
        • 例 1
  • Java类的属性:成员变量的定义和声明
        • 例 1
  • Java创建一个学生类
  • Java成员方法的声明和调用
        • 例 1
        • 1. 成员方法的返回值
        • 2. 形参、实参及成员方法的调用
        • 例 2
        • 例 3
        • 3. 方法体中的局部变量
  • Java this关键字详解(3种用法)
    • this.属性名
        • 例 1
    • this.方法名
        • 例 2
    • this( )访问构造方法
        • 例 3
  • Java创建对象详解(显式创建和隐含创建)
    • 显式创建对象
        • 1. 使用 new 关键字创建对象
        • 2. 调用 java.lang.Class 或者 java.lang.reflect.Constuctor 类的 newlnstance() 实例方法
        • 3. 调用对象的 clone() 方法
        • 4. 调用 java.io.ObjectlnputStream 对象的 readObject() 方法
        • 例 1
    • 隐含创建对象
  • Java new运算符深入剖析
  • Java访问对象的属性和行为
  • Java对象的销毁
  • Java中的空对象(null)是怎么回事?
  • Java注释:类、方法和字段注释
        • 1. 类注释
        • 2. 方法注释
        • 3. 字段注释
  • Java static关键字(静态变量和静态方法)
    • 静态变量
        • 例 1
    • 静态方法
        • 例 2
    • 静态代码块
        • 例 3
  • Java import static静态导入
  • Java static的常见问题和使用误区
    • 使用误区
  • Java final修饰符详解
    • final 修饰变量
        • final 修饰基本类型变量和引用类型变量的区别
    • final修饰方法
    • final修饰类
    • final 修饰符使用总结
        • 1. final 修饰类中的变量
        • 2. final 修饰类中的方法
        • 3. final 修饰类
  • Java main()方法
        • 例 1
  • Java中main()方法的格式为什么是固定不变的?
  • Java方法的可变参数
        • 例 1
  • Java构造方法
        • 例 1
        • 例 2
  • Java包(package)详解
    • 包定义
    • 包导入
    • 系统包
  • Java使用自定义包

转载于:http://c.biancheng.net/java/80/
最早的程序开发使用的是结构化程序设计语言,随着时间的流逝,软件的规模逐渐扩大,使用结构化语言会出现各种弊端,导致无休止地拖延开发周期,产品的质量也不尽如人意。这一切都体现了结构化语言不再适合当前的软件开发。现在程序设计者们将另一种开发思想引入程序中,那就是面向对象开发思想。

面向对象最关键的两个词汇是类与对象,实质上可以将类看作对象的抽象,它定义了对象所具有的属性和方法。学习 Java 语言必须掌握类与对象,这样可以从深层次理解 Java 这种面向对象语言的幵发理念。因此,掌握类与对象是学习 Java 语言的基础,可以使开发人员更好、更快地掌握 Java 编程思想与编程方式。

本章将详细介绍 Java 中类的定义和对象的使用。

本章学习要点

  1. 掌握类的声明和类的成员
  2. 熟悉类的构造方法及其使用
  3. 掌握对象的创建、销毁和使用
  4. 掌握 main() 方法、构造方法和析构方法的使用
  5. 熟悉 this 关键字的使用
  6. 了解系统提供的常用包
  7. 掌握如何声明和使用包

Java面向对象:对象的概念及面向对象的三个基本特征

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

对 Java 语言来说,一切皆是对象。把现实世界中的对象抽象地体现在编程世界中,一个对象代表了某个具体的操作。一个个对象最终组成了完整的程序设计,这些对象可以是独立存在的,也可以是从别的对象继承过来的。对象之间通过相互作用传递信息,实现程序开发。

对象的概念

Java 是面向对象的编程语言,对象就是面向对象程序设计的核心。所谓对象就是真实世界中的实体,对象与实体是一一对应的,也就是说现实世界中每一个实体都是一个对象,它是一种具体的概念。对象有以下特点:

  • 对象具有属性和行为。
  • 对象具有变化的状态。
  • 对象具有唯一性。
  • 对象都是某个类别的实例。
  • 一切皆为对象,真实世界中的所有事物都可以视为对象。

例如,在真实世界的学校里,会有学生和老师等实体,学生有学号、姓名、所在班级等属性(数据),学生还有学习、提问、吃饭和走路等操作。学生只是抽象的描述,这个抽象的描述称为“类”。在学校里活动的是学生个体,即张同学、李同学等,这些具体的个体称为“对象”,“对象”也称为“实例”。

面向对象的三大核心特性

面向对象开发模式更有利于人们开拓思维,在具体的开发过程中便于程序的划分,方便程序员分工合作,提高开发效率。面向对象程序设计有以下优点。

  1. 可重用性:代码重复使用,减少代码量,提高开发效率。下面介绍的面向对象的三大核心特性(继承、封装和多态)都围绕这个核心。
  2. 可扩展性:指新的功能可以很容易地加入到系统中来,便于软件的修改。
  3. 可管理性:能够将功能与数据结合,方便管理。

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

继承性

如同生活中的子女继承父母拥有的所有财产,程序中的继承性是指子类拥有父类的全部特征和行为,这是类之间的一种关系。Java 只支持单继承。

例如定义一个语文老师类和数学老师类,如果不采用继承方式,那么两个类中需要定义的属性和方法如图 1 所示。

img
图 1 语文老师类和数学老师类中的属性和方法

从图 1 能够看出,语文老师类和数学老师类中的许多属性和方法相同,这些相同的属性和方法可以提取出来放在一个父类中,这个父类用于被语文老师类和数学老师类继承。当然父类还可以继承别的类,如图 2 所示。

img
图 2 父类继承示例图

总结图 2 的继承关系,可以用概括的树形关系来表示,如图 3 所示。

img
图 3 类继承示例图

从图 3 中可以看出,学校主要人员是一个大的类别,老师和学生是学校主要人员的两个子类,而老师又可以分为语文老师和数学老师两个子类,学生也可以分为班长和组长两个子类。

使用这种层次形的分类方式,是为了将多个类的通用属性和方法提取出来,放在它们的父类中,然后只需要在子类中各自定义自己独有的属性和方法,并以继承的形式在父类中获取它们的通用属性和方法即可。

提示:C++ 支持多继承,多继承就是一个子类可有多个父类。例如,客轮是轮船也是交通工具,客轮的父类是轮船和交通工具。多继承会引起很多冲突问题,因此现在很多面向对象的语言都不支持多继承。Java 语言是单继承的,即只能有一个父类,但 Java 可以实现多个接口(接口类似于类,但接口的成员没有执行体。详细了解可参考《Java接口》一节),可以防止多继承所引起的冲突问题。

封装性

封装是将代码及其处理的数据绑定在一起的一种编程机制,该机制保证了程序和数据都不受外部干扰且不被误用。封装的目的在于保护信息,使用它的主要优点如下。

  • 保护类中的信息,它可以阻止在外部定义的代码随意访问内部代码和数据。
  • 隐藏细节信息,一些不需要程序员修改和使用的信息,比如取款机中的键盘,用户只需要知道按哪个键实现什么操作就可以,至于它内部是如何运行的,用户不需要知道。
  • 有助于建立各个系统之间的松耦合关系,提高系统的独立性。当一个系统的实现方式发生变化时,只要它的接口不变,就不会影响其他系统的使用。例如 U 盘,不管里面的存储方式怎么改变,只要 U 盘上的 USB 接口不变,就不会影响用户的正常操作。
  • 提高软件的复用率,降低成本。每个系统都是一个相对独立的整体,可以在不同的环境中得到使用。例如,一个 U 盘可以在多台电脑上使用。

Java 语言的基本封装单位是类。由于类的用途是封装复杂性,所以类的内部有隐藏实现复杂性的机制。Java 提供了私有和公有的访问模式,类的公有接口代表外部的用户应该知道或可以知道的每件东西,私有的方法数据只能通过该类的成员代码来访问,这就可以确保不会发生不希望的事情。

多态性

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

img
图 4 多态示例图

从图 4 中可以看出,老师类中的许多属性和方法可以被语文老师类和数学老师类同时使用,这样也不易出错。

Java认识类和对象

在面向对象中,类和对象是最基本、最重要的组成单元。类实际上是表示一个客观世界某类群体的一些基本特征抽象。对象就是表示一个个具体的东西。所以说类是对象的抽象,对象是类的具体。

让我们来看看人类所具有的一些特征,这些特征包括属性(一些参数、数值)以及方法(一些行为,他能干什么)。

每个人都有身高、体重、年龄、血型等属性,人会劳动、会直立行走、会用自己的头脑去创造工具等方法。人之所以能区别于其他类型的动物,是因为每个人都具有“人”这个群体的属性与方法。

“人类”只是一个抽象的概念,它仅仅是一个概念,是不存在的实体!但是所有具备“人类”这个群体的属性与方法的对象都叫人!这个对象“人” 是实际存在的实体!每个人都是“人”这个群体的一个对象。

老虎为什么不是人?因为它不具备“人”这个群体的属性与方法,老虎不会直立行走,不会使用工具等,所以说老虎不是人!也就是说,类是概念模型,定义对象的所有特性和所需的操作,对象是真实的模型,是一个具体的实体。

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

对象或实体所拥有的特征在类中表示时称为类的属性。例如,每个人都具有姓名、年龄和体重,这是所有人共有的特征。但是每一个对象的属性值又各不相同,例如,小明和小红都具有体重这个属性,但是他们的体重值是不同的。

对象执行的操作称为类的方法。比如,“人”这个对象都具有的行为是“吃饭”,因此,吃饭就是“人”类的一个方法。

综上所述,类是描述实体的“模板”和“原型”,它定义了属于这个类的对象所应该具有的状态和行为。比如一名学生在上课。一名正在上课的学生是类,它定义的信息有:姓名、上课。

使用该类定义的不同姓名的人在上课是对象,他们可能是小明、小红、小丽、张会等。在 Java 面向对象编程中,用自定义的类模型可以创建该类的一个实例,也就是对象。

类是实体对象的概念模型,因此通常是笼统的、不具体的。关于类和对象,初学者在理解上是存在一定难度的。表 1 给出了类和对象的更多示例。

对象
正在清洁的环卫工人小刘
教室里的学生张丽
汽车一辆黄色的宝马跑车
一辆白色的林肯轿车
动物一只叫“猫咪”的小花猫
一只叫“欢欢”的贵宾犬

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

Java类的定义及定义类时可用的关键字

类是 Java 中的一种重要的引用数据类型,也是组成 Java 程序的基本要素,因为所有的 Java 程序都是基于类的。本节介绍如何定义类。

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

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

提示:上述语法中,中括号“[]”中的部分表示可以省略,竖线“|”表示“或关系”,例如 abstract|final,说明可以使用 abstract 或 final 关键字,但是两个关键字不能同时出现。

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

  • public:表示“共有”的意思。如果使用 public 修饰,则可以被其他类和程序访问。每个 Java 程序的主类都必须是 public 类,作为公共工具供其他类和程序使用的类应定义为 public 类。
  • abstract:如果类被 abstract 修饰,则该类为抽象类,抽象类不能被实例化,但抽象类中可以有抽象方法(使用 abstract 修饰的方法)和具体方法(没有使用 abstract 修饰的方法)。继承该抽象类的所有子类都必须实现该抽象类中的所有抽象方法(除非子类也是抽象类)。
  • final:如果类被 final 修饰,则不允许被继承。
  • class:声明类的关键字。
  • class_name:类的名称。
  • extends:表示继承其他类。
  • implements:表示实现某些接口。
  • property_type:表示成员变量的类型。
  • property:表示成员变量名称。
  • function():表示成员方法名称。

Java 类名的命名规则:

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

例 1

创建一个新的类,就是创建一个新的数据类型。实例化一个类,就是得到类的一个对象。因此,对象就是一组变量和相关方法的集合,其中变量表明对象的状态和属性,方法表明对象所具有的行为。定义一个类的步骤如下所述。

(1) 声明类。编写类的最外层框架,声明一个名称为 Person 的类。

public class Person {
    // 类的主体
}

(2) 编写类的属性。类中的数据和方法统称为类成员。其中,类的属性就是类的数据成员。通过在类的主体中定义变量来描述类所具有的特征(属性),这里声明的变量称为类的成员变量。

(3) 编写类的方法。类的方法描述了类所具有的行为,是类的方法成员。可以简单地把方法理解为独立完成某个功能的单元模块。

下面来定义一个简单的 Person 类。

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

如上述代码,在 Person 类中首先定义了两个属性,分别为 name 和 age,然后定义了一个名称为 tell() 的方法。

Java类的属性:成员变量的定义和声明

在 Java 中类的成员变量定义了类的属性。例如,一个学生类中一般需要有姓名、性别和年龄等属性,这时就需要定义姓名、性别和年龄 3 个属性。声明成员变量的语法如下:

[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 Student {
    public String name;    // 姓名
    final int sex = 0;    // 性别:0表示女孩,1表示男孩
    private int age;    // 年龄
}

上述示例的 Student 类中定义了 3 个成员变量:String 类型的 name、int 类型的 sex 和 int 类型的 age。其中,name 的访问修饰符为 public,初始化值为 null;sex 的访问修饰符为 friendly(默认),初始化值为 0,表示性别为女,且其值无法更改;age 的访问修饰符为 private,初始化值为 0。

例 1

下面以一个简单的例子来介绍成员变量的初始值,代码如下所示。

public class Counter {
    static int sum;
    public static void main(String[] args) {
        System.out.println(sum);
    }
}

在这里用静态的方法来修饰变量 sum,输出结果是 int 类型的初始值,即:0。

Java创建一个学生类

创建一个表示学生的实体类 Student,其中有学生姓名、性别和年龄信息。要求使用属性来表示学生信息,最终编写测试代码。

首先定义一个名为 Student 的类,代码如下:

public class Student {
    // 学生类
}

在类中通过属性定义学生、性别和年龄,代码如下:

public class Student {
    public String Name;    // 学生姓名
    public int Age;    // 学生年龄
    private boolean Sex;    // 学生性别
}

在上述代码中将学生性别属性 Sex 设置为 private 作用域。为了对该属性进行获取和设置,还需要编写 isSex 和 setSex 方法。代码如下:

public boolean isSex() {
    return Sex;
}
public void setSex(boolean sex) {
    this.Sex = sex;
}

在 Student 类中添加 main() 方法,然后创建两个学生类的实例,并输出学生信息。最终代码如下:

public static void main(String[] args) {
    Student zhang = new Student(); // 创建第一个实例
    zhang.Name = "张子同";
    String isMan = zhang.isSex() ? "女" : "男";
    System.out.println("姓名:" + zhang.Name + "性别:" + isMan + "年龄:" + zhang.Age);
    Student li = new Student(); // 创建第二个实例
    li.Name = "李子文";
    li.Sex = true;
    li.Age = 15;
    String isWoman = li.isSex() ? "女" : "男";
    System.out.println("姓名:" + li.Name + "性别:" + isWoman + "年龄:" + li.Age);
}

输出结果如下:

姓名:张子同性别:男年龄:0
姓名:李子文性别:女年龄:15

由输出结果可以看到,在第一个实例 zhang 中由于仅设置了 Name 属性的值,所以 boolean 类型的 Sex 默认使用值 false,int 类型的 Age 默认使用值 0。第二个实例 li 同时设置了这三个属性的值。

Java成员方法的声明和调用

声明成员方法可以定义类的行为,行为表示一个对象能够做的事情或者能够从一个对象取得的信息。类的各种功能操作都是用方法来实现的,属性只不过提供了相应的数据。一个完整的方法通常包括方法名称、方法主体、方法参数和方法返回值类型,其结构如图 1 所示。

img
图 1 方法组成元素

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

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

注意:上述语法中,中括号“[]”中的部分表示可以省略,竖线“|”表示“或”,例如 public|private,说明可以使用 public 或 private 关键字,但是两个关键字不能同时出现。

上述代码中一个方法包含 4 部分:方法的返回值、方法名称、方法的参数和方法体。其中 retum_type 是方法返回值的数据类型,数据类型可以是原始的数据类型,即常用的 8 种数据类型,也可以是一个引用数据类型,如一个类、接口和数组等。

除了这些,一个方法还可以没有返回值,即返回类型为 void,像 main() 方法。method_name 表示自定义的方法名称,方法的名称首先要遵循标识符的命名约定,除此之外,方法的名称第一个单词的第一个字母是小写,第二单词的第一个字母是大写,依此类推。

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

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

注意:上面所提到的重写、重载和抽象类,由于篇幅有限,我们会在教程《Java方法重载》《Java方法重写》和《Java抽象类》中讲解,这里大致了解就可以。

例 1

为上一节创建的学生类 Student 添加一个可以返回学生信息字符串的方法。代码如下:

public class Student {
    public StringBuffer printInfo(Student st) {
        StringBuffer sb = new StringBuffer();
        sb.append("学生姓名:"+st.Name+"\n 学生年龄:"+st.Age+"\n 学生性别:"+st.isSex());
        return sb;
    }
}

上述代码创建了一个名称为 printInfo 的方法,其返回值类型为 StringBuffer(引用数据类型)。该方法需要传递一个 Student 类型的参数,最后需要将一个 StringBuffer 类型的数据返回。

1. 成员方法的返回值

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

return 表达式

或者

return (表达式)

其中,表达式可以是常量、变量、对象等。表达式的数据类型必须与声明成员方法时给出的返回值类型一致。

2. 形参、实参及成员方法的调用

一般来说,可以通过以下方式来调用成员方法:

methodName({paramList})

关于方法的参数,经常会提到形参与实参,形参是定义方法时参数列表中出现的参数,实参是调用方法时为方法传递的参数。

例 2

下面 retumMin() 方法中的 m 和 n 是形参,调用 retumMin() 方法时的 x 和 y 是实参。

public int returnMin(int m,int n) {
    return Math.min(m,n);    // m和n是形参
}
public static void main(String[] args) {
    int x = 50;
    int y = 100;
    Test t = new Test();
    int i = t.returnMin(x,y);    // x和y是实参
    System.out.println(i);
}

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

  • 形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只有在方法内部有效,方法调用结束返回主调方法后则不能再使用该形参变量。
  • 实参可以是常量、变量、表达式、方法等,无论实参是何种类型的量,在进行方法调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应预先用赋值、输入等办法使实参获得确定值。
  • 实参和形参在数量、类型和顺序上应严格一致,否则会发生“类型不匹配” 的错误。
  • 方法调用中发生的数据传送是单向的,即只能把实参的值传送绐形参,而不能把形参的值反向地传送给实参。因此在方法调用过程中,形参的值发生改变,而实参中的值不会变化。

例 3

下面的示例演示了调用 add() 方法前后形参 x 的变化。

public int add(int x) {
    x += 30;
    System.out.println("形参 x 的值:"+x);
    return x;
}
public static void main(String[] args) {
    int x = 150;
    System.out.println("调用 add() 方法之前 x 的值:"+x);
    Test t = new Test();
    int i = t.add(x);
    System.out.println("实参 x 的值:"+x);
    System.out.println("调用 add() 方法的返回值:"+i);
}

运行上述程序,输出结果如下:

调用 add() 方法之前 x 的值:150
形参 x 的值:180
实参 x 的值:150
调用 add() 方法的返回值:180

从输出结果可以看出,形参 x 值的改变,并没有影响实参 x。

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

  1. 对无参成员方法来说,是没有实际参数列表的(即没有 paramList),但方法名后的括号不能省略。
  2. 对带参数的成员方法来说,实参的个数、顺序以及它们的数据类型必须与形式参数的个数、顺序以及它们的数据类型保持一致,各个实参间用逗号分隔。实参名与形参名可以相同,也可以不同。
  3. 实参也可以是表达式,此时一定要注意使表达式的数据类型与形参的数据类型相同,或者使表达式的类型按 Java 类型转换规则达到形参指明的数据类型。
  4. 实参变量对形参变量的数据传递是“值传递”,即只能由实参传递给形参,而不能由形参传递给实参。程序中执行到调用成员方法时,Java 把实参值复制到一个临时的存储区(栈)中,形参的任何修改都在栈中进行,当退出该成员方法时,Java 自动清除栈中的内容。

3. 方法体中的局部变量

在方法体内可以定义本方法所使用的变量,这种变量是局部变量。它的生存期与作用域是在本方法内,也就是说,局部变量只能在本方法内有效或可见,离开本方法则这些变量将被自动释放。

在方法体内定义变量时,变量前不能加修饰符。局部变量在使用前必须明确赋值,否则编译时会出错。另外,在一个方法内部,可以在复合语句(把多个语句用括号{}括起来组成的一个语句称复合语句)中定义变量,这些变量只在复合语句中有效。

Java this关键字详解(3种用法)

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

下面我们根据示例分别讲解 this 关键字的作用。

this.属性名

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

例 1

假设有一个教师类 Teacher 的定义如下:

public class Teacher {
    private String name;    // 教师名称
    private double salary;    // 工资
    private int age;    // 年龄
}

在上述代码中 name、salary 和 age 的作用域是 private,因此在类外部无法对它们的值进行设置。为了解决这个问题,可以为 Teacher 类添加一个构造方法,然后在构造方法中传递参数进行修改。代码如下:

// 创建构造方法,为上面的3个属性赋初始值
public Teacher(String name,double salary,int age) {
    this.name = name;    // 设置教师名称
    this.salary = salary;    // 设置教师工资
    this.age = age;    // 设置教师年龄
}

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

创建一个 main() 方法对 Teacher 类进行测试,代码如下:

public static void main(String[] args) {
    Teacher teacher = new Teacher("王刚",5000.0,45);
    System.out.println("教师信息如下:");
    System.out.println("教师名称:"+teacher.name+"\n教师工资:"+teacher.salary+"\n教师年龄:"+teacher.age);
}

运行该程序,输出的结果如下所示。

教师信息如下:
教师名称:王刚
教师工资:5000.0
教师年龄:45

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

this.方法名

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

例 2

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

/**
 * 第一种定义Dog类方法
 **/
public class Dog {
    // 定义一个jump()方法
    public void jump() {
        System.out.println("正在执行jump方法");
    }
    // 定义一个run()方法,run()方法需要借助jump()方法
    public void run() {
        Dog d = new Dog();
        d.jump();
        System.out.println("正在执行 run 方法");
    }
}

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

public class DogTest {
    public static void main(String[] args) {
        // 创建Dog对象
        Dog dog = new Dog();
        // 调用Dog对象的run()方法
        dog.run();
    }
}

在上面的程序中,一共产生了两个 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( ) 方法代码修改如下,其它代码不变。

/**
 * 第二种定义Dog类方法
 **/
// 定义一个run()方法,run()方法需要借助jump()方法
public void run() {
    // 使用this引用调用run()方法的对象
    this.jump();
    System.out.println("正在执行run方法");
}

从第一种 Dog 类定义来看,在 Dog 对象的 run( ) 方法内重新创建了一个新的 Dog 对象,并调用它的 jump( ) 方法,这意味着一个 Dog 对象的 run( ) 方法需要依赖于另一个 Dog 对象的 jump( ) 方法,这不符合逻辑。

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

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

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

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

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

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

this( )访问构造方法

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

例 3

下面定义一个 Student 类,使用 this( ) 调用构造方法给 name 赋值,Student 类的代码如下所示:

public class Student {
    String name;
    // 无参构造方法(没有参数的构造方法)
    public Student() {
        this("张三");
    }
    // 有参构造方法
    public Student(String name) {
        this.name = name;
    }
    // 输出name和age
    public void print() {
        System.out.println("姓名:" + name);
    }
    public static void main(String[] args) {
        Student stu = new Student();
        stu.print();
    }
}

输出结果为:

姓名:张三

注意:

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

Java创建对象详解(显式创建和隐含创建)

对象是对类的实例化。对象具有状态和行为,变量用来表明对象的状态,方法表明对象所具有的行为。Java 对象的生命周期包括创建、使用和清除,本文详细介绍对象的创建,在 Java 语言中创建对象分显式创建与隐含创建两种情况。

显式创建对象

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

1. 使用 new 关键字创建对象

这是常用的创建对象的方法,语法格式如下:

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

2. 调用 java.lang.Class 或者 java.lang.reflect.Constuctor 类的 newlnstance() 实例方法

在 Java 中,可以使用 java.lang.Class 或者 java.lang.reflect.Constuctor 类的 newlnstance() 实例方法来创建对象,代码格式如下:

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

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

3. 调用对象的 clone() 方法

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

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

4. 调用 java.io.ObjectlnputStream 对象的 readObject() 方法

例 1

下面创建一个示例演示常用的前三种对象创建方法。示例代码如下:

public class Student implements Cloneable {   
    // 实现 Cloneable 接口
    private String Name;    // 学生名字
    private int age;    // 学生年龄
    public Student(String name,int age) {    
        // 构造方法
        this.Name = name;
        this.age = age;
    }
    public Student() {
        this.Name = "name";
        this.age = 0;
    }
    public String toString() {
        return"学生名字:"+Name+",年龄:"+age;
    }
    public static void main(String[] args)throws Exception {
        System.out.println("---------使用 new 关键字创建对象---------");
       
        // 使用new关键字创建对象
        Student student1 = new Student("小刘",22);
        System.out.println(student1);
        System.out.println("-----------调用 java.lang.Class 的 newInstance() 方法创建对象-----------");
       
        // 调用 java.lang.Class 的 newInstance() 方法创建对象
        Class c1 = Class.forName("Student");
        Student student2 = (Student)c1.newInstance();
        System.out.println(student2);
        System.out.println("-------------------调用对象的 clone() 方法创建对象----------");
        // 调用对象的 clone() 方法创建对象
        Student student3 = (Student)student2.clone();
        System.out.println(student3);
    }
}

对上述示例的说明如下:

  • 使用 new 关键字或 Class 对象的 newInstance() 方法创建对象时,都会调用类的构造方法。
  • 使用 Class 类的 newInstance() 方法创建对象时,会调用类的默认构造方法,即无参构造方法。
  • 使用 Object 类的 clone() 方法创建对象时,不会调用类的构造方法,它会创建一个复制的对象,这个对象和原来的对象具有不同的内存地址,但它们的属性值相同。
  • 如果类没有实现 Cloneable 接口,则 clone。方法会抛出 java.lang.CloneNotSupportedException 异常,所以应该让类实现 Cloneable 接口。

程序执行结果如下:

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

隐含创建对象

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

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

2)字符串的“+”运算符运算的结果为一个新的 String 对象,示例如下:

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

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

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

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

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

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

Java new运算符深入剖析

”new“在 Java 中意思是”新的“,可以说是 Java 开发者最常用的关键字。在 Java 中 new 的操作往往意味着在内存中开辟新的空间,这个内存空间分配在内存的堆区。

堆是用来存放由 new 创建的对象和数组,即动态申请的内存都存放在堆区。栈是用来存放在方法中定义的一些基本类型的变量和对象的引用变量。

Java 中一般使用 new 来创建对象,它可以动态地为一个对象分配地址。它的通用格式如下:

classname obj = new classname( );

其中,obj 是创建的对象,classname 是类的名字,类名后边的( )指明了类的构造方法。构造方法定义了当创建一个对象时要进行的操作。

下面我们通过 String 这个类举例说明。

public class Test {
    public static void main(String[] args) {
        String a = "C语言中文网";
        String b = new String("C语言中文网");
        String c = "C语言中文网";
        String d = new String("C语言中文网");
        System.out.println(a == b);
        System.out.println(a == c);
        System.out.println(d == b);
        System.out.println(a);
        a = "Java";
        System.out.println(a);
    }
}

输出结果为:

false
true
false
C语言中文网
Java

不同方式定义字符串时堆和栈的变化:

  1. String a; 只是在栈中创建了一个 String 类的对象引用变量 a。
  2. String a = "C语言中文网";在栈中创建一个 String 类的对象引用变量 a,然后查找栈中有没有存放“C语言中文网”,如果有则直接指向“C语言中文网",如果没有,则将”C语言中文网“存放进栈,再指向。
  3. String a = new String("C语言中文网");不仅在栈中创建一个 String 类的对象引用变量 a,同时也在堆中开辟一块空间存放新建的 String 对象“C语言中文网”,变量 a 指向堆中的新建的 String 对象”C语言中文网“。

==用来比较两个对象在堆区存放的地址是否相同。根据上面的输出结果,我们可以看出:

  • 使用 new 运算符创建的 String 对象进行==操作时,两个地址是不同的。这就说明,每次对象进行 new 操作后,系统都为我们开辟堆区空间,虽然值是一样,但是地址却是不一样的。
  • 当我们没有使用 new 运算符的时候,系统会默认将这个变量保存在内存的栈区。如果变量的值存放在栈中,使用==比较时,比较的是具体的值。如果变量的值存放在堆中,使用==比较时,比较的是值所在的地址。因此在变量 a 与变量 c 进行==操作的时候,返回 true,因为变量 a 和变量 c 比较的是具体的值,即“C语言中文网”。
  • 在改变变量 a 的值后(如 a = “Java”),再次输出时,我们发现输出的结果是”Java“。事实上原来的那个“C语言中文网”在内存中并没有清除掉,而是在栈区的地址发生了改变,这次指向的是”Java“所在的地址。

注意:如果需要比较两个使用 new 创建的对象具体的值,则需要通过“equal()”方法去实现,这样才是比较引用类型变量具体值的正确方式。

这时,你可能想知道为什么对整数或字符这样的简单变量不使用 new 运算符。答案是 Java 的简单类型不是作为对象实现的。出于效率的考虑,它们是作为“常规”变量实现的。

对象有许多属性和方法,这使得 Java 对对象的处理不同于简单类型。Java 在处理对象和处理简单类型时开销不同,Java 能更高效地实现简单类型。当然,如果你希望完全使用对象类型,那么 Java 也提供了简单类型的对象版本,也就是包装类。

大家一定要明白,new 运算符是在运行期间为对象分配内存的,这使得内存的分配更加灵活和高效,你的程序在运行期间可以根据实际情况来合理地分配内存。但是,内存是有限的,因此 new 有可能由于内存不足而无法给一个对象分配内存。如果出现这种情况,就会发生运行时异常。

对于本教程中的示例程序,你不必担心内存不足的情况,但是在实际的编程中你必须考虑这种可能性。

Java访问对象的属性和行为

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

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

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

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

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

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

Student stu = null;
stu.Name = "李子文";
stu.Sex = true;
stu.Age = 15;

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

Exception in thread “main” java.lang.NullPointerException

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

Java对象的销毁

对象使用完之后需要对其进行清除。对象的清除是指释放对象占用的内存。在创建对象时,用户必须使用 new 操作符为对象分配内存。不过,在清除对象时,由系统自动进行内存回收,不需要用户额外处理。这也是 Java 语言的一大特色,某种程度上方便了程序员对内存的管理。

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

Java 语言并不要求 JVM 有 GC,也没有规定 GC 如何工作。不过常用的 JVM 都有 GC,而且大多数 GC 都使用类似的算法管理内存和执行回收操作。具体的垃圾回收实现策略有好多种,在此不再赘述。

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

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

1)对象的引用超过其作用范围。

{
    Object o = new Object();    // 对象o的作用范围,超过这个范围对象将被视为垃圾
}

2)对象被赋值为 null。

{
    Object o = new Object();
    o = null;    // 对象被赋值为null将被视为垃圾
}

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

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

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

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

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

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

Java中的空对象(null)是怎么回事?

Java 语言支持两种数据类型,分别是基本数据类型和引用数据类型,而 null 是一种特殊的引用数据类型。本节主要介绍 Java 空对象是什么及如何判断对象是否为空。

经过《Java new运算符深入剖析》一节的学习,我们知道了对象的实例化就是为对象开辟内存空间。

例如以下 3 条语句(如果理解下面内容有点困难,建议先学习《Java new运算符深入剖析》一节):

Student stu = new Student(); // 语句1
Student stu2; // 语句2
stu2 = new Student(); // 语句3

  • 语句 1 先声明一个 Student 类型的变量 stu,然后利用 new 关键字为其创建实例。一步到位,定义了一个实例变量并同时赋值。
  • 语句 2 是声明一个 Student 类型的变量 stu2,虽然从表述习惯上讲 stu2 是实例变量,但实际上此时 stu2 并未成为一个真正的实例,它仅仅只是一个变量名字。
  • 语句 3 中的 stu2 才成为了一个 Student 实例,它指向了内存中的某块地址空间。

为了明确表示那些仅有名字而没有内存空间的变量的具体内容,Java 引入了关键字 null。 null 表示“空”的意思,是绝对意义上的空,这个空指的是不存在。

一个引用变量(当变量指向一个对象时,这个变量就被称为引用变量)没有通过 new 分配内存空间,这个对象就是空对象,Java 使用关键字 null 表示空对象。示例代码如下:

String str1= null;
str1 = “C语言中文网”;

注意:null 是关键字,是大小写敏感的,不能将 null 写成 Null 或 NULL。

引用变量的默认值是 null。当试图调用一个空对象的属性或方法时,会抛出空指针异常(NullPointerException),如下代码所示:

String str1;    // 相当于 String str1= null;
// 输出null字符串
System.out.println(str1);
// 调用length()方法
int len = str1.length();

第 5 行代码不会发生编译错误,但是当代码运行到第 5 行时,系统会抛出空指针异常。这是因为调用 length() 方法时,str1 是空对象。程序员应该避免调用空对象的属性和方法。

判断一个对象是否为 null,可以用if (obj == null) { }来判断。代码如下:

// 判断对象是否为null
if (str1 != null) {
    int len = str1.length();
}

如果把上面代码改成如下代码:

String str2 = "";
int num = str2.length();
System.out.println(num);    // 输出结果为0

运行以上代码时没有抛出异常,因为 str2 是一个值为""的字符串对象。""表示的是一个长度为 0 的空字符串,它在内存中会被分配一个空间,str2 是直接指向""内存空间的实例化对象。

产生空对象主要有以下两种可能性:

  1. 程序员自己忘记了实例化,所以程序员必须防止这种情况发生,应该仔细检查自己的代码,为自己创建的所有对象进行实例化并初始化。
  2. 空对象是其它地方传递过来的,需要通过判断对象是否为 null 进行避免。

Java注释:类、方法和字段注释

一个程序的可读性,关键取决于注释。如果一个程序想二次开发,要读懂前面的程序代码,就必须在程序中有大量的注释文档,所以对于一个优秀的程序员来说,学会在程序中适当地添加注释是非常重要的。

注释除了帮助别人了解编写的程序之外,还对程序的调试、校对等有相当大的帮助。当程序具体运行时,计算机会自动忽略注释符号之后所有的内容。教程第二章中曾经提到过注释,读者也许印象不太深,在这里复习一遍。

本节将简单地介绍类、方法、字段等地方的注释方法,这些地方的注释虽然简单但是在开发工作中却是非常重要的。

注意:本节注释使用文档注释。多行注释的内容不能用于生成一个开发者文档(文档提供类、方法和变量的解释,也可称为帮助文档),而文档注释可以。

1. 类注释

类注释一般必须放在所有的“import”语句之后,类定义之前,主要声明该类可以做什么,以及创建者、创建日期、版本和包名等一些信息。以下是一个类注释的模板。

/**
 * @projectName(项目名称): project_name
 * @package(包): package_name.file_name
 * @className(类名称): type_name
 * @description(类描述): 一句话描述该类的功能
 * @author(创建人): user 
 * @createDate(创建时间): datetime  
 * @updateUser(修改人): user 
 * @updateDate(修改时间): datetime
 * @updateRemark(修改备注): 说明本次修改内容
 * @version(版本): v1.0
 */

提示:以上以@开头的标签为 Javadoc 标记,由@和标记类型组成,缺一不可。@和标记类型之间有时可以用空格符分隔,但是不推荐用空格符分隔,这样容易出错。

一个类注释的创建人、创建时间和描述是不可缺少的。下面是一个类注释的例子。

/**
 * @author: zhangsan
 * @createDate: 2018/10/28
 * @description: this is the student class.
 */
public class student{
    .................
}

注意:没有必要在每一行的开始用*。例如,以下注释同样是合法的:

/**
   @author: zhangsan
   @createDate: 2018/10/28
   @description: this is the student class.
 */
public class student{
    .................
}

2. 方法注释

方法注释必须紧靠在方法定义的前面,主要声明方法参数、返回值、异常等信息。除了可以使用通用标签外,还可以使用下列的以@开始的标签。

  • @param 变量描述:对当前方法的参数部分添加一个说明,可以占据多行。一个方法的所有 @param 标记必须放在一起。
  • @return 返回类型描述:对当前方法添加返回值部分,可以跨越多行。
  • @throws 异常类描述:表示这个方法有可能抛出异常。有关异常的详细内容将在第 10 章中讨论。

下面是一个方法注释的例子。

/**
 * @param num1: 加数1
 * @param num2: 加数2
 * @return: 两个加数的和
 */
public int add(int num1,int num2) {
    int value = num1 + num2;
    return value;
}

以上代码的 add() 方法中声明了两个形参,并将它们两个的和作为返回值返回。

为类的构造方法添加注释时,一般声明该方法的参数信息,代码如下。

public class Student {
   String name;
   int age;
   /**
    * @description: 构造方法
    * @param name: 学生姓名
    * @param age: 学生年龄
    */
   public Student(String name,int age) {
    this.name = name;
    this.age = age;
   }
}

3. 字段注释

字段注释在定义字段的前面,用来描述字段的含义。下面是一个字段注释的例子。

/**
 * 用户名
 */
public String name;

也可以使用如下格式:

/**用户名*/
public String name;

在 Java 的编写过程中我们需要对一些程序进行注释,除了自己方便阅读,更为别人更好理解自己的程序。注释对于程序的可读性来说是非常重要的,希望读者不要忽视它。

Java static关键字(静态变量和静态方法)

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

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

调用静态成员的语法形式如下:

类名.静态成员

注意:

  • static 修饰的成员变量和方法,从属于类。
  • 普通变量和方法从属于对象。
  • 静态方法不能调用非静态成员,编译会报错。

静态变量

类的成员变量可以分为以下两种:

  1. 静态变量(或称为类变量),指被 static 修饰的成员变量。
  2. 实例变量,指没有被 static 修饰的成员变量。

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

1)静态变量

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

2)实例变量

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

静态变量在类中的作用如下:

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

例 1

创建一个带静态变量的类,然后在 main() 方法中访问该变量并输出结果。

public class StaticVar {
    public static String str1 = "Hello";
    public static void main(String[] args) {
        String str2 = "World!";
        // 直接访问str1
        String accessVar1 = str1+str2;
        System.out.println("第 1 次访问静态变量,结果为:"+accessVar1);
        // 通过类名访问str1
        String accessVar2 = StaticVar.str1+str2;
        System.out.println("第 2 次访问静态变量,结果为:"+accessVar2);
        // 通过对象svt1访问str1
        StaticVar svt1 = new StaticVar();
        svt1.str1 = svt1.str1+str2;
        String accessVar3 = svt1.str1;
        System.out.println("第3次访向静态变量,结果为:"+accessVar3);
        // 通过对象svt2访问str1
        StaticVar svt2 = new StaticVar();
        String accessVar4 = svt2.str1+str2;
        System.out.println("第 4 次访问静态变量,结果为:"+accessVar4);
    }
}

运行该程序后的结果如下所示。

第 1 次访问静态变量,结果为:HelloWorld!
第 2 次访问静态变量,结果为:HelloWorld!
第 3 次访向静态变量,结果为:HelloWorld!
第 4 次访问静态变量,结果为:HelloWorld!World!

从运行结果可以看出,在类中定义静态的属性(成员变量),在 main() 方法中可以直接访问,也可以通过类名访问,还可以通过类的实例对象来访问。

注意:静态变量是被多个实例所共享的。

静态方法

与成员变量类似,成员方法也可以分为以下两种:

  1. 静态方法(或称为类方法),指被 static 修饰的成员方法。
  2. 实例方法,指没有被 static 修饰的成员方法。

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

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

例 2

创建一个带静态变量的类,添加几个静态方法对静态变量的值进行修改,然后在 main( ) 方法中调用静态方法并输出结果。

public class StaticMethod {
    public static int count = 1;    // 定义静态变量count
    public int method1() {    
        // 实例方法method1
        count++;    // 访问静态变量count并赋值
        System.out.println("在静态方法 method1()中的 count="+count);    // 打印count
        return count;
    }
    public static int method2() {    
        // 静态方法method2
        count += count;    // 访问静态变量count并赋值
        System.out.println("在静态方法 method2()中的 count="+count);    // 打印count
        return count;
    }
    public static void PrintCount() {    
        // 静态方法PrintCount
        count += 2;
        System.out.println("在静态方法 PrintCount()中的 count="+count);    // 打印count
    }
    public static void main(String[] args) {
        StaticMethod sft = new StaticMethod();
        // 通过实例对象调用实例方法
        System.out.println("method1() 方法返回值 intro1="+sft.method1());
        // 直接调用静态方法
        System.out.println("method2() 方法返回值 intro1="+method2());
        // 通过类名调用静态方法,打印 count
        StaticMethod.PrintCount();
    }
}

运行该程序后的结果如下所示。

在静态方法 method1()中的 count=2
method1() 方法返回值 intro1=2
在静态方法 method2()中的 count=4
method2() 方法返回值 intro1=4
在静态方法 PrintCount()中的 count=6

在该程序中,静态变量 count 作为实例之间的共享数据,因此在不同的方法中调用 count,值是不一样的。从该程序中可以看出,在静态方法 method1() 和 PrintCount() 中是不可以调用非静态方法 method1() 的,而在 method1() 方法中可以调用静态方法 method2() 和 PrintCount()。

在访问非静态方法时,需要通过实例对象来访问,而在访问静态方法时,可以直接访问,也可以通过类名来访问,还可以通过实例化对象来访问。

静态代码块

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

静态代码块的特点如下:

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

例 3

编写一个 Java 类,在类中定义一个静态变量,然后使用静态代码块修改静态变量的值。最后在 main() 方法中进行测试和输出。

public class StaticCode {
    public static int count = 0;
    {
        count++;
        System.out.println("非静态代码块 count=" + count);
    }
    static {
        count++;
        System.out.println("静态代码块1 count=" + count);
    }
    static {
        count++;
        System.out.println("静态代码块2 count=" + count);
    }
    public static void main(String[] args) {
        System.out.println("*************** StaticCode1 执行 ***************");
        StaticCode sct1 = new StaticCode();
        System.out.println("*************** StaticCode2 执行 ***************");
        StaticCode sct2 = new StaticCode();
    }
}

如上述示例,为了说明静态代码块只被执行一次,特地添加了非静态代码块作为对比,并在主方法中创建了两个类的实例对象。上述示例的执行结果为:

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

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

Java import static静态导入

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

静态导入使用 import static 语句,静态导入也有两种语法,分别用于导入指定类的单个静态成员变量、方法和全部静态成员变量、方法,其中导入指定类的单个静态成员变量、方法的语法格式如下:

import static package.ClassName.fieldName|methodName;

上面语法导入 package.ClassName 类中名为 fieldName 的静态成员变量或者名为 methodName 的静态方法。例如,可以使用import static java.lang.System.out;语句来导入 java.lang.System 类的 out 静态成员变量。

导入指定类的全部静态成员变量、方法的语法格式如下:

import static package.ClassName.*;

上面语法中的星号只能代表静态成员变量或方法名。

import static 语句也放在 Java 源文件的 package 语句(如果有的话)之后、类定义之前,即放在与普通 import 语句相同的位置,而且 import 语句和 import static 语句之间没有任何顺序要求。

所谓静态成员变量、静态方法其实就是前面介绍的类变量、类方法,它们都需要使用 static 修饰,而 static 在很多地方都被翻译为静态,因此 import static 也就被翻译成了 “静态导入”。其实完全可以抛开这个翻译,用一句话来归纳 import 和 import static 的作用,使用 import 可以省略写包名,而使用 import static 可以省略类名。

下面程序使用 import static 语句来导入 java.lang.System 类下的全部静态成员变量,从而可以将程序简化成如下形式。

import static java.lang.System.*;
import static java.lang.Math.*;
public class StaticImportTest {
    public static void main(String[] args) {
        // out是java.lang.System类的静态成员变量,代表标准输出
        // PI是java.lang.Math类的静态成员变量,表示π常量
        out.println(PI);
        // 直接调用Math类的sqrt静态方法,返回256的正平方根
        out.println(sqrt(256));
    }
}

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

Java static的常见问题和使用误区

学完《Java static关键字》一节我们可能会产生很多疑问,所以本节主要讲解学习 Java 中关于 static 常见的几个问题。

1)为什么要用”static“关键字?

通常来说,用 new 创建类的对象时,数据存储空间才被分配,方法才供外界调用。有时候我们只想为特定域分配单一存储空间,不考虑要创建多少对象或者说根本就不创建任何对象,有时候我们想在没有创建对象的情况下也想调用方法。在这两种情况下,static 关键字,满足了我们的需求。

2)”static“关键字是什么意思?Java 中是否可以覆盖(子类中如果创建了一个与父类中相同名称、相同返回值类型、相同参数列表的方法,只是方法体中的实现不同,以实现不同于父类的功能,这种方式被称为方法重写,又称为方法覆盖。这里了解即可,教程后面我们会详细讲解)一个 private 或者是 static 的方法?

“static”关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例变量的情况下被访问。

Java 中 static 方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而 static 方法是编译时静态绑定的。static 方法跟类的任何实例都不相关,所以概念上不适用。

3)是否可以在 static 环境中访问非 static 变量?

static 变量在 Java 中是属于类的,它在所有的实例中的值是一样的。当类被 Java 虚拟机载入的时候,会对 static 变量进行初始化。如果你的代码尝试不用实例来访问非 static 的变量,编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例关联上。

4)static 静态方法能不能引用非静态资源?

不能,new 的时候才会产生的东西,对于初始化后就存在的静态资源来说,不能引用它。

5)static 静态方法里面能不能引用静态资源?

可以,因为都是类初始化的时候加载的。

6)非静态方法里面能不能引用静态资源?

可以,非静态方法就是实例方法,那是 new 之后才产生的,那么属于类的内容它都认识。

使用误区

1)static 关键字会改变类中成员的访问权限吗?

有些初学者会将 Java 中的 static 与 C/C++ 中的 static 关键字的功能混淆了。在这里只需要记住一点,与 C/C++ 中的 static 不同,Java 中的 static 关键字不会影响到变量或者方法的作用域。在 Java 中能够影响到访问权限的只有 private、public、protected、friendly 这几个关键字。看下面的例子就明白了:

定义一个 Student 类,代码如下:

public class Student {
    public static String name = "张三";
    private static int age = 10;
}

定义 Main 类调用 Student 类的 age 属性,代码如下:

public class Main {
    public static void main(String[] args) {
        System.out.println(Student.name);
        System.out.println(Student.age);
    }
}

代码第 4 行会提示错误“Student.age 不可视(The field Student.age is not visible)”,这说明 static 关键字并不会改变变量和方法的访问权限。

2)能通过 this 访问静态成员变量吗?

虽然对于静态方法来说没有 this,那么在非静态方法中能够通过 this 访问静态成员变量吗?先看下面的一个例子,这段代码输出的结果是什么?

public class Main {
    static int value = 33;
    public static void main(String[] args) throws Exception {
        new Main().printValue();
    }
    private void printValue() {
        int value = 3;
        System.out.println(this.value);    // 输出 33
    }
}

这里面主要考察 this 和 static 的理解。this 代表什么?this 代表当前对象,那么通过 new Main() 来调用 printValue 的话,当前对象就是通过 new Main() 生成的对象。而 static 变量是被对象所享有的,因此在 printValue 中的 this.value 的值毫无疑问是 33。在 printValue 方法内部的 value 是局部变量,根本不可能与 this 关联,所以输出结果是 33。在这里永远要记住一点:静态成员变量虽然独立于对象,但是不代表不可以通过对象去访问,所有的静态方法和静态变量都可以通过对象访问(只要访问权限足够)。

3)static 能作用于局部变量么?

在 C/C++ 中 static 是可以作用域局部变量的,但是在 Java 中切记,Java 语法规定 static 是不允许用来修饰局部变量。

Java final修饰符详解

final 在 Java 中的意思是最终,也可以称为完结器,表示对象是最终形态的,不可改变的意思。final 应用于类、方法和变量时意义是不同的,但本质是一样的,都表示不可改变,类似 C# 里的 sealed 关键字。

使用 final 关键字声明类、变量和方法需要注意以下几点:

  • final 用在变量的前面表示变量的值不可以改变,此时该变量可以被称为常量。
  • final 用在方法的前面表示方法不可以被重写(子类中如果创建了一个与父类中相同名称、相同返回值类型、相同参数列表的方法,只是方法体中的实现不同,以实现不同于父类的功能,这种方式被称为方法重写,又称为方法覆盖。这里了解即可,教程后面我们会详细讲解)。
  • final 用在类的前面表示该类不能有子类,即该类不可以被继承。

final 修饰变量

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

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

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

public class FinalDemo {
    void doSomething() {
        // 没有在声明的同时赋值
        final int e;
        // 只能赋值一次
        e = 100;
        System.out.print(e);
        // 声明的同时赋值
        final int f = 200;
    }
    // 实例常量
    final int a = 5; // 直接赋值
    final int b; // 空白final变量
    // 静态常量
    final static int c = 12;// 直接赋值
    final static int d; // 空白final变量
    // 静态代码块
    static {
        // 初始化静态变量
        d = 32;
    }
    // 构造方法
    FinalDemo() {
        // 初始化实例变量
        b = 3;
        // 第二次赋值,会发生编译错误
        // b = 4;
    }
}

上述代码第 4 行和第 6 行是声明局部常量,其中第 4 行只是声明没有赋值,但必须在使用之前赋值(见代码第 6 行),其实局部常量最好在声明的同时初始化。代码第 13、14、16 和 17 行都声明成员常量。代码第 13 和 14 行是实例常量,如果是空白 final 变量(见代码第 14 行),则需要在构造方法中初始化(见代码第 27 行)。代码第 16 和 17 行是静态常量,如果是空白 final 变量(见代码第 17 行),则需要在静态代码块中初始化(见代码第 21 行)。

另外,无论是那种常量只能赋值一次,见代码第 29 行为 b 常量赋值,因为之前 b 已经赋值过一次,因此这里会发生编译错误。

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

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

下面程序示范了 final 修饰数组和 Person 对象的情形。

import java.util.Arrays;
class Person {
    private int age;
    public Person() {
    }
    // 有参数的构造器
    public Person(int age) {
        this.age = age;
    }
    // 省略age的setter和getter方法
    // age 的 setter 和 getter 方法
}
public class FinalReferenceTest {
    public static void main(String[] args) {
        // final修饰数组变量,iArr是一个引用变量
        final int[] iArr = { 5, 6, 12, 9 };
        System.out.println(Arrays.toString(iArr));
        // 对数组元素进行排序,合法
        Arrays.sort(iArr);
        System.out.println(Arrays.toString(iArr));
        // 对数组元素赋值,合法
        iArr[2] = -8;
        System.out.println(Arrays.toString(iArr));
        // 下面语句对iArr重新赋值,非法
        // iArr = null;
        // final修饰Person变量,p是一个引用变量
        final Person p = new Person(45);
        // 改变Person对象的age实例变量,合法
        p.setAge(23);
        System.out.println(p.getAge());
        // 下面语句对P重新赋值,非法
        // p = null;
    }
}

从上面程序中可以看出,使用 final 修饰的引用类型变量不能被重新赋值,但可以改变引用类型变量所引用对象的内容。例如上面 iArr 变量所引用的数组对象,final 修饰后的 iArr 变量不能被重新赋值,但 iArr 所引用数组的数组元素可以被改变。与此类似的是,p 变量也使用了 final 修饰,表明 p 变量不能被重新赋值,但 p 变量所引用 Person 对象的成员变量的值可以被改变。

注意:在使用 final 声明变量时,要求全部的字母大写,如 SEX,这点在开发中是非常重要的。

如果一个程序中的变量使用 public static final 声明,则此变量将称为全局变量,如下面的代码:

public static final String SEX= “女”;

final修饰方法

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

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

下面程序试图重写 final 方法,将会引发编译错误。

public class FinalMethodTest {
    public final void test() {
    }
}
class Sub extends FinalMethodTest {
    // 下面方法定义将出现编译错误,不能重写final方法
    public void test() {
    }
}

上面程序中父类是 FinalMethodTest,该类里定义的 test() 方法是一个 final 方法,如果其子类试图重写该方法,将会引发编译错误。

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

下面程序示范了如何在子类中“重写”父类的 private final 方法。

public class PrivateFinalMethodTest {
    private final void test() {
    }
}
class Sub extends PrivateFinalMethodTest {
    // 下面的方法定义不会出现问题
    public void test() {
    }
}

上面程序没有任何问题,虽然子类和父类同样包含了同名的 void test() 方法,但子类并不是重写父类的方法,因此即使父类的 void test() 方法使用了 final 修饰,子类中依然可以定义 void test() 方法。

final 修饰的方法仅仅是不能被重写,并不是不能被重载,因此下面程序完全没有问题。

public class FinalOverload {
    // final 修饰的方法只是不能被重写,完全可以被重载
    public final void test(){}
    public final void test(String arg){}
}

final修饰类

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

下面代码示范了 final 修饰的类不可被继承。

final class SuperClass {
}
class SubClass extends SuperClass {    //编译错误
}

因为 SuperClass 类是一个 final 类,而 SubClass 试图继承 SuperClass 类,这将会引起编译错误。

final 修饰符使用总结

1. final 修饰类中的变量

表示该变量一旦被初始化便不可改变,这里不可改变的意思对基本类型变量来说是其值不可变,而对对象引用类型变量来说其引用不可再变。其初始化可以在两个地方:一是其定义处,也就是说在 final 变量定义时直接给其赋值;二是在构造方法中。这两个地方只能选其一,要么在定义时给值,要么在构造方法中给值,不能同时既在定义时赋值,又在构造方法中赋予另外的值。

2. final 修饰类中的方法

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

3. final 修饰类

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

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

Java main()方法

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

下面先来看最简单的一个 Java 应用程序 HelloWorld,我们将通过这个例子讲解 Java 类中 main() 方法的奥秘,程序的代码如下:

public class HelloWorld {
    public static void main(String args[]) {
        System.out.println("Hello World!");
    }
}

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

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

下面的示例代码演示了如何在 main() 方法中调用本类的静态和非静态方法。

public class Student {
    public void Speak1() {
        System.out.println("你好!");
    }
    public static void Speak2() {
        System.out.println("Java!");
    }
    public static void main(String[] args) {
        // Speak1();    // 错误调用
        Speak2();    // 可以直接调用静态方法Speak2()
        Student t = new Student();
        t.Speak1();    // 调用非静态方法,需要通过类的对象来调用
    }
}

由上面代码可以看出,在 main() 方法中只能直接调用静态方法,如果想调用非静态方法,需要将当前类实例化,然后通过类的对象来调用。

例 1

创建一个 Java 程序,编写代码实现程序执行时统计传递参数的数量及每个参数值。示例代码如下:

public class TestMain {
    public static void main(String[] args) {
        int n = args.length;    // 获取参数数量
        System.out.println("一共有 "+n+" 个参数");
        if(n > 0) {   
            // 判断参数个数是否大于0
            for(int i = 0;i < n;i++) {
                System.out.println(args[i]);
            }
        }
    }
}

1)将代码保存到 TestMain.java 文件中(这里把 TestMain.java 文件放到了 D 盘的 myJava 文件夹),然后用如下 Java 命令对程序进行编译:

javac TestMain.java

2)程序编译成功后用如下 Java 命令执行 Test 程序:

java TestMain 参数列表    // 多个之间用空格隔开

这里使用如下三个语句执行程序:

java TestMain
java TestMain apple banana
java TestMain one two three four five six

3)执行结果如下所示:

C:\Users\leovo>d:

D:\myJava>javac TestMain.java

D:\myJava>java TestMain
一共有 0 个参数

D:\myJava>java TestMain apple banana
一共有 2 个参数
apple
banana

D:\myJava>java TestMain one two three four five six
一共有 6 个参数
one
two
three
four
five
six

D:\myJava>

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

Java中main()方法的格式为什么是固定不变的?

在《Java main()方法》一节中提到 main() 方法定义必须是“public static void main(String[] 字符串数组参数名)”,本节告诉大家为什么在 Java 中定义 main 方法的语法是不可改变的。

1)public :这个字段决定了方法是否可以被外部方法调用。我们知道C语言里面的函数是运行操作系统环境中的,可以认为操作系统一个大的方法,这个方法调用了C语言的 main()。Java 中也是同样的道理,只不过 Java 的 main 方法是跑在 JVM 中的。如果不限定 public,JVM 就不能调用 main 方法。

注意:在 Java SE1.4 以前是不强制 main 方法为 public 的。当 main 方法不是 public 时,有些版本的 Java 解释器也可以执行 Java 应用程序。Java SE1.4 及以后的版本中强制 main 方法是 public。

面向对象的语言叫方法,例如 Java。面向过程的语言叫函数,例如C语言。实际上方法就是函数,函数就是方法,只是在不同的语言不同的称呼而已。

2)static:在C语言中,所有的函数都可以被直接调用,main 函数相当于全局方法,没有类的概念,但在 Java 中一般先要创建一个类的对象,再通过对象调用方法,但是在执行 main 方法之前创建一个对象显然是不可能的,因为 JVM 规定 main 方法为入口方法,从 main 开始执行。static 关键字就解决了这个问题,static 关键字修饰的方法可以视为类方法,不需要创建对象就可以直接调用该方法。

3)void:由于 Java 的主方法是栈中最底层的方法,所以并不存在能够接收的值,没有能接收 main 方法的返回值,所以使用 void。

为什么 Java 不能像C语言,C++ 那样返回 int 型值?

C语言的 main 函数的调用过程:首先,程序进入到入口方法之前,是发生了很多事情的。操作系统的安排,启动运行时库,运行时库再初始化好环境,然后启动你的入口函数,你的程序才正常的运行起来。等你的程序运行结束后,就退回到运行时库,然后再退回到操作系统,然后系统再调度其他程序执行。因为 main() 不是由操作系统直接调用,所以返回类型不一样也是可以理解的。可以认为运行时库就是操作系统和 main 函数的中间联系人,在C语言中由运行时库直接调用 main 函数,所以 main 函数的返回值也是运行时库接收。

在 Java 中,可以认为是操作系统→JVM→main 方法这样的调用过程,JVM 直接调用 main 方法,所以如果 main 方法有返回值,即 JVM 接收。但是 JVM 被设计为不接受任何返回值,所以 main 方法不能有返回值,只能 void。

4)String[]:类似于C语言中的参数 char**和int,Java 中数组可以记录数目,所以省去了 int 类型的参数来表示字符串个数。

因此,main() 方法定义必须是:“public static void main(String[] 字符串数组参数名) ”,这是 Java 的规范。

Java方法的可变参数

在具体实际开发过程中,有时方法中参数的个数是不确定的。为了解决这个问题,在 J2SE 5.0 版本中引入了可变参数的概念。

声明可变参数的语法格式如下:

methodName({paramList},paramType…paramName)

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

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

例 1

每次参加考试的人数是不固定的,但是每次考试完之后都需要打印出本次考试的总人数以及参加考试的学生名单。下面编写程序,使用方法的可变参数实现该功能,具体的代码如下:

public class StudentTestMethod {
    // 定义输出考试学生的人数及姓名的方法
    public void print(String...names) {
        int count = names.length;    // 获取总个数
        System.out.println("本次参加考试的有"+count+"人,名单如下:");
        for(int i = 0;i < names.length;i++) {
            System.out.println(names[i]);
        }
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        StudentTestMethod student = new StudentTestMethod();
        student.print("张强","李成","王勇");    // 传入3个值
        student.print("马丽","陈玲");
    }
}

在 Student TestMethod 类中定义了 print() 方法和 main() 方法。print() 方法声明了一个 String 类型的可变参数,方法体打印可变参数的总个数以及参数值。在 main() 方法中创建了 StudentTestMethod 类的实例,然后分别传入不同个数的参数调用 print() 方法。

运行 StudentTestMethod 类,输出结果如下:

本次参加考试的有3人,名单如下:
张强
李成
王勇
本次参加考试的有2人,名单如下:
马丽
陈玲

Java构造方法

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

Java 构造方法有以下特点:

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

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

这时候大家可能会产生疑问,构造方法不是没有返回值吗?为什么不能用 void 声明呢?

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

注意:构造方法不能被 static、final、synchronized、abstract 和 native(类似于 abstract)修饰。构造方法用于初始化一个新对象,所以用 static 修饰没有意义。构造方法不能被子类继承,所以用 final 和 abstract 修饰没有意义。多个线程不会同时创建内存地址相同的同一个对象,所以用 synchronized 修饰没有必要。如果不了解除 static、final 之外其他的关键字,教程后面会详细讲解。

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

class class_name {
    public class_name(){}    // 默认无参构造方法
    public ciass_name([paramList]){}    // 定义构造方法// 类主体
}

在一个类中,与类名相同的方法就是构造方法。每个类可以具有多个构造方法,但要求它们各自包含不同的方法参数。

例 1

构造方法主要有无参构造方法和有参构造方法两种,示例如下:

public class MyClass {
    private int m;    // 定义私有变量
    MyClass() {
        // 定义无参的构造方法
        m = 0;
    }
    MyClass(int m) {
        // 定义有参的构造方法
        this.m = m;
    }
}

该示例定义了两个构造方法,分别是无参构造方法和有参构造方法。在一个类中定义多个具有不同参数的同名方法,这就是方法的重载。这两个构造方法的名称都与类名相同,均为 MyClass。在实例化该类时可以调用不同的构造方法进行初始化。

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

提示:无参数的构造方法也被称为 Nullary 构造方法。只有编译程序自动加入的构造方法,才称为默认构造函数。如果自行编写无参数、没有内容的构造函数,就不称为默认构造函数了(只是 Nullary 构造函数)。虽然只是名词定义,不过认证考试时要区别一下两者的不同。

例 2

要在不同的条件下使用不同的初始化行为创建类的对象,这时候就需要在一个类中创建多个构造方法。下面通过一个示例来演示构造方法的使用。

1)首先在员工类 Worker 中定义两个构造方法,代码如下:

public class Worker {
    public String name;    // 姓名
    private int age;    // 年龄
    // 定义带有一个参数的构造方法
    public Worker(String name) {
        this.name = name;
    }
    // 定义带有两个参数的构造方法
    public Worker(String name,int age) {
        this.name = name;
        this.age = age;
    }
    public String toString() {
        return "大家好!我是新来的员工,我叫"+name+",今年"+age+"岁。";
    }
}

在 Worker 类中定义了两个属性,其中 name 属性不可改变。分别定义了带有一个参数和带有两个参数的构造方法,并对其属性进行初始化。最后定义了该类的 toString() 方法,返回一条新进员工的介绍语句。

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

2)在 TestWorker 类中创建 main() 方法作为程序的入口处,在 main() 方法中调用不同的构造方法实例化 Worker 对象,并对该对象中的属性进行初始化,代码如下:

public class TestWorker {
    public static void main(String[] args) {
        System.out.println("-----------带有一个参数的构造方法-----------");
        // 调用带有一个参数的构造方法
        Worker worker1 = new Worker("张强");
        System.out.println(worker1);
        System.out.println("-----------带有两个参数的构造方法------------");
        // 调用带有两个参数的构造方法
        Worker worker2 = new Worker("李丽",25);
        System.out.println(worker2);
    }
}

在上述代码中,创建了两个不同的 Worker 对象:一个是姓名为张强的员工对象,一个是姓名为李丽、年龄为 25 的员工对象。对于第一个 Worker 对象 Worker1,并未指定 age 属性值,因此程序会将其值采用默认值 0。对于第二个 Worker 对象 Worker2,分别对其指定了 name 属性值和 age 属性值,因此程序会将传递的参数值重新赋值给 Worker 类中的属性值。

注意:Java 构造器的工作方式与 C++ 一样。但是,要记住所有的 Java 对象都是在堆中构造的,构造器总是伴随着 new 操作符一起使用。C++ 程序员最易犯的错误就是忘记 new 操作符,例如以下语句:

Worker worker("张三",12);

这条语句在 C++ 中能够正常运行,但在 Java 中却不行。

运行 TestWorker 类,输出的结果如下:

-----------带有一个参数的构造方法-----------
大家好!我是新来的员工,我叫张强,今年0岁。
-----------带有两个参数的构造方法------------
大家好!我是新来的员工,我叫李丽,今年25岁。

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

Java包(package)详解

在编写 Java 程序时,随着程序架构越来越大,类的个数也越来越多,这时就会发现管理程序中维护类名称也是一件很麻烦的事,尤其是一些同名问题的发生。有时,开发人员还可能需要将处理同一方面的问题的类放在同一个目录下,以便于管理。

为了解决上述问题,Java 引入了包(package)机制,提供了类的多层命名空间,用于解决类的命名冲突、类文件管理等问题。

包允许将类组合成较小的单元(类似文件夹),它基本上隐藏了类,并避免了名称上的冲突。包允许在更广泛的范围内保护类、数据和方法。你可以在包内定义类,而在包外的代码不能访问该类。这使你的类相互之间有隐私,但不被其他世界所知。

包的 3 个作用如下:

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

包定义

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

package 包名;

Java 包的命名规则如下:

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

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

包导入

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

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

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

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

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

import 包名+类名;

上面语句用于直接导入指定类,例如导入前面的 example.Test 类,代码如下:

import example.Test;

使用 import 语句导入指定包下全部类的用法按如下:

import example.*;

上面 import 语句中的星号(*)只能代表类,不能代表包,表明导入 example 包下的所有类。

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

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

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

在一些极端的情况下,import 语句也帮不了我们,此时只能在源文件中使用类全名。例如,需要在程序中使用 java.sql 包下的类,也需要使用 java.util 包下的类,则可以使用如下两行 import 语句:

import java.util.;
import java.sql.
;

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

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

上面错误提示:在 Test.java 文件的第 25 行使用了 Date 类,而 import 语句导入的 java.sql 和 java.util 包下都包含了 Date 类,系统不知道使用哪个包下的 Date 类。在这种情况下,如果需要指定包下的 Date 类,则只能使用该类的全名,代码如下:

// 为了让引用更加明确,即使使用了 import 语句,也还是需要使用类的全名
java.sql.Date d = new java.sql.Date();

系统包

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

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

读者现在只需对这些包有一个大致印象即可,随着教程后面的介绍,读者会逐渐熟悉它们的用法。

Java使用自定义包

包的声明和使用非常简单,在了解基本语法之后,下面通过一个案例演示在 Java 程序中声明包,以及不同包之间类的使用。

1)创建一个名为 com.dao 的包。

2)向 com.dao 包中添加一个 Student 类,该类包含一个返回 String 类型数组的 GetAll() 方法。Student 类代码如下:

package com.dao;
public class Student {
    public static String[] GetAll() {
        String[] namelist = {"李潘","邓国良","任玲玲","许月月","欧阳娜","赵晓慧"};
        return namelist;
    }
}

3)创建 com.test 包,在该包里创建带 main() 方法的 Test 类。

4)在 main() 方法中遍历 Student 类的 GetAll() 方法中的元素内容,在遍历内容之前,使用 import 引入 com.dao 整个包。完整代码如下:

package com.test;
import com.dao.Student;
public class Test {
    public static void main(String[] args) {
        System.out.println("学生信息如下:");
        for(String str:Student.GetAll()) {
            System.out.println(str);
        }
    }
}

5)运行上一步骤的代码进行测试,最终的输出结果如下:

学生信息如下:
李潘
邓国良
任玲玲
许月月
欧阳娜
赵晓慧
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java类和对象 的相关文章

随机推荐

  • Segmentation fault错误总结大

    Segmentation Fault原因总结 理解 Segmentation fault https www cnblogs com wpgraceii p 10622582 html
  • OpenCV基础教程大全

    文章目录 关于本教程 OpenCV是什么 xff1f IPPICV 加速 OpenCV有哪些应用 xff1f OpenCV 使用开源许可证 什么是计算机视觉 xff1f 人类的视觉计算机的视觉场景信息可以辅助计算机视觉使用统计的方法来对抗噪
  • 不同编程语言的程序可不可以通过接口相互调用?一些库为啥可以提供不同语言的接口?

    不同编程语言的程序经常互相调用 编程语言之间的直接调用称为互操作 xff0c 他们之间的接口叫做 Foreign Function Interface 在Linux 平台上 xff0c 互操作性最好的语言应该是C xff0c 因为有比较统一
  • 如何使用Markdown内嵌HTML来更改字体颜色

    Markdown 通过简单标记语法 xff0c 使普通文本内容具有一定格式 但它本身不支持修改字体 字号与颜色等功能的 但其支持内嵌HTML xff0c 因此可以实现很多好玩的功能 一 更改字体 大小 颜色 span class token
  • 【程序员读论文】题外篇:怎么读论文

    文章目录 1 如何高效读论文 xff1f 痛苦选择顺序笔记小结讨论 2 如何有针对地高效地阅读一篇学术论文 xff1f 3 一文教你如何快速高效阅读Paper xff08 硕士生版 xff09 前言Paper从哪来Paper怎么读Paper
  • 【程序员读论文】推荐一款OCR软件,识别PDF论文上的文字

    在我们读论文过程中 xff0c 我们会有将PDF论文中的一些文字复制下来的需求 xff0c 但因为PDF的特殊性 xff0c 要么复制不出来 xff0c 要么复制的有问题 通过OCR软件 xff0c 我们可以通过现在的图形文字识别技术 xf
  • uC/OS-III 的特点

    1 其中最有用的功能应该是时间片轮转法 xff08 roundrobin 这个是 uC OS II 中不支持的 xff0c 但是现在已经是 uC OS III 的一个功能了 2 uC OS III 被设计用于 32 位处理器 xff0c 但
  • ORBSLAM2 文章翻译

    ORBSLAM2 一种适用于单目 双目和RGB D相机的开源slam系统 摘要 本文提出了ORB SLAM2 xff0c 一种适用于单目 双目和RGB D相机的slam系统 xff0c 包含地图重用 xff0c 回环检测 xff0c 重定位
  • 【程序员读论文】LeCun, Y., Bengio, Y. & Hinton, G. Deep learning. *Nature* **521,** 436–444 (2015).

    文章目录 一 先看题目 摘要 结论二 文章主体三 总结 今天要读的论文是深度学习的里程碑之作 xff0c 集齐了三位在深度学习领域举足轻重的人物 论文名称 xff1a LeCun Y Bengio Y amp Hinton G Deep l
  • HTML入门教程(非常详细)

    转载于我最喜欢的C语言中文网 xff1a http c biancheng net view 9395 html 文章目录 网站到底是什么 xff1f 1 什么是网页 xff1f 2 什么是网站 xff1f 1 网站服务器 xff08 Se
  • JSON教程(非常详细)

    之前写过有关C语言JSON库 xff1a C语言开源库 在Clion上使用开源库cJSON JSON和XML的对比 xff1a JSON vs XML xff0c 为什么JSON更好 xff1f 下面就好好来了解一下JSON 文章目录 JS
  • 理清gcc、libc、glibc、libc++、libstdc++的关系

    转载一篇好文 xff1a https www jianshu com p a3c983edabd1 当你在Linux下写C C 43 43 代码的时候 xff0c 是不是会遇到许多编译链接的问题 xff1f 时不时报个glibc gcc x
  • Linux中C语言标准库glibc源码下载

    在这篇文章理清gcc libc glibc libc 43 43 libstdc 43 43 的关系 xff0c 我们大概理解了libc xff0c glibc之间的一些关系 下面我们就开了解一些Linux中C语言标准库glibc源码 在这
  • 记录、总结、复盘的重要性和方法(另有周报、月报、年度总结撰写方法)

    文章目录 一 记录1 记录的分类2 学习记录3 工作记录定义分类作用提醒作用跟踪作用证明作用 写好日志时间维度内容维度感想维度 4 生活记录 二 总结和复盘1 总结2 复盘什么叫做复盘 xff1f 什么时候复盘比较合适 xff1f 怎样进行
  • PPT画图文章总结

    一图抵千言 xff0c 在平常的PPT汇报中 xff0c 一张好的图片可以让我们的展示更加清晰 xff0c 也让听得人更快的了解我们的内容 要想起之前师兄发了文章 xff0c 需要提供一个封面示意图 xff0c 当时好像是花钱请别人做的 x
  • 传统学科怎么和深度学习领域结合

    这篇博客 程序员读论文 LeCun Y Bengio Y amp Hinton G Deep learning Nature 521 436 444 2015 中的论文提到深度学习将在很多行业上有广阔的前景 最近看到毕导的公众号发文菜鸡程序
  • 现在快2022年了,c++为什么还要实现(.cpp)和声明(.h)分开?像 Java 或 C# 都不需要声明头文件,C++ 委员会为什么不解决这个问题?

    链接 xff1a https www zhihu com question 506962663 answer 2278836594 因为 C 43 43 牵扯面更广 xff0c 改起来更麻烦 很多语言其实都有一个事实上的实现标准 xff0c
  • Java程序设计基础

    文章目录 Java标识符和关键字标识符关键字 Java注释 xff1a 单行 多行和文档注释1 xff09 单行注释2 xff09 多行注释3 xff09 文档注释 Javadoc xff08 文档注释 xff09 详解Javadoc标签J
  • 几本对于笔试和面试有用的书(干货~)

    黑客帝国 jpg 这儿放几本对程序员笔试和面试有益的书籍o o the power of coding coder jpg 4本408核心书籍 xff1a 数据结构计算机操作系统计算机网络计算机组成原理 面试宝典 xff1a 程序员面试宝典
  • Java类和对象

    文章目录 本章学习要点 Java面向对象 xff1a 对象的概念及面向对象的三个基本特征对象的概念面向对象的三大核心特性继承性封装性多态性 Java认识类和对象Java类的定义及定义类时可用的关键字例 1 Java类的属性 xff1a 成员