Core Java 总结(数据类型,表达式问题)

2016-10-18 整理html


写一个程序判断整数的奇偶java

public static boolean isOdd(int i){
  return i % 2 == 1;
}

百度百科定义:奇数(英文:odd)数学术语 ,口语中也称做单数, 整数中,能被2整除的数是偶数,不能被2整除的数是奇数,奇数个位为1,3,5,7,9。偶数可用2k表示,奇数可用2k+1表示,这里k就是整数。奇数能够分为:

正奇数:一、三、五、七、九、十一、1三、1五、1七、1九、2一、2三、2五、2七、2九、3一、33.........
负奇数:-一、-三、-五、-七、-九、-十一、-1三、-1五、-1七、-1九、-2一、-23.-2五、-2七、-2九、-3一、-33.........
奇数能够被定义为被2 整除余数为1 的整数。可是在 int 数值中,有一半都是负数,而 isOdd 方法对于对全部负奇数的判断都会失败。在任何负整数上调用该方法都回返回false ,无论该整数是偶数仍是奇数。正确写法:

public static boolean isOdd(int i){
  return i % 2 != 0;
}

用位操做符AND(&)来替代对2的取余操做符会更好,注意优先级。

public static boolean isOdd(int i){
  return (i & 1) != 0;
}
解析

在jdk1.5+的环境下,以下4条语句,讨论互相==比较的输出结果

int i02=59; // 这是一个基本类型,存储在栈中。

Integer i01=59; // 调用 Integer 的 valueOf 方法,自动装箱。使用享元模式,看值是否在 [-128,127],且 IntegerCache 中是否存在此对象,若是存在,则直接返回引用,不然建立一个新的对象。因程序初次运行,没有 59 ,因此直接建立了一个新的对象

Integer i03 =Integer.valueOf(59); //  由于 IntegerCache 中已经存在此对象,因此,直接返回引用。

Integer i04 = new Integer(59) ; // 直接建立一个新的对象。

System. out .println(i01== i02); // i01 是 Integer 对象, i02 是 int ,这里比较的不是地址,而是值。 Integer 会自动拆箱成 int ,而后进行值的比较。因此为真。

System. out .println(i01== i03); // 由于 i03 返回的是 i01 的引用,因此,为真。

System. out .println(i03==i04);  // 由于 i04 是从新建立的对象,因此 i03,i04 是指向不一样的对象,所以比较结果为假。

System. out .println(i02== i04); // 由于 i02 是基本类型,因此此时 i04 会自动拆箱,进行值比较,因此,结果为真。
解析

具体参考:c++

减少内存的占用问题——享元模式和单例模式的对比分析

用命令行:  java xxx a b c 方式运行如下代码的结果是?

 

这里java xxx a b c 表示运行java字节码文件xxx,参数为 a b c,由于只输入了三个参数,且args是数组下标从0开始,而程序中使用到agrs[3]显然数组越界。抛出数组越界异常。
解析

下面代码的输出结果是?

须要知道计算机用补码存储数值

10的原码:0000 0000 | 0000 0000 | 0000 0000 | 00001010

~10:      1111111111111111,1111111111110101  变为负数,下面求该负数的补码:

~10反码:10000000000000000,0000000000001010 符号位不变,其他位取反

~10补码:10000000000000000,0000000000001011,等于 -11

下面记住公式

-n=~n+1可推出 ~n = -n-1
解析

下面代码的输出结果是?

System.out.println(2.00 - 1.10);
可能认为该程序打印0.90,可是编译器如何才能知道你想要打印小数点后两位小数呢?

实际它打印的是0.8999999999999999。问题在于1.1 这个数字不能被精确表示成为一个double,所以它被表示成为最接近它的double 值。该程序从2 中减去的就是这个值。更通常地说,问题在于并非全部的小数均可以用二进制浮点数来精确表示的。若是你正在用的是JDK 5.0 或更新的版本,那么可使用相似c的方式,Java的printf 工具来订正该程序:

System.out.printf("%.2f%n",2.00 - 1.10);

这条语句打印的是正确的结果,可是这并不表示它就是对底层问题的通用解决方案:它使用的仍旧是二进制浮点数的double 运算。浮点运算在一个范围很广的值域上提供了很好的近似,可是它一般不能产生精确的结果。二进制浮点对于货币计算是很是不适合的,由于它不可能将0.1,或者10的其它任何次负幂精确表示为一个长度有限的二进制小数。

解决该问题的一种方式是使用某种整数类型,例如int 或long,而且以分为单位来执行计算。注意这样作请确保该整数类型大到足够表示在程序中你将要用到的全部值。对本题int 就足够了。下面是用int 以分为单位表示货币值后重写的println 语句:
System.out.println((200 - 110) + "cents");

解决该问题的另外一种方式是使用执行精确小数运算的BigDecimal工具类。它还能够经过 JDBC 与 SQL DECIMAL 类型进行互操做。这里要注意: 必定要用 BigDecimal(String) 构造器,而不用BigDecimal(double)。后一个构造器将用它的参数的“精确”值来建立一个实例:new BigDecimal(.1)将返回一个表示0.100000000000000055511151231257827021181583404541015625 的BigDecimal。经过正确使用BigDecimal,程序就能够打印出咱们所指望的结果0.90import java.math.BigDecimal;
public class Change1{
  public static void main(String args[]){
    System.out.println(new BigDecimal("2.00").subtract(new BigDecimal("1.10")));
  }
}
这个版本并非十分地完美,由于Java 并无为BigDecimal 提供任何语言上的支持。使用BigDecimal 的计算颇有可能比那些使用原始类型的计算要慢一些,对某些大量使用小数计算的程序来讲,这可能会成为问题。总之, 在须要精确答案的地方,要避免使用float 和double,对于货币计算,要使用int、long 或BigDecimal。
解析

浮点数表示能够参考程序员

从如何判断浮点数是否等于0提及——浮点数的机器级表示

下面代码的输出结果是?

        final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000;
        final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;
        System.out.println(MICROS_PER_DAY / MILLIS_PER_DAY);
除数和被除数都是long 类型的,long 类型大到了能够很容易地保存这两个乘积而不产生溢出。所以,看起来程序打印的一定是1000。

问题在于常数 MICROS_PER_DAY 的计算“确实”溢出了。尽管计算的结果能安全放入long 中,而且其空间还有富余,可是这个结果并不适合放入int 中。这个计算彻底是以int 运算来执行的,而且只有在运算完成以后,其结果才被提高到long,而此时已经太迟了,计算已经溢出。

从int提高到long是一种拓宽原始类型转换(widening primitive conversion),它保留了(不正确的)数值。这个值以后被MILLIS_PER_DAY 整除,而MILLIS_PER_DAY 的计算是正确的,由于它适合int 运算。这样整除的结果就获得了5(前者返回的是一个小了200 倍的数值)。

那么为何计算会是以int运算来执行的呢?

由于全部乘在一块儿的因子都是默认int数值。将两个int 数值相乘时,将获得另外一个int 数值,这是Java 的语言特性,经过使用long 常量来替代int 常量做为每个乘积的第一个因子,咱们就能够修改这个程序。这样作能够强制表达式中全部的后续计算都用long 来完成。尽管这么作只在MICROS_PER_DAY 表达式中是必需的,可是在两个乘积中都这么作是一种很好的方式。类似地使用long 做为乘积的“第一个”数值也并不老是必需的,可是这么作也是一种很好的形式。在两个计算中都以long数值开始能够很清楚地代表它们都不会溢出。下面的程序将打印1000:

final long MICROS_PER_DAY = 24L * 60 * 60 * 1000 * 1000;
final long MILLIS_PER_DAY = 24L * 60 * 60 * 1000;
System.out.println(MICROS_PER_DAY/MILLIS_PER_DAY);



小结:当操做很大的数字时,千万要提防溢出。即便用来保存结果的变量已显得足够大,也并不意味着要产生结果的计算具备正确的类型。当拿不许时,就使用long 运算来执行整个计算。
解析

下面代码的输出结果是?

System.out.println(12345 + 5432l);
表面上看,这是一个很简单的题,打印66666。

实际上,当运行该程序时,它打印的是17777。仔细看 + 操做符的两个操做数,咱们是将一个int 类型的12345 加到了 long 类型的5432l 上。请注意左操做数开头的数字1 和右操做数结尾的小写字母l 之间的细微差别。数字1 的水平笔划 和 垂直笔划 之间是一个锐角,而与此相对照的是,小写字母 l 是一个直角。

这个写法确实已经引发了混乱,这里有一个教训:在 long 型字面常量中,必定要用大写的L,千万不要用小写的l。这样就能够彻底避免混乱。

System.out.println(12345 + 5432L);

相似的,要避免使用单独的一个 l 字母做为变量名。由于很难经过观察来判断它究竟是 l 仍是数字 1。属于编程不规范。

System.out.println(1);

总之,小写字母 l 和数字1 在大多数字体中几乎是同样的。为避免程序的读者对两者产生混淆,千万不要使用小写的 l 来做为 long 型字面常量的结尾或是做为变量名。Java 从C 编程语言中继承良多,包括long 型字面常量的语法。也许当初容许用小写的 l 来编写long 型字面常量自己就是一个错误。
解析

下面代码的输出结果是?

System.out.println(Long.toHexString(0x100000000L + 0xcafebabe));
看起来应该打印1cafebabe。毕竟这是十六进制数字10000000016 与cafebabe16 的和。该程序使用的是long 型运算,它能够支持16位十六进制数,所以运算溢出是不可能的。

然而,运行该程序,发现它打印出来的是cafebabe,并无任何前导的1。这个输出表示的是正确结果的低32 位,可是不知何故,第33 位丢失了。看起来程序好像执行的是int 型运算而不是long 型运算。

注意:十进制字面常量具备一个很好的属性,即全部的十进制字面常量都是正的,而十六进制或是八进制字面常量并不具有这个属性。要想书写一个负的十进制常量,可使用一元取反操做符(-减号)链接一个十进制字面常量。以这种方式,十进制书写任何int 或long 型的数值,无论它是正的仍是负的,而且负的十进制常数能够很明确地用一个减号符号来标识。

十六进制和八进制字面常量并非这么回事,它们能够具备正的以及负的数值。若是十六进制和八进制字面常量的最高位被置位了,那么它们就是负数。在这个程序中,数字0xcafebabe是一个int 常量,它的最高位被置位了,因此它是一个负数。它等于十进制数值-889275714。

该程序执行的这个加法是一种“混合类型的计算(mixed-type computation):左操做数是long 类型的,而右操做数是int 类型的。为了执行该计算,Java 将int 类型的数值用拓宽原始类型转换提高为一个long 类型,而后对两个long 类型数值相加。由于int 是一个有符号的整数类型,因此它将负的int 类型的数值提高为一个在数值上相等的long 类型数值。这个加法的右操做数0xcafebabe 被提高为了long 类型的数值0xffffffffcafebabeL。这个数值以后被加到了左操做数0x100000000L 上。看成为int 类型来被审视时,通过符号扩展以后的右操做数的高32 位是-1,而左操做数的高32 位是1,将这两个数值相加就获得了0,这也就解释了为何在程序输出中前导1 丢失了。下面所示是用手写的加法实现。(在加法上面的数字是进位)
      1111111
    0xf f f f f f f f c a f e b a b eL
+  0x00000001 0 0 00 0 0 0 0L
----------------------------------
    0x00000000 c a f e b a b eL

修改需用一个long 十六进制字面常量来表示右操做数便可。这就能够避免具备破坏力的符号扩展,而且程序也就能够打印出咱们所指望的结果1cafebabe:
public class JoyOfHex{
  public static void main(String[] args){
    System.out.println(Long.toHexString(0x100000000L + 0xcafebabeL));
  }
}

本题给咱们的教训是:混合类型的计算可能会产生混淆,尤为是十六进制和八进制字面常量无需显式的减号符号就能够表示负的数值。最好是避免混合类型的计算。
解析

Math.round(11.5)等于多少? Math.round(-11.5)等于多少?

Java的Math类中提供了三个与取整有关的方法:ceil、floor、round,这些方法的做用与它们的英文名称的含义相对应,

floor:向下取整数。返回double类型-----n. 地板,地面

         例如:Math.floor(-4.2) = -5.0

-----------------------------------------------------------

ceil:   向上取整数。返回double类型-----vt. 装天花板;

         例如:Math.ceil(5.6) = 6.0

-----------------------------------------------------------

最不舒服的是round方法,算法为Math.floor(x+0.5),即将原数加0.5再向下取整,因此,Math.round(11.5)的结果为12.0,Math.round(-11.5)的结果为-11.0。
解析

下面代码的输出结果是?

这里主要是有一点:

ceil 方法上有这么一段注释:若是参数小于0且大于-1.0,结果为 -0

其实ceil 和 floor 方法 上注释都有:若是参数是 NaN、无穷、正 0、负 0,那么结果与参数相同,若是是 -0.0,那么其结果是 -0.0

答案是 -0.0 和 -1.0
解析

下面代码的输出结果是?

        int a = 0;
        int b = 1;
        System.out.println(b / a);
没什么好说的吧,抛出除数为0的算术异常

Exception in thread "main" java.lang.ArithmeticException: / by zero
解析

下面代码的输出结果是?

        double a = 0;
        int b = 1;
        System.out.println(b / a);
Infinity

浮点数除以0之因此不会抛出异常的一个最重要的缘由是浮点数0不一样于整数0,是不能准确表示的。实际上浮点数0指的是一个无限趋近于0的数,一个正数除以一个无限趋近于0的数结果就是无限趋近于正无穷大,也就是infinity。

infinity一般也称之为非数(NaN,not a number),是浮点数的一种特殊形态。
解析

通过强制类型转换之后,变量a,b的值分别为多少?

short类型,a的二进制是:0000 0000 1000 0000,强制转换截后8位,正数用源码表示,负数用补码表示,第一位是符号。所以a截取后8位的二进制是:1000 0000,第一位是1,表示是一个负数,二进制的值是128,因此结果是 b = -128,a仍是128。
解析

下面代码的输出结果是?

这里涉及java的自动装包/自动拆包(AutoBoxing/UnBoxing), Byte的首字母为大写,是类,在add函数内实现++操做,会自动拆包成byte类型,因此add函数仍是不能实现自增功能。也就是说add函数只是个摆设,没有任何做用。

Byte类型值大小为-128~127之间。 add(++a);这里++a会越界,a的值变为-128 。add(b); 前面说了,add不起任何做用,b仍是127。
解析

下面代码的输出结果是?

被final修饰的变量是常量,这里的b6=b4+b5能够当作是b6=10;在编译时就已经变为b6=10了,而b1和b2是byte类型,java中进行计算时候将他们提高为int类型再进行计算,b1+b2计算后已是int类型,赋值给b3,b3是byte类型,类型不匹配,编译不会经过,须要进行强制转换。

记住:Java中的byte,short,char变量进行计算时都会提高为int类型。
解析 

下面代码的输出结果是?

Byte是byte的包装类型,自动初始化为null而不是0,答案为:

null 42 42
解析

下面代码的输出结果是?

 1         Integer integer = 42;
 2         Long long1 = 42L;
 3         Double double1 = 42.0;
 4 
 5         System.out.println(integer == long1);
 6         System.out.println(integer == double1);
 7         System.out.println(long1 == double1);
 8         System.out.println(integer.equals(double1));
 9         System.out.println(double1.equals(long1));
10         System.out.println(integer.equals(long1));
11         System.out.println(long1.equals(42));
12         System.out.println(long1.equals(42L));
13         System.out.println(integer.equals(new Integer(42)));
5,6,7编译错误,包装类的“==”运算在不遇到算术运算的状况下,两边都是包装类那么不会自动拆箱(只要有一个是基本类型,就会自动拆箱),不能直接比较不一样类型的变量,必须强制转化。

下面看包装类Integer的equals方法源码,其它包装类也重写了equals方法:先判断是否是属于一个类型,而后拆箱比较数值大小。

public boolean equals(Object obj) {
  if (obj instanceof Integer) {
    return value == ((Integer)obj).intValue();
  }
  return false;
}

8行输出false,不是一个类型

9行输出false

10行输出false

11行输出false,42默认是int类型

12行true

13行true



补充下,看String的equals方法源码:先比较是否是一个对象,如不是,一样的思路,先使用instanceof判断是否是一个类型的,若是是才判断字符串内容。

public boolean equals(Object anObject) {
  if (this == anObject) {
    return true;
  }
  if (anObject instanceof String) {
    String anotherString = (String)anObject;
    int n = value.length;
    if (n == anotherString.value.length) {
      char v1[] = value;
      char v2[] = anotherString.value;
      int i = 0;
      while (n-- != 0) {
        if (v1[i] != v2[i])
          return false;
        i++;
      }
      return true;
    }
  }
  return false;
}
解析

下面代码的输出结果是?

        Map<String, Boolean> map = new HashMap<>();
        System.out.println((map != null ? map.get("test") : false));
报异常:Exception in thread "main" java.lang.NullPointerException

回忆:基本数据类型的自动装箱(autoboxing)、拆箱(unboxing)是自J2SE 5.0开始提供的功能。

通常咱们要建立一个类的对象实例的时候,咱们会这样:

Class a = new Class(parameters);

当咱们建立一个Integer对象时,却能够这样:

Integer i = 100;

注意和 int i = 100; 是有区别的,实际上,执行上面那句代码的时候,系统为咱们执行了: Integer i = Integer.valueOf(100); 这里暂且不讨论这个原理是怎么实现的(什么时候拆箱、什么时候装箱),也略过普通数据类型和对象类型的区别。咱们能够理解为,当咱们本身写的代码符合装(拆)箱规范的时候,编译器就会自动帮咱们拆(装)箱。那么这种不被程序员控制的自动拆(装)箱必定格外当心,可能存在问题。

注意:三目运算符的语法规范,当第二,第三位操做数分别为基本类型和对象时,其中的对象就会拆箱为基本类型进行操做。因此,结果就是因为使用了三目运算符,而且第2、第三位操做数分别是基本类型和对象。因为该对象为null,因此在拆箱过程当中调用null.booleanValue()的时候就报了空指针异常。

若是代码这么写,就不会报错:

Map<String, Boolean> map = new HashMap<>();

System.out.println((map != null ? map.get("test") : Boolean.FALSE));

保证了三目运算符的第二第三位操做数都为对象类型。
解析

下面代码的输出结果是?

char x = 'X';
int i = 0;
System.out.println(true ? x : 0);
System.out.println(false ? i : x);
打印 X88。第一个print 语句打印的是X,而第二个打印的倒是88。

请注意在这两个表达式中,每个表达式的第二个和第三个操做数的类型都不相同:x 是char 类型的,而0 和i 都是int 类型的。而混合类型的计算会引发混乱,条件表达式结果类型的规则过于冗长和复杂,其核心就是4点:

若是第二个和第三个操做数具备相同的类型,那么它就是条件表达式的类型。
若是一个操做数的类型是T,T 表示byte、short 或char,而另外一个操做数是一个int 类型的常量表达式,它的值是能够用类型T 表示的,那么条件表达式的类型就是T。
不然,将对操做数类型运用二进制数字提高,而条件表达式的类型就是第二个和第三个操做数被提高以后的类型。
若是第二,三个操做数有一个是基本类型,另外一个是对象类型,那么对象类型会进行自动拆箱。
程序的两个条件表达式中,一个操做数的类型是char,另外一个的类型是int。在两个表达式中,int 操做数都是0,它能够被表示成一个char。然而,只有第一个表达式中的int 操做数是常量(0),而第二个表达式中的int 操做数是变量(i)。所以,第2 点被应用到了第一个表达式上,它返回的类型是char,而第3 点被应用到了第二个表达式上,其返回的类型是对int 和char 运用了二进制数字提高以后的类型,即int。

条件表达式的类型将肯定哪个重载的print 方法将被调用。对第一个表达式来讲,PrintStream.print(char)将被调用,而对第二个表达式来讲,PrintStream.print(int)将被调用。前一个重载方法将变量x 的值做为Unicode字符(X)来打印,然后一个重载方法将其做为一个十进制整数(88)来打印。

总之,最好是在条件表达式中使用类型相同的第二和第三操做数。
解析  

下面代码的输出结果是?

张飞算计魏延,关羽,或者调戏妇女。

==  优先级高于 三目运算符,先判断   true == true,此时返回为  true,

boolean b=true ? false : true == true ? false : true;转化为

true ? false : (true == true) ? false : true;

true ? false : ((true == true) ? false : true);

false,后面不会执行了。
解析

参考文章:Java也适用。面试

c/c++系列的运算符优先级总结

下面代码的输出结果是?

JVM 加载 class 文件时,就会执行静态代码块,静态代码块中初始化了一个变量x并初始化为5,因为该变量是个局部变量,静态代码快执行完后变被释放。

接着两个静态成员变量x,y,并无赋初值,会有默认值,int类型为0

main方法里执行x--操做,变量单独进行自增或自减操做x--和--x的效果同样,此时x变为了-1。

调用MyMethod()方法,在该方法中对x和y进行计算,因为x和y都是静态成员变量,因此在整个类的生命周期内的x和y都是同一个。y=x++ + ++x能够当作是y=(x++)+(++x),当++或者--和其它变量进行运算时,x++表示先运算,再自增,++x表示先自增再参与运算,因此x为-1参与运算,而后自增,x此时为0,++x后x为1,而后参与运算,那么y=-1+1就为0,此时x为1

执行并打印x+y + ++x运算方式和前面相同,最后计算结果就为3
解析

类Demo中存在方法func0、func一、func二、func3和func4,请问该方法中,哪些是不合法的定义?

数据类型的转换,分为自动转换和强制转换。自动转换是程序在执行过程当中 “ 悄然 ” 进行的转换,不须要用户提早声明,通常是从位数低的类型向位数高的类型转换;强制类型转换则必须在代码中声明,转换顺序不受限制。

自动数据类型转换

自动转换按从低到高的顺序转换。不一样类型数据间的优先关系以下: 
    低 --------------------------------------------->byte,short,char-> int -> long -> float -> double

强制数据类型转换

强制转换的格式是在须要转型的数据前加上 “( )” ,而后在括号内加入须要转化的数据类型。有的数据通过转型运算后,精度会丢失,而有的会更加精确

答案:

1没有返回值,4也不对。
解析

下面代码的输出结果是?

        short s1 = 1;
        s1 = s1 + 1;
        s1 += 1;
        System.out.println(s1); 
s1 = s1 + 1;

因为s1+1运算时会自动提高表达式的类型,因此结果是int型,再赋值给short类型s1时,编译器将报告须要强制转换类型的错误。

s1 += 1;

因为 +=是java语言规定的运算符,java编译器会对它进行特殊处理,且效率高,所以能够正确编译。当使用+=、-=、*=、/=、%= 运算符对基本类型进行运算时,遵循规则:运算符右边的数值将首先被强制转换成与运算符左边数值相同的类型,而后再执行运算,且运算结果与运算符左边数值类型相同。
解析

下面代码的输出结果是?

System.out.println((int) (char) (byte) -1);
以int 数值-1 开始,而后从int转型为byte,以后转型为char,最后转型回int。第一个转型将数值从32 位窄化到了8 位,第二个转型将数值从8 位拓宽到了16 位,最后一个转型又将数值从16 位拓宽回了32 位。

运行该程序,打印65535。

由于Java 使用了基于2 的补码的二进制运算,所以int 类型的数值-1 的全部32 位都是置位的,补码表示就是全f的16进制。从int 到byte 的转型是很简单的,它执行了一个窄化原始类型转化(narrowing primitiveconversion),直接将除低8 位以外的全部位所有砍掉。这样作留下的是一个8位都被置位了的byte,它仍旧表示-1。

从byte 到char 的转型稍微麻烦一点,由于byte 是一个有符号类型,而char是一个无符号类型。在将一个整数类型转换成另外一个宽度更宽的整数类型时,一般是能够保持其数值的,可是却不可能将一个负的byte 数值表示成一个char。所以,从byte 到char 的转换有些复杂。

有一条很简单的规则可以描述从较窄的整型转换成较宽的整型时的符号扩展行为:

若是最初的数值类型是有符号的,那么就执行符号扩展;
若是它是char,那么无论它将要被转换成什么类型,都执行零扩展。
了解这条规则可使咱们很容易地解决这个题。由于byte 是一个有符号的类型,因此在将byte 数值-1 转换成char 时,会发生符号扩展。做为结果的char 数值的16 个位就都被置位了,所以它等于2^16-1,即65535。从char 到int 的转型也是一个拓宽原始类型转换,它将执行零扩展而不是符号扩展。做为结果的int 数值也就成了65535。

尽管这条简单的规则描述了在有符号和无符号整型之间进行拓宽原始类型时的符号扩展行为,可是最好仍是不要编写出依赖于它的程序。最好将你的意图明确地表达出来。

若是在将一个char 数值 c 转型为一个宽度更宽的类型,而且不但愿有符号扩展,那么为清晰表达意图,能够考虑使用一个位掩码,即便它并非必需的:
int i = c & 0xffff;

或者,书写一句注释来描述转换的行为:

int i = c; //不会执行符号扩展

若是在将一个char 数值c 转型为一个宽度更宽的整型,而且但愿有符号扩展,那么就先将char 转型为一个short,它与char 具备一样的宽度,可是它是有符号的。在给出了这种细微的代码以后,应也为它书写一句注释:

int i = (short) c; //转型将引发符号扩展

若是在将一个byte 数值b 转型为一个char,而且不但愿有符号扩展,那么必须使用一个位掩码来限制它。这是一种通用作法,因此不须要任何注释:

char c = (char) (b & 0xff);
解析

下面代码的输出结果是?

int x = 1984; // (0x7c0)
int y = 2001; // (0x7d1)
x ^= y ^= x ^= y;
System.out.println("x= " + x + "; y= " + y);
乍一看,会认为程序应该交换变量x 和y 的值。

实际上运行却打印 x = 0; y = 1984。出错了。关于亦或交换算法,好久之前,当中央处理器只有少数寄存器时,人们发现能够经过利用异或操做符(^)的属性(x ^ y ^ x) == y 来避免使用临时变量:

x = x ^ y;
y = y ^ x;
x = y ^ x;

这个惯用法曾经在C 语言中被普遍使用过,并进一步被构建到了C++ 中,可是它并不保证在两者中均可以正确运行。有一点是确定的,那就是它在Java 中确定是不能正确运行的。

Java 语言规范描述到:操做符的操做数是从左向右求值。为了求表达式 x ^= expr 的值,x 的值是在计算expr 以前被提取的,而且这两个值的异或结果被赋给变量x。在程序中,变量x 的值被提取了两次——每次在表达式中出现时都提取一次——可是两次提取都发生在全部的赋值操做以前。

// Java 中x^= y^= x^= y 的实际行为
int tmp1 = x ; // x 在表达式中第一次出现
int tmp2 = y ; // y 的第一次出现
int tmp3 = x ^ y ; // 计算x ^ y
x = tmp3 ; // 最后一个赋值:存储x ^ y 到 x
y = tmp2 ^ tmp3 ; // 第二个赋值:存储最初的x 值到y 中
x = tmp1 ^ y ; // 第一个赋值:存储0 到x 中

在C 和C++中,并无指定表达式的计算顺序。当编译表达式x ^= expr 时,许多C 和C++编译器都是在计算expr 以后才提取x 的值的,这就使得上述的惯用法能够正常运转。尽管它能够正常运转,可是它仍然违背了C/C++有关不能在两个连续的序列点之间重复修改变量的规则。所以,这个惯用法的行为在C 和C++中也没有明肯定义。为了看重其价值,咱们仍是能够写出不用临时变量就能够互换两个变量内容的Java 表达式的。可是它一样是丑陋而无用的:
// 杀鸡用牛刀的作法,千万不要这么作!
y = (x^= (y^= x))^ y ;

在单个的表达式中不要对相同的变量赋值两次。表达式若是包含对相同变量的屡次赋值,就会引发混乱,而且不多可以执行你但愿的操做。即便对多个变量进行赋值也很容易出错。更通常地讲,要避免所谓聪明的编程技巧。它们都是易于产生bug 的,很难以维护,而且运行速度常常是比它们所替代掉的简单直观的代码要慢。

修改:

int x = 1984; // (0x7c0)
int y = 2001; // (0x7d1)
x = x ^ y;
y = y ^ x;
x = y ^ x;
System.out.println("x= " + x + "; y= " + y);

另外,用异或交换变量既不会加快运行速度(反而更慢,六读三写加三次异或),也不会节省空间(中间变量tmp 一般会用寄存器,而不是内存空间存储)。
解析

详细参考理论:算法

一道面试题:用多种方法实现两个数的交换 

下面代码的输出结果是?

 

首先,咱们要知道,静态的方法也是能够经过对象来访问的,这一点很奇怪,可是确实是能够。其次,null能够被强制类型转换成任意类型的对象,因而能够经过它来执行静态方法。输出testMethod
解析

在JAVA中如何跳出当前的多重嵌套循环?写一段代码示意。

break语句能够强迫程序中断循环,当程序执行到break语句时,即会离开循环,继续执行循环外的下一个语句,若是Break语句出如今嵌套循环中的内层循环,则break语句只会跳出当前层的循环,若是跳出多重循环,可在外面的循环语句前定义一个标号,而后在里层循环体的代码中使用带有标号的break语句,便可跳出外层循环。

flag: for (int i = 0; i < 100; i++) {
  for (int j = 0; j < 100; j++) {
    System.out.println("i = " + i + ", " + "j = " + j);
    if (j == 2) {
      break flag;
    }
  }
}

打印:

i = 0, j = 0
i = 0, j = 1
i = 0, j = 2

区别continue语句:它能够强迫程序跳到当前循环的起始处,当程序运行到continue语句时,即会中止运行剩余的循环主体,而是回到循环的开始处继续运行,记住,不是跳出整个循环执行下一条语句,这是Break和continue的主要区别所在,实际上使用continue就是中断一次循环的执行
解析

下面代码的输出结果是?

 

for(条件1;条件2;条件3) {

    //语句

}

执行顺序是条件1->条件2->语句->条件3->条件2->语句->条件3->条件2.......

若是条件2为true,则一直执行。若是条件2位false,则for循环结束。

打印 ABDCBDCB
解析

下面代码的输出结果是?

        for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
            if (b == 0x90)
                System.out.print("Joy!");
        }
0x90 是一个int 常量,它超出了byte 数值的范围。由于0x90 是一个两位的十六进制字面常量,每个十六进制位都占据4 个比特的位置,因此整个数值也只占据8 个比特,即1 个byte。问题在于byte 是有符号类型。常量0x90 是一个正的最高位被置位的8 位int 数值。合法的byte数值是从-128 到+127,可是int 常量0x90 等于+144。拿一个byte 与一个int 进行的比较是一个混合类型比较(mixed-type comparison)。

若是把byte 数值想象为苹果,把int 数值想象成为桔子,那么该程序就是在拿苹果与桔子比较。为了比较byte 数值(byte)0x90 和int 数值0x90,Java 经过拓宽原始类型转换将byte 提高为一个int,而后比较这两个int 数值。由于byte 是一个有符号类型,因此这个转换执行的是符号扩展,将负的byte 数值提高为了在数字上相等的int数值。在本例中,该转换将(byte)0x90提高为int数值-112,它不等于int 数值0x90,即+144。

能够将int 转型为byte,以后就能够进行比较了:

if (b == (byte)0x90)
  System.out.println("Joy!");

或者,能够用一个屏蔽码来消除符号扩展的影响,从而将byte 转型为int:

if ((b & 0xff) == 0x90)
  System.out.print("Joy!");

上面的两个解决方案均可以正常运行,可是避免这类问题的最佳方法仍是将常量值移出到循环的外面,并将其在一个常量声明中定义它。
解析

下面代码的输出结果是?

public class Test1 {
    private static final byte TARGET = 0x90;

    public static void main(String[] args) {
        for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
            if (b == TARGET) {
                System.out.print("Joy!");
            }
        }
    }
}
通不过编译。常量声明有问题,0x90 对于byte 类型来讲不是一个有效的数值。修改:

private static final byte TARGET = (byte)0x90;

总之,要避免混合类型比较,由于它们内在地容易引发混乱。请使用声明的常量替代“魔幻数字”,它说明了常量的含义,集中了常量的定义,而且根除了重复的定义。
解析

下面代码的输出结果是?

public class Test1 {
    public static final int END = Integer.MAX_VALUE;
    public static final int START = END - 100;

    public static void main(String[] args) {
        int count = 0;
        for (int i = START; i <= END; i++)
            count++;
        System.out.println(count);
    }
}
死循环

这个循环会在循环索引(i)小于或等于Integer.MAX_VALUE 时持续运行,当i 达到Integer.MAX_VALUE,而且再次被执行增量操做时,它就有绕回到了Integer.MIN_VALUE。

若是循环会迭代到int 数值的边界附近时,最好是使用一个long 变量做为循环索引。只需将循环索引的类型从int 改变为long 就能够解决该问题,从而使程序打印出指望的101:

更通常地讲,这里的教训就是int 不能表示全部的整数。不管什么时候使用了一个整数类型,都要意识到其边界条件。因此一般最好是使用一个取之范围更大的类型。(整数类型包括byte、charshortint 和long)
解析

下面代码的输出结果是?

public class Test1 {
    public static void main(String[] args) {
        int i = 0;
        while (-1 << i != 0)
            i++;
        System.out.println(i);
    }
}
常量-1 是全部32 位都被置位的int 数值(0xffffffff)。左移操做符将最右边的 i 位设置为0,并保持其他的32 - i 位为1。很明显,这个循环将完成32 次迭代,由于-1 << i 对任何小于32 的i 来讲都不等于0。故推断打印32。

实际上,它不会打印任何东西,而是进入了一个死循环。

由于(-1 << 32)等于-1 而不是0。若是左值是int,移位长度老是介于0 到31 之间,若是左操做数是long ,介于0 到63 之间。这个长度是对32取余的,若是左操做数是long 类型的,则对64 取余。若是对一个int 数值移位32 位,或者是对一个long 数值移位64 位,都只能返回这个数值自身的值。这条规则做用于所有的三个移位操做符:<<、>>和>>>。没有任何移位长度可让一个int 数值丢弃其全部的32 位,或者是让一个long数值丢弃其全部的64 位。

修改:不让-1 重复地移位,而是将前一次移位操做的结果保存起来,而且让它在每次迭代时左移 1 位,这样避免了移位32,出现死循环,下面打印32:

public class Shifty {
  public static void main(String[] args) {
    int distance = 0;
    for (int val = -1; val != 0; val <<= 1)
      distance++;
    System.out.println(distance);
  }
}

记住原则:若是可能的话,移位长度应该是常量。虽然并不可能老是可使用常量的移位长度,可是当必须使用一个很是量的移位长度时,请确保程序能够应付这种问题。
解析

下面代码的输出结果是?

        int distance = 1;
        distance <<= 1;
        System.out.println(distance);
        distance <<= -1;
        System.out.println(distance);
可能会猜测:负的移位长度的右移操做符能够起到左移操做符的做用,反之亦然。可是状况并不是如此。

铭记:右移操做符老是起到右移的做用,而左移操做符也老是起到左移的做用。负的移位长度经过只保留低5 位而剔除其余位的方式被转换成了正的移位长度——若是左操做数是long 类型的,则保留低6 位。所以,若是要将一个int数值左移,其移位长度为-1,那么移位的效果是它被左移了31 位。

总之,移位长度是对32 取余的,或者若是左操做数是long 类型的,则对64 取余。所以,使用任何移位操做符和移位长度,都不可能将一个数值的全部位所有移走。同时,咱们也不可能用右移操做符来执行左移操做,反之亦然。请使用常量的移位长度,若是移位长度不能设为常量,那么就要小心。

答案:

2
0
解析

下面代码的输出结果是?

        double i = 1.0 / 0;
        double j = 1.0e40;

        while (i != i + 1) {
            System.out.println("hello");
            while (j != j + 1) {
                System.out.println("world");
            }
        }
可能会有人回答:两个while都是无限循环,由于i不可能等于i + 1,同理j。先打印hello(换行)在无限循环打印world换行

实际上运行以后没有任何输出,Java 强制要求使用IEEE 754 浮点数算术运算,它可让你用一个double 或float 来表示无穷大,而无穷大加1 仍是无穷大。若是 i 在循环开始以前被初始化为无穷大,那么终止条件测试(i != i + 1)就会被计算为false,从而使循环永远都不会执行。

同理,一个浮点数值越大,它和其后继数值之间的间隔就越大。浮点数的这种分布是用固定数量的有效位来表示它们的必然结果。对一个足够大的浮点数加1 不会改变它的值,由于1 不足以“填补它与其后继者之间的空隙”。浮点数操做返回的是最接近其精确的数学结果的浮点数值。一旦毗邻的浮点数值之间的距离大于2,那么对其中的一个浮点数值加1 将不会产生任何效果,由于其结果没有达到两个数值之间的一半,舍掉了,无效的。

对于float 类型,加1 不会产生任何效果的最小级数是2的25,即33,554,432;而对于double 类型,最小级数是2的54,大约是1.8 × 10^16。

毗邻的浮点数值之间的距离被称为一个ulp,它是“最小单位(unit in the last place)”的首字母缩写词。在5.0 版中,引入了Math.ulp 方法来计算float 或 double 数值的ulp。

总之,用一个double 或一个float 数值来表示无穷大是能够的。第二点,将一个很小的浮点数加到一个很大的浮点数上时,将不会改变大的浮点数的值。记住二进制浮点算术只是对实际算术的一种近似。
解析

下面代码的输出结果是什么,为何?

        double i = 0.0 / 0;
        while (i == i) {
            System.out.println("hello world");
        }
什么输出都没有。

由于 IEEE 754 浮点算术保留了一个特殊的值用来表示一个不是数字的数量。这个值就是NaN(“不是一个数字(Not a Number)”的缩写),对于全部没有良好的数字定义的浮点计算,例如 0.0 / 0.0,其值都是它。规范中描述道,NaN 不等于任何浮点数值,包括它自身在内。所以,若是 i 在循环开始以前被初始化为NaN,那么终止条件测试(i == i)的计算结果就是false,循环就永远不会执行。很奇怪但倒是事实。

 为了表达清晰,可使用标准类库提供的常量:

double i = Double.NaN;

NaN 还有其余的惊人之处。任何浮点操做,只要它的一个或多个操做数为NaN,那么其结果为NaN。这条规则是很是合理的。

总之,float 和double 类型都有一个特殊的NaN 值,用来表示不是数字的数量。
解析

下面代码的输出结果是?

        short i = -1;
        while (i != 0) {
            i >>>= 1;
        }
        System.out.println(i);
无限循环

无符号右移操做符把0 从左边移入,而>>>=是一个复合赋值操做符,,而符合运算符会进行自动的类型窄化转换。(复合赋值操做符包括*=、/=、%=、+=、-=、<<=、>>=、>>>=、&=、^=和|=。)

由于i 的初始值((short)0xffff)是非0 的,因此循环体会被执行。在执行移位操做时,第一步是将i 提高为int 类型。全部算数操做都会对short、byte和char 类型的操做数执行这样的提高。这种提高是一个拓宽原始类型转换,所以没有任何信息会丢失。这种提高执行的是符号扩展,所以所产生的int 数值是0xffffffff。而后数值右移1 位,但不使用符号扩展,所以产生了int数值0x7fffffff。最后,这个数值被存回到 i 中。为了将int 数值存入short变量,Java 复合运算符执行的是的窄化转换,它直接将高16 位截掉。这样就只剩下 (short)oxffff 了,咱们又回到了开始处。循环的第二次以及后续的迭代行为都是同样的,所以循环将永远不会终止。

若是将i 声明为一个short 或byte 变量,而且初始化为任何负数,那么这种行为也会发生。若是 声明 i 为一个char,那么没法获得无限循环,由于 char 是无符号的,因此发生在移位以前的拓宽原始类型转换不会执行符号扩展。

总之,不要在short、byte 或char 类型的变量之上使用复合赋值操做符。由于这样的表达式执行的是混合类型算术运算,它容易形成混乱。它们执行将隐式地执行会丢失信息的窄化转型。
修改:

long i = -1; // 64 bits,int也行

这样循环执行迭代的次数与最大的整数类型所占据的位数相同
解析

下面代码的输出结果是?

        for (int i = 1; i <= 100; i++) {
            if (i % 10 * 10 == 0) {
                System.out.println(i);
            }
        }
也许有人回答是100

实际运行是:

10
20
30
40
50
60
70
80
90
100

由于算术运算符里,%和*,/同时出现是从左向右运算,相似+,-。%的优先级和*,/ 同样。
解析
相关文章
相关标签/搜索