3.3 Java面向对象基础
面向对象(Object Oriented,OO)是软件开发方法。面向对象的概念和应用已超越了程序设计和软件开发,扩展到如数据库系统、交互式界面、应用结构、应用平台、分布式系统、网络管理结构、CAD技术、人工智能等领域。面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。
3.3.1 类与对象
1.类与对象的定义
类是现实世界或思维世界中的实体在计算机中的反映,它将数据以及这些数据上的操作封装在一起。
对象是具有类类型的变量。类和对象是面向对象编程技术中最基本的概念。
2.类与对象的关系
类是对象的抽象,而对象是类的具体实例。类是抽象的,不占用内存,而对象是具体的,占用存储空间。类是用于创建对象的蓝图,它是一个定义包括在特定类型的对象中的方法和变量的软件模板。
3.对象的创建
1)对象(object)代表现实世界中可以明确标识的一个实体。例如,一个学生、一张桌子、一间教室,一台计算机都可以看作是一个对象。每个对象都有自己独特的状态标识和行为。
2)对象的属性(attribute)或者状态(state),学生有姓名和学号,该学生特有的姓名和学号就是该学生(对象)的属性。
3)对象的行为(behavior)由方法定义。调用对象的一个方法,其实就是给对象发消息,要求对象完成一个动作。可以定义学生对象具备学习的行为。学生对象可以调用学习的方法,执行学习的动作。
首先也是最重要的就是进行对象的创建:类名对象名=new类名();。
4.类类型的声明
类必须要被定义才可以被使用。
接下来最重要的就是如何将我们所学习的这些知识进行应用,用例如下:
【例3-13】 定义类的使用
程序运行结果如图3-13所示。
图3-13 类类型的说明
● 在此程序中,定义一个People类之后,在这个类中对类的属性进行创建,并对类的属性进行赋值,同时对类的方法进行定义,在主函数中对对象进行实例化,随后调用类的方法实现想输出的语句,这样一个简单的关于应用类的小程序代码就完成了。
3.3.2 封装和继承
1.封装
面向对象即是将功能封装进对象,强调具备了功能的对象。而封装性就是尽可能地隐藏对象内部细节,对外形成一道边界,只保留有限的接口和方法与外界进行交互。封装的原则是使对象以外的部分不能随意地访问和操作对象的内部属性,从而避免了外界对对象内部属性的破坏。可以通过对类的成员设置一定的访问权限,实现类中成员的信息隐藏。
优点:将变化隔离、便于使用、提高重用性、提高安全性。
原则:将不需要对外提供的内容都隐藏起来。即把属性都隐藏,提供公共方法对其访问。
具体访问权限如下:
1)private:类中限定为private的成员,只能被这个类本身访问。如果一个类的构造方法声明为private,则其他类不能生成该类的一个实例。
2)default:类中不加任何访问权限限定的成员属于默认的(default)访问状态,可以被这个类本身和同一个包中的类所访问。
3)protected:类中限定为protected的成员,可以被这个类本身和它的子类(包括同一个包中以及不同包中的子类)和同一个包中的所有其他的类所访问。
4)public:类中限定为public的成员,可以被所有的类访问。
下面是封装的示例。
【例3-14】 封装的使用
程序运行结果如3-14所示。
图3-14 封装
● 在此程序中,定义一个EncapTest类,同时对三个属性进行定义,随后再利用类的方法进行属性的赋值。在主函数中对对象进行实例化,并对类的属性进行赋值,随后利用类的方法输出人员的基本信息。这是一个很明显的封装的体现,可以看到这里通过private对类的属性进行了最认真的封装,属性设置为私有的,只能本类才能访问,其他类都访问不了,如此就对信息进行了隐藏。在之后的引用中可以做到调用属性,不会让其他的类对这封装的类进行调用,当然实例中public方法是外部类访问该类成员变量的入口。
2.继承
子类的对象拥有父类的全部属性与方法,称作子类对父类的继承。
子类继承父类,可以继承父类的方法和属性,实现了多态以及代码的重用,因此解决了系统的重用性和扩展性。但是继承破坏了封装,因为它是对子类开放的,修改父类会导致所有子类的改变,因此继承一定程度上又破坏了系统的可扩展性。所以,继承需要慎用,只有明确的is-a关系才能使用。同时继承是在程序开发过程中重构得到的,而不是程序设计之初就使用继承,很多面向对象开发者滥用继承,结果造成后期的代码解决不了需求的变化。因此优先使用组合而不是继承,是面向对象开发中一个重要的经验。
1)Java中父类可以拥有多个子类,但是子类只能继承一个父类,称为单继承。
2)继承实现了代码的复用。
3)Java中所有的类都是通过直接或间接地继承Java.lang.Object类得到的。
4)子类不能继承父类中访问权限为private的成员变量和方法。
5)子类可以重写父类的方法,即命名与父类同名的成员变量。
6)访问父类被隐藏的成员变量,如super.variable。
7)调用父类中被重写的方法,如super.Method([paramlist])。
8)调用父类的构造函数,如super([paramlist])。
9)父类变,子类就必须变。
下面是继承的示例。
【例3-15】 Java继承的意义
程序运行结果如图3-15所示。
图3-15 继承
● 在此程序中定义SuperClass类,随后定义一个SubClass对这个类进行继承,利用super调用父类的方法,再利用this对父类的属性进行修改,在主函数中对对象进行实例化并对类的属性进行赋值。
3.3.3 多态
对象的多态性是指在父类中定义的属性或方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为。这使得同一个属性或方法在父类及其各个子类中具有不同的语义。例如:“几何图形”的“绘图”方法,“椭圆”和“多边形”都是“几何图”的子类,其“绘图”方法功能不同。
继承是通过重写父类的同一方法的几个不同子类来体现的,那么就是通过实现接口并覆盖接口中同一方法的几个不同的类体现的。在接口的多态中,指向接口的引用必须是指定实现了该接口的一个类的实例程序,在运行时,根据对象引用的实际类型来执行对应的方法。
其实在继承链中对象方法的调用存在一个优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
Java的多态性体现在两个方面:由方法重载实现的静态多态性(编译时多态)和方法重写实现的动态多态性(运行时多态)。
1)编译时多态:在编译阶段,具体调用哪个被重载的方法,编译器会根据参数的不同来静态确定调用相应的方法。
2)运行时多态:由于子类继承了父类所有的属性(私有的除外),所以子类对象可以作为父类对象使用。程序中凡是使用父类对象的地方,都可以用子类对象来代替。一个对象可以通过引用子类的实例来调用子类的方法。
3.3.4 接口和抽象类
1.接口
接口,英文称作Interface,在软件工程中,接口泛指供别人调用的方法或者函数。从这里,我们可以体会到Java语言设计者的初衷,它是对行为的抽象。在Java中,定一个接口的形式如下:
implements是一个类,实现一个接口用的关键字,它是用来实现接口中定义的抽象方法。实现一个接口,必须实现接口中的所有方法。
下面是接口的示例。
【例3-16】 接口的实现
程序运行结果如图3-16所示。
● 在此程序中,创建一个Animal接口,并创建People类来实现Animal接口,同时在People类中实现接口中的方法,在主函数中对这个对象来进行实例化,实现接口的方法。
图3-16 接口
2.抽象类
如果想要灵活地使用抽象类就要首先了解抽象方法。抽象方法是一种特殊的方法:它只有声明,而没有具体的实现。抽象方法的声明格式如下:
如果一个类含有抽象方法,则称这个类为抽象类,抽象类必须在类前用abstract关键字修饰。因为抽象类中含有无具体实现的方法,所以不能用抽象类创建对象。
抽象类就是为了继承而存在的,如果你定义了一个抽象类,却不去继承它,那么等于白白创建了这个抽象类,因为你不能用它来做任何事情。对于一个父类,如果它的某个方法在父类中实现出来没有任何意义,必须根据子类的实际需求来进行不同的实现,那么就可以将这个方法声明为抽象方法,此时这个类也就成为抽象类了。
包含抽象方法的类称为抽象类,但并不意味着抽象类中只能有抽象方法,它和普通类一样,同样可以拥有成员变量和普通的成员方法。注意,抽象类和普通类的主要有以下三点区别。
1)抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。
2)抽象类不能用来创建对象。
3)如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为抽象类。
除此之外,抽象类和普通的类并没有区别。
下面是抽象类示例。
【例3-17】 抽象类Animal的声明和使用
程序运行结果如图3-17所示。
图3-17 抽象类
● 这是个最基本的经典的简洁的抽象类的方法继承,在这里面主函数中抽象类不能被实例化,所以也就是需要一个继承抽象类的类来实现抽象类中的方法,“不确定动物怎么叫的”,定义成抽象方法,来解决父类方法的不确定性。
● 抽象方法在父类中不能实现,所以没有函数体。但在后续继承时,要具体实现此方法。当继承的父类是抽象类时,需要将抽象类中的所有抽象方法全部实现。