最近分布式课程上有个课设需要用到java RMI,并且新的项目有一个模块师兄以前也一直希望我帮他用java实现,因此决定这次扎扎实实的将java“入个门”。
Programming Paradigm还有程序的结构根本没有概念,所以根本没有特别的在意一些语法以及程序运行的细节,只是知道“哦!这样能跑出正确结果!”。直到现在回头看才发现这种工作和学习的方式真的是浪费时间,虽然从开始真正了解java不到一周,但是决定从这次开始好好的努力记录学习的过程,因为经历了这么多才发现,记忆是靠不住的,大脑的容量是有限的,每天接受的信息太多,因此自然会自动删除很多信息。
好了,说了这么多废话,开始第一次笔记,想整理的内容是关于Java中的static的用法,同时,也和C++的static做一个对比记录,方便自己以后翻看,也希望能帮助到对这些概念比较模糊的朋友。
static是静态的意思,在C++中,如果申明一个static类型的成员变量,该变量实际上存在于进程的Data Segment,也就是数据段,因此在C++中,static定义的变量,无论出现在源文件的哪个角落(函数内,函数外,类内,类外),其实都有全局变量的意思,该变量在整个进程运行生命期内存在,且只会维护一份实体。然而,Java是没有全局变量的概念的,Java的可执行程序.class文件,并不是像C/C++的可执行程序一样直接由内核装入开始运行并执行CPU的命令集的,而是运行在Java虚拟机也就是JVM中,JVM其实完全可以看作一个中间件,屏蔽了底层系统的不同,.class文件中其实是JVM的指令集,因此Java才有了它经典的slogan:complie once, run anywhere...
那么,就从对比Java和C++中的static的用法对比开始:
1.static修饰成员变量
例如,对于Java来说,在一个类中 ,修饰一个成员变量:这里可以直接为number指定一个初值:
private static int number = 0;
并且,在Java中非静态方法内不能声明static变量
public void foo() {
static int number = 0; /*错误,成员方法内不能定义static变量*/
}
对于static成员变量来说,由于其是一个全局变量,因此,要保证该变量只能被定义一次,如果在类内进行定义,或者在头文件中进行定义那么在类被多次实例化和头文件被多次包含时,static变量就会被重复定义了,这是不允许的,因此,static成员变量的定义需要放在类外。
class Test
{
public:
Test(){}
~Test(){}
static int number; /*非const static变量不能在类内初始化,只能声明*/
const static int const_number = 0; /*const static变量可以直接在类内初始化*/
};
int Test::number = 10;/*非const的static变量的定义必须在源文件(不能是头文件)的类外定义*/
并且,在C++的成员函数中,static变量也是可以声明和定义的,这时static变量依然保持它的性质:唯一一份实例
class Test
{
public:
/*class members or functions*/
int foo()
{
static int number = 0; /*正确*/
return number++;
}
};
2.static修饰成员方法/函数
在Java和C++中,static修饰成员方法/函数的方式倒是一致,被static修饰的方法/函数可以直接用类名进行调用。
对于Java是直接用类名.方法的形式调用静态成员方法
Class Test{
/*other members*/
public static void foo(){
System.out.print("foo\n");
}
}
Test.foo(); /*Java调用静态成员方法*/
而对于C++来说,用类名::成员函数的方式来调用静态成员函数
class Test
{
public:
/*other members*/
static void foo()
{
cout << "foo" << endl;
}
};
Test::foo(); /*C++调用静态成员函数*/
3.static和非static初始化块
这个特性是Java独有的,感觉是个很有趣的特性,也是和C++一个比较大的区别,对于C++的类来说,所有的初始化都是从构造函数开始的,包括vtbl。然而对于Java来说,构造函数恐怕是要靠边站的一个角色了,因为在它的前面,还有static初始化块和非static初始化块。
static初始化块和非static初始化块的示例代码如下:
class Test{
public Test() {
System.out.println("construct Test:tt");
}
/*staic 初始化块*/
static
{
System.out.println("static initial block");
}
/*非 static 初始化块*/
{
System.out.println("non-static initial block");
}
}
如果在main()方法中使用 Test tt = new Test();来创建一个对象,那么将会有一下输出:
static initial block
non-static initial block
construct Test:tt
可以看到,三个初始化过程的顺序为:
- static初始化块
- 非static初始化块
- 构造函数
其实到这里,还不能很好的说明一些问题,那么如果一个继承Test的子类也有上述结构,那么在实例化时会怎么样呢?
class Test{
public Test() {
System.out.println("parent construct Test:tt");
}
/*staic 初始化块*/
static
{
System.out.println("parent static initial block");
}
/*非 static 初始化块*/
{
System.out.println("parent non-static initial block");
}
}
class NewTest extends Test{
public NewTest() {
System.out.println("child construct Test:tt");
}
/*staic 初始化块*/
static
{
System.out.println("child static initial block");
}
/*非 static 初始化块*/
{
System.out.println("child non-static initial block");
}
}
在main()方法中使用 Test tt = new NewTest();来创建一个对象,那么将会有一下输出:
parent static initial block
child static initial block
parent non-static initial block
parent construct Test:tt
child non-static initial block
child construct Test:tt
可以看到,static初始化是最先执行的
其次是父类的non-staic块和构造函数
最后才是子类的non-static快和构造函数。
这个其实很好理解,在执行new NewTest()时,JVM首先要用classLoader将NewTest以及它的父类加载到JVM中,这时默认就会按照父类->子类的顺序调用static静态初始化块,static初始化块仅仅在该类第一次加载到JVM时运行一次,因为它是属于整个类而不是某一个对象的,而non-static初始化块和构造函数是在类被实例化为一个对象时才会运行的,因此,如果一个类被实例化多次,static块也只会执行一次,non-static块将在每一次实例化时执行。
关于C++和Java类中static的区别,暂时只理解了这么多,另外,还发现另外一个区别:
- Java中,如果你没有给你的类定义一个默认构造函数,那么Java会给你创建一个无参数的构造函数,然而,如果你的类定义了一个带参数的构造函数(却没有定义一个无参数的默认构造函数)那么将不能再用classname obj = new classname();的方式实例化一个对象了,必须传入你所定义的参数,也就是说,Java不会再为你提供一个默认构造函数了。
- C++中,无论如何,只要你没有定义默认的构造函数,C++都会为你创建一个。