Java之类与对象(一)

来源:互联网 发布:mac系统怎么装steam 编辑:程序博客网 时间:2024/05/16 14:55

对于像Java这样的纯面向对象语言,类是最基本的抽象单位,一直以来我总是希望自己能写出一篇文章,来好好的梳理下自己对类和对象的理解。今天,这个愿望似乎要实现了!不知从哪个地方写起,因为这方面设计的东西太多了,说到类,就不难想到继承、多态、封装,就不难想到作用域及生命周期,很多的东西一下子涌上心头,让我不知道该从哪儿下手。本章系Java之美[从菜鸟到高手演变]系列之类与对象,希望通过我的分析,能让读者朋友们更加牢固记住相关的知识点,掌握类与对象方面的精髓!

阅读过程中有任何问题,请联系egg:

邮箱:xtfggef@gmail.com   微博:http://weibo.com/xtfggef

如有转载,请说明出处:http://blog.csdn.net/zhangerqing

一、类的创建及初始化

类通常是一类事物的抽象,如人就是一个类,你、我、他是这个类的具体实例,也就是对象。在Java中我们可以通过形如:class A {}来创建一个类,我们说过Java是面向对象的语言,每个对象都应该拥有它自己的属性和方法,就拿人来说,肤色、身高等是人的属性,吃、喝、玩等都是方法,也就是说属性描绘了类的特点,而方法描述了类的功能,体现在Java的类中就像下面的代码这样:

[java] view plaincopy
  1. public class Person {  
  2.       
  3.     String name;  
  4.     int age;  
  5.       
  6.     void eat(){  
  7.           
  8.     }  
  9. }  
在面向对象的思想中,一切物体皆对象,我们以对象为单位进行编程,将这个对象所有的属性方法包装在一起,就是封装。一般情况,我们通过类的构造器来创建类对象,构造器是一个拥有和类名同样的名字的方法,我们可以对它传递参数,进行一些初始化工作,如,当我们需要在创建对象的时候,初始化其姓名及年龄,我们可以这样来做:

[java] view plaincopy
  1. public class Person {  
  2.       
  3.     String name;  
  4.     int age;  
  5.       
  6.     public Person(String name,int age){  
  7.         this.name = name;  
  8.         this.age = age;  
  9.     }  
  10.     void eat(){  
  11.           
  12.     }  
  13. }  
测试类中:

[java] view plaincopy
  1. public class ClassTest {  
  2.   
  3.     public static void main(String[] args) {  
  4.         Person person = new Person("egg",23);  
  5.     }  
  6. }  

new 操作符会为我们在内存中开辟空间,person是对象名,也是引用,在栈上分配,指向由new在堆上分配的内容,具体的JVM内存管理,请看我的另一篇博文:JVM内存管理与垃圾回收,里面有详细的介绍,此处非重点,不去深讲。我们再来分析一下这个过程:当调用new Person()时,编译器首先检查下原类Person中是否有Person()构造方法,此处因为有public Person(String name,int age),所以new的时候,直接调用的该方法,但是很多时候,我们并没有显示声明构造方法,此时,编译器在调用的new Person()的时候,会自动为我们的Person类添加一个无参的空Person()构造方法:Person(){},来执行类的构造过程。说到构造方法,我们来看看下面的这段代码:

[java] view plaincopy
  1. public class Person {  
  2.   
  3.     public Person(int id) {  
  4.         System.out.println("person(" + id + ")");  
  5.     }  
  6.   
  7.     public static void main(String[] args) {  
  8.         Build b = new Build();  
  9.     }  
  10. }  
  11.   
  12. class Build {  
  13.     Person p1 = new Person(1);  
  14.   
  15.     public Build() {  
  16.         System.out.println("this is build's block!");  
  17.         Person p2 = new Person(2);  
  18.     }  
  19.   
  20.     Person p3 = new Person(3);  
  21.   
  22. }  
此处我主要想说明,用构造器创建类和变量的初始化顺序,该程序输出:

person(1)
person(3)
this is build's block!
person(2)

说明:不论变量放在哪儿,都会先于任意一个方法的执行前执行,包括构造方法,而构造方法是一个类必须会执行的方法,不需要显示的进行调用。同时,不论变量在哪儿分布,只要在方法外部,就一定先于方法初始化。说到这儿,我们不得不谈一下另一个关键的知识点静态块和非静态块。二者都有很简单的声明方式,只需一对大括号{}就行,静态块的话,在前面加static关键字,我们写个小程序来看看:

[java] view plaincopy
  1. public class Person {  
  2.   
  3.     /*静态块*/  
  4.     static{  
  5.         System.out.println("this is static block!");  
  6.     }  
  7.     /*非静态块*/  
  8.     {  
  9.         System.out.println("this is non-static block!");  
  10.     }  
  11.     public Person(int id) {  
  12.         System.out.println("person(" + id + ")");  
  13.     }  
  14.     public static void main(String[] args) {  
  15.         Person p1 = new Person(1);  
  16.         Person p2 = new Person(2);  
  17.     }  
  18. }  
该程序输出:

this is static block!
this is non-static block!
person(1)
this is non-static block!
person(2)

说明什么问题?观察一下:我们new了两个对象,可是静态块只执行了一次,而非静态块执行了两个,且都是在调用构造器之前。我们似乎得出了一些结论:静态块是在类的装载时执行的(装入.class文件后),且只执行一次。而非静态块是在调用构造方法之前执行的,每生成一个实例对象,就会调用一次非静态块。此处,我想引入一个很重要的知识点:static关键字。一般来说,被声明为static的变量或者方法,或者前面说的块,都属于类变量、类方法,属于类的属性信息(在方法区分配内存)。如静态块一样,其它的静态数据也具有这个特点:初始化只在类装载的时候执行一次。对于类变量和类方法,还有一个重要的特点就是,外部对象对他们的引用可以直接通过类名来调用,如下面的代码:

[java] view plaincopy
  1. public class Person {  
  2.     static int id = 10;  
  3. }  
  4.   
  5. class Test{  
  6.     public static void main(String[] args) {  
  7.         System.out.println(Person.id);  
  8.     }  
  9. }  
除了使用new操作符,我们还有一些其它方法来创建类,如Java的反射机制,我们会有专门的博文来介绍相关的知识点。

我们总结下对象的创建过程:(含顺序)根据下面这个程序的输出:

[java] view plaincopy
  1. public class Person {  
  2.       
  3.     public Person(int id) {  
  4.         System.out.println("person(" + id + ")");  
  5.     }  
  6. }  
  7.   
  8. class Build {  
  9.       
  10.     /*静态块*/  
  11.     static{  
  12.         System.out.println("this is static block!");  
  13.     }  
  14.     /*非静态块*/  
  15.     {  
  16.         System.out.println("this is non-static block!");  
  17.     }  
  18.     Person p1 = new Person(1);//------------1-----------  
  19.   
  20.     public Build() {  
  21.         System.out.println("this is build's block!");  
  22.         Person p2 = new Person(2);  
  23.     }  
  24.   
  25.     Person p3 = new Person(3);  
  26.   
  27.     public static void main(String[] args) {  
  28.         Build b = new Build();  
  29.     }  
  30. }  
this is static block!
this is non-static block!
person(1)
person(3)
this is build's block!
person(2)

1、先装载.class文件,创建Class对象,对静态数据(由static声明的)进行初始化,而且只进行一次初始化。

2、new Build()在堆上进行空间分配。

3、执行非静态块。

4、执行所有方法外定义的变量的初始化。

5、执行构造器。

我们再来看个例子:将static放在上述例子的注释1行之前,输出会有变化:

this is static block!
person(1)
this is non-static block!
person(3)
this is build's block!
person(2)

正好验证了我们上面的结论,总体来说执行顺序为:静态块,静态属性->非静态块,属性->构造器。接下来我们分析一下类的属性和方法。

补充:此处经网友a52360509指正,对于静态块和静态属性或者非静态块和属性,初始化顺序决定于它们在代码中的顺序。

属性:

类中的属性一般分为类属性(全局变量)、实例属性(全局变量)、局部属性(局部变量)。<我是这么分的,尽管有人不这么分,但是分法无所谓,理解它们的含义最重要>.

类属性:前面已经说过就是那些声明为static的属性,在整个过程中只进行一次初始化,在内存中只开辟一个空间,不论在哪儿调用,值保持一致。一旦被修改,所有引用它的地方都会跟着修改。一般直接通过类名进行调用。

实例属性:实例变量是可以不进行初始化,比如一个整型的实例变量假如没有初始化,则默认值为0;而局部变量假如不赋初值语法上是通过的,但是在使用这个变量是程序就报错了。实例变量在堆和栈中都分配内存空间,在堆当中分配的是对象本身,而栈中则是对这个对象的引用。

局部属性:局部变量是在方法内部声明的变量,生命期仅在方法内,方法结束后变量就消失了;局部变量必须初始化再使用,否则会报错,也就是说,假如你在方法内定义了一个局部变量,并且没有赋值,那么你在使用这个变量的时候一定得赋值,不然就报错了。同时,局部变量可屏蔽全局变量。

方法:

方法就是类的行为,形如:

[java] view plaincopy
  1. public void say(){  
  2.         dosomething;  
  3.         ...  
  4.     }  

由方法头和方法体构成,方法头包括:访问控制符(如public private等)、返回类型(返回类型决定该方法在调用后产生的结果的类型)、方法名、参数列表组成。声明为void的方法,返回值为空。在特殊的情况下,我们可以为方法添加一些特殊的关键字以实现特殊的功能,如synchronized、final、static、abstract等等。方法方面我只想介绍两个重要的方面:重载(Overload)和重写(Override亦叫覆写)。

重载:

是指在同一个类中,具有相同的方法名,不同的参数列表的方法之间的一种机制。参数列表的不同体现在:类型不同、个数不同、顺序不同,只要满足任一一个,就可以进行方法重载。

[java] view plaincopy
  1. public class AreaCal {  
  2.   
  3.     /* 计算长方形的面积 */  
  4.     public int area(int width, int height) {  
  5.         return width * height;  
  6.     }  
  7.   
  8.     /* 计算正方形的面积 */  
  9.     public int area(int edge) {  
  10.         return edge * edge;  
  11.     }  
  12.   
  13.     /* 计算圆的面积 */  
  14.     public double area(float radius) {  
  15.         return 3.14 * radius * radius;  
  16.     }  
  17. }  

如例所示,同一个方法名area,同步传入不同的参数,实现不同的功能,这就是方法重载,用这样的机制有什么好处?个人感觉就是在模型上的一种统一,同样的功能,调用同样的方法,只需传入不同的参数,增强了程序的可读性和易于维护,当有很多个功能相似的方法的时候,如果我们为每个方法设计一个名称,想通过名称来区分它们的话,会很糟糕,而且会让人觉得程序的可读性差,设计不够巧妙!此处问题来了:我们可不可通过方法的返回值来区别方法重载?让我们看个例子:

[java] view plaincopy
  1. public int area(int width, int height) {  
  2.     return width * height;  
  3. }  
  4.   
  5. public float area(int width, int height){  
  6.     return width * height;    
  7. }  

当其他环境调用这两个方法时,并不会先得到他们的返回值,返回值只有在方法执行完毕才返回,在调用之初,编译器无法判断他们的区别,所以只能当同名方法来处理:报错!所以试图通过返回值来进行方法重载是不正确的!

重写:

重写是在继承中存在的,在两个类(子类和父类之间存在的关系)中,子类重写父类的方法,方法名相同,参数也相同的一种机制。

[java] view plaincopy
  1. public class B extends A {  
  2.       
  3.     public String a(String name) {  
  4.         return "welcome to you :" + name;  
  5.     }  
  6. }  
  7. class A {  
  8.     public String a(String name){  
  9.         return "hello:"+name;  
  10.     }  
  11. }  

当子类继承了父类后,想对父类的方法功能进行扩展,就可以使用重写,这样做的目的就是:当你需要扩展新的功能的时候,不需要新建方法,在父类的方法中进行补充,这样一种面向对象思维的体现,不用重写同样可以达到这样的效果,但是用了重写更加符合OO思想。类似的还有一种概念,叫隐藏:当子类和父类方法名相同,参数不同时,子类隐藏父类的方法实现,这就是一种机制,一种叫法,没什么实际意义,相当于我们新建了方法,只是方法名和父类的相同,但是不是父类的方法的实现。
Java中不定参数调用

有的时候,我们不能确定方法的参数个数,我们可以采取这种机制(String ... value),如下面的代码:

[java] view plaincopy
  1. public class B{  
  2.       
  3.     public static String a(String ... value) {  
  4.         return "welcome to you :" + value;  
  5.     }  
  6.       
  7.     public static void main(String[] args) {  
  8.         System.out.println(a("egg"));  
  9.     }  
  10. }  

打印出来的结果是:welcome to you :[Ljava.lang.String;@61de33。后面这部分信息一看就是数组的信息,所以我们继续研究,发现:当你将参数写出String ... value的形式的时候,已经默认相当于:String[] value 因此你需要在方法内部获取的数据的时候写成数组下标的形式:

[java] view plaincopy
  1. public static String a(String ... value) {  
  2.         return "welcome to you :" + value[0];  
  3.     }  

当有多个参数时,同样用下标来取值:

[java] view plaincopy
  1. public class B {  
  2.   
  3.     public static String a(String... value) {  
  4.         return "welcome to you :" + value[0] + value[1] + value[2];  
  5.     }  
  6.   
  7.     public static void main(String[] args) {  
  8.         System.out.println(a("egg"":niu"":baby!"));  
  9.     }  
  10. }  

这种机制在有些开发中特别有用,降低代码量,使程序更加简洁,不过有时会牺牲一点可读性。

二、类与对象的关系

经过上面这么多的演练,我们总结下类和对象的关系:看看下面的代码:

[java] view plaincopy
  1. public class B {  
  2.   
  3.     public static void main(String[] args) {  
  4.         B b = new B();  
  5.         A a = new A();  
  6.     }  
  7. }  
  8.   
  9. class A {  
  10.   
  11. }  

这段代码中,A和B都是类,a和b属于对象,他们之间是种什么关系呢?

1、类是一类具有相同属性的事物的统称,是一种抽象。

2、对象是类的具体体现,又称实例。

3、类是一种静态的概念,而对象是一种动态的机制。

每个人都有不同的看法,还有什么关系,希望大家补充,笔者真心希望各位读者能认真思考,积极提出宝贵的建议!因为有不少读者提出之前的博文篇幅过长,希望我尽量写的短一点儿,方便阅读,所以我将其它内容放在了下一篇,希望朋友们能够继续阅读!

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 苹果手机下载有云朵下载不了怎么办 手机下载登录忘了密码了怎么办 软软件被手机加密忘了密码怎么办 苹果手机想下载东西忘了密码怎么办 已经不念书几年了突然想上学怎么办 江湖风云录把王老爷子杀了怎么办 练扫踢胫骨旁边的肌肉受伤了怎么办 四个月宝宝没抱住摔了头部怎么办 老公老是跟年轻的小姑娘聊天怎么办 老婆出轨老公想离婚又舍不得怎么办 孕妇打完无痛分娩针就想睡觉怎么办 熟食店开空调菜品吹的很干怎么办 不锈钢锅在液化气烧了发黄怎么办 在小镇门面卤菜店不好卖怎么办? 被辣椒辣到嘴唇了该怎么办 沁园净水机不制水指示灯不亮怎么办 太辣了辣得胃疼怎么办 出现连接问题或mmi码无效怎么办 存折丢了怎么办卡号也不记得了 车内皮子被烂苹果腐蚀有印怎么办 锅被腐蚀后变黑色应该怎么办 后厨炉灶里的炉芯进水了怎么办 小儿九个月老是流黄鼻子该怎么办 肉炖的老了不烂怎么办 吃了凉东西现在一直打嗝应该怎么办 喝了很多水还是觉得口渴怎么办 刚买的猪肝没洗直接炒了怎么办 四个多月的宝宝吃了脏东西怎么办 狗吃了脏东西拉稀呕吐怎么办 五个月宝宝怕吃药导致奶不喝怎么办 蒸锅锅盖吸住了怎么办锅比锅盖要大 豇豆没熟孕妇吃了中毒怎么办 孩子积食拉不出粑粑憋的直哭怎么办 2岁宝宝总是半夜拉粑粑怎么办 金毛拉很臭的稀粑粑怎么办 点餐系统登录后没有菜单怎么办? 环亚在线微交易亏了钱怎么办 钢管舞报了教练班觉得学不会怎么办 微信上聊天被外国人给骗了该怎么办 微信冒充朋友骗走我钱怎么办 凉皮调料水鸡精味精放多了怎么办