数据类型和类型检测

本文探讨了编程语言中的数据类型、变量概念,特别关注Java中的基本数据类型和对象数据类型。文章详细介绍了动态与静态数据类型检测的区别,强调了可变性和不可变性的使用以及防御式拷贝的重要性,还讨论了数组、集合等复杂数据类型的特点和类型检查的作用。
摘要由CSDN通过智能技术生成

Data Type And Type Checking

1.编程语言中的数据类型

类型和变量

  • 一个类型是一系列值的集合,这些集合可以抽象出一个相同的特点,并且可以相互实现计算

    • 例如:
      • 布尔类型:true or false
      • 整形:1,2,3…
      • 浮点数类型:1.2,2.4,5.55…
      • 字符串类型:“hello”,“world”
  • 变量:一个被命名的地址,存储了某种类型的值

JAVA中变量指向的地址不一定存储的就是我们想要的值,也有可能是值的“遥控器”

Java中的数据类型

Java是一个纯粹的面向对象的编程语言,因此在Java中几乎所有变量都是一个对象,但是为了高效性,可移植性和轻便性,Java也有一些基本数据类型

  • 基本数据类型:int,double,boolean,char…
    • 基本数据类型存储在栈上
    • 每个基本数据类型都有对应的包装类
    • 包装类和基本数据类型直接可以自动装包和拆包
  • 对象数据类型:String,BigInteger,BigDecimal…
    • 对象数据类型存储在堆上
    • 定义的对象变量应该是一个引用,也就是上文说到的"遥控板"
    • 引用是存储在栈上的
    • 对象数据类型在Java中是单继承的,并且有一个最终父类:Object
    • 继承关系的层次结构如图1
      图1

虽然在栈上开辟空间存储数据以及销毁数据要快于在堆上,不过在现代结构的优化下,在堆上和栈上存储的代价几乎相同

2.动态 Vs. 静态数据类型检测

类型转换

int a = 2;
double a = 2;
int a = (int)18.7
double a = (double)2/3;

int a = 18.7;
String a = 1;

结果:以上代码中1-4显示编译通过,6-7编译错误

向大的类型中存储小的值,会发生自动类型转换,不会发生问题;但是向小的类型中存储大的值,就会发生精度丢失,因此会报错,需要显示的指定,是否要丢失这部分精度

静态检测和动态检测

  • 静态检测:在代码运行之前发现bug
  • 动态检测:在代码运行时发现bug
  • 无检测:语言不帮助你检测,自己找bug

三种检测方式的效率:静态>>动态>>无检查

静态检测

在编译阶段发现错误,避免将错误带入到运行阶段,可提高程序正确性与健壮性

运行阶段发现bug会带来更多的时间代价

检测内容:

  • 语法错误,例如:多了一个逗号或奇怪的单词
  • 类名/方法名错误,例如:Math.abd(-1)
  • 参数数目错误,例如:Math.abs(20, -10)
  • 参数类型错误,例如:Math.abs("hello")
  • 返回类型错误,实际的返回类型和方法要求的不能隐式转换
动态检测

在运行阶段发现错误

检测内容:

  • 非法的参数值,实参类型不能隐式转换为形参类型
  • 非法的返回值,返回类型和变量类型不能隐式转换
  • 越界:遍历数组、集合的时候索引超出了它们的容量
  • 空指针:对象的引用指向null

动态与静态的区别:静态检查只考虑编译阶段能确定的“值”;动态检查关心运行阶段能确定的“值”

3.可变性与不可变性

什么是赋值?

赋值是指,在内存空间中开辟一个空间,写入特定值,并且把变量和这块空间相关联

基本数据类型 Vs. 对象数据类型

  • 基本数据类型:在栈上开辟空间,只与栈相关联
  • 对象数据类型:在栈上开辟空间存储引用,在堆上开辟空间存储对象

引用就像一个遥控板,而对象则是电视机,可以在任何地方使用遥控板控制电视机,而不需要背着电视到处移动

变与不变

int a = 10;
a = 20;

String s = "hello";
s = "world";

改变的方式有两种:改变变量和改变值

  • 改变变量:改变变量的指向,让它指向另一块空间
  • 改变值:改变变量指向的内存空间中的值,而不改变其指向

在编程中应该更多的使用immutable的数据,这样能大大提高程序的安全性

什么时候是哪种改变?
  • 改变值:基本数据类型都是改变值,可变对象数据类型访问其堆上的空间改变其中的值
  • 改变变量:对象数据类型改变其引用的值

还可以使用final关键字修饰变量,被修饰的变量其本身不可变。

对于基本数据类型,其本身就是值,因此值不可变

对于对象数据类型,其本身实际上是"遥控板"即引用,因此引用本身不可变

不变对象 Vs. 可变对象
  • 不变对象:引用指向的值不可变
  • 可变对象:拥有方法可以修改堆上数据

String类型就是一个不可变类型,不能对字符串本身进行增删的操作

StringBuilder则是一个可变类型,可以对其指向的数据进行增加删除的操作

可变类型的优势
  1. 可变类型可以最少化拷贝以提高效率
  2. 可以获得更好的性能
  3. 适用于多个模块之间共享数据
不可变类型的优势
  • 不可变类型更"安全"

4. 防御式拷贝

  • 试想以下场景:向一个方法中传入一个可变对象,这是一件相当危险的事。不同于基本数据类型和不可变数据类型,传入一个可变对象,相当于把堆上数据的权柄全部交给了这个方法,客户端的数据可以随意被远端程序员更改,虽然社会是充满真善美的,但是我们还是喜欢由自己掌握自己的命运。

  • 再试想以下场景:我重生成为了一个后端程序员,每天勤勤恳恳写代码,有一天我一如既往的打开电脑准备开启码代码的一天,发现我的数据被改了,在小小的电脑里挖呀挖呀挖,焦头烂额找不出错误,最后把代码发给了gpt,少不了一番自嘲。原来是你的一个方法里,向客户端返回了一个可变对象,客户端拿着这个对象到处霍霍,把值给霍霍乱了。

以上两个场景都是可变对象可能带来的安全隐患,贸然将数据权柄交给它人是一件相当莽撞的事情。为了解决这个问题,最好的方式就是使用不可变对象。还有一种方式就是采用所谓的防御式拷贝,简单来说就是创建一个新的可变类型对象,并且把原始对象的数据拷贝到新的堆内存上,然后将新的对象返回或传入。

  • 大部分时候这个拷贝不会被客户端修改,可能造成大量的内存浪费
  • 直接使用不可变对象,节省了频繁拷贝的代价
  • 使用可变类型最好的方法:一个堆内存只有与一个引用相关联。

5.复杂数据类型:数组和集合

数组

  • 数组是一个连续的组,其中存储相同数据类型的变量

  • Java中数组和c++中的不一样,Java中数组的数据也存储在堆上

  • 数组名也是一个引用

  • 数组中的元素通过索引访问

集合

List

List和数组很像,不同的是List的容量可以变化

List还可以对对其中的数据进行增删查改

Set

无索引,无法通过索引访问元素

Set集合中不存在相同值的元素

Map

Map中存放的是一对又一对的键值对,通过key寻找value

Map中的元素也不能通过索引访问

Map中只能每个键值只能存在一个

迭代器

迭代器是一个可以逐步遍历结合的对象

通过迭代器不能改变集合中对象的值

如果集合发生了增加或删除元素,那么之前的迭代器会失效

6.总结

  • 类型检查
    • 类型检查可以帮助提升程序的健壮性,可以帮助找出程序的错误
    • 类型检查是简单易懂的
    • 类型检查帮助你更好的维护代码
  • 可变性与不可变性
    • 可变数据具有高效性
    • 不可变数据具有安全性
    • 如何在效率和安全中选择一个合适的尺度,需要通过具体的要求进行分析
    • 关键的设计准则在于,使用尽量多的不可变对象和不可变引用
  • 26
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沅筱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值