Java高级类特性(二)
一、static关键字
static关键字用来声明成员属于类,而不是属于类的对象。
1). static (类)变量
类变量可以被类的所有对象共享,以便与不共享的成员变量区分开来。
static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
注意:static成员变量的初始化顺序按照定义的顺序进行初始化。
2). static (类)方法
静态方法可以通过类名直接调用该方法,而不用通过对象调用。
static方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用。
注意:虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法/变量的。
例如:
1 class PersonCount { 2 private int personID; 3 4 private static int num = 0; 5 6 public PersonCount() { 7 num++; 8 personID = num; 9 } 10 11 public static String getPersonDes() { 12 return "this is a policeman"; 13 } 14 } 15 16 class TestPersonCount { 17 public static void main(String[] args) { 18 // 直接用类名来访问该静态方法,而不需要该类的对象 19 String s = PersonCount.getPersonDes(); 20 System.out.println(s); 21 } 22 }
3)static代码块
static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。
为什么说static块可以用来优化程序性能,是因为它的特性:只会在类加载的时候执行一次。下面看个例子:
class Person{
private Date birthDate;
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
Date startDate = Date.valueOf("1946");
Date endDate = Date.valueOf("1964");
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}
isBornBoomer是用来这个人是否是1946-1964年出生的,而每次isBornBoomer被调用的时候,都会生成startDate和birthDate两个对象,造成了空间浪费,如果改成这样效率会更好:
class Person{
private Date birthDate;
private static Date startDate,endDate;
static{
startDate = Date.valueOf("1946");
endDate = Date.valueOf("1964");
}
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}
因此,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。
main()是静态的,因为它必须在任何实例化发生前被顺序地访问,以便应用程序的运行。静态方法不能被覆盖成非静态。同样,非静态方法也不能被覆盖成静态方法。
"独身"设计模式
独身设计模式,也就是说一个类只产生一个对象。那么怎么才能做到这一点呢??我们知道构造器是用来构造对象的。首先要对构造器入手。既然只产生一个对象,那么我们就干脆先一刀砍断,把构造器的访问权限定义成私有,不能在类的外面再构造该类的对象。也就是说只能在类的里面调用该类的构造器来产生对象。那么在该类里应该定义一个静态的属性,并初始化该类的一个对象。(原因是静态的东西只执行一次,也就是说该属性只初始化一次。那么每次得到的应该是同一个实例)
class TestSC {
public static void main(String[] args) {
SingleClass sc1 = SingleClass.sc;
SingleClass sc2 = SingleClass.sc;
sc1.test();
sc2.test();
}
}
class SingleClass {
int i = 0;
static SingleClass sc = new SingleClass();
private SingleClass() {
}
public void test() {
System.out.println("hello " + (++i));
}
}
运行的结果为:
hello 1
hello 2
说明是同一个实例。
在类的设计的时候,我们也应该遵守封装的要求。把属性定义成私有的。再定义一个共有的方法来去到该属性的值。
改后的代码:
class TestSC {
public static void main(String[] args) {
SingleClass sc1 = SingleClass.getSingleClass();
SingleClass sc2 = SingleClass.getSingleClass();
sc1.test();
sc2.test();
}
}
class SingleClass {
int i = 0;
private static SingleClass sc = new SingleClass();
private SingleClass() {
}
/*
* 因为在类的外面不能来构造给类的实例了, 所有该方法定义成静态的,通过类名直接可以调用。
*/
public static SingleClass getSingleClass() {
return sc;
}
public void test() {
System.out.println("hello " + (++i));
}
}
二、final关键字
1 、final类
Java编程语言允许关键字final修饰类。如果这样做了,类便不能被继承。比如,类Java.lang.String就是一个final类。这样做是出于安全原因,因为它保证,如果方法有字符串的引用,它肯定就是类String的字符串,而不是某个其它类的字符串。
2 、final方法
方法也可以被标记为final。被标记为final的方法不能被覆盖。这是由于安全原因。如果方法具有不能被改变的实现,而且对于对象的一致状态是关键的,那么就要使方法成为final。
被声明为final的方法有时被用于优化。编译器能产生直接对方法调用的代码,而不是通常的涉及运行时查找的虚拟方法调用。
被标记为static或private的方法被自动地final。
3 、final变量
如果变量被标记为final,其结果是使它成为常数。想改变final变量的值会导致一个编译错误。下面是一个正确定义final变量的例子:
public final int PI = 3.14;
三、内部类
内部类,有时叫做嵌套类。内部类允许一个类定义被放到另一个类定义里。内部类是一个有用的特征,因为它们允许将逻辑上同属性的类组合到一起,并在另一个类中控制一个类的可视性。内部类可以访问外部类的属性和方法。你可以把内部类看作"方法"一样,在使用的时候调用执行。你也可以把内部类看作"属性"一样,在构造内部类对象的时候,也会在堆里为内部类的属性分配存储空间。所以内部类也有类似像修饰属性,方法那样的修饰符,比如:public,private,static 等等。当一个类没有用static 关键字修饰的时候,这个内部类就叫做成员类,类似属性,方法,作为类的成员。
内部类有如下属性:
1 内部类只在定义他们的代码段内可见。
2 内部类可以被定义在方法中。如果方法中的变量被标记为final,那么,就可以被内部类中的方法访问。
3 内部类可以使用所嵌套类的类和实例变量以及所嵌套的块中的本地变量。
4 内部类可以被定义为abstract.
5 只有内部类可以被声明为private或protected,以便防护它们不受来自外部类的访问。
6 一个内部类可以作为一个接口,由另一个内部类实现。
7 被声明为static的内部类自动地成为顶层类。这些内部类失去了在本地范围和其它内部类中使用数据或变量的能力。
8 内部类不能声明任何static成员;只有顶层类可以声明static成员。因此,一个需求static成员的内部类必须使用来自顶层类的成员。
四、成员内部类
例1:
public class OC1 {
private int size;
public class IC {
public void addSize() {
size++;
}
}
public void testTheIC() {
IC i = OC1.new IC();
i.addSize();
}
}
内部对象拥有一个外部对象的引用:
例2:
这个例子阐述了如何在其它类(外部类的外部)实例化内部类:
class OC2 {
private int size;
public class IC {
public void addSize() {
size++;
}
}
}
public class TestIC // Test Inner Class
{
public static void main(String[] args) {
OC2 outer = new OC2();
// 因为是成员内部类,所以必须用外部类的对象来构造内部类的对象,类似调用方法一样。
OC2.IC inner = outer.new IC();
inner.addSize();
}
}
内部类要在外部类实例的上下文中实例化:
例3:
this的一个作用是调用本类的其它构造器,另外一个作用就是做隐含参数的调用,代表当前的实例。完整的写法应该是 该类的类名.this 如下例:
本例阐述如何区分同名变量:
public class OC3 {
private int size;
public class IC // Inner Class
{
private int size;
public void addSize(int size) {
// 方法里的临时变量,当方法执行完自动消失
size++;
this.size++;
// 代表本类的当前对象,全称是IC.this.size++;
OC3.this.size++;
}
}
}
四、静态内部类(也叫顶层类)
1) 内部类的最简单形式;
2) 不能和外部类同名;
3) 被编译成一个独立的类文件;
4) 只能访问外部类的静态方法、静态实例变量(包括私有类型);
class OC4 {
private static int size;
// 声明一个内部类 叫 "IC"
public static class SIC // Static Inner Class
{
public void addSize() {
// 访问外部类的属性
size++;
}
}
}
public class TestSIC // Test Static Inner Class
{
public static void main(String[] args) {
// 因为内部类是静态内部类,所以直接可以构造内部类的一个对象,与调用静态方法类似
OC4.SIC inner = new OC4.SIC();
inner.addSize();
}
}
五、方法内部定义内部类(又称局部内部类)
1) 定义在方法里;
2) 最少的一种内部类;
3) 和局部变量类似, 不能被定义为public,protected,private和static;
4) 只能访问final型局部变量。
class OC5 {
// 内部类访问,应该定义成final
public Object makeObject(final int i) {
class MIC // Methord Inner Class
{
int k = i;
public String toString() {
return ("属性k :" + k);
}
}
return new MIC();
}
public static void main(String[] args) {
OC5 oc = new OC5();
Object o = oc.makeObject(5);
System.out.println(o);
}
}
注意:在方法中定义的内部类的方法,不能访问外方法的运行时变量空间(line 10),可以访问外方法的非运行时变量空间(line 11)。
六、匿名内部类
1) 没有类名;
2) 没有class关键字;
3) 没有继承和声明;
4) 没有构造函数;
有时候定义一个类,并不需要提供名字。所以叫匿名类。
class OC6 {
// 多态,传递的参数应该是实现该接口的任何类产生的对象
public void testFly(Fly f) {
f.fly();
}
public static void main(String[] args) {
OC6 oc = new OC6();
// 画下划线的代码就是构造了实现Fly接口的某个类的对象,类名并不需要知道,只
// 知道该对象具有接口的功能就行。
oc.testFly(new Fly() {
public void fly() {
System.out.println("fly higher and higher");
}
});
}
}
interface Fly {
public void fly();
}
匿名类具有广泛性。不只是对接口才有,对抽象类或者具体的类都适用。例如:
class OC7 {
public static void main(String[] args) {
System.out.println(new Person() {
public String toString() {
return "this is a person";
}
});
}
}
class Person {
}
注意:如果是接口或者抽象类的话,在匿名类里面必须实现接口或者抽象类里所有的抽象方法。如果是具体的类的话就没必要了,需要的话可以覆盖方法,不需要时可以不写任何代码。看下例:
class OC7 // Outer Class 7
{
public static void main(String[] args) {
// 这里其实是Person类里的一个子类,只不过该子类并没有扩展功能
System.out.println(new Person() {
});
}
}
class Person {
public String toString() {
return "this is a person";
}
}