软件构造 | Data Type and Type Checking
1 Data type in programming languages
primitive types
基本数据类型int
、long
、boolean
、double
object types
对象数据类型
2 Static vs. dynamic data type checking
Conversion by casting 类型转换
Static Typing vs. Dynamic Typing
java
是静态类型语言,所有变量的类型在编译时已知,因此编译器可以推导表达式类型,在编译阶段进行类型检查(动态类型语言: 在运行阶段进行类型检查)
静态检查 >> 动态动态 >> 无检查
静态检查:可在编译阶段发现错误,避免了将错误 带入到运行阶段,可提高程序正确性/健壮性
3 Mutability and Immutability
Changing a variable or its value
改变一个变量、改变一个变量的值,二者有何区别?
- 改变一个变量:将该变量指向另 一个存储空间。
- 改变一个变量的值:将该变量 当前指向的存储空间中写入一个新的值。
Immutability
不变数据类型:一旦被创建,其值不能改变
编译器进行静态类型检查时,如判断final
变量首次赋值后发生了改变,会提示错误
final
类无法派生 子类final
变量无法改变值/引用final
方法 无法被子类重写
Mutability and Immutability
- 不变对象:一旦被创建,始终指向同一个值/引用
- 可变对象:拥有方法可以修改自己的值/引用
String
是一种不变对象
String s = "a";
s = s.concat("b");
StringBuilder
是一种可变对象
StringBuilder sb = new StringBuilder("a");
sb.append("b");
两者的区别:当只有一个引用指向该对象 ,二者没有区别;但是有多个引用的时候,差异就出现了。
String t = s;
t = t + "c"; // t + "c"就创建了新的对象,需要在次赋给t
StringBuilder tb = sb;
tb.append("c");
- 使用不可变类型,对其频繁修改会产生大量的临时拷贝(需要垃圾回收);不可变类型更“安全”, 在其他质量指标上表现更好
- 可变类型因 为最少化拷贝,可以提高效率;使用可变数据类型,可获得更好的性能;也适合于在多个模块之间共享数据。传递可变对象是一个潜在的错误源泉,一旦被 无意中改变,则这种错误非常难于跟踪和发现
How to modify the code?
- 通过防御式拷贝,给客户端返回一个全新的Date对象(不可变类型 不需要防御式拷贝)
More Examples of Defensive Copying
Defensive Copying防御式拷贝
在Java编程中,防御式拷贝(Defensive Copying)通常用于确保数据对象的不可变性和安全性。下面通过一个示例来详细解释防御式拷贝在Java中的应用:
假设我们有一个Person
类,其中包含一个List
类型的属性List<String> phoneNumbers
,表示电话号码列表。如果我们希望在Person
类的方法中避免原始的phoneNumbers
列表被修改,就可以使用防御式拷贝来处理。
示例代码如下所示:
import java.util.ArrayList;
import java.util.List;
public class Person {
private List<String> phoneNumbers;
public Person(List<String> phoneNumbers) {
// 使用防御式拷贝,复制传入的phoneNumbers列表
this.phoneNumbers = new ArrayList<>(phoneNumbers);
}
// 获取电话号码列表
public List<String> getPhoneNumbers() {
// 返回一个新的列表,而不是直接返回原始phoneNumbers列表
return new ArrayList<>(phoneNumbers);
}
}
在上面的示例中,Person
类中的构造方法和getPhoneNumbers
方法都使用防御式拷贝来处理phoneNumbers
属性。在构造方法中,传入的phoneNumbers
列表会被复制一份,而不是直接引用;在getPhoneNumbers
方法中,同样也会返回一个新的列表的副本,而不是直接返回原始列表。
通过这种方式,无论在何时调用Person
类的方法,都不会影响原始的电话号码列表,因为每次都是操作副本而不是直接操作原始数据。这样可以确保数据对象的不可变性,避免数据被意外修改或破坏。
总结来说,防御式拷贝在Java中常用于确保数据对象的安全性和不可变性,通过复制数据对象的副本而不是直接操作原始数据,可以有效避免程序中因为数据修改而导致的问题。
4 Snapshot diagram as a code-level, run-time, and moment view
Snapshot diagrams
在Java编程中,“Snapshot diagrams”(快照图)通常指的是程序在某个时间点上(通常是调试或分析的目的)的内存状态的图形表示。这种图形表示可以帮助开发人员理解程序的内部状态、数据结构和对象之间的关系。
下面详细分析Java中快照图的概念和用途:
-
概念:
- 快照图是一种用于展示程序在某一时刻内存状态的图形表示方式,通常以树状结构或其他形式展示对象和数据结构之间的关系。
- 快照图可以显示每个对象在内存中的位置、属性值、引用关系等信息,有助于开发人员理解程序的执行过程和数据流动。
-
用途:
- 调试分析:通过快照图,开发人员可以检查程序在某个特定时间点的内存状态,有助于排查bug和理解程序的行为。
- 性能优化:快照图可以帮助开发人员监视内存使用情况,识别内存泄漏或不必要的对象创建,以优化程序性能。
- 数据结构分析:通过快照图,开发人员可以更好地理解数据结构的组织和关联,从而优化数据处理算法。
-
工具支持:
- Java生态系统中有许多工具可以生成和显示快照图,如Java Heap Profiler、VisualVM等。这些工具通常提供了丰富的功能,帮助开发人员可视化内存状态和进行深入分析。
-
示例:
- 下面是一个简单的示例代码,在调试时可以使用快照图来观察对象的状态:
public class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public static void main(String[] args) { Person p1 = new Person("Alice", 30); Person p2 = new Person("Bob", 25); // 这里可以生成快照图来观察p1和p2对象的内存状态 } }
总而言之,快照图在Java开发中是一个重要工具,有助于开发人员理解程序的内存状态和数据结构,进而提高代码质量和性能。通过使用工具生成快照图,开发人员可以更好地管理内存资源,优化程序的执行效率。
Primitive values
基本类型的值
Object values
对象类型的值
不可变对象:用双线椭圆
不可变的引用:用双线箭头
引用是不可变的,但指向 的值却可以是可变的,可变的引用,也可指向不可变的值.
在Java中,垃圾回收(Garbage Collection)是一种自动内存管理机制,用于管理和释放不再被程序所引用的内存对象,从而避免内存泄漏和提高内存利用率。以下是对Java中垃圾回收机制的简要解释:
-
内存分配和管理:
- 在Java程序中,内存分为几个区域:堆(Heap)、栈(Stack)、方法区(Method Area)等。其中,堆是用于存储对象的内存区域。
-
标记-清除算法:
- Java的垃圾回收机制通常采用标记-清除算法(Mark and Sweep)来识别和释放不再被引用的对象。
- 当Java程序中的对象不再被任何变量或引用所指向时,垃圾回收器会对这些对象进行标记,并在适当的时机将其清除(释放其占用的内存)。
-
垃圾回收器:
- Java的垃圾回收器是Java虚拟机(JVM)的一部分,负责执行实际的垃圾回收操作。
- JVM会周期性地触发垃圾回收,但具体的回收时机和策略取决于具体的垃圾回收器实现。
-
对象的可达性分析:
- 垃圾回收器通过可达性分析来确定哪些对象是“活跃”的(即仍然被程序所引用),然后清除其他对象。
- 对象的可达性是通过根对象(如线程栈中的变量、静态变量等)作为起点,对程序中的对象进行遍历,来判断对象是否可以被访问到。
-
不同类型的垃圾回收器:
- Java虚拟机提供了不同类型的垃圾回收器,如串行回收器(Serial Garbage Collector)、并行回收器(Parallel Garbage Collector)、CMS回收器(Concurrent Mark-Sweep Garbage Collector)和G1回收器(G1 Garbage Collector)等。
- 这些回收器有不同的适用场景和性能特点,可以根据具体的应用程序需求进行选择和配置。
Java中的垃圾回收机制通过自动管理内存,使得程序员无需手动释放内存,从而减少了内存泄漏和提高了程序的可靠性。然而,开发人员也需要理解垃圾回收机制的原理和实际运行情况,以便更好地优化程序性能和内存利用。
5 Complex data types: Arrays and Collections
-
Array:Arrays are fixed-length sequences of another type T.
定长数组不可改变长度 -
List:Lists are variable-length sequences of another type T .
List<Integer> list = new ArrayList<Integer>();
在Java中,数组(Array)和列表(List)都是用来存储一组元素的数据结构,但它们有一些重要的区别和特性。以下是对Java中数组和列表的详细分析:
- 数组(Array):
-
固定长度:
- 数组在创建时需要指定固定的长度,且长度不能动态改变。
-
类型必须一致:
- 数组中的元素类型必须一致,即数组中的所有元素必须是同一种数据类型。
-
直接存取:
- 数组可以通过索引直接存取元素,具有O(1)的读取时间复杂度。
-
内存分配:
- 数组在内存中是连续存储的,因此可以高效地利用缓存和内存。
-
长度不可变:
- 数组的长度一旦确定就不能改变。如果需要更改数组的长度,通常需要创建一个新的数组并复制原数组的元素。
- 列表(List):
-
动态长度:
- 列表是一个接口,有多种实现类(如ArrayList、LinkedList等),可以根据需要动态改变长度。
-
元素类型可以不同:
- 列表可以存储不同类型的元素,因为它的元素类型是通过泛型参数来定义的。
-
灵活的操作:
- 列表提供了丰富的操作方法,如添加、删除、插入、查找等,使得对数据的操作更加灵活方便。
-
遍历:
- 列表提供了便利的遍历接口(如for-each循环、迭代器等),使得对列表中元素的访问更加方便。
-
内部实现不一定连续:
- 列表的内部实现可以是基于数组或链表等数据结构,因此它的内存分配和元素访问方式可能会有所不同。
综上所述,数组是一个静态的、固定长度且元素类型一致的数据结构,拥有高效的访问速度;而列表则是一个动态的、长度可变、元素类型不固定的数据结构,提供了更多的操作方法和灵活性。在实际应用中,开发人员需要根据具体需求来选择数组或列表来存储和处理数据。
Iterating
// Iterating an array
int max = 0;
for (int i=0; i<array.length; i++) {
max = Math.max(array[i], max);
}
// Iterating a List
int max = 0;
for (int x : list) {
max = Math.max(x, max);
}
Set:A Set is an unordered collection of zero or more unique objects.
s1.contains(e) // test if the set contains an element
s1.containsAll(s2) // test whether s1 ⊇ s2
s1.removeAll(s2) // remove s2 from s1
Map:A Map is similar to a dictionary (key-value)
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
Declaration:
Note:We cannot create a collection of primitive types.
e.g. Set<int> does not work. but Set<Integer> numbers work)
Creating List, Set, and Map variables
List , Set , and Map are all interfaces:
- 它们定义了各自类型的工作方式,但不提供实施代码。
- 优势:用户有权在不同情况下选择不同的实现方式
Implementations of List , Set , and Map :
List( ArrayList and LinkedList)
、Set:(HashSet)
、Map( HashMap)
Note:The usual implementations of Java’s collections types — List, Set, Map — are all mutable: ArrayList, HashMap, etc.