6 static、final和常量设计
6.1static(静态)
6.1.1static变量
- static关键字可用在变量、方法、类和匿名方法块中,在内存中只有一种拷贝
- 静态变量是类的共有成员
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[] args)
{
System.out.println(Potato.price);//5
//System.out.println(Potato.content);// wrong
System.out.println("-------------");
Potato obj1 = new Potato(10,"青椒土豆丝");
System.out.println(Potato.price);//10
System.out.println(obj1.price);//10
}
}
6.1.2static方法
- static变量只依赖于类存在(通过类即可访问),不依赖对象实例存在。
- 在静态方法中,只能使用静态变量,不能使用非静态变量
- 静态方法中禁止引用非静态方法
public class StaticMethodTest
{
int a = 1;
static int b = 2;//静态变量
public static void hello()//静态方法
{
System.out.println("0");
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("3");
hello(); //ok,call static methods
System.out.println(a); //ok,call non-static methods
System.out.println(b); //ok,call static variables
}
public static void main(String[] args)
{
StaticMethodTest.hello();//02
//StaticMethodTest.hi();//error,不能使用类名来引用非静态方法
StaticMethodTest foo = new StaticMethodTest();
foo.hello();//warning,but it is ok,不建议使用对象调用静态方法,常用类调用//02
foo.hi();//right//30212
}
}
6.1.3static快
- 只在类第一次被加载时调用,在程序运行期间,这段代码只运行一次
- 执行顺序:static快>匿名快>构造函数
class StaticBlock
{
//static block > anonymous block > constructor function
static
{
System.out.println("1");
}//static block
{
System.out.println("2");
}//匿名代码块
public StaticBlock()//constructor function
{
System.out.println("3");
}
{
System.out.println("4");
}//匿名代码块
}
public class StaticBlockTest
{
public static void main(String[] args)
{
System.out.println("0");//0
StaticBlock obj1 = new StaticBlock();//1243
StaticBlock obj2 = new StaticBlock();//243
}
}
注不建议编写块代码,因为块代码会给程序带来混淆。建议将块代码封装成函数再调用。
6.2单例模式(单态模式)
- 限定某一个类在整个程序运行过程中,只能保留一个实例对象在内存空间(内存空间中,一个类只有一个对象存在)
- 设计模式:在软件开发过程中,经过验证的用于解决在特定环境下的、重复出现的、特定问题的解决方案。
- 单例模式:保证一个类有且只有一个对象
- 采用static来共享对象实例
- 采用private构造函数,防止外界new操作
public class Singleton
{
private static Singleton obj = new Singleton();//共享同一个对象
private String content;
private Singleton()//确保只能在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());//def
System.out.println(obj2.getContent());//def
System.out.println(obj1==obj2);//ture,obj1和obj2指向同一个对象
Singleton obj3 = new Singleton();
System.out.println(obj3.getContent());//abc
System.out.println(obj3==obj1);//false
}
}
6.3 final
-
可以用来修饰类,方法和字段
-
final修饰的类不能被继承
final public class FinalFather { } class Son1 extends FinalFather//继承报错 { }
-
父类如果有final的方法,子类不能改写此方法
class FinalMethodFather { public final void f1() { } } public class FinalMethodSon extends FinalMethodFather { public void f1()//不可改写父类final方法 { } }
-
final的变量,不能再次赋值
-
如果是基本型别的变量,则不能修改其值
public class FinalPrimitive { public static void main(String[] args) { final int a = 5; a = 10;//报错 } }
-
如果是对象实例,那么不能修改其指针(但是可以修改对象内部的值)
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();//报错,不能修改其指针 } }
-
6.4常量设计和常量池
6.4.1常量设计
-
常量:一种不会修改的变量
- Java没有constant关键字
- 不能修改final
- 不会修改/只读/只要一份,static
- 方便访问public
-
Java中的常量
- public static final
- 建议变量名字全大写以”__“相连。
public class Constants
{
public static final double PI_NUMBER = 3.14;
public static final String DEFAUT_COUNTRY = "China";
public static void main(String[] args)
{
System.out.println(Constants.PI_NUMBER);
System.out.println(Constants.DEFAUT_COUNTRY);
}
}
- 一种特殊的常量:接口内定义的变量默认是常量
interface SpecialAnimal
{
String color = "yellow";//default:public static final
public void move();//抽象类
}
public class Cat implements SpecialAnimal
{
public void move()//补全抽象类
{
System.out.println("I can move");
}
public static void main(String[] args)
{
Cat cat = new Cat();
// cat.color = "white";//error,the variables ininterface are constants
}
}
6.4.2常量池
- Java为很多基本类型的包装类/字符串都建立常量池
- 常量池:相同的值只存储一份,节省内存,共享访问
- 基本类型的包装类
- Boolean:true,false
- Byte:-128~127
- Character:0~127
- Short,Int.Long:-128~127
- Float,Double:没有缓存(常量池)
public class CacheTest
{
public static void main(String[] args)
{
Boolean b1 = true;//ture ,false
Boolean b2 = true;
//判断指针是否相同
System.out.println("Boolean Test: " + String.valueOf(b1==b2));//true
Byte b3 = 127;//-128~127
Byte b4 = 127;
System.out.println("Byte Test: " + String.valueOf(b3==b4));//true
Character c1 = 127; //\u0000-\u007f
Character c2 = 127;
System.out.println("Charater 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;//-128~127
System.out.println("Integer Test: " + String.valueOf(i1==i2));//true
Long l1 = -128L;//-128~127
Long l2 = -128L;//-128~127
System.out.println("Long Test: " + String.valueOf(l1==l2));//true
Float f1 = 0.5f;//没有常量池
Float f2 = 0.5f;
//在内存中会有两个0.5float类型
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为常量字符串都建立常量池缓存机制
public class StringConstantTest
{
public static void main(String[] args)
{
String s1 = "abc";
String s2 = "abc";
String s3 = "a" + "b" + "c";
String s4 = "ab";
String s5 = s4 + "c";
String s6 = "ab" + c;
System.out.println(s1==s2);//true
System.out.println(s1==s3);//true,java编译器会优化已经确定的变量
System.out.println(s1==s5);//false,有变量不优化
System.out.println(s1==s6);//true都是确定的变量会优化
}
}
-
基本类型的包装和字符串有两种创建方式
-
常量式赋值创建,放在栈内存(将被常量化)
Integer a = 10; String b = "abc";
-
new对象进行创建,放在堆内存(不会常量化)
Integer c = new Integer(10); String d = new String("abc");
-
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);
//i2是常量,放在栈内存常量池中,i3是new出对象,放在堆内存中
System.out.println(i2==i3);//false,两个对象比较,比较其地址
System.out.println(i1==i3);//true
Integer i4 = new Integer(5);
Integer i5 = new Integer(5);
//i4和i5操作将会使得i4和i5自动拆箱为基本类型并运算得到10,相当于i4+i5变成了int类型
System.out.println(i1==(i4+i5));//true
System.out.println(i2==(i4+i5));//true
System.out.println(i3==(i4+i5));//true
}
}
6.5 不可变对象和字符串
6.5.1不可变对象
- 不可变对象
- 一旦创建,这个对象(状态/值)就不能更改了
- 且内在的成员变量的值也不能修改
- 八个基本型别的包装类
- String,BigInteger和BigDecimal等
String a = new String("abc");//a-->"abc"
String b = a;//b-->"abc"
a = "def";//a-->"def"
System.out.println(b);//abc
public static void change(String b)
{
b = "def";
}
a = new String("abc");//a-->"abc"
change(a);//b-->"abc",b-->"def"
System.out.println(a);//abc
注:只要是对象,函数调用都是传指针
-
可变对象
- 普通对象
-
如何创建不可变对象
- immutable对象是不可改变的,有改变,请clone/new一个对象进行修改
- 所有的属性都是final和private的
- 不提供setter方法
- 类是final的,或者所有的方法都是final
- 类中包含mutable对象,那么返回拷贝需要深度clone
-
不可变对象优点
- 只读,线程安全
- 并发读,提高性能
- 可以重复使用
-
不可变对象缺点
- 制造垃圾,浪费空间
6.5.2字符串(一种典型的不可变对象)
-
String的两种定义
- String a = “abc”;//常量复制,栈分配内存
- String b = new String(“abc”);//new对象,堆分配内存
-
字符串内容比较:equals方法
-
是否指向同一个对象:指针比较==
-
字符串的加法
String a = "abc"; a = a + "def";//由于String不可修改,效率差 //使用StringBuffer/StringBuider类的append方法进行修改 //StringBuffer/StringBuilder的对象都是可变对象 //StringBuffer(同步,线程安全,修改快速),StringBuilder(不同步,线程不安全,修改更快)
-
比较三种字符串加法的效率
import java.util.Calendar; public class StringApeendTest { 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());//2895ms 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());//5ms Calendar t3 = Calendar.getInstance(); StringBuilder c = new StringBuilder(""); for(int i=0;i<n;i++) { c.append(i); c.append(","); } System.out.println(Calendar.getInstance().getTimeInMillis()-t3.getTimeInMillis());//2ms } }
-
不可变对象的传递
public class ArgumentPassing { public static void changeValue(int a) { a = 10; } public static void changeValue(String s1) { s1 = "def"; } public static void changeValue(StringBuffer s1) { s1.append("def");//可变对象,原地扩张 } public static void main(String[] args) { int a = 5; String b = "abc";//不可变对象 StringBuffer c = new StringBuffer("abc");//可变对象 changeValue(a);//传值,5 changeValue(b);//传指针,abc,b-->"abc",s1-->"abc",s1-->"def". changeValue(c);//abcdef System.out.println(a); System.out.println(b); System.out.println(c); } }