三个修饰符

一、abstract

1.1 abstract修饰类

抽象的。

  • 因为父类不应该直接创建对象,但是创建也不会提升错误,为了限制这种错误行为的发生,可以给类加一个修饰符abstract,表示该类不能创建对象。
  1. // 限制类的对象创建
  2. public abstract class Animal {
  3. }
  4. public class Dog extends Animal{
  5. }
  6. public class TestMain {
  7. public static void main(String[] args) {
  8. // Animal animal = new Animal(); //会报错,因为abstract修饰类时不能创建对象
  9. Animal animal = new Dog();
  10. }
  11. }

1.2 abstract修饰方法
  • 父类的方法,如果每个子类都会重写该方法,意味着该方法在父类中的实现根本不会被调用,此时可以删掉父类的方法实现,只留下方法的声明,并加上abstract关键字,表示该方法为抽象方法。

注意:

一个抽象类中可以没有抽象方法。

但是有抽象方法的类必须是抽象类。

在继承一个抽象类时,如果该类中有抽象方法,其子类要么也定义成抽象类,要么实现所有的抽象方法。

public abstract class Animal {
    public abstract void eat();
}

public class Dog extends Animal{
    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }
}

二、static

2.1 概念

静态的。类的。

2.2 修饰属性

类属性,该类所有的对象共享的属性。不需要创建对象就可以访问,应该使用类名来访问类属性。

注意:实例属性是对象创建时才分配空间,每个对象都有独立的空间。类属性是整个类共享的一个空间,在方法区。所以所有的对象对该属性进行操作会相互影响。应该使用类名操作。

public class Student {
    public String name; // 实例属性,每个对象特有
    public static String classes; // 类属性,对象共享一个属性
}

public class TestMain {
    public static void main(String[] args) {
        Student student1 = new Student();
        student1.name = "张三";
        Student.classes = "一班";

        Student student2 = new Student();
        student2.name = "李四";
        Student.classes = "二班"; // 对类属性进行修改,会影响所有的对象

        Student student3 = new Student();

        System.out.println(student1.name);
        System.out.println(Student.classes);
        System.out.println(student2.name);
        System.out.println(Student.classes);
        System.out.println(student3.name);
        System.out.println(Student.classes);
    }
}

2.3 修饰方法

当static修饰方法时,直接通过类名就可以访问该方法,不需要创建对象。

public class Student {
    public static void m1() {

    }
}
public class TestMain {
    public static void main(String[] args) {
        Student.m1();
    }
}

2.4 类方法中this和super的要求

在类方法中,只能访问类属性和其他类方法,不能访问与实例属性和实例方法,也不能使用this和super

public class Student {
    public String name; // 实例属性,每个对象特有
    public static String classes; // 类属性,对象共享一个属性

    public void say() {
        System.out.println(name);
    }

    public static void m1() {
        // 在类方法中,只能访问类属性和其他类方法,不能访问与实例属性和实例方法,也不能使用this和super
        System.out.println(classes);
//        System.out.println(name); // name需要对象才能访问
        m2();
//        say(); // say方法需要对象才能访问
//        System.out.println(this.name); // this表示当前对象
//        System.out.println(super.toString()); // super表示父类对象
    }

    public static void m2() {
    }
}

2.5 修饰代码块

代码块,是指在类中定义的一段代码(不在方法中)。

直接定义的代码叫做动态代码块,使用static定义的代码叫做静态代码块。

public class Employee {
    {
        System.out.println("动态代码块");
    }

    static {
        System.out.println("静态代码块");
    }
}

代码块一般用来初始化属性。动态代码块在创建对象时执行,静态代码块在加载类时执行。

public class Employee {
    public static int count;
    public String name;

    // 在创建对象时执行,一般用来给属性赋值
    {
        Random random = new Random();
        int num = random.nextInt(100);
        if(num % 2 == 0) {
            name = "张三";
        }else {
            name = "李四";
        }
        System.out.println("动态代码块");

    }

    // 在加载类时执行,一般用来给类属性赋值,或者加载系统资源
    static {
        Random random = new Random();
        count = random.nextInt(100);
        System.out.println("静态代码块");
    }
}

注意:静态代码块在类加载时执行,仅执行一次(只会加载一次),动态代码块每次创建对象时都会执行。

2.6 对象创建过程中的顺序

过程分析:

  • 加载父类,给父类的静态属性赋值,并执行父类的静态代码块。

  • 加载子类,给子类的静态属性赋值,并执行子类的静态代码块。

  • 父类的对象操作

    • 给属性赋值
    • 执行动态代码块
    • 执行构造方法
  • 子类的对象操作

    • 给属性赋值
    • 执行动态代码块
    • 执行构造方法
public class Animal {
    public String a1 = "Animal内的实例属性";
    public static String b1 = "Animal内的静态属性";

    public Animal() {
        System.out.println("Animal开始创建对象");
    }

    {
        System.out.println(a1);
        System.out.println("Animal内的动态代码块");
    }

    static {
        System.out.println(b1);
        System.out.println("Animal内的静态代码块");
    }
}
public class Dog extends Animal{
    public String d1 = "Dog内的实例属性";
    public static String c1 = "Dog内的静态属性";

    public Dog() {
        System.out.println("Dog开始创建对象");
    }

    {
        System.out.println(d1);
        System.out.println("Dog内的动态代码块");
    }

    static {
        System.out.println(c1);
        System.out.println("Dog内的静态代码块");
    }
}
public class TestDog {
    public static void main(String[] args) {
        new Dog();
    }
}
运行结果:
Animal内的静态属性
Animal内的静态代码块
Dog内的静态属性
Dog内的静态代码块
Animal内的实例属性
Animal内的动态代码块
Animal开始创建对象
Dog内的实例属性
Dog内的动态代码块
Dog开始创建对象

三、final

final即最终的。

3.1 修饰类

表示该类不可被继承。

// 表示类不能被继承
public final class Father {
}

// 下面的代码会报错
public class Son extends Father{
}

3.2 修饰方法

表示该方法不可被重写。

public class Father {
    public final void m1() {
    }
}

public class Son extends Father{
    // 以下代码会报错,因为final的方法不能被重写
    @Override
    public void m1() {
        System.out.println("son");
    }
}

经典面试题:

final修饰方法时能否与abstract一起使用?

final修饰类时能否与abstract一起使用?

3.3 修饰属性、变量

表示该属性或变量不能再次赋值(修改)。

注意:如果一个实例属性使用了final关键字,必须在创建完对象后就有值,所以可以在属性定义时直接赋值,也可以在构造方法中赋值,也可以在动态代码块中赋值,但是赋值后就不能改变。

public class Son extends Father{
    public final String name; // 可以直接赋值public final String name = "张三";

    public Son() {
        name = "李四"; // 也可在构造方法中赋值
    }

    //{
    //    name = "李四"; // 也可以在动态代码块中赋值
    //}
}

如果静态属性是final的,需要在加载类后有值,可以在声明时赋值,也可以在静态代码块中赋值。但是赋值后就不能改变。

public  class Father {
    public final static int count; // 可以直接赋值public final static int count = 5;

    static {
        count = 10; // 也可以在静态代码块块中赋值
    }
}

修饰普通变量,只要在使用前赋值即可,但是赋值后不能改变。

public  class Father {    
    public void m1() {
        final int n;
        n = 5;
//        n = 10; // 会报错    
    }
}

注意:由于final修饰的变量或属性都具备有赋值后就不能改变的特征,所以统称为常量。常量的定义规则是全大写,单词之间用下划线拼接。

public class MyClass{
    public static final int PAGE_SIZE = 10; // 页面大小
}

注意:当final修饰的变量引用一个对象时,是指变量中保存的对象的地址不能改变,而不是对象的属性值不能变,如果是数组,是指数组的地址不能变,不是数组的值不能变。

public class TestMain {
    public static void main(String[] args) {
        // arr中值?是数组分配空间对应的地址,所以是指地址不能变
        final int [] arr = new int[5]; 
        arr[0] = 5; // 而数组中成员赋值是改变的变量的值,而不是数组的地址
        arr[0] = 6;
//        arr = new int[10]; // 此行代码有错,地址不能改变

        // dog中的值是指Dog对象的地址,所以是指地址不能变
        final Dog dog = new Dog();
        dog.age = 1;// 而给属性赋值只是改变的该对象的属性值,地址没有改变
        dog.age = 2;
//        dog = new Dog();// 此行代码有错,地址不能改变
    }

    // 数组扩容
    public void m1(final int [] arr) {
//        arr = new int[arr.length + 1]; // 此行代码报错,原因是final修饰的引用变量中保存的地址不能改变
    }
}