类与对象
1、类的定义与命名
类是Java程序的基本要素,一个Java应用程序就是由若干个类所构成。类声明的变量被称为对象变量,简称对象。
类的定义包括两部分:类声明和类体。基本格式为:
class 类名 {
内容
}
其中class是关键字,用来定义类。类名必须是合法的Java标识符。两个大括号及其之间的内容是类体。
类的命名:类在命名时要符合标识符规定,不能使用非法字符(语法要求)。同时也建议遵守以下命名风格(非语法要求):
- 命名使用拉丁字母时,名字的首字母应大写。
- 类名最好见名知意,不要使用拼音。当类名由几个单词复合而成,每个单词的首字母应大写(驼峰习惯)。
更具体的命名规范可以参考阿里Java开发手册。
2、类体
类的目的是抽象出一类事物共有的属性和行为,并用一定的语法格式描述所抽象出的属性和行为。类声明之后的一对大括号"{}"及括号之间的内容称为类体。
类体一般由如下所述的两部分构成:
- 变量的声明:用来存储属性的值(体现对象的属性)。
- 方法的定义:方法可以对类中声明的变量进行操作,即给出算法(体现对象的行为)。
class Fruit {
/* 成员变量 */
char color;
float weight;
/* 方法 */
void grow() {
int sun; // 局部变量
... // do sth
}
}
成员变量
- 成员变量的类型,可以是Java中的任何一种数据类型,包括八大基本类型和引用类型(数组、对象和接口)。
- 成员变量的范围,成员变量在整个类中都有效,其有效性与它在类体中书写的先后顺序无关。但不建议把成员变量分散的写在方法之间,一般习惯先写成员变量再写方法。
- 成员变量的声明:
- 虽然可以在一行间声明多个变量,但建议每行只命名一个变量,避免混淆且便于添加注释。
- 成员变量命名与类的命名的区别是成员变量建议首字母小写。
方法
- 方法头,方法头由方法的类型、名称和名称之后的一对小括号以及其中的参数所构成。小括号中也不一定要有参数。
- 方法体,方法头之后的一对大括号及大括号中的内容称为方法体。方法体中一般包括局部变量的声明和其他Java语句。在方法体中可以对成员变量和局部变量进行操作。
- 区分成员变量和局部变量,局部变量只在方法中有效。如果成员变量和局部变量同名,方法将隐藏成员变量。若想要使用被隐藏的成员变量,必须使用关键字
this.变量名
进行操作。 - 局部变量没有默认值,成员变量与数组单元一样有默认值,而局部变量没有默认值,因此在使用局部变量前,必须保证局部变量有具体的值。
需要注意的问题
类体由两部分组成,一部分是变量的声明,另一部分是方法的定义,对成员变量的操作只能放在方法中,但可以在声明时对成员变量赋值。
class Fruit {
float weight = 1f; // 可以在声明的时候赋值
char color;
color = 'w'; // 错误,不允许在方法外操作
}
3、构造方法与对象的创建
在面向对象语言中,用类声明的变量被称为对象。和基本数据类型不同,由于对象属于引用类型,在用类声明对象后,还需要创建对象。当使用类创建一个对象时,也可以称给出了这个类的一个实例。
构造方法
构造方法是类中的一种特殊方法,当程序用类创建对象时需使用它的构造方法。类中的构造方法的名字必须与它所在的类的名字完全相同,而且没有类型。允许在一个类中编写若干个构造方法,但必须保证它们的参数不同。参数不同是指:参数的个数不同或者参数的个数相同但参数中存在某个参数的类型不同。
- 若没有编写构造方法,系统会默认给出一个无参方法,且方法体中没有任何语句。
/* 默认构造方法 */
Fruit() {
}
- 若类中定义了一个或多个方法,Java将不提供默认的构造方法。
- 需要注意的是,构造方法没有类型,与类名同名但有类型的方法不是构造方法。
创建对象
- 声明对象
/* 一般格式 */
类名 对象名;
/* 举例 */
Fruit fruit;
- 为声明的对象分配变量:使用
new
运算符和类的构造方法为声明的对象分配变量,即创建对象。如果类中没有构造方法,系统会调用默认的构造方法。
/* 1. 没有构造方法 */
class Fruit {
char color;
float weight;
void grow(int sun) {
...
}
}
public class Test {
public static void main(String[] args) {
Fruit apple; // 声明对象
apple = new Fruit();// 为对象分配变量(使用默认构造方法)
}
}
/* 2. 有构造方法 */
class Fruit {
char color;
float weight;
/* 构造方法1 */
Fruit(char color) {
/* 局部变量和成员变量同名,成员变量被隐藏,因此使用this关键字指定成员变量 */
this.color = color;
}
/* 构造方法2 */
Fruit(float weight) {
this.weight = weight;
}
}
public class Test {
public static void main(String[] args) {
Fruit apple, banana; // 声明对象 apple 和 banana
apple = new Fruit('r'); // 为对象 apple 分配变量(使用new和类中的构造方法1)
banana = new Fruit(2f); // 为对象 banana 分配变量(使用new和类中的构造方法2)
/* 由于已经在类中定义了构造方法,Java不提供默认构造方法,因此下面的操作是错误的。 */
apple = new Fruit();
}
}
使用对象
/* 对象操作自己的变量 */
对象.变量
/* 对象调用类中的方法 */
对象.方法
/* 实际举例 */
class Fruit {
char color;
float weight;
void grow(int sun) {
weight += sun * 0.1f;
}
}
public class Test {
public static void main(String[] args) {
Fruit apple;
apple = new Fruit();
apple.grow(10); // 调用类中的方法
System.out.println(apple.weight); // 操作类中的变量,这里的输出结果为 1.0
}
}
对象的引用和实体
类是体现封装的一种数据类型(封装着数据和对数据的操作),类所声明的变量称为对象,对象负责存放引用,以确保对象可以操作分配给该对象的变量以及调用类中的方法。而分配给对象的变量被习惯性的称为实体。
- 避免使用空对象
- 没有实体的对象称为空对象,如果使用空对象调用方法产生行为,就可能出现空指针异常(NullPointerException)。因此,在编写程序是要避免使用空对象。
- 对象与实体的相关结论
- 一个类声明的两个对象如果具有相同的引用,二者就具有完全相同的变量(实体)。
- 对于同一个类的两个对象,可以这样赋值:
object1 = object2
。于是上面两个对象将具有相同的引用,即具有相同的实体。
- 垃圾收集
- Java具有垃圾收集机制,如果某个实体不再被任何对象引用,就会释放实体占有的内存。
引用类型的创建过程
引用数据类型除类之外,还包括数组和接口。这里以Fruit apple = new Fruit('r');
为例:
- 声明对象,在栈内存中为 apple 对象分配一块空间,此时 apple 的空间中还没有任何数据,或者说 apple 是一个空对象。
- 在堆内存中为 Fruit 实体分配一块空间(Fruit中包含的成员变量都会被分配到空间),并对成员变量进行初始化操作。
- 根据类中对成员变量的定义,为该对象实体的成员变量进行赋值操作。
- 执行构造方法中的语句,此时对象 apple 和实体 Fruit(‘r’) 之间还没有建立联系。
- 将实体在堆内存中的地址赋值给栈中的 apple;通过 apple 就可以找到堆中实体的具体信息。
4、参数传值
方法中常常会有参数,参数属于局部变量,参数变量必须有具体的值。
传值机制
基本数据类型
- 方法中的参数变量是传入变量的拷贝,对参数变量的修改不会影响到原来变量的值。
- 传入变量的精度不可以高于参数变量的精度。如不可以向int型参数传入float值,但可以向float型参数传入int值。
引用类型
- 传递的仅是对象中存放的引用(地址),而不是对象所引用的实体。因此当修改参数对象引用的实体,就相当于修改原对象引用的实体。
5、实例成员与类成员
static关键字可以用来修饰类的成员方法、成员变量。static修饰的变量或方法可以在没有创建对象实例的时候直接通过类名进行调用。
实例变量与类变量
当一个变量使用static
关键字进行修饰就称其为类变量(静态变量),否则称为实例变量。
实例变量和类变量的区别
- 不同对象的实例变量互不相同,每个对象的实例变量占用不同的内存空间。而所有的对象共享一个类变量,即所有对象的类变量仅占用了相同的一处内存。
- 实例变量仅能在对象实体中访问,而类变量可以使用类名直接访问。
实例方法和类方法
在方法类型前添加static
关键字的方法称为类方法(静态方法),反之称为实例方法。
实例方法和类方法的区别
- 只有当类创建第一个对象后,类中的实例方法才分配入口地址。而类方法在该类被加载到内存时就直接被分配了入口地址。不过,无论是实例方法还是类方法,都是所有对象共享同一个入口地址。
- 实例方法仅能通过对象实体进行调用。而类方法可以直接通过类名进行调用。
- 由于类方法创建在创建之前实例成员变量和实例方法还没有分配内存,因此类方法中不允许操作实例变量和调用实例方法,
this
关键字也不能出现在类方法中。
6、方法重载
重载(Overload)是Java中多态的一种,重载的意思是,一个类中可以有多个同名的方法,但这些方法的参数必须不同。即参数满足以下条件之一:
- 参数个数不同
- 参数个数相同,但参数列表中对应的某个参数的类型不同。
class PPAP {
void merge(Pen pen) {
system.out.println("pen");
}
void merge(Apple apple) {
system.out.println("apple");
}
void merge(Pen pen, Apple apple) {
system.out.println("pineapple");
}
}
public class Test {
public static void main(String[] args) {
/* 假设存在Apple类和Pen类 */
Apple apple = new Apple();
Pen pen = new Pen();
/* 调用重载方法 */
PPAP ppap = new PPAP();
ppap.merge(pen); // 输出 pen
ppap.merge(apple); // 输出 apple
ppap.merge(pen, apple); // 输出 pineapple
}
}
方法的返回类型不参与比较,因此,如果两个方法的名字相同,但返回类型不同,也必须保证参数不同。
调用重载时的歧义
除了需要保证参数不同外,在调用重载方法的时候也可能引发歧义:
class Sum {
static float sum(int a, float b) {
return a + b;
}
static float sum(float a, int b) {
return a + b;
}
}
public class Test {
public static void main(String[] args) {
Sum.sum(1.0f, 1);
Sum.sum(1, 1); // 报错:Ambiguous method call. Both
}
}
在上面的例子中Sum.sum(1, 1);
将会报错,因为编译器无法确定使用哪个方法,因此,在调用方法时应该避免引发歧义。
7、补充
基本数据类型的封装类
Java是一个面向对象的编程语言,基本类型并不具有对象的性质,为了让基本类型也具有对象的特征,就出现了包装类型。对于所有的基本数据类型,Java都提供了与之对应的包装类型(引用类型),分别为: Byte, Integer, Short, Long, Float, Double, Character.
包装类型与基本类型的区别:
- 包装类是对象,拥有方法和字段,通过引用对象的地址调用。
- 包装类型是引用的传递,基本类型是值的传递。
- 包装类型需要使用
new
关键字创建,存放在堆空间中。基本类型直接赋值,一般存放在栈空间中。 - 包装类型初始值为’null’,基本类型初始值为0。
- 包装类型的开销更大,一般只在一些容器中或字符转换等场景使用。
对象数组
数组除了可以存放基本类型,也可以直接存放引用类型,需要注意的时,数组中存放引用类型时,存放的也不是实体,而是实体的引用。