Java 关键字解析:new、static 与 final

new

要谈 new 关键字, 就必须先说说构造方法, 它们 2 个就是一对好基友.

构造方法

定义
  • 无参构造方法:
    访问修饰符 类名(){ }

  • 带参构造方法
    访问修饰符 类名(参数列表){ }

说明
  1. 构造方法可以被重载;
  2. 没有返回类型 (void 也算返回类型, 只是没有返回值);
  3. 方法名和类名必须一样;
  4. 如果不写, 系统会默认提供一个无参构造方法;
  5. 如果写了, 系统就不会提供任何构造方法;
  6. 子类不能继承父类的构造方法;
  7. 接口没有构造方法;
作用

产生对象
new 一个对象的时候构造器所要做的事

  1. 创建空间;
  2. 划分属性;
  3. 初始化值;
  4. 执行构造方法内的语句;

实例化一个对象

用 new 关键字实例化一个对象时, 所用做的事:
People p = new People();

  1. 加载 People,class 文件进入内存;
  2. 在栈内存为 p 变量申请一个空间 (4 个字节);
  3. 在堆内存为 People 对象申请空间 (大小为类中所有成员变量的大小);
  4. 对类中的成员变量进行默认初始化;
  5. (如果有) 对类的成员变量进行显示初始化;
  6. 有构造代码块, 先执行构造代码块中的语句;
  7. 执行构造方法中的语句;
  8. 把堆内存的引用赋给 p 变量;

static

(类级别的, 与对象无关)
可以修饰属性, 方法, 代码块和内部类;

static 修饰属性 (类变量)

  • 那么这个属性就可以用 类名.属性名 来访问, 也就是使这个属性称为本领的类变量, 为本类对象所共享;

static 修饰方法 (静态方法)

  • 会使这个方法称为整个类所公有的方法, 可以用 类名.方法名 访问;
  • static 修饰的方法, 不能直接访问本类中的非静态成员, 但本类的非静态方法可以访问本类的静态方法;
  • 在静态方法中不能出现 this 关键字;
  • 父类中是静态方法, 子类中不能覆盖为非静态方法;
  • 在符合覆盖规则的前提下, 在父子类中, 父类中的静态方法可以被子类中的静态方法覆盖, 但是没有多态;
  • 在使用对象调用静态方法时, 其实是在调用编译时类型的静态方法;

static 修饰初始化代码块

  • 此时初始化代码块叫做静态初始化代码块, 这个代码块只在类加载时被执行一次;
  • 可以用静态初始化代码块初始化一个类;
  • 动态初始化代码块, 些在类体的 {} 中, 这个代码块在生成对象时运行;

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class StaticTest{
static int a = 3;
static int b ;
static void method(int x){
System.out.println("x = " + x);
System.out.println("a = " + a);
System.out.println("b = " + b);
}
static {
System.out.println("静态初始化代码块");
b = a * 4;
}
public static void main(String[] args){
method(5);
}
}

输出结果:
静态初始化代码块
x = 5
a = 3
b = 12

执行顺序

  1. 首先加载 StaticTest 类, 在静态区开辟空间, 给 a 赋值为 3,b 赋值为 0;
  2. 接着执行静态初始化块, 先打印一条语句, 然后把 a*4 的值赋给 b;
  3. 然后把 main() 方法调入栈中,main() 调用 method() 方法, 把 3 传给 x;
  4. 然后打印;

注意: 在一个 static 方法中引用任何实例变量都是非法的.

main() 中的 static

一般情况下,主方法是静态方法,所以可调用静态方法,主方法为静态方法是因为它是整个软件系统的入口,而进入入口时系统中没有任何对象,只能使用类调用。

说明

在有 static 定义的类的外面, static 方法和变量能独立于任何对象而被使用.
例如想从类的外部调用一个 static 方法:
className.method();
或者调用一个 static 属性:
className.属性名;
这就是 Jaba 如何实现全局功能和全局变量的一个控制版本.
例子

1
2
3
4
5
6
7
8
9
10
11
12
13
class StaticDemo{
static int a = 42;
static int b = 99;
static void callMe(){
System.out.println("a = " + a);

}
class StaticByName{
public static void main(String[] args){
StaticDemo.callMe();
System.out.println("b = " + StaticDemo.b);
}
}

输出结果:
a = 42
b = 99

最后一点: static 成员不能被其所在 class 创建的实例访问的.

简单来说

  1. 如果不加 static 修饰的成员是对象成员, 也就是归每个对象所有;
  2. 加 static 修饰的成员是类成员, 就是可以由一个对象直接调用, 为所有对象公有.

从内存的角度来看 static

类加载的过程中, 类本身也是保存在文件中的 (字节码文件保存着类的信息),
java 通过 I/O 流把类文件读入 JVM, 这个过程称为类的加载.
JVM 会通过类路径 (classpath) 来找字节码文件.
需要的时候才会进行类的加载, 生成对象时是先加载后构造.
类变量, 会在加载时自动初始化, 初始化规则和实例变量相同.
注意:
类中的实例变量是在创建对象时被初始化的.
static 修饰的属性, 是在类加载时被创建并进行初始化,
类加载的过程只有一次, 也就是类变量只会被创建一次.

静态非静态成员的调用

  • 静态成员能直接调用静态成员;
  • 非静态成员不能调用静态成员, 能调用非静态成员;
    原因:
    从内存的角度, static 修饰的成员是随着类的加载而创建的,
    如果此时用静态成员调用非静态成员, 则会报错,
    因为在内存中还没有这个非静态成员;
    非静态成员只有在实例化对象的时候才在内存中创建, 所以非静态成员可以调用静态成员.
    说白了就一句话:

先出生的不能娶还没有出生的;(指腹为婚不在讨论的范围之内)
后出生的长大了可以娶先出生的 (现在很流行啊), 也可以娶一起出生的;

final

最终的意思, 不允许改变;
可以修饰变量, 方法和类.

final 修饰变量

  • 一旦被 final 修饰的变量, 就会变成常量, 不能被重新赋值.
  • 常量可以在初始化的时候直接赋值, 也可以在构造方法里赋值,
  • 只能二选一, 不能为常量重新赋值;
  • 常量没有默认值;
  • 锁定栈, 使栈中的数据不可以改变;
  • 静态常量只能在初始化时直接赋值;
关于引用的问题

问题: 使用 final 修饰的变量, 是引用不能变还是引用的对象不能变?

1
2
3
4
final StringBuffer a = new StringBuffer("finalTest");
a = new StringBuffer("");//报编译器错误

a.append("finalTest");//这样可以

所以被 final 修饰的变量, 是引用变量不能变, 引用变量所指向的对象中的内容还是可以改变的.

final 修饰方法

被 final 修饰的方法就不能被子类重写.

final 修饰类

  • 被 final 修饰的类将不能被继承.
  • final 类中的方法也都是 final 的.

注意:
final 不能用来修饰构造方法.

public static final int MAX = 100;

常量属性定义为 static, 节约空间
final: 因为用 final 修饰, 所以是个常量
public: 因为是常量, 所以不能更改, public 修饰给别人看也没事
static: 共享, 这样就不用 new 出来才能使用;

补充说明:
内部类要访问局部变量时, 局部变量必须定义为 final.

为什么局部内部类不能访问没有被 final 修饰的局部变量?

从内存中看,当方法里的局部变量所在方法结束时,该变量即在栈内存中消失;而内部类其实是一个类,只有内存中对它的所有引用都消失后,该内部类才”死亡”,即内部类的生命周期可能比局部变量长。如果局部内部类能访问一般的局部变量,则在多线程中,可能当方法结束后,局部内部类 (是线程类时) 还在使用局部变量。为了避免方法内的变量脱离方法而存在的现象发生,于是 java 规定局部内部类不能访问一般的局部变量。但能访问被 final 修饰的局部变量。因为常量放在堆内存中的数据段的常量池中

finally

是异常处理语句的一个部分, 是异常的同意出口, 表示总是执行.
就算是没有 catch 语句同时又抛出异常的情况下,
finally 代码块仍然会被执行。
最后要说的是,finally 代码块主要用来释放资源,比如:I/O 缓冲区,数据库连接

finalize()

是 Object 类的一个方法, 在垃圾收集器执行的时候,
会调用被回收对象的此方法,
可以覆盖此方法提供垃圾收集时的其他资源回收, 例如关闭文件等.
JVM 不保证此方法总被执行.

abstract

  • 抽象类不能实例化对象, 但是有构造方法, 抽象类中可以包含属性, 抽象类中的构造方法是用来初始化成员属性的;
  • 所有抽象对象必须被重写, 除非子类也是抽象类;
  • 可以存在没有抽象方法的抽象类;
  • 有抽象方法的类必须定义为抽象类;
  • abstract type name_method();
  • 只能修饰方法和类;
    剩下的详细总结将在面向对象的特征中介绍.

instanceof

A instanceof B
A 对象是否是 B 对象的实例

1
2
3
4
5
6
7
public boolean equals(Object obj){
if(obj instanceof Student){
if(student.name.equals(name))
return true;
}
return false;
}

this

  1. 对当前类的引用;
  2. 每个对象都有一个隐含的 this 变量, 它可以访问类的所有信息;
  3. 构造方法的相互调用, 必须放在构造方法内的第一个语句;
    在重载的构造器中, 为了重复利用代码
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
class Gril {
private int age;
private String name;
//构造方法
public Gril(int age ,String name){
this(name);
this.age = age;
//this.name = name;
}
public Gril(String name){
this.name = name;
}
public Gril(){

}
//睡觉的方法
public void sleep(Car car){
System.out.println("在车上睡觉");
}
public void sleep(Bed bed){
System.out.println("在床上睡觉");
}
public void sleep(){
System.out.println("在野外睡觉");
}
}

super

this 和 super 详解

  1. this
    this.–> 此时的 this 代表当前对象, 可以操作当前类中的所有属性和方法; 也可以操作当前类父类访问修饰符允许的方法和属性

  2. super 代表当前对象的父类, 只能操作当前类父类中访问修饰符允许的属性和方法;

    • this 不能看到的时候, super 也看不到, this 能看到的, super 还是看不到;
    • 只有在发生重写的时候, 才用 super 调用当前对象的父类中同名的方法和属性;
    • this() 调用本类的其他构造方法;
    • super() 调用当前对象的父类的构造方法;
    • 都只能放在构造方法的第一行;
    • this() 没有默认;
    • super() 为默认公共无参构造;

关于 super 更详细的介绍将在面向对象之继承中见到.