一、数据类型
数据类型:一些值的集合,以及在值上的操作
Java中的5种原始类型:int,boolean,double,long,char
对象类型:String,BigInteger
java中基本数据类型是小写,而对象数据类型是首字母大写
变量:用特定数据类型定义,并且存储满足类型约束的值
java中两种数据类型的比较:
除了object是祖先类以外,其他类都是继承了某一个类,继承形成了层次结构
子类从超类中继承可见的方法和成员,且可以重写超类方法
包裹类型:Boolean,Integer,Double,Short,Character,Float
通常在定义集合类型的时候使用它们,一般情况下尽量避免使用
包裹类型可以向下兼容它们的基本类型
重载(Overload):方法具有相同的名称,而参数列表(个数/类型)不相同,重载对返回类型是否相同不做要求
二、动态类型检查与静态类型检查:
java是静态类型语言:在编译阶段进行类型检查
动态类型语言:在运行阶段进行类型检查
一种语言提供的检查类型:静态,动态,无检查
静态>动态>无检查
Java验证类型是否始终匹配
静态类型检查在编译阶段发现错误,避免将错误带到运行阶段,提高了程序的健壮性和正确性
静态类型检查:
语法错误,函数名错误,参数类型错误,参数个数错误,返回值类型错误
动态类型检查:
非法的参数值:如整数x/y,只有在y实际为0时才会错误
非法的返回值:最后得到的返回值无法用声明类型表示,越界,空指针
静态类型往往是检查类型的,而动态类型检查检查特定值,一个错误由特定值触发,那编译阶段不会报错
动态类型错误
三、可变性与不变性:
赋值:用“=”,可以和变量声明结合在一起:double a=2.0
改变一个变量:将它指向另一个值的存储空间
改变一个变量的值:向它当前所指的值的存储空间中写入另一个值
程序不能没有变化,但是要尽量避免变化以避免副作用
不变数据类型:一旦被创建,值不能被改变 例如:String是,包裹类型是,List不是
在任何变量前面加final也可以使其成为不可变引用
final int a=5;(不能再被赋值了)
编译器会检查final修饰的变量值是否改变
尽量使用final变量作为方法的参数
注意:final类无法派生子类;final变量无法改变值/引用;final方法无法被子类重写
一旦创建一个String对象,它会始终指向同一个值,想获得一个新的字符串,需要创建一个新的String对象
2.StringBuilder是可变类型,能够改变对象的值
在main函数中,由于List是可变类型,所以计算sumAbsolute时将myData中的负数全部改为了绝对值,因此在计算sum时,结果为10,而不是预期的-10
可变类型的风险:首先容易出现问题,它改变了参数的值,超出了spec的范畴,且这种错误很难追踪和发现
其次,这种错误也很难被程序员理解,他们不仅要阅读main的代码,还要阅读sum和sumAbsolute的具体实现
例子2:返回可变类型参数:
有两个问题:
1.Date中的month取值0-11,如果到了12月,month=11,此时再加1,变成12,再set,由于setMonth的规约限制在0-11,所以会出现问题,其次,本例Date是可变类型,partyDate和groundhogAnswer指向同一个Date,如下快照图:
那么如果多次调用startofSpring,可能导致Month不断地增加,产生错误的值
可以使用java.time包中的LocalDateTime , Instant, 它们是不可变的
或者使用防御式拷贝:
return new Date(groundhogAnswer .getTime());这种拷贝大部分时候不会被客户修改,会造成大量内存浪费
不可变类型则会节省频繁复制的代价
在局部变量上使用可变类型是安全的,因为不会共享,但是在由多个引用(别名)情况下,可变类型会变得非常不安全
防御式拷贝也会被攻击:
通过date内部的set方法改变end的年份
四、代码快照图(run-time,moment,code level)
正在进行中的方法和本地变量在堆栈中,对象在堆中
对象值是用其类型标记的圆。–当我们想要显示更多细节时,我们在其中写字段名,箭头指向它们的值。对于更详细信息,字段可以包括它们声明的类型。
不可变对象(其设计者希望总是表示相同的值)在快照图中用双边框表示,就像我们的图中的String字符串对象一样。
不可变的引用:双线箭头
引用是不可变的,其指向的值可以是可变的;可变的引用,也可以指向不可变的值
对可变值的不可变引用如下:
静态检查到了final修饰的变量要改变,于是报错
还可以对不可变值进行可变引用:
五、复杂数据类型:数组和集合
数组:定长,提前声明长度,会越界
List:一个接口,变长,里面的成员是对象
迭代:对数组:用计数变量,从0到<array.length
对list:for each(但是不能增删元素)
Set:集合:每个对象只能在里面存有一个或不存有,方法:
Map:键,值成对存储
实现方式:List:LinkedList,ArrayList
Set:HashSet
Map:HashMap
next()返回集合中的下一个元素,它是可变方法
hasNext()判断迭代器是否到达集合结尾
一个迭代器实例
代码快照图
list双线是因为用final修饰,由于ArrayList本身是可变的,所以final并不能阻止对list内部的改变
迭代器思想:提供一个访问元素的通用中间件,以一种统一的格式访问各种集合类
但可变性会暗中破坏迭代器:
删除所有以“6.”开头的课程,测试用例与预期如上
但是实际上:最后一个用例执行结果:
代码快照图如下:
for each此时会显示越界
使用迭代器自己的remove方法,迭代器会自动调整索引
但是不会完全解决问题,如果多个迭代器指向同一个集合类,它们之间不会告知对方各自对集合类的修改
六、不可变类型:
基本类型和它们的包裹类型都是不可变的
集合实用程序类有一些方法来获取这些可变集合的不可修改的视图:
但是,对list的改变还是会导致对listCopy的改变:
不可修改的包装器有两个主要用途,如下:
在集合构建后使其不可变。在这种情况下,最好不维护对备份集合的引用。这绝对保证了不变性。允许某些客户端只读访问您的数据结构。您保留对备份集合的引用,但分发对包装器的引用。通过这种方式,客户端可以查看但不能修改,同时您保持完全访问。
要从某些已知值创建不可变的集合,使用