8.1 this关键字

this.属性名

大部分时候,普通方法访问其他方法、成员变量时无须使用 this 前缀,但如果方法里有个局部变量和成员变量同名,但程序又需要在该方法里访问这个被覆盖的成员变量,则必须使用 this 前缀

  1. public class Teacher {
  2. private String name; // 教师名称
  3. private double salary; // 工资
  4. private int age; // 年龄
  5. // 创建构造方法,为上面的3个属性赋初始值
  6. public Teacher(String name,double salary,int age) {
  7. this.name = name; // 设置教师名称
  8. this.salary = salary; // 设置教师工资
  9. this.age = age; // 设置教师年龄
  10. }
  11. }

当一个类的属性(成员变量)名与访问该属性的方法参数名相同时,则需要使用 this 关键字来访问类中的属性,以区分类的属性和方法中的参数

this.方法名

this 关键字最大的作用就是让类中一个方法,访问该类里的另一个方法或实例变量

  1. // 定义一个run()方法,run()方法需要借助jump()方法
  2. public void run() {
  3. // 使用this引用调用run()方法的对象
  4. this.jump(); //这里的this也可省略
  5. System.out.println("正在执行run方法");
  6. }
  7. //Java 允许对象的一个成员直接调用另一个成员,可以省略 this 前缀

:static 修饰的方法中不能使用 this 引用。并且 Java 语法规定,静态成员不能直接访问非静态成员

this()访问构造方法

this( ) 用来访问本类的构造方法(构造方法是类的一种特殊方法,方法名称和类名相同,没有返回值

  1. public class Student {
  2. String name;
  3. // 无参构造方法(没有参数的构造方法)
  4. public Student() {
  5. this("张三");
  6. }
  7. // 有参构造方法
  8. public Student(String name) {
  9. this.name = name;
  10. }
  11. public static void main(String[] args) {
  12. Student stu = new Student();
  13. stu.print();
  14. }
  15. }

注意:

  • this( ) 不能在普通方法中使用,只能写在构造方法中。
  • 在构造方法中使用时,必须是第一条语句。

8.2 super关键字

功能

  • 在子类的构造方法中显式的调用父类构造方法
  • 访问父类的成员方法和变量

super调用父类构造方法

super 关键字可以在子类的构造方法中显式地调用父类的构造方法,基本格式如下:

  1. super(parameter-list);

其中,parameter-list 指定了父类构造方法中的所有参数。super( ) 必须是在子类构造方法的方法体的第一行。

public class Person {
    public Person(String name, int age) {
    }
    public Person(String name, int age, String sex) {
    }
}
public class Student extends Person {
    public Student(String name, int age, String birth) {
        super(name, age); // 调用父类中含有2个参数的构造方法
    }
    public Student(String name, int age, String sex, String birth) {
        super(name, age, sex); // 调用父类中含有3个参数的构造方法
    }
}

编译器会自动在子类构造方法的第一句加上super();来调用父类的无参构造方法,必须写在子类构造方法的第一句,也可以省略不写。通过 super 来调用父类其它构造方法时,只需要把相应的参数传过去

super访问父类成员

当子类的成员变量或方法与父类同名时,可以使用 super 关键字来访问。如果子类重写了父类的某一个方法,即子类和父类有相同的方法定义,但是有不同的方法体,此时,我们可以通过 super 来调用父类里面的这个方法。

使用 super 访问父类中的成员与 this 关键字的使用相似,只不过它引用的是子类的父类,语法格式如下:

super.member

其中,member 是父类中的属性或方法。使用 super 访问父类的属性和方法时不用位于第一行。

super与this的区别

super 关键字的用法:

  • super.父类属性名:调用父类中的属性
  • super.父类方法名:调用父类中的方法
  • super():调用父类的无参构造方法
  • super(参数):调用父类的有参构造方法

this 关键字的用法:

  • this.属性名:表示当前对象的属性
  • this.方法名(参数):表示调用当前对象的方法

8.3 final关键字

  • final 在 Java 中的意思是最终,也可以称为完结器,表示对象是最终形态的,不可改变的意思
  • final 用在变量的前面表示变量的值不可以改变,此时该变量可以被称为常量。
  • final 用在方法的前面表示方法不可以被重写

    子类中如果创建了一个与父类中相同名称、相同返回值类型、相同参数列表的方法,只是方法体中的实现不同,以实现不同于父类的功能,这种方式被称为方法重写,又称为方法覆盖

  • final 用在的前面表示该类不能有子类,即该类不可以被继承

final修饰变量

final 修饰的变量即成为常量,只能赋值一次,但是 final 所修饰局部变量和成员变量有所不同。

  1. final 修饰的局部变量必须使用之前被赋值一次才能使用。
  2. final 修饰的成员变量在声明时没有赋值的叫“空白 final 变量”。空白 final 变量必须在构造方法或静态代码块中初始化
public class FinalDemo {
    void doSomething() {
        // 没有在声明的同时赋值
        final int e;
        // 只能赋值一次
        e = 100;
        System.out.print(e);
        // 声明的同时赋值
        final int f = 200;
    }

final 修饰基本类型变量和引用类型变量的区别

当使用 final 修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变。 但对于引用类型变量而言,它保存的仅仅是一个引用,final 只保证这个引用类型变量所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变。

public class FinalReferenceTest {
    public static void main(String[] args) {
        // final修饰数组变量,iArr是一个引用变量
        final int[] iArr = { 5, 6, 12, 9 };
        System.out.println(Arrays.toString(iArr));
        // 对数组元素进行排序,合法
        Arrays.sort(iArr);
        System.out.println(Arrays.toString(iArr));
        // 对数组元素赋值,合法
        iArr[2] = -8;
        System.out.println(Arrays.toString(iArr));
        // 下面语句对iArr重新赋值,非法
        // iArr = null;
        // final修饰Person变量,p是一个引用变量
        final Person p = new Person(45);
        // 改变Person对象的age实例变量,合法
        p.setAge(23);
        System.out.println(p.getAge());
        // 下面语句对P重新赋值,非法
        // p = null;
    }
}

注意:在使用 final 声明变量时,要求全部的字母大写

final修饰方法

final 修饰的方法不可被重写,如果出于某些原因,不希望子类重写父类的某个方法,则可以使用 final 修饰该方法

public class FinalMethodTest {
    public final void test() {
    }
}
class Sub extends FinalMethodTest {
    // 下面方法定义将出现编译错误,不能重写final方法
    public void test() {
    }
}

public class PrivateFinalMethodTest {
    private final void test() {
    }
}
class Sub extends PrivateFinalMethodTest {
    // 下面的方法定义不会出现问题
    public void test() {
    }
}

public class FinalOverload {
    // final 修饰的方法只是不能被重写,完全可以被重载
    public final void test(){}
    public final void test(String arg){}
}

final修饰类

final 修饰的类不能被继承。当子类继承父类时,将可以访问到父类内部数据,并可通过重写父类方法来改变父类方法的实现细节,这可能导致一些不安全的因素。为了保证某个类不可被继承,则可以使用 final 修饰这个类

8.4 static关键字

概述

静态变量:在类中,使用 static 修饰符修饰的属性(成员变量)称为静态变量,也可以称为类变量,常量称为静态常量

静态方法:方法称为静态方法或类方法

静态成员不依赖于类的特定实例,被类的所有实例共享,就是说 static 修饰的方法或者变量不需要依赖于对象来进行访问,只要这个类被加载,Java 虚拟机就可以根据类名找到它们

格式

类名.静态成员

注意:

  • static 修饰的成员变量和方法,从属于类
  • 普通变量和方法从属于对象。
  • 静态方法不能调用非静态成员,编译会报错

静态变量

类的成员变量可以分为以下两种:

  1. 静态变量(或称为类变量),指被 static 修饰的成员变量。
  2. 实例变量,指没有被 static 修饰的成员变量。

区别

静态变量 实例变量
运行时,Java 虚拟机只为静态变量分配一次内存,在加载类的过程中完成静态变量的内存分配 每创建一个实例,Java 虚拟机就会为实例变量分配一次内存
在类的内部,可以在任何方法内直接访问静态变量 在类的内部,可以在非静态方法中直接访问实例变量。
在其他类中,可以通过类名访问该类中的静态变量 在本类的静态方法或其他类中则需要通过类的实例对象进行访问

静态变量在类中的作用如下:

  • 静态变量可以被类的所有实例共享,因此静态变量可以作为实例之间的共享数据,增加实例之间的交互性。
  • 如果类的所有实例都包含一个相同的常量属性,则可以把这个属性定义为静态常量类型,从而节省内存空间。例如,在类中定义一个静态常量 PI。
public static double PI = 3.14159256;

public class StaticVar {
    public static String str1 = "Hello";
    public static void main(String[] args) {
        String str2 = "World!";
        // 直接访问str1
        String accessVar1 = str1+str2;
        System.out.println("第 1 次访问静态变量,结果为:"+accessVar1);
        // 通过类名访问str1
        String accessVar2 = StaticVar.str1+str2;
        System.out.println("第 2 次访问静态变量,结果为:"+accessVar2);
        // 通过对象svt1访问str1
        StaticVar svt1 = new StaticVar();
        svt1.str1 = svt1.str1+str2;
        String accessVar3 = svt1.str1;
        System.out.println("第3次访向静态变量,结果为:"+accessVar3);
        // 通过对象svt2访问str1
        StaticVar svt2 = new StaticVar();
        String accessVar4 = svt2.str1+str2;
        System.out.println("第 4 次访问静态变量,结果为:"+accessVar4);
    }
}

静态方法

分类

  1. 静态方法(或称为类方法),指被 static 修饰的成员方法。
  2. 实例方法,指没有被 static 修饰的成员方法。

区别

  • 静态方法不需要通过它所属的类的任何实例就可以被调用,因此在静态方法中不能使用 this 关键字,也不能直接访问所属类的实例变量和实例方法,但是可以直接访问所属类的静态变量和静态方法。另外,和 this 关键字一样,super 关键字也与类的特定实例相关,所以在静态方法中也不能使用 super 关键字。
  • 在实例方法中可以直接访问所属类的静态变量、静态方法、实例变量和实例方法。
public class StaticMethod {
    public static int count = 1;    // 定义静态变量count
    public int method1() {    
        // 实例方法method1
        count++;    // 访问静态变量count并赋值
        System.out.println("在静态方法 method1()中的 count="+count);    // 打印count
        return count;
    }
    public static int method2() {    
        // 静态方法method2
        count += count;    // 访问静态变量count并赋值
        System.out.println("在静态方法 method2()中的 count="+count);    // 打印count
        return count;
    }
    public static void PrintCount() {    
        // 静态方法PrintCount
        count += 2;
        System.out.println("在静态方法 PrintCount()中的 count="+count);    // 打印count
    }
    public static void main(String[] args) {
        StaticMethod sft = new StaticMethod();
        // 通过实例对象调用实例方法
        System.out.println("method1() 方法返回值 intro1="+sft.method1());
        // 直接调用静态方法
        System.out.println("method2() 方法返回值 intro1="+method2());
        // 通过类名调用静态方法,打印 count
        StaticMethod.PrintCount();
    }
}

在访问非静态方法时,需要通过实例对象来访问,而在访问静态方法时,可以直接访问,也可以通过类名来访问,还可以通过实例化对象来访问

静态代码块

静态代码块指 Java 类中的 static{ } 代码块,主要用于初始化类,为类的静态变量赋初始值,提升程序性能。

静态代码块的特点如下

  • 静态代码块类似于一个方法,但它不可以存在于任何方法体中。
  • 静态代码块可以置于类中的任何地方,类中可以有多个静态初始化块。
  • Java 虚拟机在加载类时执行静态代码块,所以很多时候会将一些只需要进行一次的初始化操作都放在 static 代码块中进行。
  • 如果类中包含多个静态代码块,则 Java 虚拟机将按它们在类中出现的顺序依次执行它们,每个静态代码块只会被执行一次。
  • 静态代码块与静态方法一样,不能直接访问类的实例变量和实例方法,而需要通过类的实例对象来访问。
public class StaticCode {
    public static int count = 0;
    {
        count++;
        System.out.println("非静态代码块 count=" + count);
    }
    static {
        count++;
        System.out.println("静态代码块1 count=" + count);
    }
    static {
        count++;
        System.out.println("静态代码块2 count=" + count);
    }
    public static void main(String[] args) {
        System.out.println("*************** StaticCode1 执行 ***************");
        StaticCode sct1 = new StaticCode();
        System.out.println("*************** StaticCode2 执行 ***************");
        StaticCode sct2 = new StaticCode();
    }
}
/*
静态代码块1 count=1
静态代码块2 count=2
*************** StaticCode1 执行 ***************
非静态代码块 count=3
*************** StaticCode2 执行 ***************
非静态代码块 count=4
*/