Java学习 day11 (继承与多态)接口、多态

前言:

前天我们学习了继承,子类可以在父类的基础上重写父类内容,这样很容易误伤很多固定用法,已经写好的类,为了避免这种情况,java提供了final关键字,用来修饰不可改变内容。

我的理解是,类似于我们相机里的上锁,使用final关键字就相当于给一些内容上锁,让他们不会被轻易改变。


今日重点:

final关键字

权限

内部类

引用类型


文章索引:

final关键字

修饰类:

修饰方法:

修饰局部变量:

修饰成员变量:

权限修饰符

内部类 

内部类的同名变量访问 :

局部内部类:

类的权限修饰符小结:

匿名内部类: 

引用类型用法总结

类作为成员变量接口

接口作为成员变量

接口作为方法的参数和返回值

发红包案例 


final关键字

final关键字代表最终、不可改变的。

常见四种用法:
1. 可以用来修饰一个类
2. 可以用来修饰一个方法
3. 还可以用来修饰一个局部变量
4. 还可以用来修饰一个成员变量

修饰类:

当final关键字用来修饰一个类的时候,格式:
public final class 类名称 {
    // ...
}
含义:当前这个类不能有任何的子类。(太监类)
注意:一个类如果是final的,那么其中所有的成员方法都无法进行覆盖重写(因为没儿子。)

 代码示例:

public final class MyClass /*extends Object*/ {
    public void method() {
        System.out.println("方法执行!");
    }
}

修饰方法:

当final关键字用来修饰一个方法的时候,这个方法就是最终方法,也就是不能被覆盖重写。
格式:
修饰符 final 返回值类型 方法名称(参数列表) {
    // 方法体
}

注意事项:
对于类、方法来说,abstract关键字(抽象方法)和final关键字不能同时使用,因为矛盾。

因为抽象方法一定要被子类覆盖重写,然而final关键字代表不能被覆盖重写,他俩矛盾所以不能同时使用。

 示例代码:

public final void method() {
        System.out.println("方法执行!");
    }

修饰局部变量:

只有方法里的才叫局部变量,类里的叫成员变量。

    public static void main(String[] args) {
        // 一旦使用final用来修饰局部变量,那么这个变量就不能进行更改。
        // “一次赋值,终生不变”
        final int num2 = 200;
        System.out.println(num2); // 200


        // 正确写法!只要保证有唯一一次赋值即可
        final int num3;
        num3 = 30;

引申——什么叫做不可变?

对于基本类型来说,不可变指的是数值不可变,比如int  abc= 10,那么abc一辈子就等于10了,而对于引用类型中的不可变,指的是指向地址不可变(他的内容是可以改变的!)。

        final Student stu2 = new Student("高圆圆");
        // 错误写法!final的引用类型变量,其中的地址不可改变
//        stu2 = new Student("赵又廷");
        System.out.println(stu2.getName()); // 高圆圆
        stu2.setName("高圆圆圆圆圆圆");
        System.out.println(stu2.getName()); // 高圆圆圆圆圆圆

这里通过set方法修改了值,但他的引用并没有发生改变,但是内容却改变了。


修饰成员变量:

对于成员变量来说,如果使用final关键字修饰,那么这个变量也照样是不可变。

1. 由于成员变量具有默认值,所以用了final之后必须手动赋值,不会再给默认值了。
2. 对于final的成员变量,要么使用直接赋值,要么通过构造方法赋值。二者选其一。
3. 必须保证类当中所有重载的构造方法,都最终会对final的成员变量进行赋值。
public class Person {

    private final String name/* = "鹿晗"*/;  
//这里使用了final关键字对他进行修饰,可以直接在后面对他进行赋值。

    public Person() {
        name = "关晓彤";  //这里通过构造方法对他进行了赋值。
    }

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

    public String getName() {
        return name;
    }

//    public void setName(String name) {
//        this.name = name;
//    }
//不能在这样写了!!因为与final关键字矛盾!使用了这个关键字只能赋值一次!不能再用构造方法赋值为空
//了!!
}

权限修饰符

Java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限,

public:公共的。

protected:受保护的

default:默认的

private:私有的

注意事项:(default)并不是关键字“default”,而是根本不写。

可见,public具有最大权限。private则是最小权限。

编写代码时,如果没有特殊的考虑,建议这样使用权限:

成员变量使用 private  ,隐藏细节。

构造方法使用 public  ,方便创建对象。

成员方法使用 public  ,方便调用方法。

小贴士:不加权限修饰符,其访问能力与default修饰符相同


内部类 

内部类说白了就是在一个类里还有一个类,就像身体里包含了心脏。

如果一个事物的内部包含另一个事物,那么这就是一个类内部包含另一个类。
例如:身体和心脏的关系。又如:汽车和发动机的关系。

分类:
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("外部类的方法");
        new Heart().beat();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

那么如何外部类,如何使用成员内部类呢?

如何使用成员内部类?有两种方式:
1. 间接方式:在外部类的方法当中,使用内部类;然后main只是调用外部类的方法。
2. 直接方式,公式:
类名称 对象名 = new 类名称();
【外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();】

第一种方法示例:

//在类body中的代码:

    public class Heart { // 成员内部类
        // 内部类的方法
        public void beat() {
            System.out.println("心脏跳动:蹦蹦蹦!");
            System.out.println("我叫:" + name); // 正确写法!
        }
    }

    // 外部类的方法
    public void methodBody() {
        System.out.println("外部类的方法");
        new Heart().beat();//这里是外部类创建了个匿名对象,间接访问了内部类中的方法
    }


//测试类中的代码:
    public static void main(String[] args) {
        Body body = new Body(); // 创建了一个外部类的对象
        // 通过外部类的对象,调用外部类的方法,里面间接在使用内部类Heart
        body.methodBody();
    }

 运行结果:

外部类的方法
心脏跳动:蹦蹦蹦!
我叫:null

第二种方法示例代码:

//测试类中的方法
//公式:【外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();】
        Body.Heart heart = new Body().new Heart();
        heart.beat();

运行结果:

心脏跳动:蹦蹦蹦!
我叫:null


内部类的同名变量访问 :

如果出现了重名现象,那么格式是:外部类名称.this.外部类成员变量名
//这里是外部类
public 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();
    }
}

局部内部类:

如果一个类是定义在一个方法内部的,那么这就是一个局部内部类。
“局部”:只有当前所属的方法才能使用它,出了这个方法外面就不能用了。

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

 代码示例:

//这里是一个外部类
class Outer {
//这是一个方法
    public void methodOuter() {
//这是在方法里的类,所以叫局部内部类
        class Inner { // 局部内部类
            int num = 10;
            public void methodInner() {
                System.out.println(num); // 10
            }
        }
        Inner inner = new Inner();  //②再通过外部方法创建内部类对象
        inner.methodInner();  //③再用创建好的内部类对象调用局部内部类的方法
    }
}


//测试类:
public class DemoMain {
    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关键字可以省略。

原因:
1. new出来的对象在堆内存当中。
2. 局部变量是跟着方法走的,在栈内存当中。
3. 方法运行结束之后,立刻出栈,局部变量就会立刻消失。
4. 但是new出来的对象会在堆当中持续存在,直到垃圾回收消失。

示例代码:

//这里是个外部类
public class MyOuter {
//外部类中的方法
    public void methodOuter() {
        int num = 10; // 所在方法的局部变量
      //局部内部类
        class MyInner {
            public void methodInner() {
                System.out.println(num);
            }
        }
    }
}

匿名内部类: 

匿名内部类 :是内部类的简化写法。它的本质是一个 带具体实现的父类或者父接口的匿名的子类对象

开发中,最常用到的内部类就是匿名内部类了。以接口举例,当你使用一个接口时,似乎得做如下几步操作:

1. 定义子类

2. 重写接口中的方法

3. 创建子类对象

4. 调用重写后的方法

我们的目的,最终只是为了调用方法,那么能不能简化一下,把以上四步合成一步呢?匿名内部类就是做这样的快捷方式。

使用前提:

匿名内部类必须继承一个父类或者实现一个父接口


使用格式:

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

匿名内部类的定义格式:
接口名称 对象名 = new 接口名称() {
    // 覆盖重写所有抽象方法
};

 代码示例:

//这里是一个接口:
public interface MyInterface {
    void method1(); // 抽象方法
}

//这里是测试类:
public class DemoMain {
//主方法
    public static void main(String[] args) {

        // 使用匿名内部类,但不是匿名对象,对象名称就叫objA
        MyInterface objA = new MyInterface() {
         //这里重写了抽象方法
            @Override
            public void method1() {
                System.out.println("匿名内部类实现了方法!111-A");
            }
        };
        objA.method1();  //通过匿名内部类对象调用了里面的方法

匿名内部类的原理:

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

这里的匿名内部类的内容是写在接口里的!


匿名内部类的注意事项: 

1. 匿名内部类,在【创建对象】的时候,只能使用唯一一次。
如果希望多次创建对象,而且类的内容一样的话,那么就需要使用单独定义的实现类了。
2. 匿名对象,在【调用方法】的时候,只能调用唯一一次。
如果希望同一个对象,调用多次方法,那么必须给对象起个名字。
3. 匿名内部类是省略了【实现类/子类名称】,但是匿名对象是省略了【对象名称】
强调:匿名内部类和匿名对象不是一回事!!!

引用类型用法总结

类作为成员变量接口

类里面的成员变量不止可以用基本类型对他进行赋值,也可以用引用类型(比如类)对他进行赋值。

比如:

// 这里是一个类
public class Hero {
    private String name; // 英雄的名字  (这里就是基本类型进行赋值)
    private int age; // 英雄的年龄  (这里就是基本类型进行赋值)
    private Weapon 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() + "攻击敌方。");
    }
//get set 基本方法
    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;
    }
//基本的get set
    public String getCode() {
        return code;
    }
    public void setCode(String code) {
        this.code = code;
    }
}

主程序/测试类:

public class DemoMain {
    public static void main(String[] args) {
        // 创建一个英雄角色
        Hero hero = new Hero();
        // 为英雄起一个名字,并且设置年龄
        hero.setName("盖伦");
        hero.setAge(20);

        // 创建一个武器对象,并设置名字为AK47
        Weapon weapon = new Weapon("AK-47"); 
        // 为英雄配备武器
        hero.setWeapon(weapon);

        // 年龄为20的盖伦用AK-47攻击敌方。
        hero.attack();
    }
}

类作为成员变量时,对它进行赋值的操作,实际上,是赋给它该类的一个对象。


接口作为成员变量

跟上面的类作为成员变量一样,接口也可以作为成员变量。

首先创建一个英雄类:

public class Hero {

    private String name; // 英雄的名称 
    private Skill 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("施放技能完成。");
    }
//get set基础方法
    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 DemoGame {
    public static void main(String[] args) {
        Hero hero = new Hero(); //创建 英雄对象
        hero.setName("艾希"); // 设置英雄的名称

        // 设置英雄技能
//        hero.setSkill(new SkillImpl()); // 使用单独定义的实现类
//可以看到上面的skillimpl只是为了实现接口skill,就使用了一次,所以可以给他改成匿名内部类
 
       // 匿名内部类写法:
//        Skill skill = new Skill() {
//            @Override  //这里是在匿名内部类里重写了技能的使用方法
//            public void use() {
//                System.out.println("Pia~pia~pia~");
//            }
//        };
//        hero.setSkill(skill);
//可以看到上面的匿名内部类创建的skill对象也只使用了一次,所以可以进一步简化:
        // 同时使用匿名内部类和匿名对象:
        hero.setSkill(new Skill() {
            @Override
            public void use() {
                System.out.println("Biu~Pia~Biu~Pia~");
            }
        });
        hero.attack();//调用展示方法
    }
}

接口作为方法的参数和返回值

使用接口也可以作为方法的参数和返回值,比如ArrayList集合,正是java.util.List接口的实现类,所以,List接口作为参数或者返回值类型时,可以将  ArrayList的对象进行传递或返回。

代码示例:

public class DemoInterface {
    public static void main(String[] args) {
        // 左边是接口名称,右边是实现类名称,这就是多态写法
        List<String> list = new ArrayList<>();
 //这里调用了addnames方法,同时使用list接口作为了返回值。
        List<String> result = addNames(list); //把刚刚方法和返回值传递给接口,并创建对象result
        for (int i = 0; i < result.size(); i++) {
            System.out.println(result.get(i));
        }
    }
//这里创建了一个方法,以list(字符串类型)接口作为返回参数类型,添加了几个字符串到list接口中。
    public static List<String> addNames(List<String> list) {
        list.add("迪丽热巴");
        list.add("古力娜扎");
        list.add("玛尔扎哈");
        list.add("沙扬娜拉");
        return list; //返回了添加好字符串的集合 这里是以list接口作为返回值返回
    }
}

接口(list)作为参数时,传递它的子类对象。

接口作为返回值类型时,返回它的子类对象。


发红包案例 

场景说明:
    红包发出去之后,所有人都有红包,大家抢完了之后,最后一个红包给群主自己。
大多数代码都是现成的,我们需要做的就是填空题。
我们自己要做的事情有:
    1. 设置一下程序的标题,通过构造方法的字符串参数
    2. 设置群主名称
    3. 设置分发策略:平均,还是随机?

红包分发的策略:
    1. 普通红包(平均):totalMoney / totalCount,余数放在最后一个红包当中。
    2. 手气红包(随机):最少1分钱,最多不超过平均数的2倍。应该越发越少。

已经写好的类:

1. RedPacketFrame  :一个抽象类,包含了一些属性,是红包案例的页面。

public abstract class RedPacketFrame extends JFrame {
    /*
    ownerName  :  群主名称 */
    public String ownerName = "谁谁谁谁";
/*  openMode  : 红包的类型  [普通红包/手气红包]
public  OpenMode  openMode =  null;
*/

    /**
     * 构造方法:生成红包界面.
     *
     * @param title 页面的标题.
     */
    public RedPacketFrame(String title) {
        super(title);
        init();//  页面相关的初始化操作
    }

    /*  set方法  */
    public void setOwnerName(String ownerName) {
        this.ownerName = ownerName;
    }

    public void setOpenMode(OpenMode openMode) {
        this.openMode = openMode;
    }
}

 2.OpenMode  :一个接口,包含一个分配方法,用来指定红包类型。

public  interface  OpenMode {
    /**
     *  @param  totalMoney 总金额,单位是"分"。总金额为方便计算,已经转换为整数,单位为分。
     *  @param  count  红包个数
     *  @return  ArrayList<Integer> 元素为各个红包的金额值,所有元素的值累和等于总金额.
     *
     *  请将totalMoney,分成count分,保存到ArrayList<Integer>中,返回即可.
     */
    public  abstract   ArrayList<Integer>  divide(int  totalMoney,  int  count);
}

首先我们先创建一个子类myred,继承RedPacketFrame抽象类,并重写他的抽象方法:

public class MyRed extends RedPacketFrame {
    /**
     * 构造方法:生成红包界面。
     *
     * @param title 界面的标题
     */
    public MyRed(String title) {
        super(title);
    }
}

然后创建一个测试类,并创建myred的对象。

然后我们需要重写平均分发与随机分发的方法,首先创建平均分发与随机分发的实现类,继承OpenMode接口,实现里面的抽象方法。

然后再在实现类中创建相应的对象,并调用发红包方法。

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);
    }
}
import java.util.ArrayList;
//创建一个平均分发实现类,并继承OpenMode接口
public class NormalMode implements OpenMode {
    @Override  //重写他的抽象方法
    public ArrayList<Integer> divide(final int totalMoney, final int totalCount) {
        ArrayList<Integer> list = new ArrayList<>();  //创建一个数组对象叫list

// totalMoney 塞多少钱   totalCount //分多少份
        int avg = totalMoney / totalCount; // 平均值 这里用总金额/人数=每个人的红包塞多少钱
        int mod = totalMoney % totalCount; // 余数,模,零头 
 //然后通过除余,求出剩下多少没除开的,把他塞到最后一个红包中。

        // 注意totalCount - 1代表,最后一个先留着
//循环人数-1次,比如有三个人就循环两次,每次循环放一个钱数进去。
        for (int i = 0; i < totalCount - 1; i++) {
            list.add(avg);
        }

        // 有零头,需要放在最后一个红包当中
        list.add(avg + mod);   //最后一次除了固定的平均值还把余数放进去了

        return list;  //返回这个生成的数组。
    }
}
//这里继承了发红包的抽象类,重写了他的抽象方法
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;
//注:random.nextInt()的方法是指生成从0-A之间的随机数,所以这里用0~到还剩多少钱/剩余人数*2
//+1是为了确保他至少有一分钱,不是0!
            list.add(money); // 将一个随机红包放入集合
            leftMoney -= money; // 剩下的金额越发越少
            leftCount--; // 剩下还应该再发的红包个数,递减
        }

        // 最后一个红包不需要随机,直接放进去就得了
        list.add(leftMoney);

        return list;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Andy393939

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值