类的继承、多态、封装
继承、多态和封装是Java面向对象的三大特征,它们是面向对象程序开发的重要环节,如果在程序中使用得当,这三大特性将整个程序的架构变得非常有弹性,同时可以减少代码的冗余性,继承机制的使用可以复用一些定义完好的类,减少重复代码的编写。多态机制可以动态调整对象的调用,降低对象之间的依存关系。
2.5.1 继承
继承在面向对象的过程开发思想中是一个非常重要的概念,通过继承,可以使用以前定义的类的成员方法和成员变量,经过简单的程序编码可以构建强大的新类。这样节约了很多编程时间,同时减少了出错的机会,也可以提高软件的可维护性、可扩展性。
继承可以理解成现实世界中的“是一种(is-a)”的关系,例如:轿车是一种汽车,而汽车又是一种交通工具,如图1。
应用is-a关系可以通过已知事物认识未知事物,从而提高认知新事物的效率。Java程序设计语言同样可以通过继承机制现有类的基础上去定义一个新类,并在原有类的基础上添加新的方法或修改原有方法,从而提高变成效率,减少出错几率。
实现继承是通过extends关键字在声明类时指定其父类,其声明格式如下:
[修饰符]class 类名 extends 父类名
参数:修饰符是可选参数,它是权限修饰符,用于限定类的访问级别,extends父类名用于指定继承的父类,其中父类又可称为超类,它可以是任何可继承的类。
【例】完成以下功能:
·定义Person类:name、age属性及say方法
·定义一个Student类,要求有name、age、school属性及say方法
类完成后我们会发现以下的问题:
学生是一个人吗?学生扩充了人的概念
Person和Student中是不是有很多重复的代码段
实际上此种情况就是类扩展的一种前兆,如果是一个好的程序,Student应该想办法去重用Person类,即在Person类的基础上进行扩展。
练习:1、编写一个 car类,包括成员变量颜色、速度、档位,成员方法换档变速。
2、编写一个SaloonCar类继Car类,通过改写数据,使得输出为:
子类扩展父类已有的功能,同时可以将父类中的操作全部继承下来。
继承的规定
Java只支持单继承,不允许多继承。即:
日常生活中很好理解:一个孩子只能有一个亲爸(有直接血缘关系的)
【扩展:此概念是由C++产生的,C++中是只允许一个子类有多个父类的】
Java允许有多层继承:
现实角度:假设爷爷非常富有,爸爸也在爷爷的基础上变得更富有,如果你也是一个非常懂得做生意的人,则资产传到你手里的时候,你也会继续扩充自己的财富。
即:java只允许多层继承,而不能多重继承,一个子类只能有一个父类,一个父类可以有多个子类。
Super关键字
继承某个父类而生成新的子类,不但拥有父类的变量与方法,还可以为子类添加新的成员变量和成员方法,增强父类的功能,也就是所谓的扩展.甚至还可以在子类中为父类的某个方法定义多个重载方法,增加该类的灵活性,但是,有时父类的方法不能完全适应子类,或者子类需要有不同的行为.例如:父类的dirve() 方法是输出开车的描述信息,而子类有可能要添加更多的功能,或者要丰富drive()方法,在开车时要检测油箱的油量是否充足,如果油箱是空的,那么无法使用drive()方法.
在这种情况下,子类可以重写父类的某个方法,或者说是覆盖父类的某个方法,只要在子类中定义与父类相同的方法既可.但是要注意方法的声明一定要和父类的方法声明一样,或者干脆把父类某方法的声明代码直接复制过来,去掉方法体,重新编写新的业务代码。
- 使用super关键字操作父类中被覆盖的方法
定义方法:
super.父类中的成员变量名;
super.父类中的方法名;
这段代码重写了父类的drive()方法,但是在oil变量大于0时,又需要执行父类的drive()方法,所以,使用了super关键字去访问父类的drive()方法,这样子就不必关心父类的drive()方法如何实现的。
例:在重写父类drive()方法时,加一个判断,如果油量<20,提示:“油量不足20%,请注意加油”
2、使用super关键字调用父类的构造方法
子类并不继承父类的构造方法。如果子类要使用父类的构造方法,需要使用super关键字,并且一定要在子类的构造方法中使用。
定义方法:
Super([构造参数列表])
构造参数列表:可选参数,根据调用父类构造方法的形参声明来填充。
[例]在上例中的car类中添加一个构造函数,用于初始化车子的颜色。
2.5.2 修饰符
Java中定义了private、public、protected和默认的权限修饰符,这些修饰符控制着对类和类的成员变量以及成员方法的访问规则。下表描述了修饰符的访问权限。
类的位置 | Private | Protected | 默认修饰符 | Public |
本类 | 可见 | 可见 | 可见 | 可见 |
同包其他类 | 不可见 | 可见 | 可见 | 可见 |
其他包的类 | 不可见 | 不可见 | 不可见 | 可见 |
同包的子类 | 不可见 | 可见 | 可见 | 可见 |
其他包的子类 | 不可见 | 可见 | 不可见 | 可见 |
2.5.2.1 public修饰符
Public是公有类型的权限修饰符,也就是说,使用public修饰的类、成员变量和成员方法,其他类都可以访问,包括任意包中的任意类以及子类。
例如:之前所有实例都使用了main()主方法,该方法的权限修饰符就使用了public修饰符,其声明代码如下:
Public static void main(String args[])
该方法必须声明为public,因为这个方法必须能够被虚拟机访问以执行程序。
2.5.2.1 private修饰符
private修饰符是私有权限修饰符,只有本类,也就是定义private私有成员的类能够访问,对于其他方式的访问,它都会拒绝,即:“不要碰我哦”。
如果一个类的成员变量或成员方法被修饰为private,该成员变量只能在本类中使用,在子类中是不可见的,并且对其他包的类也是不可见的。
例:定义一个Superclass类,在该类中定义private修饰的私有方法print()。然后创建子类与其他类进行访问,并把鼠标停留在有错误的代码位置。查看错误信息。
2.5.2.2 protected修饰符
protected是保护级别的权限修饰符,它保护成员不会被其他包或非子类访问。也就是说protected修饰的成员,只能被子类(可以不是直接子类,即间接继承的子类也可以)和同一个包中定义的其他类访问。
public class lei {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Superclass object = new Superclass();
object.print();
}
}
class Superclass{
protected void print(){
System.out.println("我是Superclass类的print方法");
}
}
2.5.2.3 默认权限修饰符
当不添加任何权限修饰时,编译器会使用默认的修饰符,该修饰符的权限级别与protected类似,不同之处在于,在其他包定义的子类无法访问父类默认权限修饰的成员。
练习:
1.设计一个表示二维平面上点的类Point,包含有表示坐标位置的protected 类型的成员变量x 和y,获取和设置x 和y 值的public 方法。
2.设计一个表示二维平面上圆的类Circle,它继承自类Point,还包含有表示圆半径的protected 类型的成员变量r、获取和设置r 值的public 方法、计算圆面积的public 方法。
3.设计一个表示圆柱体的类Cylinder,它继承自类Circle,还包含有表示圆柱体高的protected 类型的成员变量h、获取和设置h 值的public 方法、计算圆柱体体积的public方法。
4.建立Cylinder 对象,输出其轴心位置坐标、半径、面积、高及其体积的值。
2.5.3 封装
封装是指把对象的属性隐藏在对象的内部,不允许外部直接访问和修改。
2.5.3.1 把属性隐藏
要实现封装,第一步就是设置类的成员变量(即对象的属性)使用private修饰符,使其他类无法直接访问该成员变量,以防止外部直接访问和修改。例如:
class Car{
private String color ="黑色"; //颜色
private boolean running ; //行驶状态
}
2.5.3.2 定义设置器
把成员变量设置为private私有权限之后,其他类就不能访问了,必须通过本类定义的设置器方法来设置和修改成员变量的值,设置器方法的命名一般以set作为前缀,属性名作为后缀,
setcolor()方法从名称上就能够理解,它用于设置color属性值。例如:
public void setColor(String color){
this.color = color; //把参数color赋值给成员变量color
}
Public是方法的权限修饰符,对于设置器而言,它需要暴露给其他类,使其可以访问,所以使用public修饰符。参数列表定义了String类型的color参数,在方法体中区别参数color与成员变量color,使用了this关键字引用成员变量,并赋值为color参数的值。
以后设置对象的color属性就可以使用setcolor()方法完成,例如:
car whitecar = new car();
whitecar.setcolor("红色");
2.5.3.3 定义访问器
和设置器一样,因为成员变类被设置为private私有权限,相对于其他类,就隐藏了这个成员变量,所有要访问器方法读取对象的属性值,访问其以get作为方法名称的前缀,以属性名作为后缀。例如:
public String getcolor(){
return color;
}
三、类的多态性
多态性:
●方法的多态性
重载:同一个方法名称根据传入参数的不同,完成的功能也不同
覆写:同一个方法根据类的不同得到不同的功能
● 对象的多态性
向上转型
向下转型
面向对象程序开发的一个思路:[建议]永远不去继承一个普通类
对象的多态性是在继承应用上的一种扩展,所以,程序要先有继承关系才能够使用多态性
向上转型 父类 父类对象 = 子类实例
向下转型 子类 子类对象 = (子类)父类实例对象
【例】定义一个父类Person有一个打印的方法,定义一个子类Student继承并覆写父类的打印方法。
public class Demo1 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
//向上转型 父类 父类对象 = 子类实例 new 的是子类,就一定会找被覆写的方法
// 子类为父类对象实例化,调用的方法一定是子类所覆写的方法
Person per = new Student();
per.print() ;
}
}
class Person{
public void print(){
System.out.println("1、Person类 ,print()");
}
}
class Student extends Person{
public void print(){ //覆写
System.out.println("2、Student类 ,print()");
}
}
覆写:子类覆写父类的方法,在不同的类中
重载:用于实现同一个功能,但是需要不同的参数满足不同的用户
【例】子类中会有扩充某些功能,而这些功能在父类中没有定义,则需要向下转型。
public class Demo1 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
/* //向上转型 父类 父类对象 = 子类实例 new 的是子类,就一定会找被覆写的方法
// 子类为父类对象实例化,调用的方法一定是子类所覆写的方法
Person per = new Student();
per.print() ;
//向下转型 子类 子类对象 = (子类)父类实例对象
//子类扩充了某些功能,而这些功能在父类中没有定义,则需向下转型
Student stu = (Student)per ;
stu.studentfun() ;*/
}
}
class Person{
public void print(){
System.out.println("1、Person类 ,print()");
}
}
class Student extends Person{
public void print(){ //覆写
System.out.println("2、Student类 ,print()");
}
public void studentfun(){
System.out.println("3、Student类,studentfun()");
}
}
总结:1、子类为父类对象实例化,调用的方法一定是子类所覆写的方法
2、子类扩充了某些功能,而这些功能在父类中没有定义,则需向下转型
向上转型:子类→父类,自动
假设父亲是个最大的领导,张三由一个小职员提升为CEO
向下转型:父类→子类,强制
假设当上CEO的张三,因为管理不当,又被踢回了一个最普通的职业
日常生活:
假设有一天一个小孩跑了跑到张三面前,说,张三你是我父亲,请给我100万。
●如果张三知道自己有那么一个孩子,则肯定会同意并给他
●这个孩子张三根本不知道,会给他吗?
取决于张三和孩子之间的关系。
要让父类知道子类的存在,才能进行向下转型。
public class Demo1 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
/* //向上转型 父类 父类对象 = 子类实例 new 的是子类,就一定会找被覆写的方法
// 子类为父类对象实例化,调用的方法一定是子类所覆写的方法
Person per = new Student();
per.print() ;
//向下转型 子类 子类对象 = (子类)父类实例对象
//子类扩充了某些功能,而这些功能在父类中没有定义,则需向下转型
Student stu = (Student)per ;
stu.studentfun() ;*/
Person per = new Person() ;
Student stu = (Student)per ;
stu.print() ;
stu.studentfun();
}
}
class Person{
public void print(){
System.out.println("1、Person类 ,print()");
}
}
class Student extends Person{
public void print(){ //覆写
System.out.println("2、Student类 ,print()");
}
public void studentfun(){
System.out.println("3、Student类,studentfun()");
}
}
[练习]主人养了3只爱喝酒的小老鼠,它们分别是白鼠、灰鼠和花鼠,有一天,主人买来了两瓶好酒。这酒很贵,主人没有喝过,想让3只小老鼠先试试酒劲有多大,可是,这三只小老鼠喝酒后的反应不一样,通过以下的实例阐述小老鼠喝酒后分别是什么反应。
分别创建主人类、鼠类(创建drink()喝酒方法)和3个试酒的鼠类(继承鼠类并重写父类的drink()方法),使用Java的多态性实现主人给3只老鼠喝酒的动作,并分别输出每只老鼠喝酒后的反应。另外,3个试酒的鼠类都继承Mouse鼠类。
练习: 模仿人们放气球的程序.编写在窗框绘制椭圆的程序,要求在窗框上绘制10个彩色椭圆,各椭圆的大小不一,但最大不超过窗框尺寸的1/5,并且各椭圆的颜色不一.
[练习]给窗口添加背景图片
[测试]完成下图中当输入姓名并按下“按下”键时可以在JTextField中显示出来。定义一个类继承JFrame类,在构造方法中定义窗口,并调用成员方法initialize,成员方法initialize用来加载窗口中各类组件,并对按下按钮添加ActionListener()事件。