概述
★ 组合
★ 继承
一、组合语法
★ 组合的概念
◆ 由现有类的对象组成新的类
★ class WaterSource // 水源
{ private String s; // 字符串对象
WaterSource()
{ System.out.println(“WaterSource()”);
s=“Constructed”; }
public String toString()
{ return s;}
}
public class SprinklerSystem // 洒水系统
{ private String value1,value2,value3,
value4;
private WaterSource source =
new WaterSource();
private int i;
private float f;
public String toString()
{ return “value1=”+value1+“”+.....
“source=”+source;}
// 以下为简单的测试程序
public static void main(String[] args)
{
SprinklerSystem sprinklers = new
SprinklerSystem();
System.out.println(sprinklers);
}
★ 解释:toString()方法
◆ 每一个非基本类型的对象都有一个toString方法
◆ 调用时机:当编译器需要一个String(字符串)
而你却只有一个对象时,该对象中
的toString方法将被调用
二、继承语法
1、 继承的概念和语法
★ 使用extends关键字
★ class Cleaner
{
private String s="Cleaner";
public void append(String a ){s+=a;}
public void dilute(){ append(" dilute()");}
public void apply(){ append(" apply()");}
public void scrub(){ append(" scrub()");}
public String toString(){ return s;}
public static void main(String[] args)
{
Cleaner x=new Cleaner();
x.dilute(); x.apply(); x.scrub();
System.out.println(x);
}
}
public class Detergent extends Cleaner
{
public void scrub() // 改写
{ append(“ Detergent.scrub()”);
super.scrub();}
public void foam() // 增加
{ append(“foam()”);}
public static void main(String[] args)
{
Detergent x=new Detergent();
x.dilute(); x.apply(); x.scrub(); x.foam();
System.out.println(x);
System.out.println(“Testing base class:”);
Cleaner.main(args);
}
}
★ 解释:
1、数据成员指定为private,方法指定为public
◆ 如果数据成员指定为private,该数据成员同样
是往下遗传的,只是在子类中不能访问父类的
private对象而已
★ 解释:
2、可以为每个类都创建一个main方法
◆ 优点:方便调试
◆ 注意:只有在命令行所调用的那个类的main方法
会被调用
二、继承语法
2、 初始化基类
⑴ 当创建了一个导出类的对象时,该对象
包含了一个基类的子对象
★ 关键:该子对象与你直接用基类创建的
对象是一样的
⑵ 无参构造器情形
★ Java会自动在导出类的构造器中插入对
基类构造器的调用
◆ 首先调用基类构造器,再调用导出类的
构造器(典例分析:P129)
⑶ 有参构造器情形
★ 必须用关键字super显示地调用基类的
构造器
◆ 典例分析:P130
★ class Game
{ Game(int i)
{ print(“Game constructor”);}
}
class BoardGame extends Game
{ BoardGame(int i)
{ super(i);
print(“BoardGame constructor”);}
}
三、组合使用组合和继承
1、 同时使用组合和继承
★ 典例分析:P132~133
◆ 关键:充分利用图形来分析和理解各个
类之间的层次关系
★ 虽然编译器强制你去初始化基类,并且
要求你要在构造器起始处就就要这么做,
但是它并不监督你必须将每个对象成员
都初始化
◆ 这就是为什么虽然有构造器,仍然需要
自动初始化的原因
2、 重载(overload)
⑴ 重载的定义
★ 类对自身已有的同名方法的重新定义
⑵ 重载的本质
★ 利用不同的参数列表,包括形式参数的
个数、类型、顺序的不同来区分重载
★ 重载的典例:
◆ family(){}
◆ family(String ch){ address=ch;}
◆ family(String ch,float n)
{ address=ch;pay=n;}
3、 覆盖(override)
⑴ 覆盖的定义
★ 子类对继承自父类方法的重新定义
⑵ 覆盖的本质
★ 是指在子类中定义了这样的一个方法:
该方法具有与父类完全相同的返回值
类型、方法名和参数列表(但是重新
编写了方法体)
⑴ 通过覆盖,父类中的方法被屏蔽掉,我们在
子类中使用的是新定义的方法
⑵ 如果在子类中需要调用父类的被覆盖的方法,
那么我们该怎么做?
★ 使用super关键字来实现
4、 重载与覆盖的区别
★ 关键:发生的地点不同
◆ 重载发生在同一个类中
◆ 覆盖发生在基类与派生类中
5、 Java中的重载与覆盖
⑴ 如果Java的基类拥有某个已被多次重载
的方法,那么在导出类中重新定义该方
法并不会覆盖在基类的任何版本
★ 方法名相同,但参数列表不同
★ class Homer
{ char doh(char c)
{ print(“doh(char)”);
return ‘d’;}
float doh(float c) // 重载
{ print(“doh(float)”);
return 1.0f;}
}
★ class Milhouse {}
class Bart extends Homer
{ void doh(Milhouse m) // 重载而非覆盖
{ print(“doh(Milhouse)”);}
}
public class Hide // 以下为测试程序
{ .... }
★ 分析:
◆ 从前面的分析可以看出:重载是水平的(发生
在类的内部),覆盖是垂直的(发生在基类与
派生类之间)
◆ 然而现在讨论的问题同上面两点有所不同:
站在垂直的角度来探讨重载的问题
⑵ 补充说明
★ Java SE5新增了@override注解
◆ 当你试图覆盖该方法但却不小心重载时,
编译器会产生一条错误信息
四、在组合和继承之间选择
★ is-a的关系是用继承来表达的
has-a的关系是用组合来表达的
◆ 典例:交通工具与车的关系(特殊化)
◆ 典例:车与引擎、轮胎、车窗和车门的
关系(详见P137分析)
★ class Engine // 引擎
{
public void start(){} // 启动
public void rev(){} // 倒车
public void stop(){} } // 停止
class Wheel{...} // 轮胎
class Window{...} // 车窗
class Door{...} // 车门
★ public class car{
public Engine engine=new Engine ();
public Wheel[] wheel=new Wheel[4];
public Door left=new Door(),
right=new Door();
public Car()
{ for(int i=0;i<4;i++)
wheel [i]=new Wheel();}
....
}
五、向上转型
1、 可以从两方面来理解继承
⑴ 为新的类提供方法
⑵ 更重要在于:用来表现新类和基类之间
的关系,即新类是基类的一种类型
★ class Instrument // 乐器
{
public void play() // 演奏
{ System.out.println(“playing....!”);}
static void tune(Instrument i) // 调音
{ i.play();}
}
★ public class Wind extends Instrument
{ // 音乐分为管乐、弦乐、鼓乐等等
public static void main(String[] args)
{
Wind flute = new Wind(); // 长笛
Instrument.tune(flute);
}
}
2、 向上转型
★ 这种将Wind引用转换为Instrument引用
的动作,我们称之为向上转型
★ 鉴于Java对类型的检查十分严格,这种接受某种
类型的方法,同样也可以接受另外一种类型就会
显得比较奇怪
★ 向上转型是从一个较专用类型向较通用类型转换
所以总是很安全
◆ 导出类是基类的一个超集,它必须至少具备基类
中所含有的所有方法
六、final关键字
1、 基本概念
⑴ final的基本概念
★ 最终的,无法改变的
⑵ 常量可以进一步细分为:
★ 编译期常量
◆ 编译期间就能确定它的值
★ 非编译期常量
◆ 直到运行期间才能确定它的值
★ 典例分析
1、编译期常量
◆ private final int valueOne = 9;
2、非编译期常量
◆ private final int i4 = rand.nextInt(20);
2、 final数据
★ 对于基本类型,final使数值恒定不变
对于对象引用,final使引用恒定不变
1、一旦引用被初始化指向一个对象,就无法再把它
指向另一个对象
★ 然而,对象其自身却是可以修改的
★ 与C++相应概念的对比分析:
◆ 常引用与常对象
2、final关键字可以与static关键字一起使用
★ 按照惯例:一个既是static又是final的域,将采用
大写字母表示,并且单词之间用下划线分隔
★ 典例:
◆ public static final int VALUE_THREE = 39;
★ 典例分析:P140~141代码
class Value
{ // 只有一个域,并且为包访问(缺省)
int i;
// 仅含一个构造器
public Value(int i) { this.i = i;}
}
public class FinalData
{ private static Random rand
= new Random(47); // 47为种子数
private String id;
// 构造器
public FinalData(String id)
{ this.id = id;}
// 编译期常量
private final int valueOne = 9;
private static final int VALUE_TWO = 99;
// final与static final的区别在于: 尽管都属于常量,
但后者是属于类这个层次的,将在类被装载的时候
被初始化,而不是每次创建对象时都初始化
// 典型的公共常量,可以被包以外访问
public static final int VALUE_THREE = 39;
// 非编译期常量
private final int i4 = rand.nextInt(20);
static final int INT_5 = rand.nextInt(20);
// 对象引用(可看出final与static final的区别)
private Value v1 = new Value(11);
private final Value v2 = new Value(22);
private static final Value VAL_3
= new Value(33);
// 数组
private final int[] a = { 1,2,3,4,5,6 };
// 再次注意到toString方法的应用
public String toString()
{
return id + ": " + "i4 = " + i4
+ ", INT_5 = " + INT_5;
}
// 测试程序
public static void main(String[] args)
{
FinalData fd1 = new FinalData(“fd1”);
//! fd1.valueOne++; // 错误
fd1.v2.i++; // 对象本身可以改变
fd1.v1 = new Value(9); // OK,非常量
for(int i = 0;i < fd1.a.length;i++)
fd1.a[i]++; // 对象本身可以改变
// 错误,引用不能改变(不能指向另一个对象)
//! fd1.v2 = new Value(0);
//! fd1.VAL_3 = new Value(1);
//! fd1.a = new int[3];
print(fd1);
print(“Creating new FinalData”);
FinalData fd2 = new FinalData(“fd2”);
print(fd1);
print(fd2);
}
} /* Output:
// 通过i4与INT_5分析:final与static final的区别
2、 final方法
⑴ 使用final方法的原因之一
★ 将方法锁定,以防止任何继承修改它的
含义(慎用:参见P145分析Vector类与
ArrayList类)
⑵ 使用final方法的原因之二
★ 效率:编译器将方法的所有调用转换为
内嵌调用
◆ 副作用:使代码膨胀(最新版本的Java
已不再需要final来进行优化了)
⑶ 对private方法添加final修饰词没有意义
★ 问题:如果试图覆盖一个private方法?
◆ 覆盖只有在某方法是基类接口一部分时
才会出现(即此时你并没有覆盖该方法,
仅是生成了一个新的方法)
★ Java的加载方式与C/C++不一样
◆ 每个类的编译代码都存在于它自己独立
的文件中,该文件只有在需要使用程序
代码时才会被加载
◆ 典例分析(P146)
★ Java的加载方式与C/C++不一样
◆ 每个类的编译代码都存在于它自己独立
的文件中,该文件只有在需要使用程序
代码时才会被加载
◆ 典例分析(P146)
★ public class Beetle extends Insect
{ private int k=printInit(“....”);
public Beetle()
{ print(“k=” + k);print(“j=”+j);}
private static int x2=printInit(“.....”);
public static void main(String[] args)
{ print(“Beetle constructor”);
Beetle b=new Beetle();}
}
★ 解释:假设在Java上运行Beetle
1、首先访问Beetle.main()方法(static方法)
◆ 加载器开始启动并找出Beetle类的编译代码
◆ 加载过程中,编译器注意到它有一个基类,于是
继续加载基类(如果基类上还有其自身的基类,
继续加载,以此类推)
、向上加载到根基类
◆ 首先执行根基类中的static初始化,然后是它的
导出类,以此类推
⑴ 因为做的原因在于:导出类的static初始化可能
会依赖于基类成员能否被正确初始化
⑵ 这也再次证明了初始化第二基本原则的推论:
静态初始化是在类的加载时期完成的
3、到现在为止,必要的类都已经加载完毕,可以
开始创建对象了
⑴ 首先创建基类对象(按照初始化第一基本原则)
◆ 先进行自动初始化:对象中的所有基本类型都会
被设为默认值,而对象引用设为null
◆ 其次,进行指定初始化
◆ 最后,调用基类的构造器进行初始化
⑵ 当基类对象创建完毕,接下来要创建的是派生类
对象
◆ 仍然按照初始化的第一基本原则进行
◆ 即:首先进行自动初始化,再指定初始化,最后
利用构造器进行初始化
最后
以上就是寂寞毛衣为你收集整理的java编程思想学习笔记 第七章 复用类的全部内容,希望文章能够帮你解决java编程思想学习笔记 第七章 复用类所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复