Static关键字用法

认识static,首先要了解final

  1. 带有final修饰符的变量被用作全局常量,例如java.lang.Math中的变量PI:
1
public static final double PI = 3.14159265358979323846 
  1. 同时可以将final修饰符用于函数声明,final修饰的方法无法被重写(override),如果父类中final修饰的方法同时访问控制权限为private,将会导致子类中不能直接继承到此方法

  2. 可以将final修饰符应用于类声明。 标有final的类不能再被继承-这是其最终实现,final类的示例之一是java.lang.String类 。它是final类,因此没有人可以继承它,故可以访问其成员变量

对于static:在《Java编程思想》P86页有这样一段话:

  “static方法就是没有this的方法。在static方法内部不能调用非静态方法,反过来是可以的。而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。这实际上正是static方法的主要用途。”

  这段话虽然只是说明了static方法的特殊之处,但是可以看出static关键字的基本作用,简而言之,一句话来描述就是:方便在没有创建对象的情况下来进行调用(方法/变量)。

  很显然,被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问:

  1. static修饰成员方法:static修饰的方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都必须依赖具体的对象才能够被调用。但是要注意的是,虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法/变量的。
  2. static修饰成员变量:static修饰的变量也称为静态变量,静态变量和非静态变量的区别是:静态变量被所有对象共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
  3. static修饰代码块:static关键字还有一个比较重要的作用就是用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来依次执行每个static块,并且只会执行一次。

误区

  1. 在C/C++中static关键字是可以作用于局部变量的,但是在Java中是不允许使用static修饰局部变量的。这是Java语法的规定。
  2. 虽然对于静态方法来说没有this,但是我们在非静态方法中能够通过this访问静态方法成员变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {

static int value = 11;

public static void main(String[] args) {

new Test().printValue();

}

private void printValue() {
int value = 22;
System.out.println(this.value);
}
}

输出的结果为:11

注意这里的this表示的是当前对象,那么通过new Test()来调用printValue()的话,当前对象就是通过new Test()生成的对象。而static变量是被对象所享有的,因此在printValue中的this.value的值毫无疑问是11,在printValue方法内部的value是局部变量,根本不可能与this关联,所以输出11。需要记住的是:静态成员变量虽然独立于对象,但是不代表不可以通过对象去访问,所有的静态方法和静态变量都可以通过对象访问(只要权限足够)。

继承

继承这块主要需要了解关键字super的用法,由于子类不能继承父类的构造方法,因此,如果要调用父类的构造方法,可以使用 super 关键字。super 可以用来访问父类的构造方法、普通方法和属性,super 关键字的功能主要有以下两种:

  • 在子类的构造方法中显式的调用父类构造方法
  • 访问父类的成员方法和变量。
  1. super调用父类构造方法:可以在子类的构造方法中显式地调用父类的构造方法,基本格式如下:

    1
    super(parameter-list);

    其中,==parameter-list 指定了父类构造方法中的所有参数。super( ) 必须是在子类构造方法的方法体的第一行==

例:

1
2
3
4
5
6
7
8
9
public class Person {
public Person(String name, int age) {

}

public Person(String name, int age, String sex) {

}
}

子类 Student 继承了 Person类,使用 super 语句来定义 Student 类的构造方法。示例代码如下:

1
2
3
4
5
6
7
8
9
public class Student extends Person {
public Student(String name, int age, String birth) {
super(name, age); // 调用父类中含有2个参数的构造方法
}

public Student(String name, int age, String sex, String birth) {
super(name, age, sex); // 调用父类中含有3个参数的构造方法
}
}

注意:编译器会自动在子类构造方法的第一句加上super();来调用父类的无参构造方法。通过 super 来调用父类其它构造方法时,只需要把相应的参数传过去,如果一个类中没有写任何的构造方法,JVM 会生成一个默认的无参构造方法。在继承关系中,由于在子类的构造方法中,第一条语句默认为调用父类的无参构造方法(即默认为 super(),一般这行代码省略了)。所以==当在父类中定义了有参构造方法,但是没有定义无参构造方法时,编译器会强制要求我们在子类中定义一个相同参数类型的构造方法==。

  1. super访问父类成员:当子类的成员变量或方法与父类同名时,可以使用 super 关键字来访问。如果子类重写了父类的某一个方法,即子类和父类有相同的方法定义,但是有不同的方法体,此时,我们可以通过 super 来调用父类里面的这个方法。

例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person {
int age = 12;
}

class Student extends Person {
int age = 18;

void display() {
System.out.println("学生年龄:" + super.age);
}
}

class Test {
public static void main(String[] args) {
Student stu = new Student();
stu.display();
}
}

输出结果为:

1
学生年龄:12

例2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person {
void message() {
System.out.println("This is person class");
}
}

class Student extends Person {
void message() {
System.out.println("This is student class");
}

void display() {
message();
super.message();
}
}

class Test {
public static void main(String args[]) {
Student s = new Student();
s.display();
}
}

输出结果为:

1
2
This is student class
This is person class

上述例子可以看到如果只调用方法 message( ),是当前的类 message( ) 被调用,使用 super 关键字时,是父类的 message( ) 被调用。

super和this的区别

关于Java中 super 和 this 关键字的异同,可简单总结为以下几条:

  1. 子类和父类中变量或方法名称相同时,用 super 关键字来访问。可以理解为 super 是指向自己父类对象的一个指针。在子类中调用父类的构造方法。

  2. this 是自身的一个对象,代表对象本身,可以理解为 this 是指向对象本身的一个指针,在同一个类中调用其它方法

  3. this( ) 和 super( ) 都指的是对象,所以,均不可以在 static 环境中使用,包括 static 变量、static 方法和 static 语句块

  4. 从本质上讲,this 是一个指向对象本身的指针, 然而 super 是一个 Java 关键字

例3:在 Animal 类和 Cat 类中分别定义了 public 类型的 name 属性和 private 类型的 name 属性,并且 Cat 类继承 Animal 类。那么,我们可以在 Cat 类中通过 super 关键字来访问父类 Animal 中的 name 属性,通过 this 关键字来访问本类中的 name 属性,如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 父类Animal的定义
public class Animal {
public String name; // 动物名字
}

//子类Cat的定义
public class Cat extends Animal {
private String name; // 名字

public Cat(String aname, String dname) {
super.name = aname; // 通过super关键字来访问父类中的name属性
this.name = dname; // 通过this关键字来访问本类中的name属性
}

public String toString() {
return "我是" + super.name + ",我的名字叫" + this.name;
}

public static void main(String[] args) {
Animal cat = new Cat("动物", "喵星人");
System.out.println(cat);//java直接输出对象默认调用toString()函数
}
}

多态

Java实现多态有 3 个必要条件:继承、重写和向上转型。只有满足这 3 个条件,开发人员才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而执行不同的行为。

  • 继承:在多态中必须存在有继承关系的子类和父类
  • 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法
  • 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才既能可以调用父类的方法,又能调用子类的方法(向上转型属于自动类型转换,而向下转型属于强类型转换)

假设我们定义一种收入,需要给它报税,那么先定义一个Income类:

1
2
3
4
5
6
class Income {
protected double income;
public double getTax() {
return income * 0.1; // 税率10%
}
}

对于工资收入,可以减去一个基数,那么我们可以从Income派生出SalaryIncome,并覆写getTax()

1
2
3
4
5
6
7
8
9
class Salary extends Income {
@Override
public double getTax() {
if (income <= 5000) {
return 0;
}
return (income - 5000) * 0.2;
}
}

如果你享受国务院特殊津贴,那么按照规定,可以全部免税:

1
2
3
4
5
6
class StateCouncilSpecialAllowance extends Income {
@Override
public double getTax() {
return 0;
}
}

现在,我们要编写一个报税的财务软件,对于一个人的所有收入进行报税,可以这么写:

1
2
3
4
5
6
7
public double totalTax(Income... incomes) {
double total = 0;
for (Income income: incomes) {
total = total + income.getTax(); //注意此处
}
return total;
}
1
2
3
4
5
6
7
8
9
10
11
//主函数
public class Main {
public static void main(String[] args) {
// 给一个有普通收入、工资收入和享受国务院特殊津贴的小伙伴算税:
Income[] incomes = new Income[] {
new Income(3000),
new Salary(7500),
new StateCouncilSpecialAllowance(15000)
};
System.out.println(totalTax(incomes));
}

观察totalTax()方法:利用多态,totalTax()方法只需要和Income打交道,它完全不需要知道SalaryStateCouncilSpecialAllowance的存在,就可以正确计算出总的税。如果我们要新增一种稿费收入,只需要从Income派生,然后正确覆写getTax()方法就可以。把新的类型传入totalTax(),不需要修改任何代码,可见多态具有一个非常强大的功能,就是允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码。