黑马程序员----面向对象???(略长)

来源:互联网 发布:现在淘宝卖零食好做吗 编辑:程序博客网 时间:2024/05/01 23:51

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------


一、什么叫面向对象

相信学过C语言的同学们都知道,C语言是面向过程的,那么Java是面向对象的,这都是什么意思呢?

面向过程:

面向过程是C语言的编程思想,是一种思路。我们都知道编程就是解决问题的,那么现在有这样一个需求:用程序来描述一个人进房间看电视。
main(){开门();进房间();关门();打开电视();看电视();}
这就是用面向过程的思想来描述这个问题,我们观察能发现其实面向过程解决问题就是把问题分成一个一个的小问题,然后利用函数进行模块化编程,来解决问题。

面向对象:

面向对象同样是一种编程思想,Java就是面向对象的,现在同样上一个问题,但是我们使用面向对象的思想来解决。
main(){门.开();人.行走(房间);门.关();电视机.开机();人.观看();}
我们可以看到这就是使用面向对象的思路来解决同样的一个问题,似乎两种方式有很大的差别对吧?那么我们下面就分析一下它们的区别。

面向过程和面向对象的区别:

当我们使用面向过程解决问题时,我们的程序是由一个一个的功能组成的,也就是说面向过程的侧重点是功能的实现,将问题分解为一个一个的小功能来挨个实现调用,最终解决问题。但是当我们使用面向对象的思想解决时,我们的侧重点变成了一个一个的对象,从上面伪代码可以看出,不管是开门关门,进房间,还是看电视等,都不仅仅是一个功能,而是先有了一个一个的对象,也就是门,人和电视机。然后让这些对象提高功能给我们来使用,这就是两种思想最大的差别:面向过程更像是一个执行者,自己调用函数来解决问题,而面向对象则更像是一个指挥者,它不亲自实现这些功能,而是找到应该能实现这些功能的对象,让这个对象帮它完成工作。例如开门这个功能:面向过程就是自己学会开门,然后去开门。而面向对象则是去找到门这个对象,使用它的开功能。

面向对象开发流程:

查找对象,建立对象,使用对象,维护对象之间的关系

举例:我们要造汽车。首先我们需要一个图纸(汽车类),然后通过这个图纸造出不同的汽车(不同的对象),之后我们就可以使用汽车对象的功能了。


小结:

面向对象开发是一种开发思想,这种开发思想是基于面向过程的,它的核心是将万事万物看做对象(万物皆对象)。然后设计它们对应的类,然后根据每个对象的特点创建类的实例,之后就可以使用这些对象的功能了,这种开发方式在开发大型程序时的优势要远大于面向过程,它在代码的复用性,扩展性和可维护性方面都优于面向过程,因此现在的主流语言都是面向对象的,或者至少是部分面向对象。



二、什么叫做类

在上一小节中,我们提到了类,那么什么叫做类呢?这要从对象说起,所谓对象就是现实世界中一个个有形或者无形的具体事物,例如:桌子A,椅子B,人C,蚂蚁D,文章E等等。也就是说对象指的是一个具体的事物,比如一个叫做何龙的人,一条叫旺财的狗等等。到此我们发现问题了,我们总不能为每个对象都定义一吧,这样的话工作量也太大了点,光是人就要定义个几十亿个。。。。。因此我们发现虽然每个人都是特有的,但是每个人都有共性的东西,例如:姓名,年龄,身高,职业等等。我们是不是可以把这些共性的部分提取出来呢,然后在创建对象时,按照每个人对象的特性创建,答案是可以的,这就是类。类就是对一类事物的描述,例如学生类,它包含有姓名,年龄,成绩,学号等,但是它又不会具体的说明姓名是什么,或者学号是什么等,而是在创建对象的时候由创建对象来决定这些属性的具体数值

类的作用:

类的功能主要体现在它可以大大提升代码的复用性和扩展性,同时类也是面向对象的基础,没有类,也就没有面向对象编程。这也很好理解,如果没有图纸,我们怎么做出汽车呢?面向对象编程是比较抽象的一种编程思想,需要大家去想象,我觉得记理论太难,而且也不易于理解,如果我们能想到好用的例子,那么最好了。就好像此处的类与对象的关系,我们比喻成图纸和汽车的关系,他们之间的关系就很明显了。


类的成分:

既然我们说类是用于描述事物的,那么我们到底要描述事物的哪些方面呢?其实我们会发现,不管是任何事物,对它的描述无外乎就两个方面,一个是表现特征,也就是属性,另一个是功能,也就是方法

属性--成员变量:

说到变量,大家肯定都知道局部变量,这也是面向过程中有的东西,当我们需要存储数据时,我们就会使用到局部变量,那么这个局部变量跟成员变量有什么关系呢?
1.作用
二者的作用相同,都是为了存储数据而存在的。
2.存放位置
局部变量存放在栈中,而成员变量存放在堆中
3.生命周期
局部变量生命周期与它所在的代码块相关,一旦到了代码块结束,它也就消亡了。
而成员变量要分两种情况:
a.静态成员变量:它的生命周期与类一样
b.非静态成员变量:它的生命周期与对象一样
4.初始值
局部变量没有初始值,因此一般定义时都要初始化,而成员变量由于存在堆中,在堆中的数据是有默认初始值的。
堆中数据类型对应初始值:int:0,double:0.0,float:0.0f,boolean:false,char:空格->'\u0000',引用类型:null。 

说到这里,大家都能看出成员变量与局部变量的不同了吧。那么成员变量具体是干什么的呢?

通俗的说它就是形容一个事物的特征的。例如形容人:名字,年龄,身高,职业等。这都是形容一个人的特征,因此它们都属于成员变量。

方法--成员方法:

说方法可能不太利于理解,我们可以称之为功能,它是描述一个事物会什么的,还是举个人的例子:吃饭,睡觉,走路等等。这些都是人这个类的方法。

属性,方法与类:

到此我们该总结一下这三者的关系了,还是形象的举例子来形容吧。
名字叫何龙,年龄为20岁,身高171.........的一个人........他会吃饭,睡觉,走路,编程。
这就是它们三者的关系。



三、四种引用类型

什么是引用类型呢?

我们知道数据类型大方面分两种:1.基本数据类型(int,char,boolean等)2.引用数据类型
引用数据类型包括数组,类,接口。

引用类型分类:

a.强引用:A a = new A();此时即便内存不够了,虚拟机也不会释放该内存而是抛出异常终止程序

b.软引用:内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存,

软引用可用来实现内存敏感的高速缓存

c.弱引用:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,

 一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存

d.虚引用:虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,

在任何时候都可能被垃圾回收器回收



四、面向对象三大特征之封装

封装:隐藏(不一定是私有化,例如包也是一种封装机制)对象的属性和实现细节,仅对外提供公共访问方法
最小封装体:函数。
原则:把不需要对外公开的内容都隐藏起来,把所有属性都隐藏起来直接访问属性有安全隐患,缺乏数据校验),提供公共方法访问
好处:1.隔离变化,2.便于使用,3.提高重用性,4.提高安全性

面向对象编程本身就是在不停的封装,封装事物,形成类,这就好像我们使用电脑一样,电脑就是被封装好了,内部的结构,硬件都隐藏起来了,对外提供的就是主机上的几个键和一堆接口而已。这样封装的目的也很明显,首先我们使用电脑更简单了,看起来也舒服啊对吧。其次就是更安全了,因为我们本身不懂电脑的内部结构,如果就这么暴露出来,那很容易不小心破坏它,而且由于我们不懂,所以出了问题也没法解决。再其次就是我们根本没必要看到内部的结构,我们只是使用电脑的功能而已。

这就是封装的好处,可以让我们在轻松简单的使用电脑强大的功能的同时,不需要去了解电脑的内部结构,而且也保护了电脑本身不会轻易的被破坏。



五、构造函数

它也是一个方法,但是它是一个比较特别的方法。

特点:

1.这个方法是没有返回值的,学过面向过程的同学们都知道,定义一个函数的第一步就是确定返回值类型。而且构造函数的这个特点可不同于返回值为void的函数。
返回值为void的函数它依然是有返回值的,只是返回值类型为void而已。
2.这个方法的名字与类名一致
3.这个方法内部不能写return语句,当一个普通函数返回值为void时,也可以不写return语句,但是这只是显式的没写,但是系统会自动加上一句return ;因此它依然是
有返回语句的,而构造函数则是根本没有返回语句。
4.这个方法不是我们来调用的,而是由系统自动调用的

作用:

构造函数的作用就是对对象进行特定的初始化,我们知道即便都是属于学生类的几个学生对象,他们的属性也是不同的,例如有的名字叫张三,有的叫王六等等,而且这些属性应该是对象被创建的时候就具有的,那么给这些属性赋值的行为就应该放到构造函数中。
这点不同于成员方法,成员方法的作用是为类添加功能

与成员方法区别:

除了上述的特点的区别外,在使用上也有区别,成员方法作为类的功能,是在被对象或类(静态方法)调用时才执行,而构造函数则是在对象创建是自动被系统调用执行的。成员方法可以被调用多次,这就好像一个人可以多次吃饭一样,而构造函数只在创建时调用一次,这就好像人只在出生的时候起一次名字,确定一次性别一样(比喻不太恰当哈)。


定义构造函数:

观察以下代码:
class Demo{}
我们发现其中并没有构造函数,难道构造函数也像成员函数一样是可有可无的吗?
答案是NO,其实有很多时候我们可能并不需要构造函数,那如果这样还要写一个空的构造函数的话,代码就显得很冗余,因此jvm发现我们没有写构造函数时,它就会自动添加一个空参数空函数体的构造函数到类中,因此可以说一个类是肯定有构造函数的。这点也是构造函数不同于成员方法的一点
上面代码实际上等价于以下代码:
class Demo{Demo(){}}
那么我们就要考虑了,到底什么时候需要自己定义一个构造函数呢?
其实很简单,当我们要创建的对象有一些初始的属性时,我们就要使用构造函数了,例如一个人,他被创建出来名字,年龄,体重这些都是要有的,因此我们将这些内容放到构造函数中,例如以下代码:
class Person{private String name;private int age;private double weight;Person(String name,int age,double weight){this.name=name;this.age=age;this.weight=weight;}}
我们可以看到,这个人有三个属性,也就是成员变量:name,age,weight。它有一个构造函数Person,当jvm发现这个构造函数后就不会自动添加那个无参构造函数进来了。在构造函数内,我们使用三个参数分别对三个属性进行了初始化,这里我们发现了一个特别的词this,这个我们下节讨论。
既然构造函数也是一个函数,只是有点特别而已,那么构造函数是否可以像普通函数那样重载呢?
答案是YES,例如以下代码:
class Person{private String name;private int age;Person(){name=null;age=0;}Person(String name){this.name=name;}Person(String name,int age){this.name=name;this.age=age;}}
可以看到,构造函数也是可以重载的,且重载的原则与普通函数一致。

构造代码块

说到构造函数,不得不提另一种初始化方式,那就是构造代码块。构造代码块的功能也是对对象进行初始化,不过不同于构造函数的是,构造代码块是对该类创建的所有对象进行初始化,而构造函数是有针对性的进行初始化
构造代码块的书写也很简单,就是在类中一对大括号括起来就OK了。
例如以下代码:
class Chinese{private String name;private int age;private String Country;Chinese(String name,int age){this.name=name;this.age=age;}{Country="中国(CN)";}}class PersonDemo{public static void main(String[] args){Chinese ch1=new Chinese("helong1",22);Chinese ch2=new Chinese("helong2",29);}}
我们可以看到,我们使用构造函数对姓名和年龄这个每个对象都不一定相同的属性进行初始化,而使用构造代码块对中国人类的每个对象的国籍进行初始化,因为我们知道中国人类的国籍都是中国,这是大家都一样的部分,因此我们使用构造代码块来进行初始化。
因此构造代码块的作用就是对所有对象都一样的部分进行初始化


六、this关键字

在上面的程序中,我们多次看到这个家伙this,那么它到底是个什么东西呢?有什么作用呢?为什么总是使用它呢?下面我们一一介绍。

what is this?

this代表正在调用this所在方法的对象。在我们没使用this前,是否还考虑这样一个问题:我们知道每个对象的属性都不一样,这就是说每个对象在堆中有自己的空间存放自己的属性,当我们使用方法访问到对象的属性时,我们发现貌似没有明确的标示说这个属性是哪个对象的,例如以下代码:
class Demo{private int count;Demo(int c){count=c;}public void show(){System.out.println(count);}}main方法中:{Demo d1=new Demo(22);Demo d2=new Demo(33);d1.show();d2.show();}
我们知道打印结果是22和33,那么我们不仅要问了,我们都知道,类中的方法是存在方法区的,且只有一份,也就是对象共享的,而且方法内并没有标示是打印哪个对象的count属性,那么jvm是如何确定的呢?
到此,该是我们this出场了,没错,就是this,this在此处代表的就是正在调用show方法的对象,也就是在d1.show();中this代表d1,可是这也没有解决明确count的问题啊?其实jvm会在show方法内的count前默认加上一个this编程this.count,也就是d1.count,这样就可以明确知道是哪个对象的count属性了。那么既然this不加也可以,我们为什么要学它呢?就一直让jvm自动添加呗,这就涉及到了下一节。

this作用:

作用:1.解决局部变量和成员变量同名的情况,2.用于构造函数间互相调用
以上就是它的作用,下面用代码来体会它的这两个作用。例如一下代码:
class Demo{private int count;private int temp;Demo(int count){//1.解决局部变量和成员变量同名的情况this.count=count;}Demo(int count,int temp){//2.用于构造函数间互相调用this(count);this.temp=temp;}}
下面我们解释一下这两个作用:
1.这个问题出现在构造函数以及成员方法内,如果这些函数是带参数的,且最有可能的就是这些参数是用于给对象的属性赋值的,或者比较的等等,通常这类参数的名字都是与成员变量一致的,例如姓名name,传入的参数一般也为name,因为这样更加的见名知意,如果刻意的修改,例如为了避免重复而将参数该为personName等,会增加阅读的难度,且不符合变量命名标准。但是如果使用同名参数,那么在代码中就变成了如下;
Demo(int count){count=count;}
我们的初衷是将count赋给对象的count,但是编译器可不知道,编译器查找一个名字的顺序是:先从局部变量开始找(参数也是局部变量),找不到再去找成员变量等,因此此处在编译器看来就是将参数count的值赋给了参数count。这明显不是我们的目的,因此我们需要使用this来表示第一个count是当前对象的count属性,如下代码:
Demo(int count){this.count=count;}
这样就达到了我们的目的,且很好的保持了命名的规范性和代码可读性
2.当我们在使用构造函数初始化对象时,经常会重载构造函数,例如人类的构造函数,我们可以定义一个初始化姓名的,再来一个初始化姓名和年龄的,这个时候在定义初始化姓名和年龄的构造函数时,如果可以把对姓名的初始化交给那个初始化姓名的构造函数来做,那是大大的提高了代码的复用性的,因此我们需要构造函数间的互相调用。而构造函数的互相调用不像其他成员方法那样直接使用方法名,传入参数即可。而是用this来代替方法名,传入参数。因此可以说this的这个作用的主要目的是提高代码复用性
小知识点:如果构造函数内部使用this互相调用,那么该this语句必须放在运行代码的第一行
原因分析:这也是很好理解的,因为构造函数本身是对数据进行初始化的,如果这个初始化的函数不在第一行,那难免会发生逻辑混乱


七、static关键字

static简介:

静态,用于修饰类中的成员,被static修饰的成员变量将不再存储于堆中,而是存放到方法区中,也就是原来存放类中方法的那个地方。


静态特点:

1.随着类的加载而加载到共享区(也就是上图的方法区)中(当类加载到内存中时,静态变量(类变量)和方法已经在内存中了,而普通成员变量(实例变量)还没出现)。
2.优先于对象存在(没有对象也能使用静态数据)。
3.被所有对象共享
4.可被类名直接调用

静态成员:

静态变量:由于类的静态变量在对象被创建前就存在了,因此我们可以得知这个变量的值是对象间共享的,因为就一份。也就是说那些共享数据才会被设置为静态变量。大家在这里要注意了,不是共享属性,而是共享数据,也就是说人这个类的对象都有姓名这个共享属性,但是姓名的值不是共享数据,因此姓名不能被定义为静态,而中国人类的国籍这个共享属性的值也是一样的共享数据,因此国籍可以被定义为静态的
静态方法:当静态方法不访问非静态成员时就定义为静态,好处是可以使用类名直接调用该方法

静态利弊:

利:1.对于对象中共享的数据开辟一个空间,节省内存(对于静态变量来说)。2.可以直接被类调用。
弊:1.生命周期过长。2.静态方法只能访问静态成员,这是它的局限性。

静态代码块:

很类似于构造代码块对吧,其实它的功能也是类似的,就是对类(不是对象,因为这时还没对象呢)进行初始化

到此我们学了很多初始化的方式了,包括:构造函数,构造代码块,静态代码块,堆中的默认初始化,类中的显式初始化,那么当它们都存在时的顺序是怎样的呢?

先看代码:
class Demo{private int count=5;//类中显式初始化Demo(int count){//1.解决局部变量和成员变量同名的情况System.out.println("构造函数初始化前的count:"+count);this.count=count;System.out.println("这是构造函数!");}{System.out.println("构造代码块初始化前的count:"+count);count=6;System.out.println("这是构造代码块!");}static{System.out.println("这是静态代码块!");}public void show(){System.out.println(count);}}class DemoTest {public static void main(String[] args) {Demo d=new Demo(10);d.show();}}
运行图:

从图中可以得到结论:最先运行的是静态代码块,其次是显式初始化,然后是构造代码块,之后是构造函数

对象的初始化过程:
1.class文件加载到内存中。
2.静态代码块运行。
3.在堆中开辟空间,分配地址。
4.在堆中建立特有属性,进行默认初始化。
5.类中显式初始化。
6.构造代码块初始化。
7.构造函数初始化。
8.将在堆中分配的地址赋给栈中的对象名。

八、综合练习

/*4.面向对象:查找对象,建立对象,使用对象,维护对象间的关系5.匿名对象(只使用一次对象的方法时)6.成员变量和局部变量7.封装(函数是最小封装体)练习基本封装思路(所有数据封装,提供公共访问接口set,get)8.构造函数(重载),构造代码块(运行优先于构造函数)9.this关键字a.局部变量和成员变量同名的问题b.构造函数间互相调用(此语句需放到第一行最先执行---即:当初始化函数中还有初始化语句时,要最先执行初始化语句)*/class Person{private String name;Person(){name="";age=0;}Person(String name){if(name.length()>20)System.out.println("名称过长,请少于20个字符!");else//通过this确定成员变量,jvm查找name时优先在函数内部找,如果不加this,则两个name都是指形参this.name=name;}Person(String name,int age){//this用法:构造函数间互相调用,必须放在函数第一行this(name);if(age<=0||age>130)System.out.println("年龄数据不正常!");elsethis.age=age;}public void setName(String name){if(name.length()>20)System.out.println("名称过长,请少于20个字符!");else//通过this确定成员变量,jvm查找name时优先在函数内部找,如果不加this,则两个name都是指形参this.name=name;}public String getName(){return this.name;}private int age;public void setAge(int age){if(age<=0||age>130)System.out.println("年龄数据不正常!");elsethis.age=age;}public int getAge(){return this.age;}public void show(){System.out.println("姓名:"+this.name+",年龄:"+this.age);}}class Test2{public static void main(String[] args){//匿名对象new Person().show();//姓名:,年龄:0Person p1=new Person();p1.setName("helong");p1.setAge(23);p1.show();//姓名:helong,年龄:23Person p2=new Person("liudongyuan");System.out.println(p2.getName());//liudongyuanp2.setAge(144);//年龄数据不正常p2.show();//姓名:liudongyuan,年龄:0Person p3=new Person("zhegemingzizhenshichangashibushibuhefaa",111);//名称过长,请少于20个字符p3.show();//姓名:null,年龄:111}}
运行图:





九、设计模式--单例设计模式

什么是设计模式:

设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的;设计模式使代码编制真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
多个设计模式综合使用,就形成了框架。


单例设计模式:

目的:保证类在内存中只存在一个对象

作用:例如配置文件类的对象,我们知道不管哪个部分在使用或者修改该配置文件,本质上都应该修改的是同一个配置文件,因此可以应用单例设计模式。

实现单例设计模式的三步骤:

1.构造函数私有化(使得外界不能通过类创建对象)。

2.类中定义一个私有静态本类对象成员(作为那个唯一的一个本类对象存在,私有是保护它不能被外界直接访问,静态是为了被静态方法访问)。

3.定义一个公有静态方法返回唯一对象(公有是为了外界都能使用,静态是为了类名调用)。

要实现单例设计模式有两种方式:懒汉式,饿汉式。

饿汉式:

代码:
class SingleClass{private String name;private static SingleClass sc=new SingleClass();private SingleClass(){name="helong";}public static SingleClass getInstance(){return sc;}public void setName(String str){name=str;}public void show(){System.out.println(name);}}class SingleClassTest{public static void main(String[] args){SingleClass s1=SingleClass.getInstance();s1.show();s1.setName("123123");SingleClass s2=SingleClass.getInstance();s2.show();}}

运行图:


饿汉式:

代码:
class SingleClass{private String name;private static SingleClass sc=null;private SingleClass(){name="helong";}public static SingleClass getInstance(){if(sc==null)sc=new SingleClass();return sc;}public void setName(String str){name=str;}public void show(){System.out.println(name);}}class SingleClassTest{public static void main(String[] args){SingleClass s1=SingleClass.getInstance();s1.show();s1.setName("123123");SingleClass s2=SingleClass.getInstance();s2.show();}}

运行图:


我们发现,貌似两种方式并没有什么明显的区别,运行结果也一样,代码中不一样的地方也不多,无非就是饿汉式是类一进入内存,sc就初始化了,而懒汉式则是在外界调用方法getInstance()时才给sc赋值。但是其实懒汉式存在着安全问题
假如此时有两个线程在获取SingleClass的对象,我们知道所谓的多线程不过是CPU在快速切换而已,也就是说同一时间只有一个线程在运行(对于单核CPU来说),我们想象一种情况,当线程1运行到代码
public static SingleClass getInstance(){if(sc==null)//Asc=new SingleClass();//Breturn sc;}
中的A处是判断成功了,因此此时还没有初始化sc对象,但是线程1失去了执行权,由线程2来执行,线程2也执行到A处时,依然判断成功,因为还是没有对sc进行初始化,因此在线程2中,为sc赋值了一个对象,并返回了。此时线程2失去了执行权,由线程1继续从B处执行,也就是又创建了一个对象并返回,因此不符合我们单例设计模式的初衷,内存中出现了多个对象
出现这个问题的原因是什么呢?
我们发现当线程1在操作sc时操作了一半却被迫中止了,此时线程2执行,线程2又操作sc,这就造成了访问冲突
解决思路:
我们需要让线程1在操作sc时,其他线程不能操作sc,这就可以了。
解决办法:同步函数同步代码块
同步函数代码:
public static synchronized SingleClass getInstance(){if(sc==null)sc=new SingleClass();return sc;}
使用synchronized来标示SingleClass方法,使得该方法在被一个线程操作时,其他线程不能操作,但是我们发现这种做法的效率极低,因为我们其实只是需要在sc为null时,进行措施而已,而不是每时每刻都进行这种同步处理,因此我们一般使用下面这种效率更高的方式:
同步代码块代码:
public staticSingleClass getInstance(){if(sc==null)synchronized(SingleClass.class){if(sc=null)sc=new SingleClass();}return sc;}
可以看出这种方式的效率高于上一种。

开发中一般使用饿汉式,因为相对来讲,效率方面与懒汉式没什么区别,但是饿汉式的代码更简洁。




------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------


0 0
原创粉丝点击