Java基础 第二天
最近一直为找工作头疼,虽然说,我还有一年可以准备的,但依然压力很大。但压力就是动力,每到这个时候,我就会很努力.还是要声明一下,这个系列的文章,只是我复习的一个笔记,参考的是孙鑫老师的课程。今天大概的介绍一下面向对象编程吧。
一、面向对象编程概述
面向对象,英文呢,是OOP,也就是Object Oriented Programming。
用一句话来说
面向对象编程描述的一系列对象,以及对象之间的相互作用。而大家一开始接触的面向过程编程,描述的一些列函数,以及函数之间的相互作用。(这是我的理解)
面向对象编程与面向过程编程的区别
下面来具体看看有什么区别吧:
- 前面已经提到,面向过程的是函数的集合,而面向对象是对象的集合,所以这算一个区别;
- 有过面向过程编程经验的同学(还是以我为例吧,菜鸟的想法大家总是喜闻乐见的),肯定会遇到这样的情况,每当我们拿到一个问题,总是先去想解决这个问题的算法,再去确定数据结构是什么,比如拿到一个简单的题目,要计算一个长方形的面积,那么我就先想着去构造一个计算面积的函数,然后再去想想用什么数据结构来解决,这是面向过程编程的典型思路;而真正面向对象编程,我们应该先确定数据结构,再确定运算的过程,也即对同样的问题,我们首先应该想到去构造一个长方形的类,这个类(长方形)有哪些属性(长,宽等),有哪些方法(输出面积,输出长宽等),待这些都想明白了,再去考虑如何实现这些方法。这个区别也明显的吧。
- 补充上面一段,对于面向过程编程,我们一般会构造一个数据结构来存储数据,然后另外定义函数(方法)来操作数据结构中的数据;而面向对象编程,通常我们看到的是一个个类,类是什么?是数据+对数据操作的方法的封装,所以对于面向过程编程,我们通常要构造一个对象模型(也就是类),这个类就将数据和方法组织在一起了。
二、类和对象
2.1 什么叫做类
在现实生活中,人就是对象,人类就是一个类,人与人在个体上是不同的,但是总的来说,是相似的,所以我们把相似的对象划归成一个类。
在软件中,类就是一个模板,它定义了通用于一个特定种类的所有对象的状态以及行为。
说白了,类就是一个抽象模板,它是对一些列本质上相同的对象的描述,类中应该定义对象的状态信息(变量信息)和行为(方法)。以人为例,人的状态有哪些?是否睡觉,是否吃饭,身高信息,体重信息,年龄信息等,相关的方法就更多了。
2.2 什么叫做对象
对象是类的实例,这个对于初学者来说还是有点困惑吧,他们会问,什么是实例?
我想,实例就是利用这个类创建的一个东西。比如有一个类叫Person,那么,利用这句话,Person Mars = new Person();就创建了一个人的实例Mars,那么Mars就是对象啦。
三、面向对象程序设计
从程序设计的角度来看,在面向对象程序中,类是最基本的单元了(其实也不对,类中还有很多内容,但是从宏观来看的确是这样)。类实质上还是一种数据类型,只是相比较基本数据类型(如int),它比较特殊罢了,它是我们自己定义的数据类型。只要你是数据类型,那么我们就可以使用类名来声明一个变量(也可以说对象)。
在Java中,声明了一个变量,还不可以使用,必须再使用new运算符,去创建一个对象实体后,才可以使用这个对象。这是因为,声明只是在栈内存中有了一个名字,但是这个名字还没有对应的空间,需要使用new,在堆内存中申请一个空间,然后栈中的变量和堆中的空间对应上之后,这个对象才可以被使用。
3.1 类的构造方法
当我们用类构造一个对象的时候,我们一般会给这个对象初始化一系列属性,而构造函数就是用来初始化这些属性的。构造函数的名字很特别,与类名是相同的,并且没有返回值。
我们不能直接调用构造方法,必须通过new关键字来自动调用,从而创建类的实例。Java的类都要求有构造方法,如果没有定义构造方法,Java编译器会为我们提供一个缺省的构造方法,也就是不带参数的构造方法。
class Student{ int num;//学号 String name;//姓名 /** *构造方法,初始化对象的属性 */ Student(){ num = 0; name=null; } public static void main(String [] args){ Student st = new Student();//ok } }
其实我们也会发现,就算没有定义Student(),程序依然正确,这就是因为编译器提供了一个缺省的构造方法,系统用默认值初始化对象的成员变量。
我认为,我们有必要了解各种数据类型的默认值:
数值型 <---> 0
boolean <---> false
char <---> '\0’
对象 <---> null
所以如果没有定义Student(),那么我们的变量还是会变为num=0,name=null。
还有一个内容要补充一下,就是new关键字:
上面我们已经使用了new,那么使用了它之后,会发生的事情我们也就知道啦:
为新生成的对象在堆内存中开辟一个空间,引起对象当中相应构造方法的调用,并且为对象返回一个引用,这样声明的对象就和开辟的空间联系起来了。
3.2方法的重载
重载的英文是overload,之所以要强调一下这个,是因为要区分方法重载和覆盖(override)的区别,覆盖在以后会讲到(3.5.2),现在我们先来讲一下重载。其实有了C++学习经验的同学肯定已经明白了。
重载构成的条件:方法的名称相同,但参数类型或参数个数不同,才能构成方法的重载。
什么时候需要用重载呢?举一个例子,不一定恰当,比如有一个方法来处理两个数相加的问题,这两个数有可能是int型的,那么我可以设置这个方法为void add(int a,int b),同样还有可能是两个double类型的数相加,为了让语义连贯,我们还应该使用add来处理,所以这个时候可以用到重载,void add(double a,double b)。
其实构造函数也可以重载,有时候构造函数我们需要传递一个参数进来,这就是重载啦。
举个例子:
class Cal{ int a; int b; Cal(){ a = 0; b = 0; } //带参数的构造函数 Cal(int a,int b){ this.a = a; this.b = b; } int add(){ return this.a+this.b; } //add的重载 int add(int a,int b){ return a+b; } public static void main(String []args){ Cal t = new Cal(); System.out.println(t.add());//0 System.out.println(t.add(1,1));//1 } }
但是,要注意,如果参数相同,而返回类型不同,这样可以吗?答案是显然的,不可以!所以一定要注意,一定是参数类型或者参数个数不相同。
3.3 特殊变量this
上一节中,我们看到一个Cal的构造方法中用到了this,这也是一个挺有意思的东西,下面就简单介绍一下:
this顾名思义,代表对象本身,记住代表的是一个对象的自身。
当类中有两个同名变量,一个属于类(比如Cal中的成员变量a),而另一个属于某个特定的方法(如构造方法中的局部变量a),可以使用this区分成员变量和局部变量。
在Cal(int a,int b)的函数体(作用域就在函数体内),其实已经屏蔽了外面的成员变量a和b。也就是说如果在函数体内这样写,a=a,b=b,那么就是自己给自己赋值,这显然不是我们想要的,于是就要通过this来帮助,this.a就是告诉编译器,这个a是属于我类的,而在这个函数体内(注意只是在这个函数体内),不加this的a是属于这个函数的。
下面我们来想一个问题,一个类的所有对象(类的实例化)在内存中是怎么样的呢?
这些对象调用的成员方法再内存中只有一份拷贝,尽管在内存中有可能有多个对象,但是方法再内存中只有一个拷贝,数据成员则好一些,在类的每一个对象所在的内存空间中都存在着一份拷贝。this变量允许相同的实例方法为不同的对象工作,举个例子来说,无论Cal有多少个对象,在内存中,方法add()只有一份拷贝,那么每当我们调用一个实例方法,比如Cal的对象t调用add()时,this变量便被设置成当前使用的对象,方法的代码(也就是add())将与this所代表的对象的数据进行关联。
3.4 static和final
3.4.1 static
static顾名思义(额..我老喜欢这样了)是静态的意思,啥是静态呢?也就是说不收外界动的影响,它是不依赖该类的特定实例的,被所有实例共享。当然了,这只是粗的理解,下面我来具体看看。
我们在哪些地方会看到或者说用到它。我们在看代码的或者写代码的时候经常会在一些变量或者一些方法(函数)的声明前面看到这个关键字,不如static int 后面跟上一个什么东西。
当static放在一个变量前面的话,那么这个变量就成为静态变量(如果前面木有static那么就叫做实例变量),如果它放在一个方法前面,那么这个方法就是静态方法(对应的有实例方法)。
当一个类被加载了,那么相关的静态变量与方法就会被加载的内存的一个固定位置,所以调用这些东西根本不需要实例化类的对象。
我们要注意一点,在静态方法中,也就是在用static声明的方法中,不能调用非静态的方法,也不能引用非静态的成员变量。反之,则可以。大家要想一想这是为什么?
本大菜鸟猜大家肯定懒得想了,看了这么长的文章早该昏昏欲睡了吧,所以就直接说吧。
上面提到,调用静态方法或者静态变量的时候,根本不需要实例化对象,只要类被加载进来就行了。所以,如果在静态方法里调用了非静态的内容,那么这些内容还没被分配空间,根本就无法使用啊,所以这是不可以的。
那么,能不能用类实例化的对象去调用静态方法呢?显然是可以的。。
由于静态变量是属于某一个类的(所有对象共享),因此它的改变在程序的生命周期内是持久的,我们可以利用这个来计数。(注意:只能在非分布式程序的情况下,如果在分布式的情况下应该想想别的办法了)
对了,还有一个重要的内容:
**如果一个类文件中有main,并且在这个文件中定义了一个内部类,那么一定要记得把这个类声明成static的,才能在main中使用。
3.4.2 final
final是用来定义常量的,例如:final double PI = 3.1415;
之所以PI要大小,是因为我们通常约定在定义常量的时候,采用大写的形式。
下面,有一个很重要的问题,常量在什么时候赋值呢?
final常量可以在声明的同时赋初值,也可以在构造函数中赋初值(注意:是初值,也就是之前并没被赋值)。
当一个变量被定义为常量,那么这个变量的值以后都不会被改变,也就是说,无论定义了多少个对象,这个对象的成员变量PI都为3.1415不会为其他的,这个时候我们想,成员变量在类的每一个对象所在的内存空间中都存在着一份拷贝,而这个常量的值是不会被改变,所以我们浪费了内存,那么有什么办法节省内存呢?答案就是利用static。但是要注意,在用static声明之后,就要立即初始化了。
为了节省内存,我们通常将常量声明为静态的。
3.5 类的继承
3.5.1 类的继承概念
在Java中,通过关键字extends继承一个已有的类,被继承的类称为父类(超类,基类),新的类称为子类(派生类)。要注意,在Java中,不允许多继承。
首先来解释一下为什么要继承?继承为我们的开发提供了很大的便利,通过继承,我们可以很方便的获得父类的方法。对了,刚刚提到在Java中不允许多继承,那么难道就没有办法了吗?错,其实我们还可以利用接口,以后我们会介绍。
嗯,前面提到,通过继承,我们可以使用父类的方法,有些时候,父类的方法不能满足我们,比如说,父类是一个图形,子类是一个三角形,那么三角形的画法肯定跟普通图形的画法不一样,所以我们要重新修改一下这个方法。
我们不需要专门去修改父类的方法,因为这样会影响到别的子类的使用(比如圆,矩形等),这里就要介绍一个新知识了,方法的覆盖(override),注意与3.2节做一个比较。
3.5.2 方法的覆盖
在子类中定义一个与父类同名、返回类型、参数类型均相同一个方法,称为方法的覆盖。注意:覆盖发生在子类与父类之间。
举个例子:
class Animal{ void run{ System.out.println("animal run"); } } class Fish extends Animal{ void run{ System.out.println("fish swim); } }
3.5.3 super
super提供了对父类的访问,可以使用super访问父类被子类隐藏的变量或者覆盖的方法。
每个子类构造方法的第一条语句,都是隐含地调用super(),如果父类没有这种形式的构造函数,那么在编译的时候就会报错。但是如果指定了super(参数);就不会再调用super()了。
还要注意一点,super(),注意是加括号的,只能放在构造函数中,无论括号里面有木有参数,都只能放在构造函数中,并且只能放在第一句,这很重要。为什么要放在第一句?这是因为默认第一句就是一个super()虽然是隐藏的。如果你不写在第一句,那么就已经调用了父类的构造函数了,你后面又来一次,就会出现混乱。
另外,在子类覆盖了父类的方法中,不会自动调用父类方法中的内容,如果想要使用父类方法中的内容,需要这样:super.相应的方法,来使用父类中的代码。
这里正好可以解释一个现象,就是子类被实例化的时候,总是先调用父类的不带参数的构造函数。
class Test{ Test(){ System.out.println("父类的构造函数:不带参数"); } Test(String str){ System.out.println("父类的构造函数:带参数="+str); } void print(){ System.out.println("父类的输出方法"); } public static void main(String []args){ Test1 t = new Test1("s"); /* * 输出为: * 父类的构造函数:带参数=s * 子类的构造函数:带参数=s */ t.print(); /* * 子类的输出方法 * 父类的输出方法 */ ///////////////////// /* Test1 t1 = new Test1(); /* * 输出为: * 父类的构造函数:不带参数 * 子类的构造函数:不带参数 */ } } class Test1 extends Test{ Test1(){ System.out.println("子类的构造函数:不带参数"); } Test1(String str){ super(str);//必须放在第一句,不然会报错,如果没有这句话,则输出的是,父类的构造函数:不带参数 System.out.println("子类的构造函数:带参数="+str); } void print(){ System.out.println("子类的输出方法"); super.print();//此时可不放在第一句了 } }
3.5.3 多态性
多态性正是面向对象编程灵活的地方。利用多态性,我们可以通过覆盖父类的方法来实现,在运行时根据传递的对象引用,来调用相应的方法。
举个例子:
class Test{ void print(){ System.out.println("Test"); } public static void main(String []args){ Test t = new Test(); t.print(); Test1 t1 = new Test1(); Test2 t2 = new Test2(); t = t1; t.print(); t = t2; t.print(); if(t instanceof Test2){ System.out.println("t当前是Test2的实例"); } if(t instanceof Test){ System.out.println("t当前是Test的实例"); } if(t1 instanceof Test){ System.out.println("t1当前是Test的实例"); } } } class Test1 extends Test{ void print(){ System.out.println("Test1"); } } class Test2 extends Test{ void print(){ System.out.println("Test2"); } } /* * 输出: * Test * Test1 * Test2 * t当前是Test2的实例 * t当前是Test的实例 * t1当前是Test的实例 */
其中有一个instanceof,这是一个非常非常有用的东西啊,通过它可以判断当前对象是哪一个类的实例,一定要会用它,我曾帮师兄做过一个编辑器,利用GEF做的,经常需要判断当前的模型属于哪个具体的类,就要用到instanceof,实在是太方便了。
我们还可以看到,子类一定是父类的实例哦。
四、小结
今天主要介绍了面向对象的基本知识,真的是非常的基本了,不然一篇文章根本说不完。
下面是…吐槽时间
最近被老板安排做一些非常繁琐且对长姿势没用的工作,心里很是郁闷啊,不过看老板也忙的不得了,能分担点就分担点吧。写这个博客其实也蛮浪费的时间的,但是可以让我静下心来,还是挺好的。不过,现在写这些东西,估计已经没人看了吧,如果有同学再看,希望不要误导你,这个只是我自己笔记,其实也算不上笔记,都是自己回想当时看的内容写的,加上自己的一些体会。
要期末考试了,需要攒人品。OMG,下个星期还要汇报,好烦啊。。
OK,今天到这里吧,过段时间会写一些关于GEF的东西,下次见咯~
(文章地址:http://www.cnblogs.com/njuduyu/archive/2013/05/24/3097531.html)

浙公网安备 33010602011771号