欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

软件构造笔记 03

程序员文章站 2022-03-01 15:48:50
...

数据类型基础知识

数据类型: 一组值以及可以对其执行的操作

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

java中包括基本数据类型和对象数据类型,二者对比情况:

基本数据类型 对象数据类型
int, long, byte, short, char, float, double, boolean Classes, Interfaces, arrays, enums, annotations
只有值,没有ID(与其他值无法区分) 既有ID,也有值
不可变的(immutable) 一些可变(mutable),一些不可变
在栈中分配内存 在堆中分配内存
无法实现表达的一致 可以实现表达和泛型的一致
代价低 代价高

对象类型的层次结构:

继承: 如果类A继承了类B,A称为B的子类,B称为A的超类,子类会获得超累所有可见的实例域和方法,并且可以通过重写来改变方法的行为,Java中所有的类都继承自Object类,如果一个类没有显式地继承一个类,则会继承Object。

包装基础数据类型: 通过将基础数据类型包装为对象数据类型(Boolean,Integer,Short,Long,Character,Float,Double)以实现表达的一致性,通常实在定义容器类型时使用,但会降低性能,故在一般情况下尽量避免使用。

//e.g.
List<Integer> list = new Array<Integer>();
list.add(1);
//1不是Integer对象类型,但编译能自动完成转换,但会降低效率,等价于
list.add(Integer.valueof(1));

静态/动态类型检查

静态类型语言(如java): 所有变量的类型在编译时已知,因此编译器可以推导表达式类型,在编译阶段进行类型检查。

例:如果变量 a 和变量 b 被定义为 int 型,编译器会推导出 a+b 也是 int 型。

动态类型语言(如Python): 在运行阶段进行类型检查

静态检查内容

为关于类型的检查,在编译阶段进行检查,避免将错误带入到运行阶段。

  • 语法错误(动态类型语言也会进行此类检查)
  • 类名/函数名错误
  • 参数数目错误
  • 参数类型错误
  • 返回值类型错误

动态检查内容

为关于值的检查,如除零错误。

  • 非法的参数值
  • 非法的返回值
  • 越界
  • 空指针

可变性和不可变性

两种变化

改变一个变量: 将该变量指向另一个存储空间

改变一个变量的值: 在该变量指向的存储空间中写入一个新的值

变化是麻烦的源头,但程序不能没有变化,好的程序员应该尽可能避免变化以避免副作用。

不变性

不变性: 一种重要的设计原则

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

不变引用: 一旦确定其指向的对象,不能再改变指向其他对象,如Java中的 final 关键字

关于final的补充:fianl类无法派生子类,final变量无法改变值或引用,final方法无法被子类重写

可变对象与不可变对象

不可变对象: 一旦被创建,始终指向同一个值或引用,如String类

对于不可变对象String执行如下代码:

String s = "a";
s = s.concat("b");//这一方法并不会改变原先的值,而是创建一个新的String对象

不可变对象: 拥有方法可以修改自己的值或引用,如StirngBuilder类

对于可变对象StringBuilder执行类似代码

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

二者对应的snapshot diagram如下:
软件构造笔记 03
在上述例子中,只有一个引用指向该对象,二者并没有区别,但有多个引用时,差异就出现了

在上述代码的基础上继续执行:

Sting t = "c";
t = t + "c";

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

二者对应的snapshot diagram如下:
软件构造笔记 03
可变对象的优点: 在使用不可变类型时,对其频繁修改会产生大量的临时拷贝,而可变类型往往会进行最小化拷贝以提高效率,并且可变类型也适合在多个模块之间共享数据。

但是可变性可能使得难以理解程序在做什么,更难满足方法规约,此时不可变类型更加安全,在其他质量指标上表现更好。

可变对象的风险:

  • 传递可变值: 将一个可变对象作为方法的参数传入 ,若该方法在无意中改变了这个对象的值,则方法外部的对象也会随之改变,会产生难以察觉的错误,同时,这样的程序会变得难以理解。
/**@return the sum of the number in the list */
public static int sum(List<Integer> list){
	int sum = 0;
	for(int x : list)
		sum += x;
	return sum;
}

/**@return the sum if the absolute values of the numbers in the list */
public static int sumAbsolute(List<Integer> list){
	// let's reuse sum(), first wi take absolute values
	for(int i = 0; i < list.size(); ++i){
		list.set(i, Math.abs(list.get(i)));
	return sum(list);
}

// meanwhile, somewhere else in the code...
public static void main(Stirng[] args){
	List<Integer> myData = Arrays.asList(-5, -3, -2);
	System.out.println(sumAbslute(myData));         //the list myData will change to [5, 3, 2]
	System.out.println(sum(myData));                //the result will be 10
}
  • 返回可变值: 将一个可变对象的值作为返回值,在接收该变量并改变其值后,可能会产生错误。
/** @return the first day of spring this year */
public static Date startOfSpring(){
	if(groundhogAnswer == null)
		groundhogAnswer = askGroundhog();
	return groundhogAnswer;
	static Date groundhogAnswer = null;
}

public static void main(String[] args){
	Date partyDate = startOfSpring();
	partyDate.setMonth(partyDate.getMonth() + 1);
	Date homeDate = startOfSpring();  //what the homeDate wiil be?
	
}

安全使用可变对象:

  • 在仅有一个引用时使用,如作为局部变量使用
  • 防御式拷贝,返回一个全新的对象,但是可能造成内存浪费,而不可变对象不需要防御式拷贝
	return new Date(groundhogAnswer.getTime());

复杂数据类型

Array: 数组是另一种数据类型T的定长序列

List: List是另一种数据类型T的边长序列

Set: Set是0个或多个唯一对象(互不重复)的无序集合

Map: Map是一种类似字典的(key-value)集合

Iterator: 一个遍历一组元素并逐个返回元素的对象

不可变包装

List,Set,Map的通用实现是可变的,Collections 类可以获得这些可变集合的不可变观察,这种 “不可变” 实在运行阶段获得的,编译阶段无法据此进行静态检查

List<String> list = new ArrayList<>();
list.add("ab");
List<String> listCopy = Collections.unmodifiableList(list);
listCopy.add("c");  //执行时会抛出异常
list.add("c");
System.out.println(listCopy.size()); //如果注释掉抛出异常的代码,输出值为2

两种主要用法:

  • 当集合构建完成后将其转变为不可变类型
  • 对特定用户对数据结构访问设置只读权限