29

Java 解惑系列(五):绝对值小于 0 的整数存在么

 4 years ago
source link: https://mp.weixin.qq.com/s/Cm2VU2rH0S-hWUl3h5h15w
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

问题一:BigInteger你了解不?

看一下下面的代码,最终将打印什么呢?

public static void main(String[] args) {
    BigInteger fiveThousand = new BigInteger("5000");
    BigInteger fiftyThousand = new BigInteger("50000");
    BigInteger fiveHundredThousand = new BigInteger("500000");
    BigInteger total = BigInteger.ZERO;
    total.add(fiveThousand);
    total.add(fiftyThousand);
    total.add(fiveHundredThousand);
    System.out.println(total);
}

代码很简单,就是累加这几个大整数,那么这个程序最终会打印 555000 么,如果你觉得会的话,那么说明你对 BigInteger 掌握的还是不够好哟。运行这段代码,可以发现最终打印的是0。

解惑1:

很显然,就这个例子而言,所有这些加法对total没有产生什么影响。原因就是:

BigInteger实例是不可改变的。

StringBigDecimal ,以及包装类类型: IntegerLongDouble 等都是如此,我们不能修改现有实例的值,对这些类型的修改将返回新的实例。所以针对该例子,我们只需要将最终计算的结果赋值到新的实例上就可以:

public static void main(String[] args) {
    BigInteger fiveThousand = new BigInteger("5000");
    BigInteger fiftyThousand = new BigInteger("50000");
    BigInteger fiveHundredThousand = new BigInteger("500000");
    BigInteger total = BigInteger.ZERO;
    total = total.add(fiveThousand);
    total = total.add(fiftyThousand);
    total = total.add(fiveHundredThousand);
    System.out.println(total);
}

问题二:八进制的问题

来看下下面的代码,最终会打印什么?

public static void main(String[] args) {
    int[] vals = {5, 6, 7, 8, 9, 010, 011, 012};
    int[] valTemps = new int[vals.length];
    for (int i = 0; i < vals.length; i++) {
        valTemps[i] = vals[i] - 5;
    }
    System.out.println(Arrays.toString(valTemps));
}

方法很简单,我们将一个整数数组中的值减去固定值之后,赋值给新的数组,那么程序会打印什么呢?如果你认为会打印 [0, 1, 2, 3, 4, 5, 6, 7] ,那你可能就需要接着往下看了。

解惑2:

很遗憾,运行程序后,程序的结果是: [0, 1, 2, 3, 4, 3, 4, 5] ,和我们预期不同的是数组后面三个值,那么为什么会这样呢?也许聪明的你很快就看出问题所在了。没错,就是原数组中后面三个值都是 0 开头的。

Java中以0开头的整型字面值常量将按照八进制数值来计算。

也就是说,这里出现的三个 0 开头的值,其实是八进制的值,那么计算的时候首先要转换为十进制,那么 010 的十进制就是 8011 的十进制是 9 ,012的十进制是 10 ,然后进行计算,最终结果就是我们看到的这样了。

除了八进制是以 0 开头外,二进制是以 0b 开头,而16进制则是以 0x 开头。

所以我们写代码的时候,千万不要没事在整型变量前面给加一个 0

问题三:绝对值的问题

看下下面这个问题,先简单说下, Math.abs() 是用于求一个数值的绝对值,最终会有打印结果么?

public static void main(String[] args) {
    for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++) {
        if (Math.abs(i) < 0) {
            System.out.println("this value is:" + i);
        }
    }
}

解惑3:

问题很简单,就是判断 int类型 整数范围内,有没有绝对值小于0的数值存在。一般情况下,对于绝对值,我们都知道:

正数的绝对值是它本身;负数的绝对值是对该值取反;0的绝对值是0;

正常来看,是不会有打印结果的,不过结合我们前面学习到的,想必你应该知道,int类型整数范围内,还真有一个值可以满足,那就是 Integer.MIN_VALUE

Integer.MIN_VALUE,该值比较特殊,对该值取反还是它本身;对该值取绝对值仍然是它本身。

所以,int类型范围内绝对值小于0的有就仅有一个,就是 Integer.MIN_VALUE ,同样对于Long类型范围内的数值,绝对值小于0的也是有一个,就是 Long.MIN_VALUE

所以说,如果你哪天遇到别人问你: 整数范围内有没有绝对值小于0的? ,就可以很明白的回答了。

问题四:传递性的问题

在数学运算中,我们学习过, 等于(=) 定义了一种数与数之间的等价关系,这种关系当且仅当它是 自反的传递的对称的 ,我们这里只说狭义的与等于相关的传递性:

传递性,如果 x==y ,并且 y==z ,那么 a==z

那么接下来,需要你出出手了,你能写一段违反传递性的代码么?

解惑4:

其实,我们前面已经学习过原生类型的转换中,涉及到窄化与拓展的时候,有可能精度丢失,这时候就可以借助这块的内容来写出违反传递性的代码,比如说:

public static void main(String[] args) {
    long x = Long.MAX_VALUE;
    double y = (double) Long.MAX_VALUE;
    long z = Long.MAX_VALUE - 1;
    System.out.println(x == y);
    System.out.println(y == z);
    System.out.println(x == z);
}

这里就会打印 true true false ,这里涉及到了类型拓展时精度丢失的问题,并且涉及到了double类型在数值特别大的时候,会出现 double == (double+1) 的情况,也就是两个double类型之间的差大于1的时候,前面已经介绍过,这里就不多说了。

问题五:静态方法的问题

看下下面的这个程序,最终程序会通过编译并运行成功么?

public static void main(String[] args) {
    Main main = null;
    main.print();
}

public static void print() {
    System.out.println("hello world");
}

也就是我们在一个 null 对象上调用了一个方法,那么这个程序会抛出空指针异常么?

解惑5:

很遗憾,这个程序可以正常运行,并且会打印 hello world ,为什么会这样呢?我们学习Java的时候学习过,在 null 对象上调用方法将会抛出 NullPointException 的,但这里为什么可以正常运行呢?原因就在于 print 是一个静态方法。

静态方法调用的表达式对象是被忽略的,没有任何要求其对象值为非空的限制。

也就是在静态方法上通过对象而不是类进行调用时,该对象可以是空值,无论该值是不是空值,该值在运行的时候是会被忽略的。

另外,这里再多说一点,我们学习静态方法的时候还了解到:

对静态方法的调用是不存在动态调用这一说的,静态方法的调用对象是编译时就被选定的。

比如说下面的代码:

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.brak();
        Dog nipper = new Basenji();
        nipper.brak();
    }
}

class Dog {
    public static void brak() {
        System.out.println("Dog");
    }
}
class Basenji extends Dog {
    public static void brak() {
        System.out.println("Basenji");
    }
}

最终打印的结果是 Dog Dog ,所以这一点也需要注意下。

总之,我们在这个例子中学到的就是:

不要用一个表达式对象来调用静态方法,最好使用类来进行调用。

问题六:循环变量问题

来看下下面的代码,最终程序会怎么执行呢?会打印什么呢?

public class Main {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++)
            Dog dog = new Dog();
    }
}
class Dog {
    Dog() {
        System.out.println("hello world");
    }
}

看上去程序会循环10次,创建10个对象,然后打印十次 hello world ,可事实上真的是这样么?

这里备注下:我以前面试的时候,有次笔试就出现过这个小问题,当时我左看右看没看出来这里有什么问题,可能当时比较年轻吧。

很遗憾,这里什么都不会打印,连编译都不会通过。

解惑6:

这里的解答也比较简单,因为Java语言规范有说明【JLS14.12-14】:

  • Java中不允许一个本地变量声明作为一条语句出现在for、while或do循环中重复执行;

  • 一个本地变量声明作为一条语句只能直接出现在一个语句块中(一个语句块是由一对花括号以及包含在这对花括号中的语句和声明构成的)。

也就是,这里创建对象的这行代码,不能直接出现在for循环中,需要将该对象声明放置于一个语句块中:

public static void main(String[] args) {
    for (int i = 0; i < 10; i++) {
        Dog dog = new Dog();   
    }
}

所以说,这里的小问题可能需要我们注意下。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK