Advertisement

第七章面对对象核心技术

阅读量:

目录

7.1类的封装

7.2类的继承

7.2.1extends关键字

7.2.2方法的重写

7.2.3所有类的父类--object类

7.3类的多态

7.3.1方法的重载

7.3.2向上转型

7.3.3向下转型

7.3.4instanceof关键字

7.4抽象类与接口

7.4.1抽象类与抽象方法

7.4.2接口的声明及实现

7.4.3多重继承

7.4.4区分抽象类与接口

7.5访问控制

7.5.1访问控制符

7.5.2java类包

7.5.3final关键字

7.6内部类

7.6.1成员内部类

7.6.2局部内部类

7.6.3匿名内部类

7.6.4静态内部类

7.6.5内部类的继承

7.7小结


7.1类的封装

该类采用封装技术将关键信息储存在内部,并设置访问权限限制措施;外界只能通过本类预先定义的方法来完成对该隐秘数据的读取与操作。举个实例来说吧:一台普通的个人电脑内部构造复杂,通常包含着主板芯片组、中央处理器(CPU)、硬盘存储介质以及内存组件等核心组件。而作为普通用户我们完全无需深入了解这些细节就能正常使用电脑系统;电脑制造商则会采用封闭式的机箱设计,并对外提供若干标准接口如鼠标键盘显示器等外设接口以满足用户的日常使用需求。

复制代码
 封装的特点:

    
  
    
 只能通过规定的方法访问数据。
    
 隐藏类的实例细节,方便修改和实现。
    
 实现封装的具体步骤如下:
    
  
    
 修改属性的可见性来限制对属性的访问,一般设为 private。
    
 为每个属性创建一对赋值(setter)方法和取值(getter)方法,一般设为 public,用于属性的读写。
    
 在赋值和取值方法中,加入属性控制语句(对属性值的合法性进行判断)。

如上述代码所示,在 Employee 类中通过关键字 private 对属性进行修饰表明只有该类内部成员才能访问这些属性。然而通过这些属性的方法 setXxx() 可以实现赋值功能而 getXxx() 则可用于获取这些属性的值。在 setAge() 方法中首先检查接收的 age 参数如果超出 18 至 40 的有效范围则将该属性设置为默认值 20 否则将其设为接收的 age 值

使用打包技术,在实现过程中确保了对数据访问权限的有效控制,并满足了对象 age 的合法性要求。在设置 attribute 值的过程中,默认会施加一些约束条件;这样做的好处是可以为每个 attribute 设置默认合理的初始 value;此外,在读取 attribute 时可以直接引用其 name 属性。

7.2类的继承

7.2.1extends关键字

在OO(Object-Oriented)技术中,继承是其核心概念之一。从机制上讲,在OO编程中实现类与类之间的关联性时所遵循的原则与日常生活中常见的'遗传'行为具有诸多相似之处。具体而言,在类与类之间会共同保留自上层对象的关键属性,并通过这种方式降低代码复用率的同时显著提升系统运行效能。

Java语言中的继承机制是在现有类型基础上进行拓展的手段,在这种机制下会生成新的类型。这些已有的类型则被称为父类型、基类型或超类型,在它们的基础上会产生出一系列特定于子类型的特性与行为特征。而子类型则不仅继承了这些基础属性和方法,在功能上还能够进一步整合并新增一些特定的属性与操作方法以满足复杂需求。

该关键字紧跟在子类名称之后,并紧随其后的是该类所继承的父类名称。例如:

Java中使用 extends关键字来进行信息传递;英文名称是 extension而非 inheritance;extension很好地反映了 subclass与 superclass之间的关系;从这一角度来看, 使用 inheritance来描述 subclass与 superclass的关系存在误导性, 更恰当的方式是采用 extension.

提示:当父类中有带有参数的构造函数但未提供无参数版本时,在子类中必须定义带有参数的构造函数。这会导致默认调用父类中的无参数构建设造函数。但由于父类本身并未提供无参数版本,默认调用会导致错误。

复制代码
 public class PeopleTest {

    
     public static void main(String[] args) {
    
     // 创建Student类对象
    
     People stuPeople = new Student("王丽丽", 23, "女", "410521198902145589", "00001", "计算机应用与技术");
    
     System.out.println("----------------学生信息---------------------");
    
     System.out.println(stuPeople);
    
     // 创建Teacher类对象
    
     People teaPeople = new Teacher("张文", 30, "男", "410521198203128847", 5, "计算机应用与技术");
    
     System.out.println("----------------教师信息----------------------");
    
     System.out.println(teaPeople);
    
     }
    
 }
复制代码
 使用继承的注意点:

    
 子类一般比父类包含更多的属性和方法。
    
 父类中的 private 成员在子类中是不可见的,因此在子类中不能直接使用它们。
    
 父类和其子类间必须存在“是一个”即“is-a”的关系,否则不能用继承。但也并不是所有符合“is-a”关系的都应该用继承。例如,正方形是一个矩形,但不能让正方形类来继承矩形类,因为正方形不能从矩形扩展得到任何东西。正确的继承关系是正方形类继承图形类。
    
 Java 只允许单一继承(即一个子类只能有一个直接父类),C++ 可以多重继承(即一个子类有多个直接父类)。
复制代码
 继承的优缺点

    
 在面向对象语言中,继承是必不可少的、非常优秀的语言机制,它有如下优点:
    
 实现代码共享,减少创建类的工作量,使子类可以拥有父类的方法和属性。
    
 提高代码维护性和可重用性。
    
 提高代码的可扩展性,更好的实现父类的方法。
    
  
    
 自然界的所有事物都是优点和缺点并存的,继承的缺点如下:
    
 继承是侵入性的。只要继承,就必须拥有父类的属性和方法。
    
 降低代码灵活性。子类拥有父类的属性和方法后多了些约束。
    
 增强代码耦合性(开发项目的原则为高内聚低耦合)。当父类的常量、变量和方法被修改时,需要考虑子类的修改,有可能会导致大段的代码需要重构。

7.2.2方法的重写

在子类中实现了与父类相同名称的、同样返回同一类型的值、具有相同参数列表的方法时(即该种方式),如果仅通过修改其方法体以实现与其不同的功能(即称为method override),则这种行为被称为method override(亦称method overriding)。当需要为子类实现与其父类不同的功能时(即此时parent class methods无法满足children class的需求),则必须进行method override。

复制代码
 在重写方法时,需要遵循下面的规则:

    
 参数列表必须完全与被重写的方法参数列表相同。
    
 返回的类型必须与被重写的方法的返回类型相同(Java1.5 版本之前返回值类型必须一样,之后的 Java 版本放宽了限制,返回值类型必须小于或者等于父类方法的返回值类型)。
    
 访问权限不能比父类中被重写方法的访问权限更低(public>protected>default>private)。
    
 重写方法一定不能抛出新的检査异常或者比被重写方法声明更加宽泛的检査型异常。例如,父类的一个方法声明了一个检査异常 IOException,在重写这个方法时就不能抛出 Exception,只能拋出 IOException 的子类异常,可以抛出非检査异常。
    
  
    
 另外还要注意以下几条:
    
 重写的方法可以使用 @Override 注解来标识。
    
 父类的成员方法只能被它的子类重写。
    
 声明为 final 的方法不能被重写。
    
 声明为 static 的方法不能被重写,但是能够再次声明。
    
 构造方法不能被重写。
    
 子类和父类在同一个包中时,子类可以重写父类的所有方法,除了声明为 private 和 final 的方法。
    
 子类和父类不在同一个包中时,子类只能重写父类的声明为 public 和 protected 的非 final 方法。
    
 如果不能继承一个方法,则不能重写这个方法。

在上述代码中,在Animal类中定义了一个名为getInfo且返回类型为String的方法。而Cat类继承自该基类。因此Cat继承了Animal中的getInfo方法。然而在Cat里我们又实现了这个接口,并非简单的调用父本的方法。

7.2.3所有类的父类--object类

Java 类库中定义了特殊的一个称为Object的对象,并且同时也是所有其他类型的基础——即它们都继承自它。这表明,在Java编程语言中允许将任何类型的对象赋值给名为Object变量的空间位置上。当定义一个新的类别时(如果未指定其继承关系),默认情况下其继承关系为空。因此以下两个类别表示的内容是一样的

因为 Java 中的每一个类都继承自 Object 类, 所以 Java 对象都可以调用其父类的方法

7.3类的多态

7.3.1方法的重载

Java支持在一个类中声明多个具有相同名称的方法,并且这些方法只需在参数列表上有所区别即可。如果同一个类中包含有两组或两组以上的方法具有相同的名称且其形参列表不相同时,则这种情况则被称作方法重载(overload)。

这些方法实现的功能相同,并且都是基于格式化的输出进行操作。通过参数的不同来识别各个方法,并对每个情况进行相应的格式化处理与输出结果生成。这些功能共同构成了重载的方法体系。在实际应用中,在确定实参类型后会选择对应的处理方式以完成功能执行。

在同一个类内需满足名称一致但参数设置不同的条件才能实现功能相同且操作方式一致的方法称为重载。对于其他属性或附加信息(如返回值类型及修饰符等),这些因素与实现功能相同的重载操作并无关联。

与其采用冗余的方法命名方式(即重复定义相同或类似的功能模块),不如通过优化设计减少命名空间的占用;对于一些功能具有相似性(即实现相同或相近功能的不同模块),若需新增一个功能模块,则应当对其赋予独特的名称以确保系统运行时不会引发冲突)。

复制代码
  
    
 对于int f( ) { }和void( ) { }两个方法,如果这样调用int result = f();,系统可以识别是调用返回值类型为 int 的方法,但 Java 调用方法时可以忽略方法返回值,如果采用如下方法来调用f();,你能判断是调用哪个方法吗?如果你尚且不能判断,那么 Java 系统也会糊涂。在编程过程中有一条重要规则就是不要让系统糊涂,系统一糊涂,肯定就是你错了。因此,Java 里不能用方法返回值类型作为区分方法重载的依据。

7.3.2向上转型

7.3.3向下转型

二者放一起概述

称将某一类元素强制转为另一类元素的过程为类型转换。本节所指的对象类型的转变仅限于具有继承关系的对象。在尝试对不具备继承关系的对象执行强制转码操作时,请注意此操作可能导致 Java 强制转码错误(java.lang.ClassCastException)出现。

复制代码
    Java 语言允许某个类型的引用变量引用子类的实例,而且可以对这个引用变量进行类型转换。

在两个类构成子代与父类关系的前提下,在Java中进行引用类型的转换主要包含两种类型的转换方式:一是向上转型(upcasting),二是向下转型(downcasting)。

复制代码
 1)向上转型

    
 父类引用指向子类对象为向上转型,语法格式如下:
    
 fatherClass obj = new sonClass();
    
 其中,fatherClass 是父类名称或接口名称,
    
 obj 是创建的对象,sonClass 是子类名称。

向转型即为将某一个具体实例的地址直接赋值给其对应父节点引用变量,并无需进行复杂的强制类型转换操作;通过向上转型机制可访问并调用该节点定义的所有公共成员;然而由于该节点仅拥有部分基础功能属性和行为定义,在这种情况下无法访问或调用该节点特有的非公共成员;而具体运行效果则取决于该节点所继承的具体实现细节以及相关的上下文环境设置情况等信息

复制代码
 2)向下转型

    
 与向上转型相反,子类对象指向父类引用为向下转型,语法格式如下:
    
 sonClass obj = (sonClass) fatherClass;
    
 其中,fatherClass 是父类名称,obj 是创建的对象,sonClass 是子类名称。

进行向下转型时可以访问子类的所有成员。
需要注意的是,在某些特定条件下可能会引发异常。
具体来说:

  • 当父类引用对象指向子类实例时,
  • 这种行为(即进行 downward cast)不会出现错误。
  • 但如果 parent reference 实际是指向 parent 类自身,
  • 那么这种行为(downward cast)将存在安全隐患,
  • 虽然从编译器层面并不会报错,
  • 但在运行时可能会触发 Java 强制类型转换异常,
  • 因此建议使用 instanceof 检查来避免此类问题。

结果为:

当引用类型变量被使用时,在访问所引用对象的属性以及方法时,Java 虚拟机将遵循以下绑定规则:

  • 实例方法与引用变量实际引用的对象的方法之间建立了一种关联关系,在这种情况下属于动态关联关系。由于Java虚拟机在运行时会根据实际情况自动决定如何对这些关联关系进行处理。
    例如:animal.eat() 会将 eat() 方法与其所指代的具体对象(如 Cat 类)建立关联关系。
  • 静态方法与引用变量所声明的具体类型的方法之间建立了一种明确的连接关系,在这种情况下属于静态连接关系。由于在编译阶段就已经对这些连接关系进行了预先确定。
    例如:animal.staticEat() 会将其 staticEat() 方法与其声明的具体类型(如 Animal 类)建立明确的连接关系。
  • 成员变量(包括静态成员和实例成员)与其所声明类型的成员之间建立了固定的对应关系,在这种情况中属于静态对应关系。由于在编译阶段就已经完成了这种对应关系的确立。
    例如:animal.nameanimal.staticName 都是其所属的 Animal 类型成员之间的固定对应关系。

7.3.4instanceof关键字

注:改写说明

obj 属于一类对象吗?Class 代表一个类或接口(即接口)。当属于该类(或其子类)实例时,结果 result 会返回true值;否则返回false。

复制代码
 1)声明一个 class 类的对象,判断 obj 是否为 class 类的实例对象(很普遍的一种用法),如以下代码:

    
 Integer integer = new Integer(1);
    
 System.out.println(integer instanceof  Integer);    // true
    
  
    
 2)声明一个 class 接口实现类的对象 obj,判断 obj 是否为 class 接口实现类的实例对象,如以下代码:
    
 Java 集合中的 List 接口有个典型实现类 ArrayList。
    
 public class ArrayList<E> extends AbstractList<E>
    
     implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    
  
    
 所以我们可以用 instanceof 运算符判断 ArrayList 类的对象是否属于 List 接口的实例,如果是返回 true,否则返回 false。
    
 ArrayList arrayList = new ArrayList();
    
 System.out.println(arrayList instanceof List);    // true
    
  
    
 或者反过来也是返回 true
    
 List list = new ArrayList();
    
 System.out.println(list instanceof ArrayList);    // true
    
  
    
 3)obj 是 class 类的直接或间接子类
    
 我们新建一个父类 Person.class,代码如下:
    
 public class Person {
    
 }
    
  
    
 创建 Person 的子类 Man,代码如下:
    
 public class Man extends Person {
    
 }

我们定义了一个 Animal 基类,并在其下设 Cow 和 Sheep 亚类;在 Test 类的主要函数中生成该 Sheeps 实例并将其作为参数传递给动物调用方法

7.4抽象类与接口

7.4.1抽象类与抽象方法

Java语言将各类划分为两类:具体系数与抽象类。在之前的章节中所接触过的各类都是具体系数。本节将重点讲解抽象类的相关知识

在面向对象编程的思想框架下,每个具体事物都由相应的类别来描述;然而并非所有类别都能够有效地描述具体的实例;若某个类别无法提供足够的细节以准确描述特定的对象,则该类别被定义为抽象类型。

其中,该类或该方法被称为抽象的;class_name 代表所指的具体抽象类名称;method_name 代表所指的具体抽象方法名称;parameter-list 列出了具体的方法参数列表。

如果某个方法被赋予abstract修饰词,则表示它是抽象类。这类抽象类仅作声明不具实现内容。值得注意的是,在Java中abstract关键字仅可用于定义普通类的方法,并禁止应用于静态方法或构造函数。

抽象方法的 3 个特征如下:

  1. 抽象方法无体。
  2. 抽象方法只能存在于抽象类中。
  3. 当子类继承父类时,需继承父类全部的抽象方法。

请注意,在为抽象方法声明 abstract 标签时(或称作 abstract),不允许同时声明 private 标签。这是因为所有抽象方法都必须由其子类重新实现该功能。然而,在这种情况下(即为该抽象方法声明了 private 标签),则会导致其所有继承者都无法实现该功能

运行结果:

7.4.2接口的声明及实现

源自多个类的共性部分而形成的模板称为abstract类型(Abstract Class)。若对这种abstract过程实施得更为深入,则可发展出一种更为特别的abstract类型即interface(Interface)。在 Java 中interface被视为一个至关重要的核心概念。它可被视为一种特殊类型的类别其主要区别在于所有interface都没有具体的执行体。由一系列全局常量及公共 abstract 方法构成。

定义接口

  • public标识为接口提供了访问权限修饰符;若未指定修饰符,则将默认值应用于该接口,在这种情况下其可访问范围仅限于所属包内。
  • interface_name定义为接口名称;命名遵循Java命名规范(从语义角度而言),建议由多个有意义单词首字母大写的组合而成;若仅从语法角度考虑,则只需保证合法即可。
  • extends标识为继承关系实现;
  • interface1_name指定被继承的目标接口名称;
  • constant_name用于标识常量类型;一般指static和final型变量;
  • returnType定义为返回类型;
  • parameter_list描述方法参数列表,在此类别中操作员无返回体。

该接口在声明、变量和方法上施加了诸多约束,这些约束作为其特性的列举归纳。

  • 使用了public访问权限的接口允许任何类调用;未指定public访问权限时,默认其可见性仅限于所属的小包。
  • 无需附加修饰符即可声明方法,在接口中所定义的方法将隐式地被视为公有(public)并抽象(abstract)。
  • 在 Java 接口中所定义的所有变量本质上都是静态且不可变(constancy);因此在接口中所定义的所有变量均需预先初始化。
  • 没有提供构造函数的方法无法被实例化(instantiated)。

实现接口

该系统架构设计旨在提供灵活的功能扩展能力;单一类型实体最多可同时与多个接口协同工作;通过 extends 关键字进行基线扩展;而通过 implements 关键字完成具体功能;这一机制允许单一类型实体整合多样化的功能需求;从而克服了单继承系统在灵活性方面的局限性。

在设计这个系统时,在IMath接口上仅声明了两个未被实现的方法,在这些方法的实际功能需由其对应的具体实施类来完成。具体来说,在MathClass实体内部设置了两个私有属性,并为它们设定初始值;同时为该实体提供了必要的构造函数。作为实现了MathClass接口的对象实例而言,在其生命周期内将自动触发所有声明在其上的成员方法。为了完成后续测试任务,在测试阶段将使用上述对象实例;并通过依次调用其相关成员函数来完成各项操作需求。

7.4.3多重继承

所谓的多重继承是指一个类能够从多个父类中同时继承行为与属性。然而,在已知的事实中,在Java编程语言中,默认情况下仅限于单层 inheritance. 在某些情况下人们可能会质疑:是否将multi inheritance引入系统是明智之举?不过,在某些特定场景下实现multi inheritance确实是有必要的。例如在遗传领域中我们可以观察到这种情况:我们既承袭了父亲的行为与特征又承袭了母亲的行为与特征. 然而令人庆幸的是Java语言非常宽容它为我们提供了两种途径来实现这一需求:通过接口或者使用内部类. 综上所述,在合理应用的情况下掌握并灵活运用这些机制有助于提高代码的质量.

7.4.4区分抽象类与接口

Java语言中采用抽象类与接口作为定义抽象概念的两种机制。
由于它们的存在,在Java语言中实现了强大的面向对象能力。
两者之间在支持抽象概念方面具有显著相似性,并且能够相互替代。
但是仍然存在一些区别。

虽然抽象类和接口之间存在明显的相似之处,并且有时可以在程序设计中相互替代使用的位置关系上具有一定的重叠性;但这并不意味着它们之间的差异就可以被忽视或弥补。以下将从语法结构层和设计思想层两个维度详细阐述抽象类与接口的区别与关联。

7.5访问控制

7.5.1访问控制符

In the Java programming language, several access modifiers are provided, including public, private, protected, final, abstract, static, transient, and volatile. These modifiers can be used as class modifiers, variable modifiers, or method modifiers.

借助访问控制修饰符可以实现对对象私有属性的限制,从而带来三个关键的优势。

  • 确保不会被非法获取封装数据。
    • 有助于维护或保障数据完整性。
    • 在修改类的私有实现细节时,则可防止在整个应用程序中出现一系列连锁反应。

访问修饰符用于指示类、属性或方法是否允许外部代码进行访问与调用。对于类而言,默认为不可见或public修饰符;而对于方法与属性,则可分别采用public、private、protected或friendly这4种访问修饰符(其中friendly表示未作特殊限制时默认采用)。

1. private

private修饰的类成员仅限于本类的方法进行访问与修改,并不对外 accessible 或继承子类可见;由此可见此修饰符所具有的极强保护功能。具体而言我们可以设想一个名为PhoneCard的电话卡类别显然每个电话卡都应包含一个密钥字段因而我们将该密钥域明确指定为私有字段类型以确保其信息的安全性并防止外部或其他子类对象对其直接访问或引用。

2. friendly(默认)

当某个类不带有明确的权限修饰符时,默认会拥有某种权限特性。

同样地,在Java编程语言中,默认情况下如果某个类型未声明访问权限,则该类型的所有成员均被视为该类型的友元(friend),即具有友元属性。此外,在同一目录下的所有类型都被视为同一个整体(即为同一个包)。因此,在Java项目开发过程中,默认情况下每个目录下的类型都属于同一个逻辑单位(即为同一个包)。这意味着开发者应将他们自定义的类型组织到同一目录下,并使这些代码无需附加修饰即可运行。

3. protected

通过使用protected关键字进行修饰的Java类型成员,在继承体系中将允许三个层次的对象进行访问:包括本类本身、同一包内的其他相关类型以及子代类型。这种设计的核心功能在于赋予子代类型对外部环境进行属性和方法操作的能力,并且在此基础之上实现与其他外部模块的有效交互。如果不使用,则默认仅限于内部类型。

4. public

当一个class声明为public时,则称该class获得了与其他package内的class进行访问的权利;若某package内的other classes通过import语句引入公共class,则可在外部成功调用与引用该class.

将该类的方法设置为public的行为实质上构成了对外提供的接口。通过这种方式不仅实现了信息的安全性保护机制而且也防止了外部代码直接访问或修改内部数据从而阻止了外部代码对系统的非法操作这正是数据封装这一核心概念的本质体现作为Java程序的基本规范之一所有程序都需要在其主体中使用public修饰符指定其主类以确保系统的安全性与完整性

在 StudentTest 类中,“stu.idNumber="043765290763137806";”这一代码行将会触发 “The field User.password is not visible”错误信息。建议对该代码行添加注释标记“// 用于隐藏字段idNumber”的同时重新运行 StudentTest.java 文件。观察到的结果如下:

7.5.2java类包

编写 Java 程序时, 随着程序架构逐渐增大, 类的数量显著增加, 便发现了管理与维护各类名称所带来的一系列麻烦. 尤其是同一名称的问题容易出现. 有时开发人员可能会将处理同一类型问题相关的类组织到同一个目录中.

软件包能够将类组织成小型集合(类似于文件夹),通过这种方式实现了对类的隐藏管理并消除了名称上的潜在冲突。软件包提供了更广泛的范围来保护各类别及其相关数据与操作。在同一个软件包内部你可以定义新的类别,在外部代码块中则无法访问这些内部类别。这种设计实现了一种独立性与私密性管理机制。

包的 3 个作用如下:

  1. 区分相同名称的类。
  2. 能够较好地管理大量的类。
  3. 控制访问范围。

在Java编程中,默认情况下程序位于默认包中,在开发时通常建议为项目中的各个类和接口创建独立的包以提高代码可维护性。正确的位置是代码开头处,在每个源文件只能声明一个包的情况下,默认情况下程序位于默认包中,并且该机制同样适用于各类元素如类、接口、枚举以及注释的管理与组织。为了实现清晰的对象-oriented设计目标,在编写Java代码时应遵循正确的命名规范标准。

package 包名;

Java 包的命名规则如下:

  • 所有软件包名称必须完全使用小写字母表示,并且即使包含多个单词也需全部采用小写字母。
  • 当软件包名称包含多级结构时,则以‘.’作为各层级之间的分隔符。
  • 软件包名称通常默认采用反转的域名作为主名称,并避免以‘www.’开头。
  • 自定义类别的软件包不得以‘java’作为其首字母标识。

当源文件未声明任何包时,默认情况下,类、接口、枚举以及注释类型的文件会被放置在一个名为默认的匿名包内。在实际的企业开发环境中,默认情况下很少将类置于默认的空包中。

复制代码
 包导入

    
 如果使用不同包中的其它类,需要使用该类的全名(包名+类名)。代码如下:
    
 example.Test test = new example.Test();
    
  
    
 其中,example 是包名,Test 是包中的类名,test 是类的对象。
    
  
    
 为了简化编程,Java 引入了 import 关键字,import 可以向某个 Java 文件中导入指定包层次下的某个类或全部类。import 语句位于 package 语句之后,类定义之前。一个 Java 源文件只能包含一个 package 语句,但可以包含多个 import 语句。
    
  
    
 使用 import 导入单个类的语法格式如下:
    
 import 包名+类名;
    
  
    
 上面语句用于直接导入指定类,例如导入前面的 example.Test 类,代码如下:
    
 import example.Test;
    
  
    
 使用 import 语句导入指定包下全部类的用法按如下:
    
 import example.*;
    
  
    
 上面 import 语句中的星号(*)只能代表类,不能代表包,表明导入 example 包下的所有类。
复制代码
 系统包

    
 Java SE 提供了一些系统包,其中包含了 Java 开发中常用的基础类。在 Java 语言中,开发人员可以自定义包,也可以使用系统包,常用的系统包如表 1 所示。
    
  
    
 表1 Java中常用的系统包
    
 包	说明
    
 java.lang	Java 的核心类库,包含运行 Java 程序必不可少的系统类,如基本数据类型、基本数学函数、
    
 字符串处理、异常处理和线程类等,系统默认加载这个包
    
 java.io	Java 语言的标准输入/输出类库,如基本输入/输出流、文件输入/输出、过滤输入/输出流等
    
 java.util	包含如处理时间的 Date 类,处理动态数组的 Vector 类,以及 Stack 和 HashTable 类
    
 java.awt	构建图形用户界面(GUI)的类库,低级绘图操作 Graphics 类、图形界面组件和布局管理
    
 (如 Checkbox 类、Container 类、LayoutManger 接口等),以及用户界面交互控制和事
    
 件响应(如 Event 类)
    
 java.awt.image	处理和操纵来自网上的图片的 Java 工具类库
    
 java.wat.peer	很少在程序中直接用到,使得同一个 Java 程序在不同的软硬件平台上运行
    
 java.net	实现网络功能的类库有 Socket 类、ServerSocket 类
    
 java.lang.reflect	提供用于反射对象的工具
    
 java.util.zip	实现文件压缩功能
    
 java.awt.datatransfer	处理数据传输的工具类,包括剪贴板、字符串发送器等
    
 java.sql	实现 JDBC 的类库
    
 java.rmi	提供远程连接与载入的支持
    
 java. security	提供安全性方面的有关支持
    
 读者现在只需对这些包有一个大致印象即可,随着教程后面的介绍,读者会逐渐熟悉它们的用法。

7.5.3final关键字

Java 中,“final”一词的意义即是最终形态的标识符。它也可视为终止标记,在程序运行中一旦被赋予该关键字,则表明所指对象处于不能再变更的状态。该标识符体现出对象属性无法被进一步修改的特点。当应用于类、方法或变量时,“final”一词的意义各不相同。然而,在本质上它们的作用一致——都是为了限制某些操作的可能性以确保程序稳定性和可预测性。与C# 中的sealed关键字相比,则多了几分确定性与终结性特质。

使用 final 关键字声明类、变量和方法需要注意以下几点:

  • final前缀用于变量表示其值不可更改,则称该变量为常量。
  • method不可重写(若在一个子class中创建了一个与parent class具有相同name、return type及parameters的方法,则仅其实现方式有所差异以实现不同的功能此操作被称为method重写或method覆盖此处略过细节后续课程将深入分析此概念)。
  • final前缀用于class标识其实现无法继承任何子class。
复制代码
 final 修饰变量

    
 final 修饰的变量即成为常量,只能赋值一次,但是 final 所修饰局部变量和成员变量有所不同。
    
 final 修饰的局部变量必须使用之前被赋值一次才能使用。
    
 final 修饰的成员变量在声明时没有赋值的叫“空白 final 变量”。空白 final 变量必须在构造方法或静态代码块中初始化。
    
 注意:final 修饰的变量不能被赋值这种说法是错误的,严格的说法是,final 修饰的变量不可被改变,一旦获得了初始值,该 final 变量的值就不能被重新赋值。

上述代码中第 4 行及 6 行均用于声明局部常量性质,在具体实现中需注意以下事项:首先,在第 4 行仅声明了局部变量却不进行赋值操作(见代码中的第 6 行),这种做法并不推荐;建议在声明时同时进行初始化操作以避免潜在的问题。此外,在代码的不同部分分别进行了多处局部常量的定义:具体而言,在代码的 13 到 17 行中涉及了多个成员级常量设置。其中,在 13 和 14 行定义的是实例级别的非静态常量;若为无值且final类型的变量(见代码第 14 行),则应在构造函数中完成初始化配置(参考代码中的第 27 行)。而对于位于类体内的静态资源管理部分,则在 16 和 17 行处进行了相应的静态资源管理设置:这些设置主要针对无值且final类型的静态资源管理需求,并应在静态块阶段完成必要的初始化工作(如参考代码中的第 21 行)。

另外,在任何情况下常量只能被赋值一次;查看代码第29行处理b变量的赋值;由于在上一步骤中b已经被赋值过一次;因此这将导致编译器报错。

复制代码
 final修饰方法

    
 final 修饰的方法不可被重写,如果出于某些原因,不希望子类重写父类的某个方法,则可以使用 final 修饰该方法。
    
  
    
 Java 提供的 Object 类里就有一个 final 方法 getClass(),因为 Java 不希望任何类重写这个方法,所以使用 final 把这个方法密封起来。但对于该类提供的 toString() 和 equals() 方法,都允许子类重写,因此没有使用 final 修饰它们。
    
  
    
 下面程序试图重写 final 方法,将会引发编译错误。
    
 public class FinalMethodTest {
    
     public final void test() {
    
     }
    
 }
    
 class Sub extends FinalMethodTest {
    
     // 下面方法定义将出现编译错误,不能重写final方法
    
     public void test() {
    
     }
    
 }
    
 上面程序中父类是 FinalMethodTest,该类里定义的 test() 方法是一个 final 方法,如果其子类试图重写该方法,将会引发编译错误。
    
  
    
 对于一个 private 方法,因为它仅在当前类中可见,其子类无法访问该方法,所以子类无法重写该方法——如果子类中定义一个与父类 private 方法有相同方法名、相同形参列表、相同返回值类型的方法,也不是方法重写,只是重新定义了一个新方法。因此,即使使用 final 修饰一个 private 访问权限的方法,依然可以在其子类中定义与该方法具有相同方法名、相同形参列表、相同返回值类型的方法。
    
  
    
 下面程序示范了如何在子类中“重写”父类的 private final 方法。
    
 public class PrivateFinalMethodTest {
    
     private final void test() {
    
     }
    
 }
    
 class Sub extends PrivateFinalMethodTest {
    
     // 下面的方法定义不会出现问题
    
     public void test() {
    
     }
    
 }
    
 上面程序没有任何问题,虽然子类和父类同样包含了同名的 void test() 方法,但子类并不是重写父类的方法,因此即使父类的 void test() 方法使用了 final 修饰,子类中依然可以定义 void test() 方法。
复制代码
 final修饰类

    
 final 修饰的类不能被继承。当子类继承父类时,将可以访问到父类内部数据,并可通过重写父类方法来改变父类方法的实现细节,这可能导致一些不安全的因素。为了保证某个类不可被继承,则可以使用 final 修饰这个类。
    
  
    
 下面代码示范了 final 修饰的类不可被继承。
    
 final class SuperClass {
    
 }
    
 class SubClass extends SuperClass {    //编译错误
    
 }
    
 因为 SuperClass 类是一个 final 类,而 SubClass 试图继承 SuperClass 类,这将会引起编译错误。
复制代码
 final 修饰符使用总结

    
 1. final 修饰类中的变量
    
 表示该变量一旦被初始化便不可改变,这里不可改变的意思对基本类型变量来说是其值不可变,而对对象引用类型变量来说其引用不可再变。其初始化可以在两个地方:一是其定义处,也就是说在 final 变量定义时直接给其赋值;二是在构造方法中。这两个地方只能选其一,要么在定义时给值,要么在构造方法中给值,不能同时既在定义时赋值,又在构造方法中赋予另外的值。
    
 2. final 修饰类中的方法
    
 说明这种方法提供的功能已经满足当前要求,不需要进行扩展,并且也不允许任何从此类继承的类来重写这种方法,但是继承仍然可以继承这个方法,也就是说可以直接使用。在声明类中,一个 final 方法只被实现一次。
    
 3. final 修饰类
    
 表示该类是无法被任何其他类继承的,意味着此类在一个继承树中是一个叶子类,并且此类的设计已被认为很完美而不需要进行修改或扩展。
    
  
    
 对于 final 类中的成员,可以定义其为 final,也可以不是 final。而对于方法,由于所属类为 final 的关系,自然也就成了 final 型。也可以明确地给 final 类中的方法加上一个 final,这显然没有意义。

7.6内部类

7.6.1成员内部类

复制代码
 在类内部可定义成员变量和方法,且在类内部也可以定义另一个类。如果在类 Outer 的内部再定义一个类 Inner,此时类 Inner 就称为内部类(或称为嵌套类),而类 Outer 则称为外部类(或称为宿主类)。

    
  
    
 内部类可以很好地实现隐藏,一般的非内部类是不允许有 private 与 protected 权限的,但内部类可以。内部类拥有外部类的所有元素的访问权限。
    
  
    
 内部类可以分为:实例内部类、静态内部类和成员内部类,每种内部类都有它特定的一些特点,本节先详细介绍一些和内部类相关的知识。
    
  
    
 在类 A 中定义类 B,那么类 B 就是内部类,也称为嵌套类,相对而言,类 A 就是外部类。如果有多层嵌套,例如类 A 中有内部类 B,而类 B 中还有内部类 C,那么通常将最外层的类称为顶层类(或者顶级类)。

内部类的特点如下:

  1. 内部类型依然是一个独立存在的类别,在经过编译后会生成独立的.class文件。尽管其名称带有外部类别前缀以及附带$符号标识。
  2. 内部类型无法通过常规途径进行访问。由于它作为外部类型的成员存在,在不使用特殊机制的情况下仍可自由获取外部类型的成员变量(无论是private还是protected)。
  3. 当内部类型被声明为静态时,则不具备对外部类型成员变量的自由访问权限;此时只能获取外部类型中的静态成员变量。
复制代码
 有关内部类的说明有如下几点。

    
 外部类只有两种访问级别:public 和默认;内部类则有 4 种访问级别:public、protected、 private 和默认。
    
 在外部类中可以直接通过内部类的类名访问内部类。
    
 InnerClass ic = new InnerClass();    // InnerClass为内部类的类名
    
 在外部类以外的其他类中则需要通过内部类的完整类名访问内部类。
    
 Test.InnerClass ti = newTest().new InnerClass();    // Test.innerClass是内部类的完整类名
    
 内部类与外部类不能重名。
    
  
    
 提示:内部类的很多访问规则可以参考变量和方法。另外使用内部类可以使程序结构变得紧凑,但是却在一定程度上破坏了 Java 面向对象的思想。

7.6.2局部内部类

复制代码
 局部内部类是指在一个方法中定义的内部类。示例代码如下:

    
 纯文本复制
    
 public class Test {
    
     public void method() {
    
     class Inner {
    
         // 局部内部类
    
     }
    
     }
    
 }
    
 局部内部类有如下特点:
    
  
    
 1)局部内部类与局部变量一样,不能使用访问控制修饰符(public、private 和 protected)和 static 修饰符修饰。
    
  
    
 2)局部内部类只在当前方法中有效。
    
 3)局部内部类中不能定义 static 成员。
    
  
    
 4)局部内部类中还可以包含内部类,但是这些内部类也不能使用访问控制修饰符(public、private 和 protected)和 static 修饰符修饰。
    
  
    
 5)在局部内部类中可以访问外部类的所有成员。
    
  
    
 6)在局部内部类中只可以访问当前方法中 final 类型的参数与变量。如果方法中的成员与外部类中的成员同名,则可以使用 <OuterClassName>.this.<MemberName> 的形式访问外部类中的成员。

7.6.3匿名内部类

复制代码
 匿名类是指没有类名的内部类,必须在创建时使用 new 语句来声明类。其语法形式如下:

    
  
    
 new <类或接口>() {
    
     // 类的主体
    
 };

匿名类有两种实现方式:

  • 继承一个类,重写其方法。
  • 实现一个接口(可以是多个),实现其方法
复制代码
  
    
 1)匿名类和局部内部类一样,可以访问外部类的所有成员。如果匿名类位于一个方法中,则匿名类只能访问方法中 final 类型的局部变量和参数。
    
 public static void main(String[] args) {
    
     int a = 10;
    
     final int b = 10;
    
     Out anonyInter = new Out() {
    
     void show() {
    
         // System.out.println("调用了匿名类的 show() 方法"+a);    // 编译出错
    
         System.out.println("调用了匿名类的 show() 方法"+b);    // 编译通过
    
     }
    
     };
    
     anonyInter.show();
    
 }
    
 从 Java 8 开始添加了 Effectively final 功能,在 Java 8 及以后的版本中代码第 6 行不会出现编译错误,详情可点击《Java8新特性之Effectively final》进行学习。
    
  
    
 2)匿名类中允许使用非静态代码块进行成员初始化操作。
    
 Out anonyInter = new Out() {
    
     int i; {    // 非静态代码块
    
     i = 10;    //成员初始化
    
     }
    
     public void show() {
    
     System.out.println("调用了匿名类的 show() 方法"+i);
    
     }
    
 };
    
 3)匿名类的非静态代码块会在父类的构造方法之后被执行。

7.6.4静态内部类

复制代码
 静态内部类是指使用 static 修饰的内部类。示例代码如下:

    
 public class Outer {
    
     static class Inner {
    
     // 静态内部类
    
     }
    
 }
    
 上述示例中的 Inner 类就是静态内部类。静态内部类有如下特点。
    
  
    
 1)在创建静态内部类的实例时,不需要创建外部类的实例。
    
 public class Outer {
    
     static class Inner {
    
     }
    
 }
    
 class OtherClass {
    
     Outer.Inner oi = new Outer.Inner();
    
 }
    
 2)静态内部类中可以定义静态成员和实例成员。外部类以外的其他类需要通过完整的类名访问静态内部类中的静态成员,如果要访问静态内部类中的实例成员,则需要通过静态内部类的实例。
    
 public class Outer {
    
     static class Inner {
    
     int a = 0;    // 实例变量a
    
     static int b = 0;    // 静态变量 b
    
     }
    
 }
    
 class OtherClass {
    
     Outer.Inner oi = new Outer.Inner();
    
     int a2 = oi.a;    // 访问实例成员
    
     int b2 = Outer.Inner.b;    // 访问静态成员
    
 }
    
 3)静态内部类可以直接访问外部类的静态成员,如果要访问外部类的实例成员,则需要通过外部类的实例去访问。
    
 纯文本复制
    
 public class Outer {
    
     int a = 0;    // 实例变量
    
     static int b = 0;    // 静态变量
    
     static class Inner {
    
     Outer o = new Outer;
    
     int a2 = o.a;    // 访问实例变量
    
     int b2 = b;    // 访问静态变量
    
     }
    
 }

7.6.5内部类的继承

复制代码
 实例内部类是指没有用 static 修饰的内部类,有的地方也称为非静态内部类。示例代码如下:

    
 public class Outer {
    
     class Inner {
    
     // 实例内部类
    
     }
    
 }
    
 上述示例中的 Inner 类就是实例内部类。实例内部类有如下特点。
    
  
    
 1)在外部类的静态方法和外部类以外的其他类中,必须通过外部类的实例创建内部类的实例。
    
 public class Outer {
    
     class Inner1 {
    
     }
    
     Inner1 i = new Inner1(); // 不需要创建外部类实例
    
     public void method1() {
    
     Inner1 i = new Inner1(); // 不需要创建外部类实例
    
     }
    
     public static void method2() {
    
     Inner1 i = new Outer().new inner1(); // 需要创建外部类实例
    
     }
    
     class Inner2 {
    
     Inner1 i = new Inner1(); // 不需要创建外部类实例
    
     }
    
 }
    
 class OtherClass {
    
     Outer.Inner i = new Outer().new Inner(); // 需要创建外部类实例
    
 }
    
 2)在实例内部类中,可以访问外部类的所有成员。
    
 public class Outer {
    
     public int a = 100;
    
     static int b = 100;
    
     final int c = 100;
    
     private int d = 100;
    
     public String method1() {
    
     return "实例方法1";
    
     }
    
     public static String method2() {
    
     return "静态方法2";
    
     }
    
     class Inner {
    
     int a2 = a + 1; // 访问public的a
    
     int b2 = b + 1; // 访问static的b
    
     int c2 = c + 1; // 访问final的c
    
     int d2 = d + 1; // 访问private的d
    
     String str1 = method1(); // 访问实例方法method1
    
     String str2 = method2(); // 访问静态方法method2
    
     }
    
     public static void main(String[] args) {
    
     Inner i = new Outer().new Inner(); // 创建内部类实例
    
     System.out.println(i.a2); // 输出101
    
     System.out.println(i.b2); // 输出101
    
     System.out.println(i.c2); // 输出101
    
     System.out.println(i.d2); // 输出101
    
     System.out.println(i.str1); // 输出实例方法1
    
     System.out.println(i.str2); // 输出静态方法2
    
     }
    
 }
    
 提示:如果有多层嵌套,则内部类可以访问所有外部类的成员。
    
  
    
 3)在外部类中不能直接访问内部类的成员,而必须通过内部类的实例去访问。如果类 A 包含内部类 B,类 B 中包含内部类 C,则在类 A 中不能直接访问类 C,而应该通过类 B 的实例去访问类 C。
    
  
    
 4)外部类实例与内部类实例是一对多的关系,也就是说一个内部类实例只对应一个外部类实例,而一个外部类实例则可以对应多个内部类实例。
    
  
    
 如果实例内部类 B 与外部类 A 包含有同名的成员 t,则在类 B 中 t 和 this.t 都表示 B 中的成员 t,而 A.this.t 表示 A 中的成员 t。
    
 public class Outer {
    
     int a = 10;
    
     class Inner {
    
     int a = 20;
    
     int b1 = a;
    
     int b2 = this.a;
    
     int b3 = Outer.this.a;
    
     }
    
     public static void main(String[] args) {
    
     Inner i = new Outer().new Inner();
    
     System.out.println(i.b1); // 输出20
    
     System.out.println(i.b2); // 输出20
    
     System.out.println(i.b3); // 输出10
    
     }
    
 }
    
 5)在实例内部类中不能定义 static 成员,除非同时使用 final 和 static 修饰。

7.7小结

关注念安8

全部评论 (0)

还没有任何评论哟~