第一节 继承
1.1 继承的概述
面向对象的三大特征:封装性、继承性、多态性
继承是多态的前提,如果没有继承,就没有多态。
继承主要解决的问题就是:共性抽取
继承关系当中的特点:
1、子类可以拥有父类的"内容"
2、子类还可以拥有自己专有的内容
1.2 继承的格式
在继承的关系中,”子类就是一个父类“,也就是说,子类可以被当作父类看待
定义父类的格式:(一个普通的类定义)
public class 父类名称{
//…
}
定义子类的格式:
public class 子类名称 extends 父类名称{
//…
}
//定义一个父类:员工
public class Employee {
public void method(){
System.out.println("方法执行!");
}
}
//定义一个子类:讲师
public class Teacher extends Employee{
}
//定义一个子类:助手
public class Assistant extends Employee{
}
public class Demo01Extends {
public static void main(String[] args) {
Teacher teacher = new Teacher();
teacher.method();
Assistant assistant = new Assistant();
assistant.method();
}
}
1.3 继承中成员变量的访问特点
在父子类的继承关系当中,如果成员变量重名,则创建子类对象时,访问有两种方式:
直接通过子类对象访问成员变量:
等号左边是谁,就优先用谁,没有则向上找
间接通过成员方法访问成员变量:
该方法属于谁,就优先用谁,没有则向上找
public class Fu {
int numFu = 10;
int num = 100;
public void methodFu(){
//使用的是本类当中的,不会向下找子类的
System.out.println(num);
}
}
public class Zi extends Fu{
int numZi = 20;
int num = 200;
public void methodZi(){
//因为本类当中有num,所以这里用的是本类的num
System.out.println(num);
}
}
public class Demo02ExtendsField {
public static void main(String[] args) {
Fu fu = new Fu();//创建父类对象
System.out.println(fu.numFu);//只能使用父类的东西,没有任何子类内容
Zi zi = new Zi();
System.out.println(zi.numZi);
System.out.println(zi.numFu);
System.out.println("==========");
//等号左边是谁,就优先用谁
System.out.println(zi.num);//200
System.out.println(fu.num);//100
System.out.println("==========");
//这个方法是子类的,优先用子类的,没有再向上找
zi.methodZi();//200
//这个方法是父类当中定义的
zi.methodFu();//100
}
}
1.4 区分子类方法中重名的三种
局部变量: 直接写成员变量名
本类的成员变量: this.成员变量名
父类的成员变量: super.成员变量名
public class Fu1 {
int num = 10;
}
public class Zi1 extends Fu1{
int num = 20;
public void method(){
int num = 30;
System.out.println(num);//30
System.out.println(this.num);//20
System.out.println(super.num);//10
}
}
public class Demo03ExtendsField {
public static void main(String[] args) {
Zi1 zi = new Zi1();
zi.method();
}
}
1.5 继承中成员方法的访问特点
在父子类的继承关系当中,创建子类对象,访问成员方法的规则:
创建的对象是谁,就有先用谁,如果没有则向上找
注意事项:
无论是成员方法还是成员变量,如果没有都是向上找父类,绝对不会向下找子类的
public class Fu2 {
public void fuMethod(){
System.out.println("父类方法执行!");
}
public void method(){
System.out.println("父类重名方法执行!");
}
}
public class Zi2 extends Fu2 {
public void ziMethod(){
System.out.println("子类方法执行!");
}
public void method(){
System.out.println("子类重名方法执行!");
}
}
public class Demo04ExtendsMethod {
public static void main(String[] args) {
Zi2 zi = new Zi2();
zi.ziMethod();//子类方法执行!
zi.fuMethod();//父类方法执行!
//创建了子类对象,所以优先用子类方法
zi.method();//子类重名方法执行!
}
}
1.6 继承中方法的覆盖重写
重写(Override)
概念:在继承关系当中,方法的名称一样,参数列表也一样
重写(Override):方法的名称一样,参数列表【也一样】。覆盖、覆写 重载(Overload):方法的名称一样,参数列表【不一样】
方法的覆盖重写特点:创建的是子类对象,则优先用子类方法
方法覆盖重写的注意事项:
1、必须保证父子类之间方法的名称相同,参数列表也相同
@Override:写在方法前面,用来检测是不是有效的正确覆盖重写
这个注解就算不写,只要满足要求,也是正确的方法覆盖重写
2、子类方法的返回值必须【小于等于】父类方法的返回值范围
Object类是所有类的公共最高父类
3、子类方法的权限必须【大于等于】父类方法的权限修饰符
public>protected>(default)>private
备注:(default)不是关键字default,而是什么都不写,留空
//老款手机
public class Phone {
public void call(){
System.out.println("打电话");
}
public void send(){
System.out.println("发短信");
}
public void show(){
System.out.println("显示号码");
}
}
//新款手机
public class NewPhone extends Phone{
@Override
public void show() {
super.show();
System.out.println("显示名字");
System.out.println("显示头像");
}
}
public class Demo06Phone {
public static void main(String[] args) {
Phone phone = new Phone();
phone.call();
phone.send();
phone.show();
NewPhone newPhone = new NewPhone();
newPhone.call();
newPhone.send();
newPhone.show();
}
}
1.7 继承中构造方法的访问特点
继承关系中,父子类构造方法的访问特点:
1、子类构造方法当中有一个默认隐含的“super()”调用,所以一定是先调用父类构造方法,后执行的子类构造
2、子类构造可以通过super关键字来调用父类重载构造
3、super的父类构造调用,必须是子类构造方法的第一个语句,不能一个子类构造调用多次super构造
总结:
子类必须调用父类构造方法,不写则赠送super();写了则用写的指定的super调用,super只能有一个,还必须是第一个
1.8 super关键字的三种用法
super关键字的用法有三种:
1、在子类的成员方法中,访问父类的成员变量
2、在子类的成员方法中,访问父类的成员方法
3、在子类的构造方法中,访问父类的构造方法
public class Fu3 {
int num = 10;
public void method(){
System.out.println("父类方法");
}
}
public class Zi3 extends Fu3{
int num = 20;
public Zi3(){
super();
}
public void methodZi(){
System.out.println(super.num);//父类中的num
}
public void method(){
super.method();//访问父类中的method
System.out.println("子类方法");
}
}
1.9 this关键字的三种用法
super关键字用来访问父类内容,而this关键字用来访问本类内容。用法有三种:
1、在本类的成员方法中,访问本类的成员变量
2、在本类的成员方法中,访问本类的另一个成员方法
3、在苯类的构造方法中,访问本类的另一个构造方法
注意事项:
1、this(…)调用也必须是构造方法的第一个语句,唯一一个
2、super和this两种构造调用,不能同时使用
1.10 this和super关键字的内存图
Java语言是单继承的,一个类的直接父类只能有唯一一个
Java语言可以多级继承
一个子类的直接父类是唯一的,但是一个父类可以拥有很多个子类
第二节 抽象类
2.1 抽象的概念
如果父类当中的方法不确定如何进行{}方法体实现,那么这就应该是一个抽象方法
抽象方法:就是加上abstract关键字,然后去掉大括号,直接分号结束
抽象类:抽象方法所在的类,必须是抽象类才行,在class之前写上abstract即可
public abstract class Animal {
//这事一个抽象方法,代表吃东西,但是具体吃什么(大括号的内容)不确定
public abstract void eat();
//这事普通的成员方法
public void normalMethod(){
}
}
2.2 抽象方法和抽象类的使用
如何使用抽象类和抽象方法:
1、不能直接创建new抽象类对象
2、必须用一个子类来继承抽象父类
3、子类必须覆盖重写抽象父类当中所有的抽象方法
覆盖重写(实现):子类去掉抽象方法的abstract关键字,然后补上方法体大括号
4、创建子类对象进行使用
public abstract class Animal {
//这事一个抽象方法,代表吃东西,但是具体吃什么(大括号的内容)不确定
public abstract void eat();
//这事普通的成员方法
public void normalMethod(){
}
}
public class Cat extends Animal{
@Override
public void eat(){
System.out.println("猫吃鱼");
}
}
public class Demo01Main {
public static void main(String[] args) {
Cat cat = new Cat();
cat.eat();
}
}
注意事项:
1、抽象类不能创建对象,如果创建,编译无法通过而报错,只能创建其非抽象子类的对象
2、抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的
3、抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类
4、抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错,除非该子类也是抽象类
一个抽象类不一定含有抽象方法
只要保证抽象方法所在的类是抽象类即可
这样没有抽象方法的抽象类,也不能直接创建对象,在一些特殊场景下有用途
2.3 练习
群聊发红包
//定义用户
public class User {
private String name;//姓名
private int money;//余额,也就是当前用户的金额
public User() {
}
public User(String name, int money) {
this.name = name;
this.money = money;
}
//展示用户抢红包前的余额
public void startMoney(){
System.out.println("我是" + name + ",我的余额有:" + money + "元");
}
//展示用户抢红包后的余额
public void endMoney(){
System.out.println("我是" + name + ",我的余额有:" + money + "元");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
//定义发红包者
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;
}
}
//定义抢红包者
import java.util.ArrayList;
import java.util.Random;
public class Member extends User{
public Member() {
}
public Member(String name, int money) {
super(name, money);
}
public void receive (ArrayList<Integer> list){
//从多个红包当中随便抽取一个,给自己
//随机获取一个集合当中的索引编号
int index = new Random().nextInt(list.size());
//根据索引,从集合当中删除,并且得到被删除的红包,给自己
int delta = list.remove(index);
System.out.println("我是" + getName() + ",我抢到了:" + delta + "元");
//当前用户原本的金额
int money = super.getMoney();
//重新设置余额
super.setMoney(money + delta);
}
}
import java.util.ArrayList;
public class RedPackage {
public static void main(String[] args) {
Manager manager = new Manager("小松狮",1000);
Member memberA = new Member("成员A", 20);
Member memberB = new Member("成员B", 50);
Member memberC = new Member("成员C", 30);
manager.startMoney();
memberA.startMoney();
memberB.startMoney();
memberC.startMoney();
System.out.println("开始抢红包啦!");
ArrayList<Integer> redList = manager.send(200,3);
memberA.receive(redList);
memberB.receive(redList);
memberC.receive(redList);
manager.endMoney();
memberA.endMoney();
memberB.endMoney();
memberC.endMoney();
}
}
第三节 接口
3.1 接口的概述与定义格式
接口就是多个类的公共规范
接口是一种引用数据类型,最重要的内容就是其中的:抽象方法
如何定义一个接口的格式:
public interface 接口名称{
//接口内容
}
备注:换成了关键字interface之后,编译生成的字节码文件仍然是.java–>.class
3.2 接口的抽象方法定义与使用
在任何版本的java中,接口都能定义抽象方法:
格式:
public abstract 返回值类型 方法名称(参数列表);
注意事项:
1、接口当中的抽象方法,修饰符必须是两个固定的关键字: public abstract
2、这两个关键字修饰符,可以选择性地省略
3、方法的三要素,可以随意定义
接口使用步骤:
1、接口不能直接使用,必须有一个”实现类“来”实现“该接口
格式:
public class 实现类名称 implements 接口名称{
//…
}
2、接口的实现类必须覆盖重写(实现)接口中所有的抽象方法
实现:去掉abstract关键字,加上方法体大括号
3、创建实现类的对象,进行使用
注意事项:
如果实现类并没有覆盖重写接口中的所有的抽象方法,那么这个实现类自己就必须是抽象类
public interface MyInterfaceAbstract {
public abstract void methodAbs();
}
public class MyInterfaceAbstractImpl implements MyInterfaceAbstract{
@Override
public void methodAbs() {
System.out.println("这是一个方法");
}
}
public class Interface {
public static void main(String[] args) {
//错误写法!不能直接new接口对象使用
//MyInterfaceAbstract inter = new MyInterfaceAbstract();
//创建实现类的对象使用
MyInterfaceAbstractImpl impl = new MyInterfaceAbstractImpl();
impl.methodAbs();
}
}
3.3 接口的默认方法定义与使用
从Java 8开始,接口里允许定义默认方法
格式: public default 返回值类型 方法名称(参数列表)
{
方法体
}备注:接口当中的默认方法,可以解决接口升级的问题
public interface MyInterfaceDefault {
//抽象方法
public abstract void methodAbs();
public default void methodDefault(){
System.out.println("这是新添加的默认方法!");
}
}
public class MyInterfaceDefaultA implements MyInterfaceDefault{
@Override
public void methodAbs() {
System.out.println("实现类A");
}
}
public class MyInterfaceDefaultB implements MyInterfaceDefault{
@Override
public void methodAbs() {
System.out.println("实现类B");
}
@Override
public void methodDefault() {
System.out.println("实现类B覆盖重写了接口的默认方法");
}
}
public class Demo01Interface {
public static void main(String[] args) {
//创建了实现类的对象
MyInterfaceDefaultA a = new MyInterfaceDefaultA();
a.methodAbs();//调用抽象方法,实际运行的是右侧实现类
//调用默认方法,如果实现类当中没有,会向上找接口
a.methodDefault();//这是新添加的默认方法!
MyInterfaceDefaultB b = new MyInterfaceDefaultB();
b.methodAbs();//实现类B
b.methodDefault();//实现类B覆盖重写了接口的默认方法
}
}
3.4 接口的静态方法定义与使用
从Java8开始,接口当中允许定义静态方法
格式:
public static 返回值类型 方法名称(参数列表)
{
方法体
}
提示:就是将abstract或者default换成static即可,带上方法体
public interface MyInterfaceStatic {
public static void methodStatic(){
System.out.println("这是接口的静态使用!");
}
}
注意:不能通过接口实现类的对象来调用接口当中的静态方法
正确用法:通过接口名称,直接调用其中的静态方法
格式:
接口名称.静态方法名(参数);
public class Demo02Interface {
public static void main(String[] args) {
MyInterfaceStaticImpl impl = new MyInterfaceStaticImpl();//没必要写
//impl.methodStatic();错误写法!
MyInterfaceStatic.methodStatic();
}
}
3.5 接口的私有方法定义与使用
问题描述:
我们需要抽取一个共有方法,用来解决两个默认方法之间重复代码的问题
但是这个共有方法不应该让实现类使用,应该是私有化的
解决方案:
从java9开始,接口当中允许定义私有方法
1、普通私有方法,解决多个默认方法之间重复代码问题
格式: private 返回值类型 方法名称(参数列表){ 方法体 }
2、静态私有方法,解决多个静态方法之间重复代码问题
格式: private static 返回值类型 方法名称(参数列表){ 方法体 }
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");
}
}
public interface MyInterfacePrivateB {
public static void methodDefault1(){
System.out.println("静态方法1");
methodCommon();
}
public static void methodDefault2(){
System.out.println("静态方法2");
methodCommon();
}
private static void methodCommon(){
System.out.println("AAA");
System.out.println("BBB");
System.out.println("CCC");
}
}
3.6 接口的常量定义与使用
接口当中也可以定义”成员变量“,但是必须使用public static final三个关键字进行修饰
从效果上看,这其实就是接口的【常量】
格式:
public static final 数据类型 常量名称 = 数据值;
备注:
一旦使用final关键字进行修饰,说明不可改变
注意事项:
1、接口当中的常量,可以省略public static final,注意:不写也照样是这样
2、接口当中的常量,必须进行赋值,不能不赋值
3、接口中常量的名称,使用完全大写的字母,用下划线进行分隔(推荐命名)
public interface MyInterfaceConst {
//这其实就是一个常量,一旦赋值,不可以修改
public static final int NUM_OF_MY_CLASS = 10;
}
public class Demo03Interface {
public static void main(String[] args) {
System.out.println(MyInterfaceConst.NUM_OF_MY_CLASS);
}
}
3.7 继承父类并实现多个接口
使用接口的时候,需要注意:
1、接口是没有静态代码块或者构造方法的
2、一个类的直接父类是唯一的,但是一个类可以同时实现多个接口
格式:
public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB{
//覆盖重写所有抽象方法
}
3、如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可
4、如果实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类就必须是一个抽象类
5、如果实现类所实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写
6、一个类如果直接父类当中的方法,和接口当中的默认方法产生了冲突,优先用父类当中的方法
public interface MyInterfaceA {
public abstract void methodA();
public abstract void methodAbs();
}
public interface MyInterfaceB {
public abstract void methodB();
public abstract void methodAbs();
}
public class MyInterfaceImpl implements MyInterfaceA,MyInterfaceB{
@Override
public void methodA() {
System.out.println("覆盖重写了A方法");
}
@Override
public void methodAbs() {
System.out.println("覆盖重写了AB接口都有的抽象方法");
}
@Override
public void methodB() {
System.out.println("覆盖重写了B方法");
}
}
1、类与类之间是单继承的,直接父类只有一个
2、类与接口之间是多实现的,一个类可以实现多个接口
3、接口与接口之间是多继承的
注意事项:
1、多个父接口当中的抽象方法如果重复,没关系
2、多个父接口当中的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写,而且要带着default关键字
第四节 多态
4.1 多态的概述和使用
面向对象三大特征:封装性、继承性、多态性
extends继承或者implements实现,是多态性的前提
一个对象同时拥有多种形态,这就叫做对象的多态性
代码当中体现多态性,其实就是一句话:父类引用指向子类对象
格式:
父类名称 对象名 = new 子类名称();
或者:
接口名称 对象名 = new 实现类名称();
public class Fu {
public void method(){
System.out.println("父类方法");
}
public void methodFu(){
System.out.println("父类持有方法");
}
}
public class Zi extends Fu{
@Override
public void method() {
System.out.println("子类方法");
}
}
public class Demo01Multi {
public static void main(String[] args) {
//使用多态写法
//左侧父类的引用,指向了右侧子类的对象
Fu obj = new Zi();
obj.method();//子类方法
obj.methodFu();//父类持有方法
}
}
4.2 多态中成员变量的使用特点
访问成员变量的两种方式:
1、直接通过对象名称访问:看等号左边是谁,优先用谁,没有则向上找
2、间接通过成员方法访问:看该方法属于谁,优先用谁,没有则向上找
public class Fu {
int num = 10;
public void showNum(){
System.out.println(num);
}
}
public class Zi extends Fu{
int num = 20;
int age = 18;
@Override
public void showNum() {
System.out.println(num);
}
}
public class Demo01MultiField {
public static void main(String[] args) {
//使用多态写法,父类引用指向子类对象
Fu obj = new Zi();
System.out.println(obj.num);//10
// System.out.println(obj.age);//错误写法!
System.out.println("========");
//子类没有覆盖重写,就是父:10
//子类如果覆盖重写,就是子:20
obj.showNum();//20
}
}
在多态的代码当中,成员方法的访问规则是:
看new的是谁,就优先用谁,没有则向上找
口诀:编译看左边,运行看右边
对比:
成员变量:编译看左边,运行还看左边
成员方法:编译看左边,运行看右边
public class Fu {
int num = 10;
public void showNum(){
System.out.println(num);
}
public void method(){
System.out.println("父类方法");
}
public void methodFu(){
System.out.println("父类特有方法");
}
}
public class Zi extends Fu{
int num = 20;
int age = 18;
@Override
public void showNum() {
System.out.println(num);
}
@Override
public void method() {
System.out.println("子类方法");
}
public void methodZi(){
System.out.println("子类特有方法");
}
}
public class Demo02MultiMethod {
public static void main(String[] args) {
Fu obj = new Zi();
obj.method();//优先用子
obj.methodFu();//子类没有,父类有,找到父类
// 编译看左边,左边是Fu,Fu当中没有methodZi方法,所以编译报错
// obj.methodZi(); 错误写法!
}
}
4.3 对象的向上转型
对象的向上转型,其实就是多态写法:
格式:父类名称 对象名 = new 子类名称();
含义:右侧创建一个子类对象,把它当作父类看待使用
注意事项:向上转型一定是安全的,从小范围转向了大范围
类似于:
double num = 100;//正确,int---->double,自动类型转换
public abstract class Animal {
public abstract void eat ();
}
public class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
public class Main {
public static void main(String[] args) {
Animal one = new Cat();
one.eat();
}
}
4.4 对象的向下转型
向上转型一定是安全的,没有问题的,但是有一个弊端:
对象一旦向上转型为父类,那么就无法调用子类原本持有的内容
解决方案:用对象的向下转型【还原】
格式:子类名称 对象名 = (子类名称) 父类对象;
含义:将父类对象【还原】成为本来的子类对象
注意事项:
1、必须保证对象本来创建的时候,就是猫,才能向下转型成为猫
2、如果对象创建的时候本来不是猫,现在非要向下转型成为猫,就会报错,ClassCastException
类似于:
int num = (int) 10.0;//正确
int num = (int) 10.5;//不可以,精度损失
public abstract class Animal {
public abstract void eat ();
}
public class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
public void catchMouse(){
System.out.println("猫捉老鼠");
}
}
public class Main {
public static void main(String[] args) {
Animal one = new Cat();
one.eat();//猫吃鱼
Cat cat = (Cat) one;
cat.catchMouse();//猫捉老鼠
}
}
4.5 instanceof关键字
如何才能知道一个父类引用的对象,本来是什么子类?
格式: 对象 instanceof 类名称
这将会得到一个boolean值结果,也就是判断前面的对象能不能当作后面类型的实例
public class Dog extends Animal{
@Override
public void eat() {
System.out.println("够吃肉");
}
public void watchHouse(){
System.out.println("狗看家");
}
}
public class Instanceof {
public static void main(String[] args) {
Animal animal = new Cat();
animal.eat();//猫吃鱼
//如果希望调用子类特有方法,需要向下转型
//判断一下父类引用animal本来是不是Cat
if (animal instanceof Cat){
Cat cat = (Cat) animal;
cat.catchMouse();
}
if (animal instanceof Dog){
Dog dog = (Dog) animal;
dog.watchHouse();
}
}
}
4.6 练习
定义USB接口,具备最基本的开启功能和关闭功能。鼠标和键盘要想在电脑上使用,那么鼠标和键盘也必须遵守USB规范,实现USB接口,否则鼠标和键盘生产出来也无法使用
进行描述笔记本类,实现笔记本使用USB鼠标、USB键盘
USB接口,包含打开设备功能、关闭设备功能
笔记本类,包含开机功能、关机功能、使用USB设备功能
鼠标类,要实现USB接口,并具备点击的方法
键盘类,要实现USB接口,具备敲击的方法
public interface USB {
public abstract void open();
public abstract void close();
}
public class Computer {
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 Keybroad){
Keybroad keybroad = (Keybroad) usb;
keybroad.input();
}
usb.close();
}
}
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("鼠标点击");
}
}
public class Keybroad implements USB{
@Override
public void open() {
System.out.println("打开键盘");
}
@Override
public void close() {
System.out.println("关闭键盘");
}
public void input(){
System.out.println("键盘输入");
}
}
public class Main {
public static void main(String[] args) {
Computer computer = new Computer();
computer.powerOn();
// Mouse mouse = new Mouse();
USB usbMouse = new Mouse();//多态写法
computer.usbDevice(usbMouse);
Keybroad keybroad = new Keybroad();//没有使用多态写法
//方法参数是USB类型,传递进去的是实现类对象
computer.usbDevice(keybroad);
computer.powerOff();
}
}
第五节 final关键字
5.1 final关键字概念与四种用法
final关键字代表最终、不可改变的
常见四种用法:
1、可以用来修饰一个类
2、可以用来修饰一个方法
3、可以用来修饰一个局部变量
4、可以用来修饰一个成员变量
5.2 final关键字修饰类
当final关键字用来修饰一个类的时候
格式:
public final class 类名称{
// …
}
含义:当前这个类不能有任何的子类
注意事项:
1、不能使用一个final类作为父类
2、一个类如果是final的,那么其中所有的成员方法都无法进行覆盖重写
public final class MyClass {
public void method(){
System.out.println("方法执行");
}
}
5.3 final关键字修饰成员方法
当final关键字用来修饰一个方法的时候,这个方法就是最终方法,也就是不能被覆盖重新
格式:
修饰符 final 返回值类型 方法名称(参数列表){
//方法体
}
注意事项:
对于类、方法来说,abstract关键字和final关键字不能同时使用,因为矛盾
public abstract class Fu {
public void method(){
System.out.println("父类方法执行");
}
//public abstract final void methodAbs();错误!
public abstract void methodAbs();
}
public class Zi extends Fu{
@Override
public void method() {
System.out.println("子类覆盖重写父类的方法");
}
@Override
public void methodAbs() {
}
}
5.4 final关键字修饰局部变量
一旦使用final用来修饰局部变量,那么这个变量就不能进行更改
对于基本类型来说,不可变说的是变量当中的数据不可改变
对于引用类型来说,不可变说的是变量当中的地址值不可改变
5.5 final关键字修饰成员变量
对于成员变量来说,如果使用final关键字修饰,那么这个变量也照样不可变
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;
}
// public void setName(String name) {
// this.name = name;
// }
}
5.6 权限修饰符
第六节 内部类
6.1 内部类的概念与分类
如果一个事物的内部包含另一个事物,那么这就是一个类内部包含另一个类
分类:
1、成员内部类
2、局部内部类(包含匿名内部类)
成员内部类的定义格式: 修饰符 class 外部类名称{
修饰符 class 内部类名称{
//…
}
//…
}
注意:内用外,随意访问;外用内,需要借助内部类对象
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("外部类的方法");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
如何使用成员内部类?两种方式:
1、间接方式:在外部类的方法当中,使用内部类;然后main只能调用外部类的方法
2、直接方式:公式:
类名称 对象名 = 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("外部类的方法");
Heart heart = new Heart();
heart.beat();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Demo01InnerClass {
public static void main(String[] args) {
//间接方式
Body body = new Body();
body.methodBody();
//直接方式
Body.Heart heart = new Body().new Heart();
heart.beat();
}
}
6.2 内部类的同名变量访问
如果出现了重名现象,那么格式是:外部类名称.this.外部类成员变量名
public class Outer {
int num = 10;
public class Inner{
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) {
Outer.Inner obj = new Outer().new Inner();
obj.methodInner();
}
}
6.3 局部内部类定义
如果一个类是定义在一个方法内部的,那么这就是一个局部内部类
”局部“:只有当前所属的方法才能使用它,出了这个方法外面就不能用了
定义格式:
修饰符 class 外部类名称{
修饰符 返回值类型 外部类方法名称(参数列表){
class 局部内部类名称{
//…
}
}
}
小节一下类的权限修饰符:
public > protected > (default) > private
定义一个类的时候,权限修饰符规则:
1、外部类:public/(default)
2、成员内部类:public/protected/(default)/private
3、局部内部类:什么都不能写
public class Outer {
public void methodOuter(){
class Inner{
int num = 10;
public void methodInner(){
System.out.println(num);
}
}
Inner inner = new Inner();
inner.methodInner();
}
}
public class DemoMain {
public static void main(String[] args) {
Outer obj = new Outer();
obj.methodOuter();
}
}
局部内部类如果希望访问所在方法的局部变量,那么这个局部变量必须是【有效final的】
备注:从Java 8+开始,只要局部变量事实不变,那么final关键字可以省略
原因:
1、new出来的对象在堆内存当中
2、局部变量是跟着方法走的,在栈内存当中
3、方法运行结束之后,立刻出栈,局部变量就会立刻消失
4、但是new出来的对象会在堆当中持续存在,直到垃圾回收消失
6.4 匿名内部类
如果接口的实现类(或者是父类的子类)只需要使用唯一的一次
那么这种情况下就可以省略掉该类的定义,而改为使用【匿名内部类】
匿名内部类的定义格式:
接口名称 对象名 = new 接口名称(){
//覆盖重写所有抽象方法
};
public interface MyInterface {
void method();
}
public class MyDemo {
public static void main(String[] args) {
MyInterface myInterface = new MyInterface() {
@Override
public void method() {
System.out.println("匿名内部类实现了方法");
}
};
myInterface.method();
}
}
对格式"new 接口名称(){…}"进行解析:
1、new代表创建对象的动作
2、接口名称就是匿名内部类需要实现哪个接口
3、{…}这才是匿名内部类的内容
另外还要注意几点问题:
1、匿名内部类,在【创建对象】的时候,只能使用唯一一次
如果希望多次创建对象,而且类的内容一样的话,那么就必须使用单独定义的实现类了
2、匿名对象,在【调用方法】的时候,只能调用唯一一次
如果希望同一个对象,调用多次方法,那么必须给对象起个名字
3、匿名内部类是省略了【实现类/子类名称】,但是匿名对象是省略了【对象名称】
public interface MyInterface {
void methodA();
void methodB();
}
public class MyDemo {
public static void main(String[] args) {
MyInterface myInterface1 = new MyInterface() {
@Override
public void methodA() {
System.out.println("匿名内部类实现了方法111");
}
@Override
public void methodB() {
System.out.println("匿名内部类实现了方法222");
}
};
myInterface1.methodA();
myInterface1.methodB();
new MyInterface(){
@Override
public void methodA() {
System.out.println("匿名内部类实现了方法333");
}
@Override
public void methodB() {
System.out.println("匿名内部类实现了方法444");
}
}.methodA();
}
}
6.5 类作为成员变量类型
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 void attack(){
System.out.println(age + "岁的" + name + "使用" + weapon.getCode() + "攻击对方");
}
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 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 Main {
public static void main(String[] args) {
Hero hero = new Hero();
hero.setName("盖伦");
hero.setAge(20);
Weapon weapon = new Weapon();
weapon.setCode("多兰剑");
hero.setWeapon(weapon);
hero.attack();
}
}
6.6 接口作为成员变量类型
public class Hero {
private String name;
private Skill skill;
public Hero() {
}
public Hero(String name, Skill skill) {
this.name = name;
this.skill = skill;
}
public void attack(){
System.out.println(name + "开始施放技能:");
skill.use();
System.out.println("施放技能完毕");
}
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 interface Skill {
void use();
}
public class Main {
public static void main(String[] args) {
Hero hero = new Hero();
hero.setName("艾希");
/* 匿名内部类:
Skill skill = new Skill() {
@Override
public void use() {
System.out.println("biu~biu~biu");
}
};
hero.setSkill(skill);*/
//使用匿名内部类和匿名对象
hero.setSkill(new Skill() {
@Override
public void use() {
System.out.println("Biu~pia~biu~pia");
}
});
hero.attack();
}
}
6.7 接口作为方法的参数和或返回值
import java.util.ArrayList;
import java.util.List;
/*
java.util.list正是ArrayList所实现的接口
*/
public class MainInterface {
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("小锦鲤");
return list;
}
}
6.8 随机红包案例
public class RandomMode {
public ArrayList<Integer> divide(final int totalMoney, final int totalCount){
ArrayList<Integer> list = new ArrayList<>();
//随机分配,有可能多,有可能少
//最少一分钱,最多不超过"剩下金额平均数的2倍"
//第一次发红包,随即范围是0.01元-6.66元
//第一次发完之后,剩下的至少3.34元
//此时还需要再发2个红包
//此时的再发范围应该是0.01元-3.34元(取不到右边,剩下0.01)
Random r = new Random();//创建一个随机数生成器
int leftMoney = totalMoney;//剩下多少钱
int leftCount = totalCount;//剩下多少份
//随机发前n-1个,最后一个不需要随机
for (int i = 0; i < totalMoney - 1; i++){
//按照公式生成随机金额
int money = r.nextInt(leftMoney / leftCount * 2) + 1;
list.add(money);//将一个随机红包放入集合
leftMoney -= money;//剩下的金额越发越少
leftCount --;//剩下还应该再发的红包个数,递减
}
list.add(leftMoney);//最后一个红包不需要随机,直接放入
return list;
}
}