3-1数据类型与类型检验

大纲
1编程语言中的数据类型
2静态与动态数据类型检查
3易变性和不变性
4快照图
5复杂数据类型:数组和集合
6有用的不可变类型
7摘要

本次课程目的
1.了解编程语言中数据类型、静态和动态类型检查的基本知识
2.理解可变/不变的数据类型
3. 可变数据类型的危险性
4. 使用不变性来提高正确性、清晰度和易变性——不变数据类型的优越性
5. 使用快照图演示程序执行期间特定时间的状态。 ——用Snapshot图理解数据类型
6. 用集合类表达复杂数据类型、
7. 了解空引用Null的危害并避免它

1编程语言中的数据类型
类型和变量
▪ 类型是一组值,以及可以对这些值执行的操作。数据类型 ▪ 示例:
–boolean:真值(true或false)
–int:整数(0,1,-47)
–double:实数(3.14,1.0,-2.1)
–字符串:文本(“hello”,“example”)。
▪ 变量:用特定数据类型定义,可存储满足类型约束的值

Java中的类型
▪ Java有几种基本类型,例如:
–int(对于5和-200这样的整数,但限制在±2^31或大约±20亿的范围内)
–long(对于高达±2^63的较大整数)
–boolean(对于true或false)
–double(对于表示实数子集的浮点数)
–char(对于a这样的单个字符 ‘$’ )
▪ Java也有对象类型,例如:
–String表示一个字符序列。
–BigInteger表示任意大小的整数。
▪ 根据Java约定,原语类型是小写的,而对象类型是从大写字母开始的。

Java中的类型
对象类型层次
▪所有类最终都继承自Object类
–除Object之外的所有类都有一个父类,用extends子句指定 class Guitar extends Instrument { … }
▪ 如果省略extends子句,则默认为Objec
▪ 类是其所有超类的实例(从其超类继承可见字段和方法)可以重写方法以更改其行为

包装类:将基本类型包装为对象类
通常是在定义容器类型的时候使用它们(容器类型操作的元素要求是 对象类型,所以需要对基本数据类型进行包装,转换为对象类型)
一般情况下避免使用包装类,会降低性能
java可以自动拆箱和装箱

拆装箱举例
操作符
▪ 运算符:执行简单计算的符号操操作符
–赋值:=
–加法:+
–减法:-
–乘法:*
–除法:/
▪ 操作顺序:遵循标准数学规则
-1括号–2乘法和除法-3加减法
▪ 字符串连接(+)
– String text = “hello” + " world";
– text = text + " number " + 5; // text = “hello world number 5”

操作是接受输入并产生输出(有时改变值本身)的功能。
–作为中缀、前缀或后缀运算符。例如,a+b调用操作+:int×int→int。
–作为对象的方法。例如,bigint1.add(bigint2)调用操作add:biginger×biginger→biginger。
–作为一种功能。例如,Math.sin(theta)调用操作sin:double→double。在这里,Math不是一个对象。它是包含sin函数的类。

重载运算符/操作
▪ 有些操作被重载,因为相同的操作名用于不同的类型
▪ 对于Java中的数值原语类型,算术运算符+、-、*、/经常被重载。
▪ 方法也可以重载。大多数编程语言都有某种程度的重载

2静态与动态数据类型检查
类型转换
静态类型与动态类型
▪ Java是一种静态类型语言。
静态型语言–所有变量的类型在编译时(在程序运行之前)已知,因此编译器也可以推断所有表达式的类型。
–如果a和b声明为int,那么编译器将得出结论:a+b也是int。
–事实上,Eclipse环境在编写代码时就这样做了,所以您在输入时会发现许多错误。

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

静态检查和动态检查
▪ 一种语言可以提供三种自动检查:
静态检查:在程序运行之前自动发现错误。
–动态检查:执行代码时自动发现错误。
–不检查:语言根本无法帮助您找到错误。你得自己小心,否则会得到错误的答案。
▪ 不用说,静态地捕获一个bug比动态地捕获它要好,动态地捕获它比完全不捕获它要好。

Java会始终验证数据类型是否匹配,当你输入例如String a = 5这样的语句时编译器便会报错。

静态检查
▪ 静态检查意味着在编译时检查错误。
▪ bug是编程的祸根。
▪ 静态类型可防止大量bug感染程序:准确地说,错误是由对错误类型的参数应用操作引起的。
▪ 如果你写了一行代码,比如:
“5”*“6” 尝试将两个字符串相乘,然后静态类型检查将在您仍在编程时捕获此错误,而不是等待在执行期间到达行。

错误类型:
▪ 语法错误,如额外的标点或虚假的单词。甚至像Python这样的动态类型语言也会进行这种静态检查。
▪ 类名/函数名错误, like Math.sine(2) . (正确的名称是sin)
▪ 错误的参数数目,比如Math.sin(30,20)。
▪ 错误的参数类型,比如Math.sin(“30”)。
▪ 错误的返回类型,如返回“30”;来自声明为返回int的函数。
▪ 非法参数值:例如,整数表达式x/y只有在y实际上为零时才是错误的;否则它就可以工作。所以在这个表达式中,除以0不是静态错误,而是动态错误。
▪ 非法的返回值,即特定的返回值不能在类型中表示时。
▪ 超出范围的索引,例如,对字符串使用负索引或索引太大。▪ 对空对象引用调用方法。

静态与动态检查
▪ 静态检查往往是关于类型、与变量的特定值无关的错误。
–静态类型确保变量具有该集合中的某些值,但我们直到运行时才知道它究竟具有哪个值。
–因此,如果错误只由某些值引起,例如除以零或索引超出范围,那么编译器不会对此引发静态错误。
▪ 相比之下,动态检查往往是针对特定值导致的错误。

基础类型不是实数
▪ Java和许多其他编程语言中的一个陷阱是,它的原始数值类型有corner case,它们的行为与我们习惯的整数和实数不同。
▪ 因此,一些真正应该动态检查的错误根本不检查。
–整数除法:5/2不返回小数,返回截断整数。–整数溢出。如果计算结果太正或太负,无法在该有限范围内进行匹配,则它将安静地遍历并返回错误的答案。(无静态/动态检查!) e.g., int big = 200000*200000;
– 浮点类型中的特殊值
NaN (“Not a Number”), POSITIVE_INFINITY, and NEGATIVE_INFINITY.
• Int a=resulOfFunction(XX);
E.g., -9 • the result of Math.sqrt(a) is NaN, 不是动态类型错误!
• Math.sqrt(a) + 6 的结果是多少?

3易变性和不变性

Assignment 赋值
▪ 使用“=”给变量赋值
▪ 示例:
–String foo;
–foo=“IAP 6.092”;
▪ 赋值可以与变量声明结合使用
▪ 示例:
–double badPi=3.14;
–boolean isJanuary=true;
更改变量或其值
▪ 改变变量和改变值有什么区别?
–分配给变量时,将更改变量箭头指向的位置。您可以将其指向不同的值。
–当分配给可变值(如数组或列表)的内容时,将更改该值内的引用。
▪ 改变是不好但必要的
▪ 好的程序员避免尽可能避免改变,因为变量可能会意外地改变。

不变性
▪ 不变性是一个主要的设计原则。
▪ 不可变类型是其值一旦创建就永远无法更改的类型。
▪ Java还提供了不可变的引用:只分配一次而从不重新分配的变量。
–要使引用不可变,请使用关键字final声明它
final int n = 5;
final Person a = new Person(“Ross”);
▪ 如果Java编译器不确信您的最终变量在运行时只分配一次,那么它将产生一个编译器错误。所以final提供了对不可变引用的静态检查。

最好使用final来声明方法的参数和尽可能多的局部变量。
▪ 与变量的类型一样,这些声明也是重要的文档,对代码的读者很有用,并且由编译器进行静态检查。
▪ 注意:–final一个类声明意味着它不能被继承。
–final变量表示它始终包含相同的值/引用,但不能更改
–final方法表示它不能被子类重写

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

字符串作为不可变类型
▪ 字符串是不可变类型的示例。
▪ 字符串对象始终表示同一字符串。
▪ 由于String是不可变的,因此一旦创建,String对象总是具有相同的值。
▪ 要在字符串末尾添加内容,必须创建新的字符串对象:
不可变类型String的代码快照
StringBuilder作为可变类型
▪ StringBuilder是可变类型的一个示例。
▪ 它有删除部分字符串、插入或替换字符等方法。
▪ 此类具有更改对象值的方法,而不仅仅是返回新值:
可变类型StringBuffered的代码快照
–当只有一个对象引用时,可变性和不可变性之间的区别并不重要。
▪ 但当有其他对象引用时,它们的行为方式有很大的不同。
–当另一个变量t指向与s相同的String对象,而另一个变量tb指向与sb相同的StringBuilder时,不可变和可变对象之间的差异就变得更加明显。
在这里插入图片描述
可变类型的优势
▪ 使用不可变的字符串,这会生成很多临时副本
——第一个数字(“0”)实际上在生成最后一个字符串的过程中被复制了n次,第二个数字被复制了n-1次,依此类推。–尽管我们只连接了n个元素,但仅复制所有内容实际上要花费O(n2)时间。
▪ StringBuilder旨在最小化此复制。
–它使用了一个简单但聪明的内部数据结构,在使用to String()调用请求最后一个字符串时,完全避免在最后进行任何复制。
在这里插入图片描述
可变类型的优势
▪ 获得良好的性能是我们使用可变对象的原因之一。
▪ 另一个是方便的共享:程序的两个部分可以通过共享一个通用的可变数据结构来更方便地进行通信。
▪ “全局变量”
▪ 但你一定知道全局变量的缺点…

突变风险
▪ 既然可变类型看起来比不可变类型强大得多,那么究竟为什么要选择不可变类型呢?
–StringBuilder应该能够执行字符串所能执行的所有操作,以及set()和append()和其他所有操作。
▪ 答案是,不可变类型更安全,更易于理解,更易于更改。
–易变性使你更难理解你的程序在做什么,更难执行方法规约。
▪ 选择可变或不可变类型取决于性能和安全之间的权衡

例1安全性

–在本例中,很容易认为责任在于sumAbsolute()的实现者超出了规范允许的范围。
–但实际上,传递可变对象可能会导致一个潜在的错误。一些程序员会不经意地修改列表,通常是出于好的意图,比如重用或性能,但是会导致一个很难跟踪的错误。
▪ 容易理解?
–在读取main()时,sum()和sumAbsolute()会有什么假设?
–读者是否清楚地看到myData被其中一个更改了?

先调用sumAbsolute函数会导致list中的数值被改变,从而导致sum函数得不到期望的结果。

例二

PartyPlanning函数修改了date变量,导致groundHogAnswer无法得到正确的结果。

▪ 在这两个例子中,如果列表和日期都是不可变的类型,那么问题就完全避免了。
▪ 这些漏洞本来是设计上不可能的。
▪ 你不应该用Date类!
–使用包java.time中的一个类:LocalDateTime、Instant等–在它们的规范中都保证它们是不可变的。

如何修改代码?
▪ 在示例1中:–返回对象的新副本(防御性拷贝),即返回新日期(groundhogAnswer.getTime());
–但是,为每个客户端使用额外的空间
-即使99%的客户端从未更改它返回的日期。在我们的记忆中,可能会有很多the first Day of the Spring的复制品。
▪ 如果我们改用不可变类型,那么程序的不同部分可以在内存中安全地共享相同的值,因此需要更少的复制和内存空间。
▪ 不可变比可变更有效,因为不可变类型不需要进行防御性复制。

别名使可变类型具有风险
▪ 如果在方法中完全本地使用可变对象,并且只引用一个对象,那么使用可变对象就可以了。
▪ 在我们刚才看到的两个例子中,导致问题的原因是对同一个可变对象有多个引用(也称为别名)。
–在List示例中,List(在sum和sumAsolute中)和myData(在main中)都指向同一个列表。一个程序员(sumAsolute’s)认为可以修改列表;另一个程序员(main’s)希望列表保持不变。因为这些别名,main的程序员得不到正确的结果。
–在日期示例中,有两个变量名指向日期对象groundhogAnswer和partyDate。这些别名位于代码的完全不同的部分,由不同的程序员控制,他们可能不知道对方在做什么。

防御式拷贝例子

4快照图作为代码级、运行时和时刻视图
软件构造:转换btw视图

快照关系图
▪ 它将有助于我们绘制运行时发生的事情的图片,以便理解一些微妙的问题。
▪ 快照图显示程序在运行时的内部状态—其堆栈(进行中的方法及其局部变量)和堆(当前存在的对象)。
▪ 为什么使用快照关系图?
–通过图片相互交谈。
–说明基本类型与对象类型、不可变值与不可变引用、指针别名、堆栈与堆、抽象与具体表示等概念。
–帮助解释你的团队项目设计(其他人和助教)。
–为后续课程中更丰富的设计符号铺平道路。

快照关系图中的基本值和对象值
▪ 基本值–基本值由常量表示。传入箭头是对来自变量或对象字段的值的引用。
在这里插入图片描述
▪ 对象值–对象值是按其类型标记的元素。–当我们想显示更多细节时,我们会在里面写上字段名,箭头指向它们的值。更详细地说,字段可以包括它们声明的类型。
在这里插入图片描述
重新分配和不可变值
▪ 例如,如果我们有一个字符串变量s,我们可以将它从值“a”重新赋值为“ab”String s=“a”;
s=s+“b”;
▪ String是不可变类型的一个例子,这种类型的值一旦创建就永远无法更改。
▪ 在快照关系图中,不可变对象(其设计器希望始终表示相同的值)由双边框表示,与关系图中的字符串对象类似。

在这里插入图片描述

可变值
▪ 相比之下,StringBuilder(一个内置Java类)是一个表示字符串的可变对象,它有一些方法可以更改对象的值: StringBuilder sb=新的StringBuilder(“a”);sb.append(“b”);
▪ 这两个快照图看起来非常不同:易变性和不可变性之间的差异将在使代码免受错误的影响方面发挥重要作用。
在这里插入图片描述

不可赋值/不可变引用
▪ Java还提供了不可变的引用:只分配一次而从不重新分配的变量。
要使引用不可变,请使用关键字final声明它:
final int n=5;
▪ 如果Java编译器不确信您的最终变量在运行时只分配一次,那么它将产生一个编译器错误。所以final对不可变引用进行静态检查。
▪ 在快照关系图中,不可赋值引用(final)由双箭头表示。

▪ 一个对象,它的id永远不会改变(不能重新分配给另一个数字),但是它的age’可以改变。
▪ 对可变值(例如:final StringBuilder sb)的不可变/不可赋值引用,即使我们指向同一对象,其值也可以更改。
▪ 对不可变值(如字符串s)的可变/可重分配引用,其中变量的值可以更改,因为它可以重新指向其他对象。

在这里插入图片描述
例子:针对可变值的不可变 引用
针对不可变值的可变引用
5复杂数据类型:数组和集合
Array
▪ 数组是一种类型T的定长序列。
例如,下面介绍如何声明数组变量并构造一个数组值来分配给它:int[]a=new int[100];、▪ int[]数组类型包含所有可能的数组值,但特定的数组值一旦创建,就永远无法更改其长度。
▪ 对数组类型的操作包括:
–索引:a[2]
–赋值:a[2]=0
–求长度:a.length

List
▪ 列表是另一种类型T的可变长度序列。ListList=new ArrayList();
▪ 它的一些操作:
–索引:list.get(2)
–赋值:list.set(2,0)
–长度:list.size()
▪ 注1:列表是一个接口。
▪ 注2:列表中的成员必须是对象。
List

Set
▪ 集合是零个或多个唯一对象的无序集合。
▪ 对象不能多次出现在集合中。要么进要么出。
–s1.contains(e)测试集合是否包含元素
–s1.containsAll(s2)测试s1⊇s2
–s1.removeAll(s2)是否从s1中移除s2
▪ Set是一个抽象接口

Set

Map
▪ 映射,类似于字典(键值)
–Map.put(key,val)添加映射键→va
l–Map.get(key)获取键值
–Map.containsKey(key)测试映射是否有键值、
–Map.remove(key)删除映射
▪ Map是一个抽象接口

在这里插入图片描述
声明列表、集合和映射变量
▪ 使用Java集合,我们可以限制集合中包含的对象的类型。
▪ 当我们添加一个item时,编译器可以执行静态检查以确保我们只添加适当类型的项。
▪ 然后,当我们拿出一个item,我们保证它的类型将是我们所期望的。

▪ List list = new ArrayList();
▪ List list = new ArrayList<>();

▪ Declaration: List cities;
// a List of Strings Set numbers;
// 一个Integer类型的Set
Map<String, Turtle> turtles; // 一个有String类型的键和Turtle类型的值的图
▪ 我们不能创建基本数据类型的集合
– 例如, Set
. – 但是我们可以使用int的包装类Integer(e.g. Set numbers).

sequence.add(5);
// 将 5 添加到第二个位置 = sequence.get(1);
// 得到第二个元素

创建列表、集合和映射变量
▪ Java有助于区分类型的规范,它是做什么的?
抽象接口—实现—代码是什么?
Concrete Class 实现类
▪ List、Set和Map都是接口:–它们定义了这些类型的工作方式,但不提供实现代码。
–优点:用户有权在不同的情况下选择不同的实现。
▪ List、Set和Map的实现:
–List:ArrayList和LinkedList
–Set:HashSe
t–Map:HashMap

作为可变类型的迭代器
▪ 迭代器是一个遍历元素集合并逐个返回元素的对象。
▪ 使用for(。。。:…)循环遍历列表或数组。
▪ 迭代器有两个方法:
–next()返回集合中的下一个元素—这是一个mutator方法!
–hasNext()测试迭代器是否已到达集合的末尾。
在这里插入图片描述

6有用的不可变类型
▪ 基 本类型及其封装对象类型都是不可变的
–如果需要用大数值计算,BigInteger和BigDecimal是不可变的。
▪ 不要使用可变日期,根据所需的计时粒度使用java.time中相应的不可变类型。
▪ Java集合类型的通常实现
——列表、集合、映射—都是可变的:ArrayList、HashMap等。
▪ Collections实用程序类具有获取这些可变集合的不可修改视图的方法:
–Collections.unmodifiableList
–Collections.unmodifiableSet
–Collections.unmodifiableMap

可变数据类型被不可变包装器包装
▪ Java集合类提供了一个有趣的折衷方案:不可变包装器。
–Collections.unmodifiableList()获取(可变)列表,并将其包装为一个看起来像列表的对象,但其赋值函数已禁用–set()、add()、remove()等,否则将引发异常。
因此,可以使用mutators构造一个列表,然后将其密封在一个不可修改的包装器中(并放弃对原始可变列表的引用,获得一个不可变列表。
▪ 缺点是在运行时获得不变性,而不是在编译时。
–如果尝试对这个不可修改的列表进行sort()排序,Java不会在编译时发出警告,但运行时会出现异常。
–但这仍然比什么都没有要好,因此使用不可修改的List、Map和Set可以非常好地降低出现错误的风险。

不可修改的包装类
▪ 不可修改的包装器通过截取将修改集合的所有操作并抛出UnsupportedOperationException来取消修改集合的能力。
▪ 不可修改的包装器有两个主要用途,如下所示:
–使一个集合在生成后不可修改。在这种情况下,最好不要维护对备份集合的引用。这绝对保证了不变性。
–允许某些客户端只读访问您的数据结构。保留对备份集合的引用,但分发对包装器的引用。这样,当您保持完全访问时,客户端可以查看但不能修改。

7 空引用

▪ 在Java中,对对象和数组的引用也可以采用特殊的值Null,这意味着引用不指向对象。Null值是Java类型系统中一个的漏洞。
▪ 基本类型不能为空,编译器将拒绝此类带有静态错误的尝试:
例如 int size=null 是非法的
▪ 可以将空值赋给任何非基元变量,编译器在编译时很高兴地接受此代码,但在运行时会出现错误,因为不能调用任何方法或使用具有这些引用之一的任何字段(抛出NullPointerExceptions):
String name=null;name.length();int[]points=null;points.length;
▪ null与空字符串“”或空数组不同。
▪ 非原语数组和集合(如List)可能为非空,但包含空值
▪ 一旦有人试图使用集合的内容,这些空值很可能会导致错误。
▪ 空值既麻烦又不安全,因此建议您从设计词汇表中删除它们。
▪ 空值在参数和返回值中被隐式禁止。如果一个方法允许一个参数为空值或者如果它可能因此返回一个空值,它应该显式地声明它。
但总的来说这些都不是好主意,应当尽量避免空值。

本次总结
静态类型检查:
–防止错误。静态检查通过在运行前捕获类型错误和其他错误来帮助提高安全性。
–易于理解。它有助于理解,因为类型是在代码中显式声明的。
–随时准备更改。静态检查通过标识需要同步更改的其他位置,使更改代码更容易。例如,当您更改变量的名称或类型时,编译器会立即在使用该变量的所有位置显示错误,并提醒您更新它们。

易变性对性能和便利性很有用,但它也会产生错误风险,因为它要求使用对象的代码在全局级别上表现良好,这使我们必须进行的推理和测试变得非常复杂,从而对其正确性抱有信心
▪ 理解不可变对象(如字符串)和不可变引用(如最终变量)之间的区别。
▪ 快照关系图有助于理解这一点。
–对象是值,由快照关系图中的圆圈表示,不可变的对象有一个双边框,指示它从不更改其值。
–引用是指向对象的指针,由快照关系图中的箭头表示,不可变引用是带双线的箭头,表示箭头不能移动以指向其他对象。

关键的设计原则是不变性:尽可能使用不变性对象和不变性引用。
–Safe from Bug。不可变对象不易受别名导致的错误影响。不可变引用总是指向同一个对象。
–易于理解。因为一个不可变的对象或引用总是意味着同一件事,所以对于代码的读者来说,更简单的解释是——他们不必跟踪所有的代码来找到对象或引用可能被更改的所有位置,因为它不能被更改。
–随时准备改变。如果对象或引用不能在运行时更改,那么当程序更改时,依赖于该对象或引用的代码就不必进行修改。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值