目录

[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即可访问
  • 所有的对象实例,如上例中的obj1obj2关于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

  • Javafinal关键字可以用来修饰类、方法、字段

  • 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
    • finalstatic可以颠倒顺序
  • 常量的定义
    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, BigIntegerBigDecimal
  • 不可变对象也是传指针(引用)
  • 由于不可变,临时变量指向新内存,外部实参指针不改动
     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一个对象进行修改
  • 所有的属性都是finalprivate
  • 不提供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 字符串加法

1
2
String a = "abc";
a = a + "def"; //由于String不可修改,效率差
  • 使用StringBuffer/StringBuilder类的append方法进行修改
  • StringBuffer/StringBuilder的对象都是可变对象
  • StringBuffer 同步,线程安全,修改快速
  • StringBuilder 不同步,线程不安全,修改更快
 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
import java.util.Calendar;

public class StringAppendTest {
  public static void main(String[] args) {
    int n = 50000;
    Calendar t1 = Calendar.getInstance();
    String a = new String();
    for(int i=0;i<n;i++)
    {
      a = a + i + ",";
    }
    System.out.println(Calendar.getInstance().getTimeInMillis() - t1.getTimeInMillis());
    
    Calendar t2 = Calendar.getInstance();
    StringBuffer b = new StringBuffer("");
    for(int i=0;i<n;i++)
    {
      b.append(i);
      b.append(",");
    }
    System.out.println(Calendar.getInstance().getTimeInMillis() - t2.getTimeInMillis());
    
    Calendar t3 = Calendar.getInstance();
    StringBuilder c = new StringBuilder("");
    for(int i=0;i<n;i++)
    {
      b.append(i);
      b.append(",");
    }
    System.out.println(Calendar.getInstance().getTimeInMillis() - t3.getTimeInMillis());    
  }
}

/**
 * 
 * 输出结果:
 * 8829
 * 19
 * 5
 * 
 */