软件构造——数据类型与类型检验

数据类型与类型检验

编程语言中的数据类型

  1. 数据类型:一组值以及可以对其执行的操作
  2. 变量:用特定数据类型定义,可存储满足类型约束的值
  3. JAVA中的基本数据类型与对象数据类型:
  • 元素:

    • 基本数据类型:int,double,long,byte,short,char,float,boolean
    • 对象数据类型:String,BigInteger
  • 是否有值和ID:

    • 基本数据类型:只有值,没有ID(与其他值无法区分)
    • 对象数据类型:既有值,又有ID
  • 是否可变:

    • 基本数据类型:不可变
    • 对象数据类型:有些可变,有些不可变
  • 空间分配位置:

    • 基本数据类型:在栈中分配内存
    • 对象数据类型:在堆中分配内存
  • 根据JAVA传统习惯,基本数据类型小写,对象数据类型的第一个字母大写

  1. 对象类型形成层次结构
  • 根节点是Object类型,除了Object外的所有类都有一个父类,这个父类一般使用extends子句进行指定,如果该子句被省略则该结点的父类默认为Object
  • 一个类是其超级父类的一个实例an instance of
    • 从其超级类中继承可见域和方法
    • 可以重写超级类中的方法
  1. 将基本数据类型使用不可变immutable容器封装为对象类型
  • 基本数据类型与对象类型的对应关系

    • boolean -> Boolean

    • int -> Integer

    • short -> Short

    • long -> Long

    • char -> Character

    • float -> Float

    • double -> Double

  • 用途:当定义容器类型时要求容器类型操作的元素类型必须是对象类型,因此如果该元素实际属于基本类型的话就需要使用基本类型对应的对象类型。

    • List<Integer> list = new ArrayList<>()
  • 注意:

    • 除非必要,建议不要使用,因为其对应的对象类型比原本的基本类型性能会低
    • 上述list后续操作可以直接使用list.add(1),元素1明显是基本类型int, 但是声明容器类型时我们声明的是对象类型Integer, 这不会报错,因为语言会对此操作自动转换,等价于list.add(Integer.valueOf(1))
  1. 操作
  • 操作符:加减乘除那些

  • 操作符优先级:括号>乘除>加减(最基本情况)

  • 字符串连接符:”+“

    • 其实是对”+“操作符的一种重载

    • String text = “hello” + “ world ” + 1234; // text = “hello world 1234”

数据类型检查

  1. 强制类型转换
  • 显式类型转换:在数据前使用”(期望类型)“方法进行转换,比如int a = (int) 18.7
  • 隐式类型转换:有很多种,可以说只要不是使用显示转换的、机器自动完成的类型转换都可以归为隐式类型转换,比如double a = 2
  1. JAVA是一种静态类型语言
  • 所有变量的类型在程序运行之前的编译阶段就已经可知,编译器因此就可以推导出所有表达式结果的数据类型,在编译时进行类型检查
  • IDE环境在你写代码时就会同步进行上述工作,因此你会发现在你正是运行程序之前,IDE就会指出你程序目前存在的错误
  1. 编程语言的自动检查:
  • 三种类型

    • 静态检查
    • 动态检查
    • 不检查
  • 概念:

    • 静态检查:在运行之前(一般是编译阶段)进行检查
    • 动态检查:在运行阶段进行检查
    • 不检查:此种编程语言完全不帮你找错,你只能通过错误的结果反推错误
  • 举例:

    • 静态检查:JAVA
    • 动态检查:PYTHON
  • 优劣

    • 静态检查 >> 动态检查 >> 不检查
    • 原因:不检查必然是最不好的,静态检查在运行之前的代码编写阶段就可以立即提醒你发生了操作符连接的操作数之间的类型不匹配错误,及时修改避免在运行阶段才发现,毕竟这种错误肯定是越早发现越好,减少修改成本。
  1. 静态检查
  • 语法错误检查(动态类型检查的语言也会进行除了类型检查外的其它语法错误)
  • 类名函数名错误检查
  • 参数数目检查
  • 参数类型错误
  • 返回值类型错误
  1. 动态检查:
  • 非法的参数值:整数除0错误
  • 非法的返回值:当返回值无法被表示成类型时非法的返回值报错在动态检查阶段
  • 越界:如对一个字符串使用过大或负索引
  • 空指针:调用空指针
  1. 静态检查和动态检查:
  • 静态检查专注于检查类型和那些无关具体数值的变量发生的错误
  • 动态检查专注于检查由特定数值引发的错误
  1. 一些不会被动态检查出来并报错的特殊”错误“
  • 整数除法: 5/2
  • 整数溢出
  • 浮点数除0错误:返回INFINITY
  • 非法参数: Math.sqrt(a) 且 a < 0

可变性与不可变性

  1. 不可变数据类型:一旦被创建其值就不可以被改变
  • 格式:在定义时前面加final
  • 如果final类型变量在首次赋值后改变了值,编译器在进行静态类型检查时就会报错
  • 优点:方法的输入参数、全局变量应尽量使用final类型变量
  • 一个final类型的class无法派生出子类
  • 一个final类型的method无法被子类重写
  1. String类型与StringBuilder类型
  • String类型:

    • String类型是一个不可变类型

    • 当一个String类型的对象发生修改时其实是隐含创建了一个新的String类型对象,并让原变量指向新String对象,旧对象依然占据着存储空间

  • StringBuilder类型:

    • StringBuilder类型是一个可变类型
    • 可以理解为StringBuilder类型就是可以不新创建对象而是直接在原对象上进行修改的增强版String类型
  • 两者的区别:

    • 当不涉及多个引用指向一个对象时两者返回值是一样的,只是存储空间String会产生浪费
    • 当涉及到多个引用指向一个对象时,有副本时,两者会有明显差异。由于StringBuilder实则只创建了一个对象(不会隐含创建新对象),因此修改任何一个指向该对象的引用都会直接修改对象本身,这也就意味着当使用其它引用调用对象时返回的是修改后的对象的值;而指向String的引用只会再创建一个新的对象,当使用其他引用调用对象时返回的时修改前的旧对象值。
    • 修改:对不可变类型的频繁修改会产生大量的临时拷贝垃圾,而可变类型则不会
    • 安全性:不可变类型比可变类型更加安全,因为在多线程同时进行的情况下,一个可变对象的值很可能被其它线程修改,而这种错误往往非常隐蔽,所以如果传入的参数是不可变类型时可以添加防御式拷贝(使用new方法)传值引用
    • 使用建议:涉及到传参这样多模块交互的场景用不可变类型或对可变类型进行防御式拷贝传值,在同一个模块中且不涉及多个引用时的局部变量就大胆使用可变类型提高效率
  1. 不可变数据类型:基本数据类型 + 基本数据类型相应的对象类型Double, Integer , String , …
  2. 可变数据类型:Date , StringBuilder , List
posted @ 2022-06-08 16:39  dcyyyyyy  阅读(50)  评论(0编辑  收藏  举报