软件构造复习第四章 数据类型与类型检验

Data type in programming languages

数据类型Type 是一组值的集合,以及可以在这些值上所进行的操作。例如

  • Boolean:真值 (true or false).
  • int:整数如(0, 1, -47)

变量Variables用特定数据类型定义,可存储满足类型约束的值
如 String foo; 则foo是一个字符串类型的变量

Java 基本数据类型

  • int (范围 ± \pm ± 2^31)
  • Boolean (true or false)
  • double (浮点数,实数的一部分)
  • char (单个字符如 ‘A’ ‘a’ 等)

Java 对象数据类型
例如

  • String 代表一个字符的序列
  • BigInteger 任意大小的整数

基本数据类型都是小写字母,而对象数据类型开头字母都是大写的
两种类型的比较

基本数据类型对象数据类型
int, long, byte, short, char, float, double, booleanClasses, interfaces, arrays, enums,annotations
只有值,没有ID既有ID,也有值
Immutable 不可变的部分可变(mutable),部分不可变
在栈中分配内存在堆中分配内存
无法实现表达的统一通过泛型实现表达的统一

对象类型可以形成层次结构
Object 是所有对象类型的根(父类)
类与类之间存在继承关系,子类可以从父类继承属性和方法也可以重写父类的方法来改变行为。
例如

 class Dog extends Animal{} 

则Dog继承Animal,Dog是Animal的子类,同时Dog和Animal都是Objects的子类

Static vs. dynamic data type checking

在程序运行中可能会发生类型转换

 int a=int18.7; // a=18
 int a=18.7; //error!

更隐蔽的错误如

double a=2/3; //a = 0.0

Java 是静态类型语言a statically-typed language在编译阶段进行类型检查,当然也有动态类型语言如Python等,这类语言在运行阶段进行类型检查

  • Static checking静态类型检查: 在运行之前检查bug
  • Dynamic checking动态类型检查: 当程序运行时检查bug
  • No checking无检查: 这类语言不会检查错误,需要自己观察,否则返回错误的结果

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

  • Syntax errors 语法错误
  • Wrong names 类名/函数名错误,如Math.sine(2) 正确的应该为Math.sin(2)
  • Wrong number of arguments 参数数目错误如Math.sin(30,20)
  • Wrong argument types 参数类型错误,如Math.sin(“30”)
  • Wrong return types 返回值类型错误,如声明返回String类型的返回值,却返回了int类型的值

动态类型检查可以检查

  • Illegal argument values 非法的参数值
  • Unrepresentable return values 非法的返回值
  • Out-of-range indexes 越界
  • Calling a method on a null object reference. 空指针引用

对比而言,静态倾向于检查一个变量的类型是否错误,动态检查倾向于检查可能出现的特定值导致的错误

Mutability & Immutability

改变一个变量Changing a variable:将该变量指向另一个值的存储空间
改变一个变量的值Changing a variable‘s value:将该变量当前指向的值的存储空间中写入一个新的值。
尽管程序不能没有变化,但是应该尽可能避免变化,以避免副作用
重要设计原则Immutability 不变性
Immutable types 不变数据类型:一旦被创建,其值不能改变。
如果是引用类型,也可以是不变的,即一旦确定其指向的对象,不能再被改变。需要通过final关键字来实现如

final int n =5

则n的值只能是5。如果编译器无法确定final变量不会改变,就提示错误,这也是静态类型检查的一部分。
基于上述原因尽量使用 final变量作为方法的输入参数或者作为局部变量。
final特性:

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

相对于Imumutability,也有Mutability
Immutable object不变对象:一旦被创建,始终指向同一个值/引用
Mutable object 可变对象:拥有方法可以修改自己的值/引用
例子:
String 类是Immutable 类,一旦创建,则该String的值就不会改变
如果修改,则是新建一个String。

String s = "a";
s = s.concat("b");

Snapshot Diagram如下图所示
Snapshot双边椭圆代表该对象为Immutable的
StringBuilder是mutable类,创建之后,可以通过方法修改,而不是新建一个StringBuilder

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

如下图所示:
在这里插入图片描述
这两者的区别在于如果存在多个引用指向一个对象,mutable类的值修改可能导致程序错误
例如

String t = s;
t = t + "c";

StringBuilder tb = sb;
tb.append("c");

Snapshot diagram如下图所示:
在这里插入图片描述
此时Immutable类s指向ab,t指向abc,程序正常,
而StringBuilder 则sb,tb都指向同一个对象,对tb执行的append,也会对sb起作用,可能会导致出现不希望出现的情况
mutable 类的优势在于:当使用不可变类型时,对其频繁修改会产生大量的临时拷贝(需要垃圾回收),而使用可变类型可以最少化拷贝以提高效率
例如StringBuilder类,如果需要频繁对一个字符串进行操作,使用StringBuilder类可以减少很多拷贝

//String
String s = "";
for(int i = 0; i < n; ++i){
	s = s + n; 
}
//StringBuilder
StringBuilder sb = new StringBuilder();
for(int i = 0; i < n; ++i){
	sb.append(String.valueOf(i));
}
String s= sb.toString();

上方共创建n+1个String,下方仅一个StringBuilder和一个String。
由此可见使用可变数据类型,可获得更好的性能,也适合于在多个模块之间共享数据,类似于全局变量。
如何防御mutable带来的隐患?
通过防御式拷贝,给客户端返回一个全新的Date对象。但是大部分时候该拷贝不会被客户端修改,可能造成大量的内存浪费。此时如果使用不可变类型, 则节省了频繁复制的代价。
安全的使用可变类型:局部变量,不会涉及共享;只有一个引用
如果有多个引用(别名),使用可变类型就非常不安全。
使用Immutable类更“安全”, 在其他质量指标上表现更好
使用mutable还是immutable,需要在性能和安全性折中
下面是一个防御式编程的例子

public final class Period{
	private final Date start, end;//Invariant: start <=end
	//denfensively copies parameters
	public Period(Date start, Date end){
		this.start = new Date(start.getTime());
		this.end = new Date(end.getTime());
		if(this.start.after(this.end)){
			throw new IllegalArgumentExcepiton(start + " > " + end);
		}
	}
	//defensively copy fields
	public Date start(){
		return new Date(start.getTime());
	} 
	public Date end(){
		return new Date(end.getTime());
	}
}

Snapshot diagram

code-level, run-time, and moment view

  • 刻画运行中某一时刻的程序状态用于描述程序运行时的内部状态
  • 便于程序员之间的交流
  • 便于刻画各类变量随时间变化
  • 便于解释设计思路

Snapshot diagram的元素

  • 基本类型的值用单纯的值表示即可。箭头指向该值代表某一个变量或者对象的属性引用了该值
    在这里插入图片描述
  • 对象类型的值用椭圆表示,圈内部为对象的属性 这些属性可以带上他们的类型是什么
    在这里插入图片描述

引用可以改派。(可以从这个例子看出 Immutable类用双线椭圆表示)

String s = "a";
s = s.concat("b");

在这里插入图片描述
另一个修改值的例子,表明值直接修改而不是修改引用

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

在这里插入图片描述
final表示不可变的引用 ,在Snapshot diagram中不可变的引用是用双线箭头表示的
在这里插入图片描述
但注意

//引用是不可改变的,但指向的值是可以改变的,
final StringBuilder sb;
//同样,可变的引用,也可指向不可变的值 
String s;

例子:

String s1 = new String("abc");
List<String> list =new Arraylist<String>();
list.add(1);

s1 = s1.concat("d");
System.out.println(list.get(0));

String s2 = s1.concat("e");
list.set(0,s2);
System.out.println(list.get(0));

注意
第一次会输出 abc
第二次会输出 abcde
Snapshot Diagram如下图所示
在这里插入图片描述

Complex data types: Arrays and Collections

Arrays 数组

Arrays are fixed-length sequences of another type T
数组是存放固定数量T类型变量的数据结构

int[] A = new int [100]; 

这是个java中长度为100的整数数组,这个数组可以装下可能的int类型的数,但是长度无法改变

Collections 集合

List

Lists are variable-length sequences of another type T
List可以起到与数组同样的作用,但是长度可以变!

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

一些List提供的操作

list.get(i);
list.set(i,n);
list.size();

需要注意的是

  • List是一个接口Interface(不能被实例化)
  • List的成员必须是对象类型(不能是int等)

Set

A Set is an unordered collection of zero or more unique objects

Set<Object> s1 = new HashSet<Object>();
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中出现一次,
  • set是一个抽象接口

Map

A Map is similar to a dictionary (key-value)

Map<K, V> map = new HashMap<K, V>();
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像是一个映射表 从K映射到V
map也是一个抽象接口

Iterator

** a mutable type 迭代器**
迭代器的方法

  • next() 返回集合中的下个元素 mutator method可以修改数据!
  • hasNext() 判断集合中是否有下一个元素
  • remove() 在集合中移除当前项

使用迭代器是因为如果使用for循环修改集合中的对象,程序会出现意想不到的错误

public static void dropCourse(Arraylist<String> subjects){
	for(String subject:subjects){
		if(subject.startsWith("6.")){
			subjects.remove(subject);//尝试移除集合中所有以6.开头的字符串
		}
	}
}

程序在运行时会报错在这里插入图片描述
可以通过迭代器来实现这样的操作

public static void dropCourse(Arraylist<String> subjects){
	Iterator iter = subjects.iterator();
	while(iter.hasNext()){
		String subject = iter.next();
		if(subject.startsWith("6.")){
			iter.remove();//尝试移除集合中所有以6.开头的字符串
		}
	}
}

Useful immutable types

基本类型及其封装对象类型都是不可变的
Collections 提供了包装list,set,map的方法

Collections.unmodifiableList();
Collections.unmodifiableSet();
Collections.unmodifiableMap();

这种包装器得到的结果是不可变的:只能看
但是这种“不可变”是在运行阶段获得的,编译阶段无法据此进行静态检查
也就是说,如果代码中有对这样产生的集合进行修改,程序是不会报错的。只有运行时才会报错。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值