static,final,transient等关键字原理作用

static

  静态修饰,可以让方法等脱离类的约束,不用创建对象即可访问和调用,所以静态方法或变量无法用this引用。

用途

静态变量:

1
private static String str = "str";

静态方法

  在静态方法内不能调用非静态方法和非静态成员变量,因为非静态依赖于对象,但非静态方法中可以调用静态方法和静态变量。

静态导包

  静态导包可以导入包内的静态资源,可以直接调用静态方法等,

1
import static xxx.xxx.xxx;

静态代码块

1
2
3
static {
......
}

构造函数是静态方法吗?

以下内容基于参考博客实例构造器是不是静态方法?,博主RednaxelaFX写的非常详细和通俗易懂,所以我部分就是直接复述原文内容。

Java语言规范第三版中规定静态方法:

  静态方法即类方法,在不引用对象时调用,尝试用this或super引用当前对象的或引用静态方法主体中任意函数内声明类型参数时会在编译阶段报错(An attempt to reference the current object using the keyword this or the keyword super or to reference the type parameters of any surrounding declaration in the body of a class method results in a compile-time error),静态方法不可被声明为抽象类型abstract,非静态的方法即实例方法,实例方法总是依赖于对象被调用,而静态方法总不依赖于对象。

Java语言规范第三版中规定this:

  关键字this只能在实例方法的主体、实例初始化器或构造函数中使用,或者在类的实例变量的初始化器中使用。如果它出现在其他任何地方,就会出现编译时错误。
(The keyword this may be used only in the body of an instance method, instance initializer or constructor, or in the initializer of an instance variable of a class. If it appears anywhere else, a compile-time error occurs)

  当被调用方法处于调用者的类中时,可以用this代替对象,静态方法中无法引用this,但构造函数中是可以引用this的,所以至少可以确定构造函数并不是静态方法。

Java虚拟机规范中3.6.1规定类方法和实例方法的区别:

  调用类方法时,所有参数按顺序存放在被调用方法的局部变量区中的连续区域,从局部变量0开始;在调用实例方法时,局部变量0用来存放该方法所属的对象实例,所有参数从局部变量1开始存放在局部变量区中的连续区域中。区别就是实例方法存入了对象实例this。

Java虚拟机规范中4.3.3规定方法描述符:

无论是类方法还是实例方法,其方法描述符都相同,this作为调用实例方法的一个隐式参数,不会反映在方法描述符中。

Java中支持三种多态

  • 通过虚方法override覆写的方式来实现子类型多态,
  • 通过方法重载支持的ad-hoc多态,
  • 通过泛型支持的参数化多态。一般情况多态一词多指子类型多态

Java虚拟机规范中8.4.3.3描述final方法:

  final声明方法,防止子类覆盖或隐藏方法,若尝试重写或隐藏final方法,或将final方法声明为abstract会在编译时报错,private私有方法或final类中的某些方法(?all methods declared immediately within a final class)等很像final方法,因为它们都是不可覆写的。

  运行时,机器码生成器或优化器可以内联final方法的主体,用方法主体内的代码代替方法的调用,内联的流程必须保留方法调用的语义。特别是若实例方法调用的目标是null时,即使方法是内联的也要抛出NullPointerException。编译器要保证异常被抛出在正确的位置,这样方法的实际参数可以在方法调用前按照正确的顺序来计算。

如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
final class Point {  
int x, y;
void move(int dx, int dy) { x += dx; y += dy; }
}
class Test {
public static void main(String[] args) {
Point[] p = new Point[100];
for (int i = 0; i < p.length; i++) {
p[i] = new Point();
p[i].move(i, p.length-1-i);
}
}
}

  内联到main函数中

1
2
3
4
5
6
7
for (int i = 0; i < p.length; i++) {  
p[i] = new Point();
Point pi = p[i];
int j = p.length-1-i;
pi.x += i;
pi.y += j;
}

虚方法: 静态方法都非虚,final和private声明的实例方法也都非虚,其他方法都属于虚方法。

  Java中非虚方法可以通过静态绑定(static binding)来选择实际的调用目标,无法覆写所以无法多态,所以调用目标只能有一个。

  虚方法则一般要等到运行时根据调用者的具体类型来选择要调用哪个版本的方法,这个过程即运行时绑定(runtime binding)。

  final方法可以在运行时得到内联,所以所有的非虚方法在运行时都可以安全的内联,因为不会在运行时有所异动,都可以在编译阶段完成。一个优化比较激进的JVM会进一步在源码中声明为虚方法,但若运行时(类层次分析CHA)分析若只有一个调用目标时仍可以将方法内联到调用者内(方法调用是会消耗性能的,所以早期的时候应该是不建议频繁函数间调用),目前主流的JVM都会进行此类优化,所以我们在编程时没有必要为了优化性能而特意的去将方法声明为final

HotSpot VM 实现静态绑定:

1
2
3
4
bool methodOopDesc::can_be_statically_bound() const {  
if (is_final_method()) return true;
return vtable_index() == nonvirtual_vtable_index;
}

  所以只要方法是final或者说它不是虚方法,就可以静态绑定。

Java虚拟机规范中定义了四种字节码指令处理Java程序不同种类方法的调用:

1
2
3
4
· invokestatic - 用于调用类(静态)方法 
· invokespecial - 用于调用实例方法,特化于super方法调用、private方法调用与构造器调用
· invokevirtual - 用于调用一般实例方法(包括声明为final但不为private的实例方法)
· invokeinterface - 用于调用接口方法

  其中invokestatic和invokespecial调用的目标应该是可以静态绑定的,因为两类都无法进行子类型多态,invokevirtual和invokeinterface一般要运行时绑定,但JVM会在可优化时根据final或实际调用对象等尝试进行静态绑定。

Java语言规范中关于构造器部分内容:

  构造器用于创建类的实例-对象,构造器并不是类的成员,不会被继承,因此也不会被隐藏或覆盖

  所以实力构造器是不是静态方法呢?结果是否定的,首先构造器不是方法,然后构造器有隐式参数this,绑定对象。实例构造器无法被隐藏或覆写,所以无法多态,因此可以进行静态绑定,所以在这种情况下构造器是“静态”的,但仅限于在这里和静态方法相似了,它们有很多不同。

总结不同点:

  1. 构造器不是方法
  2. 构造器有隐式参数this
  3. 构造器只能通过new关键字被调用,而无法像方法那样被调用,这样保证了对象的创建和初始化是一体的。

相同点:

  都无法被覆写或隐藏,都可以静态绑定,被调用者内联。


对象的创造过程

Java语言规范描述:

  1. 类的实例对象显式的通过类实例创建表达式创建{15.9 new [TypeArguments] <?> ( [ArgumentList] ) [ClassBody],如 new ArrayList <> ();}

  2. 数组的实例对象显式的通过数组创建表达式创建{15.10.1 new Type [Dims]等,如new double[2][2][2];}

  3. 字符串连接操作符”+”{15.18.1 String1 + String2,若只有一个字符串,会将另一个操作数转换为字符串,注意+是左结合的,即1+2+“a”=“3a”,反之”a12“}用于非变量表达式{15.28 即常量表达式}中,会隐式的创建一个String类型对象

  4. 对数组初始化器表达式求值{10.6 {1,2,3} },会隐式的创建一个新的数组对象:初始化类或接口{12.4},创建类的新实例{15.9},执行局部变量声明语句{14.4}

  5. Boolean,Integer等基本类型的包装类型的新对象会通过装箱转换被隐式的创建。

  加载类(从class文件得到类,解析验证类信息等)->初始化超类->初始化类

  初始化类不会初始化其实现的接口,初始化接口也不会初始化超接口。

类中的加载顺序:

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
27
28
29
30
public class Parent {
static Print printStatic = new Print("父类静态变量");
private Print printInstance = new Print("父类实例变量");

static {
new Print("父类静态域");
}

public Parent() {
new Print("父类构造器");
}
}

public class Son extends Parent {
static Print printStatic = new Print("子类静态变量");
private Print printInstance = new Print("子类实例变量");

static {
new Print("子类静态域");
}
static Print printStatic1 = new Print("子类静态变量1");

public Son() {
new Print("子类构造器");
}

public static void main(String[] args){
Son son = new Son();
}
}

输出结果:

1
2
3
4
5
6
7
8
9
父类静态变量
父类静态域
子类静态变量
子类静态域
子类静态变量1
父类实例变量
父类构造器
子类实例变量
子类构造器

类的加载顺序:

  父类静态代码块和静态变量->子类静态代码块和静态变量->父类实例变量初始化->父类构造函数->子类实例变量初始化->子类构造函数,其中静态部分根据代码顺序加载


final

用途

final变量:

  只能被赋值一次,否则会在编译时报错。若final被赋值为对象的引用,对象可以正常因为操作而修改成员数据,只是final变量会永远指向此对象。

  常量变量是指用常量表达式初始化的或String类型的final变量

  有三类变量被隐式的声明为final

  • 接口的域
  • 带资源的try语句中的资源
  • 多重catch子句中的异常参数。单catch子句的异常参数不会被隐式声明final,但其实可以看作final。

final方法:

  final声明方法,表示其不可被重写,优点是编译时静态绑定,相较运行时绑定肯定是速度快一些的。

final类:

  final类不能有任何子类。


final关键字的优点

  final关键字某些情况下可以优化性能,因为静态绑定但一些JVM会对某些情况进行优化,即使没有用final和static声明也会进行静态绑定,所以本人目前说不清此处,我的判断是没有必要因为优化性能刻意的使用final,应该是为了给与“只读“属性而去使用final。

transient

  Java对象的序列化,即将对象转化为字节序列,包含对象的数据和信息,序列化后对象可以写入数据库或文件,也可以用于网络传输,一般当我们使用缓存cache或远程调用rpc的时候,会实现Serializable接口来实现可序列化。

  transient关键字让修饰的成员变量不被序列化。


部分参考博客:

https://rednaxelafx.iteye.com/blog/652719

http://www.cnblogs.com/chenpi/p/6185773.html

若内容涉及侵权请及时告知,我会尽快修改和删除相关内容