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; } } ``` 他们在内存当中的分配情况如下图所示: ![](https://cs-wiki.oss-cn-shanghai.aliyuncs.com/image-20230412102001874.png) 注意`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 > } > ```