Java 自增自减运算符和移位运算符介绍

Posted JavaGuide

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 自增自减运算符和移位运算符介绍相关的知识,希望对你有一定的参考价值。

摘自 JavaGuide (「Java学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识。准备 Java 面试,首选 JavaGuide!)

自增自减运算符

在写代码的过程中,常见的一种情况是需要某个整数类型变量增加 1 或减少 1,Java 提供了一种特殊的运算符,用于这种表达式,叫做自增运算符(++)和自减运算符(--)。

++ 和 -- 运算符可以放在变量之前,也可以放在变量之后,当运算符放在变量之前时(前缀),先自增/减,再赋值;当运算符放在变量之后时(后缀),先赋值,再自增/减。例如,当 b = ++a 时,先自增(自己增加 1),再赋值(赋值给 b);当 b = a++ 时,先赋值(赋值给 b),再自增(自己增加 1)。也就是,++a 输出的是 a+1 的值,a++输出的是 a 值。用一句口诀就是:“符号在前就先加/减,符号在后就后加/减”。

移位运算符

移位运算符是最基本的运算符之一,几乎每种编程语言都包含这一运算符。移位操作中,被操作的数据被视为二进制数,移位就是将其向左或向右移动若干位的运算。

移位运算符在各种框架以及 JDK 自身的源码中使用还是挺广泛的,HashMap(JDK1.8) 中的 hash 方法的源码就用到了移位运算符:

static final int hash(Object key) 
    int h;
    // key.hashCode():返回散列值也就是hashcode
    // ^ :按位异或
    // >>>:无符号右移,忽略符号位,空位都以0补齐
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  

在 Java 代码里使用 <<>>>>>转换成的指令码运行起来会更高效些。

掌握最基本的移位运算符知识还是很有必要的,这不光可以帮助我们在代码中使用,还可以帮助我们理解源码中涉及到移位运算符的代码。

Java 中有三种移位运算符:

  • << :左移运算符,向左移若干位,高位丢弃,低位补零。x << 1,相当于 x 乘以 2(不溢出的情况下)。
  • >> :带符号右移,向右移若干位,高位补符号位,低位丢弃。正数高位补 0,负数高位补 1。x >> 1,相当于 x 除以 2。
  • >>> :无符号右移,忽略符号位,空位都以 0 补齐。

由于 doublefloat 在二进制中的表现比较特殊,因此不能来进行移位操作。

移位操作符实际上支持的类型只有intlong,编译器在对shortbytechar类型进行移位前,都会将其转换为int类型再操作。

如果移位的位数超过数值所占有的位数会怎样?

当 int 类型左移/右移位数大于等于 32 位操作时,会先求余(%)后再进行左移/右移操作。也就是说左移/右移 32 位相当于不进行移位操作(32%32=0),左移/右移 42 位相当于左移/右移 10 位(42%32=10)。当 long 类型进行左移/右移操作时,由于 long 对应的二进制是 64 位,因此求余操作的基数也变成了 64。

也就是说:x<<42等同于x<<10x>>42等同于x>>10x >>>42等同于x >>> 10

左移运算符代码示例

int i = -1;
System.out.println("初始数据: " + i);
System.out.println("初始数据对应的二进制字符串: " + Integer.toBinaryString(i));
i <<= 10;
System.out.println("左移 10 位后的数据 " + i);
System.out.println("左移 10 位后的数据对应的二进制字符 " + Integer.toBinaryString(i));

输出:

初始数据: -1
初始数据对应的二进制字符串: 11111111111111111111111111111111
左移 10 位后的数据 -1024
左移 10 位后的数据对应的二进制字符 11111111111111111111110000000000

由于左移位数大于等于 32 位操作时,会先求余(%)后再进行左移操作,所以下面的代码左移 42 位相当于左移 10 位(42%32=10),输出结果和前面的代码一样。

int i = -1;
System.out.println("初始数据: " + i);
System.out.println("初始数据对应的二进制字符串: " + Integer.toBinaryString(i));
i <<= 42;
System.out.println("左移 10 位后的数据 " + i);
System.out.println("左移 10 位后的数据对应的二进制字符 " + Integer.toBinaryString(i));

右移运算符使用类似,篇幅问题,这里就不做演示了。

Java自增自减运算符

Java10个基础题

1: 自增自减运算符

  • ++和–运算符可以放在变量之前,也可以放在变量之后,当运算符放在变量之前时(前缀),先自增/减,再赋值;当运算符放在变量之后时(后缀),先赋值,再自增/减
  • 例如,当 b = ++a 时,先自增(自己增加 1),再赋值(赋值给 b);当 b = a++ 时,先赋值(赋值给 b),再自增(自己增加 1)。也就是,++a 输出的是 a+1 的值,a++输出的是 a 本身值。

2: 自动装箱与拆箱

  • 装箱:将基本类型用它们对应的引用类型包装起来
  • 拆箱:将包装类型转换为基本数据类型
  • 装箱其实就是调用了 包装类的valueOf()方法,拆箱其实就是调用了 xxxValue()方法。
Integer i = 10;  //装箱
int n = i;   //拆箱

Integer i = 10 等价于 Integer i = Integer.valueOf(10)
int n = i 等价于 int n = i.intValue();

3: ==和equals比较

  • == 比较基本数据类型的时候,比较的是值,引用数据类型比较的是地址(new的对象,==比较永远是false)
  • equals:属于Object类的方法,如果我们没有重写过equals方法,那么它就是 ==,但是字符串里面的equals被重写过了,比较的是值
String str1 = "abc";
String str2 = new String("abc");
String str3 = "abc";
String str4 =  "xxx";
String str5 = "abc" + "xxx";
String str6 = s3 + s4;

str1 == str2:false
str1.equals(str2)true
str1 == str5:false
str1 == str6:false
str5 == str6:false
str5.equals(str6)true

4: 为什么重写 equals 时必须重写 hashCode 方法?

  • 比如在HasMap中,put一个值,则先会计算出其hashCode值,得到对应的索引位置,那么可能该索引位置上已经有值了,那么此时就会调用equals方法,比较两个值是不是一样的。所以重写 equals 时必须重写 hashCode 方法

5: String、StringBuffer、StringBuilder

  • String是final修饰的,不可变,每次操作都会产生新的String对象
  • StringBuffer是线程安全的,StringBuilder线程不安全的
  • 性能:StringBuilder > StringBuffer > String,所以经常需要改变字符串内容时使用前面两个效率会更高

6: 重载和重写的区别

  • 重载: 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
  • 重写: 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法。

7: 接口和抽象类的区别

  • 抽象类可以存在普通成员函数,而接口中只能存在public abstract 方法。
  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的。
  • 抽象类只能继承一个,接口可以实现多个。

8: List和Set的区别

  • List:有序,按对象进入的顺序保存对象,可重复,允许多个Null元素对象,可以使用Iterator取出所有元素,在逐一遍历,还可以使用get(int index)获取指定下标的元素
  • Set:无序,不可重复,最多允许有一个Null元素对象,取元素时只能用Iterator接口取得所有元素,在逐一遍历各个元素

9: ArrayList和LinkedList区别

  • Arraylist 底层使用的是 Object 数组;LinkedList 底层使用的是 双向链表 数据结构
  • LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。
  • ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
  • ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响;LinkedList 采用链表存储,所以对于add(E e)方法的插入,删除元素时间复杂度不受元素位置的影响

10: continue、break、和 return 的区别是什么?

  • continue :指跳出当前的这一次循环,继续下一次循环。
  • break :指跳出整个循环体,继续执行循环下面的语句。
  • return; :直接使用 return 结束方法执行,用于没有返回值函数的方法
  • return value; :return 一个特定值,用于有返回值函数的方法

以上是关于Java 自增自减运算符和移位运算符介绍的主要内容,如果未能解决你的问题,请参考以下文章

Java运算符使用总结(重点:自增自减位运算和逻辑运算)

Java自增自减运算符

java自增自减运算符

java自增自减运算符详解,含爱奇艺,小米,腾讯,阿里

在C语言中自增自减运算符有啥作用?

学习自增自减运算符