this 和 static 关键字
@[toc]
---
## this 关键字
举个例子,假设某个类中有一个方法 `peel` 且没有重载,如果我们使用了相同的构造函数创建了该类的两个对象 a 和 b,并且分别都调用了 `peel` 方法。那么我们怎么知道调用的是对象 a 的 `peel()`方法还是对象 b 的 `peel()` 方法呢?
```java
class Banana {
void peel(int i) {
/*...*/
}
}
public class BananaPeel {
public static void main(String[] args) {
Banana a = new Banana(), b = new Banana();
a.peel(1);
b.peel(2);
}
}
```
这是因为 Java 编译器做了一些底层工作,方法中其实还有一个**隐式参数 `this`**,该参数隐密地传入了一个指向调用该方法对象的引用。**也就是说谁(某个对象)调用了这个方法,这个方法的隐式参数就指向谁**
```java
Banana.peel(a, 1)
Banana.peel(b, 2)
```
**`this` 关键字只能在非静态方法内部使用**(因为静态方法并不属于某个对象,它是属于整个类的)
<br>
关键字 `this` 还有另外一个含义。 **如果构造函数的第一个语句形如 `this(...)`, 这个构造函数将调用同一个类的另一个构造函数**,这样可以避免代码重复。下面是一个典型的例子:
```java
public class Flower {
int petalCount = 0;
String s = "initial value";
Flower(int petals) {
petalCount = petals;
System.out.println("Constructor w/ int arg only, petalCount = " + petalCount);
}
Flower(String s, int petals) {
this(petals);
//- this(s); // Can't call two!
this.s = s; // Another use of "this"
System.out.println("String & int args");
}
```
尽管可以用 `this `调用一个构造器,但是**不能**调用两个。此外,**必须将构造函数调用至于最起始处即第一行**,否则编译器会报错。并且,编译器**不允许**你在一个构造器之外的方法里调用构造器。
另外,这个例子展示了 `this` 的另一个用法。参数列表中的变量名 `s` 和成员变量名 `s` 相同,会引起混淆。**你可以通过 `this.s` 表明你指的是成员变量 `s`,从而避免重复**。
## static 关键字
static 关键字可以修饰方法、变量、代码块、类、导包
### static 变量
**static 变量**:就是不需要创建对象,直接通过类就能访问的变量。访问的形式为 `类名.变量名`,在类加载的时候就完成初始化,内存当中只有一份,所有的对象都共享这一个变量。需要对象之间共享变量的时候使用。
```java
public class Static{
public static String staticVal = "static";
}
public class Main{
public void method1() {
System.out.println(Static.staticVal);
}
}
```
底层原理,JVM 的内存包含了栈、堆和方法区等,方法区中主要是放一些方法和常量。那么,如果我们有以下的对象:
```java
public class Person{
public String name;
public static Integer age;
}
public class Main{
public static void main(String[] args) {
Person person = new Person();
person.name = "itmtx";
person.age = 18;
}
}
```
他们在内存当中的分配情况如下图所示:

注意`name`和`age`存储位置的不同,`name` 是存储存储到堆上面的,和对象绑定在一起,而<u>常量 `age` 是Person类进行管理的,存储在静态存储区上面,不论建立多少个对象,都只指向这个一个地方。在类加载的时候,静态存储区中的内容已经初始化完成,所以在内存当中就只有这一份</u>。
实例变量和类变量(静态变量)的区别:
- 存放位置
类变量随着类的加载而存在于方法区中
实例变量随着对象的建立而存在于堆内存中
- 生命周期:
类变量生命周期最长,随着类的消失而消失
实例变量生命周期随着对象的消失而消失
### static 方法
**static 方法**:就是可以使用 `类名.方法名(方法参数...)` 进行访问的方法(其实就是<u>没有 `this` 的方法</u>),<u>静态方法当中,不可以直接调用非静态的方法</u>。具体的使用如下
```java
public class Static{
public void notStatic() {
//todo
}
public static void staticMethod() {
System.out.println("===静态方法===");
notStatic();//error 不可以这么调用,会报错
}
}
public class Main{
public void method1() {
Static.staticMethod();
}
}
```
### static 代码块
**static 代码块**:static 静态代码块的作用是初始化一些静态变量使用的,在 static 代码块里面,可以调用静态变量方法
```java
public class Static{
public static List<String> staticList = new ArrayList<>(2);
static {
staticList.add("static1");
staticList.add("static2");
}
}
public class Main{
public void method1() {
System.out.println(Static.staticList.get(0));
}
}
```
### static 类
**static 类**:static 修饰类,只能够在<u>静态内部类</u>当中使用,所谓的静态内部类,就是在类里面套一层类,并且这个类是使用static修饰的。这样做可以把一个类隐藏到外部类里面,而且不需要内部类引用外围类对象。
```java
public class Static{
public static class InnerStaticClass {
public void method() {
System.out.println("===InnerStaticClass====");
}
}
}
public class Main{
public void method1() {
Static.InnerStaticClass innerClass = new Static.InnerStaticClass();
}
}
```
### 静态导包
**静态导包**:静态导包是简化书写使用的,使用了静态导包之后,就可以直接使用相关的静态变量,静态方法,不需要使用`类名.变量`和`类名.方法`来进行调用,一般的使用场景是junit判断是否相等的情况下使用。
> static 关键字一般都是用在**工具类**里面,工具类只需要一套,载入内存的时候对这些变量和方法进行初始化就可以了,不需要每一次都创建对象,然后调用相应的变量和方法,否则会浪费过多的内存资源。但是,<u>如果 static 的变量和方法太多,而这些变量和方法是在 JVM 关闭的时候才进行清理的,就可能会导致内存溢出</u>。
>
> 对于程序当中**常用的变量,而且基本不需要变更的**,也可以使用 static 关键字进行修饰,然后使用 static 静态代码块进行初始化,那么程序在调用的时候就可以直接在内存中拿来就用,不需要再堆上面再分配空间,然后初始化成一样的数据,这样会降低效率而且会浪费内存。基于这个原因,JUnit 使用 @BeforeClass 注解的方法,必须是 static 方法的原因就显而易见了,因为在单元测试当中,@BeforeClass是在单元测试载入时候初始化相关的数据使用的,而且这些数据是一整类都通用,那么让他们初始化到内存当中,用的时候直接调可以大大节约时间和空间。
## 成员变量和局部变量的区别
Java 中的变量分为成员变量和局部变量,它们的区别如下:
**成员变量**:
- 成员变量是在类的范围里定义的变量
- 成员变量有默认初始值
- 未被 `static` 修饰的成员变量也叫<u>实例变量</u>,它存储于对象所在的堆内存中,生命周期与【对象】相同
- 被 `static` 修饰的成员变量也叫<u>类变量</u>,它存储于方法区中,生命周期与【当前类】相同。
**局部变量**:
- 局部变量是在方法里定义的变量
- 局部变量没有默认初始值
- 局部变量存储于栈内存中,作用的范围结束,变量空间会自动的释放
> 作用域决定了在其内定义的变量名的可见性和生命周期。在 Java、C++、C 中,作用域由花括号 `{ }`的位置决定。例如:
>
> ```java
> {
> int x = 12;
> // Only x available
> {
> int q = 96;
> // Both x & q available
> }
> // Only x available
> // q is out of scope
> }
> ```
