Java 每日一题:Song 类的设计与应用

一、题目背景

在 Java 编程中,类的设计与应用是构建复杂程序的基础。通过对实际场景中的对象进行抽象建模,我们能够将现实世界中的事物转化为代码中的类,从而实现各种功能需求。本题围绕音乐领域的歌曲信息管理展开,旨在通过设计一个Song类,考查对类的成员变量、成员方法的设计以及数组操作和排序算法的运用。

二、题目要求分析

类的设计

成员变量:需要定义五个私有成员变量,分别是releaseYear(发行年)、songTitle(歌曲名)、singerName(歌手名)、songType(歌曲类型)和dailyPlayCount(日播放量)。私有访问控制权限保证了数据的安全性,外部类不能直接访问和修改这些变量,只能通过类提供的公共方法来操作。
成员方法

带参构造方法Song(int releaseYear, String songTitle, String singerName, String songType, int dailyPlayCount)用于在创建Song对象时初始化各个成员变量。
公有成员方法int compareSong(Song dstobj)用于比较当前歌曲对象和另一个目标歌曲对象的发行年,为后续的排序操作提供依据。
设计缺省成员方法用于获取各个成员变量的值,方便外部类间接访问私有成员变量。同时,重写toString()方法,以更友好的格式输出歌曲的完整信息。

主类实现

创建一个包含 5 个元素的Song对象数组,并为每个对象的成员变量赋予题目给定的值。
实现按照发行年进行升序排序,并输出排序后的结果及每首歌曲的完整信息。
统计歌曲类型为PopMusic的歌曲数目,并输出这些歌曲的详细信息。

拓展练习(选做):实现按照日播放量进行升序排序,当日播放量相同时,按照歌曲名升序排序。这需要对现有程序进行修改或补充,通常会涉及到使用 Java 的Comparator接口来定制排序规则。

三、代码实现

1. Song类源代码

// 定义 Song 类
class Song {
    // 发行年
    private int releaseYear;
    // 歌曲名
    private String songTitle;
    // 歌手名
    private String singerName;
    // 歌曲类型
    private String songType;
    // 日播放量
    private int dailyPlayCount;

    // 带参构造方法,用于初始化歌曲的各项信息
    public Song(int releaseYear, String songTitle, String singerName, String songType, int dailyPlayCount) {
        this.releaseYear = releaseYear;
        this.songTitle = songTitle;
        this.singerName = singerName;
        this.songType = songType;
        this.dailyPlayCount = dailyPlayCount;
    }

    // 比较当前歌曲和目标歌曲的发行年,用于排序
    public int compareSong(Song dstobj) {
        return this.releaseYear - dstobj.releaseYear;
    }

    // 获取发行年
    public int getReleaseYear() {
        return releaseYear;
    }

    // 获取歌曲名
    public String getSongTitle() {
        return songTitle;
    }

    // 获取歌手名
    public String getSingerName() {
        return singerName;
    }

    // 获取歌曲类型
    public String getSongType() {
        return songType;
    }

    // 获取日播放量
    public int getDailyPlayCount() {
        return dailyPlayCount;
    }

    // 输出歌曲的完整信息
    @Override
    public String toString() {
        return "发行年: " + releaseYear + ", 歌曲名: " + songTitle + ", 歌手名: " + singerName +
                ", 歌曲类型: " + songType + ", 日播放量: " + dailyPlayCount;
    }
}
代码分析

成员变量定义:使用private关键字修饰五个成员变量,确保数据的封装性。外部类无法直接访问这些变量,只能通过类提供的公共方法进行操作,这样可以避免数据被意外修改,提高代码的安全性和稳定性。
构造方法:带参构造方法接受五个参数,分别对应五个成员变量,在创建Song对象时,通过参数传递来初始化对象的各个属性,保证每个Song对象在创建时都具有完整的信息。
compareSong方法:该方法用于比较当前歌曲对象和传入的目标歌曲对象的发行年。返回值为两个发行年的差值,如果当前歌曲的发行年小于目标歌曲的发行年,返回负数;如果相等,返回 0;如果大于,返回正数。这个方法为后续按照发行年进行排序提供了比较的逻辑。
获取方法getReleaseYeargetSongTitlegetSingerNamegetSongTypegetDailyPlayCount方法用于返回相应成员变量的值,使得外部类能够获取到Song对象的属性信息,同时又不破坏数据的封装性。
toString方法:重写toString方法,将Song对象的所有属性以字符串的形式组合起来返回。这样在需要输出Song对象信息时,直接调用System.out.println(song)就可以以清晰的格式输出歌曲的完整信息,提高了代码的可读性和易用性。

2. 主类源代码

// 主类
public class Main {
    public static void main(String[] args) {
        // 创建 Song 对象数组并初始化
        Song[] songs = {
                new Song(1973, "Yesterday Once More", "Carpenters", "PopMusic", 5000),
                new Song(2007, "Your Man", "Josh Turner", "CountryMusic", 3000),
                new Song(1997, "My Heart Will Go On", "Céline Dion", "PopMusic", 10000),
                new Song(2008, "Love Story", "Taylor Swift", "Country - popMusic", 20000),
                new Song(1977, "We Will Rock You", "Queen", "RockMusic", 10000)
        };

        // 按照发行年进行升序排序
        for (int i = 0; i < songs.length - 1; i++) {
            for (int j = 0; j < songs.length - i - 1; j++) {
                if (songs[j].compareSong(songs[j + 1]) > 0) {
                    Song temp = songs[j];
                    songs[j] = songs[j + 1];
                    songs[j + 1] = temp;
                }
            }
        }

        // 输出按发行年排序后的结果
        System.out.println("按发行年升序排序后的结果:");
        for (Song song : songs) {
            System.out.println(song);
        }

        // 统计 PopMusic 类型的歌曲数目并输出信息
        int popMusicCount = 0;
        System.out.println("
PopMusic 类型的歌曲信息:");
        for (Song song : songs) {
            if ("PopMusic".equals(song.getSongType())) {
                popMusicCount++;
                System.out.println(song);
            }
        }
        System.out.println("PopMusic 类型的歌曲数目:" + popMusicCount);
    }
}
代码分析

对象数组创建与初始化:在main方法中,创建了一个Song类型的数组songs,并使用给定的歌曲信息初始化数组中的每个元素。通过这种方式,将多个Song对象组织在一起,方便后续对这些歌曲信息进行批量处理。
发行年排序:使用冒泡排序算法对songs数组按照发行年进行升序排序。冒泡排序是一种简单的比较排序算法,它通过多次比较相邻元素并交换位置,将最大(或最小)的元素逐步 “冒泡” 到数组的末尾。在每次外层循环中,内层循环比较相邻的两个歌曲对象的发行年,如果前一个歌曲的发行年大于后一个歌曲的发行年,则交换它们的位置。经过多次循环后,数组中的歌曲对象就会按照发行年升序排列。
结果输出:排序完成后,通过for - each循环遍历songs数组,调用每个Song对象的toString方法,将按发行年排序后的歌曲信息输出到控制台。接着,再次遍历数组,统计歌曲类型为PopMusic的歌曲数目,并输出这些歌曲的详细信息。通过这种方式,实现了对歌曲信息的筛选和统计功能。

3. 拓展功能主类源代码

import java.util.Arrays;
import java.util.Comparator;

// 主类(拓展功能)
public class Main_extended {
    public static void main(String[] args) {
        // 创建 Song 对象数组并初始化
        Song[] songs = {
                new Song(1973, "Yesterday Once More", "Carpenters", "PopMusic", 5000),
                new Song(2007, "Your Man", "Josh Turner", "CountryMusic", 3000),
                new Song(1997, "My Heart Will Go On", "Céline Dion", "PopMusic", 10000),
                new Song(2008, "Love Story", "Taylor Swift", "Country - popMusic", 20000),
                new Song(1977, "We Will Rock You", "Queen", "RockMusic", 10000)
        };

        // 按照日播放量进行升序排序,播放量一样就按照歌曲名升序排序
        Arrays.sort(songs, new Comparator<Song>() {
            @Override
            public int compare(Song s1, Song s2) {
                if (s1.getDailyPlayCount() != s2.getDailyPlayCount()) {
                    return s1.getDailyPlayCount() - s2.getDailyPlayCount();
                } else {
                    return s1.getSongTitle().compareTo(s2.getSongTitle());
                }
            }
        });

        // 输出按日播放量和歌曲名排序后的结果
        System.out.println("按日播放量升序排序,播放量一样按歌曲名升序排序后的结果:");
        for (Song song : songs) {
            System.out.println(song);
        }
    }
}
代码分析

拓展功能实现:在拓展功能的主类Main_extended中,同样先创建并初始化了Song对象数组songs。然后,使用Arrays.sort方法对数组进行排序。与之前不同的是,这里传入了一个自定义的Comparator对象。Comparator接口用于定义比较两个对象的规则。在重写的compare方法中,首先比较两个歌曲对象的日播放量,如果日播放量不同,则按照日播放量的差值进行排序,实现日播放量升序。如果日播放量相同,则通过调用String类的compareTo方法比较歌曲名,实现歌曲名升序。这样就满足了按照日播放量进行升序排序,当日播放量相同时按照歌曲名升序排序的要求。最后,通过for - each循环遍历排序后的数组,输出按照新规则排序后的歌曲信息。

四、代码运行结果

按照发行年排序并输出结果

按发行年升序排序后的结果:
发行年: 1973, 歌曲名: Yesterday Once More, 歌手名: Carpenters, 歌曲类型: PopMusic, 日播放量: 5000
发行年: 1977, 歌曲名: We Will Rock You, 歌手名: Queen, 歌曲类型: RockMusic, 日播放量: 10000
发行年: 1997, 歌曲名: My Heart Will Go On, 歌手名: Céline Dion, 歌曲类型: PopMusic, 日播放量: 10000
发行年: 2007, 歌曲名: Your Man, 歌手名: Josh Turner, 歌曲类型: CountryMusic, 日播放量: 3000
发行年: 2008, 歌曲名: Love Story, 歌手名: Taylor Swift, 歌曲类型: Country - popMusic, 日播放量: 20000

统计PopMusic类型歌曲并输出结果

PopMusic 类型的歌曲信息:
发行年: 1973, 歌曲名: Yesterday Once More, 歌手名: Carpenters, 歌曲类型: PopMusic, 日播放量: 5000
发行年: 1997, 歌曲名: My Heart Will Go On, 歌手名: Céline Dion, 歌曲类型: PopMusic, 日播放量: 10000
PopMusic 类型的歌曲数目:2

按照日播放量和歌曲名排序并输出结果(拓展功能)

按日播放量升序排序,播放量一样按歌曲名升序排序后的结果:
发行年: 2007, 歌曲名: Your Man, 歌手名: Josh Turner, 歌曲类型: CountryMusic, 日播放量: 3000
发行年: 1973, 歌曲名: Yesterday Once More, 歌手名: Carpenters, 歌曲类型: PopMusic, 日播放量: 5000
发行年: 1997, 歌曲名: My Heart Will Go On, 歌手名: Céline Dion, 歌曲类型: PopMusic, 日播放量: 10000
发行年: 1977, 歌曲名: We Will Rock You, 歌手名: Queen, 歌曲类型: RockMusic, 日播放量: 10000
发行年: 2008, 歌曲名: Love Story, 歌手名: Taylor Swift, 歌曲类型: Country - popMusic, 日播放量: 20000

五、考点总结

类的设计与封装:本题重点考查了如何设计一个类,包括定义私有成员变量来封装数据,以及通过构造方法和公共访问方法来操作这些数据。合理的类设计能够提高代码的安全性、可维护性和复用性。
排序算法:涉及到使用冒泡排序算法对数组进行排序,同时在拓展功能中使用了Arrays.sort方法结合自定义Comparator来实现更复杂的排序规则。排序算法是数据处理中的重要工具,掌握不同排序算法的原理和应用场景对于优化程序性能至关重要。
数组操作:在主类中,创建、初始化和遍历数组的操作贯穿始终。数组是 Java 中常用的数据结构,用于存储和管理一组相同类型的数据。熟练掌握数组的基本操作是进行数据处理和算法实现的基础。
接口的应用:在拓展功能中,通过实现Comparator接口来定义自定义的比较规则,体现了 Java 中接口的灵活性和强大功能。接口可以用于实现不同类之间的行为统一,为代码的扩展性和可维护性提供支持。

六、问题问答

为什么要将成员变量设置为私有访问控制权限?
将成员变量设置为私有访问控制权限是为了实现数据的封装。封装是面向对象编程的重要特性之一,它可以隐藏类的内部实现细节,只对外提供公共的访问方法。通过将releaseYearsongTitlesingerNamesongTypedailyPlayCount设置为私有,外部类无法直接访问和修改这些变量,避免了数据被意外篡改,保证了数据的安全性和一致性。同时,类可以通过提供精心设计的公共方法来控制对这些变量的访问,例如在get方法中可以添加数据校验或日志记录等逻辑,增强了代码的可维护性和可扩展性。
在主类中,冒泡排序是如何实现按照发行年升序排序的?
在主类中,冒泡排序通过两层嵌套的for循环来实现按照发行年升序排序。外层循环控制排序的轮数,内层循环用于比较相邻元素(即歌曲对象)的发行年。在内层循环中,每次比较相邻的两个歌曲对象songs[j]songs[j + 1],通过调用compareSong方法获取两个歌曲发行年的差值。如果songs[j].compareSong(songs[j + 1]) > 0,说明前一个歌曲的发行年大于后一个歌曲的发行年,此时交换这两个歌曲对象在数组中的位置。随着内层循环的不断执行,每一轮都会将当前未排序部分中发行年最大的歌曲 “冒泡” 到数组的末尾。经过length - 1轮循环后,数组中的歌曲对象就按照发行年升序排列好了。
在拓展功能中,使用Comparator接口有什么好处?
在拓展功能中使用Comparator接口主要有以下好处:

灵活性Comparator接口允许我们为不同的排序需求定义不同的比较规则。在本题中,我们需要按照日播放量升序排序,且当日播放量相同时按照歌曲名升序排序,通过实现Comparator接口的compare方法,我们可以轻松定制这样复杂的排序规则,而不需要修改Song类的内部代码。
代码复用:如果后续还有其他不同的排序需求,例如按照歌手名排序或者按照歌曲类型排序,我们可以创建

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容