面向对象1

一、面向对象的几大特征

计算机软件系统是现实生活中的业务在计算机中的映射,而现实生活中的业务其实就是一个个对象协作的过程。面向对象编程就是按现实业务一样的方式将程序代码按一个个对象进行组织和编写,让计算机系统能够识别和理解用对象方式组织和编写的程序代码,这样就可以把现实生活中的业务对象映射到计算机系统中。

面向对象(Object-Oriented,简称OO)就是一种常见的程序结构设计方法与思想。面向对象思想的基础是将相关的数据和方法放在一起,组合成一种新的复合数据类型,也就是我们常说的类,然后使用新创建的复合数据类型作为项目的基础,实际上面向对象就是将现实生活中的实际存在的对象根据要处理或解决问题域按照需要抽象成一个个的具有特定成员和方法的类,对象所具有的状态用类的成员表示,对象的行为用方法表示,然后就是按照现实业务方式去生成和组织用于解决问题的对象并使用生成的对象对外提供的功能去解决现实业务问题,总之,面向对象的思想就是一个创建对象然后使用对象的过程,但如何创建对象和使用对象确实一门博大精深的学问,即使是面对同一个问题,不同的设计者会有不同的想法,所设计的类体结构也会有不同,从而对于问题解决的难易程度也不同,所以类设计的合理性相当重要。

面向对象的思想不仅提高了代码的高度复用性,更重要的是它使我们要解决的问题变得更简单了,与传统的面向过程的设计思想相比,它让我们由解决问题的执行者变成了解决问题的指挥者,虽然面向过程与面向对象都是一种解决问题的思想,但面向过程强调的是功能行为(也就是具体功能的实现---这是执行者才需要关心的事),即是一种解决问题的具体过程,先干啥,再干啥;面向对象则将功能封装到对象里,强调的是具备某功能的对象(这才是指挥者需要做的事,管理和使用好这些对象即可)

面向对象的编程语言有封装、继承、抽象、多态等4个主要的特征。

1、封装:

封装是保证软件部件具有优良的模块性的基础,封装的目标就是要实现软件部件的“高内聚、低耦合”,防止程序相互依赖性而带来的变动影响。在面向对象的编程语言中,对象是封装的最基本单位,面向对象的封装比传统语言的封装更为清晰、更为有力。面向对象的封装就是把描述一个对象的属性和行为的代码封装在一个“模块”中,也就是一个类中,属性用变量定义,行为用方法进行定义,方法可以直接访问同一个对象中的属性。通常情况下,只要记住让变量和访问这个变量的方法放在一起,将一个类中的成员变量全部定义成私有的,只有这个类自己的方法才可以访问到这些成员变量,这就基本上实现对象的封装,就很容易找出要分配到这个类上的方法了,就基本上算是会面向对象的编程了。

封装的两个含义:

(1)、把对象的状态和行为看成一个统一的整体,将二者存放在一个独立的模块中()

(2)、“信息隐藏”把不需要让外界知道的信息隐藏起来,尽可能隐藏对象功能实现细节,字段; 

封装机制在程序中的体现是:把描述对象的状态用字段表示,描述对象的行为用方法表示,把字段和方法定义在一个类中,并保证外界不能任意更改其内部的状态(字段值),也不允许任意调动其内部的功能方法,但可以对外提供一些公共访问接口方法。

封装:是指隐藏对象的属性和实现细节,仅对外提供公共的访问方式。

好处:

          将变化隔离。

          便于使用。

          提高重用性。

          提高安全性。

封装原则:

          (1)、将不需要对外提供的信息内容隐藏起来。

          (2)、对于隐藏的属性,可以提供公共方法对其访问,同时其本身也可以对外提供一些公用的功能方法。

eg:
class Person{
	private String name;
	private int age;
	private int sal;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
}


把握一个原则:把对同一事物进行操作的方法和相关的方法放在同一个类中,把方法和它操作的数据放在同一个类中。

有一个很简单的例子,例如,人要在黑板上画圆,这一共涉及三个对象:人、黑板、圆,画圆的方法要分配给哪个对象呢?由于画圆需要使用到圆心和半径,圆心和半径显然是圆的属性,如果将它们在类中定义成了私有的成员变量,那么,画圆的方法必须分配给圆,它才能访问到圆心和半径这两个属性,人以后只是调用圆的画圆方法、表示给圆发给消息而已,画圆这个方法不应该分配在人这个对象上,这就是面向对象的封装性,即将对象封装成一个高度自治和相对封闭的个体,对象状态(属性)由这个对象自己的行为(方法)来读取和改变。一个更便于理解的例子就是,司机将火车刹住了,刹车的动作是分配给司机,还是分配给火车,显然,应该分配给火车,因为司机自身是不可能有那么大的力气将一个火车给停下来的,只有火车自己才能完成这一动作,火车需要调用内部的离合器和刹车片等多个器件协作才能完成刹车这个动作,司机刹车的过程只是给火车发了一个消息,通知火车要执行刹车动作而已。

 2、抽象:

抽象就是找出一些事物的相似和共性之处,然后将这些事物归为一个类,这个类只考虑这些事物的相似和共性之处,并且会忽略与当前主题和目标无关的那些方面,将注意力集中在与当前目标有关的方面。例如,看到一只蚂蚁和大象,你能够想象出它们的相同之处,那就是抽象。抽象包括行为抽象和状态抽象两个方面。例如,定义一个Person类,如下:

class Person

{
   String name;
   int age;
}

人本来是很复杂的事物,有很多方面,但因为当前系统只需要了解人的姓名和年龄,所以上面定义的类中只包含姓名和年龄这两个属性,这就是一种抽像,使用抽象可以避免考虑一些与目标无关的细节。我对抽象的理解就是不要用显微镜去看一个事物的所有方面,这样涉及的内容就太多了,而是要善于划分问题的边界,当前系统需要什么,就只考虑什么。

3、继承:

在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把这个已经存在的类所定义的内容作为自己的内容,并可以加入若干新的内容,或修改原来的方法使之更适合特殊的需要,这就是继承,但需要注意的是一定不要只是为获得某个类的数据而继承该类,这是违背了继承的初衷的,实际上java的这种继承体制是符合现实生活新旧事物变化规律的,父类就好比是老的事物,而子类就相当与新的事物,新事物总是在老事物的基础上演变而来的,它们两者之间是有联系的,新事物之所以会产生是因为旧事物出现了一些新的特征,而这些特征是旧事物所不具有的(被旧事物所排斥的),但这新事物的产生也不是凭空的,它总是在老事物的基础上产生,它还是具有老事物的某些特征。继承是子类自动共享父类数据和方法的机制,这是类之间的一种关系,提高了软件的可重用性和可扩展性。

首先有反映一般事物特性的类,然后在此基础上反映出特殊事物的类,也就是说:继承是一种从一般到特殊的关系;

特点:

         (1)、提高了代码的复用性。

         (2)、让类与类之间产生关系,有了这个关系才有了多态的特性。

         (3)Java语言中只支持单继承(有别于C++语言)。

                   因为多继承容易带来安全隐患(父类多了,功能相同的话,就会出现调用不确定性吗,覆写一个方法,到底覆写的谁的?)。

         (4)Java支持多层继承,object是每个类的超类,实现树形结构。

需要注意:

          (1)、父类的私有成员子类不能继承到;父类的构造方法不能被继承;

          (2)、Java只支持单继承,不支持多继承;一个类有且只有一个直接父类;一个类没显示的继承其他的一个类的时候,默认的直接父类就是Object类;

        虽然Java只支持单继承,但Java却也提供了解决多继承问题的机制,在Java中的接口机制中,类不仅可以实现多接口,而且接口还可以继承多接口。

4、多态:

多态是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。多态性增强了软件的灵活性和扩展性。

多态产生的前提条件:事物之间必须存在一定的联系或关系(继承或者实现关系);

    好处:提高了程序的扩展性和灵活性。

    弊端:使用父类型的引用只能访问父类中的成员与方法。

一个很常见也很好理解的例子,例如,下面代码中的UserDao是一个接口,它定义引用变量userDao指向的实例对象由daofactory.getDao()在执行的时候返回,有时候指向的是UserJdbcDao这个实现,有时候指向的是UserHibernateDao这个实现,这样,不用修改源代码,就可以改变userDao指向的具体类实现,从而导致userDao.insertUser()方法调用的具体代码也随之改变,即有时候调用的是UserJdbcDaoinsertUser方法,有时候调用的是UserHibernateDaoinsertUser方法:

UserDao userDao = daofactory.getDao(); 

userDao.insertUser(user);

java中多态机制的实现:

父类或接口类型的引用变量可以指向子类或具体实现该接口的类的实例对象,而该引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。(也就是说多态是一种晚绑定(later-binding)) 

二、抽象类与接口

1、含有abstract修饰符的class即为抽象类,abstract类不能创建的实例对象。含有abstract方法的类必须定义为abstract classabstract class类中的方法不必是抽象的。abstract class类中定义抽象方法必须在具体(Concrete)子类中实现,所以,不能有抽象构造方法或抽象静态方法。如果的子类没有实现抽象父类中的所有抽象方法,那么子类也必须定义为abstract类型。

2、接口(interface)可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。接口更多的是在系统架构设计方法发挥作用,主要用于定义模块之间的通信契约。而抽象类在代码实现方面发挥作用,可以实现代码的重用(模板设计模式)。

下面比较一下两者的语法区别:

(1)、抽象类可以有构造方法,接口中不能有构造方法。

(2)、抽象类中可以有普通成员变量,接口中没有普通成员变量

(3)、抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。

(4)、抽象类中的抽象方法的访问类型可以是publicprotected和(默认类型,虽然eclipse下不报错,但应该也不行),但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。

(5)、抽象类中可以包含静态方法,接口中不能包含静态方法

(6)、抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。

三、重载(Overloaded)与重写/覆盖(Override)

Overload是重载的意思,Override是覆盖的意思,也就是重写。

1、重载Overload表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同(即参数个数或类型不同)。

overload对我们来说可能比较熟悉,可以翻译为重载,它是指我们可以定义一些名称相同的方法,通过定义不同的输入参数来区分这些方法,然后再调用时,VM就会根据不同的参数样式,来选择合适的方法执行。在使用重载要注意以下的几点:

(1)、在使用重载时只能通过不同的参数样式。例如,不同的参数类型,不同的参数个数,不同的参数顺序(当然,同一方法内的几个参数类型必须不一样,例如可以是fun(int,float),但是不能为fun(int,int));

(2)、不能通过访问权限、返回类型、抛出的异常进行重载;

(3)、方法的异常类型和数目不会对重载造成影响;

(4)、对于继承来说,如果某一方法在父类中是访问权限是priavte,那么就不能在子类对其进行重载,如果定义的话,也只是定义了一个新方法,而不会达到重载的效果。

至于Overloaded的方法是否可以改变返回值的类型这个问题。

如果几个Overloaded的方法的参数列表不一样,它们的返回者类型当然也可以不一样。如果两个方法的参数列表完全一样,是否可以让它们的返回值不同来实现重载Overload。这是不行的,我们可以用反证法来说明这个问题,因为我们有时候调用一个方法时也可以不定义返回结果变量,即不要关心其返回结果,例如,我们调用map.remove(key)方法时,虽然remove方法有返回值,但是我们通常都不会定义接收返回结果的变量,这时候假设该类中有两个名称和参数列表完全相同的方法,仅仅是返回类型不同,java就无法确定编程者倒底是想调用哪个方法了,因为它无法通过返回结果类型来判断。

2、重写Override表示子类中的方法可以与父类中的某个方法的名称和参数完全相同,通过子类创建的实例对象调用这个方法时,将调用子类中的定义方法,这相当于把父类中定义的那个完全相同的方法给覆盖了,这也是面向对象编程的多态性的一种表现。子类覆盖父类的方法时,只能比父类抛出更少的异常,或者是抛出父类抛出的异常的子异常,因为子类可以解决父类的一些问题,不能比父类有更多的问题。子类方法的访问权限只能比父类的更大,不能更小。如果父类的方法是private类型,那么,子类则不存在覆盖的限制,相当于子类中增加了一个全新的方法。 

override可以翻译为覆盖,从字面就可以知道,它是覆盖了一个方法并且对其重写,以求达到不同的作用。对我们来说最熟悉的覆盖就是对接口方法的实现,在接口中一般只是对方法进行了声明,而我们在实现时,就需要实现接口声明的所有方法。除了这个典型的用法以外,我们在继承中也可能会在子类覆盖父类中的方法。在覆盖要注意以下的几点:

(1)、覆盖的方法的标志必须要和被覆盖的方法的标志完全匹配,才能达到覆盖的效果;

(2)、覆盖的方法的返回值必须和被覆盖的方法的返回一致,或更小;

(3)、覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类;

(4)、被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。

总之如下:

(1)、方法签名必须相同;

(2)、子类方法的返回值类型比父类方法的返回值类型更小或相等(此处的类型更小指的是从继承关系上来看的而并非是指基本数据类型的取值范围大小);    

     子类方法声明抛出的异常应比父类方法申明抛出的异常更小或相等;

(3)、子类方法的访问权限应比父类方法更大或相等;但private类型的方法是不可以进行override的,因为在子类中private方法是invisible的;

四、==与equals()比较

实际上java中的数据分为两类:引用类型(类类型)和非引用类型(原生类型)

对于原生类型的数据只能使用==比较,而对于引用类型的数据的比较使用==和equals()均可。

下面对二者作如下简介:

由于原生数据类型变量的比较相对简单,在此就不介绍了,下面重点说引用类型数据使用==和equals()比较的区别,如果变量指向的数据是对象类型的,那么,这时候涉及了两块内存,对象本身占用一块内存(堆内存),变量也占用一块内存,例如Objet obj = new Object();变量obj是一个内存,new Object()是另一个内存,此时,变量obj所对应的内存中存储的数值就是对象占用的那块内存的首地址。实际上比较的情况主要分为两种:

(1)、第一种情况是:对于那些没有override Object类中的equals()方法的引用类型数据而言,使用==操作符和equals()方法比较的效果是一样的,因为这种类型的数据的equals()方法是直接或间接来自于Object类中的equals()方法,而Object类中的equals()方法的实现就是使用==比较的。

Object类的equals方法的实现代码如下:

boolean equals(Object o){

return this==o;

}


(2)、第二种情况就是:那些override了Object类中的equals方法的引用类型数据,此时使用==和equals方法比较是有区别的。具体区别如何就得看对方是如何override equals方法的。就拿String类来说吧,String类override了Object类的equals方法。具体的实现如下:

//String类中override了Object类中的equals()
public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = count;
            if (n == anotherString.count) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = offset;
                int j = anotherString.offset;
                while (n-- != 0) {
                    if (v1[i++] != v2[j++])
                        return false;
                }
                return true;
            }
        }
        return false;
    }


String类中的equals先先是比较两个变量指向的内存地址是否相等,再判断传入的anObject是否是String类型的,如果是的话再转化为字符数组去比较,比较String变量代表的字符串中每个字符是否相等,若都相等,则认为两字符串相等,否则就不等。例如,对于下面的代码:

String a=new String("foo");

String b=new String("foo");


两条new语句创建了两个对象,然后用a,b这两个变量分别指向了其中一个对象,这是两个不同的对象,它们的首地址是不同的,即ab中存储的数值是不相同的,所以,表达式a==b将返回false,而这两个对象中的内容是相同的,所以,表达式a.equals(b)将返回true

总之,如果一个类没有自己定义equals方法,它默认的equals方法(从Object类继承的)就是使用==操作符,也是在比较两个变量指向的对象是否是同一对象,这时候使用equals和使用==会得到同样的结果,如果比较的是两个独立的对象则总返回false。如果你编写的类希望能够比较该类创建的两个实例对象的内容是否相同,那么你必须覆盖equals方法,由你自己写代码来决定在什么情况即可认为两个对象的内容是相同的。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值