第七章 面向对象核心技术
7.1 类的封装
在面向对象编程中,封装被视为一种核心机制,在软件开发中发挥着关键作用。实现对目标对象属性与行为的打包处理后才能有效管理系统的复杂性与多样性,在软件设计中这一原则具有重要指导意义。本节旨在深入探讨如何实现对类信息的有效打包与管理。
例7.1创建R1这个类,实现餐馆点菜的场景.
1. public class R1 {
2. public static void main(String[] args) {
3. String cookName="Tom Cruise";//厨师的名字叫 Tom Cruise
4. System.out.println("**请让厨师为我做一份香辣肉丝。***");
5. System.out.println(cookName +"切葱花");
6. System.out.println(cookName+"洗蔬菜");
7. System.out.println(cookName +"开始烹饪"+"香辣肉丝");
8. System.out.println("**请问厨师叫什么名字?***");
9. System.out.println(cookName);
10. System.out.println("**请让厨师给我切一点葱花。***");
11. System.out.println(cookName + "切葱花");
12.
13. }
14.
15. }

例7.2将厨师封装成cook类,实现餐馆点菜的场景.
1. public class R2 {
2. public static void main(String[] args) {
3. Cook1 cook =new Cook1();// 创建厨师类的对象
4. System.out.println("**请让厨师为我做一份香辣肉丝。");
5. cook.cooking("香辣肉丝");//厨师烹饪香辣肉丝
6. System.out.println("**你们的厨师叫什么名字?***");
7. System.out.println(cook.name);//厨师回答自己的名字
8. System.out.println("**请让厨师给我切一点葱花。***");
9. cook.cutOnion();// 厨师去切葱花
10. }}
11. class Cook1{
12. String name;//厨师的名字
13. public Cook1() {
14. this.name ="Tom Cruise";}//厨师的名字叫Tom Cruise
15. void cutOnion() {//厨师切葱花
16. System.out.println(name +"切葱花");
17. }
18. void washVegetables(){//厨师洗蔬菜
19. System.out.println(name+"洗蔬菜");
20. }
21. void cooking(String dish) {//厨师烹饪顾客点的菜
22. washVegetables();
23. cutOnion();
24. System.out.println(name+"开始烹饪"+dish);
25. }
26.
27. }

以独立类的形式封装厨师,并将其工作流程定义为特定的行为模式。每当需要让厨师烹饪时,必须通过调用对象成员方法来完成这一操作;然而我们却无法了解该方法的具体实现细节以及其背后的设计逻辑依据。因此,在这种情况下我们也就无法随意更改功能模块设置。此外,在这种背景下餐馆无需披露与厨房相关的任何信息;并且厨房人员也不会被无故指派烹饪任务;因此厨房的一些属性和行为选择性地被隐藏起来
例7.3将厨师的属性和部分法方法用private修饰
1. public class R3 {
2. public static void main(String[] args) {
3. Cook2 cook =new Cook2();//创建厨师类的对象
4. System.out.println("**请让厨师为我做一份香辣肉丝。");
5. cook.cooking("香辣肉丝");//厨师烹饪香辣肉丝
6. System.out.println("**你们的厨师叫什么名字?");
7. System.out.println(cook.name);//厨师回答自己的名字
8. System.out.println("**请让厨师给我切一点葱花。***");
9. cook.cutOnion();// 厨师去切葱花
10. }}
11. class Cook2{
12. private String name;//厨师的名字
13. public Cook2() {
14. this.name ="Tom Cruise";
15. }//厨师的名字叫Tom Cruise
16. private void cutOnion() {//厨师切葱花
17. System.out.println(name+"切葱花");
18. }
19. private void washVegetables() {//厨师洗蔬菜
20. System.out.println(name+ "洗蔬菜");
21. }
22. void cooking(String dish) {//厨师烹饪顾客点的菜
23. washVegetables();
24. cutOnion();
25. System.out.println (name+"开始烹饪"+ dish);
26. }
27.
28. }

通过这个实例可以看出,在这一过程中顾客始终与服务员保持交流,并与厨房人员间接沟通。在这一过程中,顾客无法直接了解自己品尝到的食物是如何由厨师制作完成的。这种编程模式被称为封装。
将对象的属性与行为整合在一起的载体是类,在软件设计中类通常会隐藏实现细节并提供公共接口以供外部使用。这种设计思想的核心就是所谓的封装思想。
7.2 类的继承的
继承在面向对象开发思想中是一个非常重要的概念,它使整个程序架构具有一定的弹性,在程序中复用已经定义完善类不仅可以减少软件开发周期,还可以提高软件的可维护性和可扩展性。本节将详细讲解类的继承。
在第6章中曾简要介绍过继承,其基本思想是基于某个父类的扩展,制定出一个新的子类,子米可以继承父类原有的属性和方法,也可以增加原来父类所不具备的属性和方法,或者直接重写父可以说平行四边形类继承了四边形类
1. public class R4 {
2. private Cook2 cook = new Cook2();//餐厅封装的厨师类
3. public void takeOrder(String dish) {//下单
4. cook.cooking(dish);// 通知厨师做菜
5. System.out.println("您的菜好了,请慢用。");
6. }
7. public String saySorry(){//拒绝顾客请求
8. return"抱歉,餐厅不提供此项服务。";
9. }
10. public static void main(String[] args) {
11. R4 water = new R4();//创建餐厅对象,为顾客提供服务
12. System.out.println("**请让厨师为我做一份香辣肉丝。***");
13. water.takeOrder("香辣肉丝");//服务员给顾客下单
14. System.out.println("**你们的厨师叫什么名字?***");
15. System.out.println(water.saySorry());// 服务员给顾客善意的答复
16. System.out.println("**请让厨师给我切一点葱花。***");
17. System.out.println(water.saySorry());// /服务员给顾客善意的答复
18. }
19. }

例7.5创建Pad2类,继承Computer类
1. class Computer {//父类:电脑
2.
3. String screen="液晶显示屏";
4. void startup() {
5. System.out.println("电脑正在开机,请等待...");
6. }
7. }
8. public class Pad2 extends Computer {
9. String battery="5000毫安电池";// 子类独有的属性
10. public static void main(String[] args) {
11. Computer pc = new Computer();// 电脑类
12. System.out.println("computer的屏幕是:"+pc.screen);
13. pc.startup();
14. Pad2 ipad = new Pad2();//平板电脑类
15. System.out.println("pad的屏幕是:"+ ipad.screen);//子类可以直接使用父类
16. System.out.println("pad的电池是:"+ipad.battery);//子类独有的属性
17. ipad.startup();// 子类可以直接使用父类方法
18. }
19. }

7.2.2方法的重写
1.重写的实现
继承不仅仅是为父类增加功能,并且还可以对父类的方法进行覆盖。在子类中对父类的方法进行保留后,在实现细节上进行重构,在访问权限级别上进行改变,并对返回值类型进行优化(注:优化可能是基于J2SE 5.0及以上版本提供的新特性)
例7.6创建Pad3类继承Computer2类,并重写父类的showPicture方法
1. class Computer2{//父类:电脑
2. void showPicture(){
3. System.out.println("手指点击触摸屏");
4. }}
5. public class Pad3 extends Computer2{//子类:平板电脑
6. void showPicture() {
7. System.out.println("手机点击触摸屏");
8. }
9. public static void main(String[] args) {
10. Computer2 pc=new Computer2();// 电脑类
11. System.out.print("pc打开图片:");
12. pc.showPicture();
13. Pad3 ipad=new Pad3();//平板电脑类
14. System.out.print("ipad打开图片:");
15. ipad.showPicture();//重写父类方法
16. Computer2 computerpad=new Pad3();//父类声明,子类实现
17. System.out.print("computerpad打开图片:");
18. computerpad.showPicture();//调用父类方法,实现子类重写的逻辑
19. }}

例7.7通过实现Pad4类的生成,并采用继承的方式完成Computer3类功能的实现;接着需要对ParentClass的方法进行重新编写,并通过super关键字调用其父类的方法。
1. class Computer3 {// 父类:电脑
2. String sayHello() {
3. return"欢迎使用";
4. }}
5. public class Pad4 extends Computer3{//子类:平板电脑
6. String sayHello() {// 子类重写父类方法
7. return super.sayHello()+"平板电脑";//调用父类方法,在其结果后添加字符串
8. }
9. public static void main(String[] args) {
10. Computer3 pc = new Computer3();// 电脑类
11. System.out.println(pc.sayHello());
12. Pad4 ipad = new Pad4();// 平板电脑类
13. System.out.println(ipad.sayHello());
14. }}

7.2.3 所有类的父类——Object类
在开始学习使用class关键字定义类时,就应用了继承原理,因为在Java中,所有的类都直接或间接继承了java.lang.Object类。Object类是比较特殊的类,它是所有类的父类,是Java类层中的最高层类。当创建一个类 class Anything (时,总是在继承,除非某个类已经指定要从其他类继承,否则它就是从java.lang.Object类继承而来的,可见Java中的每个类 等价于都源于java.lang.Object类,如 String、Integer等类都是继承于Object类;除此之外自定义的类也都继承于Object类。由于所 class Anything extends Object有类都是Object子类,所以在定义类时,省略了extends Object
关键字,如图7.10所示便描述了这一原则。 图7.10定义类时可以省略extends Objec
在Object类中主要包括cloneO、finalizeO、equalsO、toStringO 关键字
等方法,其中常用的两个方法为equals()和toString0方法。由于所有的类都是Object 类的子类,所以任何类都可以重写Object类中的方法。

例7.8为项目创建ObjectInstance类,在所述类中重新编写toSpring方法,并在组方法中输出该实例对象。
1. public class Ob {
2. public String toString() {//重写toString()方法
3. return"在"+getClass().getName()+"类中重写toString()方法";
4. }
5. public static void main(String[] args) {
6. System.out.println(new Ob());//打印本类对象
7.
8. }
9.
10. }

1. getClass()方法
getClass方法是Objecet类定义的方法,它会返回对象执行时的Class实例,然后使用比实的用getName(0方法可以取得类的名称。
语法如下:
getClass ().getName () ;
可以将getClass()方法与toString0方法联合使用。
该功能旨在将对象转换为其字符串表示形式;其结果是一个String实例;在实际应用中通常会重写toString0方法;以特定的方式呈现输出内容;当这个类转换为字符串或与字形连接时……会自动调用重写的toString0方法。
3. equals()方法
在之前的章节中已经详细阐述了equals0方法,在那个部分主要对比了等于号运算符与equals方法,并解释说明等于号比较链用于判断两个对象的引用是否一致;而另一方面,则指出equals方法则是用来比较两个对象的具体内容;基于上述理论基础,请结合下面的实例进一步理解相关内容。
在项目中创建一个名为OverWritrEqual的类,在其静态方法中初始化两个字符串实例对象,并调用比较方法判断这两个字符串实例是否相等
1. class V{}
2. public class Over {
3.
4. public static void main(String[] args) {
5. String s1="123";//实例化两个对象,内容相同
6. String s2="123";
7. System.out.println(s1.equals(s2));//使用equals()方法调用
8. V v1=new V();//实例化两个V类对象
9. V v2=new V();
10. System.out.println(v1.equals(v2));//使用equals()方法比较v1与v2对象
11. }
12.
13. }

7.3 类的多态
多态指的是同一名称具有多种语义,在程序设计语言中将被定义为“一种定义对应多种实现”。例如,在两个整型操作数的情况下,运算符“+”执行加法操作;而在两个字符型操作数的情况下,则会将它们连接成一个字符串。“+”这一运算符便实现了两者的结合与统一。利用这种特性可以使程序代码更加简洁高效,并且允许对所有类对象进行通用的操作处理。类的多态性可以从以下两个方面进行体现:其一是通过方法重载实现不同功能;其二是通过类的上下转型机制实现功能转换;本节将详细阐述这两种表现形式。
7.3.1 方法的重载
在第6章中曾学习过构造方法,知道构造方法的名称由类名决定,所构造方法只有一个名称,
但如果希望以不同的方式来实例化对象,就需要使用多个构造方法来完成。由于这些构造方法都需Stanc要根据类名进行命名,为了让方法名相同而形参不同的构造方法同时存在,必须用到“方法重载”。
虽然方法重载起源于构造方法,但是它也可以应用到其他方法中。本节将讲述方法的重载。
方法的重载就是在同一个类中允许同时存在一个以上的同名方法,只要这些方法的参数个数或类型不同即可。为了更好地解释重载,来看下面的实例。
例7.10通过在项目环境中构建相应的类,并为其设计多个功能重叠的方法;随后,在统一的结果处理流程中获取并展示各个实现方法的具体返回数据
1. public class OverLoadText {
2. public static int add(int a) {
3. return a;
4. }
5. //定义第一个方法参数个数不同的方法
6. public static int add(int a,int b) {
7. return a+b;
8. }
9. //定义与第一个方法相同名称.参数类型不同的方法
10. public static double add(double a,double b) {
11. return a+b;
12. }
13. //定义一个成员方法
14. public static int add(double a,int b) {
15. return (int)(a+b);
16. }//定义不定长参数
17. public static int add(int...a) {
18. int s=0;
19. //根据参数个数循环操作
20. for(int i=0;i<a.length;i++) {
21. s +=a[i];//将每个参数的值相加
22. }
23. return s;//将计算结果返回
24. }
25. public static void main(String args[]) {//主方法
26. System.out.println("调用add(int)方法:"+ add(1));//调用add(int)方法
27. System.out.println("调用add(int,int)方法:"+ add(1,2));//调用add(int,int)方法方法
28. System.out.println("调用add(double,double)方法:"+ add(2.1,3.3));//调用add(double,double)方法
29. System.out.println("调用add(int a, double b)方法:"+ add(1, 3.3));//调用add(int a, double b)方法
30. System.out.println("调用add(double a, int b)方法:"+ add(2.1, 3));//调用add(double a, int b)方法
31. System.out.println("调用add(int... a)不定长参数方法:"+ add(1,2,3,4,5,6,7,8,9));//调用add(int... a)方法
32. System.out.println("调用add(int... a)不定长参数方法:"+ add(2,3,4));//调用add(int... a)方法
33. }}

在之前的学习中,在一个文件内创建了两个不同的类别。然而这两个类别彼此独立,并不在对方的范围内存在。然而如果在一个现有类别(即一个已有的类别)内再次定义一个新类别,则该新类别将被称为内部类别(internal class)。为了更好地理解这一概念,请考虑这样一个场景:类似于汽车与发动机之间的关系。显然,在这种情况下,并不能单独用属性或方法来表示一个发动机——因为发动机本身就是一个独立的系统或实体(即是一个独立的类型),而它又作为一部分存在于汽车这个更大的体系之中(就像嵌套级别的类型一样)。这种嵌套式的类型设计非常有用,并且能够帮助我们更好地组织代码结构(如成员级内部类型、局部级内部类型以及匿名类型)。本节将深入探讨这一高级特性及其应用方式
在项目环境中构建OuterClass这一类。在此类内部定义了内层class innerClas以及具有的dot()方法,并在其中 instantiate OMarClass 类的对象并实现doit()方法。
1. public class OuterClass {
2. innerClass in=new innerClass();
3. public void ouf() {
4. in.inf();
5. }class innerClass{//创建一个类
6. innerClass(){
7. }public void inf() {//内部类成员方法
8.
9. }int y=0;//定义内部类成员变量
10. }public innerClass doit() {
11. //外部类方法,返回值为内部类引用
12. in.y= 4; //外部类不可以直接访问内部类成员变量
13. return new innerClass();
14. }public static void main(String args[]) {
15. OuterClass out = new OuterClass();
16. //内部类的对象实例化操作必须在外部类或外部类的非静态方法中实现
17. OuterClass.innerClass in = out.doit();
18. OuterClass.innerClass in2 = out.new innerClass();
19. }}
在项目中构建一个名为Interfacelnner的类,并定义一个内类InnerClass来实现该接口Outnterfas。最后,在doit()方法中设置其返回值类型为该接口。
1. interface OutInterface{
2. public void f();
3. }//定义一个接口
4. public class InterfaceInner {
5. public static void main(String args[]){//实例化一个OuterClass2对象
6. OuterClass2 out = new OuterClass2();
7. //调用doit()方法,返回一个OutInterface接口
8. OutInterface outinter = out.doit();
9. outinter.f(); //调用f()方法
10. }}
11. class OuterClass2 {
12. //定义一个内部类实现OutInterface 接口
13. private class InnerClass implements OutInterface {//继承类 OutInterface
14. InnerClass(String s){//内部类构造方法
15. System.out.println(s);
16. }
17. public void f() {//实现接口中的f()方法
18. System.out.println("访问内部类中的f()方法");
19. }}
20. public OutInterface doit() { //定义一个方法,返回值类型OutInter接口
21. return new InnerClass("访问内部类构造方法");
22. }
23. }

通过this关键字区分内部对象与外部对象中的字段引用关系
局部内部类
嵌套类不仅可以作为独立的实体直接进行定义,并且还可以在代码块内的局部位置进行声明;例如,在成员方法或其他局部区域均可定义嵌套(内部)类型。
匿名内部类
为了进一步优化,在doit方法中的return语句及其内部类定义可以通过具体实例展示,并且例如,在return语句中编写返回值为一个匿名内部类。
1.
2. interface OutInterface2 {//定义一个接口
3. }class OuterClass4{
4. public OutInterface2 doit() {//定义doit()方法
5. return new OutInterface2() {//声明匿名内部类
6. private int i= 0;
7. public int getValue() {
8. return i;//返回值
9. }
10. };
11. }}
在非 static 内 classes 中无法声明 static 成员。
static internal class 具有显著特征:它依赖于创建它的 external 非 static 类型。
从另一个角度讲,在这种情况下外部对象是不可见的。
当定义 static internal class 对象时:
(1) 如果您想创建 static internal class 对象,则无需显式地构造其 parent 类型;
(2) 但是您无法通过该对象访问 parent 类型的 non-static 成员。
例如:
public class Outer {
public class StaticInnerClass extends Outer {
// 这里 StaticInnerClass 是 static 的
}
}
1. public classStaticInnerClass{
2. int x = 100;//定义一个变量x
3. static classInnert{//静态内部类
4. void doitInner(){
5. // System.out.print1n("外部类"+x);}//不能调用外部类的成员变量x
在内部类的doidnner()方法中引用成员变量x,在这种情况下由于Inner被声明为state k类型而成员变量x却不是static类型的原因导致了无法在doitInner0方法中引用该变量。当我们在进行程序测试的过程中发现如果每个Java源文件都需要编写一个主方法将会产生大量冗余代码而实际上这类主方法并不必要为此问题提供了一个解决方案即通过将主方法嵌入到静态内部类之中从而避免了不必要的冗余代码生成。
例7.26在静态内部类中定义主方法。
1. public class Static {
2. int x=100;//定义变量X
3. static class Inner{//静态内部类
4. void doitInner() {
5. //System.out.println("外部类"+x);
6. }
7. }
8. public static void main(String[] args) {//主方法
9. System.out.println();//输出
10.
11. }
12.
13. }
如果编译示例7.26中的class文件,则会生成一个名为基于Java的静态内 classes(StateChass)的状态变化器(StateChass)。通过编写java-static-inner-class-specification(java-static-inner-class-spec),即可允许在主 class 中访问其静态内部 class 的方法(methods)。完成后将所有.class文件打包时,请移除StaticInnerClass$Inner独立类文件以避免冲突。
通过观看视频得知,在Java中内部类与其他普通类一样是可以被继承的(inheritable)。然而相比其他类型的外部 class而言,在Java中实现内部 class 的继承关系相对复杂(complex),因为这需要特别的方法来进行操作(operation)。
例如,在项目中创建一个名为OutputInnerClass的新内部类时,请确保该新类型能够继承自ClassA中的现有内部 ClassB。
1. public class OutputInnerClass extends ClassA.ClassB{//继承内部类classB
2. public OutputInnerClass(ClassA a) {
3. a.super();
4. }
5. }class ClassA{
6. class ClassB
7. }}
在对象继承体系中,super()方法被定义为调用父类Outer本身的无参数构造函数。这一行为特别适用于嵌套结构中的内层对象初始化场景。当嵌套对象在不提供具体参数的情况下启动自身初始化流程时,默认会触发其内部结构所关联的所有前级实例创建机制。这种机制确保了一层层的对象能够顺利建立基础架构并逐步完成整体构建过程。
当嵌套对象在不提供具体参数的情况下启动自身初始化流程时,默认会触发其内部结构所关联的所有前级实例创建机制。这种机制确保了一层层的对象能够顺利建立基础架构并逐步完成整体构建过程。
以上规则构成了清晰的对象层次化设计框架,在实际应用开发过程中具有重要指导意义
本章小结
重载
构造函数属于一类特殊的函数类型,在创建实例过程中具有独特的功能特点。其主要用途在于,在对象被实例化的过程中为其实例分配并设置其属性值。
由于它们的名字通常与定义它们所属类的名字保持一致,在设计不同初始化逻辑时需要确保这些方法能够正确调用。
然而,在尝试通过不同的途径来实现对象的不同初始化需求时,
重写
覆盖(也可称为重写)是继承的一种实现方式
具体而言
重构
重构被视为在继承过程中的一种特殊重写方式,在这种情况下,仅涉及子类方法的实现细节,并确保成员方法的名称、参数类型及其个数以及返回值均保持不变。
向上转型
1. 本质:父类的引用指向了子类的对象。
2. 语法:父类类型 引用名 = new 子类类型();
向下转型
1. 语法:子类类型 引用名 = (子类类型) 父类引用。
1、一个静态内部类不仅具备声明 static 成员的能力(特性),而对非静态内部类则无法声明 static 成员(属性)。
2、这种类型的内部类主要特性在于其无法访问外部类中的非静态成员(方法),因此在实际开发中这类内部类相对罕见(少见)。
3、由于缺乏访问外部非静态成员(变量)的能力(机制),静态内部类无法直接调用这些成员变量(功能)。
4、在进行程序调试时,默认为每个 Java 文件添加一个主方法(函数)会导致大量冗余代码(代码)生成(问题),但此类情况并不常见(少见)。为此建议将主方法编写为一个嵌套在该文件中的 static 类内对象(机制)。
内部类继承必须满足四个条件:
一是外部类前需紧跟内部类前缀.
二是任何继承内部类的对象都必须提供一个带参数的构造方法.
此构造函数参数应指定外部对象引用类型.
四是通过a.super()调用父对象的方法即可完成对象引用传递
接口的定义 接口:方法定义和常量值的集合
接口的特点:
1、接口是一种特殊的抽象类,只包含常量和方法的定义,而没有方法的实现。
在接口中明确标识多个需要达成的方法,并无需关注这些类之间的层次结构
在类体中可以使用接口中定义的常量,必须实现接口中定义的所有方法。
3、一个类可以实现多个接口,在implements字句中用逗号隔开。
