一、位操作
1 按位非运算符 ~
~将primitive主数据类型的字节组合值取反;
int x = 10; // 00001010
x = ~x; // 11110101
2 按位与运算符 &
若两个位都是1才返回1,否则返回0;
int x = 10; // 00001010
int y = 6; // 00000110
int a = x & y; // 00000010
3 按位或运算 |
只要其中一个为1则返回1,否则返回0;
int a = x | y; // 00001110
4 按位异或运算符 ^
位相同返回0,否则返回1;
int a = x ^ y; // 00001100
5 移位运算符
Java使用补码存储负值,改变正负号时要把位取反然后加1;
整数最左方的位是符号位,负整数此位是1,正整数此位是0;
5.1 右移运算符 >>
将运算数的二进制码整体右移指定位数,右移后的空位用符号位补充,即正整数用0补充,负整数用1补充;
int x = -11; // 11110101
int y = x >> 2; // 11111101
5.2 无符号右移运算符 <<
将运算数的二进制码整体右移指定位数,右移后的空位用0补充;
int y = x >>> 2; // 00111101
5.3 左移运算符 <<
将运算数的二进制码整体左移指定位数,左移后的空位用0补充;
int y = x << 2; // 11010100
二、不变性
1 String的不变性
创建新的String时,Java虚拟机会把它放到称为"String Pool"的特殊存储区中;
若已经出现同值得String,Java虚拟机不会重复建立String,只会引用已存在者;
因为String是不变的,引用变量无法改变其它参考变量引用到的同一个String值;
如下,实际上会创建10个String对象——"0", "01", ..., "0123456789",最后引用到"0123456789"这个值;
String s = "0";
for(int x =1; x< 10; x++){
s = s + x;
}
String Pool不受Garbage Collector管理,因此在for循环中建立的10个String有9个是在浪费空间;
2 包装类的不变性
包装类的主要用途在于:
将primitive主数据类型包装成对象;
使用静态的工具方法(如Integer.parseInt());
包装对象创建后就无法改变该对象的值;
如创建以下包装类,其值永远是42,包装对象没有setter;
Integer iWrap = new Integer(42);
三、断言
以前,程序员在程序中加入一大堆System.out.println()命令显示出错的信息,列出变量值,还有执行到哪里的信息以观察流程控制的走向;
等到程序正确执行,还要将所有println()拿掉;既麻烦又易出错;
1 断言
现在,使用断言可代替传统方法;
执行时,若没有特别设定的话,被加入到程序中的assert命令会被Java虚拟机忽略;
若指定Java虚拟机打开断言,能在不变动任何代码的情况下帮助除错;
2 使用断言
assert后的条件表达式必须是true,如下:
// 若true,继续执行
// 若false,抛出AssertionError
assert (height > 0);
也可加上断言失败后的提示信息:
assert (height > 0): "height = " + height;
注意:不要再assert中改变对象的状态;
3 带有断言的编译和执行
编译:
javac Test.java
执行:
java -ea Test
四、块区域
之前讨论过,局部变量的生命期范围只在声明它的方法还待在栈上的期间内;
但某些变量的生命周期更短;
区段程序代码在方法中,以{}字符区分,常见有循环、条件测试运算;
五、链接的调用
使用合法的快捷方式让语法更加干净、方便阅读;
如下:
StringBuffer sb = new StringBuffer("spring");
sb = sb.delete(3,6).insert(2,"umme").deleteCharAt(1); // summer
最左边的方法是sb.delete(3, 6),返回StringBuffer对象,其值为"spr";
新创建的StringBuffer对象调用insert(),返回StringBuffer对象,其值为"spummer";
新创建的StringBuffer对象调用deleteCharAt(),返回StringBuffer对象,其值为"summer";
更常见、更有用的例子如下,调用方法但不需要一个引用的方法;
class Foo{
public static void main(String[] args){
// 需调用go(),但又不需要维持一个对Foo的引用
new Foo().go();
}
void go(){
// 要执行的程序
}
}
六、内部类
内部类是在一个外部类的内部再定义一个类,类名不必和文件名相同;
内部类可以是静态static、public、default、protected、private;其中,public修饰的任何地方都能访问,protected修饰的只能在同一个包下或继承外部类的情况下访问,default修饰的只能在同一个包下访问,private修饰的只能在同一个类下访问;
外部类只能使用public或default;
内部类是一个编译时的概念,编译成功后就会变成完全不同的两个类;
1 成员内部类
成员内部类不能含有static的变量/方法,因为成员内部类需要先创建外部类才能创建自己;
成员内部类,即作为外部类的成员,可直接使用外部类的所有成员和方法,即使是private的;
class Circle { // 外部类
private double radius = 0;
public static int count =1;
public Circle(double radius) { // 构造函数
this.radius = radius;
}
class Draw { //内部类
public void drawSahpe() {
System.out.println(radius); //外部类的private成员
System.out.println(count); //外部类的静态成员
}
}
}
注意:当成员内部类拥有和外部类同名的成员变量/方法,发生隐藏现象——默认情况下访问的是成员内部类的成员;
若要访问外部类的同名成员,需以下形式进行访问:
外部类.this.成员变量/方法
外部类访问内部类的成员变量/方法,必须通过内部类的对象,即先创建一个成员内部类的对象,再通过指向该对象的引用进行访问;
class Circle { // 外部类
private double radius = 0;
public Circle(double radius) {
this.radius = radius;
getDrawInstance().drawSahpe(); //必须先创建成员内部类的对象,再进行访问
}
private Draw getDrawInstance() {
return new Draw();
}
class Draw { //内部类
public void drawSahpe() {
System.out.println(radius); //外部类的private成员
}
}
}
成员内部类是依附外部类存在的,即若要创建成员内部类的对象,必须存在一个外部类的对象;
public class Test {
public static void main(String[] args) {
//第一种方式:
Outter outter = new Outter();
Outter.Inner inner = outter.new Inner(); //必须通过Outter对象来创建
//第二种方式:
Outter.Inner inner1 = outter.getInnerInstance();
}
}
class Outter { // 外部类
private Inner inner = null;
public Outter() {
}
public Inner getInnerInstance() {
if(inner == null)
inner = new Inner();
return inner;
}
class Inner { // 内部类
public Inner() {
}
}
}
2 局部内部类
局部内部类是定义在一个方法或一个作用域内的类,访问权限只在方法内火该作用域内;
局部内部类类似方法中的一个局部变量,不能有public、protected、private、static修饰符;
class People{
public People() {
}
}
class Man{ // 外部类
public Man(){
}
public People getWoman(){
class Woman extends People{ //局部内部类
int age =0;
}
return new Woman();
}
}
3 匿名内部类
匿名内部类不能加访问修饰符;
new 匿名类,该类必须先定义;
4 静态内部类
静态内部类也是定义在一个类中的类,且在类的前面加关键字static;
静态内部类不依赖于外部类,且不能使用外部类的非静态成员变量/方法,因为在没有外部类的对象的情况下,可以创建静态内部类的对象;
public class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
}
}
class Outter { // 外部类
int a = 10;
static int b = 6;
public Outter() {
}
static class Inner { // 静态内部类
public Inner() {
// System.out.println(a); 编译器报错,因为静态内部类无法访问外部类的非静态变量/方法
System.out.println(b);
}
}
}
5 内部类的继承
内部类的继承是内部类被继承,普通类extends内部类;
public class InheritInner extends WithInner.Inner {
// InheritInner() 是不能通过编译的,一定要加上形参
InheritInner(WithInner wi) {
wi.super();
}
public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner obj = new InheritInner(wi);
}
}
class WithInner {
class Inner {
}
}
要想创建Inner的对象,必须先创建WithInner的对象后才能创建Inner对象,那么现在你要用一个类InheritInner继承Inner类,在继承过程中构造方法会被调用,即使你不写也会调用默认构造方法,但问题出现了,在调用父类Inner构造函数时找不到WithInner的对象,所以就必须给InheritInner类的构造函数传入WithInner对象,再通过wi.super();方法调用Inner的默认构造方法,因为这是创建对象的基本流程,所以这句话wi.super();是必须的。
注意:构造函数必须要有指向外部类对象的引用,并通过该引用调用super();
七、存取权限和存取修饰符
Java有4中存取权限等级和3种存取修饰符;
1 存取权限
1.1 public
代表任何程序代码都可存取的公开事物(类、变量、方法、构造函数等);
1.2 protected
只在同一个包中的默认事物或不同包的子类继承受保护的部分能存取;
只能用在继承上,若不同包的子类有父类实例的引用,子类无法透过次引用存取父类的protected方法,唯一办法就是通过继承取得此方法;
1.3 default
只有在同一个包中的默认事物能存取;
1.4 private
只有同一个类中的程序代码才能存取;针对类不是对象,因此Dog可看到别的Dog的私用部分,而Cat不能看到Dog的私用部分;
2 存取修饰符
2.1 public
2.2 protected
2.3 缺省
2.4 private
注意:通常只会用到public和private;
八、String和StringBuffer/StringBuilder
1 String
String是不可变的;
对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象
1.1 String str="hello world"和String str=new String("hello world")的区别
str1和str3都在编译期间生成字面常量和符号引用,运行期间字面常量存储在运行时常量池中;JVM在运行时常量池中查找是否存在相同的字面常量,若存在,则直接引用指向已经存在的字面常量,否则在运行时常量池中开辟一个空间存储该字面常量,并将引用指向该字面常量;
通过new关键字生成对象是在堆区进行,而在堆区进行对象生成的过程是不会检查该对象是否已经存在,因此通过new创建对象,创建出来的一定是不同的对象,即使字符串内容是相同的;
String str1 = "hello world";
String str2 = new String("hello world");
String str3 = "hello world";
String str4 = new String("hello world");
System.out.println(str1==str2); // false
System.out.println(str1==str3); // true
System.out.println(str2==str4); // false
1.2 +运算符
// Java编译器把该语句编译成String b = "hello2";,因此a==b
String a = "hello2"; String b = "hello"+2;
// 由于有符号引用,因此String c = b + 2;不会在编译期间将其当作字面常量处理
// 生成的对象保存在堆上,因此a!=c
String a = "hello2"; String b = "hello"; String c = b + 2;
// final修饰的变量在class文件常量池中保存一个副本,即不会通过连接进行访问
// 对final变量的访问在编译期间会直接被代替为真实的值,即String c = "hello" + 2;
// 因此a==c
String a = "hello2"; final String b = "hello"; String c = b + 2;
// 虽然final修饰变量b,但由于其赋值是通过调用方法返回的,其值必须在运行期间才能确定
// 因此a!=c
public class Main {
public static void main(String[] args) {
String a = "hello2";
final String b = getHello();
String c = b + 2;
System.out.println((a == c));
}
public static String getHello() {
return "hello";
}
}
2 StringBuffer
StringBuffer是可变的,是线程安全的,在多线程访问时起到安全保护作用;
StringBuffer类每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用;
多数情况下推荐使用StringBuffer,特别是字符串对象经常改变的情况下;
StringBuffer sb = new StringBuffer("This is a").append(" simple").append("test.");
sb.delete(0,5);
sb.insert(0,"This ");
sb.toString();
3 StringBuilder
StringBuilder是可变的,但是线程不安全的;
除非确定系统的瓶颈是在StringBuffer上,或确定模块不会运行在多线程模式下,才使用StringBuilder,否则使用StringBuffer;
九、多维数组
1 创建
在Java中,二维数组只是个数组的数组,三维数组是数组的数组的数组;
如下,Java虚拟机创建了4个元素的数组,这些元素实际上是对2个元素数组的引用变量;
int[][] a2d = new int[4][2];
2 操作
2.1 存取第三个数组的第二个元素;
int x = a2d[2][1];
2.2 对某个子数组创建引用
int[] copy = a2d[1];
2.3 初始化2*3数组
int[][] x = {{1,2,3}, {2,3,4}};
2.4 创建非常规二维数组
// 创建长度为2的第一层
int[][] y = new int[2][];
// 创建三元素的子数组
y[0] = new int[3];
// 创建五元素的子数组
y[1] = new int[5];
十、枚举
public enum Members {JERRY, BOBBY, PHIL};
// Members类型的selectedBandMember只能是JERRY, BOBBY, PHIL这三种值
public Members selectedBandMember;
if(selectedBandMember == Members.JERRY){
}else if(selectedBandMember.equals(Members.BOBBY)){
}