Java运算符中的注意事项

修饰符

Java protected 关键字详解 | 菜鸟教程 (runoob.com)

protected的可见性在于两点:

  1. 基类的 protected 成员是包内可见的,并且对子类可见;
  2. 若子类与基类不在同一包中,那么在子类中,子类实例可以访问其从基类继承而来的protected方法,而不能访问基类实例的protected方法。
package p1;
public class Father1 {
    protected void f() {}    // 父类Father1中的protected方法
}
 
package p1;
public class Son1 extends Father1 {}
 
package p11;
public class Son11 extends Father1{}
 
package p1;
public class Test {
    
    protected void testClone() throws CloneNotSupportedException {
        super.clone(); // Compile OK     ----(5)
        this.clone(); // Compile OK     ----(6)
    }
    
    public static void main(String[] args) {

        Father1 father1 = new Father1();
        father1.clone();//Compile Error ('clone()' has protected access in 'java.lang.Object')    ---- (0)

        Son1 son1 = new Son1();
        son1.f(); // Compile OK     ----(1)
        son1.clone(); // Compile Error ('clone()' has protected access in 'java.lang.Object')    ----(2)

        Son11 son11 = new Son11();
        son11.f(); // Compile OK     ----(3)
        son11.clone(); // Compile Error     ----(4)
    }
}

(1)和(3)继承自Father1可见性为p1和Father1的子类Son1、Son11。由于Test在p1因此编译可以通过。

(0)(2)(4)clone()方法属于java.lang包下,因此可见性为java.lang包和Object子类Father1、Son1、Son11。但由于调用clone方法的类Test在p1包下因此编译不通过。三者的clone()在类Father1、Son1、Son11中是可见的例如(5)和(6)能够编译通过。

(5)子类调用父类protected方法;(6)子类调用继承自Object父类的protected方法。

数值类型之间的转换

数值类型之间的合法转换

image-20240723213652483

上图有6个实心箭头,表示无信息丢失的转换;有3个虚箭头,表示可能有精度损失的转换。例如,123456789是一个大整数,它所包含的位数比float类型所能够表达的位数多。当将这个整型数值转换为float类型时,将会得到同样大小的结果,但却失去了一定的精度。但是转double不会损失精度,不过long转double数量级小的时候并不会有损失,但是大数量级会出现误差,因此long转double也不推荐。

/**
 * Java中各种数据类型的取值范围如下:
 *
 * short:16位,有符号,取值范围为-32768(-2^15)到32767(2^15 - 1),默认值为0。
 *
 * int:32位,有符号,取值范围为-2147483648(-2^31)到2147483647(2^31 - 1),默认值为0。
 *
 * long:64位,有符号,取值范围为-9223372036854775808(-2^63)到9223372036854775807(2^63 -1),默认值为0L。
 *
 * float:32位,有符号,可表示的大致范围为-3.4028235E38到3.4028235E38,默认值为0.0f。
 *
 * double:64位,有符号,可表示的大致范围为-1.7976931348623157E308到1.7976931348623157E308,默认值为0.0d。
 *
 * 字节是用来度量数据类型所占空间的单位,其中:
 *
 * short:2字节
 *
 * int:4字节
 *
 * long:8字节
 *
 * float:4字节
 *
 * double:8字节
 */  
	int int1 = -123456789;
    int int2 = 123456789;

    long long1 = 9223372036854775807L;
    long long2 = -9223372036854775808L;        


    float float1 = int1;
    float float2 = int2;
    System.out.println("int1 -> float1: " + float1); //精度损失
    System.out.println("int2 -> float2: " + float2); //精度损失

    double double3 = long1;
    double double4 = long2;
    long long5 = 1234567899L;
    double double5 = long5;
    System.out.println("long -> double3: " + double3);// 会有精度损失
    System.out.println("long -> double4: " + double4);// 有损失
    System.out.println("long -> double5: " + double5);// ok

    char ch1 = '1';
    char ch2 = '9';
    int int5 = ch1 - '0';
    int int6 = ch2;
    System.out.println("ch1 -> int5: " + int5); //无损
    System.out.println("ch2 -> int6: " + int6); //编码

当使用两种不同数值类型进行二元操作时,要先将两个操作数转换为同一种类型,然后再进行计算:

  • 如果两个操作数中有一个是double类型,另一个操作数就会转换为double类型。
  • 否则,如果其中一个操作数是float类型,另一个操作数将会转换为float类型。
  • 否则,如果其中一个操作数是long类型,另一个操作数将会转换为long类型。
  • 否则,两个操作数都将被转换为int类型。
    byte byte1 = 1;
    byte byte2 = -1;

    short short1 = 200;
    short short2 = -200;

    int i1 = 3000000;
    int i2 = -3000000;

    long l1 = 400000000L;
    long l2 = -400000000L;

    double d1 = 500000000d;
    double d2 = -500000000d;

    float f1 = 3.1f;
    float f2 = -3.1f;

//        float f3 = f1 + d1; // 报错double to float
//        float f4 = f1 + d1 + l1; // 报错double to float

//        long l3 = f1 + l1; // 报错float to long
    double d3 = f1 + l1; // 没报错,但这里long转float后参与计算会损失精度
    System.out.println(d3); //4.0E8

//        int i3 = l1 + i1; // 报错long to int

//        short short3 = byte1 + short1; //报错int to short(没有double,float,long类型,则都会先转为int再进行计算)
    int i3 = byte1 + short1; //不报错
    System.out.println(i3); //201

【小结】数值型计算时都会先转换为最大类型而后才计算,当参与计算的数值不包含double,float,long类型时都会转换为int类型再进行数值计算

【注意】包含float和double的话一定要注意可能出现的精度损失问题

强制类型转换

如果试图将一个数值从一种类型强制转换为另一种类型,而又超出了目标类型的表示范围,结果就会截断成一个完全不同的值。例如,(byte)300的实际值为44。

// 00000001 00101100 -> 00101100(byte只占一个字节)
System.out.println((byte)300); // 44

位运算符

处理整型类型时,可以直接对组成整型数值的各个位完成操作。这意味着可以使用掩码技术得到整数中的各个位。位运算符包括:

image-20240723234139255

这些运算符按位模式处理。例如,如果n是一个整数变量,而且用二进制表示的n从右边数第4位为1,则

image-20240723234227293

会返回1,否则返回0。利用&并结合使用适当的2的幂,可以把其他位掩掉,而只保留其中的某一位。

在Java中,&, |, ^, 和 ~ 是位运算符,用于按位操作整数类型的数据(byte, short, int, long)。下面是这些运算符的详细解释及使用方法:

  1. &(按位与)运算符

    • 功能:逐位比较两个操作数的二进制表示,如果对应位都是1,则结果的该位也为1;否则为0。

    • 示例:

      int a = 5;    // 二进制: 0101
      int b = 3;    // 二进制: 0011
      int c = a & b;// 二进制: 0001, 十进制: 1
      
  2. |(按位或)运算符

    • 功能:逐位比较两个操作数的二进制表示,如果对应位中有任意一个是1,则结果的该位也为1;否则为0。

    • 示例:

      int a = 5;    // 二进制: 0101
      int b = 3;    // 二进制: 0011
      int c = a | b;// 二进制: 0111, 十进制: 7
      
  3. ^(按位异或)运算符

    • 功能:逐位比较两个操作数的二进制表示,如果对应位不同,则结果的该位为1;如果相同,则结果的该位为0。

    • 示例:

      int a = 5;    // 二进制: 0101
      int b = 3;    // 二进制: 0011
      int c = a ^ b;// 二进制: 0110, 十进制: 6
      
  4. ~(按位非)运算符

    • 功能:对单个操作数的每一位进行反转,1变为0,0变为1。

    • 示例:

      int a = 5;    // 二进制: 00000000...0101
      int b = ~a;   // 二进制: 11111111...1010
                    // 注意,结果会受到Java整数类型的符号位影响。
      

在使用这些运算符时,要注意以下几点:

  • 按位与 (&)、按位或 (|) 和按位异或 (^) 运算符都支持短路逻辑运算符的形式 &&, ||,但后者只用于布尔表达式。
  • 按位非 (~) 运算符会反转所有位,包括最高位的符号位,所以在处理负数时要特别小心。
  • 使用位运算符可以优化某些算法,尤其是在处理二进制数据或低级编程时。

除了上述的按位运算符,Java还有两个位移运算符,<<(左移)和 >>(右移),以及无符号右移运算符 >>>

移位运算符的右操作数要完成模32的运算(除非左操作数是long类型,在这种情况下需要对右操作数模64)。例如,1 << 35的值等同于1 << 3或8

在Java中,当你使用移位运算符(如 <<>>)时,确实会对右操作数进行模操作,具体是模32还是模64取决于左操作数的类型。

  • 如果左操作数是 int 类型,那么右操作数会被模32。
  • 如果左操作数是 long 类型,那么右操作数会被模64。

这是因为 int 类型在Java中是32位的,而 long 类型是64位的。移位运算符将根据操作数的位宽循环移动位,超出位宽的部分会重新从另一端开始。

例如,对于 1 << 35

  • 首先,因为 1int 类型,所以右操作数 35 会模32,得到 3
  • 接下来,1 << 3 相当于 1 的二进制形式向左移动三位,即从 00000000...0001 移动到 00000000...1000,结果是 8

同样地,对于 1L << 35(其中 1L 表示长整型 long):

  • 因为左操作数是 long 类型,所以右操作数 35 会模64,得到 35
  • 接下来,1L << 351 的二进制形式向左移动35位。由于 long 类型是64位的,这意味着最开始的 1 会被移到第36位上,前面的35位都是零,结果是一个非常大的数字,具体是多少取决于Java虚拟机的实现细节,但通常来说,它会是一个比 1 << 3 大得多的数。

这个模运算保证了无论右操作数有多大,实际执行的位移操作都不会超过类型的最大位数,从而避免了不必要的计算。

括号与运算符级别

如果不使用圆括号,就按照给出的运算符优先级次序进行计算。同一个级别的运算符按照从左到右的次序进行计算(除了表中给出的右结合运算符外。)例如,由于&&的优先级比||的优先级高,所以表达式

image-20240723234911493

等价于

image-20240723234927083

又因为+=是右结合运算符,所以表达式

image-20240723234946777

等价于

image-20240723235005052

也就是将b += c的结果(加上c之后的b)加到a上。

image-20240723235546372

值得注意的是从右向左计算的运算符,例如

int a = 10;
int b = 5;
a /= b; // a = a/b 先计算a/b再将结果赋值给a
System.out.println(a); // 2
  • 13
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值