Java速通3:final、嵌套类、抽象类

115 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

1.final:

  1. 被 final 修饰的类:不能被继承。
  2. 被 final 修饰的方法:不能被重写。
  3. 被 final 修饰的变量:变量变为常量,只能进行一次赋值。

这是非常好的一个东西。可以避免我们造出混乱的继承结构。比如Java中的String类就是final的,它是不能被继承的。
final不能修饰抽象类或接口,因为定义抽象类或接口的目的就是为了让其他类继承或实现。

1.1.常量:

public final double PI = 3.14; // 只是不能被再次赋值,也不算是常量
public static final double PI = 3.14;  // 公共常量(被static修饰,内存中只有此一份)
private static final double PI = 3.14; // 私有常量

如果将基本类型或字符串定义为常量,且在编译时就能确定值。
编译器会使用常量值替代各处常量名(类似 c 语言的宏替换)
上面的例子是编译时能确定值的,下面举例编译时不能确定值的情况。

static final int PI = getPI();

小测试:

public static void main(String args[]) {

    String a = "go die, ";
    final String b = "final";

    String c = "go die, " + b;    // go die, final
    String d = a + b;             // go die, final
    String e = "go die, final";

    System.out.println(e == c);   // true,比较两个变量是否指向同一个对象
    System.out.println(e == d);   // false

    // 如果b不加final,那么结果就是false、false
}

解释:
在编译阶段,变量c其实已经是"go die, final"了,所以c和e其实是同一个对象。
但是d却是运行时生成的,并不引用常量池中的"go die, final"这个字符串,所以e和d并不是同一个对象。
究其根本原因,还是在于b 在编译阶段就已经被当作常量“final” 去做下面的编译了。

2.嵌套类:定义在一个类中的类

//最外层的外部类,又称顶级类
public class OuterClass{

    // 静态嵌套类
    static class StaticNestedClass{

    }

    // 内部类(非静态嵌套类)
    class InnerClass{

    }
}

2.1.内部类:没有被 static 修饰的嵌套类。 (非静态内部类)

一般情况下,类与类之间相互独立。内部类就是打破这种独立,让一个类成为另一个类的内部成员,和成员变量、成员方法同等级别。
1.非静态内部类跟实例变量、实例方法一样,都必须通过外部类的对象才能调用。

public class Persion{
    //Persion对象不实例化 这个实例变量就没用
    private int age; 

    public int getAge(){
        return age;
    }

    //Persion对象不实例化 这个内部类就没用(没法创建)
    class Hand{  

    }
}

Person person = new Persion();
Hand hand = person.new Hand();  //必须通过对象的引用去创建

2.内部类不能定义除编译时常量以外的任何 static 成员。

public class OuterClass{

    class InnerClass{
        // 不能定义静态成员
        public static int a = 10; 

        //可定义编译时常量
        private static final int PI = 3.14; 
    }
}

3.内部类可以直接访问外部类中的所有成员(即使被声明为 private)
4.外部类可以直接访问内部类实例的成员变量、方法(即使被声明为 private)
5.内部类细节

public class Outer{
    private int x = 1;

    public class Inner{
        private int x = 2;

        public void show(){
            print(x);             // 2
            print(this.x);        // 2
            print(Outer.this.x);  // 1
        }
    }
}

6.为什么要使用内部类:采用内部类技术,可以隐藏细节和内部结构,封装性更好,让程序的结构更加合理。

2.2.静态嵌套类(静态内部类):

1.静态嵌套类在行为上是个顶级类,只是定义的代码写在了一个类中。

public class Person{
    public static class Car{

    }
}

//调用(Car与Person无任何关系,只是恰巧代码在Person类里面而已)
Person.Car car = new Persion.Car();

2.静态嵌套类可直接访问外部类除实例变量、实例方法外的其他成员(即使被声明为 private)

// 从类加载角度分析以下代码原因
public class Persion{
    private int age;
    private static int count = 0;

    public static class Car{
        public void run(){
            print(age); //错误:不可直接访问外部类的实例变量、实例方法
            Persion.count = 1;  //可以不通过setter方法就能访问其外部类的private属性
        }
    }
}

什么情况下使用嵌套类?

  1. 如果类 A 只在类 C 内部会被用到,可以考虑将类 A 嵌套到类 C 中。
  2. 如果类 A 需要经常访问类 C 的非公共成员,可以考虑将类 A 嵌套到类 C 中。
  3. 如果需要经常访问非公共的实例成员,设计成内部类,否则设计成静态嵌套类。
  4. 所以设计内部类的时候,能静态就静态。

2.3.局部类:Local Class(局部内部类)

局部类:定义在代码块中的类(可以定义在方法中、for 循环中、if 语句中等)

  1. 局部类不能定义除编译时常量以外的任何 static 成员。(因为代码块里面的东西作用范围仅在代码块中有效)
  2. 局部类只能访问 final 或 有效 final 的局部变量。
  3. 局部类可直接访问外部类中的所有成员(即使被声明为 peivate)
public class Persion{

    private int age;

    public void test(){

        //java8开始,若局部变量没有被第二次赋值,就认定为是有效final
        int num = 10;

        class A{
            // static int age = 1; //不可定义
            void a(){
                print(num);
                print(age);
            }
        }
    }
}

3.抽象类(Abstract Class):被 abstract 修饰的类

在面向对象的概念中,所有的对象都是通过类来描绘的,但是并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

  1. 可以定义为抽象方法
  2. 可自定义构造方法(可通过 new 来创建)
  3. 子类必须实现抽象父类中的所有抽象方法(除非子类也是抽象类)
  4. 也可像普通类一样定义成员变量、常量、非抽象方法、嵌套类型、初始化块等
    即抽象类中可完全不定义抽象方法。

抽象类意义:

  1. 用于拓展对象的行为功能:即重用 + 扩展
public abstract class Abs{

    public int age;

    public Abs(){

    }

    public void test1(){

    }

    public abstract void test2();
    public abstract void test3();
}



public class Test extends Abs{

    //子类必须实现抽象父类中的所有抽象方法
    public void test2(){

    }

    public void test3(){

    }

    // 子类的构造方法,必须要先调用父类的构造方法
    // 所以抽象类虽然不能实例化,但其构造方法的意义是供子类调用。
    public Test(){

    }
}

抽象类使用场景:

  1. 抽取子类的公共实现到抽象父类中,并且把要求子类必须要单独实现的方法定义成抽象方法。