1、this
- 关键字 this 有两个含义:
- 1、指示 隐式参数 的 引用 。
- 当 this 在 成员名前边时,this 代表调用该属性的对象。
- 形参和成员名 重名 ,用 this 来区分。
- 当 this 在 方法中时,this 代表调用该方法的对象。
- 2、调用 该类 的 其它构造器 。
- 当 this 在构造器中时,this 代表该构造器正在初始化的对象。
- 只能在构造器的第一行出现 。
- this 引用:在 非 static 修饰方法、构造器可用。
// 示例类
class Person {
private String name;
private int age;
// 用法 1:解决成员变量与局部变量同名问题
public Person(String name, int age) {
// 隐式参数 this 指向调用对象。
// 即:调用 setName 方法的 p2 对象。
this.name = name;
this.age = age;
}
// 用法 2:构造函数中调用其他构造函数(必须放在第一行)
public Person() {
this("Unknown", 0); // 调用带参数的构造函数
}
// 用法 3:返回对象本身实现链式调用
public Person setName(String name) {
this.name = name;
return this; // 返回当前对象
}
public Person setAge(int age) {
this.age = age;
return this; // 返回当前对象
}
// 用法 4:将当前对象作为参数传递
public void register() {
RegistrationService.register(this); // 传递当前对象
}
// 用法 5:在内部类中访问外部类实例
class Introduction {
private String name = "Introduction";
public void show() {
System.out.println("外部类姓名: " + Person.this.name); // 访问外部类成员
System.out.println("内部类名称: " + this.name); // 访问内部类成员
}
}
@Override
public String toString() {
return name + " (" + age + "岁)";
}
}
// 模拟注册服务
class RegistrationService {
static void register(Person person) {
System.out.println("注册用户: " + person);
}
}
// 测试类
public class ThisKeywordDemo {
public static void main(String[] args) {
// 测试构造函数链式调用
Person p1 = new Person();
System.out.println("默认构造: " + p1);
// 测试链式方法调用
new Person().setName("张三").setAge(25).register();
// 测试内部类访问
Person p2 = new Person("李四", 30);
Person.Introduction intro = p2.new Introduction();
intro.show();
}
}
2、super
- 关键字 super 有两个含义:
- 1、调用超类的方法和属性。
- 2、调用超类的构造器。
- 只能在构造器的第一行出现 。
- 规则:子类的构造器【总会】调用父类的构造器【一次】。
- 默认情况下,子类构造器会 自动调用 父类的无参数构造器。
- 如果 子类构造器 调用 某个 父类构造器 就用 super(参数)来调用。
- super 不是一个对象的引用。
* 因为,不能将 super 赋值给 另一个对象变量 。
- 事实上,super 只是一个指示 编译器调用超类方法、属性的特殊关健字。
// 父类
class Animal {
String type = "动物";
public Animal() {
System.out.println("父类构造方法:创建基础动物");
}
public Animal(String type) {
this.type = type;
System.out.println("父类构造方法:创建" + type);
}
public void eat() {
System.out.println("动物正在进食...");
}
}
// 子类
class Cat extends Animal {
String type = "猫科动物";
// 用法 1:调用父类构造方法(必须放在第一行)
public Cat() {
super("猫"); // 显式调用父类有参构造方法
}
// 用法 2:访问被子类覆盖的父类方法
@Override
public void eat() {
super.eat(); // 先调用父类的eat方法
System.out.println("猫正在吃鱼...");
}
// 用法 3:访问被子类隐藏的父类成员变量
public void showType() {
System.out.println("子类属性: " + this.type);
System.out.println("父类属性: " + super.type); // 访问父类成员变量
}
// 内部类演示特殊用法
class Behavior {
// 用法 4:在内部类中访问外部类的父类方法
public void hybridEat() {
Cat.super.eat(); // 直接访问外部类父类的方法
}
}
}
// 测试类
public class SuperKeywordDemo {
public static void main(String[] args) {
// 测试构造方法调用
Cat cat = new Cat();
// 测试方法覆盖调用
cat.eat();
// 测试成员变量访问
cat.showType();
// 测试内部类访问父类方法
Cat.Behavior behavior = cat.new Behavior();
behavior.hybridEat();
}
}
- 在 Java 中使用 关键字 super 调用超类的方法,而在 C++ 中 则采用超类名加 :: 操作符的形式。
- 如:Manager 类的 getSalary 方法要调用 Employee::getSalary 而不是 super.getSalary 。
3、this vs super
- this 表示当前对象。
- super 不是一个对象的引用。
* 因为,不能将 super 赋值给 另一个对象变量 。
- 事实上,super 只是一个指示 编译器 调用 超类 方法 、 属性 的特殊 关健字 。
- 属性:
- this 访问本类中的属性,如果本类没有此属性则从父类中继续查找。
- super 访问父类中的属性。
- 方法:
- this 访问本类中的方法,如果本类没有此方法则从父类中继续查找。
- super 访问父类中的方法。
- 构造器:
- this 调用本类构造,必须放在构造方法的 首行 。
- super 调用父类构造,必须放在子类构造方法 首行 。
- synchronized 的使用:
- this 可以用于 synchronized 同步块中,表示 当前对象锁 。
- super 不能用于 synchronized 同步块。
- super 调用构造器、this 调用构造器 都必须出现在构造器的第一行。
- super 调用构造器、this 调用构造器 不能同时出现。
- 如果没有 super 调用,也没有 this 调用。
- 子类构造器会自动先调用 父类无参数的构造器。
- 若果有 super 调用。
- 子类构造器会根据 super 传递的参数去调用父类相应的构造器。
- 如果有 this 调用。
- 子类构造器会先找到 this 调用所对应子类被重载的构造器。
4、static
- static 关键字的基本作用:
- 在没有创建对象的情况下,可以直接用类名来调用 static 修饰的 方法和属性。
- 规则:static 成员不允许访问非 static 成员。
- 【静态成员不能访问非静态成员】
- this 引用,super 都不允许出现 static 方法中。
- 当程序直接访问一个 field,实际上前面省略了;
- 如果 field 是实例 Field,相当于省略了 this;
- 如果 field 是类 Field,相当于省略了类名;
4.1、static 修饰的方法 – 类方法 / 静态方法
- static 修饰的方法属于类而不属于 某个对象。
- 静态方法不能被 重写(在子类方法上添加 @Override 注解就提示编译报错,删除注解就正常)
- 可以直接使用 类名来调用。也可以用对象的引用来调用(会有错误提示,但能正常执行)。
- 分析时,可先将对象的引用替换成类名。
- 在 static 方法中不能使用 this 或 super 关键字。
4.2、static 修饰的成员变量 – 类变量 / 静态变量
- static 修饰的变量被所有实例所共享,不会依赖于对象。
- static 变量在内存中只有一份,在 JVM 加载类的时候,只为静态分配一次内存。
- static 变量的初始化顺序按照定义的顺序进行初始化。
/**
* 静态变量都可以通过 对象 来访问
*/
public class Demo {
static int value = 33;
public static void main(String[] args) throws Exception{
new Demo().printValue();
}
private void printValue(){
int value = 3;
// 输出结果为 33。
System.out.println(this.value);
}
}
- 局部变量既不属于类,也不属于实例。
* 所以,局部变量不能使用 static 修饰。
4.3、static 修饰的代码块 – 类代码块 / 静态代码块
- static 修饰的代码块,通常用来做程序优化的。
- 因为,静态代码块只会在类加载的时候,只执行一次。
- 当有多个静态代码块时,按照先后顺序****依次执行。
- 优化示例:
- 优化前的代码:
class Person{
private Date birthDate;
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
Date startDate = Date.valueOf("1949");
Date endDate = Date.valueOf("1966");
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}
- 优化后的代码
class Person{
private Date birthDate;
private static Date startDate,endDate;
static{
startDate = Date.valueOf("1949");
endDate = Date.valueOf("1966");
}
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}
4.4、static 修饰的内部类 – 静态内部类
- static 修饰的内部类叫静态内部类。
4.5、import static – 静态导入
- static 导入:用于导入指定类的静态属性和静态方法,这样我们可以直接使用静态属性和静态方法。
- import static java.util.concurrent.TimeUnit.SECONDS;
4.6、用 实例变量 来调用 静态方法、静态变量 的底层原理
- 当使用 实例变量 来调用 静态方法、静态变量 时,底层实际上是通过委托 类 来访问的。
- 字节层面使用:
- invokestatic 指令 来 访问 静态方法 。
- getstatic 指令 来 访问 静态变量。
- 验证的代码:
public class AboutNull {
static int staticFiled = 123;
int ordinaryFiled = 123;
private static void staticMethod(){ System.out.println("静态方法:staticMethod"); }
private void ordinaryMethod(){ System.out.println("普通方法:ordinaryMethod"); }
public static void main(String[] args) {
AboutNull aboutNull = null;
// false
System.out.println(aboutNull instanceof AboutNull);
// 输出: 静态方法:staticMethod
aboutNull.staticMethod();
// 输出: 123
System.out.println(aboutNull.staticFiled);
// 通过上边 实例变量 来调用静态属性和静态方法,没有抛出 NullPointerException 异常。
// 验证:当使用实例变量来调用静态元素时,底层实际上是通过委托类来访问的。
}
}
- 字节码分析:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
// 从常量池中,将 null 压入栈顶
0: aconst_null
// 将栈顶的值(即 null)弹出并赋值给本地变量表第 1 个位置。
1: astore_1
// 获取静态属性 System.out
2: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
// 将局部变量表 1 位置的值(即 null)压入栈顶
5: aload_1
// 检验对象是否是指定的类的实例。如果是将 1 压入栈顶,否则将 0 压入栈顶
6: instanceof #7 // class org/rainlotus/materials/javabase/oop/aboutnull/AboutNull
// 调用实例 PrintStream 的 println 方法。并将栈顶的 0 弹出并输出。
9: invokevirtual #8 // Method java/io/PrintStream.println:(Z)V
// 将局部变量表 1 位置的值(即 null)压入栈顶
12: aload_1
// 弹出栈顶的值(即 null)
13: pop
// 调用静态方法 staticMethod 。此处体现了源代码中使用实例调用(即:aboutNull.staticMethod())静态方法,底层本质上使用 类来调用静态方法。
14: invokestatic #9 // Method staticMethod:()V
17: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
20: aload_1
21: pop
// 调用静态属性 staticFiled 。此处体现了源代码中使用实例调用(即:aboutNull.staticFiled)静态属性,底层本质上使用 类来调用静态属性。
22: getstatic #10 // Field staticFiled:I
25: invokevirtual #11 // Method java/io/PrintStream.println:(I)V
5、final
- final 修饰的变量叫做最终变量,也就是常量。
- final 修饰的方法叫做最终方法。
- final 修饰的类叫做最终类。
1、final 修饰的 类
- final 修饰的 类 不可以 被其它类继承。
- 如:String、Integer、Long 等等。
2、final 修饰的 方法
- final 修饰的 方法 不可以 被子类 重写。
- 如:String、Integer、Long 等等。
3、final 修饰的 变量 – 常量
- Java 成员变量(field),默认是由系统执行初始化,程序员可以不指定初始值。
- final 修饰的成员变量(field),【必须由程序员】进行初始化。
- 假如我们让系统来初始化,变量值要么基本类型的初始值,要么是 null。
- final 又导致这些变量的值不允许被改变,那么这写变量就是去了价值。
- 1、final 修饰的实例变量,可在如下 3 个地方为 final 实例变量赋初始值,最多只能赋值一次。
* 声明时指定初始值、初始化块、构造器中。
- 实例方法 中都给 不能 为 final 实例变量 赋值。
- 2、final 修饰的类变量,可在如下 2 个地方为 final 实例变量赋初始值,最多只能赋值一次。
* 声明时指定初始值、类(静态初始化块)初始化块。
- static 方法、实例方法、实例初始化块 中都给 不能为 final 类变量 赋值。
- 3、final 修饰的局部变量, 只能在声明时,为局部变量指定初始值,最多只能赋值一次。
- 注意:
- final 修饰的 类变量 和 局部变量 中,当数据类型为 基本类型 / String 时:
- 编译时 ,会被执行 编译时常量化(类似:宏替换 )。
- 本质:查找替换。
4、final 修饰成员变量,可以被另类二次赋值 – System.out
package org.rainlotus.materials.javabase.a08_commonclass.system;
import java.io.FileNotFoundException;
import java.io.PrintStream;
public class AboutSystemOut {
public static void main(String[] args) throws FileNotFoundException {
// 编译报错,不能为 final 变量赋值。
// System.out = new PrintStream("d:/print.txt");
//
/** 作用:可以将 【标准输出】重定向 到 指定的输出流。
为何 setOut 方法可以修改 final 变量的值?
原因在于,setOut 方法内部调用了 private static native void setOut0(PrintStream out) 是一个原生方法。
不是在 Java 语言中实现的。
原生方法可以绕过 Java 语言的访问控制机制. 这是一种特殊的解决方法,你自己编写程序时不要模仿这种做法
*/
System.setOut(new PrintStream("d:/print.txt"));
System.out.println(" *");
System.out.println(" ***");
System.out.println(" *****");
System.out.println(" *");
System.out.println(" *");
System.out.println(" *");
}
}
5、空白 final 和 static final
- final 修饰成员变量时,如果在声明时没有赋值 ,则叫做 “空白 final 变量”。
- 空白 final 变量必须在 构造方法 或 静态代码块 中进行初始化。
public class Test {
final int i1 = 1;
final int i2; // 空白 final
public Test() {
i2 = 1;
}
public Test(int x) {
this.i2 = x;
}
}
- static final 修饰的字段只占据一段不能改变的存储空间 。
- 必须在声明的时候进行赋值,否则编译器将不予通过。
6、Java 8 的 <font style="color:#000000;">effectively final</font>
- 从 Java 8 开始,语法进一步简化,即:effectively(有效的) final:
- 没有显式声明 final,只要变量在初始化后未被修改,编译器会自动视为 final 变量。
- 示例:
void example() {
final int x = 10;
new Thread(() -> System.out.println(x)).start();
}
// effectively final : 就是将 上边 x 前边的 final 去掉了而已。
void example() {
// effectively final(未再修改)
int x = 10;
// 合法
new Thread(() -> System.out.println(x)).start();
}
- 如果后边要修改 x 变量的值,就需要将 x 的重新 复制 一份,传递到匿名内部类中。
void example() {
int x = 10;
// 想要将 x 的值,传入子线程中,并且,主线程后边还会修改 x 的值,就必须将 x 的复制一份。
// 即:实现 effectively final(未再修改)
int finalX = x;
new Thread(() -> System.out.println(finalX)).start();
// 再次修改 x 的变量。
x = 20;
}
6、abstract、final、static、private 关键字使用时的注意事项:
【abstract 不能与 final 方法同时出现】。Abstract 与 final 永远互斥。
- 对于 abstract 方法来说,该方法应该有子类重写,final 方法不能被重写。
- 对于 abstract 类来说,该类主要用于派生子类。final 类不能被派生。
【abstract 与 static 不能同时修饰方法】
- static 修饰方法的时候用类来调用方法。
- 用类调用 abstract 方法没有意义,所以会出错。
【abstract 与 private 不能同时修饰方法】
- private 意味着该方法不能被子类来访问,
- abstract 要求方法必须有子类来实现。
final 与 private 没有价值。(但是可以同时出现)
- private 修饰的方法不可能被重写的。
- 因此再用 final 修饰 private 方法没有太大的实际意义。
static 方法中不可以使用 this,super 关键字(静态变量不能访问非静态变量)。
7、static 变量和普通变量的区别?
所属不同
- 静态变量属于类变量,
- 成员变量属于对象变量
内存变量不同
- 静态变量存储在方法区的静态区
- 成员变量存储在内存的堆区
内存出现的时间不同
- 静态是随着类的加载而加载,随着类的消失而消失
- 成员变量随着对象的加载而加载,随着对象的消失而消失
调用不同
- 静态通过类名调用
- 成员变量通过对象调用