【JAVA进化论】LV3-1:作用在类、变量、方法上的其它修饰符

一、static关键词

简单解释:static关键词放到访问权限修饰词的后面,用来修饰类的属性、方法,被static修饰了的属性和方法被称为静态属性静态方法,静态属性和静态方法可以在不实例化对象的情况下直接通过“类名.属性/方法”的方式触发。

现在开始我们的详细介绍:

我们在之前学类的时候会发现,所有的方法和属性必须要在类做实例化产生自己的对象之后,通过对象的引用变量来触发调用,简单来说,非static的内容,都是属于类对象的,没有产生具体对象的类里的这些非static内容没有任何意义,那static呢?被static修饰的内容属于类本身的内容,静态方法和静态属性是可以直接用类调用的,你可以理解静态属性和方法就是类定义本身的属性和方法,不依赖产生的对象而让自己变得有意义,只要类存在,且被加载进jvm,它就是有意义的,它就是可以通过类本身去触发使用的,让我们来举个例子:

让我们把前面Animal的类改造下,我们知道,Animal是为了描述动物的一个超大父类,里面定义了一些作为动物应该有的类属性,比如name、age,这些属性你会发现一个特点,那就是它们必须要依赖具体某一个动物个体才有意义,换句话说,每个动物的age、name各不相同,这个具体的个体就是我们利用Animal产生的对象,name、age离开了对象个体没有意义,现在我们再来发散下思维,如果需要有一个字段来描述Animal这个类,比如Animal是动物类,假如我们未来还会有植物类,我们现在需要一个字段用来区分它们,这时我们新增一个字段叫“生物分类”的字段,Animal里这个字段的值恒等于“动物”,这个字段我们发现是专门用来形容类的,你让这个字段变成一个普通的类属性,那它每次new一个Animal对象,这个属性就跟着存到对应的对象一次,是不是觉得很浪费资源?这时就可以把这个属性定义成一个静态变量:

代码块1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Animal {

//这里定义一个静态变量,用来标记该类属于那种生物分类
public static String taxonomy = "动物类";

private String name = "xx";

public int age;

public void cry() {
System.out.println("叫声为:未知");
}

public void eat() {
System.out.println(this.name + "正在吃东西");
}

public void setName(String name) {
this.name = name;
}

public void setAge(int age) {
this.age = age;
}

}

我们怎么访问这个字段呢?我们前面已经说过,通过“类名.属性/方法”即可触发:

代码块2
1
2
3
public static void main(String[] args) {
System.out.println(Animal.taxonomy);
}

看,这样就可以访问到静态属性了,不光属性是这样,方法调用也是一样的,现在我们加一个工具方法,用来判断一个动物的年龄是否合法:

代码块3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Animal {

//这里定义一个静态变量,用来标记该类属于那种生物分类
public static String taxonomy = "动物类";

private String name = "xx";

public int age;

...省略...

public static boolean isAgeTooLong(int age) { //这个静态方法用来判断一个年龄对于一个动物来讲是否合理
return age > 10000;
}

}

方法的调用也是类似:

代码块4
1
2
3
4
public static void main(String[] args) {
System.out.println(Animal.taxonomy); //访问静态属性
System.out.println(Animal.isAgeTooLong(1)); //调用静态方法
}

那静态的变量/方法跟普通的变量/方法有什么区别呢?我们来看下流程图:

图1

按照这个特性,非静态方法仍然可以调用静态方法:

图2

在Animal内部调用甚至可以省略Animal,想想这是为什么?因为通过图1可以知道,静态内容是属于类模板本身的,而非静态内容则是类对其对象所做的约束定义,通过这些非静态的内容规定了它产生的对象所包含的内容,所以对于静态内容来说,只要有访问权限(是的,静态内容也受访问权限修饰符的影响,规则跟之前相符),不管在哪里,都可以通过类名调用。

static主要用来定义一些工具方法或者属性,这些方法和属性仅为了处理一些非对象级别的操作,比如例子里判断年龄对于动物是否合法的方法,你可以认为它就是一个工具方法,因为它跟具体的动物对象没有关系,而是对整个动物圈都有效,那你就可以把它搞成一个静态工具类。

⚠️ static是不可以直接修饰类的,只能修饰内部类,被static修饰的内部类被称作静态内部类,有关内部类的内容在后面的文档,这里先不做了解。

二、final关键词

在Java中,final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。下面就从这三个方面来了解一下final关键字的基本用法。

2.1:final修饰类

当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。

例如你不希望Animal产生任何的子类,那么可以这样定义Animal:

图3

使用final修饰了Animal之后,Cat这里就会报错:

图4

因为被final修饰的class,不允许被继承。

思考:abstract关键词可以和final一起使用吗?

2.2:final修饰方法

被final修饰的方法,不允许被覆盖重写,比如我们把Animal里的cry方法改成final的:

图5

这时Cat类里就会报错:

图6

可以看到,IDE会提示被final修饰的方法无法被重写。

⚠️ 在继承关系中,父类除了final方法不允许被子类重写private的方法同样不允许,private方法会被隐式的指定为final方法。

2.3:final修饰变量

final可以修饰成员变量、局部变量,被final修饰了的变量,不可以被再次赋值,例如:

图7

被final修饰的成员变量和局部变量的初始值赋值也是有区别的:

图8

可以看到,当成员变量设置为final时如果不赋初始值,则会提示报错,而局部变量则不会,局部变量只需要在第一次使用前赋初始值即可,赋完之后再次赋值才会提示重复赋值的错误。

结论就是final关键词的目标就是保证变量内容仅可以被赋值一次,被final修饰的变量更像是一个常量,因为它并不会再次发生改变了。

final在实际开发过程中通常和static一起使用,用来保存一个类里的常量:

图9

试想一下为什么,首先这是个静态数据,还是个public的,这就意味着任意类任意地方都有可能把它的值给改掉,这就不符合常量这一标准,所以我们需要使用final把它锁起来。

我们再来发散下思维,final控制数据不被再次赋值,那么引用变量会不会改变?

我们之前讲过,引用变量内部保存了一个对象的地址信息,而被final修饰的变量不可以被再次赋值,我们举个例子:

图10

所以final仅仅保证一个变量不会再次被赋值,但不保证引用变量引用的对象内容不发生改变。