Java基础|1-06-final_权限_内部类 @面向对象篇

写在前面:
此系列文是笔者在学习黑马的Java系列课程的过程中,参考相关课件、视频讲解、课程代码,并结合一些文档、思维导图及个人理解,对所学内容做的阶段性梳理与总结。

  • 写于:2021年1月30日 ~ 31日
  • 内容:Java后端系列笔记006(Java基础-final_权限_内部类)
  • 全文:5665字

一、final关键字

1. 1 概述

概念

  • final是Java提供的一个关键字,代表最终、不可改变

用法

final可修饰 说明
被final修饰的类,不能有任何的子类。即:不能被继承
方法 被final修饰的方法就是最终方法,不能被覆盖重写
变量(局部变量/成员变量) 若使用final修饰,则该变量是不可变的。即:不能被重新赋值

1. 2 使用方式

修饰类

  • 格式:
final class 类名称 {
   
    // ...
}
  • 注意:一个类如果是final的,那么其中所有的成员方法都无法进行覆盖重写(因为没儿子)
  • 代码演示:(被final修饰的类,不能有任何的子类)
// 当前类 (默认继承Object类)
public final class MyClass /*extends Object*/ {
   
    public void method() {
   
        System.out.println("方法执行!");
    }
}

// 错误!不能使用一个final类来作为父类
public class MySubClass extends MyClass{
   
}

修饰方法

  • 格式:
修饰符 final 返回值类型 方法名称(参数列表) {
   
    // 方法体
}
  • 注意:对于类、方法来说,abstract关键字和final关键字不能同时使用,因为矛盾
  • 代码演示:(被final修饰的方法就是最终方法,不能被覆盖重写)
// 父类
public abstract class Fu {
   
    public final void method() {
   
        System.out.println("父类方法执行!");
    }

	//abstract和final不能同时使用
    public abstract /*final*/ void methodAbs() ; 
}
// 子类 
public class Zi extends Fu {
   
    @Override
    public void methodAbs() {
    }

    // 编译报错!不能覆盖重写父类当中final的方法
// @Override
// public void method() {
   
// System.out.println("子类覆盖重写父类的方法!");
// }
}

修饰局部变量

  • 对于基本类型:变量中的数据不可改变
  • 对于引用类型:变量中的地址值不可改变(但不影响对象内部的成员变量值的修改)
  • 代码演示:(使用final修饰的局部变量,不可更改)
public class Demo01Final {
   
    public static void main(String[] args) {
   
        // final修饰:“一次赋值,终生不变”
        final int num1 = 200;
// num1 = 250; // 第二次赋值,错误!不能被重新赋值!
// num1 = 200; // 第三次赋值,错误!不能被重新赋值!

        // 正确写法!只要保证有唯一一次赋值即可
        final int num2;
        num2 = 30;
        System.out.println("============");

        // 对于【引用类型】来说,不可变说的是:变量当中的地址值不可改变
        // 创建 User 对象
		final User u = new User();
		// 创建 另一个 User对象
// u = new User(); // 报错,指向了新的对象,地址值改变。
		// 调用setName方法
		u.setName("小白"); // 可以修改
	}
}

修饰成员变量

  1. 由于成员变量具有默认值,所以用了final之后必须手动赋值,不会再给默认值了
  2. 对于final的成员变量,要么使用直接赋值,要么通过构造方法赋值。二者选其一
  3. 必须保证类当中所有重载的构造方法,都最终会对final的成员变量进行赋值
public class Person {
   
	// 初始化赋值方式一: 直接赋值(两种方式只能选一种)
    private final String NAME/* = "小白"*/; 
    
	// 赋值方式二: 构造方法赋值
    public Person() {
   
        NAME = "小白";
    }

    public Person(String name) {
   
        this.NAME = name;
    }

    public String getName() {
   
        return NAME;
    }
    
    // 以下报错: Cannot assign a value to final variable 'NAME'
// public void setName(String name) {
   
// this.NAME = name;
// }
}

小贴士:final修饰的变量是一个常量,只能赋值一次(常量名称的书写规范:所有字母都大写)


二、权限修饰符

2. 1 访问能力

Java中的四种权限修饰符:

public protected (default) private
同一个类(我自己)
同一个包(我邻居)
不同包子类(我儿子)
不同包非子类(陌生人)
  • 注意:(default)并不是关键字“default”,而是根本不写

不同权限的访问能力:public 公共的 > protected 受保护的 > (default) 默认的 > private 私有的

2. 2 使用建议

编写代码时,若无特殊的考虑,建议:

  • 成员变量使用private ,隐藏细节
  • 构造方法使用public ,方便创建对象
  • 成员方法使用public ,方便调用方法

三、内部类

3. 1 概述

可以将一个类的定义放在另一个类的定义内部,这就是内部类。 ——《Java编程思想》

概念

  • 如果一个事物的内部包含另一个事物,那么这就是一个类(外部类 )内部包含另一个类(内部类
  • 内部类最大的特点:可以直接访问私有属性,并且可以体现类与类间的包含关系
    • 例如:身体和心脏的关系。又如:电脑和显示屏的关系

广义的分类

分类 定义在外部类中的什么位置? 说明
成员内部类 成员位置(类中方法外) 无static修饰
静态内部类 成员位置 有static修饰
局部内部类 局部位置(类中方法内) 有类名
匿名内部类 局部位置 无类名

小贴士:类的五大成员——属性、方法、构造器、代码块、内部类

3. 2 成员内部类

定义格式(类中方法外)

修饰符 class 外部类名称 {
   
    修饰符 class 内部类名称 {
   
        // ...
    }
    // ...
}

访问特点

  • 内部类访问外部类成员:直接访问(即:内用外,随意访问)
  • 外部类访问内部类成员:必须建立内部类对象(即:外用内,需内部类对象)

使用方式

  • 间接方式:在外部类的方法中,使用内部类 → main只是调用外部类的方法
  • 直接方式,公式:类名称 对象名 = new 类名称();
    外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();

代码演示:

  • 定义类
public class Body {
    // 外部类
    public class Heart {
    // 成员内部类 (类中方法外)
        // 内部类的方法
        public void beat() {
   
            System.out.println("心脏跳动!");
            System.out.println("我叫:" + name); // 正确写法!(内用外,随意访问)
        }
    }

    // 外部类的成员变量
    private String name;

    // 外部类的方法
    public void methodBody() {
   
        System.out.println("外部类的方法");
        new Heart().beat();// (外用内,需要内部类对象)
    }

    //省略 Getter and Setter
}
  • 定义测试类
public class Demo01InnerClass {
   
    public static void main(String[] args) {
   
    	// 方式一: 间接使用
        Body body = new Body(); // 外部类的对象
        // 通过外部类的对象,调用外部类的方法,里面间接在使用内部类Heart
        body.methodBody();
        System.out.println("=====================");

        // 方式二: 直接使用 (按公式写)
        Body.Heart heart = new Body().new Heart();
        heart.beat();
    }
}
  • 执行结果
外部类的方法
心脏跳动!
我叫:null
=====================
心脏跳动!
我叫:null

重名问题

  • 若出现了重名现象,访问外部类成员的格式是:外部类名称.this.外部类成员变量名
class Outer {
    // 外部类
    int num = 10; // 外部类的成员变量

    public class Inner /*extends Object*/ {
    // 内部类
    
        int num = 20; // 内部类的成员变量

        public void methodInner() {
   
            int num = 30; // 内部类方法的局部变量
            System.out.println(num); //30 局部变量,就近原则
            System.out.println(this.num); //20 内部类的成员变量
            System.out.println(Outer.this.num); //10 外部类的成员变量
        }
    }
}


// 测试类
public class Demo02InnerClass {
   
    public static void main(String[] args) {
   
        // 外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();
        Outer.Inner obj = new Outer().new Inner();
        obj.methodInner();
    }
}
/* 执行结果: 30 20 10 */

编译后的文件

  • 由于内部类仍然是一个独立的类,所以编译后内部类会被编译成独立的.class文件
  • 区别:前面冠以 外部类的类名 和 $ 符号(外部类名$内部类名.class

3. 3 局部内部类

定义格式(类中方法内)

修饰符 class 外部类名称 {
   
    修饰符 返回值类型 外部类方法名称(参数列表) {
   
        class 局部内部类名称 {
   
            // ...
        }
    }
}

访问特点

  • 只有当前所属的方法才能使用它,出了这个方法就不能用了(即:在作用域内使用)

访问局部变量 - 使用final

  • 若 局部内部类 希望访问所在方法的局部变量,则该局部变量必须是有效final
    • 在Java 7 及更早版本,final必须手动添加
    • 从Java 8+开始的版本,final不用手动添加(隐式加入)
  • 原因(★ 防止局部变量访问范围扩大)
    • new出来的对象在堆内存当中
    • 但局部变量是跟着方法走的,在栈内存当中
    • 方法运行结束之后,立刻出栈,局部变量就会立刻消失
    • 但new出来的对象会在堆当中持续存在,直到垃圾回收消失
  • 即:解决生命周期不同(对象 > 局部变量)的问题,使对象可以一直访问复制到的常量(不变的局部变量),而不是可改变的量

补充 - 类的权限修饰符

  • 权限大小:public > protected > (default) > private
  • 定义一个类的时候,权限修饰符规则:
    ① 外部类:public / (default)
    ② 成员内部类:public / protected / (default) / private
    ③ 局部内部类:什么都不能写

3. 4 匿名内部类 ★

使用原因

  • 如果接口的实现类(或者是父类的子类)只需要使用唯一的一次,那么这种情况下就可以省略掉该类的定义,而改为使用 匿名内部类
  • 概述:是实现接口的一种简便写法

定义格式

接口名称 对象名 = new 接口名称() {
   
    // 覆盖重写所有抽象方法
};

/* 对格式“new 接口名称() {...}”进行解析: 1. new代表创建对象的动作 2. 接口名称就是匿名内部类需要实现哪个接口 3. {...}这才是匿名内部类的内容 */

注意事项(匿名内部类和匿名对象不是一回事)

代码演示:

  • 定义接口
public interface MyInterface {
   
    void method1(); // 抽象方法
}
  • 定义测试类 - 通过实现类使用接口
/* 使用一个接口,常规操作: 1. 定义子类 2. 重写接口中的方法 3. 创建子类对象 4. 调用重写后的方法 */
class MyInterfaceImpl implements MyInterface {
   
    @Override
    public void method1() {
   
        System.out.println("实现类覆盖重写了方法!111");
    }
}

public class DemoMain1 {
   
    public static void main(String[] args) {
   
        MyInterface obj = new MyInterfaceImpl();
        obj.method1();

		// 错误!'MyInterface' is abstract; cannot be instantiated
// MyInterface some = new MyInterface(); 
   }
}
  • 定义测试类 - 通过匿名内部类使用接口
/* 通过 匿名内部类 / 匿名对象 简化操作 */
public class DemoMain2 {
   
    public static void main(String[] args) {
   
    
        // 使用匿名内部类,但不是匿名对象,对象名称就叫objA
        MyInterface objA = new MyInterface() {
   
            @Override
            public void method1() {
   
                System.out.println("匿名内部类实现了方法!A");
            }
        };
        objA.method1();
        System.out.println("=================");

        // 使用了匿名内部类,而且省略了对象名称,也是匿名对象
        new MyInterface() {
   
            @Override
            public void method1() {
   
                System.out.println("匿名内部类实现了方法!B");
            }
        }.method1();
        
        // 匿名对象无法调用第二次方法,若要再调用,需要再创建一个匿名内部类的匿名对象
    }
}

全部评论

相关推荐

评论
点赞
收藏
分享
牛客网
牛客企业服务