面向对象进阶二:抽象、接口、代码块、内部类、枚举
今天我们继续学习面向对象的语法知识,我们今天学习的主要内容是:抽象、接口、枚举。
学会这些语法知识,可以让我们编写代码更灵活,代码的复用性更高。
一、抽象
同学们,接下来我们学习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;
}
}
}
最终再总结一下枚举的应用场景:枚举一般表示几个固定的值,然后作为参数进行传输。