09、关键字 this、static、super、final 等

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 修饰的局部变量 只能在声明时,为局部变量指定初始值最多只能赋值一次

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 变量和普通变量的区别?


  • 所属不同

    • 静态变量属于类变量,
    • 成员变量属于对象变量
  • 内存变量不同

    • 静态变量存储在方法区的静态区
    • 成员变量存储在内存的堆区
  • 内存出现的时间不同

    • 静态是随着类的加载而加载,随着类的消失而消失
    • 成员变量随着对象的加载而加载,随着对象的消失而消失
  • 调用不同

    • 静态通过类名调用
    • 成员变量通过对象调用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值