5.1 对象的定义和引用
类是对某一类事物的描述,是抽象的、概念上的定义;对象是实际存在的该类事物的每个个体,因而也称实例(instance)。
如果将对象比作汽车,那么类就是汽车的设计图纸。所以面向对象程序设计的重点是类的设计,而不是对象的设计。
5.1.1类的定义
class Person
{ int age;
void shout()
{System.out.println(“oh,my god! I am “ + age);}
}
age是类的属性 ,也叫类成员变量 。
shout是方法也叫类的成员函数。
shout方法可以直接访问同一个类中的age变量 ,如果一个方法中有与成员变量同名的局部变量,该方法中对这个变量名的访问是局部变量,而不再是成员变量。
5.1.2对象的创建
Person p1 = new Person();
执行完后的内存状态
5.1.3对象的初始化
当一个对象被创建时,会通过构造方法对其中各种类型的成员变量自动进行初始化赋值,如果类没有定义构造函数,就使用缺省构造方法,缺省构造函数对不同类型的成员变量赋不同的初值。
5.1.4对象的使用
创建新的对象之后,我们就可以使用“ 对象名.对象成员 ”的格式,来访问对象的成员(包括属性和方法)
class TestPerson
{ public static void main(String[] args)
{ Person p1 = new Person();
Person p2 =new Person();
p1.age = -30;
p1.shout();
p2.shout();
}
}
程序的内存布局:
5.1.5对象的生命周期:
5.1.6对象的比较
“==”运算符与equals()方法的区别
==:同一个对象,equals():对象的内容相同
5.1.7匿名对象
我们也可以不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象, 如:new Person().shout();
如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象。
我们经常将匿名对象作为实参传递给一个函数调用。
5.1.8构造方法
构造方法的定义
它具有与类相同的名称;
它不含返回值;
它不能在方法中用return语句返回一个值
注意:在构造方法里不含返回值的概念是不同于“void”的,在定义构造方法时加了“void”,结果这个方法就不再被自动调用了。
构造方法的作用
当一个类的实例对象刚产生时,这个类的构造方法就会被自动调用,我们可以在这个方法中加入要完成初始化工作的代码。这就好像我们规定每个“人”一出生就必须先洗澡,我们就可以在“人”的构造方法中加入完成“洗澡”的程序代码,于是每个“人”一出生就会自动完成“洗澡”,程序就不必再在每个人刚出生时一个一个地告诉他们要“洗澡”了。
构造方法的重载
1.和一般的方法重载一样,重载的构造方法具有不同个数或不同类型的参数,编译器就可以根据这一点判断出用new 关键字产生对象时,该调用哪个构造方法了。产生对象的格式是:new 类名(参数列表) ;
2.重载构造方法可以完成不同初始化的操作, 如:p3=new Person(“Tom”,18);语句,会做这样几件事:创建指定类的新实例对象,在堆内存中为实例对象分配内存空间,并调用指定类的构造方法,最后将实例对象的首地址赋值给引用变量p3。
p1=new Person(“Tom”,18) 的内存状态变化过程分析
!1637846774172](C:\Users\CYX\AppData\Roaming\Typora\typora-user-images\1637846774172.png)
构造方法的一些细节
•在java每个类里都至少有一个构造方法,如果程序员没有在一个类里定义构造方法,系统会自动为这个类产生一个默认的构造方法,这个默认构造方法没有参数,在其方法体中也没有任何代码,即什么也不做。
•由于系统提供的默认构造方法往往不能满足编程者的需求,我们可以自己定义类的构造方法,来满足我们的需要,一旦编程者为该类定义了构造方法,系统就不再提供默认的构造方法了。
•声明构造方法,如无特殊需要,应使用public关键字。
5.2 案例分析
银行账户对象的创建,要求能够存放用户的账号、户名、密码、和个人账户余额等信息,并包含存款、取款、查询余额和修改账户密码等操作,类名为Cust,并利用Cust类类创建对象,对象的账号为100,户名为Tom,密码1111,账户余额10000.
1 | Class Cust{ |
1 | Class MainDemo{ |
5.3 静态成员与实例成员
当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份,例如所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量。
静态成员(类成员):类的所有对象共享的成员,拥有有一个固定的存储 空间,每个对象都可以访问该成员,但通常只通过类名来访问,静态成员用static修饰符来修饰。
实例成员(对象成员):类的每个对象所独有的成员,不同对象的同一实例成员分别存储于不同的位置,只有通过具体的对象才能访问属于该对象的实例成员,实例成员没有static修饰符。
5.3.1静态属性与实例属性的比较
在Cust类中,增加属性bankName代表账户所属的银行,因为它是每个账户所共有的,将其定义为静态属性。再创建一个账户总数(或总编号)allNum,它代表当前一共创建了多少个账户,也不属于任何一个账户对象,而是属于Cust类的,也将其定义为静态属性。最后定义一个流水编号,它是对象所独有的,应该定义为实例属性。
5.3.2静态方法
•在静态方法里只能直接调用同类中其它的静态成员(包括变量和方法),而不能直接访问类中的非静态成员。这是因为,对于非静态的方法和变量,需要先创建类的实例对象后才可使用,而静态方法在使用前不用创建任何对象。
• 静态方法不能以任何方式引用this和super关键字(super关键字在下一章讲解)。与上面的道理一样,因为静态方法在使用前不用创建任何实例对象,当静态方法被调用时,this所引用的对象根本就没有产生。
•main()方法是静态的,因此JVM在执行main方法时不创建main方法所在的类的实例对象,因而在main()方法中,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员,这种情况,我们在以后的例子中会多次碰到。
5.3.3理解main方法的语法
由于java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public,又因为java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static的,该方法接收一个String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数。
1 | Public class StaticDemo{ |
5.3.4静态成员的加法
1 | Class HasStatic{ |
5.3.4静态代码块
•一个类中可以使用不包含在任何方法体中的静态代码块(static block ),当类被载入时,静态代码块被执行,且只被执行一次,静态块经常用来进行类属性的初始化。
•类中的静态代码块被自动执行,尽管我们产生了类的多个实例对象,但其中的静态代码块只被执行了一次。当一个程序中用到了其他的类,类是在第一次被使用的时候才被装载,而不是在程序启动时就装载程序中所有可能要用到的类。
5.3.5this是什么?
除了匿名对象外,任何一个对象一经创建,就会创建一个对该对象的引用——对象名;任何一个对象名都可以通过this来代替,通过”this.成员名”的方式,可以访问该对象的每个成员方法和成员变量。有时,这个this是必不可少的。
例子:实例变量与成员变量同名
1 | public class SimpleClassToShowThis{ |
由于Java将局部变量与成员变量同名时的变量优先视为局部变量,所以,在成员函数test()中想要访问成员变量a就可以通过this.a来实现。如下例。
5.3.6通过this来引用成员方法
如果func2方法被调用 ,一定是事先已经有了一个存在的对象,func2被作为那个对象的方法被使用。
在func2内部能引用别的对象 ,同样也能引用func2所属的那个对象。
在func2中,自己所属的那个对象的引用名称是什么呢? 答案就是this! this关键字在java程序里的作用和它的词义很接近,它在函数内部就是这个函数所属的对象的引用变量。
5.3.7this引用句柄的存放位置
每个成员方法内部,都有一个this引用变量,指向调用这个方法的对象,类中的成员方法与this之间的关系如图
5.3.8this引用句柄的应用
•一个类中的成员方法可以直接调用同类中的其他成员,其实我们在一个方法内部使用“this.其他成员”的引用方式和直接使用“其他成员”的效果是一样的,那this还有多大的作用呢?在有些情况下,我们还是非得用this关键字不可的 。
•让类的成员变量名和对其进行赋值的成员方法的形参变量同名是必要的,这样的代码谁看了都能明白这两个变量是彼此相关的,老手看到函数的定义,就能揣摩出函数中的代码,大大节省了别人和自己日后阅读程序的时间。
•假设我们有一个容器类和一个部件类,在容器类的某个方法中要创建部件类的实例对象,而部件类的构造方法要接收一个代表其所在容器的参数。
•构造方法是在产生对象时被java系统自动调用的,我们不能在程序中象调用其他方法一样去调用构造方法。但我们可以在一个构造方法里调用其他重载的构造方法,不是用构造方法名,而是用this(参数列表)的形式,根据其中的参数列表,选择相应的构造方法。
5.4 方法的重载
方法的签名:由方法名和方法的参数类型组成,Java通过方法签名而不是方法名来区分一个方法,这意味着可以有方法名相同但参数类型不同的方法。例如,Cust类的方法签名如下:
Cust(String,int,String,int )
getMoney(int)
setMoney(int)
search()
changePWD(String)
成员方法重载的例子
如果声明了两个或两个以上同名但参数类型不同的方法,这就称为方法的重载。例如,可以为Cust类的取款方法getMoney重载,考虑取款上限为Limited元,构造如下方法:
1 | void getMoney(int newMoney,int Limited) |
5.5 类的封装与访问控制
•如果外面的程序可以随意修改一个类的成员变量,会造成不可预料的程序错误,就象一个人的身高,不能被外部随意修改,只能通过各种摄取营养的方法去修改这个属性。
•在定义一个类的成员(包括变量和方法)时,使用private关键字说明这个成员的访问权限,这个成员成了类的私有成员,只能被这个类的其他成员方法调用,而不能被其他的类中的方法所调用。
5.5.1访问权限修饰符
public: 定义者和使用者可以位于不同的包(存放.class文件的文件夹)中
default: 定义者和使用者可以位于相同的包(存放.class文件的文件夹)中
private: 定义者和使用者可以位于同一个类中
protected: 定义者和使用者可以位于当前类或当前类的所有子类中
Java访问控制总结:
访问权限 | 同一个类 | 同一个包 | 子类 | 跨包访问 |
---|---|---|---|---|
private | 是 | 否 | 否 | 否 |
default | 是 | 是 | 否 | 否 |
prrotected | 是 | 是 | 是 | 否 |
public | 是 | 是 | 是 | 是 |
5.5.2实现类的封装性
为了实现良好的封装性,我们通常将类的成员变量声明为private,再通过public的方法来对这个变量进行访问。对一个变量的操作,一般都有读取和赋值操作,我们分别定义两个方法来实现这两种操作,一个是getXxx()(Xxx表示要访问的成员变量的名字),用来读取这个成员变量操作,另外一个是setXxx()用来对这个成员变量赋值。
一个类通常就是一个小的模块,我们应该让模块仅仅公开必须要让外界知道的内容,而隐藏其它一切内容。我们在进行程序的详细设计时,应尽量避免一个模块直接修改或操作另一个模块的数据,模块设计追求强内聚(许多功能尽量在类的内部独立完成,不让外面干预),弱耦合(提供给外部尽量少的方法调用)。用总统指挥一支军队的例子来说明这种效果。
5.3.3类的封装所带来的优点
•隐藏类的实现细节;
•让使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑,限制对属性的不合理操作;
•便于修改,增强代码的可维护性;