继承
面向对象的三大特征:封装性、继承性、多态性。
继承是多态的前提,如果没有继承,就没有多态。
变量的访问:
局部变量:直接写成员变量名
本类的成员变量:this.成员变量名
父类的成员变量:super.成员变量名
在父子类的继承关系中,创建子类对象,访问成员方法的规则:
创建的对象是谁,就优先用谁,如果没有则向上找。
注意事项:无论是成员方法还是成员变量,如果没有都是向上找父类,绝对不会向下找子类。
重写(Override):在继承关系中,方法的名称一样,参数列表也一样。也叫覆盖、覆写。
方法的覆盖重写特点:创建的是子类对象,则优先用子类方法。
方法覆盖重写的注意事项:
1.必须保证父子类间方法的名称相同,参数列表也相同。
@Override:写在方法前面,用来检测是不是有效的正确覆盖重写。
这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。
2.子类方法的返回值必须小于等于父类方法的返回值范围。
3.子类方法的权限必须大于等于父类方法的权限修饰符。
public > protected > (default) > private
备注:(default)不是关键字default,而是什么都不写,留空。
覆盖重写的应用场景:
super关键字的用法有三种:
1.在子类的成员方法中,访问父类的成员变量。
2.在子类的成员方法中,访问父类的成员方法。
3.在子类的构造方法中,访问父类的构造方法。
super关键字用来访问父类内容,而this关键字用来访问本类内容。用法也有三种:
1.在本类的成员方法中,访问本类的成员变量。
2.在本类的成员方法中,访问本类的另一个成员方法。
3.在本类的构造方法中,访问本类的另一个构造方法。
在第三种用法当中要注意:
A.this(…)调用必须是构造方法中的第一个语句,与super相同,唯一一个。
B.super和this两种构造调用,不能同时使用。
看下列代码的内存图:
package cn.itcast.demo01;
public class Fu {
int num = 10;
public void method() {
System.out.println("父类方法");
}
}
package cn.itcast.demo01;
public class Zi extends Fu {
int num = 20;
@Override
public void method() {
super.method();// 调用了父类方法
System.out.println("子类方法");
}
public void show() {
int num = 30;
System.out.println(num);// 30
System.out.println(this.num);// 20
System.out.println(super.num);// 10
}
}
package cn.itcast.demo01;
public class Demo01Method {
public static void main(String[] args) {
Zi zi = new Zi();
zi.show();
zi.method();
}
}
java继承的三个特点:
抽象类
抽象方法:就是加上abstract关键字,然后去掉大括号,直接分号结束。
抽象类:抽象方法所在的类,必须是抽象类才行。在class之前写上abstract即可。
package cn.itcast.demo01;
public abstract class Animal {
// 这是一个抽象方法,代表吃东西, 但是具体吃什么(大括号中的内容)不确定。
public abstract void eat();
// 这是普通的成员方法
public void nomalMethod(){
}
}
如何使用抽象类和抽象方法:
- 不能直接创建new抽象类对象。
- 必须用一个子类来继承抽象父类。
- 子类必须覆盖重写抽象父类当中所有的抽象方法。
覆盖重写(实现):去掉抽象方法的abstract关键字,然后补上方法体大括号。 - 创建子类对象进行使用。
注意事项:
1.抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
2.抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
package cn.itcast.demo01;
public abstract class Fu {
public Fu() {
System.out.println("抽象父类构造方法执行!");
}
public abstract void eat();
}
package cn.itcast.demo01;
public class Zi extends Fu {
public Zi() {
System.out.println("子类构造方法执行");
}
@Override
public void eat() {
System.out.println("吃饭饭");
}
}
package cn.itcast.demo01;
public class Demo01Method {
public static void main(String[] args) {
Zi zi = new Zi();
zi.eat();
}
}
3.一个抽象类不一定含有抽象方法,只要保证抽象方法所在的类是抽象类,即可。
没有抽象方法的抽象类,也不能直接创建对象,在一些特殊场景下有用途。例如:适配器模式
4.抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。
package cn.itcast.demo01;
public abstract class Animal {
public abstract void eat();
public abstract void sleep();
}
package cn.itcast.demo01;
public abstract class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
package cn.itcast.demo01;
public class DogGolden extends Dog {
@Override
public void sleep() {
System.out.println("呼呼呼...");
}
}
package cn.itcast.demo01;
public class Demo01Method {
public static void main(String[] args) {
DogHa ha = new DogHa();
ha.eat();;
ha.sleep();
System.out.println("===========================");
DogGolden golden = new DogGolden();
golden.eat();
golden.sleep();
}
}
综合案例:发红包
群主发普通红包。某群有多名成员,群主给成员发普通红包。普通红包的规则:
- 群主的一笔金额,从群主余额中扣除,平均分成n等份,让成员领取。
- 成员领取红包后,保存到成员余额中。
请根据描述,完成案例中所有类的定义以及指定类之间的继承关系,并完成发红包的操作。
分析:
package cn.itcast.demo01;
public class User {
private String name; // 姓名
private int money; // 余额,也就是当前用户拥有的钱数
public User(String name, int money) {
this.name = name;
this.money = money;
}
public User() {
}
// 展示一下当前用户有多少钱
public void show() {
System.out.println("我叫:" + name + ",我有多少钱:" + money);
}
public void setName(String name) {
this.name = name;
}
public void setMoney(int money) {
this.money = money;
}
public String getName() {
return name;
}
public int getMoney() {
return money;
}
}
package cn.itcast.demo01;
import java.util.ArrayList;
// 群主的类
public class Manager extends User {
public Manager() {
}
public Manager(String name, int money) {
super(name, money);
}
public ArrayList<Integer> send(int totalMoney, int count) {
// 首先需要一个集合,用来存储若干个红包的金额
ArrayList<Integer> redList = new ArrayList<>();
// 看一下群主有多少钱
int leftMoney = super.getMoney(); // 群主当前余额
if (totalMoney > leftMoney) {
System.out.println("余额不足");
return redList;
}
// 扣钱,其实就是重新设置余额
super.setMoney(leftMoney - totalMoney);
// 发红包需要平均拆分成count份
int avg = totalMoney / count;
// 除不开的零头,包在最后一个红包中
int mod = totalMoney % count; // 余数,也就是甩下的零头
// 下面把红包一个一个放到集合中
for (int i = 0; i < count - 1; i++) {
redList.add(avg);
}
//最后一个红包
int last = avg + mod;
redList.add(last);
return redList;
}
}
package cn.itcast.demo01;
import java.util.ArrayList;
public class Demo01Method {
public static void main(String[] args) {
Manager manager = new Manager("群主", 100);
Member one = new Member("成员A", 0);
Member two = new Member("成员B", 0);
Member three = new Member("成员C", 0);
manager.show();
one.show();
two.show();
three.show();
System.out.println("=========================");
// 群主总共发20块钱,分成3个红包
ArrayList<Integer> redList = manager.send(20, 3);
// 三个普通成员收红包
one.receive(redList);
two.receive(redList);
three.receive(redList);
manager.show(); // 100-20=80
//6、6、8随机分给三个人
one.show();
two.show();
three.show();
}
}
接口
接口就是多个类的公共规范。
接口是一种引用数据类型,最重要的内容就是其中的:抽象方法。
如何定义一个接口的格式:
public interface 接口名称 {
//接口内容
}
备注:换成了关键字interface之后,编译生成的字节码文件仍然是: .java–> .class
如果是Java7,那么接口中可以包含的内容有:
1.常量
2.抽象方法
如果是Java8,还可以额外包含有:
3.默认方法
4.静态方法
如果是Java9,还可以额外包含有:
5.私有方法
在任何版本的Java中,接口都能定义抽象方法。
格式:
public abstract 返回值类型 方法名称(参数列表);
注意事项:
- 接口当中的抽象方法,修饰符必须是两个固定的关键字:public abstract
- 这两个关键字修饰符,可以选择性地省略。
- 方法的三要素,可以随意定义。
package cn.itcast.demo01;
public interface MyInterface {
// 这是一个抽象方法
public abstract void methodAbs1();
// 这也是抽象方法
abstract void methodAbs2();
// 这也是抽象方法
public void methodAbs3();
// 这也是抽象方法
void methodAbs4();
}
接口使用步骤:
- 接口不能直接使用,必须有一个“实现类”来“实现”该接口。
格式:
public class 实现类名称 implements 接口名称 {
// …
} - 接口的实现类必须覆盖重写接口中所有的抽象方法。
- 创建实现类的对象,进行使用。
注意事项:如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类自己就必须是抽象类。
从Java8开始,接口里允许定义默认方法。
格式:
public default 返回值类型 方法名称(参数列表){
方法体
}
备注:接口当中的默认方法,可以解决接口升级的问题。
1.接口的默认方法,可以通过接口实现类对象,直接调用。
2.接口的默认方法,也可以被接口实现类进行覆盖重写。
package cn.itcast.demo01;
public interface MyInterface {
// 抽象方法
public abstract void methodAbs();
// 新添加了一个抽象方法
// public abstract void methodAbs2();
// 新添加的方法,改成默认方法
public default void methodDefault() {
System.out.println("这是新添加的默认方法");
}
}
package cn.itcast.demo01;
public class MyInterfaceDefaultA implements MyInterface {
@Override
public void methodAbs() {
System.out.println("实现了抽象方法,AAA");
}
}
package cn.itcast.demo01;
public class MyInterfaceDefaultB implements MyInterface {
@Override
public void methodAbs() {
System.out.println("实现了抽象方法,BBB");
}
@Override
public void methodDefault() {
System.out.println("实现类B覆盖重写了接口的默认方法");
}
}
package cn.itcast.demo01;
public class Demo01Method {
public static void main(String[] args) {
// 创建了实现类对象
MyInterfaceDefaultA myInterfaceDefaultA = new MyInterfaceDefaultA();
myInterfaceDefaultA.methodAbs();
// 调用默认方法,如果实现类当中没有,会向上找接口
myInterfaceDefaultA.methodDefault();
System.out.println("===================");
MyInterfaceDefaultB myInterfaceDefaultB = new MyInterfaceDefaultB();
myInterfaceDefaultB.methodAbs();
myInterfaceDefaultB.methodDefault(); // 实现类B覆盖重写了接口的默认方法
}
}
从Java8开始,接口当中允许定义静态方法。
格式:
public static 返回值类型 方法名称(参数列表){
方法体
}
注意:不能通过接口实现类的对象来直接调用接口当中的静态方法。
正确用法:通过接口名称,直接调用其中的静态方法。
格式:
接口名称.静态方法名(参数)
package cn.itcast.demo01;
public interface MyInterfaceStatic {
public static void methodStatic() {
System.out.println("这是接口的静态方法!");
}
}
package cn.itcast.demo01;
public class MyInterfaceStaticImpl implements MyInterfaceStatic {
public static void main(String[] args) {
MyInterfaceStaticImpl impl = new MyInterfaceStaticImpl();
// 错误写法!
// impl.methodStatic();
// 直接通过接口名称调用静态方法
MyInterfaceStatic.methodStatic();
}
}
package cn.itcast.demo01;
public class Demo01Method {
public static void main(String[] args) {
}
}
从Java9开始,接口当中允许定义私有方法。
1.普通私有方法,解决多个默认方法之间重复代码问题
格式:
private 返回值类型 方法名称(参数列表){
方法体
}
2.静态私有方法,解决多个静态方法之间重复代码问题
格式:
private static 返回值类型 方法名称(参数列表){
方法体
}
package cn.itcast.demo01;
public interface MyInterfacePrivateA {
public default void methodDefault1() {
System.out.println("默认方法1");
methodCommon();
}
public default void methodDefault2() {
System.out.println("默认方法2");
methodCommon();
}
private void methodCommon() {
System.out.println("AAA");
System.out.println("BBB");
System.out.println("CCC");
}
}
package cn.itcast.demo01;
public class MyInterfacePrivateAImpl implements MyInterfacePrivateA {
public void methodAnother() {
// 直接访问到了接口中的默认方法,这样是错误的!
// methodCommon();
}
}
接口当中也可以定义“成员变量”,但是必须使用public static final三个关键字进行修饰。
从效果上看,这其实就是接口的常量。
格式:
public static final 数据类型 常量名称 = 数据值;
注意事项:
- 接口当中的常量,可以省略public static final,注意:不写也照样是这样。
- 接口当中的常量,必须进行赋值,不能不赋值。
- 接口中常量的名称,使用完全大写的字母,用下划线进行分隔。(推荐命名规则)
在Java 9+版本中,接口的内容可以有:
1.成员变量其实是常量,格式:
[public] [static] [final] 数据类型 常量名称 = 数据值;
注意:
常量必须进行赋值,而且一旦赋值不能改变。
常量名称完全大写,用下划线进行分隔。
2.接口中最重要的就是抽象方法,格式:
[public] [abstract] 返回值类型 方法名称(参数列表);
注意:实现类必须覆盖重写接口所有的抽象方法,除非实现类是抽象类。
3.从Java 8开始,接口里允许定义默认方法,格式:
[public] default 返回值类型 方法名称(参数列表){ 方法体 }
注意:默认方法也可以被覆盖重写
4.从Java 8开始,接口里允许定义静态方法,格式:
[public] static 返回值类型 方法名称(参数列表){ 方法体 }
注意:应该通过接口名称进行调用,不能通过实现类对象调用接口静态方法。
5.从Java 9开始,接口里允许定义私有方法,格式:
普通私有方法:private 返回值类型 方法名称(参数列表){ 方法体 }
静态私有方法: private static 返回值类型 方法名称(参数列表){ 方法体 }
注意:private的方法只有接口自己才能调用,不能被实现类或别人使用。
使用接口的时候,需要注意:
1.接口是没有静态代码块或者构造方法的。
2.一个类的直接父类是唯一的,但是一个类可以同时实现多个接口。
格式:
public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB {
// 覆盖重写所有抽象方法
}
3.如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可。
4.如果实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类就必须是一个抽象类。
5.如果实现类所实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写。
6.一个类如果直接父类当中的方法,和接口当中的默认方法产生了冲突,优先使用父类当中的方法。
1.类与类之间是单继承的。直接父类只有一个。
2.类与接口之间是多实现的。一个类可以实现多个接口。
3.接口与接口之间是多继承的。
注意事项:
1.多个父亲接口当中的抽象方法如果重复,没关系。
2.多个父亲接口当中的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写,而且带着default关键字。
多态
多态概念:左父右子就是多态
成员变量:编译看左,运行看左。
成员方法:编译看左,运行看右。
使用多态的好处:
向上向下转型:
如何才能知道一个父类引用的对象,本来是什么子类?
格式:
对象 instanceof 类型
这将会得到一个boolean值,也就是判断前面的对象能不能当做后面类型的实例。
向下转型一定要进行instanceof判断。
与接口和多态相关的综合案例:
笔记本电脑(laptop)通常具备使用USB设备的功能。在生产时,笔记本都预留了可以插入USB设备的USB接口,
但具体是什么USB设备,笔记本厂商并不关心,只要符合USB规格的设备都可以。
定义USB接口,具备最基本的开启功能和关闭功能。鼠标和键盘要想能在电脑上使用,那么鼠标和键盘也必须遵守
USB规范,实现USB接口,否则鼠标和键盘的生产出来也无法使用
案例分析:
进行描述笔记本类,实现笔记本使用USB鼠标、USB键盘
USB接口,包含开启功能、关闭功能
笔记本类,包含运行功能、关机功能、使用USB设备功能
鼠标类,要实现USB接口,并具备点击的方法
键盘类,要实现USB接口,具备敲击的方法
package cn.itcast.demo01;
// 鼠标就是USB设备
public class Mouse implements USB {
@Override
public void open() {
System.out.println("打开鼠标");
}
@Override
public void close() {
System.out.println("关闭鼠标");
}
public void click() {
System.out.println("鼠标点击");
}
}
package cn.itcast.demo01;
// 键盘就是USB设备
public class Keyboard implements USB {
@Override
public void open() {
System.out.println("打开键盘");
}
@Override
public void close() {
System.out.println("关闭键盘");
}
public void type() {
System.out.println("键盘输入");
}
}
package cn.itcast.demo01;
public interface USB {
public abstract void open(); // 打开设备
public abstract void close(); // 关闭设备
}
package cn.itcast.demo01;
public class Laptop {
public void powerOn() {
System.out.println("笔记本电脑开机");
}
public void powerOff() {
System.out.println("笔记本电脑关机");
}
// 使用USB设备的方法,使用接口作为方法的参数
public void usbDevice(USB usb) {
usb.open(); // 打开
if (usb instanceof Mouse) { // 一定要先判断
Mouse mouse = (Mouse) usb; // 向下转型
mouse.click();
} else if (usb instanceof Keyboard) {
Keyboard keyboard = (Keyboard) usb; // 向下转型
keyboard.type();
}
usb.close(); // 关闭
}
}
package cn.itcast.demo01;
import javafx.css.converter.LadderConverter;
public class Demo01Method {
public static void main(String[] args) {
Laptop laptop = new Laptop();
laptop.powerOn();
// 准备一个鼠标供电脑使用
Mouse mouse = new Mouse(); // 多态写法
// 首先进行向上转型
USB usbMouse = mouse;
// 参数是USB类型,我正好传递进去的就是USB鼠标
laptop.usbDevice(usbMouse);
// 创建一个USB键盘
Keyboard keyboard = new Keyboard(); // 没有使用多态写法
// 方法参数是USB类型,传递进去的是实现类对象
laptop.usbDevice(keyboard); // 正确写法
laptop.powerOff();
}
}
final关键字
final关键字代表最终、不可改变的。
常见四种用法:
- 可以用来修饰一个类
- 可以用来修饰一个方法
- 还可以用来修饰一个局部变量
- 还可以用来修饰一个成员变量
当final关键字用来修饰一个类的时候,格式:
public final class 类名称 {
// …
}
含义:当前这个类不能有任何的子类。(太监类)
注意:一个类如果是final的,那么其中所有的成员方法都无法进行覆盖重写(因为没儿子)。
当final关键字用来修饰一个方法的时候,这个方法就是最终方法,也就是不能被覆盖重写。
格式:
修饰符 final 返回值类型 方法名称(参数列表){
// …
}
注意事项:
对于类、方法来说,abstract关键字和final关键字不能同时使用,因为矛盾。
一旦使用final用来修饰局部变量,那么这个变量就不能进行更改。“一次赋值,终生不变”
对于基本类型来说,不可变说的是变量当中的数据不可改变。
对于引用类型来说,不可变说的是变量当中的地址值不可改变。
对于成员变量来说,如果使用final关键字修饰,那么这个变量也照样是不可变。
1.由于成员变量具有默认值,所以用了final之后必须手动赋值,不会再给默认值了。
2.对于final的成员变量,要么使用直接赋值,要么通过构造方法赋值。二者选其一。
3.必须保证类当中所有重载的构造方法,都最终会对final的成员变量进行赋值。
权限修饰符
内部类
如果一个事物的内部包含另一个事物,那么这就是一个类内部包含另一个类。
例如:身体和心脏的关系,又如:汽车和发动机的关系。
分类:
1.成员内部类
2.局部内部类(包含匿名内部类)
成员内部类的定义格式:
修饰符 class 外部类名称 {
修饰符 class 内部类名称 {
// …
}
// …
}
注意:内用外,随意访问。外用内,需要内部类对象。
如何使用成员内部类?有两种方式:
1.间接方式:在外部类的方法当中,使用内部类;然后main只是调用外部类的方法。
2.直接方式:记住公式:
外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();
package cn.itcast.demo01;
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();
}
}
package cn.itcast.demo01;
import javafx.css.converter.LadderConverter;
public class Demo01Method {
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();
}
}
如果出现了重名现象,那么格式是:外部类名称.this.外部类成员变量名
package cn.itcast.demo01;
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 局部内部类名称 {
// ...
}
}
}
package cn.itcast.demo01;
public class Outer {
public void methodOuter() {
class Inner { // 局部内部类
int num = 10;
public void methodInner() {
System.out.println(num); // 10
}
}
Inner inner = new Inner();
inner.methodInner();
}
}
package cn.itcast.demo01;
import javafx.css.converter.LadderConverter;
public class Demo01Method {
public static void main(String[] args) {
Outer obj = new Outer();
obj.methodOuter();
}
}
小结一下类的权限修饰符:
public > protected > (default) > private
定义一个类的时候,权限修饰符规则:
1.外部类:public / (default)
2.成员内部类:public / protected / (default) / private
3.局部内部类:什么都不写
局部内部类,如果希望访问所在方法的局部变量,那么这个局部变量必须是有效final的。
备注:从Java 8+开始,只要局部变量事实不变,那么final关键字可以省略。
原因:
- new出来的对象在堆内存当中。
- 局部变量是跟着方法走的,在栈内存当中。
- 方法运行结束之后,立刻出栈,局部变量就会立刻消失。
- 但是new出来的对象会在堆当中持续存在,直到垃圾回收消失。
匿名内部类(最重要,没有之一):
如果接口的实现类(或者是父类的子类)只需要使用唯一的一次,
那么这种情况下就可以省略掉该类的定义,而改为使用匿名内部类。
匿名内部类的定义格式:
接口名称 对象名 = new 接口名称() {
// 覆盖重写所有抽象方法
};
package cn.itcast.demo01;
public class Demo01Method {
public static void main(String[] args) {
// MyInterface impl = new MyInterfaceImpl();
// impl.method();
// 使用匿名内部类
MyInterface obj = new MyInterface() {
@Override
public void method() {
System.out.println("匿名内部类实现了方法!");
}
};
obj.method();
}
}
对格式“new 接口名称() {…}”进行解析:
1.new代表创建对象的动作
2.接口名称就是匿名内部类需要实现哪个接口
3.{…}这才是匿名内部类的内容
另外还要注意几点问题:
1.匿名内部类,在创建对象的时候,只能使用唯一一次。
如果希望多次创建对象,而且类的内容一样的话,那么就必须使用单独定义的实现类了。
2.匿名对象,在调用方法的时候,只能调用唯一一次。
如果希望同一个对象,调用多次方法,那么必须给对象起个名字。
package cn.itcast.demo01;
public class Demo01Method {
public static void main(String[] args) {
// MyInterface impl = new MyInterfaceImpl();
// impl.method();
// 使用匿名内部类,而且省略了对象名称,也是匿名对象
new MyInterface() {
@Override
public void method() {
System.out.println("匿名内部类实现了方法!");
}
}.method();
}
}
3.匿名内部类是省略了【实现类/子类名称】,但是匿名对象是省略了【对象名称】
强调:匿名内部类和匿名对象不是一回事
类作为成员变量类型:
package cn.itcast.demo01;
// 游戏当中的英雄
public class Hero {
private String name; // 英雄名字
}
这里的String就是一个类。
使用接口作为成员变量类型:
package cn.itcast.demo01;
public interface Skill {
void use(); // 释放技能的抽象方法
}
package cn.itcast.demo01;
public class SkillImpl implements Skill {
@Override
public void use() {
System.out.println("Biu~biu~biu~");
}
}
package cn.itcast.demo01;
// 游戏当中的英雄
public class Hero {
private String name; // 英雄名字
private Skill skill; // 英雄的技能
public void setName(String name) {
this.name = name;
}
public void attack() {
System.out.println("我叫:" + name + ",开始施放技能:");
skill.use(); // 调用接口中的抽象方法
System.out.println("施放技能完成");
}
public void setSkill(Skill skill) {
this.skill = skill;
}
public String getName() {
return name;
}
public Skill getSkill() {
return skill;
}
public Hero() {
}
public Hero(String name, Skill skill) {
this.name = name;
this.skill = skill;
}
}
package cn.itcast.demo01;
public class Demo01Method {
public static void main(String[] args) {
Hero hero = new Hero();
hero.setName("艾希");
// 设置英雄技能
hero.setSkill(new SkillImpl()); // 使用单独定义的实现类
// 还可以改成使用匿名内部类
// Skill skill = new Skill() {
// @Override
// public void use() {
// System.out.println("Pia~pia~pia");
// }
// };
// hero.setSkill(skill);
// 进一步简化,同时使用匿名内部类和匿名对象
hero.setSkill(new Skill() {
@Override
public void use() {
System.out.println("Biu~Pia~Biu~Pia~");
}
});
hero.attack();
}
}
接口作为方法的参数或返回值:
package cn.itcast.demo01;
import java.util.ArrayList;
import java.util.List;
/*
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;
}
}
发红包案例:
场景说明:
红包发出去之后,所有人都有红包,大家抢完了之后,最后一个红包给群主自己。
大多数代码都是现成的,我们需要做的就是填空题。
我们自己要做的事情有:
1.设置一下程序的标题,通过构造方法的字符串参数。
2.设置群主名称
3.设置分发策略:平均,还是随机?
给定一个接口:
package cn.itcast.demo01;
import java.util.ArrayList;
public interface OpenMode {
/**
* 请将totalMoney分成count份,保存到ArrayList<Integer>中,返回即可。
*
* @param totalMoney 总金额为方便计算,已经转换为整数,单位为分。
* @param totalCount 红包个数
* @return ArrayList<Integer> 元素为各个红包的金额值,所有元素的值累和等于总金额。
*/
ArrayList<Integer> divide(int totalMoney, int totalCount);
}
给定一个界面代码类:
package cn.itcast.demo01;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.HashMap;
/**
* 红包的框架 RedPacketFrame
*
* AWT / Swing / JavaFX
*
* @author 不是我
*/
public abstract class RedPacketFrame extends JFrame {
private static final long serialVersionUID = 1L;
private static final String DIR = "01\\pic";
private ArrayList<Integer> moneyList = null;
private static int initMoney = 0;
private static int totalMoney = 0; // 单位为“分”
private static int count = 0;
private static HashMap<JPanel, JLabel> panelLable = new HashMap<>();
// 设置字体
private static Font fontYaHei = new Font("微软雅黑", Font.BOLD, 20);
private static Font msgFont = new Font("微软雅黑", Font.BOLD, 20);
private static Font totalShowFont = new Font("微软雅黑", Font.BOLD, 40);
private static Font nameFont = new Font("微软雅黑", Font.BOLD, 40);
private static Font showNameFont = new Font("微软雅黑", Font.BOLD, 20);
private static Font showMoneyFont = new Font("微软雅黑", Font.BOLD, 50);
private static Font showResultFont = new Font("微软雅黑", Font.BOLD, 15);
/**
* 窗体大小 WIDTH:400 HEIGHT:600
*/
private static final int FRAME_WIDTH = 416; // 静态全局窗口大小
private static final int FRAME_HEIGHT = 650;
private static JLayeredPane layeredPane = null;
/// private static JPanel contentPane = null;
/**
* page1:输入页面 - InputPanel . 组件和初始化!
*/
private static JPanel inputPanel = new JPanel();
// private static JTextField input_total = new JTextField("200"); // 测试用
// private static JTextField input_count = new JTextField("3"); // 测试用
private static JTextField input_total = new JTextField();
private static JTextField input_count = new JTextField();
private static JTextField input_people = new JTextField("30");
private static JTextField input_msg = new JTextField("恭喜发财 , 大吉大利");
private static JTextField input_total_show = new JTextField("$ " + input_total.getText().trim());
private static JLabel input_inMoney = new JLabel(); // 不可见
private static JLabel input_bg_label = new JLabel(new ImageIcon(DIR + "\\01_input.jpg"));
static {
// 设置位置
input_total.setBounds(200, 90, 150, 50);
input_count.setBounds(200, 215, 150, 50);
input_people.setBounds(90, 275, 25, 30);
input_msg.setBounds(180, 340, 200, 50);
input_total_show.setBounds(130, 430, 200, 80);
input_inMoney.setBounds(10, 535, 380, 65);
input_bg_label.setBounds(0, 0, 400, 600); // 背景
// 设置字体
input_total.setFont(fontYaHei);
input_count.setFont(fontYaHei);
input_people.setFont(fontYaHei);
input_msg.setFont(msgFont);
input_msg.setForeground(new Color(255, 233, 38)); // 字体颜色 为金色
input_total_show.setFont(totalShowFont);
input_inMoney.setFont(fontYaHei);
// 透明
input_people.setOpaque(false);
input_total_show.setOpaque(false);
// 编 辑 -- 不可编辑
input_people.setEditable(false);
input_total_show.setEditable(false);
// 边界 -- 无
input_total.setBorder(null);
input_count.setBorder(null);
input_people.setBorder(null);
input_msg.setBorder(null);
input_total_show.setBorder(null);
}
/**
* page2:打开页面 - openPanel . 组件和初始化!
*/
private static JPanel openPanel = new JPanel();
private static JTextField open_ownerName = new JTextField("谁谁谁");
private static JLabel open_label = new JLabel(new ImageIcon(DIR + "\\02_open_2.gif"));
private static JLabel open_bg_label = new JLabel(new ImageIcon(DIR + "\\02_open_1.jpg"));
static {
// 设置 位置.
open_ownerName.setBounds(0, 110, 400, 50);
open_bg_label.setBounds(0, 0, 400, 620);
open_label.setBounds(102, 280, 200, 200);
open_ownerName.setHorizontalAlignment(JTextField.CENTER);
// 设置字体
open_ownerName.setFont(nameFont);
open_ownerName.setForeground(new Color(255, 200, 163)); // 字体颜色 为金色
// 背景色
// open_name.setOpaque(false);
open_ownerName.setBackground(new Color(219, 90, 68));
// 不可编辑
open_ownerName.setEditable(false);
// 边框
open_ownerName.setBorder(null);
}
/**
* page3:展示页面 - showPanel . 组件和初始化!
*/
private static JPanel showPanel = new JPanel();
private static JPanel showPanel2 = new JPanel();
private static JScrollPane show_jsp = new JScrollPane(showPanel2);
private static JLabel show_bg_label = new JLabel(new ImageIcon(DIR + "\\03_money_1.jpg"));
private static JTextField show_name = new JTextField("用户名称");
private static JTextField show_msg = new JTextField("祝福信息");
private static JTextField show_money = new JTextField("99.99");
private static JTextField show_result = new JTextField(count + "个红包共" + (totalMoney / 100.0) + "元,被抢光了");
static {
// 分别设置水平和垂直滚动条自动出现
// jsp.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
// jsp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
/*
* 两部分 页面 . 1.本人获得的红包-- showPanel 2.别人获得的红包-- show_jsp
*/
show_name.setBounds(125, 180, 100, 30);
show_name.setOpaque(false);
show_name.setBorder(null);
show_name.setFont(showNameFont);
show_msg.setBounds(0, 220, 400, 30);
show_msg.setOpaque(false);
show_msg.setBorder(null);
show_msg.setFont(msgFont);
show_msg.setHorizontalAlignment(JTextField.CENTER);
show_money.setBounds(0, 270, 250, 40);
show_money.setOpaque(false);
show_money.setBorder(null);
show_money.setFont(showMoneyFont);
show_money.setForeground(new Color(255, 233, 38)); // 字体颜色 为金色
show_money.setHorizontalAlignment(SwingConstants.RIGHT);
show_result.setBounds(10, 460, 400, 20);
show_result.setOpaque(false);
show_result.setBorder(null);
show_result.setFont(showResultFont);
show_result.setForeground(new Color(170, 170, 170)); // 字体颜色 为灰色
// 设置 图片.
show_bg_label.setBounds(0, 0, 400, 500);
}
static {
// 页面和 背景的对应关系.
panelLable.put(inputPanel, input_bg_label);
panelLable.put(openPanel, open_bg_label);
panelLable.put(showPanel, show_bg_label);
}
private void init() {
// 层次面板-- 用于设置背景
layeredPane = this.getLayeredPane();
// System.out.println("层次面板||" + layeredPane);
// System.out.println(layeredPane);
// 初始化框架 -- logo 和基本设置
initFrame();
// 初始化 三个页面 -- 准备页面
initPanel();
// 2.添加 页面 --第一个页面, 输入 panel 设置到 页面上.
setPanel(inputPanel);
// 3.添加 监听
addListener();
}
/**
* 初始化框架 -- logo 和基本设置
*/
private void initFrame() {
// logo
this.setIconImage(Toolkit.getDefaultToolkit().getImage(DIR + "\\logo.gif"));
// System.out.println("LOGO初始化...");
// 窗口设置
this.setSize(FRAME_WIDTH, FRAME_HEIGHT); // 设置界面大小
this.setLocation(280, 30); // 设置界面出现的位置
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setLayout(null);
// 测试期 注释 拖 拽 , 运行放开
// this.setResizable(false);
this.setVisible(true);
}
/**
* 初始化页面-- 准备三个页面
*/
private void initPanel() {
// System.out.println("页面初始化...");
initInputPanel();
initOpenPanel();
initShowPanel();
}
private void initInputPanel() {
inputPanel.setLayout(null);
inputPanel.setBounds(0, -5, 400, 600);
// this.add(bg_label);
inputPanel.add(input_total);
inputPanel.add(input_count);
inputPanel.add(input_people);
inputPanel.add(input_msg);
inputPanel.add(input_total_show);
inputPanel.add(input_inMoney);
// System.out.println("输入页面||" + inputPanel);
}
private void initOpenPanel() {
openPanel.setLayout(null);
openPanel.setBounds(0, 0, 400, 600);
// this.add(bg_label);
openPanel.add(open_ownerName);
openPanel.add(open_label);
// System.out.println("打开页面||" + openPanel);
}
private void initShowPanel() {
showPanel.setLayout(null);
showPanel.setBounds(10, 10, 300, 600);
// ==============
showPanel.add(show_name);
showPanel.add(show_msg);
showPanel.add(show_money);
showPanel.add(show_result);
// System.out.println("展示页面||" + showPanel);
// ====================================
// showPanel2.setLayout(null);
// showPanel2.setBounds(0, 500, 401, 300);
showPanel2.setPreferredSize(new Dimension(300, 1000));
showPanel2.setBackground(Color.white);
show_jsp.setBounds(0, 500, 400, 110);
}
/**
* 每次打开页面, 设置 panel的方法
*/
private void setPanel(JPanel panel) {
// 移除当前页面
layeredPane.removeAll();
// System.out.println("重新设置:新页面");
// 背景lable添加到layeredPane的默认层
layeredPane.add(panelLable.get(panel), JLayeredPane.DEFAULT_LAYER);
// 面板panel设置为透明
panel.setOpaque(false);
// 面板panel 添加到 layeredPane的modal层
layeredPane.add(panel, JLayeredPane.MODAL_LAYER);
}
// private void setShowPanel(JPanel show) {
// setPanel(show);
// layeredPane.add(show_jsp, JLayeredPane.MODAL_LAYER);
//
// }
/**
* 设置组件的监听器
*/
private void addListener() {
input_total.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
// System.out.println(e);
String input_total_money = input_total.getText();
input_total_show.setText("$ " + input_total_money);
}
});
input_count.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
// System.out.println(e);
// System.out.println("个数:" + input_count.getText());
}
});
input_msg.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
// System.out.println(e);
// System.out.println("留言:" + input_msg.getText());
}
});
input_inMoney.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
try {
// 获取页面的值.
totalMoney = (int) (Double.parseDouble(input_total.getText()) * 100); // 转换成"分"
count = Integer.parseInt(input_count.getText());
if (count > 30) {
JOptionPane.showMessageDialog(null, "红包个数不得超过30个", "红包个数有误", JOptionPane.INFORMATION_MESSAGE);
return;
}
initMoney = totalMoney;
System.out.println("总金额:[" + totalMoney + "]分");
System.out.println("红包个数:[" + count + "]个");
input_inMoney.removeMouseListener(this);
// System.out.println("跳转-->打开新页面");
// 设置群主名称
open_ownerName.setText(ownerName);
// 设置打开页面
setPanel(openPanel);
} catch (Exception e2) {
JOptionPane.showMessageDialog(null, "请输入正确【总金额】或【红包个数】", "输入信息有误", JOptionPane.ERROR_MESSAGE);
}
}
});
// open_ownerName ,点击 [名称],触发的方法 , 提示如何设置群主名称.
open_ownerName.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent arg0) {
JOptionPane.showMessageDialog(null, "请通过【setOwnerName】方法设置群主名称", "群主名称未设置",
JOptionPane.QUESTION_MESSAGE);
}
});
// open label , 点击 [开],触发的方法,提示如何设置打开方式.
open_label.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (openWay == null) {
JOptionPane.showMessageDialog(null, "请通过【setOpenWay】方法设置打开方式", "打开方式未设置",
JOptionPane.QUESTION_MESSAGE);
return;
}
// System.out.println("跳转-->展示页面");
moneyList = openWay.divide(totalMoney, count);
// System.out.println(moneyList);
/*
* showPanel 添加数据
*
*/
show_name.setText(ownerName);
show_msg.setText(input_msg.getText());
if (moneyList.size() > 0) {
show_money.setText(moneyList.get(moneyList.size() - 1) / 100.0 + "");
}
show_result.setText(count + "个红包共" + (initMoney / 100.0) + "元,被抢光了");
open_label.removeMouseListener(this);
setPanel(showPanel);
// 添加数据
for (int i = 0; i < moneyList.size(); i++) {
JTextField tf = new JTextField();
tf.setBorder(null);
tf.setFont(showNameFont);
tf.setHorizontalAlignment(JTextField.LEFT);
if (i == moneyList.size() - 1) {
tf.setText(ownerName + ":\t" + moneyList.get(i) / 100.0 + "元");
} else {
tf.setText("群成员-" + i + ":\t" + moneyList.get(i) / 100.0 + "元");
}
showPanel2.add(tf);
}
layeredPane.add(show_jsp, JLayeredPane.MODAL_LAYER);
}
});
}
/* ======================================================================
* **********************************************************************
* * 以上代码均为页面部分处理,包括布局/互动/跳转/显示等,大家 *
* * *
* * *
* **********************************************************************
* ======================================================================
*/
/**
* ownerName : 群主名称
*/
private String ownerName = "谁谁谁"; // 群主名称
/**
* openWay : 红包的类型 [普通红包/手气红包]
*/
private OpenMode openWay = null;
/**
* 构造方法:生成红包界面。
*
* @param title 界面的标题
*/
public RedPacketFrame(String title) {
super(title);
// 页面相关的初始化
init();
}
public void setOwnerName(String ownerName) {
this.ownerName = ownerName;
}
public void setOpenWay(OpenMode openWay) {
this.openWay = openWay;
}
}
图片文件夹资源:
红包分发的策略:
1.普通红包(平均):totalMoney / totalCount,余数放在最后一个红包当中。
2.手气红包(随机):最少1分钱,最多不超过平均数的2倍。应该越发越少。
自己实现部分:
普通红包
package cn.itcast.demo01;
public class MyRed extends RedPacketFrame {
/**
* 构造方法:生成红包界面。
*
* @param title 界面的标题
*/
public MyRed(String title) {
super(title);
}
}
package cn.itcast.demo01;
import java.util.ArrayList;
public class NormalMode implements OpenMode {
@Override
public ArrayList<Integer> divide(final int totalMoney, final int totalCount) {
ArrayList<Integer> list = new ArrayList<>();
int avg = totalMoney / totalCount; // 平均值
int mod = totalMoney % totalCount; // 余数,模,零头
// 注意totalCount - 1代表,最后一个先留着
for (int i = 0; i < totalCount - 1; i++) {
list.add(avg);
}
// 有零头,需要放在最后一个红包中
list.add(avg + mod);
return list;
}
}
package cn.itcast.demo01;
public class Bootstrap {
public static void main(String[] args) {
MyRed red = new MyRed("我的红包");
// 设置群主名称
red.setOwnerName("王思聪");
// 普通红包
OpenMode normal = new NormalMode();
red.setOpenWay(normal);
}
}
手气红包
package cn.itcast.demo01;
import java.util.ArrayList;
import java.util.Random;
public class RandomMode implements OpenMode {
@Override
public ArrayList<Integer> divide(final int totalMoney, final int totalCount) {
ArrayList<Integer> list = new ArrayList<>();
// 随机分配,有可能多,有可能少。
// 最少1分钱,最多不超过剩下金额平均数的2倍
// 第一次发红包,随机范围是0.01元~6.66元
// 第一次发完之后,剩下的至少是3.34元。
// 此时还需要再发2个红包
// 此时的再发范围应该是0.01~3.34元(取不到右边,剩下0.01)
// 总结一下,范围的公式是:1 + random.nextInt(leftMoney / leftCount * 2);
Random r = new Random(); // 首先创建一个随机数生成器
// totalMoney是总金额,totalCount是总份数,不变
// 额外定义两个变量,分别代表剩下多少钱,剩下多少份
int leftMoney = totalMoney;
int leftCount = totalCount;
// 随机发前n-1个,最后一个不需要随机
for (int i = 0; i < totalCount - 1; i++) {
// 按照公式生成随机金额
int money = r.nextInt(leftMoney / leftCount * 2) + 1;
list.add(money); // 将一个随机红包放入集合
leftMoney -= money; // 剩下的金额越发越少
leftCount--; // 剩下还应该再发的红包个数,递减
}
// 最后一个红包不需要随机,直接放进去就得了
list.add(leftMoney);
return list;
}
}
package cn.itcast.demo01;
public class Bootstrap {
public static void main(String[] args) {
MyRed red = new MyRed("我的红包");
// 设置群主名称
red.setOwnerName("王思聪");
// 普通红包
// OpenMode normal = new NormalMode();
// red.setOpenWay(normal);
// 手气红包
OpenMode random = new RandomMode();
red.setOpenWay(random);
}
}
来源:CSDN
作者:Mr.魏(魏先生)
链接:https://blog.csdn.net/weixin_43873349/article/details/89787113