(参考http://www.cnblogs.com/xdp-gacl/p/3624100.html 点击打开链接,以此为模板 自己做了整理、修改)
目录
一. 面向对象的引入
1.1 面向对象
1.1.1 面向对象的思想
面向对象的基本思想,是从现实世界中客观存在的事物出发来构造软件系统,并在系统的构造中尽可能地运用人类的常规思维方式。面向对象更加强调人类在平时的日常生活中的逻辑思维、普遍的方法与准则,比如:抽象、分类,继承、聚合、多态等。人在思考的时候,首先眼睛里看到的是一个一个具体的对象。
1.1.2 面向对象的简单理解
在程序里面首先考虑的是这个问题领域或者程序里面应该具有哪些对象,而不是把注意力集中在具体的一步一步的过程。面向对象的思维是,当我们碰到某个具体的问题时,首先应该考虑这个问题涉及到了哪些对象,这些对象之间有着怎样的关系,再把它们抽象出来应用在程序中。
1.1.3 面向对象与面向过程的比较
面向对象:拿到一个问题先考虑这个问题涉及到哪些类和对象,这是第一点。然后在分析这些类和对象具有哪些属性和方法,这是第二点。最后分析类与类之间的有着哪些关系,这是第三点。
面向过程:拿到一个问题先考虑这个问题的处理过程,第一步做什么,第二部做什么,想的是具体的步骤。面向过程和面向对象的思想有着本质的区别。
面向对象有一个非常重要的设计思维:合适的方法应该出现在合适的类里面
1.1.4 为什么使用面向对象
面向对象编程,是通过一组对象之间互相配合,通过沟通完成特定的功能。从事软件开发有一个值得苦苦追求的境界就是提高代码的可重用性(reusable),可扩展性。如果使用面向过程,一般情况下属性和方法是分开的,它们没有聚合的关系,不是合在一起的,如果想重复利用就比较麻烦,而且复用的层次只能局限于方法这个层次上,而面向对象则不同,它是把属性和方法综合在一个类里面,当我们想复用的时候只需要将整个对象进行复用就行了,操作起来也方便。
1.2 Java和面向对象
对象和类是分不开的,首先必须定义了一个类才能构造出对象。首先定义了方法才能调用这个方法。对象是Java的核心,做任何事情你首先要给我构造出一个对象才能够去做。
在Java类中,包含静态的属性和动态的方法,是两者的一个综合。所以在抽象出一个类的过程中必须考虑两个方面,第一方面是它们有着哪些静态的属性,第二方面是它们有着哪些动态的方法。写Java程序的时候,我们一上来就是写 public class (声明一个类),在这个class的{ }中再添加成员变量和方法。
每一个Java里面的class(类)都对应于我们现实生活中的某一类事物,是对这一类事物的一个抽象。
先在Java中创建一个狗类,代码如下。通过后续内容的学习再来理解这个创建的过程。
package cn.javastudy.summary;
/**
* 一类事物封装到JAVA里面首先得写class,定义这个类,类名是什么可以自己取。
* 这里把类名叫做Dog
*/
public class Dog {
/**
* 接下来就是写这个狗这个类的属性或者叫成员变量,
* 比如说狗这个类的毛的颜色,怎么定义这个属性呢,
* 首先得定义毛的一个类型,如使用int来定义毛的颜色类型
*/
int furcolor; //定义属性:毛的颜色
float height; //定义属性:狗的高度
float weight; //定义属性:狗的体重
/**
* 狗的颜色,高度,体重这些属性定义完了,接下来要定义的就是方法了。
* 如写一个CatchMouse()方法,捉老鼠的方法。
* CatchMouse这个方法里面有一个对象类型的参数,捉哪一只老鼠,这个对象参数是属于Mouse这个类的
* @param m
*/
void CatchMouse(Mouse m){
//在方法体内写捉老鼠这个过程,怎么捉,跑着捉,走着捉
System.out.println("我捉到老鼠了,汪汪!,老鼠要尖叫了!");
/**
* 老鼠尖叫一声,表示被狗咬到了,咬到了能不叫吗,很自然而然地想到,
* 尖叫(scream())这个方法是属于Mouse这个类里面的某一个方法。
* 老鼠自己调用它,让它自己尖叫。这就是面向对象的思维。
*/
m.scream();
}
public static void main(String[] args) {
Dog d = new Dog();//首先用new关键字创建一只狗
Mouse m=new Mouse();//造出一只老鼠。
d.CatchMouse(m);//然后用这只狗去抓老鼠,让狗调用CatchMouse()方法去捉某只老鼠。
}
}
package cn.javastudy.summary;
/**
* 封装的老鼠类
*/
public class Mouse {
/**
* 老鼠自己有一个发出尖叫的方法
* 当被狗咬到时就会发出尖叫
*/
public void scream() {
System.out.println("我被狗咬到了,好痛啊!");
}
}
从这个意义上来讲,Java里面所定义的每一个类就相当于是一种新的数据类型。跟 int , float , String 等是一样的,只不过它是一种新定义的类型罢了。
二. 对象与类
2.1 对象和类的概念
对象是计算机语言中对问题域中的事物的描述,对象通过“属性(attribute)”和“方法(method)”来分别对应事物所具有的静态属性和动态方法。
类是用于描述同一类别的对象而抽象出来的概念,类中定义了这一个类别的对象共同具备的静态属性和动态方法。
类可以看成是同一类别对象的模板,对象可以看成是某个类的一个具体实例。
eg.什么叫瓶子?
瓶子的定义:具有某些类特征的东西就是瓶子,比分说什么样的形状,比方说有个口,能倒水,能装水,一般有个盖等等。给瓶子下定义的过程,其实就是把瓶子里的某些东西抽象出来了,所以瓶子在这里是叫做一类事物的一个抽象,在你脑子里有瓶子的概念,可瓶子的概念在你脑子里到底是什么呢?瓶子的概念在你脑子里叫做一类事物的一个抽象。怎么抽象的呢?你往往抽象的是这两个方面:第一个方面我们叫它静态的属性,瓶子应该具有哪些特征,比分说瓶子应有个口,这是它的具有的一个静态属性,瓶子一般有一个盖,这也是它的具有一个静态属性,除此之外,你还可能给它总结动态的属性,什么动态的属性呢?比放说瓶子能倒水,这是它的动态属性。瓶子这个概念在你脑子里如果你细细的思维的话,其实你给它做了两方面的总结,一方面是静态的,一方面是动态的。反映到Java的类上,一个就是成员变量(静态属性),一个就是方法(动态属性)方法是可以执行的,可以动的。成员变量是某一个类的静态属性。所以你脑子里瓶子的概念实际上是一类事物的一个抽象,这种东西我们叫它类,椅子是类,桌子是类,学生是类。什么是对象呢?这一类事物的具体的某个实例就叫做对象。所以一类事物的具体的某一个东西,符合这类事物具体的特征的某个东西就叫做对象。瓶子是一个类,某个瓶子就是瓶子这个类里面的一个对象。
2.2 如何抽象出一个类
有两个方面,一个方面是它的静态属性,另一个方面是它的动态方法。反映到Java的类中就是一方面为成员变量,另一方面为方法。
eg.职员这个类该怎么抽象出来?也是从两个方面,一方面是它的静态属性,另一方面它的动态方法
职员有哪些属性呢?有姓名,年龄,目前工资数额等属性,他有哪些方法呢?让这个职员来显示姓名,显示年龄,修改姓名,领取工资。当然显示姓名,显示年龄,修改姓名,领取工资这些也可以让别人来做,但面向对象的设计思维是最合适的方法应该出现在最合适的类里面。显示姓名,显示年龄,修改姓名,领取工资由谁来做更合适呢,那就是职员自己最合适。所以这些方法应该出现在职员这个类里面。
一定要区分类和对象,什么是类?什么是对象?类是具备相同特征的同一类别的事物的抽象,而对象则是这个抽象出来的类的某一个具体的实例。也可以将实例(Instance)和对象(Object)理解为同一事物。对于类来说,它具有一些属性(成员变量),具体的对象也有属性(成员变量),每一个对象都各自有一份,只是具体到每一个对象它们的属性取值不同而已。
eg. 比如从职员这个类实例化出来的两个职员:职员A和职员B,他们都有姓名,年龄,目前工资数额这些属性,但他们的名字,年龄,领取的工资数额都不一样。这样就能把职员A和职员B区分开来了,正是因为他们的属性值不一样,所以这个对象才能和另外的对象区分开来,所以通过属性是可以区分两个对象的。猫是一个类,这只猫是一个对象,这只猫和另外一只猫该怎么区分开来呢?那就得看你的猫这个类是怎么定义的了,猫有猫毛,毛有颜色。OK,这只猫是黑猫,另一只猫是白猫,这样通过猫毛的颜色区分开来了。如果只定义一个,如捉老鼠,白猫也能捉,黑猫也能捉,这样就没办法区分出黑猫和白猫了,所以根据方法是没办法区分两个对象的。所以每个对象都有自己的属性,属性值和另外一个对象一般是不一样的。
2.3 类(对象)和类(对象)之间的关系
2.3.1 关联关系
最弱的一种关系,两个类或两个对象之间有关系,但是关系不紧密。研究生类和教授类,教授可以指导研究生,研究生可以向教授求教,这就是两者之间的关系。例子中的是指导与求教的关系,而关系和关系又是不一样的,比如你和你老婆的关系,你和其她女性朋友的关系是不能够混为一谈的。
关联关系反映到代码中往往是一个类的方法里面的参数是另一个类的具体的某一个对象,比如上文例子中猫咬老鼠老鼠叫了,是哪只老鼠在叫?咬是猫类里面的一个方法,这个方法的参数是某一个具体的老鼠类构造出来的老鼠对象,这只猫所咬到的这只老鼠在叫。
2.3.2 继承关系
继承关系的逻辑思想是:“XX是一种XX",是比较强的一种关系,eg. 游泳运动员继承运动员,游泳运动员是一种运动员。学生继承人,学生是一种人;老师继承人,老师是一种人,学生不是一种老师,所以学生类不继承老师类。球类运动员继承运动员,球类运动员又派生出足球运动员、篮球运动员、排球运动员等等。
上图中类的关系类似于一个继承树,不过这是在理想化的情况之下,只有一个根节点。实际开发中的情况肯定要复杂得多,一个类可以从多个类继承过来,eg. 金丝猴类继承动物类,另外它也继承”受国家保护的物种“这个类,所以现实中一个类完全有可能继承多个父类,C++正是因为想封装这种复杂的继承关系,所以C++中存在多重继承。而Java只有单继承,但是Java中可以通过接口来弥补单继承的不足。
2.3.3 聚合关系
聚合就是整体与部分的关系,”XX是XX的一部分“。上图中队长是球队的一部分,队长和球队是聚合关系;队员也是球队的一部分,队员和球队也是聚合关系。聚合关系分得再细一点的话就是聚集关系和组合关系,eg. 比如球队、队长、队员,这三者是聚集关系,假如这个队长既是足球队的队长同时又是篮球队的队长,球队和队长之间不存在我离不开你,你离不开我的情况。还有一种情况叫做组合关系,组合关系说的是两者之间密不可分。一个人的脑袋不可能既属于你又属于他,所以脑袋和你是密不可分的组合关系。eg.男A和女B女C都是同学,但是男A娶了女B和女C只是普通同学,男A和女B的关系更为密切,是一种更为严格的聚合关系,专门取名为组合关系。
2.3.4 实现关系
父类中有某个方法,但是没有具体地实现,而留给他的子类去实现,这就是实现关系。和实现关系相关的还有一种叫作多态。
2.3.5 多态
2.4 Java中类的代码编写
2.4.1 Java中类的定义
Java中通过class关键字定义一个类,在class关键字之后加上自定义的类名即可。上图中使用 class person 定义了一个person类,然后在person类的类体里面定义这个person类具有的属性(成员变量)和方法。其中 int id 和 int age 就是两个属性(成员变量),一个人具备身份证号 id 和 年龄 age 。而其中的getAge()、setAge(int i) 和 getId()就是三个方法,分别用来获取人的年龄、设置人的年龄 和 获取人的身份证 id ,在方法 getAge() 中使用return age ; 语句返回年龄值,在方法 getId() 中使用return id ;返回身份证号,在方法 setAge(int i) 中 以 age = i ;给年龄赋值。
2.4.2 成员变量
Java中的任何变量都要先声明,然后再赋值,然后再使用。
成员变量和局部变量有一个重要区别:成员变量在类里面声明的时候如果没有进行初始化Java会给它赋一个默认的值,而局部变量Java不会帮它初始化,所以在方法里面声明一个局部变量的时候一定要记得给它赋一个初始化值,否则会报错。默认初始化值大多是0 ,Boolean类型的为false ,引用类型的为 null 。
三. 对象的创建和使用
3.1 引用数据类型
引用数据类型和基本数据类型两者间存在着巨大的区别,当声明一个变量 int i = 0 ; 时,系统会马上给这个变量 i 分配一个内存空间(在栈内存中分配一小块区域用来装数字0),这个内存空间里面的值是0,以后需要使用这个值的时候就通过 i 这个变量名进行访问,这就是基本数据类型,所以基本数据类型只占 1 块内存。基本数据类型之外的数据类型统称为引用数据类型。我们定义一个 Mouse m ,这个 m 就是一个引用类型的数据,引用类型的数据占 2 块内存,我们定义好某个类之后,还需要通过new 关键字把这个类的对象实例化出来,只有真正地创建一个具体的对象出来之后才能使用。
如何在内存中区分类和对象
类是静态的概念,是位于代码区里面。对象是new出来的,它是位于堆内存,为什么对象要位于堆内存?因为堆内存是用来动态分配内存的,只有在运行当中才会new一个对象放堆内存里面,那这个对象到底有多大个,这个东西你不知道,你没有办法提前知道,所以你没有办法提前分配内存给这个对象,你只有在运行期间才能去分配它。什么叫运行期间?敲Javac这个命令那是在编译期间,编译完成后再敲Java命令,那就是运行期间了。只有在运行期间,才能够明白这个对象到底要分配多大的空间给它,所以把它放在堆内存里面,堆内存比较大,动态分配内存用它。如果这个对象不用了,那它就是垃圾,那么就等着垃圾收集器把它收集回去,释放掉占用的内存。
记住,以后一提到引用类型的数据,脑子里马上浮现引用类型就是一小块内存指向一大块内存。
3.2 类和对象的关系
”栈定堆动“,在内存中分析 类和对象 的关系。上图中的C类,程序从main( ) 方法开始运行。第一句是 C c1 = new C( ) ; 即在堆内存中创建了一个对象,同时也创建了这个对象的一个引用对象 c1 ,c1 位于栈内存中,c1这个引用对象指向堆中一大块内存,这一大块内存里面装的是 new 出来的那个对象。这里我们一般来说new 出来的是两个对象 c1 和 c2 (严格意义上来讲, c1 和 c2 叫做对象的引用,有时候,简称为new 出来了两个对象 c1 和 c2) 。这时候脑袋中马上要浮现出两块内存。局部变量是分配在栈内存里面的,main 方法中的 c1 和 c2 都是局部变量,所以它们在栈内存里面有分配出两小块内存,一块是 c1 一块是 c2 , c1 这块内存里面装着一个值,或者叫装着一个地址,我们不知道这个地址是什么,但是我们可以根据这个地址的值找到new 出来的C类的一个对象,而在这个对象里面有着它自己的属性(成员变量) i 和 j,里面的两小块内存是分别用来装 i 和 j 的值的,因为每一个对象都有着属于自己的属性值(成员变量),所以 c1 指向的那块内存有被分成一小块一小块,每一小块内存里面都装着这个对象的一个属性(成员变量)。如果这里的第一块装着 i 的值,第二块装着 j 的值,所以当我们去访问第一小块内存里面装着的属性(成员变量)的时候,我们应该写成 c1.i,这样才可以拿到 i 的值 ,c1.j拿到 j 的值。c2也是同理。
3.3 构造方法
构造方法(又叫,构造函数)是一个特殊的方法。构造方法是用来创建一个新的对象的,与new 组合在一块使用,使用 new + 构造方法 就可以创建出一个新的对象。在new 出一个实例的时候实际上就是在调用了一个构造方法,通过构造方法生成一个新的实例对象。
构造方法(构造函数)比较特殊,它的名字必须和类的名字一模一样,包括大小写,因为Java是区分大小写的。而且构造方法是没有返回值的,在构造方法的前面不能写任何方式的返回类型修饰符,连void 都不能写。系统会默认一个无参的构造方法,如果自己在类中有声明成员变量,也可以将成员变量作为参数重新写出不同的构造方法。如上例中默认有 构造方法Person(){},可以添加Person(int n){}, Person(int i){}, Person(int n,int i){},这属于方法的重载。但是,如果在类中显示地定义了一个或多个构造方法,并且所有的构造方法都带有参数,那么这个系统默认的无参构造方法就会自动消失掉。
构造方法案例,代码如下:
public class Person {
int id; //在person这类里面定义两个成员变量id和age,
int age=20; //给成员变量age赋了初值为20
/**这里就是person这个类的一个构造方法
* 构造方法的规则很简单,和类名要完全一样,一点都不能错,包括大小写。
* 并且没有返回值,不能写void在它前面修饰
* @param _id
* @param _age
*/
public Person(int _id,int _age ) {
id = _id;
age = _age;
}
}
构造方法写好后再与new 组合起来使用,就可以构造出一个新的实例对象。使用new 的时候就是在调用构造方法。
public static void main(String[] args) {
Person tom = new Person(1, 25); // 调用person这个构造方法创建一个新的对象,并给这个对象的成员变量赋初始值
}
当main 方法中调用 Person 构造方法的时候,内存的使用情况具体如下图所示:
当方法调用完成后,堆栈内存中为它所分配的空间会全部消失,即把这个方法调用时分配给它的内存空间全部释放出来,让其它的方法去占用。而new 出来的对象则永久地留在了堆内存中。
------------------------------------------------------------------ 我是低调的分隔线 ----------------------------------------------------------------------
吾欲之南海,一瓶一钵足矣...