Java语言特性
-
简单性(对比C++来说少了指针操作,多了内存回收等)
-
面向对象
-
分布式
- Java应用程序能够通过URL打开和访问网络上的对象,其便捷程度就好像访问本地文件一样。
-
健壮性
- Java编译器能够检测许多在其他语言中仅在运行时才能够检测出来的问题。
- Java和C++最大的不同在于Java采用的指针模型可以消除重写内存和损坏数据的可能性(对于曾经花费几个小时来检查由于指针bug而引起内存冲突的人来说,一定很喜欢Java的这一特性。)
-
可移植性
- Java中的int永远为32位的整数,而在C/C++中,int可能是16位整数、32位整数
- Java中二进制数据以固定的格式进行存储和传输,消除了字节顺序的困扰。
-
解释型
- Java解释器可以在任何移植了解释器的机器上执行Java字节码。
简单地讲,面向对象设计是一种程序设计技术。它将重点放在数据(即对象)和对象的接口上。用木匠打一个比方,一个“面向对象的”木匠始终关注的是所制作的椅子,第二位才是所使用的工具;一个“非面向对象的”木匠首先考虑的是所用的工具。在本质上,Java的面向对象能力与C++是一样的。
修饰符
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