[Java核心技术] static、final和常量设计
目录
Java 核心技术读书笔记——Java static、final和常量设计
1 static
static
静态的,Java
中特殊的关键字,可作用在变量、方法、类、匿名方法块
1.2 静态变量
- 静态变量,类共有成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
public class Potato { static int price = 5; String content = ""; public Potato(int price, String content) { this.price = price; this.content = content; } public static void main(String[] a) { System.out.println(Potato.price); //Potato.content wrong System.out.println("----------------------------------"); Potato obj1 = new Potato(10,"青椒土豆丝"); // 以下两行都输出10,Potato.price 和 obj1.price 在内存中是同一个东西 System.out.println(Potato.price); System.out.println(obj1.price); System.out.println("----------------------------------"); Potato obj2 = new Potato(20,"酸辣土豆丝"); System.out.println(Potato.price); System.out.println(obj2.price); } } /** * * 输出结果: * 5 * ---------------------------------- * 10 * 10 * ---------------------------------- * 20 * 20 * */
static
变量只依赖于类存在(通过类访问即可),不依赖于对象实例存在。即可以用过Potato.price
即可访问- 所有的对象实例,如上例中的
obj1
和obj2
关于price
变量的值共享存储在一个共同的空间(栈)
1.2 静态方法
- 静态方法也无需通过对象来引用,而通过类名可以直接引用。
- 静态方法中,只能使用静态变量,不能使用非静态变量。
- 静态方法不能调用非静态方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
public class StaticMethodTest { int a = 111111; static int b = 222222; public static void hello() { System.out.println("000000"); System.out.println(b); //System.out.println(a); //error, cannot call non-static variables //hi() //error, cannot call non-static method } public void hi() { System.out.println("333333"); hello(); //ok, call static methods System.out.println(a); //ok, call non-static variables System.out.println(b); //ok, call static variables } public static void main(String[] a) { StaticMethodTest.hello(); //StaticMethodTest.hi(); //error, 不能使用类名来引用非静态方法 StaticMethodTest foo = new StaticMethodTest(); foo.hello(); //warning, but it is ok foo.hi(); //right } }
1.3 static修饰类(内部类)
使用机会较少
1.4 static块
static块
只在类第一次被加载时调用,即程序运行期间,这段代码只运行一次- 执行顺序:
static块
>匿名块
>构造函数
- 不建议编写块代码,块代码会给程序带来混淆。建议将块代码封装成函数再调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
// StaticBlock.java class StaticBlock { //staticl block > anonymous block > constructor function // 静态代码块 static { System.out.println("22222222222222222222"); } // 匿名代码块 { System.out.println("11111111111111111111"); } // 构造函数 public StaticBlock() { System.out.println("33333333333333333333"); } // 匿名代码块 { System.out.println("44444444444444444444"); } } //StaticBlockTest.java public class StaticBlockTest { public static void main(String[] args) { System.out.println("000000000000000"); // TODO Auto-generated method stub StaticBlock obj1 = new StaticBlock(); StaticBlock obj2 = new StaticBlock(); } } /** * * 运行结果: * 000000000000000 * 22222222222222222222 * 11111111111111111111 * 44444444444444444444 * 33333333333333333333 * 11111111111111111111 * 44444444444444444444 * 33333333333333333333 * */
2 单例模式(Singleton)
-
单例模式(单态模式,Singleton),保证一个类有且只有一个对象
- 采用
static
来共享对象实例 - 采用
private
构造函数,防止外界new
操作 - 限定某一个类在整个程序运行过程中,只能保留一个实例对象在内存空间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
public class Singleton { private static Singleton obj = new Singleton(); //共享同一个对象 private String content; private Singleton() //确保只能在类内部调用构造函数 { this.content = "abc"; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public static Singleton getInstance() { //静态方法使用静态变量 //另外可以使用方法内的临时变量,但是不能引用非静态的成员变量 return obj; } public static void main(String[] args) { Singleton obj1 = Singleton.getInstance(); System.out.println(obj1.getContent()); // abc Singleton obj2 = Singleton.getInstance(); System.out.println(obj2.getContent()); // abc obj2.setContent("def"); System.out.println(obj1.getContent()); System.out.println(obj2.getContent()); System.out.println(obj1 == obj2); // true, obj1和obj2指向同一个对象 } }
- 采用
-
单例模式是
GoF
的23种设计模式(Design Pattern)中经典的一种,属于创造型模型类型。
笔记
设计模式:在软件开发过程中,经过验证的,用于解决在特定环境下的、重复出现的、特定问题的结局方案。
3 final
-
Java
的final
关键字可以用来修饰类、方法、字段 -
final
的类,不能被继承1 2 3 4 5 6 7
final public class FinalFather { } class Son1 extends FinalFather { }
-
父类中如果有
final
的方法,子类中不能改写此方法1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// FinalMethodFather.java public class FinalMethodFather { public final void f1() { } } //FinalMethodSon.java public class FinalMethodSon extends FinalMethodFather{ public void f1() // error Cannot override the final method from FinalMethodFather { } }
-
final
的变量,不能再次赋值- 如果是基本型的变量,不能修改其值
1 2 3 4 5 6 7 8
public class FinalPrimitiveType { public static void main(String[] args) { // TODO Auto-generated method stub final int a = 5; a=10; // error The final local vaiable a cannot be assigned. It must be blank and not using a compound assignment } }
- 如果是实例对象,那么不能修改其指针(但是可以修改对象内的值)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
class FinalObject { int a = 10; } public class FinalObjectTest { public static void main(String[] args) { final FinalObject obj1 = new FinalObject(); System.out.println(obj1.a); obj1.a = 20; System.out.println(obj1.a); obj1 = new FinalObject(); //final对象不能变更指针 } }
4 常量设计和常量池
4.1 常量
- 常量:一种不会修改的变量
- 不能修改,
final
- 不会修改/只读/只要一份,
static
- 方便访问
public
final
和static
可以颠倒顺序
- 不能修改,
- 常量的定义
1 2
public static final String = DEFAULT_COUNTRY = "China"; //建议变量名字全大写,以字符相连,如 DEFAULT_COUNTRY
- 一种特殊的常量:接口内定义的变量默认为常量
4.2 常量池
-
Java为很多基本类型的包装类/字符串都建立常量池
-
常量池:相同的值只存储一份,节省内存,共享访问
-
基本类型的包装类
Boolean
:true
,false
Byte
,Character
:\u00000 —— \u007f
(0 —— 127
)Short
,Int
,Long
:-128 ~ 127
Float
,Double
: 没有缓存(常量池)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
public class CacheTest { public static void main(String[] args) { Boolean b1 = true; //true,false Boolean b2 = true; System.out.println("Boolean Test: " + String.valueOf(b1 == b2)); //true Byte b3 = 127; //\u0000-\u007f Byte b4 = 127; System.out.println("Byte Test: " + String.valueOf(b3 == b4)); //true Character c1 = 127; //\u0000-\u007f Character c2 = 127; System.out.println("Character Test: " + String.valueOf(c1 == c2)); //true Short s1 = -128; //-128~127 Short s2 = -128; System.out.println("Short Test: " + String.valueOf(s1 == s2)); //true Integer i1 = -128; //-128~127 Integer i2 = -128; System.out.println("Integer Test: " + String.valueOf(i1 == i2)); //true Long l1 = -128L; //-128~127 Long l2 = -128L; System.out.println("Long Test: " + String.valueOf(l1 == l2)); //true Float f1 = 0.5f; Float f2 = 0.5f; System.out.println("Float Test: " + String.valueOf(f1 == f2)); //false Double d1 = 0.5; Double d2 = 0.5; System.out.println("Double Test: " + String.valueOf(d1 == d2)); //false } }
-
Java 为常量字符串都建立常量池缓存机制
-
字符串常量
1 2 3 4 5 6 7 8 9 10 11
public class StringConstantTest { public static void main(String[] args) { String s1 = "abc"; String s2 = "abc"; String s3 = "ab" + "c"; //都是常量,编译器将优化,下同 String s4 = "a" + "b" + "c"; System.out.println(s1 == s2); //true System.out.println(s1 == s3); //true System.out.println(s1 == s4); //true } }
-
基本类型的包装类和字符串有两种创建方式
- 常量式(字面量)赋值创建,放在栈内存(将被常量化)
1 2
Integer a = 10; Strign b = "abc";
new
对象进行创建,放在堆内存(不会常量化)1 2
Integer c = new Integer(10); String d = new String("abc");
- 这两种创建方式导致创建的对象存放的位置不同。
- 栈内存读取速度快但容量小,堆内存读取速度慢但容量大。
- 查看
BoxClassTest.java
分析Integer
类1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
/** * * 基本类型和包装类比较,将对包装类自动拆箱 * 对象比较,比较地址 * 加法(+)会自动拆箱 * */ public class BoxClassTest { public static void main(String[] args) { int i1 = 10; Integer i2 = 10; // 自动装箱 System.out.println(i1 == i2); //true // 自动拆箱 基本类型和包装类进行比较,包装类自动拆箱 Integer i3 = new Integer(10); System.out.println(i1 == i3); //true // 自动拆箱 基本类型和包装类进行比较,包装类自动拆箱 System.out.println(i2 == i3); //false // 两个对象比较,比较其地址。 // i2是常量,放在栈内存常量池中,i3是new出对象,放在堆内存中 Integer i4 = new Integer(5); Integer i5 = new Integer(5); System.out.println(i1 == (i4+i5)); //true System.out.println(i2 == (i4+i5)); //true System.out.println(i3 == (i4+i5)); //true // i4+i5 操作将会使得i4,i5自动拆箱为基本类型并运算得到10. // 基础类型10和对象比较, 将会使对象自动拆箱,做基本类型比较 Integer i6 = i4 + i5; // +操作使得i4,i5自动拆箱,得到10,因此i6 == i2. System.out.println(i1 == i6); //true System.out.println(i2 == i6); //true System.out.println(i3 == i6); //false } }
- 查看
StringNewTest.java
分析String
类1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
/** * * 常量赋值(堆内存)和 new 创建(栈内存)不是同一个对象 * 编译器只会优化确定的字符串,并缓存 * */ public class StringNewTest { public static void main(String[] args) { String s0 = "abcdef"; String s1 = "abc"; String s2 = "abc"; String s3 = new String("abc"); String s4 = new String("abc"); System.out.println(s1 == s2); //true 常量池 System.out.println(s1 == s3); //false 一个栈内存,一个堆内存 System.out.println(s3 == s4); //false 两个都是堆内存 System.out.println("========================="); String s5 = s1 + "def"; //涉及到变量,故编译器不优化 String s6 = "abc" + "def"; //都是常量 编译器会自动优化成abcdef String s7 = "abc" + new String ("def");//涉及到new对象,编译器不优化 System.out.println(s5 == s6); //false System.out.println(s5 == s7); //false System.out.println(s6 == s7); //false System.out.println(s0 == s6); //true System.out.println("========================="); String s8 = s3 + "def";//涉及到new对象,编译器不优化 String s9 = s4 + "def";//涉及到new对象,编译器不优化 String s10 = s3 + new String("def");//涉及到new对象,编译器不优化 System.out.println(s8 == s9); //false System.out.println(s8 == s10); //false System.out.println(s9 == s10); //false } }
- 常量式(字面量)赋值创建,放在栈内存(将被常量化)
5 不可变对象和字符串
5.1 不可变对象(Immutable Object)
- 一旦创建,这个对象(状态/值)不能被更改
- 其内在的成员变量的值就不能再次修改
- 八个基本类型的包装类
String
,BigInteger
和BigDecimal
等- 不可变对象也是传指针(引用)
- 由于不可变,临时变量指向新内存,外部实参指针不改动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
//示例一: String a = new String("abc"); // a -> abc String b = a; // b -> abc System.out.println(b); a = "def"; // a -> def b -> abc System.out.println(b); // abc //示例二: public static void change(String b) { b = "def"; } a = new String("abc"); // a -> abc change(a); // a -> abc,b -> abc 函数体内部 b -> def System.out.println(a); //abc
5.1.1 如何创建不可变对象
immutable
对象是不可改变的,有改变,需clone/new
一个对象进行修改- 所有的属性都是
final
和private
的 - 不提供
setter
方法 - 类是
final
的,或者所有的方法都是final
的 - 类中包含
mutable
对象,那么返回拷贝需要深度clone
5.1.2 优点
- 只读,线程安全
- 并发读,提高性能
- 可以重复使用
5.1.3 缺点
- 制造垃圾,浪费空间
- 对不可变对象进行修改时,会新开辟空间,旧对象则被搁置,知道垃圾回收
5.2 字符串
- 字符串是Java使用最多的类,是一种典型的不可变对象
- String定义
1 2
String a = "abd"; //常量赋值,栈内存分配 String b = new String("abc"); //new对象,堆内存分配
- 字符串内容比较:
equals
方法 - 是否指向同一个对象:指针比较
==
- Java 常量池
- 保存在编译期间就已经确定的内存
- 是一块特殊的内存
- 相同的常量字符串只存储一份,节省内存,共享访问
5.2.1 字符串加法
|
|
- 使用StringBuffer/StringBuilder类的append方法进行修改
- StringBuffer/StringBuilder的对象都是可变对象
- StringBuffer 同步,线程安全,修改快速
- StringBuilder 不同步,线程不安全,修改更快
|
|