黑马程序员——面向对象2:类与对象

来源:互联网 发布:2016西决g6库里的数据 编辑:程序博客网 时间:2024/05/22 14:45
------- android培训、java培训、期待与您交流! ----------

我们平常说话所使用的语言,有一个很重要的功能,就是可以用来对事物进行描述。那么同样,面向对象的编程语言也可以对事物进行描述。

1、什么是类?

类,就是对现实生活中存在事物的描述。对象,就是类所描述的事物在现实生活中具体存在的个体。这就是类和对象之间的关系。

由上所述,我们了解了类和对象之间的关系,那么类和对象到底谁先存在的呢?这就好比是“先有蛋,还是先有鸡?”实际上,类和对象谁先存在,取决于思考问题的角度。如果从计算机的角度来说,当然要有类去对某个事物进行描述以后,才能通过“new”关键字,或者其他方法,获取到该类相对应的对象。然而,如果从人的角度来说,其实是先有了生活中存在的事物个体,才会想到去对它进行描述。

打个比方,类就好比是建筑图纸,对象就好比是通过图纸具体建造出来的建筑物。图纸当中会描述建筑的高度、宽度、形状等等属性和这个建筑的用途。

2、如何定义类

那么,该如何去对现实中的事物(对象,下同)进行描述呢?首先,需要找到这一类事物的共性内容(属性+行为),也就是对这一类事物的抽象过程。拿“人”举例,每个人都有姓名、年龄、籍贯等属性,和吃饭、睡觉和学习等的行为。需要指出的是,这里说的属性和行为,其实并不具体,因为并没有指定多少岁、叫什么、是哪里人,也并没有指定吃什么、怎么睡和学什么,但是,这些抽象属性和行为的组合可以用来描述“人”这一类事物,那么这个抽象的结果对于Java语言就是,类,而对象是以类为模板,通过上面提到的“new”关键字在“堆”内存中创建的实体,而在这个实体当中,就已经封装了具体的属性值(或称为数据),和行为的具体实现方式。

代码体现,以“人”为例:

代码1:

class Person{//描述属性//姓名String name = “Jack”;//年龄int age = 20; //描述行为(方法)//吃饭public void eat(){System.out.println("吃饭");}//睡觉public void sleep()        {System.out.println("Zzz...");}}
上述代码中,属性对应的是类中的变量,行为对应的是方法(函数)。因此,定义一个类,简单说就是两部分:定义属性和方法。Java中类的属性和方法,称为类的成员,因此属性叫做成员变量,方法叫做成员函数。

3、成员变量和局部变量

定义在类的成员位置上的变量称为成员变量。定义在方法内部的变量称为局部变量。请看如下代码:

代码2:

class Demo{//成员变脸int x = 1;public void f(){//局部变量int y = 2;}}
那么,这两类变量根本的区别是什么呢?那么,这两类变量根本的区别是什么呢?

1) 作用范围

成员变量作用于整个类,而局部变量仅仅作用于方法内部。因此,由于成员变量作用域广的特点,成员函数就可以随意调用定义在本类内部的成员变量。同样,由于局部变量作用域小的特点,因此,方法与方法之间是不能互相访问对方内部的局部变量的。

2) 在内存中的位置

成员变量随对象存在于堆内存中;布局变量存在于栈内存中。

 

小知识点1:在本类中创建本类对象

请看下面代码:

代码3:

class Demo{public void f(){System.out.println("HelloWorld!");} public static void main(String[] args){Demo d = new Demo();d.f();}}
主函数的代码称为在本类中创建本类对象。这个是完全可以的,并且可以用于测试。

 

小知识点2:主函数的作用

其实,并不是每个类都需要定义主函数,在实际开发时,主函数更多的作用是对新定义的类进行测试,检查有没有错误。因此,只需要定义一个主函数,并在其内部创建多个类的对象,并对其成员变量和成员函数进行调试即可。由此,我们可以看出,主函数其实际上相当于一个入口

4、如何创建对象

还是以人为例,创建人对象,就是通过“new”关键字,在堆内存中开辟一块内存,并存储该对象的所有属性,简单说就是在堆内存中创建一个人实体。格式如下:

new Person();

如果,后面的程序再次使用到这个Person对象,那么可以将这个对象在堆内存中的地址值赋值给一个Person类的引用数据类型变量,这样这个变量就“指向”了这个对象(关于对象在内存中创建过程参见后面的博客)。实际上,诸如上述代码的写法,没有Person类类型变量指向该对象的话,那么这个对象称为匿名对象。关于匿名对象的解释见下一节。简单理解就是,给这个对象“起个名字”。代码体现如下:

Person p = new Perons();

       上述代码中,p成为类类型变量,它属于三种引用型变量之一。具体来说,p是Person这个类的类类型变量。类类型变量总是指向该类产生的对象实体(或该类子类产生的对象实体,参见多态)。

5、匿名对象

代码4:

class Person{String name;int age; public String getName(){return name;}public int getAge(){return age;}public String toString(){return “name = ”+name+“, age = ”+age;}}
匿名对象,就是没有类类型变量指向的对象,实际上也就是与被类类型变量指向的对象相对。例如下面的代码:

new Person();

匿名对象主要的作用就是在一次性调用对象的成员方法的前提下,简化书写。比如如下代码:

Personp = new Person ();Stringname = p.getName();System.out.println(name);

可以简化为:

System.out.println(new Person.getName());

注意:仅仅调用这个匿名对象的成员变量是没有意义的。因为,当我们调用对象的成员变量进行赋值以后,由于并没有被类类型变量所指向,所以在后面的代码中也就根本用不到这个对象,换句话说,这个对象并没有起到任何作用。那么赋值动作结束以后,这个对象就成了垃圾。

相反,调用匿名对象的方法却是有意义的。因为这个对象实实在在起到了作用。

 

匿名对象的使用场合1:

如果仅仅是一次性的调用对象的某个方法,那就可以使用匿名对象。相反,如果需要调用该对象的多个方法,还是需要创建一个类类型变量并指向该对象。因此,我们需要注意:凡是简化的都是有局限性的。

匿名对象的使用场合2:

可以将匿名对象作为实际参数进行传递。例如:

public static void main(String[] args){Person p = new Person();setPerson(p);}public static void setPerson(Person p){p.age = 20;p.name = “Peter”;System.out.println(p.toString());}

下面我们具体描述一下上述代码的内存过程:首先,在栈内存中为主函数开辟一块空间,然后再在堆内存中为Person对象开辟一块空间,同时为该对象中的每个成员变量赋予默认初始化值,并为这片空间分配一个地址值。接着,为Person对象中的每个成员变量进行显示初始化(前述示例代码中均为“空”)。最终,将这片空间的地址值赋予栈内存中的类类型变量p,p就指向了堆内存中的Person对象。继续执行代码,调用setPerson方法的同时将上面变量p作为实际参数传入,这时就又要为setPerson方法在栈内存中开辟一块空间,并将实际参数p所指向的地址值赋给setPerson方法中的形式参数p,此时,setPerson方法中的Person类类形变量p(形式参数)也指向了堆内存中的Person对象。然后,通过setPerson中的两行代码,为Person对象的两个成员变量赋值。最终,通过输出语句,将赋值后的Person对象的age和name成员变量值打印在控制台。在执行完setPerson方法以后,该方法所在的栈内存空间,连同其内部的形式参数(或者局部变量p)都被释放,但是由于Person对象还在被main方法中的变量p指向而不是“垃圾”。只有当主函数也执行完毕以后,其内的变量p也得到释放时,Person对象才变为“垃圾”。(更为具体的分析,请参见后面的博客)

如果,对上述代码进行简化:

public static void main(String[] args){setPerson(newPerson());}public static void setPerson(Person p){p.age = 20;p.name = “Peter”;System.out.println(p.toString());}
此时,在内存中的过程,与简化前的代码唯一的不同就是——没有了实际参数p的过渡作用,而是直接将堆内存中的Person对象的地址值赋予了setPerson方法中的名义参数p,同样,p指向了Person对象。我们要注意,这里的匿名对象Person,在setPerson方法运行期间并不是“垃圾”,因为,已经有Person类类型变量(名义参数)指向了这个对象。但是,一旦setPerson方法运行完毕,此时,该方法所在栈内存空间(连同方法内的名义参数,或者局部变量p)得到释放,此时,Person对象不再被指向,就成了“垃圾”。

如果,想手动将Person对象变为“垃圾”,可以将主函数中的变量p赋值为“null”。

6、类类型变量和对象实体之间的关系

当通过上述代码创建Person对象时,首先,在堆内存中某个地址上为这个对象开辟一块空间,与此同时就为这个对象的所有非静态成员变量赋予默认初始化值(这个动作其实是在开辟空间的同时,将这块空间清零而发生的),类类型变量赋“null”,布尔型变量赋“false”,其他基本数据类型变量赋“0”。而后,再根据类中的描述,对对象的非静态成员变量赋予显示初始化值。最后,将这个对象在内存中的地址值赋予,栈内存的Person类类型变量p,则这个变量就指向了堆内存中的Person对象。

7、对象成员变量的修改与成员函数的调用

当需要修改对象的属性值时,可以采用下述代码(以上述Person类为例):

p.age= 23;p.name= “Lucy”;

当需要调用对象的成员函数时:

p.eat();p.sleep();

8、思考

(1) 观察下面的代码,思考两个类类型变量p1和p2是否指向同一个对象?

Person p1 = new Person();Person p2 = new Person();

答案是:这两个Person类类型变量指向了不同的对象。当我们每次使用“new”关键字创建对象时,都会在堆内存不同的地址上开辟出一块新的区域,因此地址值不同的对象当然是两个不同的对象,就好比,用同一个图纸生产出来的汽车并不是同一辆汽车。

(2) 观察下面的代码(继续以前述Person类为例),最终的输出结果是什么?

代码5:

class CellPhone{String color = “白色”;int weight = 100;      public void call(){System.out.println(“打电话”);}public void sendMsg(){System.out.println(“发短信”);}public void toString(){System.out.println(“color = ”+color+“,weight = ”+weight);}}
以上面CelloPhone类为例,

CellPhone cp1 = new CellPhone();cp1.color= “黑色”;CellPhone cp2 = cp1;cp2.weight= 200;cp1.toString();
答案应该是:color= 黑色,weight = 200

这是因为,两个CelloPhone类类型变量cp1和cp2均指向同一个CellPhone对象,无论通过哪个变量对对象进行操作,另一方进行输出操作(也就是调用toString()方法)的结果都是一样的,这就叫多个引用指向了同一个对象。


0 0