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("小白"); // 可以修改
}
}
修饰成员变量
- 由于成员变量具有默认值,所以用了final之后必须手动赋值,不会再给默认值了
- 对于final的成员变量,要么使用直接赋值,要么通过构造方法赋值。二者选其一
- 必须保证类当中所有重载的构造方法,都最终会对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();
// 匿名对象无法调用第二次方法,若要再调用,需要再创建一个匿名内部类的匿名对象
}
}