程序设计语言中的数据类型
类型和变量:
类型是一组值,以及可以对这些值执行的操作。数据类型:一组值以及可以对其执行的操作
示例:
–布尔值:真值(真或假)。
–int:整数(0,1,-47)。
–double:实数(3.14,1.0,-2.1)。
–字符串:文本(“你好”、“示例”)。
变量:存储特定类型值的命名位置
–形式:类型名称;
–示例:字符串foo
变量:用特定数据类型定义,可存储满足类型约束的值Java有几种基本类型基本数据类型,例如:
–int(对于5和-200这样的整数,但限于2^31范围,或者
大约20亿)
–long(适用于2^63以上的较大整数)
–布尔值(真或假)
–double(对于代表实数子集的浮点数
数字)
–char(对于像“A”和“$”这样的单个字符)
Java也有对象类型对象数据类型,例如:
–字符串表示一系列字符。
–BigInteger表示任意大小的整数。
按照Java惯例,基本类型是小写的,而对象类型以大写字母开头。
对象类型形成层次结构:
根是对象(所有非基元都是对象)
–除对象之外的所有类都有一个父类,用扩展子句指定
class Guitar extends Instrument { … }
如果省略扩展子句,默认为对象
一个类是其所有超类的实例(继承关系)
–从其超类继承可见字段和方法
–可以覆盖方法以改变它们的行为
包装基本类型
将基本类型包装为对象类型
通常是在定义容器类型的时候使用它们(容器类型操作的元素要求是
对象类型,所以需要对基本数据类型进行包装,转换为对象类型)
一般情况下,尽量避免使用(会降低性能)
一般可以自动转换
例:
操作符
操作符:执行简单计算的符号操作符
–赋值:=
–加法:+
–减法:-
–乘法:*
–除法:/
操作顺序:遵循标准数学规则
– 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:big integer×big integer→big integer。
–作为一项功能。例如,Math.sin(theta)将操作sin称为:double → double。在这里,数学不是一个对象。它是包含sin函数的类。
静态与动态数据类型检查
Java是一种静态类型的语言。
–所有变量的类型在编译时(在程序运行之前)都是已知的,因此编译器也可以推导出所有表达式的类型。
–如果a和b被声明为int,那么编译器会得出a+b也是int的结论。
–实际上,Eclipse环境在您编写代码的时候就这样做了,所以当您还在打字的时候,就会发现很多错误。
– 在编译阶段进行类型检查
在像Python这样的动态类型语言中,这种检查被推迟到运行时(当程序运行时)。
语言可以提供的三种自动检查:
–静态检查:甚至在程序运行之前就自动发现了错误。
–动态检查:执行代码时会自动发现错误。
–不检查:语言根本不能帮助你找到错误。你必须自己小心,否则最终会得到错误的答案。
不用说,静态捕捉一个bug比动态捕捉它好,动态捕捉它比根本不捕捉它好。静态检查 >> 动态动态 >> 无检查
静态检查
静态检查意味着在编译时检查错误。
- bug是程序的毒药。
静态类型防止一大类bugs感染您的程序:准确地说,是对错误类型的参数应用操作导致的bugs。
如果你写了一行不完整的代码,比如:“5”*“6”
试图将两个字符串相乘,那么静态类型将在您仍在编程时捕获此错误,而不是等到执行期间到达该行 - 动态类型检查的语言也会进行静态检查(除了类型外的其他语法错误)
类名/函数名错误,就像Math.sine(2) .(右边
名字是sin) - 参数数目错误,比如Math.sin(30,20)。
- 参数类型错误,比如Math . sin(“30”)。
- 返回值类型错误,比如从声明返回int的函数中返回“30”。
动态检查
- 非法的参数值。例如,整数表达式x/y只有在y实际为零时才是错误的;否则它会起作用。所以在这个表达式中,被零除不是静态误差,而是动态误差。
- 非法的返回值,即当特定返回值无法在类型中表示时。
- 越界,例如,对字符串使用负的或太大的索引。
- 空指针
静态检查倾向于类型,与变量的具体值无关的错误。
–静态类型保证一个变量将从该集合中获得一些值,但是我们直到运行时才知道它到底有哪个值。
–因此,如果错误仅由某些值引起,如被零除或索引超出范围,那么编译器不会引发静态错误。
相比之下,动态检查倾向于特定值引起的错误
基本类型不是真数字
Java和许多其他编程语言中的一个陷阱是,它的原始数值类型有特例不像我们习惯的整数和实数。
因此,一些真正应该动态检查的错误根本没有检查。
–整数除法:5/2不返回分数,它返回截断的整数。
–整数溢出。如果计算结果太正或太负而不适合那个有限的范围,它会悄悄地溢出并返回一个错误的答案。(无静态/动态检查!)例如,int big = 200000 * 200000
–浮点类型中的特殊值。NaN(“不是数字”)、POSITIVE_INFINITY, and NEGATIVE_INFINITY.(正无穷大和负无穷大)
- Int a = resulOfFunction(XX);//例如,-9
- Math.sqrt(a)的结果是NaN,不是动态错误!
例:
改变变量或变量值
改变一个变量、改变一个变量的值,二者有何区别?
改变一个变量:将该变量指向另一个存储空间
改变一个变量的值:将该变量当前指向的存储空间中写入一个新的值。
- 变化是麻烦的源头,但程序不能没有变化
- 尽可能避免变化,以避免副作用
不变性
不变性:重要设计原则
不变数据类型:一旦被创建,其值不能改变
如果是引用类型,也可以是不变的:一旦确定其指向的对象,不能再被改变指向其他对象
- 要使引用不可变,请用关键字final声明它
final int n = 5;
final Person a = new Person(“Ross”);
编译器进行静态类型检查时,如判断final变量首次赋值后发生了改
变,会提示错误。
尽量使用final变量作为方法的输入参数、作为局部变量。
final表明了程序员的一种“设计决策”
提示:
final类无法派生子类
final变量无法改变值/引用
final方法无法被子类重写
可变性与不可变性
不变对象:一旦被创建,始终指向同一个值/引用
可变对象:拥有方法可以修改自己的值/引用
String是不可变类型的一个例子。
String对象总是代表相同的字符串。
由于String是不可变的,一旦创建,String对象总是具有相同的值。
要在String末尾添加内容,您必须创建一个新的String对象:
StringBuilder是可变类型的一个例子。
它有删除部分字符串、插入或替换字符等方法。
这个类有改变对象值的方法,而不仅仅是返回新值:
所以二者有区别吗?最终的值都是一样的
当只有一个引用指向该对象,二者没有区别
但是当有多个引用的时候,差异就出现了
当另一个变量t指向与s相同的String对象,而另一个变量tb指向与sb相同的StringBuilder时,不可变对象和可变对象之间的差异就变得更加明显
使用不可变类型,对其频繁修改会产生大量的临时拷贝(需要垃圾回收)
–在构建最终字符串的过程中,第一个数字(“0”)实际上被复制了n次,第二个数字被复制了n-1次,以此类推。
–实际上,即使我们只连接了n个元素,复制这些数据也要花费O(n2)个时间。
StringBuilder旨在最大限度地减少这种复制。可变类型最少化拷贝以提高效率
–它使用简单但巧妙的内部数据结构来避免任何复制,直到最后,当您使用toString()调用请求最终字符串时。
所以:
使用可变数据类型,可获得更好的性能
也适合于在多个模块之间共享数据
既然如此,为何还要用不可变类型?
不可变类型更“安全”,在其他质量指标上表现更好
可变性使得难以理解程序正在做什么,更难满足方法的规约
折中,看你看重哪个质量指标