Java程序设计6——static、final、this、super关键字介绍
1.static关键字
static是一个特殊的关键字,可以用于修饰方法、属性等成员。 我们知道通常调用一个方法需要这样做:假设一个类A是主类(主方法在这个类中),A类要调用别的类的方法,通常是这样干的:用A类构造器创建一个对象,然后用这个对象调用它的方法,被调用的方法可以是静态的,也可以是非静态的。但static这个关键字有很"开挂"的地方是不用通过类创建实例的办法来调用,可以直接通过类来访问。
package chapter1; class HelloWorld { public static void main(String args[]) { //直接使用static方法 System.out.println("方式一:直接调用类方法: "); //HelloWorld.meth(42); //通常会省略掉类名称 meth(42); //通过创建方法调用的方式 System.out.println("方式二:通过创建方法调用的方式: "); HelloWorld hw = new HelloWorld(); hw.meth(32); } static int a = 3; static int b; static void meth(int x) { System.out.println("x = " + x); System.out.println("a = " + a); System.out.println("b = " + b); } static { System.out.println("Static block initialized."); b = a * 4; } } 输出结果: Static block initialized. 方式一:直接调用类方法: x = 42 a = 3 b = 12 方式二:通过创建方法调用的方式: x = 32 a = 3 b = 12
典型的用法是main这个方法,这个方法是由JVM直接调用的,不需要创建一个某个对象X,然后通过X.main()的方式来调用。static修饰的成员表明它是属于这个类共有的,而不是属于该类的单个实例,因此通常把static修饰的属性和方法也称为类属性、类方法。不使用static修饰的普通方法、属性则属于该类的单个实例,而不是属于该类。因此通常把不用static修饰的属性和方法也称为实例属性、实例方法。static直接翻译就是静态的意思。静态成员不能直接访问非静态成员,那怎样访问非静态成员呢?用创建对象的方式呗!
声明为static的变量实质上就是全局变量。当声明一个对象时,并不产生static变量的拷贝,而是该类所有的实例变量共用同一个static变量,例如:声明一个static的变量count作为new一个类实例的计数。
声明为static的方法有以下几条限制:
1、它们仅能调用其他的static 方法。
2、它们只能访问static数据。
3、static方法内不能有this,super关键字
在定义它们的类的外面,static 方法和变量能独立于任何对象而被使用。这样,你只要在类的名字后面加点号(.)运算符即可。例如,如果你希望从类外面调用一个static方法,你可以使用下面通用的格式:classname.method( )
这里,classname是类的名字,在该类中定义static方法。可以看到,这种格式与通过对象引用变量调用非static方法的格式类似。一个static变量可以以同样的格式来访问——类名加点号运算符。这就是Java 如何实现全局功能和全局变量的一个控制版本。
总结:
1.如果不加static修饰的成员是对象成员,也就是归每个对象所有的。
2.加static修饰的成员是类成员,就是可以由一个类直接调用(也可以由类创建的实例来调用),为所有对象共有的。
static:作为修饰符, 可以用来修饰变量、方法、代码块(但绝对不能修饰类)。
被static修饰的成员变量和成员方法独立于该类的任何对象。也就是说,它不依赖类特定的实例,被类的所有实例共享。只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内找到他们。因此,static对象可以在它的任何对象创建之前访问,无需引用任何对象。
1.static变量
按照是否静态的对类成员变量进行分类可分两种:一种是被static修饰的变量,叫静态变量或类变量;另一种是没有被static修饰的变量,叫实例变量。
两者的区别是:
对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。对于实例变量,没创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响(灵活)。
类的所有对象共同拥有的一个属性,也称为类变量。这类似于C语言中的全局变量。类变量在类加载的时候初始化,而且只被初始化一次。在程序中任何对象对静态变量做修改,其他对象看到的是修改后的值。因此类变量可以用作计数器。另外,Java Static变量可以用类名直接访问,而不必需要对象。
2.static方法
静态方法可以直接通过类名调用,任何的实例也都可以调用,因此静态方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员成员方法),只能访问所属类的静态成员变量和成员方法。因为static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract。类的所有对象共同拥有的一个功能,称为静态方法。静态方法也可以用类名直接访问,而不必需要对象。所以在静态方法里不能直接访问非静态变量和非静态方法,在static方法里不能出现this或者super等关键字。
3.static代码块
static代码块也叫静态代码块,是在类中独立于类成员的static语句块,可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。用static去修饰类里面的一个独立的代码块,称为静态代码块。静态代码块在类第一次被加载的时候执行,而且只执行一次。静态代码块没有名字,因此不能显式调用,而只有在类加载的时候由虚拟机来调用。它主要用来完成一些初始化操作。
4.和类加载关系
JVM在第一次使用一个类时,会到classpath所指定的路径里去找这个类所对应的字节码文件, 并读进JVM保存起来,这个过程称之为类加载。
可见,无论是变量,方法,还是代码块,只要用static修饰,就是在类被加载时就已经"准备好了",也就是可以被使用或者已经被执行。都可以脱离对象而执行。反之,如果没有static,则必须通过对象来访问。
5.static和final一块用表示什么
static final用来修饰成员变量和成员方法,可简单理解为“全局常量”!
对于变量,表示一旦给值就不可修改,并且通过类名可以访问。
对于方法,表示不可覆盖,并且可以通过类名直接访问。
2 final 关键字
final关键字用于修饰类、变量和方法。
2.1 final变量
当你在类中定义变量时,在其前面加上final关键字,那便是说,这个变量一旦被初始化便不可改变,这里不可改变的意思对基本类型来说是其值不可变,而对于对象变量来说其引用不可再变。其初始化可以在两个地方,一是其定义处,也就是说在final变量定义时直接给其赋值,二是在构造函数中。这两个地方只能选其一,要么在定义时给值,要么在构造函数中给值,不能同时既在定义时给了值,又在构造函数中给另外的值。
当函数参数为final类型时,你可以读取使用该参数,但是无法改变该参数的值。
另外方法中的内部类在用到方法中的参变量时,此参变也必须声明为final才可使用
final修饰变量时,表示该变量一旦获得了初始值之后就不可改变,final既可以修饰成员变量(类属性和实例属性),也可以修饰局部变量、形参。final修饰的变量不是不可以改变,而是一旦获得初始值后,final变量的值就不能重新赋值。
因为final变量获得初始值之后不能重新赋值,因此final修饰成员变量和修饰局部变量有一定不同。
2.1.1 final修饰成员变量
成员变量是随类初始化或对象初始化而初始化的。当类初始化时,系统会为该类的类属性分配内存,并分配默认值;当创建对象时,系统会为该对象的实例属性分配内存,并分配默认值。也就是说,当执行静态初始化块时,可以对类属性赋初始值,当执行普通初始化块、构造器时可对实例属性赋初始值。因此,成员变量的初始值可以在定义该变量时指定默认值,可以在初始化块、构造器中指定初始值,否则,成员变量的初始值将是由系统自动分配的初始值。
对于final修饰的成员变量而言,一旦有了初始值之后,就不能重新赋值。成员变量只能在定义该成员变量时指定值或在静态初始化块、对象初始化块和构造器为成员变量指定初始值。
因此当使用final修饰成员变量的时候,要么在定义成员变量的时候指定初始值,要么在初始化块、构造器中为成员变量赋初始值。如果已经初始化了值,那么就不能在别的情况下重新赋值。
归纳如下:
final初始化的类属性、实例属性能指定初始值的地方如下:
类属性:可在静态初始化块中、声明该属性时指定初始值。
实例属性:可在非静态初始化块、声明该属性、构造器中指定初始值。
上面两者不能交叉
注意:用final修饰属性时候,可以先定义,然后再初始化,不必一步完成。类属性就在有static关键字修饰的块或方法中初始化,实例属性就在不含有static的块或方法中初始化。final和static的搭配也就是四种情况,final本身没有多少要求,只要不再次修改就行了,而是否是static属性,则在对应的要求下初始化。当然不属于任何一个static块也是可以的。也就是当作成员变量进行初始化。
package chapter6; public class TestFinalDemo { //类成员变量的初始化,在初始化时候指定值,可以 private final static int i = 3; //实例成员变量的初始化 private final int b = 4; //下面的变量将在相应的方法或初始化块中进行初始化,如果不在这两种 //情况下初始化,就必须定义时候就初始化,否则编译不通过 private final static String str1; private final int c; //既没有定义时候初始化,也没有在相应的初始化块或方法中初始化,编译不通过 private final int d; //在static块中初始化static的final变量 static{ str1 = "Hello"; } { //非static初始化块不能初始化static变量 //str1 = "Hola"; //可以初始化非static 的final变量 d = 4; } //在static方法中初始化static的final变量 public TestFinalDemo(){ //由于构造器没有用static修饰,不能初始化static的final变量 //str2 = "World"; c = 3; } //普通的方法(包括用static修饰的,不包括构造器)不能初始化final变量 public void info(){ //str1 = "ni"; } public static void main(String[] args){ TestFinalDemo tfd = new TestFinalDemo(); System.out.println("i = " + i); System.out.println("str1 = " + tfd.str1); System.out.println("b = " + tfd.b); System.out.println("c = " + tfd.c); System.out.println("d = " + tfd.d); } }
与普通成员变量不同的是,final成员变量(包括实例属性和类属性)必须由程序设计者显式初始化,系统不会对final成员进行隐式初始化。所以,如果打算在构造器、初始化块中对final成员变量进行初始化,则不要在初始化前就访问成员变量的值。否则会引起编译错误。
2.1.2 final修饰局部变量
系统不会对局部变量进行初始化,局部变量必须由程序员显式初始化。因此使用final修饰局部变量时,既可以指定默认值,也可以不指定默认值,但最终必须要有一个初始化的值。同样final修饰的局部变量也不能再次赋值。
final修饰形参:final也可以修饰形参,调用该方法时候,系统根据传入的参数来完成形参的初始化赋值,因此在形参的方法中,不能再次对形参赋值。
2.1.3 final修饰基本类型和引用类型变量的区别
2.final方法
如果一个类不允许其子类覆盖某个方法,则可以把这个方法声明为final方法。
使用final方法的原因有二:
第一、把方法锁定,防止任何继承类修改它的意义和实现。
第二、高效。编译器在遇到调用final方法时候会转入内嵌机制,大大提高执行效率。
3.final类
final类不能被继承,因此final类的成员方法没有机会被覆盖,默认都是final的。在设计类时候,如果这个类不需要有子类,类的实现细节不允许改变,并且确信这个类不会载被扩展,那么就设计为final类。
注意:主方法是main()方法,这个方法是static的,由于程序从这里开始,也在这里结束,而static方法只能调用static的成员变量,main()方法里面有两种方式调用别的方法,如果这个方法是static的,那么可以通过类.方法方式直接调用,也可以通过创建对象方式调用。如果这个方法不是static的,那么只能先创建对象,然后通过对象来调用
final变量
3.this
this关键字在java中有2种用法:
1、调用本类的构造方法,即this() 2、代表构造器当前创建的对象
3.1 调用本类的构造方法
使用this(),调用构造方法
this 关键字调用构造方法只能放在this所在的构造方法的代码块首行。
例如
package Chapter5Excercise; class Person { private String name; private int age; public Person(){//无参构造 System.out.println("一个新的Person对象被实例化"); } public Person(String name,int age){ this(); this.name = name; this.age = age; } public String getInfo(){ return "姓名:" + name + " 年龄:" + age; } }; class ClassDemo09{ public static void main(String[] args){ Person per = new Person("任磊",25); System.out.println(per.getInfo()); } };
3.2 代表构造器创建的对象
this 关键字最大的作用就是让类中一个方法访问该类的另一个方法或属性。this 关键字的需要是源于解决对象方法之间的同一性问题(虽然不同一也是可以的,但会逻辑上比较混乱、复杂)。看下面的例子:
假设定义了一个Dog类,这个Dog对象的jump方法需要调用它的run方法。假如没有this关键字,我们只能这样做:
package chapter1; public class Dog { //定义一个run方法 public static void run(){ System.out.println("The dog is running!"); } //定义一个jump方法 public static void jump(){ Dog dog = new Dog(); dog.run(); } public static void main(String[] args){ Dog dog = new Dog(); dog.jump(); } }
在上面的程序中,一共产生了两个Dog对象,在Dog类的jump方法中,创建了一个Dog对象,并使用dog实例变量来指向该对象,在主方法main()中,程序再次创建了一个Dog对象,并使用dog的引用变量来指向该Dog对象。上面的程序可能有点混乱,特别是都用了同样的实例变量名称来引用对象。这是合法的(除了String采用共享池设计外,其他的类都会创建不同的对象)。为了让上面的程序更加思路更加清晰,我们修改下上面的程序。
package chapter1; public class Dog { //定义一个run方法 public static void run(){ System.out.println("The dog is running!"); } //定义一个jump方法 public static void jump(){ Dog dog1 = new Dog(); dog1.run(); } public static void main(String[] args){ Dog dog2 = new Dog(); dog2.jump(); } }
在上面的程序中,一共产生了两个Dog对象,在Dog类的jump方法中,创建了一个Dog对象,并使用dog1实例变量来指向该对象,在主方法main()中,程序再次创建了一个Dog对象,并使用dog2的引用变量来指向该Dog对象。这样思路就很清晰了,我们在主方法创建了一个Dog,也就是dog2,但它的跳跃动作jump方法却要借助另一个Dog 也就是dog1的跑动作run()方法,虽然在逻辑上是完全没有问题的,因为在计算机中模拟的现实系统,并不会完全对应于现实,一条狗的跳要借助于另一条狗的跑。为了解决这一的"不合理",我们引入了this关键字。this关键字可以和实例变量一样,代表类的任何对象。当this出现在某个方法体中时,它所代表的对象是不确定的,但它的类型是确定的,它所代表的对象只能是当前类;只有当这个方法被调用时,它所代表的对象才被确定下来
package chapter1; public class Dog { //定义一个run方法 public void run(){ System.out.println("The dog is running!"); } //定义一个jump方法 public void jump(){ this.run(); System.out.println("当前的this指向:" + this); } public static void main(String[] args){ Dog dog2 = new Dog(); System.out.println("dog2:" + dog2); dog2.jump(); Dog dog3 = new Dog(); System.out.println("dog3:" + dog3); dog3.jump(); } } 程序的执行结果是: dog2:chapter1.Dog@c17164 The dog is running! 当前的this指向:chapter1.Dog@c17164 dog3:chapter1.Dog@1fb8ee3 The dog is running! 当前的this指向:chapter1.Dog@1fb8ee3
在上面的程序中,如果略去主方法中dog2的创建及其jump方法调用,那么jump方法体内的this所代表的对象是不确定的,因此该方法也不能用static来修饰,因为static关键字修饰的方法特点是允许类直接调用或者对象直接调用,如果用了this关键字,而主方法中如果没有关于该类的实例化,就会造成this没有可以指向的对象,造成编译错误。
再来看这个程序,我们已经知道,this在没有对象调用它所在的方法体对应的方法之前,所指向的对象是不确定的。而在实例化Dog,并赋值给dog2后,这个this所指的对象就是确定的了,它就是dog2;同样在再次实例化Dog后,并赋值给dog3后,这个this所指的对象变成了dog3.通过程序执行结果打印的dog2和对应的this以及dog3和对应的this值很容易得到这样的结论。
在现实世界里,对象的一个方法依赖于另一个方法情形是很常见的:例如吃饭方法依赖于拿筷子方法,写程序方法依赖于敲键盘方法,这种以来都是同一个对象两个方法之间的依赖。因此,Java允许对象一个成员直接调用另一个成员,可以省略this前缀。也就是将run方法改成如下形式也是正确的。
public void jump(){ run(); System.out.println("当前的this指向:" + this); }
作为这种思想的应用,可以用this来调用构造方法创建对象时候的类的属性
使用方式:this.属性
解释:先看一段代码:
1 package Chapter5Excercise; 2 class Person01 { 3 public static void main(String[] args){ 4 private String name; 5 private int age; 6 public Person01 (String n,int a){ 7 name = n; 8 age = a; 9 } 10 public String getInfo(){ 11 return "姓名:" + name + " 年龄:" + age; 12 } 13 } 14 }
在这段代码中,我们看到构造方法Person01的形式参数n,a只是随意的字符,看不出代表什么意思,这样对代码理解是很麻烦的。可以修改成与实际参数一致,方便理解。
1 public static void main(String[] args){ 2 private String name; 3 private int age; 4 public Person01 (String name,int age){ 5 name = name; 6 age = age; 7 } 8 public String getInfo(){ 9 return "姓名:" + name + " 年龄:" + age; 10 } 11 } 12 }
这样修改后还是有问题的:
name = name;
age = age;
会导致系统无法分清参数,可以实例化后执行,发现最后取得的name为null,age为0
这时候再修改一下,使用this关键字:
this.name = name;
this.age = age;
用this关键字表示的含义是:将调入函数的实际参数值,赋给类的属性,这样就不会报错了。
总而言之:谁调用this所在的方法,this就代表谁
package Chapter5Excercise; class Person009{ //属性区 private String name; private int age; //提供实例化的构造方法区 public Person009(String name,int age){ this.setName(name); this.setAge(age); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } //提供比较的功能 public boolean compare(Person009 per22){ Person009 per1 = this;//表示当前调用this所在方法的对象,也就是per1,记住原则:谁调用this所在的方法,this就代表谁 Person009 per2 = per22;//传递到方法中的对象 //判断地址是否相等 if(per1 == per2){ return true; } if((per1.name.equals(per2.name))&&(per1.age==(per2.age))){//equals用于字符串比较,不能用于整型值 return true; }else { return false; } } }; public class ThisDemo07 { public static void main(String[] args){ //实例化两个对象 Person009 per1 = new Person009("任磊",25); Person009 per2 = new Person009("任磊",25); //传递参数用于比较 if(per1.compare(per2)){ System.out.println("两个对象相等"); }else{ System.out.println("两个对象不相等"); } } };
当this作为对象的默认引用时,程序可以像访问普通引用变量一样来访问这个this引用,甚至可以把this当成普通方法的返回值。
public class ReturnThis{ public int age; public ReturnThis grow(){ age++; //return this,返回调用该方法的对象 return this; } public static void main(String[] args){ ReturnThis rt = new ReturnThis(); //可以连续调用同一个方法 rt.grow() .grow() .grow(); System.out.println("rt的age属性值是:" + rt.age); } }
4.super
super关键字使用和this比较类似。也有两种使用方式。不能放在static方法中使用。
4.1.放在子类的构造器首行,用于显式调用父类的构造器
使用语法:super(参数列表)
class Base{ public double size; public String name; public Base(double size , String name){ this.size = size; this.name = name; } } public class Sub extends Base{ public String color; public Sub(double size , String name , String color){ //通过super调用来调用父类构造器的初始化过程 super(size , name); this.color = color; } public static void main(String[] args){ Sub s = new Sub(5.6 , "测试对象" , "红色"); //输出Sub对象的三个属性 System.out.println(s.size + "--" + s.name + "--" + s.color); } }
4.2.调用被重载的父类方法,代表父类对象
使用方法:super.方法()
public void fly(String name,double speed){ super.fly("大鸵鸟",4); }
5.this 和super的对比
区别点 this super
属性访问 访问本类中的属性,如果本类没有,访问父类 访问父类中的属性
方法 访问本类中的方法,如果本类没有,访问父类 访问父类的方法
调用构造 调用本类构造,必须放在构造方法首行 调用父类构造,必须放在构造方法首行
特殊 谁调用this所在的方法,谁就是this当前所指的对象 谁调用super所在的方法,谁就是当前调用方法对象的父对象