Java 数值类型

Number

一般地,当需要使用数字的时候,我们通常使用内置数据类型,如:byte、int、long、double 等。然而,在实际开发过程中,我们经常会遇到需要使用对象,而不是内置数据类型的情形。为了解决这个问题,Java 语言为每一个内置数据类型提供了对应的包装类。所有的包装类(Integer、Long、Byte、Double、Float、Short)都是抽象类 Number 的子类。

Number 与子类结构

这种由编译器特别支持的包装称为装箱,所以当内置数据类型被当作对象使用的时候,编译器会把内置类型装箱为包装类。相似的,编译器也可以把一个对象拆箱为内置类型。Number 类属于 java.lang 包。下面是一个使用 Integer 对象的实例:

public class Test{
   public static void main(String args[]){
      Integer x = 5;
      x =  x + 10;
      System.out.println(x);
   }
}
// 15Copy to clipboardErrorCopied

当 x 被赋为整型值时,由于 x 是一个对象,所以编译器要对 x 进行装箱。然后,为了使 x 能进行加运算,所以要对 x 进行拆箱。

序号 方法与描述
1 xxxValue() 将 Number 对象转换为 xxx 数据类型的值并返回
2 compareTo() 将 number 对象与参数比较
3 equals()判断 number 对象是否与参数相等
4 valueOf() 返回一个 Number 对象指定的内置数据类型
5 toString() 以字符串形式返回值
6 parseInt() 将字符串解析为 int 类型

Math

Java 的 Math 包含了用于执行基本数学运算的属性和方法,如初等指数、对数、平方根和三角函数。Math 的方法都被定义为 static 形式,通过 Math 类可以在主函数中直接调用。

public class Test {
    public static void main (String []args)
    {
        System.out.println("90 度的正弦值:" + Math.sin(Math.PI/2));
        System.out.println("0度的余弦值:" + Math.cos(0));
        System.out.println("60度的正切值:" + Math.tan(Math.PI/3));
        System.out.println("1的反正切值:" + Math.atan(1));
        System.out.println("π/2的角度值:" + Math.toDegrees(Math.PI/2));
        System.out.println(Math.PI);
    }
}
/**
90 度的正弦值:1.0
0度的余弦值:1.0
60度的正切值:1.7320508075688767
1的反正切值:0.7853981633974483
π/2的角度值:90.0
3.141592653589793
**/Copy to clipboardErrorCopied

下面的表中列出的是 Math 类常用的一些方法:

序号 方法与描述
7 abs() 返回参数的绝对值
8 ceil() 返回大于等于( >= )给定参数的的最小整数,类型为双精度浮点型
9 floor() 返回小于等于(<=)给定参数的最大整数
10 rint() 返回与参数最接近的整数。返回类型为 double
11 round() 它表示四舍五入 ,算法为 Math.floor(x+0.5),即将原来的数字加上 0.5 后再向下取整,所以,Math.round(11.5) 的结果为 12,Math.round(-11.5) 的结果为-11
12 min() 返回两个参数中的最小值
13 max() 返回两个参数中的最大值
14 exp() 返回自然数底数 e 的参数次方
15 log() 返回参数的自然数底数的对数值
16 pow() 返回第一个参数的第二个参数次方
17 sqrt() 求参数的算术平方根
18 sin() 求指定 double 类型参数的正弦值
19 cos() 求指定 double 类型参数的余弦值
20 tan() 求指定 double 类型参数的正切值
21 asin() 求指定 double 类型参数的反正弦值
22 acos() 求指定 double 类型参数的反余弦值
23 atan() 求指定 double 类型参数的反正切值
24 atan2() 将笛卡尔坐标转换为极坐标,并返回极坐标的角度值
25 toDegrees() 将参数转化为角度
26 toRadians() 将角度转换为弧度
27 random() 返回一个随机数

floor,round 和 ceil

Math.floor 是向下取整,Math.ceil 是向上取整,Math.round 是四舍五入取整:

  • 参数的小数点后第一位小于 5,运算结果为参数整数部分。
  • 参数的小数点后第一位大于 5,运算结果为参数整数部分绝对值 +1,符号(即正负)不变。
  • 参数的小数点后第一位等于 5,正数运算结果为整数部分 +1,负数运算结果为整数部分。

通过下表可以看到各个方法的实例:

参数 Math.floor Math.round Math.ceil
1.4 1 1 2
1.5 1 2 2
1.6 1 2 2
-1.4 -2 -1 -1
-1.5 -2 -1 -1
-1.6 -2 -2 -1
public class Main {
  public static void main(String[] args) {
    double[] nums = { 1.4, 1.5, 1.6, -1.4, -1.5, -1.6 };
    for (double num : nums) {
      test(num);
    }
  }
  private static void test(double num) {
    System.out.println("Math.floor(" + num + ")=" + Math.floor(num));
    System.out.println("Math.round(" + num + ")=" + Math.round(num));
    System.out.println("Math.ceil(" + num + ")=" + Math.ceil(num));
  }
}
/**
Math.floor(1.4)=1.0
Math.round(1.4)=1
Math.ceil(1.4)=2.0
Math.floor(1.5)=1.0
Math.round(1.5)=2
Math.ceil(1.5)=2.0
Math.floor(1.6)=1.0
Math.round(1.6)=2
Math.ceil(1.6)=2.0
Math.floor(-1.4)=-2.0
Math.round(-1.4)=-1
Math.ceil(-1.4)=-1.0
Math.floor(-1.5)=-2.0
Math.round(-1.5)=-1
Math.ceil(-1.5)=-1.0
Math.floor(-1.6)=-2.0
Math.round(-1.6)=-2
Math.ceil(-1.6)=-1.0
**/

int 与 Integer 的区别

这里我们以 int 与 Integer 来介绍下基本类型与包装类型的区别。首先,int 是基本数据类型,int 变量存储的是数值。Integer 是引用类型,实际是一个对象,Integer 存储的是引用对象的地址。Integer 对象会占用更多的内存。Integer 是一个对象,需要存储对象的元数据。但是 int 是一个原始类型的数据,所以占用的空间更少。

Integer i = new Integer(100);
Integer j = new Integer(100);
System.out.print(i == j); //falseCopy to clipboardErrorCopied

因为 new 生成的是两个对象,其内存地址不同。非 new 生成的 Integer 变量与 new Integer() 生成的变量比较,结果为 false。

/**
 * 比较非new生成的Integer变量与new生成的Integer变量
 */
public class Test {
    public static void main(String[] args) {
        Integer i= new Integer(200);
        Integer j = 200;
        System.out.print(i == j);
        //输出:false
    }
}Copy to clipboardErrorCopied

因为非 new 生成的 Integer 变量指向的是 Java 常量池中的对象,而 new Integer() 生成的变量指向堆中新建的对象,两者在内存中的地址不同。所以输出为 false。两个非 new 生成的 Integer 对象进行比较,如果两个变量的值在区间 [-128,127] 之间,比较结果为 true;否则,结果为 false。

/**
 * 比较两个非new生成的Integer变量
 */
public class Test {
    public static void main(String[] args) {
        Integer i1 = 127;
        Integer ji = 127;
        System.out.println(i1 == ji);//输出:true
        Integer i2 = 128;
        Integer j2 = 128;
        System.out.println(i2 == j2);//输出:false
    }
}Copy to clipboardErrorCopied

Java 在编译 Integer i1 = 127 时,会翻译成 Integer i1 = Integer.valueOf(127)。Integer 变量(无论是否是 new 生成的)与 int 变量比较,只要两个变量的值是相等的,结果都为 true。

/**
 * 比较Integer变量与int变量
 */
public class Test {
    public static void main(String[] args) {
        Integer i1 = 200;
        Integer i2 = new Integer(200);
        int j = 200;
        System.out.println(i1 == j);//输出:true
        System.out.println(i2 == j);//输出:true
    }
}Copy to clipboardErrorCopied

包装类 Integer 变量在与基本数据类型 int 变量比较时,Integer 会自动拆包装为 int,然后进行比较,实际上就是两个 int 变量进行比较,值相等,所以为 true。

自动包装与拆包

int 类型在赋值到 Integer 类时,会自动封装,调用 Integer 的 valueOf(int i) 方法。

Integer a = 1;
Integer a = Integer.valueOf(1);
/**
 * Returns an {@code Integer} instance representing the specified
 * {@code int} value.  If a new {@code Integer} instance is not
 * required, this method should generally be used in preference to
 * the constructor {@link #Integer(int)}, as this method is likely
 * to yield significantly better space and time performance by
 * caching frequently requested values.
 *
 * This method will always cache values in the range -128 to 127,
 * inclusive, and may cache other values outside of this range.
 *
 * @param  i an {@code int} value.
 * @return an {@code Integer} instance representing {@code i}.
 * @since  1.5
 */
public static Integer valueOf(int i) {
    assert IntegerCache.high >= 127;
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}Copy to clipboardErrorCopied

当 i >= -128 && i <= 127 时,Integer.valueOf(i) 会将 i 存储在内部类 IntegerCache 的 static final Integer cache[]里,这一字节的缓存内存地址是静态的,返回值即:

IntegerCache.cache[i + (-IntegerCache.low)]Copy to clipboardErrorCopied

因此:

Integer a = 1;
Integer b = 1;Copy to clipboardErrorCopied

a 和 b 的引用都指向同一个对象,即 a == b

浮点数

浮点数的比较

编程语言原理的相关章节中我们讨论了计算机系统中浮点数的底层表示,那么在实际的编程中我们也需要注意浮点数的比较。譬如下面三种比较方式结果都不会符合预期:

// 使用简单类型比较
float a = 0.7f - 0.6f;
float b = 0.8f - 0.7f;
if (a == b) {
    System.out.println("true");
} else {
    System.out.println("false"); // false
}
// 使用封装类型比较
Float m = Float.valueOf(a);
Float n = Float.valueOf(b);
if (m.equals(n)) {
    System.out.println("true");
} else {
    System.out.println("false"); // false
}
// 使用 BigDecimal 比较
BigDecimal x = new BigDecimal(0.8f);
BigDecimal y = new BigDecimal("0.8");
if (x.equals(y)) {
    System.out.println("true");
} else {
    System.out.println("false"); // false
}Copy to clipboardErrorCopied

我们需要切换到如下的比较方式:

double diff = 1e-6;
if (Math.abs(a - b) < diff) {
    System.out.println("true"); // true
} else {
    System.out.println("false");
}
BigDecimal a1 = new BigDecimal("0.8");
BigDecimal b1 = new BigDecimal("0.7");
BigDecimal c1 = new BigDecimal("0.6");
if (a1.subtract(b1).equals(b1.subtract(c1))) {
    System.out.println("true"); // true
} else {
    System.out.println("false");
}Copy to clipboardErrorCopied

在实际的项目中,货币之类的精确表示使用整型来存储计算,表示上进行数制的互相转化。

Java 数组

数组对于每一门编程语言来说都是重要的数据结构之一,当然不同语言对数组的实现及处理也不尽相同。Java 语言中提供的数组是用来存储固定大小的同类型元素。

你可以声明一个数组变量,如 numbers[100] 来代替直接声明 100 个独立变量 number0,number1,....,number99

Arrays

java.util.Arrays 类能方便地操作数组,它提供的所有方法都是静态的。具有以下功能:

  • 给数组赋值:通过 fill 方法。
  • 对数组排序:通过 sort 方法,按升序。
  • 比较数组:通过 equals 方法比较数组中元素值是否相等。
  • 查找数组元素:通过 binarySearch 方法能对排序好的数组进行二分查找法操作。

具体说明请查看下表:

序号 方法和说明
1 public static int binarySearch(Object[] a, Object key) 用二分查找算法在给定数组中搜索给定值的对象(Byte,Int,double 等)。数组在调用前必须排序好的。如果查找值包含在数组中,则返回搜索键的索引;否则返回 (-(插入点 ) - 1)
2 public static boolean equals(long[] a, long[] a2) 如果两个指定的 long 型数组彼此相等 ,则返回 true。如果两个数组包含相同数量的元素,并且两个数组中的所有相应元素对都是相等的,则认为这两个数组是相等的。换句话说,如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。同样的方法适用于所有的其他基本数据类型(Byte,short,Int 等)
3 public static void fill(int[] a, int val) 将指定的 int 值分配给指定 int 型数组指定范围中的每个元素。同样的方法适用于所有的其他基本数据类型(Byte,short,Int 等)
4 public static void sort(Object[] a) 对指定对象数组根据其元素的自然顺序进行升序排列。同样的方法适用于所有的其他基本数据类型(Byte,short,Int 等)
import java.util.Arrays;
public class TestArrays {
    public static void output(int[] array) {
        if (array != null) {
            for (int i = 0; i < array.length; i++) {
                System.out.print(array[i] + " ");
            }
        }
        System.out.println();
    }
    public static void main(String[] args) {
        int[] array = new int[5];
        // 填充数组
        Arrays.fill(array, 5);
        System.out.println("填充数组:Arrays.fill(array, 5):");
        TestArrays.output(array);
        // 将数组的第2和第3个元素赋值为8
        Arrays.fill(array, 2, 4, 8);
        System.out.println("将数组的第2和第3个元素赋值为8:Arrays.fill(array, 2, 4, 8):");
        TestArrays.output(array);
        int[] array1 = { 7, 8, 3, 2, 12, 6, 3, 5, 4 };
        // 对数组的第2个到第6个进行排序进行排序
        Arrays.sort(array1, 2, 7);
        System.out.println("对数组的第2个到第6个元素进行排序进行排序:Arrays.sort(array,2,7):");
        TestArrays.output(array1);
        // 对整个数组进行排序
        Arrays.sort(array1);
        System.out.println("对整个数组进行排序:Arrays.sort(array1):");
        TestArrays.output(array1);
        // 比较数组元素是否相等
        System.out.println("比较数组元素是否相等:Arrays.equals(array, array1):" + "\n" + Arrays.equals(array, array1));
        int[] array2 = array1.clone();
        System.out.println("克隆后数组元素是否相等:Arrays.equals(array1, array2):" + "\n" + Arrays.equals(array1, array2));
        // 使用二分搜索算法查找指定元素所在的下标(必须是排序好的,否则结果不正确)
        Arrays.sort(array1);
        System.out.println("元素3在array1中的位置:Arrays.binarySearch(array1, 3):" + "\n" + Arrays.binarySearch(array1, 3));
        // 如果不存在就返回负数
        System.out.println("元素9在array1中的位置:Arrays.binarySearch(array1, 9):" + "\n" + Arrays.binarySearch(array1, 9));
    }
}Copy to clipboardErrorCopied

复制与扩容

数组容量如果不够用可以使用 Arrays.copyOf() 进行扩容:

Array.copy(E[] e,newLength);Copy to clipboardErrorCopied

其第一个形参指的是需要扩容的数组,后面是扩容后的大小,其内部实现其实是使用了 System.arrayCopy(); 在内部重新创建一个长度为 newLength 类型是 E 的数组。

import java.util.Arrays;
public class Main {
    public static void main(String[] args) {
        int[] a= {10,20,30,40,50};
        a= Arrays.copyOf(a,a.length+1);
        for(int i=0;i<a.length;i++) {
            System.out.println(a[i]);
        }
    }
}Copy to clipboardErrorCopied

默认补 0 ,输出结果为:10 20 30 40 50 0

排序

Arrays.sort 并不是单一的排序,而是插入排序,快速排序,归并排序三种排序的组合,为此我画了个流程图:

排序流程图

O(nlogn)只代表增长量级,同一个量级前面的常数也可以不一样,不同数量下面的实际运算时间也可以不一样。

  • 数量非常小的情况下(就像上面说到的,少于 47 的),插入排序等可能会比快速排序更快所以数组少于 47 的会进入插入排序。
  • 快排数据越无序越快(加入随机化后基本不会退化),平均常数最小,不需要额外空间,不稳定排序。
  • 归排速度稳定,常数比快排略大,需要额外空间,稳定排序。

基本类型排序

Arrays.sort(int[] a),这种形式是对于一个数组的元素进行排序,按照从小到大的顺序:

import java.util.Arrays;
public class sort1 {
    public static void main(String args[]) {
        int[] a= {9,8,7,6,4,5,3,1,2};
        Arrays.sort(a);
        for(int i=0;i<a.length;i++) {
            System.out.print(a[i]+"");
        }
    }
}Copy to clipboardErrorCopied

Arrays.sort(int [] a,int fromIndex,int toIndex) 这种形式是对数组部分排序,也就是对数组 a 的下标从 fromIndex 到 toIndex-1 的元素排序,注意:下标为 toIndex 的元素不参与排序:

import java.util.Arrays;
public class sort2 {
    public static void main(String args[]) {
        int[] a= {9,2,5,1,6,1,4,3,};
        Arrays.sort(a,0,3);
        for(int i=0;i<a.length;i++) {
            System.out.print(a[i]+"");
        }
    }
}Copy to clipboardErrorCopied

自定义排序器

import java.util.Arrays;
import java.util.Comparator;
public class sort3 {
    public static void main(String args[]) {
        Integer[] a= {9,5,6,1,3,2,4,7,8,0};
        Comparator cmp=new MyComparator();
        Arrays.sort(a);
        for(int i=0;i<a.length;i++) {
            System.out.print(a[i]+"");
        }
    }
}
class MyComparator implements Comparator<Integer>{
    @Override
    public int compare(Integer o1, Integer o2) {
        // TODO Auto-generated method stub
        if(o1<o2) {
            return 1;
        }
        else if(o1>o2) {
            return -1;
        }else {
            return 0;
        }
    }
}Copy to clipboardErrorCopied

对象排序

当我们给一个整型数组或者浮点型之类的数组排序的时候,很简单就可以达到我们排序的目的,无非是排序算法的问题。那么,如果我们现在想根据对象的一个属性值给一个对象数组进行排序。假如我们现在有一个 Car 类型,Car 类中有一个 double 型的 speed 属性用来描述车辆的速度,现在我们想根据车速来对一个 Car 数组中的车辆进行排序:

public class Car{
    private double speed;//车辆的速度属性
    public Car(double speed) {
        this.speed = speed;
    }
    public double getSpeed() {
        return speed;
    }
    public void setSpeed(double speed) {
        this.speed = speed;
    }
}Copy to clipboardErrorCopied

用 Array.sort()方法实现对车辆排序的代码:

public class Car implements Comparable<Car>{
    private double speed;
    public Car(double speed) {
        this.speed = speed;
    }
    public double getSpeed() {
        return speed;
    }
    public void setSpeed(double speed) {
        this.speed = speed;
    }
    @Override
    public int compareTo(Car newCar) {
        return Double.compare(this.getSpeed(),newCar.getSpeed());
    }
}Copy to clipboardErrorCopied

搜索

public static int binarySearch(Object[] a, Object key):用二分查找算法在给定数组中搜索给定值的对象(Byte,Int,double 等)。数组在调用前必须排序好的。如果查找值包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。

声明与创建

首先必须声明数组变量,才能在程序中使用数组。下面是声明数组变量的语法:

dataType[] arrayRefVar;   // 首选的方法
dataType arrayRefVar[];  // 效果相同,但不是首选方法Copy to clipboardErrorCopied

注意,建议使用 dataType[] arrayRefVar 的声明风格声明数组变量 dataType arrayRefVar[] 风格是来自 C/C++ 语言,在 Java 中采用是为了让 C/C++ 程序员能够快速理解 Java 语言。

创建数组

Java 语言使用 new 操作符来创建数组,语法如下:

arrayRefVar = new dataType[arraySize];Copy to clipboardErrorCopied

上面的语法语句做了两件事,首先是使用 dataType[arraySize] 创建了一个数组,然后把新创建的数组的引用赋值给变量 arrayRefVar。数组变量的声明,和创建数组可以用一条语句完成,如下所示:

dataType[] arrayRefVar = new dataType[arraySize];
dataType[] arrayRefVar = {value0, value1, ..., valuek};Copy to clipboardErrorCopied

数组与函数

数组可以作为参数传递给方法。例如,下面的例子就是一个打印 int 数组中元素的方法:

public static void printArray(int[] array) {
  for (int i = 0; i < array.length; i++) {
    System.out.print(array[i] + " ");
  }
}
// 数组作为函数的返回值
public static int[] reverse(int[] list) {
  int[] result = new int[list.length];
  for (int i = 0, j = result.length - 1; i < list.length; i++, j--) {
    result[j] = list[i];
  }
  return result;
}Copy to clipboardErrorCopied

遍历检索

循环

数组的元素类型和数组的大小都是确定的,所以当处理数组元素时候,我们通常使用基本循环或者 For-Each 循环。

public class TestArray {
   public static void main(String[] args) {
      double[] myList = {1.9, 2.9, 3.4, 3.5};
      // 打印所有数组元素
      for (int i = 0; i < myList.length; i++) {
         System.out.println(myList[i] + " ");
      }
      // 计算所有元素的总和
      double total = 0;
      for (int i = 0; i < myList.length; i++) {
         total += myList[i];
      }
      System.out.println("Total is " + total);
      // 查找最大元素
      double max = myList[0];
      for (int i = 1; i < myList.length; i++) {
         if (myList[i] > max) max = myList[i];
      }
      System.out.println("Max is " + max);
   }
}Copy to clipboardErrorCopied

JDK 1.5 引进了一种新的循环类型,被称为 for-each 循环或者加强型循环,它能在不使用下标的情况下遍历数组。语法格式如下:

for(type element: array)
{
    System.out.println(element);
}
public class TestArray {
   public static void main(String[] args) {
      double[] myList = {1.9, 2.9, 3.4, 3.5};
      // 打印所有数组元素
      for (double element: myList) {
         System.out.println(element);
      }
   }
}Copy to clipboardErrorCopied

多维数组

多维数组可以看成是数组的数组,比如二维数组就是一个特殊的一维数组,其每一个元素都是一个一维数组,例如:

String str[][] = new String[3][4];Copy to clipboardErrorCopied

多维数组的动态初始化

直接为每一维分配空间,格式如下:

type[][] typeName = new type[typeLength1][typeLength2];Copy to clipboardErrorCopied

type 可以为基本数据类型和复合数据类型,arraylength1 和 arraylength2 必须为正整数,arraylength1 为行数,arraylength2 为列数。例如:

int a[][] = new int[2][3];Copy to clipboardErrorCopied

二维数组 a 可以看成一个两行三列的数组,从最高维开始,分别为每一维分配空间,例如:

String s[][] = new String[2][];
s[0] = new String[2];
s[1] = new String[3];
s[0][0] = new String("Good");
s[0][1] = new String("Luck");
s[1][0] = new String("to");
s[1][1] = new String("you");
s[1][2] = new String("!");Copy to clipboardErrorCopied

s[0]=new String[2] 和 s[1]=new String[3] 是为最高维分配引用空间,也就是为最高维限制其能保存数据的最长的长度,然后再为其每个数组元素单独分配空间 s0=new String("Good") 等操作。

多维数组的引用(以二维数组为例)

对二维数组中的每个元素,引用方式为 arrayName[index1][index2],例如:num[1][0];