Thinking in Java学习杂记(第7章)
将一个方法调用同一个方法主体连接到一起就称为“绑定”(Binding)。若在程序运行以前执行绑定,就叫做“早期绑定”。而Java中绑定的所有方法都采用后期绑定技术,除非一个方法已被声明成final。后期绑定意味着绑定是在运行期间进行,以对象的类型为基础。
Java中提供了一种名为"抽象方法"的机制。它属于一种不完整的方法,只有一个声明,没有方法主体。抽象方法声明时采用的语法如下:
abstract void X();
包含了抽象方法的一个类叫做“抽象类”。如果一个类里包含了一个或多个抽象方法,类就必须指定成abstract(抽象)。否则,编译器将会向我们报告出一条出错信息。
如果从一个抽象类继承,而且想生成新类型的一个对象,就必须为基础类中所有抽象方法提供方法定义。如果不这样做,则派生类也会是抽象的,而且编译器会强迫我们用abstract关键字来标志那个类。即使不包括任何abstract方法,亦可将一个类声明成“抽象类”。如果一个类没必要拥有任何抽象方法,而且我们想禁止那个类的所有实例,这种能力就会显得非常重要。
"interface"(接口)关键字使抽象的概念更深入了一层。可以将其视为一个“纯”抽象类,它允许创建者规定一个类的基本形式:方法名、自变量列表以及返回类型,但不规定方法主体。接口也包含了基本数据类型的数据成员,但它们都默认为static和final。接口只提供一种形式,并不提供实施细节。
与类的继承使用extends
关键字不同,为了实现接口,要使用implements
关键字。
在实现了一个接口之后,就获得了一个普通类,可用标准方式对其进行扩展。可决定将一个接口中的方法声明明确定义为"public"。但是,即使不明确定义,它们也会默认为public。所以在实现一个接口的时候,来自接口的方法必须定义成public。此处需要注意的是,为了满足向上造型(upcasting),我们对于子类的访问限制必须小于等于基类的限制。
对于类来说,Java只能但继承,但是却可以同时实现多个接口。具体的方法就是将所有接口名置于implments关键字之后,并用逗号分割它们。可根据需要使用多个接口,而且每个接口都会成为一个独立的类型,可对其进行上溯造型。当我们需要同时继承类与实现接口时,需要将继承类写在实现的接口之前。
class A extends B implements C, D{}
接口同样也是可以继承的并且能够多继承
interface A extends B, C{}
接口中定义的字段会自动具有static和final属性。它们不能时“空白final”,但可初始化成非常数表达式。由于字段是static的,所以它们会在首次装载类之后、以及首次访问任何字段之前获得初始化。
字段并不是接口的一部分,而是保存在那个接口的static存储区域中。
在Java中,可将一个类定义置入另一个类。这就叫做“内部类”。利用它可对那些逻辑上相互联系的类进行分组,并可控制一个类在另一个类里的“可见性”。同时,若想在除外部类非static方法内部之外的任何地方生成内部类的一个对象,必须将那个对象的类型设为“外部类名.内部类名”。
public class Test {
public Test(){
System.out.println("construct Test");
}
class B{
public B(){
System.out.println("construct B");
}
}
public B constructB(){
return new B();
}
public static void main(String[] args) {
B b = new Test().constructB();
}
}
// output
/*
construct Test
construct B
*/
在需要隐藏具体实现时,使用内部类是非常有效的。因为,在上溯造型之后,不能通过造型后的字段或方法来访问内部类。这就有效的避免了客户程序的非法访问。
为了能够更好地隐藏细节,Java提供了匿名内部类的设施。使用内部匿名类之前,我们首先需要定义一个名字相同的实体类,以便我们能够在其上进行个性化修改。
public class Test {
public Test(){
System.out.println("construct Test");
}
public static void main(String[] args) {
Person p=new Person(){
public void eat(){
System.out.println("eat apple");
}
};
p.eat();
}
}
class Person{
public void eat(){
System.out.println("eat egg");
}
}
// output
/*
eat apple
*/
需要注意的是:匿名类不能拥有构建器,同时,若试图定义一个匿名内部类,并想使用在匿名内部类外部定义一个对象,则编译器要求外部对象为final属性。
在static应用于内部类时,需记住内部类的对象默认持有创建它的那个封装类的一个对象的句柄。static内部类意味着:
- 为创建一个static内部类的对象,我们不需要一个外部类对象。
- 不能从static内部类的一个对象中访问一个外部类对象。
但是,由于static成员只能位于一个类的外部级别,所以内部类不可拥有static数据或static内部类。倘若为了创建内部类的对象而不需要创建外部类的一个对象,那么可将所有东西都设为static。为了能正常工作,同时也必须将内部类设为static。
public class Test {
public Test(){
System.out.println("construct Test");
}
private static class innerTest{
public innerTest(){
System.out.println("construct innerTest");
}
}
public static innerTest getInnerTest(){
return new innerTest();
}
public static void main(String[] args) {
getInnerTest();
}
}
// output
/*
construct innerTest
*/
通常,我们不会在一个接口中设置任何代码,但static内部类可以成为接口的一部分。由于类是静态的,所以它不会违反接口的规则--static内部类只位于接口的命名空间内部。
interface Test {
static class Inner{
public Inner(){
System.out.println("construct inner");
}
}
}
class A implements Test{
public static void main(String[] args) {
Inner inner = new Inner();
}
}
// output
/*
construct inner
*/
有些时候,我们想告诉其他某些对象创建它某个内部类的一个对象,为达到这个目的,必须在new表达式中提供指向其他外部类对象的一个句柄。
public class Test {
public Test(){
System.out.println("construct Test");
}
class Inner{
public Inner(){
System.out.println("construct inner");
}
}
public static void main(String[] args) {
Test t = new Test();
Inner inner = t.new Inner();
}
}
// output
/*
construct Test
construct inner
*/
为直接创建一个内部类对象,必须利用外部类的一个对象来说生成内部类的一个对象。即,除非拥有外部类的一个对象,否则不可能创建内部类的一个对象。这是由于内部类的对象已同创建它的外部类的对象“默默”地连接到一起。然而,如果生成一个static内部类,就不需要指向外部类对象的一个句柄。
由于内部类构建器必须同封装类对象的一个句柄联系到一起,所以从一个内部类继承的时候,情况会变得更加复杂。这儿的问题是封装类的“秘密”句柄必须获得初始化,而且在衍生类中不再有一个默认的对象可以连接。解决这个问题的办法是采用一种特殊的语法,明确建立这种连接。
public class Test extends A.B{
Test(A a){
a.super();
}
public static void main(String[] args) {
new Test(new A());
}
}
class A{
A(){
System.out.println("construct A");
}
class B{
B(){
System.out.println("construct B");
}
}
}
// output
/*
construct A
construct B
*/
我们能否像重写函数一样重写内部类呢?这一概念实际并不能做任何事情。
public class Test extends A{
public class B{
public B(){
System.out.println("construct B*");
}
}
public static void main(String[] args) {
new Test();
}
}
class A{
A(){
System.out.println("construct A");
new B();
}
class B{
B(){
System.out.println("construct B");
}
}
}
// output
/*
construct A
construct B
*/
这个例子揭示出当我们从外部类继承的时候,没有任何额外的内部类继续下去。当然,如果我们需要覆盖内部类的方法,我们也可以直接继承内部类。
我们知道每个类都会生成一个.class文件,用于容纳与如何创建这个类型的对象有关的所有信息(这种信息产生了一个名为Class对象的元类),同样,内部类也必须生成相应的.class文件,用来容纳与它们的Class对象有关的信息。这些文件或类的名字遵守一种严格的形式:先是封装类的名字,再跟随一个$,再跟随内部类的名字。如,假设一个类A,内部包含类B,则会生成的.class文件包括: A.class和 A$B.class
如果内部类是匿名的,那么编译器会简单地生成数字,把它们作为内部标识符使用。若内部类嵌套与其它内部类中,则它们的名字会简单地追加在一个$以及外部标识符后面。
内部类的存在让我们能够更好地学习控制框架。一个“应用程序框架”是指一个或一系列类,它们专门设计用来解决特定类型的问题。为应用应用程序框架,我们可以从一个或多个类继承,并覆盖其中的部分方法。在覆盖方法中编写的代码用于定制由那些应用程序框架提供的常规方案,以便解决自己的实际问题。“控制框架”属于应用程序框架的一种特殊类型,受到对事件响应的需要的支配,主要用来响应事件的一个系统叫做“由事件驱动的系统”。
在进行事件监听的时候,我们需要将发生变化的东西同没有发生变化的东西区分开,“改变的意图”造成了各类Event对象的不同行动。通过创建不同的Event子类,可以表达出不同的行动。这正是内部类大显身手的地方。它们允许我们做两件事情:
- 在单独一个类里表达一个控制框架应用的全部实施细节,从而完整地封装与那个实施有关的所有东西。内部类用于表达多种不同类型action(),它们用于解决实际的问题。除此之外,通过为内部类添加private关键字,可以将实施细节完全隐藏起来,可以安全地修改。
- 内部类使我们具体的实施变得更加巧妙,因为能方便地访问外部类的任何成员。
对于一个复杂的对象,构建器的调用将遵守下面的顺序:
- 调用基础构建器。这个步骤会不断重复下去,首先得到构建的是分级结构的根部,然后是下一个衍生类,等等。直到抵达最深一层的衍生类。
- 按声明顺序调用成员初始化模块。
- 调用衍生构建器的主体。
Thinking in Java学习杂记(第7章)的更多相关文章
- Thinking in Java学习杂记(5-6章)
Java中可以通过访问控制符来控制访问权限.其中包含的类别有:public, "有好的"(无关键字), protected 以及 private.在C++中,访问指示符控制着它后面 ...
- 20145330《Java学习笔记》第一章课后练习8知识总结以及IDEA初次尝试
20145330<Java学习笔记>第一章课后练习8知识总结以及IDEA初次尝试 题目: 如果C:\workspace\Hello\src中有Main.java如下: package cc ...
- Java 学习笔记 ------第三章 基础语法
本章学习目标: 认识类型与变量 学习运算符的基本使用 了解类型转换细节 运用基本流程语法 一.类型(基本类型) 所谓基本类型,就是在使用时,得考虑一下数据用多少内存长度存比较经济,利用程序语法告诉JV ...
- Java 学习笔记 ------第四章 认识对象
本章学习目标: 区分基本类型与类类型 了解对象与参考的关系 从打包器认识对象 以对象观点看待数组 认识字符串的特性 一."=" 和 "==" 当=用于基本类型时 ...
- Java 学习笔记 ------第五章 对象封装
本章学习目标: 了解封装的概念与实现 定义类.构造函数与方法 使用方法重载与不定长度自变量 了解static方法 一.Java封装概念 在面向对象程式设计方法中,封装(英语:Encapsulation ...
- Java 学习笔记 ------第六章 继承与多态
本章学习目标: 了解继承的目的 了解继承与多态的关系 知道如何重新定义方法 认识java.lang.object 简介垃圾回收机制 一.继承 继承是java面向对象编程技术的一块基石,因为它允许创建分 ...
- [core java学习笔记][第十一章异常断言日志调试]
第11章 异常,断言,日志,调试 处理错误 捕获异常 使用异常机制的技巧 使用断言 日志 测试技巧 GUI程序排错技巧 使用调试器 11.1 处理错误 11.1.1异常分类 都继承自Throwable ...
- [core java学习笔记][第四章对象与类]
4.3 用户自定义类 4.3.1 类数组的声明 需要两次new Employee[]=staff=new Employedd[3]; staff[0]=new Employedd(参数列表); sta ...
- thinking in java学习笔记:14章 类型信息
14.2 Class 对象 https://github.com/zhaojiatao/javase 1.什么是Class对象,Class对象是用来做什么的? Class对象是java程序用来创建类的 ...
随机推荐
- MVC08
1. c# 索引器(indexer) using System; using System.IO; namespace IO { class Program { ]; static void Main ...
- CSS-水平居中、垂直居中、水平垂直居中
1.水平居中 水平居中可分为行内元素水平居中和块级元素水平居中 1.1 行内元素水平居中 这里行内元素是指文本text.图像img.按钮超链接等,只需给父元素设置text-align:center即可 ...
- 在windows上极简安装GPU版AI框架(Tensorflow、Pytorch)
在windows上极简安装GPU版AI框架 如果我们想在windows系统上安装GPU版本的AI框架,比如GPU版本的tesnorflow,通常我们会看到类似下面的安装教程 官方版本 安装CUDA 安 ...
- Vue方式自动打开浏览器配置
Vue方式自动打开浏览器配置 1. 通过 package.json 配置项目 // 必须是符合规范的json语法 "vue": { "devServer": { ...
- new Date在IE下面兼容问题
昨天碰到一个bug,用art-template模板进行渲染时候,周视图任务展示失败,都是暂无任务,我以为是模板兼容问题,但最开始我用的时候记得就是IE8的兼容性问题,引入es5-shim.min.js ...
- Flink系列之状态及检查点
Flink不同于其他实时计算的框架之处是它可以提供针对不同的状态进行编程和计算.本篇文章的主要思路如下,大家可以选择性阅读. 1. Flink的状态分类及不同点. 2. Flink针对不同的状态进行编 ...
- SpringBoot2 整合ElasticJob框架,定制化管理流程
本文源码:GitHub·点这里 || GitEE·点这里 一.ElasticJob简介 1.定时任务 在前面的文章中,说过QuartJob这个定时任务,被广泛应用的定时任务标准.但Quartz核心点在 ...
- 编写程序实现输入x,y,判断属于第几象限。
x = float(input("请输入横坐标:")) y = float(input("请输入纵坐标:")) if x > 0 and y > 0 ...
- MATLAB神经网络(1) BP神经网络的数据分类——语音特征信号分类
1.1 案例背景 1.1.1 BP神经网络概述 BP神经网络是一种多层前馈神经网络,该网络的主要特点是信号前向传递,误差反向传播.在前向传递中,输入信号从输入层经隐含层逐层处理,直至输出层.每一层的神 ...
- Centos7报Could not resolve host: mirrorlist.centos.org; Unknown error(VMware网络设置)
软件:VMware 12 Linux版本:centOS 7 网络设置:桥接模式 安装后ping百度网址时报错:Name or service not know,使用yum安装时报错:Could not ...