2、static类成员
2、1 简述
一般而言,类对象变量在生成的时候,会开辟相当大的内存空间来存储该对象拥有的属性和方法,由于属性值会因为每个对象不同而不同,所以每个对象会拥有各自的属性,此时会因为对象数增多导致空间消耗增大。而方法在不同的对象中代码却是一样的,所以,对象的方法并不是由不同的对象各自拥有重复的代码体,而是在内存中只有一份代码体,但同时给不同的对象共享使用。
具体的一个对象在调用方法的时候,会将当前对象的引用传给方法中的this引用,所以方法会在运行期间对该对象的属性直接进行操作,而不会对共享方法代码的其他对象的属性进行操作。
this引用是默认表达的,写不写效果一样,如
public void setHour(int h)
{
hour=h;
}
相当于
public void setHour(int h)
{
this.hour=h;
}
对于类中定义的static类型成员,效果和上述的一般类成员不一样。不论具有static类型的类成员是属性还是方法,该成员在内存中只有一个,不同的对象会共享此成员。此时和一般的对象成员不同的地方在于:
1)对象属性会因为每个对象不同而不同,所以每个对象会拥有各自的属性,而static类型的对象属性只有一个,不同的对象会共享此属性。
2)对象方法和static类型的方法都是只有一份拷贝,但是在使用上,static类型的方法不能访问一般的非static类型的对象属性和方法,只能访问static类型的属性。
如:
public class cls
{
public static int counter=0;
public int a=0;
public static void f()
{
a++;
}
}
编译报错为:non-static variable a cannot be referenced from a static context
原因很简单,static类型的成员即使在类对象不存在的情况下也会存在,而此时显然并不存在访问任何对象属性和对象方法成员的可能性。
如:
具有static类型成员的类:
public class cls
{
public static int sa=0;
public int val=0;
}
使用该类的程序:
public class exec
{
public static void main(String args[])
{
cls c1=new cls();
c1.sa=1;
c1.val=1;
cls c2=new cls();
c2.sa=2;
c2.val=2;
System.out.println("c1.sa="+c1.sa);
System.out.println("c1.val="+c1.val);
System.out.println("c2.sa="+c2.sa);
System.out.println("c2.val="+c2.val);
}
}
输出为:
c1.sa=2
c1.val=1
c2.sa=2
c2.val=2
注意:将c2的sa设置为2会导致c1的sa也变为2,因为它们共享此属性成员。
所以,类中的static类型成员从本质上更应该属于类的成员,而不是对象的成员,因此,static类型成员可以称为类成员,访问时可以直接通过类名来访问,如:
public class exec
{
public static void main(String args[])
{
cls c1=new cls();
cls.sa=1;
System.out.println("sa="+cls.sa);
}
}
输出为:
sa=1
类中的static类型成员甚至在对象不存在的情况下也会存在,如:
public class exec
{
public static void main(String args[])
{
System.out.println("sa="+cls.sa);
cls.sa=1;
System.out.println("sa="+cls.sa);
}
}
输出为:
sa=0
sa=1
2、2 static类型的main主函数
所以,main主函数是static类型的,因为main主函数是程序的入口,在它运行之前,任何程序代码都还没有运行,所以在Java运行环境执行main主函数的时候,并没有也不可能去创建主函数所在类的对象。因此,如果它不是static类型的,将会因为对象的不存在而不存在,但是由于是static类型,所以即使在类对象不存在的情况下,依然存在并能够被Java运行环境执行。
正是因为同样的道理,main主函数能够直接调用的函数一定都是static类型的成员。如:
public class exec
{
public static void main(String args[])
{
func();
}
public static void func()
{
System.out.println("Hello!");
}
}
如果将public static void func()改为public void func(),编译报错为:
non-static method func() cannot be referenced from a static context
当然,此时也可以写为:
public class exec
{
public static void main(String args[])
{
exec c=new exec();
c.func();
}
public void func()
{
System.out.println("Hello!");
}
}
2、3 例子
示例程序之一:统计内存中不断增加的对象个数。
具有static类型成员的类:
public class cls
{
public static int counter=0;
public cls()
{
counter++;
}
}
使用该类的程序:
public class exec
{
public static void main(String args[])
{
System.out.println(cls.counter);
cls c1=new cls();
System.out.println(cls.counter);
cls c2=new cls();
System.out.println(cls.counter);
}
}
输出为:
0
1
2
示例程序之二:统计内存中不断增加或者减少的对象个数。
具有static类型成员的类:
public class cls
{
public static int counter=0;
public cls()
{
counter++;
}
public void finalize()
{
counter--;
}
}
使用该类的程序:
public class exec
{
public static void main(String args[])
{
System.out.println(cls.counter);
cls c1=new cls();
System.out.println(cls.counter);
cls c2=new cls();
System.out.println(cls.counter);
c1=null;
System.gc();
System.out.println(cls.counter);
c2=null;
System.gc();
System.out.println(cls.counter);
}
}
说明:
将某个对象的引用指向null,说明此时的对象已经没有任何引用指向它了,所以自动被列入Java垃圾回收的队列中,等待Java虚拟机将其回收,System.gc()语句可以显式调用Java虚拟机执行回收任务。
示例程序之三:自动递增分配号码给学生对象的学号属性。
学生类:
public class stu
{
public static int counter=0;
private int number=0;
public stu()
{
counter++;
number=counter;
}
public int getNumber()
{
return number;
}
}
使用该类的程序:
public class exec
{
public static void main(String args[])
{
stu s1=new stu();
System.out.println(s1.getNumber());
stu s2=new stu();
System.out.println(s2.getNumber());
}
}
说明:不可以直接使用static类型属性作为学生的学号,否则一个已有学生的学号会随着以后学生的增加而不断递增,所以应该将学号值保存至对象属性中。
3、关于引用和对象
3、1 简述
引用和对象并不是一回事,如:
cls c1=new cls();
其实上述语句创建了两个变量,一个是引用变量,名称为c1,另外一个是对象变量:new cls(),拥有真正的全部对象成员,但是它没有名称,所以无法在程序直接被访问,必须通过引用变量来间接访问,访问方法就是:引用变量名称.对象成员。所以,任何对象变量一般都是通过引用变量来被访问的,引用变量的作用也是提供了一种访问对象的途径。
这两个变量一般都是不能省略的,如:
一个相当简单的类:
public class cls
{
public int val=0;
}
使用该类的程序为:
public class exec
{
public static void main(String args[])
{
cls c1;
System.out.println(c1.val);
}
}
编译报错:variable c1 might not have been initialized
说明:c1本身并不是对象,只是一个空的引用,由于没有真实指向一个对象,所以无法访问对象的成员。
再如:
public class exec
{
public static void main(String args[])
{
System.out.println((new cls()).val);
}
}
说明:程序正常,直接通过创建对象来访问其内部成员。只是意义不大,因为下面的语句再也没有可能访问到这个无名对象了。
3、2 引用变量的复制
考虑下述程序的输出:
public class exec
{
public static void main(String args[])
{
cls c1=new cls();
c1.val=1;
cls c2=c1;
c2.val=2;
System.out.println(c1.val);
}
}
输出为:
2
说明:语句cls c2=c1并没有创建一个新的对象变量,只是创建了一个新的引用变量,并把c1引用变量的值复制到c2引用变量中,导致c1和c2都指向同一个对象,所以,改变c2引用的对象内部属性值会影响c1引用的对象属性值。
3、3 方法参数中的引用变量
考虑下述程序的输出:
public class exec
{
public static void main(String args[])
{
cls c1=new cls();
c1.val=0;
func(c1);
System.out.println(c1.val);
}
public static void func(cls c)
{
c.val=100;
}
}
输出为:
100
说明:和上述道理是一样的,实参和形参都是引用变量,此时函数调用中并没有发生对象变量的复制,而只是引用变量的复制。
这也是为什么采用这种引用变量和对象变量分离的方式来表达类变量的原因,节省程序运行中变量赋值产生的内存消耗。当然,真要实现形参的改变不影响实参,可以参考下面的做法:
public class exec
{
public static void main(String args[])
{
cls c1=new cls();
c1.val=0;
func(c1);
System.out.println(c1.val);
}
public static void func(cls c)
{
c=new cls();
c.val=100;
}
}
输出为:
0
说明:此时形参指向新的对象,从而与实参引用指向的对象不再是同一个对象了。
3、4 String类的引用和对象
考虑下述程序的输出:
public class exec
{
public static void main(String args[])
{
String s1="Hello!";
String s2=s1;
s2="Bye!";
System.out.println(s1);
}
}
输出为:
Hello!
按照习惯,上述输出本无问题,但是按照刚才的引用论述,为什么此时改变s2引用指向的对象却不导致s1引用对象的改变呢?
这里的主要原因在于写法:语句String s1="Hello!"的真实写法应该为String s1=new String("Hello!"),上述程序可以写为:
public class exec
{
public static void main(String args[])
{
String s1=new String("Hello!");
String s2=s1;
s2=new String("Bye!");
System.out.println(s1);
}
}
而此时,s2的真实行为可以看的很清楚,它并没有修改对象的值,而是重新指向一个新的String类对象。
3、5 引用变量的比较
考虑下述程序的输出:
public class exec
{
public static void main(String args[])
{
String s1=new String("Hello!");
String s2=s1;
if(s1==s2)
System.out.println("OK!");
}
}
输出为:
OK!
说明:由于s1和s2都指向同一个String类对象,所以值相同。
再次考虑下述程序的输出:
public class exec
{
public static void main(String args[])
{
String s1=new String("Hello!");
String s2=new String("Hello!");
if(s1==s2)
System.out.println("OK!");
}
}
输出为空
说明:虽然两个引用变量指向的对象内部的属性值是相同的,但是程序比较的引用变量的值,由于s1和s2不指向同一个String类对象,所以值不相同。这说明,要想比较两个对象,不能通过引用变量来进行(引用变量只能比较是否指向同一个对象变量),而只能通过对象的成员方法来进行。
如:
public class exec
{
public static void main(String args[])
{
String s1=new String("Hello!");
String s2=new String("Hello!");
if(s1.equals(s2))
System.out.println("OK!");
}
}
输出为:
OK!
按照这个思路,可以给自己的类添加比较类对象是否一样的能力,如:
可以比较对象是否一样的类:
public class cls
{
public int val=0;
public boolean equals(cls c)
{
if(c.val==val)
return true;
else
return false;
}
}
使用该类的程序为:
public class exec
{
public static void main(String args[])
{
cls c1=new cls();
c1.val=0;
cls c2=new cls();
c2.val=0;
if(c1.equals(c2))
System.out.println("OK!");
}
}
说明:看起来,这种方式显得非常麻烦,其实它更加灵活,也就是说,类对象的一样是可以自己来控制的,不一定非要是类对象中的属性成员值一样才能算对象一样。如下面这个类,它只有在引用参数指向的对象属性值和本对象属性值相加为0时,才认为对象是一样的:
public class cls
{
public int val=0;
public boolean equals(cls c)
{
if(val + c.val==0)
return true;
else
return false;
}
}
使用该类的程序为:
public class exec
{
public static void main(String args[])
{
cls c1=new cls();
c1.val=1;
cls c2=new cls();
c2.val=-1;
if(c1.equals(c2))
System.out.println("OK!");
}
}
输出为:
OK!
[此贴子已经被作者于2010-12-12 07:41:29编辑过]