修饰符
Java protected 关键字详解 | 菜鸟教程 (runoob.com)
protected的可见性在于两点:
- 基类的 protected 成员是包内可见的,并且对子类可见;
- 若子类与基类不在同一包中,那么在子类中,子类实例可以访问其从基类继承而来的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方法。
数值类型之间的转换
数值类型之间的合法转换
上图有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
位运算符
处理整型类型时,可以直接对组成整型数值的各个位完成操作。这意味着可以使用掩码技术得到整数中的各个位。位运算符包括:
这些运算符按位模式处理。例如,如果n是一个整数变量,而且用二进制表示的n从右边数第4位为1,则
会返回1,否则返回0。利用&并结合使用适当的2的幂,可以把其他位掩掉,而只保留其中的某一位。
在Java中,&
, |
, ^
, 和 ~
是位运算符,用于按位操作整数类型的数据(byte
, short
, int
, long
)。下面是这些运算符的详细解释及使用方法:
-
&
(按位与)运算符:-
功能:逐位比较两个操作数的二进制表示,如果对应位都是1,则结果的该位也为1;否则为0。
-
示例:
int a = 5; // 二进制: 0101 int b = 3; // 二进制: 0011 int c = a & b;// 二进制: 0001, 十进制: 1
-
-
|
(按位或)运算符:-
功能:逐位比较两个操作数的二进制表示,如果对应位中有任意一个是1,则结果的该位也为1;否则为0。
-
示例:
int a = 5; // 二进制: 0101 int b = 3; // 二进制: 0011 int c = a | b;// 二进制: 0111, 十进制: 7
-
-
^
(按位异或)运算符:-
功能:逐位比较两个操作数的二进制表示,如果对应位不同,则结果的该位为1;如果相同,则结果的该位为0。
-
示例:
int a = 5; // 二进制: 0101 int b = 3; // 二进制: 0011 int c = a ^ b;// 二进制: 0110, 十进制: 6
-
-
~
(按位非)运算符:-
功能:对单个操作数的每一位进行反转,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
:
- 首先,因为
1
是int
类型,所以右操作数35
会模32,得到3
。 - 接下来,
1 << 3
相当于1
的二进制形式向左移动三位,即从00000000...0001
移动到00000000...1000
,结果是8
。
同样地,对于 1L << 35
(其中 1L
表示长整型 long
):
- 因为左操作数是
long
类型,所以右操作数35
会模64,得到35
。 - 接下来,
1L << 35
将1
的二进制形式向左移动35位。由于long
类型是64位的,这意味着最开始的1
会被移到第36位上,前面的35位都是零,结果是一个非常大的数字,具体是多少取决于Java虚拟机的实现细节,但通常来说,它会是一个比1 << 3
大得多的数。
这个模运算保证了无论右操作数有多大,实际执行的位移操作都不会超过类型的最大位数,从而避免了不必要的计算。
括号与运算符级别
如果不使用圆括号,就按照给出的运算符优先级次序进行计算。同一个级别的运算符按照从左到右的次序进行计算(除了表中给出的右结合运算符外。)例如,由于&&
的优先级比||
的优先级高,所以表达式
等价于
又因为+=是右结合运算符,所以表达式
等价于
也就是将b += c的结果(加上c之后的b)加到a上。
值得注意的是从右向左计算的运算符,例如
int a = 10;
int b = 5;
a /= b; // a = a/b 先计算a/b再将结果赋值给a
System.out.println(a); // 2