Java数据类型 方法 递归调用

Java数据类型 方法 递归调用

1.基础数据结构

java的基本数据类型有8种,包括6种数字类型、1种字符类型、1种布尔类型。

基本数据类型总览

  • 数字类型

    • 4种整数类型
      • byte/short/int/long
    • 2种浮点数类型
      • float/double
  • 1种字符类型

    • char

      表示单个字符。

      Java使用统一码对字符进行编码。

  • 1种布尔类型

    • boolean

      包括truefalse两种取值。

基本数据类型 存储大小 取值范围 默认值
byte 8位有符号 −27 到 27−1 0
short 16位有符号 -215到215-1 0
int 32位有符号 -231到231-1 0
long 64位有符号 -263到263-1 0L
float 32位,符合IEEE 754标准 负数 -3.402823e+38 到 -1.401298e-45,正数 1.401298e-45 到 3.402823e+38 0.0f
double 64位,符合IEEE 754 标准 负数 -1.797693e+308 到 -4.9000000e-324,正数 4.9000000e-324 到 1.797693e+308 0.0d
char 16位 0到216-1 '\u0000'
boolean 1位 true和false false

JVM规范中,boolean变量当作int处理,也就是4字节;

而boolean数组当做byte数组处理,即boolean类型的数组里面的每一个元素占1个字节。

数字类型直接量

直接量是在程序种直接出现的常量值

将整数类型的直接量赋值给整数类型的变量时,只要直接量没有超过变量的取值范围,即可直接赋值。

如果直接量超过了变量的取值范围,则会导致编译错误。

整数类型的直接量默认是int类型。

如果直接量超出了int类型的取值范围,则必须在其后面加上字母L或l,将直接量显式声明位long类型,否则会导致编译错误。

浮点类型的直接量默认是double类型。

如果要将直接量表示成float类型,则必须在其后面加上字母F或f。将double类型的直接量赋值给float类型的变量是不允许的,会导致编译错误。

基本数据类型之间的转换

有时需要把不同类型的值混合运算,因此需要对数据类型进行转换。

数字类型转换

不同的数字类型对应不同的范围,按照范围从小到大顺序依次是:

byte--->short--->int--->long--->float--->double

将小范围类型的变量转换为大范围类型称为拓宽类型,不需要显示声明类型转换。

大范围类型的变量转换为小范围类型成为缩窄类型必须显示声明类型转换,否则会编译错误。

字符类型与数字类型之间的转换

字符类型与数字类型之间可以转换。

将数字类型转换成字符类型时,只使用整数的低16位(浮点数类型将整数部分转换位字符类型)。

将字符类型转换为数字类型时,字符的统一码转换成指定的数值类型。如果字符的统一码超出了转换成的数值类型的取值范围,则必须显示声明类型转换

布尔类型不能与其他基本数据类型进行转换

布尔类型不能转换成其他基本数据类型,其他基本数据类型不能转换为布尔类型。


2.方法

java中的方法,是一个执行一个操作而组合到一起的语句组。

方法包括方法头、方法体,方法头又可以分成修饰符、方法名、返回值类型、参数列表。

  • 修饰符:修饰符是可选的,告诉编译器如何调用该方法
  • 方法名:方法实际名称
  • 返回值类型:方法可以返回一个值,此时返回值类型是方法要返回的值的数据类型。无返回值的时候是void
  • 参数列表:定义在方法头中的变量成为形式参数或参数,简称形参。当方法调用时,需要给参数传递一个值,成为实际参数,简称实参。参数列表指明方法中的参数类型、次序、数量。参数是可选的,方法可以不包含参数
  • 方法体:方法体包含具体的语句集合

方法名和参数列表共同构成方法签名

参数的值传递

调用方法时,需要提供实参,实参与形参必须次序相同,成为参数顺序匹配。实参必须与方法签名中的形参在次序上和数量上匹配,在类型上兼容,兼容的意思是不需要显式声明类型转换,即类型相同或者类型转换为拓展转换。

在调用参数的方法时,实参的值赋值给形参。称为值传递。

Java中只有值传递。

无论形参在方法中如何改变,实参不受影响。

  • 当参数类型时基本数据类型时,传递的是实参的值,因此不能对实参进行修改。
  • 当参数类型是对象时,传递的是对象的引用。此时可以根据对实参的引用的对象进行修改,但是不能让实参引用新的对象。

方法的重载

方法的重载是在同一个类中的多个方法具有相同的名称,但是方法签名不同。编译器可以根据方法签名决定调用哪个方法。

由于方法签名由方法名和参数列表共同构成,因此方法的重载等同于多个方法有相同的名称和不同的参数列表。

方法的重载可以增加程序的可读性,执行相似操作的方法应该具有相同的名称。

关于方法的重载,需要注意以下两点。

  • 方法签名只由方法名和参数列表共同构成,因此被重载的方法必须具有不同的参数列表,而不能通过不同的修饰符和返回值类型进行方法的重载。
  • 如果一个方法调用有多个可能的匹配,则编译器会调用最合适的匹配方法,如果编译器无法判断哪个方法最匹配,则成为歧义调用,会导致编译错误。

3.递归

程序调用自身的编程技巧称为递归。

递归方法是直接或间接调用自身的方法。

递归的要点

定义递归方法时,需要定义递归的初始状态、初始状态的调用、递归调用。

初始条件也称为终止条件,即最简单的情况,此时应该直接给出如何处理初始状态。

对于非初始状态,则需要进行递归调用,对子问题进行求解,直到初始状态,然后将结果返回给调用者,知道传给原始的调用者。

递归必须定义初始状态,且保证所有的递归调用都能达到初始状态,否则会发生无限递归,导致栈溢出。

递归的优点

代码简洁易于理解。

如果问题满足递归的特点,即可以分解成子问题,且子问题与原始问题相似,则可以使用递归给出直接、自然、简单的解法。

递归的缺点

  • 时间和空间的消耗比较大。每一次函数调用都需要在内存栈中分配空间,对栈的操作还需要时间,因此时间复杂度和空间复杂度都会比较高。
  • 如果子问题之间存在重叠,则在不加记忆化的情况下,递归会产生重复计算。导致时间复杂度过高。
  • 由于栈的空间有限,如果递归调用的次数太多,则可能导致调用栈溢出。

尾递归

当递归调用是方法中最后执行的语句且它的返回值不属于表达的一部分,这个递归调用就是尾递归。

尾递归的特点是在返回时直接传回原始的调用者,而不经过中间的调用者。这个特点很重要,现在大多数现代的编译器都会利用该特点来自动生成优化代码。

尾递归其实就是把上一次递归计算的结果保存下来给下一次递归用,关键在于每次调用都在保存结果,递归则是不收集结果,只能最后才展开。

使用尾递归来代替普通的递归,可以在空间和时间上都带来显著的提升。

代码实现

以下代码是计算斐波那契数列的普通递归与尾递归的实现。

public class Fibonacci {
    public static long fibonacci(long index) {
        if (index <= 1) {
            return index;
        } else {
            return fibonacci(index - 1) + fibonacci(index - 2);
        }
    }

    public static long fibonacciTailRecursion(long index) {
        return fibonacciTailRecursion(index, 0, 1);
    }

    public static long fibonacciTailRecursion(long index, int curr, int next) {
        if (index == 0) {
            return curr;
        } else {
            return fibonacciTailRecursion(index - 1, next, curr + next);
        }
    }
}

使用普通递归,会产生大量重复计算,导致时间复杂度过高。

使用尾递归,则不会有重复计算。

总结

  1. 递归的优点和缺点分别有哪些?

    • 递归的优点是代码简洁且易于理解,缺点是时间和空间的消耗比较大、可能产生重复计算以及可能导致调用栈溢出。
  2. 可以通过什么方式解决递归的缺点?

    • 解决递归的缺点有多种做法。尾递归是一种做法,另外还可以通过加记忆化的方式避免重复计算从而提高效率,以及改用迭代实现。
posted @ 2022-05-17 08:36  萝卜不会抛异常  阅读(318)  评论(0编辑  收藏  举报