资讯专栏INFORMATION COLUMN

第3章:抽象数据类型(ADT)和面向对象编程(OOP) 3.1数据类型和类型检查

zhangqh / 2735人阅读

摘要:所有变量的类型在编译时已知在程序运行之前,因此编译器也可以推导出所有表达式的类型。像变量的类型一样,这些声明是重要的文档,对代码读者很有用,并由编译器进行静态检查。对象类型的值对象类型的值是由其类型标记的圆。

大纲

1.编程语言中的数据类型
2.静态与动态数据类型
3.类型检查
4.易变性和不变性
5.快照图
6.复杂的数据类型:数组和集合
7.有用的不可变类型
8.空引用
9.总结

编程语言中的数据类型

类型和变量

类型是一组值,以及可以对这些值执行的操作。
变量:存储一个特定类型值的命名位置

Java中的类型

Java有几种基本类型,例如:

int(对于像5和-200这样的整数,但限于±2 ^ 31或大约±20亿的范围)

长(对于大于±2 ^ 63的整数)

布尔值(对于true或false)

双精度浮点数(代表实数的一个子集)

char(单个字符,如"A"和"$")

Java也有对象类型,例如:

字符串表示一系列字符。

BigInteger表示一个任意大小的整数。

按照Java约定,基本类型是小写字母,而对象类型以大写字母开头。

对象类型的层次结构

根是Object(所有非基元都是对象)

除Object以外的所有类都有一个父类,使用extends子句指定

class Guitar extends Instrument { ... }
如果省略了子句,则默认为Object
一个类是其所有超类的一个实例

从其超类继承可见的字段和方法

可以覆盖(Override)方法来改变他们的行为

包装的基本类型

用于原始类型的不变容器
– Boolean, Integer, Short, Long, Character, Float, Double
典型用例是集合
除非你必须使用包装的基本类型!
语言自动包装和自动解包装

运算符

运算符:执行简单计算的符号

赋值:=

加法:+

减法: -

乘法:*

除法:/

操作顺序:遵循标准的数学规则

1.括号

2.乘法和除法

3.加法和减法

字符串连接(+)

操作

操作是获取输入和生成输出的函数(有时会自行更改值)。

作为中缀,前缀或后缀运算符。 例如,a + b调用操作+:int×int→int。

作为一个对象的方法。 例如,bigint1.add(bigint2)调用操作add:BigInteger×BigInteger→BigInteger。

作为一项功能。 例如,Math.sin(theta)调用sin:double→double操作。 在这里,数学不是一个对象。 这是包含sin函数的类。

重载操作符/操作

一些操作被重载,因为相同的操作名称用于不同的类型
对于Java中的数字基本数据类型,算术运算符+, - ,*,/会严重重载。
方法也可以重载。 大多数编程语言都有一定程度的重载。
(将在3.3节OOP中讨论)

静态输入与动态输入

Java是一种静态类型的语言。

所有变量的类型在编译时已知(在程序运行之前),因此编译器也可以推导出所有表达式的类型。

如果将a和b声明为int,则编译器得出结论a + b也是int。

在编写代码时,Eclipse环境会执行此操作,事实上,您仍然可以在输入时了解许多错误。

在编译阶段进行类型检查

在像Python这样的动态类型语言中,这种检查被推迟到运行时(程序运行时)。

3.类型检查

静态检查和动态检查

一种语言可以提供的三种自动检查:

静态检查:在程序运行之前会自动发现错误。

动态检查:执行代码时会自动发现错误。

不检查:语言根本无助于您找到错误。 你必须亲自观察,否则最终会得到错误的答案。

不用说,静态捕获一个bug比动态捕获它要好,动态捕获它比根本没有捕获它要好。

静态检查

静态检查意味着在编译时检查错误。
错误是编程的祸根。
静态类型可以防止大量的错误感染程序:准确地说,通过对错误类型的参数应用操作而导致的错误。
如果你写了一行代码,如:“5”*“6”,它试图乘以两个字符串,那么静态类型在编程时会捕获这个错误,而不是等到执行过程中到达该行。

语法错误,如额外的标点符号或假词。 即使是像Python这样的动态类型语言也可以进行这种静态检查。

错误的名字,如Math.sine(2)。 (正确的名字是sin)

错误的参数数量,如Math.sin(30,20)。

错误的参数类型,如Math.sin(“30”)。

错误的返回类型,如返回“30”; 从声明的函数返回一个int。

动态检查

非法的参数值。 例如,当y实际上为零时,整数表达式x / y只是错误的; 否则它将运行。 所以在这个表达式中,除零不是一个静态错误,而是一个动态错误。

不具有代表性的返回值,即特定返回值无法在类型中表示时。

超出范围的索引,例如,在字符串上使用负值或太大的索引。

在空对象引用上调用方法。

静态与动态检查

静态检查往往是关于类型的,与变量具有的特定值无关的错误。

静态类型保证了一个变量会从该集合中获得一些值,但是我们直到运行时才知道它具有哪个值。

所以如果错误只会被某些值引起,比如被零除或索引超出范围,那么编译器不会引发关于它的静态错误。

相比之下,动态检查往往是由特定值引起的错误。

原始数据类型不是真正的数字

Java中的一个陷阱 - 以及其他许多编程语言 - 就是它的原始数字类型具有不像我们习惯的整数和实数那样的特例。
结果,真正应该动态检查的一些错误根本不被检查。

整数除法:5/2不返回分数,它返回一个截断的整数。

整数溢出。 如果计算结果过于积极或过于消极而无法适应该有限范围,则会悄然溢出并返回错误答案。 (没有静态/动态检查!)例如,int big = 200000 * 200000;

浮点类型中的特殊值。 NaN(“不是数字”),POSITIVE_INFINITY和NEGATIVE_INFINITY。 例如,double a = 7/0;

4可变性和不变性

赋值

使用“=”给变量赋值
赋值可以和变量声明结合使用

更改变量或其值

改变变量和改变数值有什么区别?

当你分配给变量时,你正在改变变量的箭头指向的地方。 您可以将其指向不同的值。

当分配给可变值的内容时(例如数组或列表),您将在该值内改变引用。

变化是必要的罪恶。
好的程序员可以避免变化的事情,因为它们可能会意外地改变。

不变性

不变性是一个主要的设计原则。
不可变类型是一种类型,它们的值一旦创建就永远不会改变。
Java也为我们提供了不可变的引用:一次赋值且永不重新赋值的变量。

为了使引用不可变,用关键字final声明它。

如果Java编译器不确定最终变量只会在运行时分配一次,那么它将产生编译器错误。 所以最终给你静态检查不可变引用。

最好使用final来声明方法的参数和尽可能多的局部变量。
像变量的类型一样,这些声明是重要的文档,对代码读者很有用,并由编译器进行静态检查。
注意:

fianl的类声明意味着它不能被继承。

final变量意味着它始终包含相同的值/参考,但不能最终更改

final方法意味着它不能被子类覆盖

可变性和不变性

对象是不可变的:一旦创建,它们总是表示相同的值。
对象是可变的:它们具有改变对象值的方法。

字符串作为不可变类型

字符串是不可变类型的一个例子。
一个String对象总是表示相同的字符串。
由于String是不可变的,一旦创建,String对象始终具有相同的值。
要将某些内容添加到字符串的末尾,您必须创建一个新的String对象:

StringBuilder作为一个可变类型

StringBuilder是一个可变类型的例子。
它具有删除字符串部分,插入或替换字符等的方法
该类具有更改对象值的方法,而不仅仅是返回新值:

可变类型的优点

使用不可变的字符串,这会产生大量的临时副本

获得良好的性能是我们使用可变对象的一个原因。

另一个是方便共享:通过共享一个常见的可变数据结构,您的程序的两个部分可以更方便地进行通信。
“全局变量”
但是你必须知道全局变量的缺点......

5快照图作为Code-level,Run-time,Moment视图

快照图

为了理解微妙的问题,我们可以绘制运行时发生的事情的图片。
快照图在运行时表示程序的内部状态 - 其堆栈(正在进行的方法及其局部变量)及其堆(当前存在的对象)。
为什么我们使用快照图表?

通过图片相互交流。

为了说明基本类型与对象类型,不可变值与不可变引用,指针别名,堆栈与堆,抽象与具体表示等概念。

帮助解释您的团队项目设计(彼此之间以及与您的技术援助相关)。

为后续课程中丰富的设计符号铺平道路。

可变值与重新分配变量

快照图给我们提供了一种可视化更改变量和更改值之间区别的方法:

当您分配给变量或字段时,您将更改变量的箭头指向的位置。 您可以将其指向不同的值。

当分配给可变值的内容时(例如数组或列表),您将在该值内改变引用。

快照图中的基本类型和对象类型

基本类型的值

基本类型的值由裸常量表示。 传入的箭头是对来自变量或对象字段的值的引用。

对象类型的值

对象类型的值是由其类型标记的圆。

当我们想要显示更多细节时,我们在其中写入字段名称,箭头指向它们的值。 有关更详细的信息,这些字段可以包含它们的声明类型。

重新分配和不可变的值

例如,如果我们有一个字符串变量s,我们可以将其从“a”值重新分配给“ab”
String s =“a”;
s = s +“b”;
字符串是不可变类型的一个例子,一种类型的值一旦创建就永远不会改变。
不变对象(它们的设计者打算始终表示相同的值)在快照图中用双边框表示,就像我们图中的String对象一样。

可变的值

相比之下,StringBuilder(一个内置的Java类)是一个可变对象,表示一串字符,并且它具有更改对象值的方法:
StringBuilder sb = new StringBuilder(“a”);
sb.append( “B”);
这两个快照图看起来非常不同,这是很好的:可变性和不可变性之间的差异将在使代码免受bug影响方面发挥重要作用。

不变的引用

Java也为我们提供了不可变的引用:一次赋值且永不重新赋值的变量。 为了使引用不可变,用关键字final声明它:
final int n = 5;
如果Java编译器不确定最终变量只会在运行时分配一次,那么它将产生编译器错误。 所以最终给出了对不可变引用的静态检查。
在快照图中,不可变引用(final)由双箭头表示。

6复杂的数据类型:数组和集合

数组

数组是另一个类型T的固定长度序列。例如,下面是如何声明一个数组变量并构造一个数组值以分配给它:
int [] a = new int [100];
int []数组类型包含所有可能的数组值,但是一旦创建了特定的数组值,永远不会改变其长度。
数组类型的操作包括:

索引:a [2]

赋值:a [2] = 0

长度:a.length

列表

我们使用List类型来代替固定长度的数组。
列表是另一个类型T的可变长度序列。 List list = new ArrayList ();
其部分操作:

索引:list.get(2)

赋值:list.set(2,0)

长度:list.size()

注1:列表是一个接口
注2:列表中的成员必须是一个对象。

集合

Set是零个或多个唯一对象的无序集合。
一个对象不能多次出现在一个集合中。 要么它在或它不在。

s1.contains(e)测试集合是否包含元素

s1.containsAll(s2)测试是否s1⊇s2

s1.removeAll(s2)从s1移除s2

Set是一个抽象接口

地图

地图类似于字典(key)

map.put(key,val)添加映射key→val

map.get(key)获取一个key的值

map.containsKey(key)测试地图是否有key

map.remove(key)删除映射

地图是一个抽象界面

声明List,Set和Map变量

使用Java集合,我们可以限制集合中包含的对象的类型。
当我们添加一个项目时,编译器可以执行静态检查,以确保我们只添加适当类型的项目。
然后,当我们取出一个对象时,我们保证它的类型将是我们所期望的。

创建List,Set和Map变量

Java有助于区分

一种类型的规范 - 它有什么作用? 抽象接口 - 实现

代码是什么? 具体类

List,Set和Map都是接口:

他们定义了这些相应的类型是如何工作的,但他们不提供实现代码。

优点:用户有权在不同情况下选择不同的实施方式。

List,Set和Map的实现:

列表:ArrayList和LinkedList

集合:HashSet

地图:HashMap

迭代器是一个可变类型的迭代器

迭代器是一个对象,它遍历一组元素并逐个返回元素。
当你使用for(...:...)循环遍历一个List或数组时,迭代器在Java中被使用。
迭代器有两种方法:

next()返回集合中的下一个元素---这是一个可变的方法!

hasNext()测试迭代器是否已到达集合的末尾。

7有用的不可变类型

原始类型和原始包装都是不可变的。

如果你需要用大数来计算,BigInteger和BigDecimal是不可变的。

不要使用可变日期,根据您需要的计时粒度,使用java.time或java.time.ZonedDateTime中适当的不可变类型。
Java的集合类型(List,Set,Map)的通常实现都是可变的:ArrayList,HashMap等
Collections实用程序类具有获取这些可变集合的不可修改视图的方法:

Collections.unmodifiableList

Collections.unmodifiableSet

Collections.unmodifiableMap

可变数据类型的不可变包装
Java集合类提供了一个有趣的折衷:不可变的包装器。

Collections.unmodifiableList()需要一个(可变的)List,并且用一个看起来像List的对象封装它,但是其禁用的mutator - set(),add(),remove()等会抛出异常。 所以你可以构造一个使用mutator的列表,然后用一个不可修改的包装器来封装它(并且抛弃你对原始可变列表的引用,并且得到一个不变的列表。

缺点是你在运行时获得了不变性,但不是在编译时。

如果你尝试排序()这个不可修改的列表,Java在编译时不会发出警告。

你会在运行时得到一个异常。

但这还是比没有好,所以使用不可修改的列表,地图和集合可以是降低错误风险的一种非常好的方法。

不可修改的包装

不可修改的包装器通过拦截所有修改集合并抛出UnsupportedOperationException的操作来取消修改集合的能力。
不可修改的包装有两个主要用途,如下所示:

一旦建立一个集合就不可变。 在这种情况下,最好不要保留对后备集合的引用。 这绝对保证了不变性。

允许某些客户端只读访问您的数据结构。 您保留对后备集合的引用,但请分发引用。 这样,客户可以看,但不能修改,而你保持完全访问。

8空引用

空引用
在Java中,对对象和数组的引用也可以采用特殊值Null,这意味着引用不指向对象。 空值是Java类型系统中的一个不幸的漏洞。
基元不能为null,编译器会拒绝这种带有静态错误的尝试:
int size = null; //非法
可以将null分配给任何非原始变量,并且编译器在编译时高兴地接受这些代码。 但是你会在运行时遇到错误,因为你不能调用任何方法或者使用带有这些引用之一的任何字段(抛出NullPointerExceptions):
String name = null;
name.length();
int [] points = null;
points.length;
null与空字符串“”或空数组不同。

空引用

非基元和像List这样的集合的数组可能是非空的,但包含null作为值
只要有人试图使用集合的内容,这些空值就可能导致错误。
空值是麻烦和不安全的,所以你最好建议将它们从你的设计词汇表中删除。
空值在参数和返回值中被隐式禁止。

来自Guava(Google)

不小心使用null会导致各种各样的错误。
研究谷歌代码库,我们发现像95%的集合不应该有任何空值,并且让这些快速失败而不是默默接受空值会对开发人员有所帮助。
另外,null很不明确。
很少有人明白空返回值应该是什么意思 - 例如,Map.get(key)可以返回null,因为map中的值为null,或者该值不在map中。 空可能意味着失败,可能意味着成功,几乎意味着任何事情。
使用除null之外的其他内容可以明确您的意思。

总结

静态类型检查:

减少错误保证安全。 静态检查通过在运行时捕获类型错误和其他错误来帮助安全。

容易明白。 它有助于理解,因为类型在代码中已明确说明。

准备改变。 静态检查通过识别需要一起更改的其他位置来更容易地更改代码。 例如,当您更改变量的名称或类型时,编译器会立即在使用该变量的所有位置显示错误,并提醒您更新它们。

可变性对于性能和便利性很有用,但它也会通过要求使用对象的代码在全范围内表现良好而造成bug的风险,这极大地增加了我们必须做的推理和测试,以确保其正确性。
确保你了解不可变对象(如String)和不可变引用(如final变量)之间的区别。
快照图可以帮助理解。

对象是由快照图中的圆圈表示的值,而不可变的对象是具有双边框的值,表示它永远不会更改其值。

引用是一个指向对象的指针,用快照图中的箭头表示,不可变引用是带有双线的箭头,表示不能将箭头移动到指向不同的对象。

关键的设计原则是不变性:尽可能使用不可变对象和不可变引用。

减少错误保证安全。 不可变对象不容易出现锯齿引起的错误。 不可变引用总是指向同一个对象。

容易明白。 因为不可变的对象或引用总是意味着相同的事物,所以代码读者推理起来更简单 - 他们不必追踪所有代码以找到可能更改对象或引用的所有位置,因为 它不能改变。

准备好改变。 如果在运行时不能更改对象或引用,那么当程序更改时,不需要修改依赖于该对象或引用的代码。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/71330.html

相关文章

  • 3抽象数据类型ADT面向对象编程OOP3.4面向对象编程OOP

    摘要:抽象数据类型的多个不同表示可以共存于同一个程序中,作为实现接口的不同类。封装和信息隐藏信息隐藏将精心设计的模块与不好的模块区分开来的唯一最重要的因素是其隐藏内部数据和其他模块的其他实施细节的程度。 大纲 面向对象的标准基本概念:对象,类,属性,方法和接口OOP的独特功能 封装和信息隐藏 继承和重写 多态性,子类型和重载 静态与动态分派 Java中一些重要的Object方法设计好的类面向...

    Heier 评论0 收藏0
  • 3抽象数据类型ADT面向对象编程OOP3.5 ADTOOP中的等价性

    摘要:抽象函数引发的关系是等价关系。所以当且仅当通过调用抽象数据类型的任何操作不能区分它们时,两个对象是相等的。必须为每个抽象数据类型适当地定义操作。一般来说,在面向对象编程中使用是一种陋习。 大纲 什么是等价性?为什么要讨论等价性?三种等价性的方式==与equals()不可变类型的等价性对象契约可变类型的等价性自动包装和等价性 什么是等价性?为什么要讨论等价性? ADT上的相等操作 ADT...

    Fundebug 评论0 收藏0
  • 3抽象数据类型ADT面向对象编程OOP3.2设计规约

    摘要:程序失败时,很难确定错误的位置。它保护客户免受单位工作细节的影响。将前提条件放在中,并将后置条件放入和。涉及可变对象的契约现在取决于每个引用可变对象的每个人的良好行为。设计规约按规约分类比较规约它是如何确定性的。 大纲 1.编程语言中的功能/方法2.规约:便于交流的编程,为什么需要规约 行为等同规约结构:前提条件和后条件测试和验证规约3.设计规约分类规约图表规约质量规约4.总结 编程...

    mozillazg 评论0 收藏0
  • 软件构造lab2

    摘要:本次实验训练抽象数据类型的设计规约测试,并使用面向对象编程技术实现。改成泛型将函数声明和调用等修改一下即可调用之前我们实现的一个图结构实现方法如下读取文件输入,识别序列,构建图结构。 本次实验训练抽象数据类型(ADT)的设计、规约、测试,并使用面向对象编程(OOP)技术实现 ADT。 3.1 Poetic Walks建立对ADT的基本印象,比如如何设计一个能够泛型化的ADT。加深对AF...

    孙吉亮 评论0 收藏0
  • 6:可维护性软件构建方法 6.2可维护性设计模式

    摘要:抽象工厂模式将具有共同主题的对象工厂分组。对可重用性和可维护性设计模式的高层考虑创造性模式工厂方法模式也称为虚拟构造器意图定义一个用于创建对象的接口,但让子类决定实例化哪个类。 大纲 创造性模式 工厂方法模式创建对象而不指定要创建的确切类。 抽象工厂模式将具有共同主题的对象工厂分组。 Builder模式通过分离构造和表示来构造复杂的对象。 结构模式 Bridge将抽象从其实现中分...

    VioletJack 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<