面向对象进阶二:抽象、接口、代码块、内部类、枚举

面向对象进阶二:抽象、接口、代码块、内部类、枚举

今天我们继续学习面向对象的语法知识,我们今天学习的主要内容是:抽象、接口、枚举。

学会这些语法知识,可以让我们编写代码更灵活,代码的复用性更高。

一、抽象

同学们,接下来我们学习Java中一种特殊的类,叫抽象类。为了让同学们掌握抽象类,会先让同学们认识一下什么是抽象类以及抽象类的特点,再学习一个抽象类的常见应用场景。

1.1 认识抽象类

我们先来认识一下什么是抽象类,以及抽象类有什么特点。

  • 在Java中有一个关键字叫abstract,它就是抽象的意思,它可以修饰类也可以修饰方法。
-abstract修饰的类,就是抽象类
-abstract修饰的方法,就是抽象方法(不允许有方法体)

接下来用代码来演示一下抽象类和抽象方法

//abstract修饰类,这个类就是抽象类
public abstract class A{
    //abstract修饰方法,这个方法就是抽象方法
    public abstract void test();
}
  • 类的成员(成员变量、成员方法、构造器),类的成员都可以有。如下面代码
// 抽象类
public abstract class A {
    //成员变量
    private String name;
    static String schoolName;

    //构造方法
    public A(){

    }

    //抽象方法
    public abstract void test();

    //实例方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • 抽象类是不能创建对象的,如果抽象类的对象就会报错

在这里插入图片描述

  • 抽象类虽然不能创建对象,但是它可以作为父类让子类继承。而且子类继承父类必须重写父类的所有抽象方法。
//B类继承A类,必须复写test方法
public class B extends A {
    @Override
    public void test() {

    }
}
  • 子类继承父类如果不复写父类的抽象方法,要想不出错,这个子类也必须是抽象类
//B类基础A类,此时B类也是抽象类,这个时候就可以不重写A类的抽象方法
public abstract class B extends A {

}

1.2 抽象类的好处

接下来我们用一个案例来说一下抽象类的应用场景和好处。需求如下图所示

在这里插入图片描述

分析需求发现,该案例中猫和狗都有名字这个属性,也都有叫这个行为,所以我们可以将共性的内容抽取成一个父类,Animal类,但是由于猫和狗叫的声音不一样,于是我们在Animal类中将叫的行为写成抽象的。代码如下

public abstract class Animal {
    private String name;

    //动物叫的行为:不具体,是抽象的
    public abstract void cry();

    public String getName() {
        return name;
    }

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

接着写一个Animal的子类,Dog类。代码如下

public class Dog extends Animal{
    public void cry(){
        System.out.println(getName() + "汪汪汪的叫~~");
    }
}

然后,再写一个Animal的子类,Cat类。代码如下

public class Cat extends Animal{
    public void cry(){
        System.out.println(getName() + "喵喵喵的叫~~");
    }
}

最后,再写一个测试类,Test类。

public class Test2 {
    public static void main(String[] args) {
        // 目标:掌握抽象类的使用场景和好处.
        Animal a = new Dog();
        a.cry();	//这时执行的是Dog类的cry方法
    }
}

再学一招,假设现在系统有需要加一个Pig类,也有叫的行为,这时候也很容易原有功能扩展。只需要让Pig类继承Animal,复写cry方法就行。

public class Pig extends Animal{
    @Override
    public void cry() {
        System.out.println(getName() + "嚯嚯嚯~~~");
    }
}

此时,创建对象时,让Animal接收Pig,就可以执行Pig的cry方法

public class Test2 {
    public static void main(String[] args) {
        // 目标:掌握抽象类的使用场景和好处.
        Animal a = new Pig();
        a.cry();	//这时执行的是Pig类的cry方法
    }
}

综上所述,我们总结一下抽象类的使用场景和好处

1.用抽象类可以把父类中相同的代码,包括方法声明都抽取到父类,这样能更好的支持多态,一提高代码的灵活性。

2.反过来用,我们不知道系统未来具体的业务实现时,我们可以先定义抽象类,将来让子类去实现,以方便系统的扩展。

1.3 模板方法模式

学习完抽象类的语法之后,接下来,我们学习一种利用抽象类实现的一种设计模式。先解释下一什么是设计模式?设计模式是解决某一类问题的最优方案

设计模式在一些源码中经常会出现,还有以后面试的时候偶尔也会被问到,所以在合适的机会,就会给同学们介绍一下设计模式的知识。

那模板方法设计模式解决什么问题呢?模板方法模式主要解决方法中存在重复代码的问题

比如A类和B类都有sing()方法,sing()方法的开头和结尾都是一样的,只是中间一段内容不一样。此时A类和B类的sing()方法中就存在一些相同的代码。

在这里插入图片描述

怎么解决上面的重复代码问题呢? 我们可以写一个抽象类C类,在C类中写一个doSing()的抽象方法。再写一个sing()方法,代码如下:

// 模板方法设计模式
public abstr法
    public final void sing(){
        System.out.println("唱一首你喜欢的歌:");

        doSing();

        System.out.println("唱完了!");
    }

    public abstract void doSing();
}

然后,写一个A类继承C类,复写doSing()方法,代码如下

public class A extends C{
    @Override
    public void doSing() {
        System.out.println("我是一只小小小小鸟,想要飞就能飞的高~~~");
    }
}

接着,再写一个B类继承C类,也复写doSing()方法,代码如下

public class B extends C{
    @Override
    public void doSing() {
        System.out.println("我们一起学猫叫,喵喵喵喵喵喵喵~~");
    }
}

最后,再写一个测试类Test

public class Test {
    public static void main(String[] args) {
        // 目标:搞清楚模板方法设计模式能解决什么问题,以及怎么写。
        B b = new B();
        b.sing();
    }
}

综上所述:模板方法模式解决了多个子类中有相同代码的问题。具体实现步骤如下

1步:定义一个抽象类,把子类中相同的代码写成一个模板方法。
第2步:把模板方法中不能确定的代码写成抽象方法,并在模板方法中调用。
第3步:子类继承抽象类,只需要父类抽象方法就可以了。
案例

在这里插入图片描述

package com.itheima.abstract03;
/*
 抽象父类  模板父类
     因为 里面有模板方法
 */
public abstract class Music {


    /*
     一个是模板方法  来放 相同代码的  相同的提取 不同的调用
     一个是抽象方法  来表示不一样代码的
     */
    // 模板方法 代表相同代码 和执行
    public final void sing(){
        System.out.println("alalalalala1673365314152");

        //调用不同歌词
        geci();
        System.out.println("eneneneneneenen1112333");
    }


    // 歌词不同 定义成抽象的 由子类实现
    public abstract void geci();

}

package com.itheima.abstract03;

public class AQZY extends Music{

    //模板方法好处在于 只重写不同部分
    @Override
    public void geci() {
        System.out.println("徘徊過多少櫥窗 住過多少旅館\n" +
                "才會覺得分離也並不冤枉\n" +
                "感情是用來瀏覽 還是用來珍藏\n" +
                "好讓日子天天都過得難忘");
    }


}

package com.itheima.abstract03;

public class FSSX extends Music{
    @Override
    public void geci() {
        System.out.println("攔路雨偏似雪花 飲泣的你凍嗎\n" +
                "這風褸我給你磨到有襟花\n" +
                "連調了職也不怕 怎麼始終牽掛\n" +
                "苦心選中今天想車你回家\n" +
                "原諒我不再送花 傷口應要結疤\n" +
                "花瓣鋪滿心裡墳場才害怕\n" +
                "如若你非我不嫁 彼此終必火化\n" +
                "一生一世等一天需要代價");
    }
}

package com.itheima.abstract03;

public class Demo {

    public static void main(String[] args) {
        AQZY aqzy = new AQZY();
        aqzy.sing();
        System.out.println("===========");
        FSSX fssx = new FSSX();
        fssx.sing();
    }
}

二、接口

同学们,接下来我们学习一个比抽象类抽象得更加彻底的一种特殊结构,叫做接口。在学习接口是什么之前,有一些事情需要给大家交代一下:Java已经发展了20多年了,在发展的过程中不同JDK版本的接口也有一些变化,所以我们在学习接口时,先以老版本为基础,学习完老版本接口的特性之后,再顺带着了解一些新版本接口的特性就可以了。

2.1 认识接口

接口是规则,对行为的规范。接口是更加彻底的抽象,JDK7之前,包括JDK7,接口中全部是抽象方法。接口同样是不能创建对象的

我们先来认识一下接口?Java提供了一个关键字interface,用这个关键字来定义接口这种特殊结构。格式如下

public interface 接口名{
    //成员变量(常量) public static final修饰
    //成员方法(抽象方法)public abstract
}

2.2 接口的好处

同学们,刚刚上面我们学习了什么是接口,以及接口的基本特点。那使用接口到底有什么好处呢?主要有下面的两点

  • 弥补了类单继承的不足,一个类同时可以实现多个接口。
  • 让程序可以面向接口编程,这样程序员可以灵活方便的切换各种业务实现。

我们看一个案例演示,假设有一个Studnet学生类,还有一个Driver司机的接口,还有一个Singer歌手的接口。

现在要写一个XiaoHan类,想让他既是学生,偶然也是司机能够开车,偶尔也是歌手能够唱歌。那我们代码就可以这样设计,如下:

public class Student {

    public void study(){
        System.out.println("学生特别的爱学习....不喜欢趴着.....");
    }
}


/*
  开车是一种技能
      开车方式不一样
 */
public interface Driver {

    //只定义 开车的功能 不实现
    void drive();

    void stopCar();
}

/*
  唱歌也是一种技能

 */
public interface Singger {

    void sing();
}

//小韩是学生  会开车  会唱歌
public class XiaoHan extends Student implements Driver,Singger{
    @Override
    public void drive() {
        System.out.println("得飘得飘 得咿的飘~~~ 单手开车");
    }

    @Override
    public void stopCar() {
        System.out.println("用叉车停车...");
    }

    @Override
    public void sing() {
        System.out.println("唱  鸡你太美!!!");
    }
}
public class Test {

    /*
      接口:在Java编程语言中是一种特殊的类型,比抽象类更抽象。
      定义接口:
        public interface 接口名{
           常量;
           jdk8之前里面只有抽象方法;
        }
      定义接口的目的是为了让 类 去实现,这样类就具备了 接口的定义的规则。
      接口可以理解为是具备一种规则的规范。
      类如何实现接口?
         public class 实现类 implements 接口{
              需要重写所有的抽象方法。 如果没有重写完 就必须是抽象类

         }
         接口的好处
            可以在继承一个类的基础上 实现多个接口。---提升类型扩展性
            让程序可以面向接口编程(参数是一个接口),程序员方便各种业务的切换。

      假设有个类型叫Student类型
          还有 一个 司机的接口    drive()  stopCar()
               一个 Singer 歌手的接口  sing()

           设计一个类
              XiaoHan  是一个学生  偶尔去开车  还去 KTV 唱歌

     */

    public static void main(String[] args) {

        XiaoHan xh = new XiaoHan();

        needDriver(xh);//因为多态!!  Driver driver= new XiaoHan();

        needSingger(xh);

        Driver driver= new XiaoHan();
        needDriver(driver);
    }

    public static void needDriver(Driver driver){//接口作为参数  你只要传递 实现该接口的对象就可以
        driver.drive();
    }

    public static void needSingger(Singger singger){
        singger.sing();
    }
}

综上所述:接口弥补了单继承的不足,同时可以轻松实现在多种业务场景之间的切换。

2.3 类与接口基本实现案例

在这里插入图片描述

首先我们写一个Animal 抽象类,用来描述动物共性信息

package com.itheima.test01;
/*
   通过该案例 理解  类和接口在设计上的区别
      父类  定义共性内容(成员变量 成员方法)
      接口  定义特性内容(抽象方法)
 */
public abstract class Animal {
    private String name;
    private int age;

    public Animal() {
    }

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    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 abstract void eat();
}

接着,写一个Swim游泳接口,表示额外的功能。

//游泳接口
public interface Swim {
    //游泳功能
    void swimming();
}

定义一个兔子 类

public class Rabbit extends Animal{

    public Rabbit() {
    }

    public Rabbit(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println("兔子吃文龙喂得文轩牌大萝卜~~");
    }
}

定义一个 Dog类在继承动物同时实现游泳接口

package com.itheima.test;

public class Dog extends Animal implements Swim{

    public Dog() {
    }

    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println("狗子吃 蓝翔....");
    }

    @Override
    public void swimming() {
        System.out.println("狗子 用狗刨泳姿游泳....");
    }
}

再写一个青蛙类 继承同时 实现游泳接口

package com.itheima.test;

public class Frog extends Animal implements Swim{

    public Frog() {
    }

    public Frog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println("青蛙吃~~苍鹰~~~");
    }

    @Override
    public void swimming() {
        System.out.println("青蛙 进行 蛙泳 训练....");
    }
}

最后,再写一个测试类Test,在测试类中使用小青蛙 兔子测试。

package com.itheima.test;

public class Demo {

    public static void main(String[] args) {
        Rabbit rabbit = new Rabbit("那兔", 74);
        rabbit.eat();

        // 狗子
        Dog dog = new Dog("哮天犬", 2);
        dog.eat();
        dog.swimming();
    }
}

在这里插入图片描述

2.4 接口的案例

各位同学,关于接口的特点以及接口的好处我们都已经学习完了。接下来我们做一个案例,先来看一下案例需求.

在这里插入图片描述

分析:

在这里插入图片描述

在这里插入图片描述

首先我们写一个学生类,用来描述学生的相关信息

package com.itheima.test02;

public class Student {
    private String name;
    private String gender;
    private double score;

    public Student() {
    }

    public Student(String name, String gender, double score) {
        this.name = name;
        this.gender = gender;
        this.score = score;
    }

    public String getName() {
        return name;
    }

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

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "姓名='" + name + '\'' +
                ", 性别='" + gender + '\'' +
                ", 分数=" + score +
                '}';
    }
}

接着,写一个StudentOperator接口,表示学生信息管理系统的两个功能。

package com.itheima.test02;

import java.util.ArrayList;

public interface StudentOperator {
    //打印全部学生信息
    void printAllInfo(ArrayList<Student> students);//参数写什么?多个学生信息--集合容器
    //打印 平均成绩
    void printAverageScore(ArrayList<Student> students);//参数写什么?多个学生信息--集合容器
}

然后,写一个子包impl 在定义一个StudentOperator接口的实现类StudentOperatorImplA,采用第1套方案对业务进行实现。

package com.itheima.test02.impl;

import com.itheima.test02.Student;
import com.itheima.test02.StudentOperator;

import java.util.ArrayList;

public class StudentOperatorImplA implements StudentOperator {
    @Override
    public void printAllInfo(ArrayList<Student> students) {
        System.out.println("全班全部学生信息如下:");
        for (int i = 0; i < students.size(); i++) {
            Student student = students.get(i);
            System.out.println(student);
        }
        System.out.println("-------------------------------------");
    }

    @Override
    public void printAverageScore(ArrayList<Student> students) {
        System.out.println("全班全部学生平均成绩如下:");
        double sum = 0;
        for (int i = 0; i < students.size(); i++) {
            Student student = students.get(i);
            sum+=student.getScore();
        }
        //求平均成绩
        double avg = sum/students.size();
        System.out.println(avg+"分");
        System.out.println("-------------------------------------");
    }
}

接着,再写一个StudentOperator接口的实现类StudentOperatorImplB,采用第2套方案对业务进行实现。

package com.itheima.test02.impl;

import com.itheima.test02.Student;
import com.itheima.test02.StudentOperator;

import java.util.ArrayList;

public class StudentOperatorImplB implements StudentOperator {
    @Override
    public void printAllInfo(ArrayList<Student> students) {
        System.out.println("全班全部学生信息如下:");
        // 定义统计变量
        int boyCount = 0;
        int girlCount = 0;
        for (int i = 0; i < students.size(); i++) {
            Student student = students.get(i);
            System.out.println(student);
            if(student.getGender().equals("男")){
                boyCount++;
            }else {
                girlCount++;
            }
        }
        System.out.println("班级男生人数:"+boyCount+"   班级女生人数:"+girlCount);
        System.out.println("总人数:"+students.size());
        System.out.println("-------------------------------------");
    }

    @Override
    public void printAverageScore(ArrayList<Student> students) {
        System.out.println("全班全部学生平均成绩如下:");
        double sum = 0;
        // 最值 套路  1:定义最值变量接收集合第一个数据  2:遍历进行判断 3:循环结束得到最值
        double max = students.get(0).getScore();
        double min = students.get(0).getScore();
        for (int i = 0; i < students.size(); i++) {
            Student student = students.get(i);
            sum+=student.getScore();
            double score = student.getScore();
            if(score>max){//求最大值
                max = score;
            }
            if(score<min){//求最小值
                min = score;
            }

        }
        //求平均成绩
        double avg = (sum-max-min)/(students.size()-2);
        System.out.println(avg+"分");
        System.out.println("-------------------------------------");
    }
}

再写一个班级管理类ClassManager,在班级管理类中使用StudentOperator的实现类StudentOperatorImpl1对学生进行操作

package com.itheima.test02;

import com.itheima.test02.impl.StudentOperatorImplA;
import com.itheima.test02.impl.StudentOperatorImplB;

import java.util.ArrayList;

public class ClassManager {


    public static void main(String[] args) {
          //定义学生集合
        ArrayList<Student> students = new ArrayList<>();
        //添加学生对象
        Student stu1 = new Student("李易峰", "男", 36);
        Student stu2 = new Student("吴亦凡", "男", 37);
        Student stu3 = new Student("蔡徐坤", "男", 32);
        Student stu4 = new Student("赵薇", "女", 43);

        students.add(stu1);
        students.add(stu2);
        students.add(stu3);
        students.add(stu4);

        //调用展示学生的方法
        StudentOperator operatorImpl = new StudentOperatorImplB();

//        showStudents(学生集合,学生操作类对象);
        showStudents(students,operatorImpl);
    }

    /*
      设计 展示学生信息的方法
          参数 学生集合 ArrayList<Student>
                学生操作类型---用具体的实现类 接口
     */
    public static void showStudents(ArrayList<Student> students,StudentOperator studentOperator){
         //调用方法
        studentOperator.printAllInfo(students);
        studentOperator.printAverageScore(students);
    }
}

注意:如果想切换班级管理系统的业务功能,随时可以将StudentOperatorImpl1切换为StudentOperatorImpl2。自己试试

2.5 接口JDK8的新特性

各位同学,对于接口最常见的特性我们都学习完了。随着JDK版本的升级,在JDK8版本以后接口中能够定义的成员也做了一些更新,从JDK8开始,接口中新增的三种方法形式。

我们看一下这三种方法分别有什么特点?

public interface A {
    /**
     * 1、默认方法:必须使用default修饰,默认会被public修饰
     * 实例方法:对象的方法,必须使用实现类的对象来访问。
     */
    default void test1(){
        System.out.println("===默认方法==");
        test2();
    }

    /**
     * 2、私有方法:必须使用private修饰。(JDK 9开始才支持的)
     *   实例方法:对象的方法。
     */
    private void test2(){
        System.out.println("===私有方法==");
    }

    /**
     * 3、静态方法:必须使用static修饰,默认会被public修饰
     */
     static void test3(){
        System.out.println("==静态方法==");
     }

     void test4();
     void test5();
     default void test6(){

     }
}

接下来我们写一个B类,实现A接口。B类作为A接口的实现类,只需要重写抽象方法就尅了,对于默认方法不需要子类重写。代码如下:

public class B implements A{
    @Override
    public void test4() {

    }

    @Override
    public void test5() {

    }
}

最后,写一个测试类,观察接口中的三种方法,是如何调用的

public class Test {
    public static void main(String[] args) {
        // 目标:掌握接口新增的三种方法形式
        B b = new B();
        b.test1();	//默认方法使用对象调用
        // b.test2();	//A接口中的私有方法,B类调用不了
        A.test3();	//静态方法,使用接口名调用
    }
}

综上所述:JDK8对接口新增的特性,有利于对程序进行扩展。

2.6 接口的其他细节

最后,给同学们介绍一下使用接口的其他细节,或者说注意事项:

  • 一个接口可以继承多个接口
public class Test {
    public static void main(String[] args) {
        // 目标:理解接口的多继承。
    }
}

interface A{
    void test1();
}
interface B{
    void test2();
}
interface C{}

//比如:D接口继承C、B、A
interface D extends C, B, A{

}

//E类在实现D接口时,必须重写D接口、以及其父类中的所有抽象方法。
class E implements D{
    @Override
    public void test1() {

    }

    @Override
    public void test2() {

    }
}

接口除了上面的多继承特点之外,在多实现、继承和实现并存时,有可能出现方法名冲突的问题,需要了解怎么解决(仅仅只是了解一下,实际上工作中几乎不会出现这种情况)

1.一个接口继承多个接口,如果多个接口中存在相同的方法声明,则此时不支持多继承
2.一个类实现多个接口,如果多个接口中存在相同的方法声明,则此时不支持多实现
3.一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会有限使用父类的方法
4.一个类实现类多个接口,多个接口中有同名的默认方法,则这个类必须重写该方法。

综上所述:一个接口可以继承多个接口,接口同时也可以被类实现。

三、代码块

代码块 :
在Java类下,使用 { } 括起来的代码被称为代码块

分类:

  • 局部代码块
    位置:方法中定义
    作用:限定变量的生命周期,及早释放,提高内存利用率
  • 构造代码块
    位置:类中方法外定义
    特点:每次构造方法执行的时,都会执行该代码块中的代码,并且在构造方法执行前执行
    作用:将多个构造方法中相同的代码,抽取到构造代码块中,提高代码的复用性
  • 静态代码块
    位置:类中方法外定义
    特点:需要通过static关键字修饰,随着类的加载而加载,并且只执行一次
    作用:在类加载的时候做一些数据初始化的操作

3.1 局部代码块(了解)

示例

public class Test {
    public static void main(String[] args) {
        {
           //局部代码块 
           int a = 10;
           System.out.println(a);

        }
    }    
}        


特点:

限定变量的生命周期,及早释放,提高内存利用率

3.2 构造(实例)代码块(了解)

示例:

public class Student{
    //实例变量
	int age;
    //实例代码块:实例代码块会执行在每一个构造方法之前
    {
        System.out.println("实例代码块执行了~~");
        age = 18;
        System.out.println("有人创建了对象:" + this);
    }

    public Student(){
        System.out.println("无参数构造器执行了~~");
    }

    public Student(String name){
        System.out.println("有参数构造器执行了~~");
    }
}

接下来在测试类中进行测试,观察创建对象时,实例代码块是否先执行了。

public class Test {
    public static void main(String[] args) {
        Student s1 = new Student();
        Student s2 = new Student("张三");
        System.out.println(s1.age);
        System.out.println(s2.age);
    }
}

特点:

实例代码块每次创建对象之前都会执行一次

3.3 静态代码块(掌握)

示例:

public class Student {
    static int number = 80;
    static String schoolName = "黑马";
    // 静态代码块
    static {
        System.out.println("静态代码块执行了~~");
        schoolName = "黑马";
    }
}

静态代码块不需要创建对象就能够执行

public class Test {
    public static void main(String[] args) {
        // 目标:认识两种代码块,了解他们的特点和基本作用。
        System.out.println(Student.number);
        System.out.println(Student.number);
        System.out.println(Student.number);

        System.out.println(Student.schoolName); // 黑马
    }
}

执行上面代码时,发现没有创建对象,静态代码块就已经执行了。

特点:

需要通过static关键字修饰,随着类的加载而加载,并且只执行一次

在这里插入图片描述

在这里插入图片描述

package com.itheima.room;

import java.util.ArrayList;

public class HitRichManRoom {

    //定义静态变量 来表示 斗地主 的 54张牌 集合
    // 随着类的加载加载一次
    public static ArrayList<String> poker = new ArrayList<>();//初始化容器 容器里面是空的

    //静态代码块也随着类的加载 加载一次
    static{
        // 拼接出来54张牌
        poker.add("♦A");
        poker.add("♣A");
        // 思考 可不可以利用 循环   写一版通过循环生成 牌的操作
    }
}

package com.itheima.room;

import java.util.ArrayList;

public class RoomTest {

    public static void main(String[] args) {
        //查看牌
        ArrayList<String> poker = HitRichManRoom.poker;

        for (int i = 0; i < poker.size(); i++) {
            System.out.println(poker.get(i));
        }
    }
}

四、内部类

内部类是类中的五大成分之一(成员变量、方法、构造器、内部类、代码块),如果一个类定义在另一个类的内部,这个类就是内部类。

当一个类的内部,包含一个完整的事物,且这个事物没有必要单独设计时,就可以把这个事物设计成内部类。

比如:汽车、的内部有发动机,发动机是包含在汽车内部的一个完整事物,可以把发动机设计成内部类。

public class Car{
	//内部类
    public class Engine{
        
    }
}

内部类有四种形式,分别是成员内部类、静态内部类、局部内部类、匿名内部类。

我们先来学习成员内部类

4.1 成员内部类

成员内部类就是类中的一个普通成员,类似于成员变量、成员方法。

//定义了一个 ZhiZunBao 类
public class ZhiZunBao {
    //属性
    private String lover = "紫霞仙子";
    private int age = 300;
    // 成员方法
    public void tiaoya(){
        System.out.println("我爱紫霞仙子");
        System.out.println("我爱白晶晶");
    }

    // 有心脏的
    // 成员内部类 表达 该类型的一个成员--心脏
    class Heart{// 只是定义在内部的一个类
        // 内部类的成员变量
        private String wife = "白晶晶";
        private int age = 14;
        // 可以有方法
        public void speak(){// 内部类的功能
            System.out.println("最爱的人是谁?"+lover);//内部类中可以访问 外部类成员变量
            tiaoya();//内部类可以访问外部的方法
            System.out.println("你的妻子是谁?"+wife);

            System.out.println("心脏的年龄:"+age);//就近原则
            // 如果想访问外部类中的成员
            System.out.println("至尊宝的年龄:"+ZhiZunBao.this.age);
        }
    }

}

成员内部类如何创建对象,格式如下

//外部类.内部类 变量名 = new 外部类().new 内部类();
Outer.Inner in = new Outer().new Inner();
//调用内部类的方法
in.test();
public class Demo {

    public static void main(String[] args) {
        //调用 内部类的speak方法
//        Heart heart = new Heart();不能直接创建
         // Heart类型 属于 ZhiZunBao 的成员
        // 所以得先有至尊宝 再有 Heart对象
//        ZhiZunBao zzb = new ZhiZunBao();
//        ZhiZunBao.Heart heart =  zzb.new Heart();
        // 上面两步可以合成 一步
        ZhiZunBao.Heart heart = new ZhiZunBao().new Heart();

        // 因为 Heart类型 属于 ZhiZunBao 的成员
        // 对外 先找 ZhiZunBao  . 的
        // 先有 外部类对象 再有内部类对象
        heart.speak();

    }
}

总结一下内部类访问成员的特点

  • 既可以访问内部类成员、也可以访问外部类成员
  • 如果内部类成员和外部类成员同名,可以使用**类名.this.成员**区分
内部类编译之后 会产生.class文件
ZhiZunBao$Heart.class 
      内部类编译之后形成了.class文件。

在这里插入图片描述

作用:在一个类的内部创建一个类,只允许当前类用这个类,别人无法直接访问。

4.2 静态内部类

静态内部类,其实就是在成员内部类的前面加了一个static关键字。静态内部类属于外部类自己持有。

public class Outer {
    //实例方法
    public void show() {
        System.out.println("Outer的show方法执行了!");
    }

    //静态方法
    public static void function() {
        System.out.println("Outer的静态function方法执行了!");
    }

    //静态内部类
    public static class Inner {
        //内部类的实例方法(需要基于内部类对象调用)
        public void method() {
            function(); //静态内部类可以访问外部类的静态内容,不能访问实例内容
        }
        //内部类的静态方法(基于内部类名来调用)
        public static void staticMethod(){
            function();
        }
    }
}

静态内部类的使用方式

外部类名.内部类名 变量名 = new 外部类名.内部类名();

//创建静态内部类对象
Outer.Inner i = new Outer.Inner();

i.method();//实例方法
Outer.Inner.staticMethod(); //静态方法

特点:

  • 可以访问内部类静态成员,不能访问实例成员。

4.3 局部内部类

局部内部类是定义在方法中的类,和局部变量一样,只能在方法中有效。所以局部内部类的局限性很强,一般在开发中是不会使用的。

public class Outer2{
    public void test(){
        //局部内部类
        class Inner{
            public void show(){
                System.out.println("Inner...show");
            }
        }
        
        //局部内部类只能在方法中创建对象,并使用
        Inner in = new Inner();
        in.show();
    }
}

4.4 匿名内部类

1.4.1 认识匿名内部类,基本使用

各位同学,接下来学习一种再实际开发中用得最多的一种内部类,叫匿名内部类。相比于前面几种内部类,匿名内部类就比较重要的。

我们还是先认识一下什么是匿名内部类?

匿名内部类是一种特殊的局部内部类;所谓匿名,指的是程序员不需要为这个类声明名字。

匿名内部类的格式
new 抽象父类/接口(){
	//针对于抽象父类/接口的方法重写
}
匿名内部类的本质

本质上抽象父类/接口(){}这个部分就是编写好逻辑的一个子类/实现类,前面的new表示创建一个子类/实现类对象。

总结为一句话:匿名内部类就是指定的抽象父类/接口的子类对象(带有完整实现逻辑)。

匿名内部类的演示

没有匿名内部类的玩法

package com.itheima.nonameinnerclass;

public  abstract  class Animal {

  public  abstract void eat();
}

package com.itheima.nonameinnerclass;

import javafx.scene.AmbientLight;

public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

在外部创建子类玩法

public class Demo {

    public static void main(String[] args) {
        //  我想养一只动物
//        Animal a = new Animal(); 抽象类不能直接实例化
        /*
          抽象类不能直接实例化?咋办?
             1: 定义子类继承Animal
             2: 实现抽象方法。
             3: 创建子类对象。
         */
        Animal a = new Cat();//多态  因为 要的是动物
        a.eat();
        System.out.println("=======上方采用 外部类方式 创建了一个抽象类的子类对象=========");
      
    }
}

有了内部类的玩法

public class Demo {

    public static void main(String[] args) {
      // 前面代码略......
        System.out.println("=======上方采用 外部类方式 创建了一个抽象类的子类对象=========");
        /*
           能不能不要在 外面去创建类  完成对象实例化?
           那就在内部 创建一个狗类 --- 局部内部类 ---语法java是支持的
         */
        class Dog extends Animal{
            @Override
            public void eat() {
                System.out.println("狗吃翔");
            }
        }
        Animal a2 = new Dog();
        a2.eat();
        System.out.println("=======上方采用 局部内部类方式 创建了一个抽象类的子类对象=========");
    }
}

使用匿名内部类的玩法

public class Demo {

    public static void main(String[] args) {
      // 前面代码略......
        System.out.println("=======上方采用 外部类方式 创建了一个抽象类的子类对象=========");
      // 前面代码略....
        System.out.println("=======上方采用 局部内部类方式 创建了一个抽象类的子类对象=========");
        
         /*
          需求是
             要一只动物。
             是什么动物不重要,也就是 动物的具体类型是不重要的 !!!
             什么是重要的
             要动物 是 看动物吃东西 --- 所以只有 动物的 重写后的  eat方法 是重要的
        所以开发过程中名字不重要,就不要给该类型取名字了---匿名内部类

        怎么实现呢
           人家的实现是一步到位的实现
              直接给你把  该类型的子类对象搞出来!!!省略了 构建类的过程
          格式
             new 父类名(){
                //重写方法
             }

               new Animal(){
                    @Override
                    public void eat() {
                        System.out.println("老虎吃鸡");
                    }
                } --- 这个代码做了两件事   定义类继承了父类,重写了方法
                ----这个整体被称为 匿名内部类  实际本质是 该Animal的子类对象
                就好比 new Dog(); new Cat();
         */
       Animal a3 =  new Animal(){
            @Override
            public void eat() {
                System.out.println("老虎吃鸡");
            }
        };
       a3.eat();
        System.out.println("=======上方采用 匿名局部内部类方式 创建了一个抽象类的子类对象=========");
     /*
        匿名内部类的本质
             是一个继承该父类的子类对象。
             样子是 匿名内部类。
          对咱们来说 匿名内部类出现 ,简化了创建子(实现)类的过程,直接得到了子类的对象。
          只是这种结构 我们不熟悉。
      */
    }
}

产生的结果,也是一个.class文件,这不过这个.class文件没有具体的名字。

在这里插入图片描述

匿名内部类的作用:简化了创建子类对象、实现类对象的书写格式。

4.4.2 匿名内部类的应用场景

学习完匿名内部类的基本使用之后,我们再来看一下匿名内部类在实际中的应用场景。其实一般我们会主动的使用匿名内部类。

(1)当方法的参数是一个接口/抽象父类,快速传递一个带有指定逻辑的子类对象/实现类对象作为参数的时候。

可以考虑匿名内部类。

如果参数位置是一个接口或者抽象类,这个时候想到匿名内部类。

前提 该接口或者该抽象类程序里面没有设计过子类。

匿名内部类就是作为一个参数去传递的!

游泳接口:

public interface Swimming {

    void swim();
}

测试类定义 游泳

public class Demo {
    public static void main(String[] args) {

        //如果参数类型是接口类型 我们应该传递什么?
        goSwimming(new Swimming() {
            @Override
            public void swim() {
                System.out.println("我爱游泳 好多泡泡。");
            }
        });
    }
                 //定义一个游泳的功能
    public static void goSwimming(Swimming swimming){
         System.out.println("下水。");
        swimming.swim();//其实在编译阶段 只是检查语法错误 语法上没有 语法上 接口里面有这个功能
         System.out.println("出水。");
    }
}

(2)如果抽象父类/接口中的方法过多,不建议使用!由于匿名内部类是一次性的,少量的方法可以用,要实现的方法过多,建议还是新建一个具体的类挨个实现之后提高复用性!

让代码编写更灵活,不需要死板的新建类,实现接口,实现方法,创建对象!

(3)匿名内部类在使用的时候,如果明确要作为哪个抽象父类或者接口的对象,那么直接在传递参数的时候new,根据提示选择即可快速生成!二、枚举

五、枚举

5.1 认识枚举

5.1.1 认识枚举、枚举的原理

同学们,接下来我们学习一个新的知识点,枚举。枚举是我们以后在项目开发中偶尔会用到的知识。话不多说,我们还是先来认识一下枚举。

枚举是一种特殊的类,它的格式是:

public enum 枚举类名{
    枚举项1,枚举项2,枚举项3;
}

其实枚举项就表示枚举类的对象,只是这些对象在定义枚举类时就预先写好了,以后就只能用这几个固定的对象。

我们用代码演示一下,定义一个枚举类A,在枚举类中定义三个枚举项X, Y, Z

public enum A{
    X,Y,Z;
}

想要获取枚举类中的枚举项,只需要用类名调用就可以了

public class Test{
    public static void main(String[] args){
        //获取枚举A类的,枚举项
        A a1 = A.X;
        A a2 = A.Y;
        A a3 = A.Z;
    }
}

刚才说,枚举项实际上是枚举类的对象,这一点其实可以通过反编译的形式来验证(需要用到反编译的命令,这里不能直接将字节码拖进idea反编译)

在这里插入图片描述

我们会看到,枚举类A是用class定义的,说明枚举确实是一个类,而且X,Y,Z都是A类的对象;而且每一个枚举项都是被public static final 修饰,所以被可以类名调用,而且不能更改。

5.1.2 枚举深入

既然枚举是一个类的话,我们能不能在枚举类中定义构造器、成员变量、成员方法呢?答案是可以的。来看一下代码吧

public enum JiaoTongDeng {
    RED,YELLOW,GREEN("jack");//列举了三个对象

//    private JiaoTongDeng(){}  构造是私有
    private String name;
     //构造
     JiaoTongDeng(){
     }

     JiaoTongDeng(String name){
         this.name = name;
     }

    public String getName() {
        return name;
    }

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

虽然枚举类中可以像类一样,写一些类的其他成员,但是一般不会这么写,如果你真要这么干的话,到不如直接写普通类来的直接。

2.2 枚举的应用场景

刚才我们认识了一下什么是枚举,接下来我们看一下枚举在实际中的运用,枚举的应用场景是这样的:枚举一般表示一组信息,然后作为参数进行传输。

我们来看一个案例。比如我们现在有这么一个应用,用户进入应用时,需要让用户选择是女生、还是男生,然后系统会根据用户选择的是男生,还是女生推荐不同的信息给用户观看。

在这里插入图片描述

这里我们就可以先定义一个枚举类,用来表示男生、或者女生

public class Constant{
    BOY,GRIL
}

再定义一个测试类,完成用户进入系统后的选择

public class Test{
    public static void main(String[] args){
        //调用方法,传递男生
        provideInfo(Constant.BOY);
    }
    
    public static void provideInfo(Constant c){
        switch(c){
            case BOY:
                System.out.println("展示一些信息给男生看");
                break;
            case GRIL:
                System.out.println("展示一些信息给女生看");
                break;
        }
    }
}

最终再总结一下枚举的应用场景:枚举一般表示几个固定的值,然后作为参数进行传输

  • 20
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值