黑马程序员技术交流社区
标题: 【石家庄校区】基础班day11-final,权限,内部类 [打印本页]
作者: xiekai_sjz 时间: 2018-9-9 10:19
标题: 【石家庄校区】基础班day11-final,权限,内部类
本帖最后由 xiekai_sjz 于 2018-9-9 10:30 编辑
day11 final, 权限, 内部类 今天我们来学习面向对象中其他的相关知识。首先是final关键字的使用,然后是java中权限修饰符,最后是内部类的分类和应用,最后我们会优化我们之前的发红包案例。
以下是今天的学习目标:
- 描述final修饰的类的特点
- 描述final修饰的方法的特点
- 能够说出权限修饰符作用范围
- 说出内部类的概念
- 能够理解引用类型作为成员变量
- 能够理解引用类型作为方法参数
- 能够理解引用类型作为方法返回值类型
以下是今天的详细笔记:
final/权限修饰符final关键字概念与四种用法final: 关键字, 最终的意思
4种使用方式:
1. 修饰一个类
2. 修饰一个方法
3. 修饰一个局部变量
4. 修饰一个成员变量
final关键字用于修饰类
final修饰类的作用:
该类不能被继承
// final类
public final class 类名称 {
// ...
}
final关键字用于修饰成员方法
final修饰成员方法的作用:
该方法不能被覆盖重写
// final方法
public final void method() {
}
final不能与abstract同时使用
final修饰的类不能被继承, 而abstract修饰的类需要被继承
final修饰的方法不能被重写, 而abstract修饰的方法需要被重写
final关键字用于修饰局部变量
final修饰局部变量的作用:
自定义常量, 一旦赋值不能修改
注意:
对于基本数据类型, 不可变指变量中的"值"不可改变
对于引用数据类型, 不可变指变量中的"地址值"不可改变
补充:
[Java] 纯文本查看 复制代码
final int num = 10; // 赋值后不能修改
// 分开赋值也可以
final int num2;
num2 = 20; // 赋值后不能修改
final Student stu = new Student("高圆圆");
//stu = new Student("赵又廷"); // 错误! 用新地址赋值是不行的
stu.setName("高圆圆圆圆"); // 正确! 地址没变, 只是修改对象中的成员变量的值
final关键字用于修饰成员变量
final修饰成员变量的作用:
自定义常量, 一旦赋值不能修改
注意:
1. 成员变量final之后必须手动赋值, 不会再给默认值
2. final的成员变量, 要么直接赋值, 要么通过构造方法赋值. 二者选其一
3. 必须保证类当中所有重载的构造方法, 都最终会对final的成员变量进行赋值
补充:
[Java] 纯文本查看 复制代码
public class Person {
private final String name /*= "鹿晗"*/; // 声明时直接赋值
public Person() {
name = "关晓彤"; // 在构造内部赋值
}
public Person(String name) {
this.name = name; // 通过参数赋值
}
}
四种权限修饰符
四种权限修饰符 (从大到小):
public > protected > (default) > private
注意事项:(default)并不是关键字“default”,而是根本不写。
访问范围 | public | protected | (default) | private |
同一个类 (我自己) | YES | YES | YES | YES |
同一个包 (我邻居) | YES | YES | YES | NO |
不同包子类 (我儿子) | YES | YES | NO | NO |
不同包非子类 (陌生人) | YES | NO | NO | NO |
内部类内部类的概念与分类内部类: 定义在一个类的内部的类
一个事物在另一个事物内部:
汽车 内部有 发动机
人 内部有 心脏
内部类的分类:
1. 成员内部类
2. 局部内部类
3. 匿名内部类 (属于局部内部类的一种)
成员内部类的定义
成员内部类的定义格式:
定义在成员位置
修饰符 class 外部类名称 {
修饰符 class 成员内部类名称 {
// ...
}
// 其他成员如 成员变量, 成员方法, 构造方法...
}
注意事项:
内部类和外部类的相互访问规则:
内部类中使用外部类成员, 随意访问, 包括外部类私有的
外部类中使用内部类成员, 需要通过内部类对象
内部类在编译后, 也会生成独立的字节码文件. 文件名规则: 外部类名$内部类名.class
成员内部类的使用
使用成员内部类有2种方式:
1. 间接方式: 在外部类的方法当中, 创建内部类对象调用方法; 然后main只是调用外部类的方法
2. 直接方式: 在其他类当中, 创建对象
外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
为什么先要创建内部类对象?
因为Heart类是非静态的成员内部类, 非静态的成员生命周期跟随对象, 所以创建了外部类对象, 才有非静态成员内部类的存在
补充:
[Java] 纯文本查看 复制代码
// 外部类
public class Body {
// 内部类
public class Heart {
public void beat() {
System.out.println("心脏跳动...");
}
}
// 方式1: 在外部类的方法中创建内部类对象
public void methodBody() {
// 创建内部类对象并调用方法
new Heart().beat();
}
}
// 其他类
public class Test {
public static void main(String[] args) {
// 方式1: 创建外部类对象, 调用方法, 间接在方法中创建内部类对象
new Body().methodBody();
// 方式2: 外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
Body.Heart heart = new Body().new Heart();
heart.beat();
}
}
5分钟练习: 创建内部类对象并调用方法
需求:
定义类: 身体 Body. 其中包含:
成员内部类: 心脏 Heart. 其中包含:
心脏跳动的方法: beat(). 方法内部打印"心脏跳动"
成员方法: methodBody(). 方法内部创建Heart对象, 并调用beat()方法
定义测试类
使用间接方式1: 创建Body对象, 调用methodBody()
使用直接方式2: 创建Heart对象, 调用beat()方法
代码:
[Java] 纯文本查看 复制代码
/*
定义类: 身体 Body. 其中包含:
成员内部类: 心脏 Heart. 其中包含:
心脏跳动的方法: beat(). 方法内部打印"心脏跳动"
成员方法: methodBody(). 方法内部创建Heart对象, 并调用beat()方法
*/
public class Body { // 身体
// 成员内部类
public class Heart { // 心脏
// 内部类的成员方法: 心脏跳动
public void beat() {
System.out.println("心脏跳动");
}
}
// 外部类的成员方法: 创建一个心脏, 让其跳动
public void methodBody() {
new Heart().beat();
}
}
/*
需求:
定义类: 身体 Body. 其中包含:
成员内部类: 心脏 Heart. 其中包含:
心脏跳动的方法: beat(). 方法内部打印"心脏跳动"
成员方法: methodBody(). 方法内部创建Heart对象, 并调用beat()方法
定义测试类
使用间接方式1: 创建Body对象, 调用methodBody()
使用直接方式2: 创建Heart对象, 调用beat()方法
*/
public class Test {
public static void main(String[] args) {
// 间接方式: 先创建Body, 然后通过Body的方法间接创建内部类Heart
Body body = new Body();
body.methodBody();
// 直接方式: 外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
Body.Heart heart = new Body().new Heart();
heart.beat();
}
}
内部类的同名变量访问
内部类访问外部类非静态成员变量格式:
外部类名称.this.外部类成员变量名
补充:
[Java] 纯文本查看 复制代码
// 外部类
public class Outer {
int num = 10; // 外部类的成员变量
// 内部类
public class Inner {
int num = 20; // 内部类的成员变量
public void methodInner() {
int num = 30; // 内部类方法的局部变量
System.out.println(num); // 局部变量,就近原则
System.out.println(this.num); // 内部类的成员变量
System.out.println(Outer.this.num); // 外部类的成员变量
}
}
}
局部内部类定义
局部内部类: 定义在方法中的类
定义格式:
修饰符 class 外部类名称 {
修饰符 返回值类型 外部类方法名称(参数列表) {
// 定义在方法中
class 局部内部类名称 {
// ...
}
}
}
局部内部类出了方法就不能使用了
定义一个类的时候,权限修饰符规则:
1. 外部类: public / (default)
2. 成员内部类: public / protected / (default) / private
3. 局部内部类: 什么都不能写
补充:
[Java] 纯文本查看 复制代码
class Outer {
// 方法
public void methodOuter() {
int num = 10;
// 局部内部类
class Inner {
public void methodInner() {
int num = 20;
System.out.println(num); // 20
}
}
// 在定义完类后, 创建局部内部类对象
Inner inner = new Inner();
inner.methodInner();
}
}
// 要调用方法才能创建局部内部类的对象
new Outer().methodOuter();
局部内部类的final问题
如果局部内部类访问所在方法的局部变量, 那么这个局部变量必须是"有效final"的
有效final的:
Java 8之前, 局部变量需要加"final"关键字, 定义为常量
Java 8开始, 只要局部变量"事实不变(代码中没有修改值)", 那么final关键字"可以省略"
要求局部变量有效final的原因:
1. new 出来的局部内部类对象, 在"堆内存"中
2. 局部变量是跟着方法走的, 在"栈内存"中
3. 方法运行结束之后, 立刻出栈, 局部变量就会立刻消失
4. 但 new 出来的局部内部类对象, 会在堆当中持续存在, 直到垃圾回收消失
5. 所以如果该 局部内部类对象 要想继续使用 局部变量, 则必须延长局部变量的生命周期且保持不变
补充:
[Java] 纯文本查看 复制代码
public class MyOuter {
public void methodOuter() {
// 有效final
int num = 10; // 所在方法的局部变量
//final int num = 10;
class MyInner {
public void methodInner() {
System.out.println(num); // 该局部变量声明周期必须足够长且保持不变
}
}
}
}
匿名内部类
匿名内部类的定义格式: (定义在方法中)
父接口名 对象名 = new 父接口名() {
// 覆盖重写所有抽象方法
};
父类名 对象名 = new 父类名() {
// 覆盖重写所有抽象方法
};
// 还可以创建匿名内部类的匿名对象, 直接调用方法
new 父接口名() {
// 覆盖重写所有抽象方法
}.方法名();
匿名内部类的使用场景:
如果接口的实现类(父类的子类)对象, 只需要使用唯一的一次, 就可以省略该类的定义, 改为使用匿名内部类
补充:
[Java] 纯文本查看 复制代码
// 创建匿名内部类对象, 并赋值给一个变量
MyInterface objA = new MyInterface() {
@Override
public void method1() {
System.out.println("匿名内部类实现了方法!");
}
};
// 对象调用方法
objA.method1();
// 创建匿名内部类的匿名对象, 直接调用方法
new MyInterface() {
@Override
public void method1() {
System.out.println("匿名内部类实现了方法!");
}
}.method1();
匿名内部类的注意事项
new 接口名称() {...}匿名内部类解析:
1. new代表创建对象的动作
2. 接口名称就是匿名内部类需要实现哪个接口
3. {...}是匿名内部类的内容
注意问题:
1. 匿名内部类, 在创建对象的时候, 只能使用唯一一次
如果希望多次创建对象,而且类的内容一样的话,那么就需要使用单独定义的实现类了。
2. 匿名对象, 在调用方法的时候, 只能调用唯一一次
如果希望同一个对象,调用多次方法,那么必须给对象起个名字。
3. 匿名内部类是省略了实现类/子类名称,但是匿名对象是省略了对象名称
强调:匿名内部类和匿名对象不是一回事!!!
5分钟练习: 匿名内部类
需求:
定义接口Animal, 接口中定义抽象方法eat();
定义实现类 Cat, 实现Animal接口
重写eat()方法, 打印"猫吃鱼"
定义测试类
创建Cat对象, 调用重写的方法
创建Animal匿名内部类对象, 重写eat()方法, 打印"狗吃肉", 并调用重写的方法
创建Animal匿名内部类对象, 重写eat()方法, 打印"奥特曼打打小怪兽", 并调用重写的方法
体会匿名内部类相比定义实现类有什么好处?
代码:
[Java] 纯文本查看 复制代码
/*
定义接口Animal, 接口中定义抽象方法eat();
*/
public interface Animal {
public abstract void eat();
}
public class Cat implements Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
public class Test {
public static void main(String[] args) {
// 普通方式: 创建Cat对象
Cat cat = new Cat();
cat.eat();
// 匿名内部类对象方法 多态
Animal dog = new Animal() {
@Override
public void eat() {
System.out.println("狗吃肉");
}
public void watchHouse() {
System.out.println("狗看家");
}
};
dog.eat();
// 无法向下转型: 子类类型 变量名 = (子类类型) 父类对象;
// dog.watchHouse();
new Animal() {
@Override
public void eat() {
System.out.println("奥特曼打打小怪兽");
}
public void charge() {
System.out.println("充电");
}
}.charge();
}
}
引用类型的使用类作为成员变量类型
知识点:
一个类的成员变量, 能否使用"类"(引用数据类型) 作为数据类型?
private 类名 变量名;
如:
[Java] 纯文本查看 复制代码
// 水杯类
public class Cup {
// 水杯的成员变量, 成员方法, 构造方法...
}
public class Phone {}
public class Student {
private int age;
private String name; // String是引用类型, 是一个类
// 学生都有自己的水杯, 怎么定义为成员变量?
private Cup cup;
private Phone phone;
// set/get
}
Student s = new Student();
s.setName("张三"); // "张三"是String类的对象
// 要设置水杯, 如何设置?
s.setCup(new Cup());
s.setPhone(new Phone());
总结:
成员变量的类型, 既可以用基本数据类型, 也可以用引用数据类型
类作为成员变量类型, 成员变量的值实际是该类的对象
5分钟练习: 使用类作为成员变量
需求:
定义类: 武器 Weapon.
私有属性: String code 表示武器名称
生成无参有参构造, set/get方法
定义类: 英雄 Hero
私有属性: 英雄名字String name, 英雄年龄int age, 英雄武器Weapon weapon
生成无参有参构造, set/get方法
定义成员方法: attack(), 打印"年龄为XXX的YYY用ZZZ攻击敌方。"
定义测试类
创建10岁的提莫 Hero对象, 为他设置武器蘑菇炸弹, 让提莫攻击
代码:
[Java] 纯文本查看 复制代码
public class Weapon {
private String code;
public Weapon() {
}
public Weapon(String code) {
this.code = code;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
public class Hero {
private String name;
private int age;
private Weapon weapon;
public Hero() {
}
public Hero(String name, int age, Weapon weapon) {
this.name = name;
this.age = age;
this.weapon = weapon;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Weapon getWeapon() {
return weapon;
}
public void setWeapon(Weapon weapon) {
this.weapon = weapon;
}
// 攻击
public void attack() {
System.out.println("年龄为" + age + "的" + name + "用" + weapon.getCode() + "攻击敌方。");
}
}
public class Test {
public static void main(String[] args) {
// 创建英雄对象
Hero timo = new Hero();
timo.setName("提莫");
timo.setAge(10);
// 创建武器
Weapon weapon = new Weapon();
weapon.setCode("蘑菇炸弹");
// 为英雄设置武器
timo.setWeapon(weapon);
// 开始进攻!
timo.attack();
}
}
接口作为成员变量类型
接口作为成员变量的类型, 值实际上是接口的实现类对象
可以利用匿名内部类对象, 简化接口类型成员变量的赋值, 同时提高程序的灵活性
5分钟练习: 接口作为成员变量
需求:
定义接口Skill, 包含抽象方法: 施放技能 use()
定义实现类: 推塔技能 TuiTaSkill, 重写use方法, 打印"人在塔在! 德玛西亚万岁!"
定义类: 英雄 Hero
私有属性: 英雄姓名String name, 英雄技能Skill skill
生成无参有参构造, set/get方法
定义攻击方法: attack(), 方法中执行以下代码:
打印: "我叫XXX, 开始施放技能:"
施放技能: skill.use();
打印: "技能施放完成"
定义测试类:
创建英雄: 盖伦, 设置技能TuiTaSkill对象, 调用攻击方法让英雄攻击
代码:
[Java] 纯文本查看 复制代码
public interface Skill {
// 施放技能
public abstract void use();
}
public class TuiTaSkill implements Skill {
@Override
public void use() {
System.out.println("人在塔在! 德玛西亚万岁!");
}
}
public class Hero {
private String name;
private Skill skill;
public Hero() {
}
public Hero(String name, Skill skill) {
this.name = name;
this.skill = skill;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Skill getSkill() {
return skill;
}
public void setSkill(Skill skill) {
this.skill = skill;
}
// 攻击
public void attack() {
System.out.println("我叫" + name + ", 开始施放技能:");
skill.use();
System.out.println("技能施放完成");
}
}
public class Test {
public static void main(String[] args) {
// 创建英雄
Hero hero = new Hero();
hero.setName("盖伦");
// 创建技能
TuiTaSkill tuiTaSkill = new TuiTaSkill();
hero.setSkill(tuiTaSkill);
// 开始进攻
hero.attack();
// 创建另一个英雄
new Hero("奥巴马", new Skill() {
@Override
public void use() {
System.out.println("双枪 Biu Biu Biu~");
}
}).attack();
}
}
接口作为方法的参数和或返回值知识点:
接口是否可以作为方法的 参数类型 或 返回值类型 ?
实际传入参数的是接口的什么?
实际作为返回值返回的是接口的什么?
如:
// 使用USB设备
public USB useDevice(USB usb) {
}
参数可以, 返回值是否也可以?
总结:
接口作为方法参数类型和返回值类型也是可以的, 调用方法时实际执行的还是子类重写的方法 编译看左边, 运行看右边
补充:
[Java] 纯文本查看 复制代码
/*
java.util.List正是ArrayList所实现的接口。
*/
public class DemoInterface {
public static void main(String[] args) {
// 左边是接口名称,右边是实现类名称,这就是多态写法
List<String> list = new ArrayList<>();
List<String> result = addNames(list);
for (int i = 0; i < result.size(); i++) {
System.out.println(result.get(i));
}
}
public static List<String> addNames(List<String> list) {
list.add("迪丽热巴");
list.add("古力娜扎");
list.add("玛尔扎哈");
list.add("沙扬娜拉");
return list;
}
}
发红包案例发红包案例: 分析5分钟练习: 导入代码, 创建基础类需求:
发红包. 红包发出去之后,所有人都有红包,大家抢完了之后,最后一个红包给群主自己
红包分发策略:
1. 普通红包(平均):
int avg = totalMoney / count; 平均的钱
int mod = totalMoney % count; 余数的钱加到最后
2. 手气红包(随机):
随机金额: 最少1分钱, 最多不超过平均数的2倍
越发越少
操作步骤:
已经提供的类:
RedPacketFrame: 一个抽象类, 包含了一些属性, 是红包案例的页面
OpenMode: 一个接口, 包含一个分配方法, 用来指定红包类型
1. 导入图片和代码
将 pic 目录放入当前模块下
建包 com.itheima.red , 将 OpenMode.java 和 RedPacketFrame.java 2个文件复制到包中
2. 创建基础类:
建类 MyRed , 继承 RedPacketFrame 类, 生成有参构造
建类 Bootstrap , 作为程序启动类:
定义main方法, 方法中创建 MyRed 对象, 通过构造方法参数设置标题
发红包案例: 普通红包平均分发5分钟练习: 实现普通发红包
我们自己要做的事情有:
1. 设置一下程序的标题,通过构造方法的字符串参数
2. 设置群主名称
3. 设置分发策略:平均,还是随机?
操作步骤:
1. 定义类 NormalMode 表示普通红包模式, 实现 OpenMode 接口
重写 divide() 方法, 方法内部按照以前平分红包方式编写代码:
创建ArrayList<Integer>对象, 用于保存分好的钱
算出平均值和零头:
int avg = totalMoney / totalCount; // 平均值
int mod = totalMoney % totalCount; // 零头
循环 totalCount - 1 次, 添加平均值
最后添加 平均值+零头
返回红包集合
2. 在 Bootstrap 类中:
使用MyRed对象调用 setOwnerName(群主姓名), 设置群主姓名
使用MyRed对象调用 setOpenWay(new NormalMode()), 设置普通发红包模式
代码:
[Java] 纯文本查看 复制代码
/*
普通模式: 平分
*/
public class NormalMode implements OpenMode {
@Override
public ArrayList<Integer> divide(int totalMoney, int totalCount) {
// 创建集合, 用于装分好的钱
ArrayList<Integer> list = new ArrayList<>();
// 计算平均值和零头
int avg = totalMoney / totalCount;
int mod = totalMoney % totalCount;
// 循环将平均值存入集合, 留最后一次
for (int i = 0; i < totalCount - 1; i++) {
list.add(avg);
}
// 添加最后一个红包
list.add(avg + mod);
// 返回红包集合
return list;
}
}
/*
程序启动类
*/
public class Bootstrap {
public static void main(String[] args) {
// 创建MyRed对象
MyRed myRed = new MyRed("发红包, 发大红包");
// 设置群主名称
myRed.setOwnerName("王大锤");
// 设置红包模式
myRed.setOpenWay(new NormalMode());
}
}
发红包案例: 手气红包随机分发5分钟练习: 实现手气红包随机金额: 最少1分钱, 最多不超过平均数的2倍
平均数: leftMoney / leftCount
平均数的2倍: leftMoney / leftCount * 2
1 <= 金额 <= leftMoney / leftCount * 2
生成符合范围的随机金额:
int money = random.nextInt(leftMoney / leftCount * 2) + 1;
list.add(money);
每次生成的随机金额要减掉
leftMoney -= money;
剩余个数也要减少
leftCount--;
实现步骤:
1. 定义类 RandomMode 表示手气红包模式, 实现 OpenMode 接口
重写 divide() 方法:
创建ArrayList<Integer>对象, 用于保存分好的钱
创建Random对象
定义变量保存总金额: int leftMoney = totalMoney;
定义变量保存份数: int leftCount = totalCount;
循环 totalCount - 1 次, 在循环中:
利用公式随机生成金额: int money = random.nextInt(leftMoney / leftCount * 2) + 1;
将随机金额添加到集合: list.add(money);
从剩余金额中减去该随机金额: leftMoney -= money;
从剩余份数中减去该份数: leftCount--;
循环接收后, 将最后剩下的金额添加到集合: list.add(leftMoney);
返回集合
2. 在 Bootstrap 类中:
使用MyRed对象调用 setOpenWay(new RandomMode()), 设置手气发红包模式
代码:
[Java] 纯文本查看 复制代码
public class RandomMode implements OpenMode {
@Override
public ArrayList<Integer> divide(int totalMoney, int totalCount) {
// 创建装钱的红包
ArrayList<Integer> list = new ArrayList<>();
// 先将总金额和红包份数存入两个临时变量, 避免直接修改总金额和红包份数导致数据不准确
int leftMoney = totalMoney;
int leftCount = totalCount;
// 除了最后一次, 每次都随机生成金额, 规则: 最少1分钱, 最多不超过平均金额的2倍
Random random = new Random();
// 按照总次数-1循环
for (int i = 0; i < totalCount - 1; i++) {
// 每次用剩余的金额, 生成随机金额
int money = random.nextInt(leftMoney / leftCount * 2) + 1;
// 添加到红包
list.add(money);
// 剩余金额要减去本次生成的随机金额
leftMoney -= money;
// 红包份数减少1个
leftCount--;
}
// 最后一次剩下的钱, 全都放入红包
list.add(leftMoney);
// 返回红包
return list;
}
}
/*
程序启动类
*/
public class Bootstrap {
public static void main(String[] args) {
// 创建MyRed对象
MyRed myRed = new MyRed("发红包, 发大红包");
// 设置群主名称
myRed.setOwnerName("王大锤");
// 设置红包模式
// myRed.setOpenWay(new NormalMode()); // 普通平分模式
myRed.setOpenWay(new RandomMode()); // 手气随机模式
}
}
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) |
黑马程序员IT技术论坛 X3.2 |