跳转至
内容字体
东观体
上图东观体
OPPO Sans
江城黑体
霞鹜臻楷
代码字体
DejaVu Sans Mono
DejaVu Sans Mono
Google Sans Code
JetBrains Mono
主题切换
返回顶部

Java面向对象与权限修饰符、final关键字、代码块及内部类

约 2052 个字 368 行代码 预计阅读时间 11 分钟

权限修饰符

在Java中,一共有4种访问修饰符,分别是publicprotecteddefaultprivate,下面是访问权限表:

public protected default private
同类 可以 可以 可以 可以
同包不同类 可以 可以 可以 不可以
不同包父子类 可以 可以 不可以 不可以
不同包且非父子类 可以 不可以 不可以 不可以

从上表可以看出来,public具有最大访问权限,private具有最小访问权限

在实际开发中,一般建议按照下面的方式使用权限修饰符(除非有特殊要求):

  1. 成员变量(属性):private修饰(封装思想)
  2. 成员方法:public修饰(便于调用)
  3. 构造方法:public修饰(便于创建对象)

需要注意的是,如果父类成员是被protected修饰,并且子类和父类并不是同包的,那么只有子类对象可以直接访问,其他类对象的类必须与父类同包才可以让该类对象直接访问,例如:

Java
1
2
3
4
5
package com.epsda.advanced.test_extends.extend;

public class Base {
    protected int num = 10;
}
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class Derived03 extends Base {
    public void test() {
        System.out.println(super.num);
    }
    public static void main(String[] args) {
        Derived03 derived03 = new Derived03();
        derived03.test();
        System.out.println(derived03.num);

        Base base = new Base();
        // 无法访问num,因为num被protected修饰,不同包时只有子类对象可以直接访问,其他类对象必须与父类同包
        // System.out.println(base.num); 
    }
}

final关键字

在Java中,final关键字一般表示「最后一个」,具体到指定成员时表现出不同的效果:

  1. final修饰类:被final修饰的类无法被继承,理解为「最后一个类」,例如 public final class A{}
  2. final修饰成员方法:被final修饰的方法无法被重写,理解为「最后一个方法」,例如public final void method(){}
  3. final修饰局部变量:被final修饰的局部变量无法被二次赋值(只要是第一次赋值都允许),理解为「最后一个局部变量赋值」,例如final int a = 1;
  4. final修饰对象引用:被final修饰的对象引用一旦指向了一个对象,将无法指向其他对象,理解为「最后一次对象引用」,例如final A a = new A();
  5. final修饰成员变量:被final修饰的成员变量必须给定初始值,并且无法通过赋值行为再次为成员变量赋值,例如final String name = "zhangsan";

注意:

  1. 因为final修饰类后对应类后该类不可以被继承,修饰成员方法后该方法不可以被重写,所以final不可以与abstract一起使用,因为final关键字要求不被继承或重写,但是abstract要求继承且重写对应方法
  2. final修饰局部变量时表示不能二次赋值,但不代表变量未初始化时不可以赋值,例如下面的代码:
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class Test1 {
    public static void main(String[] args) {
        // final修饰局部变量,初始化时属于第一次赋值
        final int a = 1;
        // a = 2; // 第二次赋值报错

        // final修饰的局部变量
        final int b;
        b = 2; // final修饰的局部变量,未初始化时,此时属于第一次赋值
        // b = 3; // 第二次赋值报错
    }
}

代码块

在Java中,代码块分为静态代码块和非静态代码块(也称实例代码块)

非静态代码块

不使用static修饰的代码块,写法如下:

Java
1
2
3
{
    // 语句
}

非静态代码块在对象中时,会优先于构造函数执行,并且有多少个对象就执行几次,例如下面的代码:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class CodeBlockTest {
    int num;

    public CodeBlockTest(int num) {
        this.num = num;
        System.out.println("我是构造函数");
    }

    {
        System.out.println("我是非静态代码块");
    }
}
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 测试
public class Test1 {
    public static void main(String[] args) {
        CodeBlockTest codeBlockTest1 = new CodeBlockTest(10);
        CodeBlockTest codeBlockTest2 = new CodeBlockTest(10);
    }
}

输出结果
我是非静态代码块
我是构造函数
我是非静态代码块
我是构造函数

需要注意,如果成员变量赋予了初始值并且该成员变量声明在非静态代码块之后,那么此时成员变量的值就是初始值而不是非静态代码块中赋予的值,否则就是非静态代码块中赋予的值:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class CodeBlockTest {

    public CodeBlockTest(int num) {
        this.num = num;
        System.out.println("我是构造函数");
    }

    {
        this.num = 20;
        System.out.println("我是非静态代码块");
    }
    int num = 10;
}

在上面代码中,num值为10而非20,除非int num = 10;在非静态代码块之前

静态代码块

使用static修饰的代码块即为静态代码块,写法如下:

Java
1
2
3
static {
    // 语句
}

静态代码块与非静态代码块一样,在类中时,创建对象会优先于构造方法和非静态代码块执行,但是静态代码块只会在第一次创建对象时执行,例如下面的代码:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class StaticCodeBlockTest {
    int num;

    public StaticCodeBlockTest(int num) {
        this.num = num;
        System.out.println("我是构造函数");
    }

    static {
        System.out.println("我是静态代码块");
    }
}
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 测试
public class Test1 {
    public static void main(String[] args) {
        StaticCodeBlockTest staticCodeBlockTest = new StaticCodeBlockTest(10);
        StaticCodeBlockTest staticCodeBlockTest1 = new StaticCodeBlockTest(10);
    }
}

输出结果
我是静态代码块
我是构造函数
我是构造函数

代码块与继承

前面提到,代码块分为静态代码块和非静态代码块,而静态代码块优先于非静态代码块并且只执行一次,非静态代码块优先于构造方法执行,那么如果现在父类和子类都有静态代码块、非静态代码块和构造方法,具体的执行顺序应该为:

  1. 父类静态代码块
  2. 子类静态代码块
  3. 父类非静态代码块
  4. 父类构造方法
  5. 子类非静态代码块
  6. 子类构造方法

例如下面的代码:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class Base {
    static {
        System.out.println("父类静态代码块");
    }

    {
        System.out.println("父类非静态代码块");
    }

    public Base() {
        System.out.println("父类构造方法");
    }
}
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class Derived extends Base{
    static {
        System.out.println("子类静态代码块");
    }

    {
        System.out.println("子类非静态代码块");
    }

    public Derived() {
        System.out.println("子类构造方法");
    }
}
Java
1
2
3
4
5
public class Test {
    public static void main(String[] args) {
        Derived derived = new Derived();
    }
}

输出结果:

Text Only
1
2
3
4
5
6
父类静态代码块
子类静态代码块
父类非静态代码块
父类构造方法
子类非静态代码块
子类构造方法

内部类

在Java中,内部类分为以下四种:

  1. 非静态成员内部类(也称实例内部类)
  2. 静态成员内部类
  3. 局部成员内部类
  4. 匿名成员内部类

非静态成员内部类

当成员内部类没有被static修饰时,称为非静态成员内部类,写法如下:

Java
1
2
3
class 类名 {
    // 成员
}

非静态成员内部类有以下的特点:

  1. 可以被finalabstract修饰
  2. 内部类成员与普通类基本一致
  3. 可以访问外部类的成员
  4. 外部类如果想访问内部类非私有成员需要通过创建内部类对象才可以访问非私有成员
  5. 当内部类中的方法存在局部变量、内部类成员变量与外部类成员变量同名时,优先访问局部变量,此时需要访问内部类成员变量,需要使用this.成员变量,如果需要访问外部类的成员变量,需要使用外部类.this.外部类成员变量

Note

注意,在JDK16之前,内部类成员(包括成员变量和成员方法)不可以是静态的,如果想要修改当前模块的JDK可以按照下面的方式进行:

File->Project Structure->Modules->选择需要改变JDK的模块->在Source页面改变Language Level17->在Dependencies页面改变Module SDK(SDK即JDK)为17->OK

需要修改回JDK8重复上面的步骤,选择JDK8即可

改变项目JDK版本只需要在选择Module的位置选择Project即可

当需要创建非静态成员内部类对象时,按照下面的方法创建:

Java
1
外部类.内部类 对象名 = new 外部类().new 内部类();

例如下面的代码:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class member_inner_class {
    int a = 1;

    class Inner{
        int a = 2;
        int inner = 3;
        // JDK16后的版本支持静态成员变量
        public static int num = 4;
        void showInner(int a){
            System.out.println(a); // 访问局部变量
            System.out.println(this.a); // 访问当前内部类变量
            System.out.println(member_inner_class.this.a); // 访问外部类变量
        }
    }

    void showOuter(){
        System.out.println(new Inner().inner);
    }
}
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class Test {
    public static void main(String[] args) {
        // 创建内部类对象
        member_inner_class.Inner mi = new member_inner_class().new Inner();
        // 上面一行相当于:
        // 创建外部类对象:member_inner_class m = new member_inner_class();
        // 通过外部类对象进行new:member_inner_class.Inner mi = m.new Inner();

        m.showOuter();
        mi.showInner(5);

        // 访问内部类中的静态成员
        System.out.println(member_inner_class.Inner.num);
        member_inner_class.Inner.method();
    }
}

静态成员内部类

静态成员内部类和非静态成员内部类基本一致,不同的是:静态内部类不可以访问外部类中的非静态成员,创建静态成员内部类方式如下:

Java
1
2
3
static class 类名 {
    // 成员
}

创建静态成员内部类对象方式如下:

Java
1
外部类.内部类 对象名 = new 外部类.内部类()

例如下面的代码:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class static_member_inner_class {
    int a = 1;
    static int b = 2;

    static class Inner{
        int a = 3;
        int b = 4;
        // 静态内部类在JDK8可以创建静态成员变量
        static int num = 1;

        void show(){
            // System.out.println(static_member_inner_class.a); 静态内部类无法访问父类的非静态成员变量
            System.out.println(static_member_inner_class.b); // 静态内部类可以访问父类的静态成员变量

            System.out.println(a); // 静态内部类可以访问自己的成员变量
            System.out.println(b);
            System.out.println(num);
        }
    }
}
Java
1
2
3
4
5
6
7
8
9
public class Test {
    public static void main(String[] args) {
        // 创建外部类对象
        static_member_inner_class sm = new static_member_inner_class();
        // 创建内部类对象
        static_member_inner_class.Inner inner = new static_member_inner_class.Inner();
        inner.show();
    }
}

局部内部类

定义与基本使用

在Java中,局部内部类可以理解为局部变量,定义在方法中或者代码块中,定义方式如下:

Java
1
2
3
4
5
方法 {
    class 类名 {

    }
}

Note

注意:局部内部类创建对象只能在内部类所在的外部类创建

例如下面的代码:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class partial_inner_class {
    int num = 1;
    public void method() {
        class Inner {
            int num = 2;
            public void showInner(int num) {
                System.out.println(num); // 访问局部变量
                System.out.println(this.num); // 访问内部类的成员变量
                System.out.println(partial_inner_class.this.num); // 访问外部类的成员变量
            }
        }

        // 创建内部类匿名对象并在外部类方法中调用内部类方法
        new Inner().showInner(10);
    }
}
Java
1
2
3
4
5
6
7
8
9
// 测试
public class Test {
    public static void main(String[] args) {
        // 创建外部类对象
        partial_inner_class p = new partial_inner_class();
        // 调用外部类的方法
        p.method();
    }
}

接口类型/抽象类型作为方法参数传递和返回

Note

下面的代码以接口类型为例,抽象类型基本一致

  1. 接口/抽象类型作为方法参数传递时,传递的是对应实现类的对象

    Java
    1
    2
    3
    4
    // 接口
    public interface Test_interface {
        void show();
    }
    
    Java
    1
    2
    3
    4
    5
    6
    7
    // 实现类
    public class Test_interfaceImpl implements Test_interface{
        @Override
        public void show() {
            System.out.println("Test_interface接口实现类");
        }
    }
    
    Java
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    // 测试
    public class Test {
        public static void main(String[] args) {
            Test_interfaceImpl testInterface = new Test_interfaceImpl();
            method(testInterface); // 传递的是实现类的对象
        }
    
        public static void method(Test_interface ti){
            ti.show(); // 多态,向上转型
        }
    }
    
  2. 接口/抽象类型作为方法参数返回时,返回的是对应实现类的对象

    Java
    1
    2
    3
    4
    // 接口
    public interface Test_interface {
        void show();
    }
    
    Java
    1
    2
    3
    4
    5
    6
    7
    // 实现类
    public class Test_interfaceImpl implements Test_interface{
        @Override
        public void show() {
            System.out.println("Test_interface接口实现类");
        }
    }
    
    Java
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    // 测试
    public class Test {
        public static void main(String[] args) {
            Test_interface testInterface = getTestInterface();
        }
    
        public static Test_interface getTestInterface(){
            return new Test_interfaceImpl();
        }
    }
    

局部类实现如下:

Java
1
2
3
4
// 接口
public interface Test_interface {
    void show();
}
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 测试
public class Test {
    public static void main(String[] args) {
        Test_interface testInterface = getTestInterface();
    }

    public static Test_interface getTestInterface() {
        class Test_interfaceImpl_1 implements Test_interface{
            @Override
            public void show() {
                System.out.println("局部内部类重写");
            }
        }

        return new Test_interfaceImpl_1(); // 返回实现类(局部内部类)的对象,多态,向上转型
    }
}

匿名内部类

定义匿名内部类

匿名内部类,即没有类名的内部类,可以替换前面的接口/抽象实现类,此时创建出的内部类即为对应接口/抽象类的实现类,创建方式如下:

Java
1
2
3
new 接口类名/抽象类名() {
    // 重写抽象类/接口类方法
};

常见的使用方式有:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 调用接口/抽象类重写后的方法
new 接口类名/抽象类名() {
    // 重写抽象类/接口类方法
}.重写后的方法名();

接口类/抽象类名 对象名 = new 接口类名/抽象类名 {
    // 重写抽象类/接口类方法
};

对象名.重写的方法名();

匿名内部类基本使用

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test {
    public static void main(String[] args) {
        // 直接调用匿名内部类重写的方法
        new Test_interface() {
            @Override
            public void show() {
                System.out.println("匿名内部类实现Test_interface接口");
            }
        }.show();

        // 使用对象调用匿名内部类重写的方法
        Test_interface ti1 = new  Test_interface() {
            @Override
            public void show() {
                System.out.println("匿名内部类实现Test_interface接口");
            }
        };

        ti1.show();
    }
}

匿名内部类修改接口/抽象类型作为方法参数传递

Java
1
2
3
4
// 接口
public interface Test_interface {
    void show();
}
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 测试
public class Test {
    public static void main(String[] args) {
        method(new Test_interface() {
            @Override
            public void show() {
                System.out.println("匿名内部类实现Test_interface接口");
            }
        });
    }

    public static void method(Test_interface ti) {// 实现类(匿名内部类)作为实参传递,多态,向上转型
        ti.show();
    }
}

匿名内部类修改接口/抽象类型作为返回值返回

Java
1
2
3
4
// 接口
public interface Test_interface {
    void show();
}
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 测试
public class Test {
    public static void main(String[] args) {
        Test_interface ti = method01();
    }

    public static Test_interface method01() {
        return new Test_interface() {
            @Override
            public void show() {
                System.out.println("匿名内部类实现Test_interface接口");
            }
        }; // 返回实现类(匿名内部类)对象,多态,向上转型
    }
}