软件构造课程总结(四)——数据类型与类型检验

编程语言中的数据类型

Java中的类型与变量

类型是一组值,以及可以对这些值执行的操作。

变量用特定数据类型定义,可存储满足类型约束的值。

Java原始类型基本数据类型:int, long, boolean, double, char

Java对象数据类型:String, BigInteger

根据Java约定,基本体类型为小写,而对象类型以大写字母开头。

  • 原始数据类型:只有值,没有ID(无法区分);不可变的 ;在栈中分配内存;代价低

  • 对象数据类型:既有ID,也有值;可变或不可变;在堆中分配内存;代价昂贵

对象类型形成层次结构

所有非原始类型的根类型都为Object

如果省略了extends语句,则默认继承自Object

一个类是其所有父级类的一个具体类型

  • 类可以从它的父级类中继承可见的字段和方法
  • 类可以覆盖继承的方法来改变它们的行为
包装原始类型

Boolean, Integer, Short, Long, Character, Float, Double

将基本类型包装为对象类型,通常是在定义集合类型的时候使用它们,一般情况下,尽量避免使用。

一般可以自动转换。

操作符&操作
操作符

操作符:执行简单计算的符号,如 =+-*/

操作符优先级:遵循标准的数学规则。

字符串连接(+):

String text = "hello" + " world";
text = text + " number " + 5; 
// text = "hello world number 5"
操作

操作是接受输入和产生输出的函数(有时也会改变值本身)。

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

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

  • 作为一个函数。例如,Math.sin(theta)调用操作sin:double → double 。在这里,Math不是一个object。它是一个包含sin函数的类。

重载

有些操作被重载,因为相同的操作名称用于不同类型。

对于Java中的数字原始类型,算术运算符+、-、*、/被大量重载。

方法也可以被重载。大多数编程语言都有一定程度的重载。

静态与动态数据类型检查

类型转换
int a = 2; // a = 2 
double a = 2; // a = 2.0 (隐式类型转换) 
int a = (int) 18.7; // a = 18 
double a = (double)2/3; // a = 0.6666…
int a = 18.7; // ERROR 
String a = 1; // ERROR
double a = 2/3; // a = 0.0
静态类型 vs 动态类型

Java是一种静态类型语言:

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

  • 如果a和b被声明为int,那么编译器得出结论,a+b也是int。

  • Eclipse 或 IDEA 在编写代码时执行静态类型检查

    (在编译阶段进行类型检查)

在像Python这样的动态类型语言中,这种检查会延迟到程序运行时。(在运行阶段进行类型检查)

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

  • 静态类型检查:在程序运行之前就会自动发现错误。
  • 动态类型检查:在执行代码时会自动发现错误。
  • 不检查:该语言根本不能自动找到错误。

不用说,静态地捕捉一个bug比动态地捕捉它要好,而动态地捕捉它也比根本不捕捉它要好。静态类型检查 >> 动态 >> 无检查

静态类型检查

静态类型检查:可在编译阶段发现错误,避免了将错误带入到运行阶段,可提高程序正确性/健壮性。

  • 语法错误:比如额外的标点符号或假词。即使是像Python这样的动态类型语言,也会进行这种静态检查。
  • 错误地命名了类名/函数名错误,如Math.sine (2)。(正确的名字是sin)
  • 参数数量错误,比如Math.sin(30,20)。
  • 参数类型错误,如Math.sin(“30”)。
  • 返回值类型错误,如从声明返回int的函数返回了“30”。
动态类型检查
  • 非法参数值。例如,整数表达式x / y只有在y实际上是0时才是错误的;否则它就能工作。所以在这个表达式中,除以零不是一个静态错误,而是一个动态错误。
  • 非法的返回值,即当特定的返回值不能在该类型中表示时。
  • 超出范围的索引越界,例如,在字符串上使用一个负的或太大的索引。
  • 调用空对象引用上的方法。(空指针)
静态检查 vs 动态检查
  • 静态检查倾向于是关于类型的,这些错误与变量所具有的特定值无关。
    • 静态类型保证了一个变量将从该集合中有一些值,但是直到运行时我们才知道它到底有哪个值。
    • 因此,如果错误只由某些值引起,如除零或索引超出范围,那么编译器就不会对它产生静态错误。
  • 相比之下,动态检查往往是关于由特定值引起的错误。
  • 总结:静态检查——关于“类型”的检查,不考虑值;动态检查——关于“值”的检查。

可变性和不可变性

可变性,不变性

改变一个变量、改变一个变量的值,二者有何区别?

  • 改变一个变量:将该变量指向另一个值的存储空间
  • 改变一个变量的值:将该变量当前指向的值的存储空间中写入一个新的值。

变化是“罪恶”,但程序不能没有变化。尽可能避免变化,以避免副作用。

不变性是一种重要设计原则。

不变数据类型:一旦被创建,其值不能改变

如果是引用类型,也可以是不变的:一旦确定其指向的对象,不能再被改变。

不变对象:一旦被创建,始终指向同一个值/引用

可变对象:拥有方法可以修改自己的值/引用

final

如果想要让一个引用类型不变,使用final进行修饰:

final int n = 5;
final Person a = new Person(Ross);

如果编译器无法确定final变量不会改变,就提示错误,这也是静态类型检查的一部分。

所以,尽量使用final变量作为方法的输入参数、作为局部变量。final表明了程序员的一种“设计决策”。

  • final类无法派生子类
  • final变量无法改变值/引用
  • final方法无法被子类重写
String & StringBuilder

String:

  • String是不可变类型
  • 要在字符串的末尾添加一些内容,您必须创建一个新的字符串对象
    请添加图片描述

StringBuilder:

  • StringBuilder是可变类型
  • 它可以删除字符串的部分、插入或替换字符等。▪
  • 这个类有更改对象的值的方法,而不仅仅是返回新的值:
    请添加图片描述
区别,优缺点

当有多个引用的时候,差异就出现了。

可变类型优点
  • 使用不可变类型,对其频繁修改会产生大量的临时拷贝(需要垃圾回收)。

  • 可变类型最少化拷贝以提高效率。

  • 使用可变数据类型,可获得更好的性能。

  • 也适合于在多个模块之间共享数据。

不可变类型优点
  • 不可变类型更“安全”,在其他质量指标上表现更好。

实际使用时需要折中考虑,取决于看重哪个质量指标。

使用可变数据类型时防范风险
  • 防御式拷贝(安全,但容易造成内存浪费)

    在此种情况下使用不可变类型可以防范风险,节省复制代价

  • 安全的使用可变类型:局部变量,不会涉及共享;只有一个引用

    如果有多个引用,则使用可变类型则就不安全

快照图

快照图表示一个程序在运行时的内部状态——它的堆栈(正在进行中的方法及其本地变量)和它的堆(当前存在的对象)。

快照图用于描述程序运行时的内部状态。

为什么使用快照图?

  • 便于程序员之间的交流
  • 便于刻画各类变量随时间变化
  • 便于解释设计思路
快照图中的原始值和对象值
基本类型的值

请添加图片描述

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

对象类型的值

请添加图片描述

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

当我们想要显示更多细节时,我们在其中写字段名,箭头指向它们的值。对于更详细信息,字段可以包括它们声明的类型。

重新分配值
不可变对象

不可变对象:用双线椭圆

String s = "a";
s = s + "b";

请添加图片描述

可变对象

可变对象:单线椭圆

StringBuilder sb = new StringBuilder("a");
sb.append("b");

请添加图片描述

不可重新分配的/不可变的引用

在快照图中,不可重新分配的引用双箭头表示。

请添加图片描述

注意:

  • 引用是不可变的,但指向的值却可以是可变的。

     final StringBuilder sb;
    
  • 可变的引用,也可指向不可变的值。

    String s;
    

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

Array

数组是另一种类型T的固定长度的序列。

int[]数组类型包括所有可能的数组值,但一个特定的数组值一旦创建,就永远不能更改其长度。

对数组类型的操作包括:

  • indexing: a[2]
  • assignment: a[2] = 0
  • length: a.length
List

列表是另一种类型T的可变长度序列。

对列表的一些操作:

  • indexing: list.get(2)
  • assignment: list.set(2, 0)
  • length: list.size()

Note:

  1. List是一个接口。2. List中的成员必须是一个对象。

列表迭代

int max = 0;
for (int x : list) {
max = Math.max(x, max);
}
Set

集合是包含零个或多个唯一对象的无序集合。

对象不能在集合中出现多次。

对集合的一些操作:

s1.contains(e)      //test if the set contains an element
s1.containsAll(s2)  //test whether s1 ⊇ s2
s1.removeAll(s2)    //remove s2 from s1

Set 是一个接口。

Map

映射类似于一个字典(键-值)。

对映射的操作:

map.put(key, val)    //add the mapping key → val
map.get(key)         //get the value for a key
map.containsKey(key) //test whether the map has a key
map.remove(key)      //delete a mapping

Map是一个抽象接口。

请添加图片描述

迭代器

迭代器是一个对象,它逐步通过元素集合并逐个返回元素。在Java中,当使用为(… : …)循环遍历列表或数组时,将使用迭代器。

一个迭代器有两个方法:

  • next()返回集合中的下一个元素,这是一个mutator方法
  • hasNext()测试迭代器是否已到达集合的结束。

在使用迭代器过程中,需要注意使用某些方法修改了列表对象时对迭代器状态的破坏。

实用的不可变类型

基本类型及其封装对象类型都是不可变的。

不要使用可变的Date,请根据您需要的计时粒度使用java中适当的不可变类型。

List, Set, Map的具体实现类都是可变的,Collection类有一些方法来获取这些可变集合的不可修改的视图:

  • Collections.unmodifiableList
  • Collections.unmodifiableSet
  • Collections.unmodifiableMap

实际上是基于list,set,map的包装器。

这种包装器得到的结果是不可变的:只能读不能写。

构造的包装器抛弃了原来的mutator方法,在调用时会抛出异常。

但是这种“不可变”是在运行阶段获得的,编译阶段无法据此进行静态检查。

不可修改的包装器通过拦截所有将修改集合的操作,并抛出一个不受支持的操作异常,从而剥夺了修改集合的能力。

不可修改的包装器有两个主要用途,如下:

  1. 在集合构建后使其不可变。
  2. 允许某些客户端只读访问您的数据结构。

要从某些已知值创建不可变集合,请使用List.ofSet.ofMap.of

List<String> a = List.of("lion", "tiger", "bear");

或者,使用List.copyOf(Java 10)来创建不可修改的可变集合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梚辰

感谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值