【JavaSE篇】——抽象类和接口

目录

🎓抽象类

🎈抽象类语法

🎈抽象类特性

🎈抽象类的作用

🎓接口

🎈语法规则

🎈接口特性

🎈接口使用(实现USB接口)

🎈实现多个接口

🎈接口间的继承

🎈接口使用实例 (给对象数组排序)

🌈Comparable接口<比较>

🌈Comparator接口<比较器>

🚩抽象类和接口的区别

🎓抽象类

抽象类自己本身是不具有什么特性的,就比如Shape类中我们有draw方法,但是Shape并没有什么具体的形状,而继承的子类比如三角形类,矩形类,圆形类等都是在有具体图形,我们称Shape类是抽象类。

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的, 如果 一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

 


在打印图形例子中 , 我们发现 , 父类 Shape 中的 draw 方法好像并没有什么实际工作 , 主要的绘制图形都是由 Shape的各种子类的 draw 方法来完成的 . 像这种没有实际工作的方法 , 我们可以把它设计成一个 抽象方法 (abstract method) , 包含抽象方法的类我们称为 抽象类 (abstract class)

🎈抽象类语法

Java 中,一个类如果被 abstract 修饰 称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法, 抽象方法不用给出具体的实现体
// 抽象类:被abstract修饰的类
abstract class Shape1{// 抽象方法:被abstract修饰的方法,没有方法体abstract public void draw();abstract void calcArea();// 抽象类也是类,也可以增加普通方法和属性public int age;public void func(){};
}
注意:抽象类也是类,内部可以包含普通方法和属性,甚至构造方法

🎈抽象类特性

1. 抽象类不能直接实例化对象 new   

肯定很多人疑问,抽象类不能实例化对象,那么存在意义在哪?—抽象类存在的意义是为了被继承

public class Test {Shape1 shape1=new Shape1();
}
//Shape1是抽象类,不能实例化

2.抽象类和普通类不一样的是抽象类可以包含抽象方法,可以包含普通类所包含的成员(上述语法已讲述)

3.抽象类和抽象方法都是由abstract修饰的,方法中没有具体的实现体(上述语法)
 4.抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰(被abstract修饰就不用重写方法,应用于有些类不需要这个方法)
abstract class Shape1{// 抽象方法:被abstract修饰的方法,没有方法体abstract public void draw();
}
class Cycle1 extends Shape1{@Overridepublic void draw() {System.out.println("画⚪");}
}

5.如果一个抽象类B继承了一个抽象类A,此时B当中不需要重写A的重写方法,但如果B再被

普通类继承,就需要重写。


6.抽象方法不能被private,final和static修饰,因为抽象方法要被子类重写
我们可以这样想,方法就是为了重写设置成私有的,怎么能被重写?
并且我们在 重写的时候说到 (java子类方法权限要等于大于父类的访问权限?)
7. 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
8. 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量
❗A类
abstract class A{public int age;public String name;public A(int age,String name){this.age=age;this.name=name;}abstract public void func();

❗B类

class B extends A{public B(){super(8,"chenle");}public void func(){System.out.println("sadasd");}
}

❗运行区


🎈抽象类的作用

抽象类本身不能被实例化 , 要想使用 , 只能创建该抽象类的子类 . 然后让子类重写抽象类中的抽象方法 .
有些同学可能会说了, 普通的类也可以被继承呀, 普通的方法也可以被重写呀, 为啥非得用抽象类和抽象方法
呢
确实如此 . 但是使用抽象类相当于多了一重编译器的校验 .
使用抽象类的场景就如上面的代码 , 实际工作不应该由父类完成, 而应由子类完成 . 那么此时如果不小心误用成父类了, 使用普通类编译器是不会报错的 . 但是父类是抽象类就会在实例化的时候提示错误 , 让我们尽早发现问题/。
很多语法存在的意义都是为了 " 预防出错 ", 例如我们曾经用过的 final 也是类似 . 创建的变量用户不去修改 , 不就相当于常量嘛? 但是加上 final 能够在不小心误修改的时候 , 让编译器及时提醒我们 .
充分利用编译器的校验 , 在实际开发中是非常有意义的 .

🎓接口

接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用
在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。

🎈语法规则

接口的定义格式与定义类的格式基本相同,将 class关键字换成 interface 关键字 ,就定义了一个接口。
public interface 接口名称{
// 抽象方法
public abstract void method1(); // public abstract 是固定搭配,可以不写
public void method2();
abstract void method3();
void method4();
// 注意:在接口中上述写法都是抽象方法,跟推荐方式4,代码更简洁
}
提示:
1. 创建接口时, 接口的命名一般以大写字母 I 开头.
2. 接口的命名一般使用 "形容词" 词性的单词.
3. 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性

🎈接口特性

1.使用interface来修饰接口

2.接口当中的成员方法,不能有具体的实现  [默认是public]

所以子类的重写方法不能默认不写,子类的重写方法是default,而子类的访问权限要大于父类的访问权限,所以我们必须在子类中的重写方法前写上public

  • 抽象方法:默认是public abstract方法
  • JDK1.8开始 ,允许有可以实现的方法,但是这个方法只能由default修饰
  • 可以实现一个静态方法
interface Ishape{public abstract void draw();//抽象方法void draw();//这是最好的表达抽象方法方式default public  void draw1(){System.out.println("默认方法");}public static void draw2(){System.out.println("静态方法");}
}

3.成员变量默认是public static final 修饰的(是不能修改的)

   成员方法 默认是public abstract 修饰(其他修饰符都是错的)

 public static final int age=10;//public static finalvoid draw3();//public abstract

4.接口不能被实例化(抽象类也不能实例化)因为接口中的方式默认为抽象方法

5.类和接口之间采用implements来实现多个接口

一旦类实现接口之后,就必须重写这个接口里面的抽象方法。接口里的普通成员方法可以重写也可以不重写。

interface Itest{void draw3();//public abstractdefault public  void draw1(){System.out.println("默认方法");}public static void draw2(){System.out.println("静态方法");}
}
class Cycle implements Itest{@Overridepublic void draw3() {System.out.println("必须重写");}@Overridepublic void draw1() {System.out.println("可以重写也可以不重写");}
}

6.将接口想象成特殊的类,我们可以当作参数,接口不能实例化,但是可以new普通的类。

interface Ishape{void draw();
}
class Cycle implements IShape{@Overridepublic void draw() {System.out.println("画⚪");}
}
class Rect implements IShape{@Overridepublic void draw() {System.out.println("画矩形");}
}class Flowers implements IShape{@Overridepublic void draw() {System.out.println("画❀");}
}
public class Test3 {public static void Map(IShape iShape){iShape.draw();}public static void main(String[] args) {Cycle cycle=new Cycle();Map(cycle);Map(new Flowers());Map(new Rect());}

相当于 Ishape ishape=new Cycle();

            ishape.draw();实现创建对象,进行对接口的调用。

7. 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class

一个接口就是一个java文件

 

8.接口中不能有静态代码块和构造方法 

9.如果不想实现接口的方法,那么就将这个类定义成抽象类,但是如果这个类被其他类继承,那么必须重写。(这个和抽象类类似)

 


🎈接口使用(实现USB接口)

接口不能直接使用,必须要有一个 " 实现类 " " 实现 " 该接口,实现接口中的所有抽象方法。
public class 类名称 implements 接口名称{
// ...
}
注意:子类和父类之间是extends 继承关系,类与接口之间是 implements 实现关系
请实现笔记本电脑使用 USB 鼠标、 USB 键盘的例子
1. USB 接口:包含打开设备、关闭设备功能
2. 笔记本类:包含开机功能、关机功能、使用 USB 设备功能
3. 鼠标类:实现 USB 接口,并具备点击功能
4. 键盘类:实现 USB 接口,并具备输入功能
package Interfaces;//USB接口
interface Iusb{void openDevice();void closeDevice();
}
//鼠标类
class Mouse implements Iusb{@Overridepublic void openDevice() {System.out.println("打开鼠标");}@Overridepublic void closeDevice() {System.out.println("关闭鼠标");}public void click(){System.out.println("点击鼠标");}
}// 键盘类,实现USB接口
class KeyBoard implements Iusb {@Overridepublic void openDevice() {System.out.println("打开键盘");}@Overridepublic void closeDevice() {System.out.println("关闭键盘");}public void inPut(){System.out.println("键盘输入");}
}// 笔记本类:使用USB设备
class Computer {public void powerOn() {System.out.println("打开笔记本电脑");}public void powerOff() {System.out.println("关闭笔记本电脑");}public static void useDevice(Iusb iusb){iusb.openDevice();if(iusb instanceof Mouse){Mouse mouse=(Mouse) iusb;mouse.click();}else if(iusb instanceof KeyBoard){KeyBoard keyBoard=(KeyBoard) iusb;keyBoard.inPut();}iusb.closeDevice();}}
public class Test4 {public static void main(String[] args) {Computer computer = new Computer();//打开设备computer.powerOn();//使用鼠标设备computer.useDevice(new Mouse());//使用键盘设备computer.useDevice(new KeyBoard());//关闭设备computer.powerOff();}
}


🎈实现多个接口

Java 中,类和类之间是单继承的,一个类只能有一个父类,即 Java 中不支持多继承 ,但是 一个类可以实现多个接 。【单继承多接口】下面通过类来表示一组动物 .

一个类可以实现多个接口。使用implements用逗号隔开。【可以解决多继承的问题】


我们可以定义一个动物类,然后狗类继承了动物类,我们实现多个接口,有些动物是又会飞又会跑,又会游泳的。

interface Running{void run();
}
interface Flying{void fly();
}
interface Swimming{void swim();
}

我们实现多个接口,而不是实现多个类,因为java中只能继承一个类,而不能继承多个类,但是一个类可以实现多个接口。我们不需要创建一个swim类,fiy类,run类,然后里面有自己的swim,fly,run的成员方法,然后狗类一个一个继承,因为java中不能多继承,所以代码的重复,而实现多个接口正是可以解决java中的多继承问题,让一个狗类同时implement多个接口,然后重写方法,即可完成了一个类继承多个接口的方法。

❗各接口

interface Running{void run();
}
interface Flying{void fly();
}
interface Swimming{void swim();
}

❗Animal类和各种子类

class Animal{int age;String name;Animal(int age,String name){this.age=age;this.name=name;}void eat(){System.out.println("正在吃饭");}
}class Dog extends Animal implements Running,Swimming{public Dog(int age, String name) {super(age, name);}@Overridepublic void run() {System.out.println(name+"正在跑");}@Overridepublic void swim() {System.out.println(name+"正在游泳");}public void eat(){System.out.println(name+"吃狗粮");}}
class Bird extends Animal implements Flying{public Bird(int age, String name) {super(age, name);}@Overridepublic void fly() {System.out.println(name+"正在飞");}public void eat(){System.out.println(name+"吃鸟粮");}
}
class Duck extends Animal implements Flying,Swimming,Running{public Duck(int age, String name) {super(age, name);}public void run() {System.out.println(name+"正在跑");}@Overridepublic void swim() {System.out.println(name+"正在游泳");}public void fly() {System.out.println(name+"正在跑");}public void eat(){System.out.println(name+"吃鸭粮");}
}

测试方法

public class Test6 {public static void walk(Running running){running.run();}public static void func(Animal animal){animal.eat();}public static void main(String[] args) {walk(new Dog(13,"chq"));walk(new Duck(12,"chhh"));}
}

🎈接口间的继承

Java 中,类和类之间是单继承的,一个类可以实现多个接口, 接口与接口之间可以多继承 。即:用接口可以达到多继承的目的。
接口可以继承一个接口 , 达到复用的效果 . 使用 extends 关键字 .

就是说我们D1类是一个既能完成A1的行为,也能完成B1的行为,还能完成C1接口行为,然后我们接口C1继承了A1和B1的接口,其实C1继承A1和B1的行为,就相当于如果类实施了C1,那么这三个A1,B1,C1都是行动的。

接口间的继承相当于把多个接口合并在一起.

🎈接口使用实例 (给对象数组排序)

❗Animal类

class Student{public int age;public String name;public Student(int age, String name) {this.age = age;this.name = name;}@Overridepublic String toString() {return "Student{" +"age=" + age +", name='" + name + '\'' +'}';}
}

❗运行 

这段代码是错误的,我们不管是按照什么来排序的,不管是年龄还是姓名都是错误的,我们可以来分析一下这段错误。

仔细思考 , 不难发现 , 和普通的整数不一样 , 两个整数是可以直接比较的 , 大小关系明确 . 而两个学生对象的大小关系怎么确定? 需要我们额外指定 .

🌈Comparable接口<比较>

让我们的 Student 类实现 Comparable 接口, 并实现其中的 compareTo 方法


✅按照年龄来比较(Comparable 接口, 并实现其中的 compareTo 方法)

class Student implements Comparable<Student>{public int age;public String name;public Student(int age, String name) {this.age = age;this.name = name;}@Overridepublic String toString() {return "Student{" +"age=" + age +", name='" + name + '\'' +'}';}@Overridepublic int compareTo(Student o) {if(this.age>o.age){return 1;}else if(this.age<o.age){return -1;}else{return 0;}}
}

当Student类实施了Comparable接口并且实现了compareTo方法,我们就可以根据年龄来进行比较。



我们可以看到底层,底层代码的比较是调用compareTo方法比较

 public static void main(String[] args) {Student student1=new Student(18,"chenle");Student student2=new Student(13,"zhang");if(student1.compareTo(student2)>0){System.out.println("student1>student2");}else {System.out.println("student2>student1");}}


🌈Comparator接口<比较器>

✅按照姓名来比较(实现Comparator接口,并实现compare方法)

我们是不能更改age的比较方法,那样势必回影响后面的运行,如果代码已经运行了一个月了,如果更改会影响很多。

所以我们需要利用 另外一个接口Comparator<T>以及接口里的compare方法

Comparator<T>接口中有很多方法,我们只需要compare抽象方法即可。



❗AgeCompare 

class AgeCompare implements Comparator<Student>{@Overridepublic int compare(Student o1, Student o2) {return o1.age-o2.age;}
}

❗运行 

 public static void main(String[] args) {Student[] students=new Student[3];students[0]=new Student(18,"chenle");students[1]=new Student(13,"zhang");students[2]=new Student(9,"hao");AgeCompare ageCompare=new AgeCompare();Arrays.sort(students,ageCompare);System.out.println(Arrays.toString(students));}



❗NameCompare 

因为比较字符串的底层是利用Comparable接口中的compareTo方法

class NameCompare implements Comparator<Student>{@Overridepublic int compare(Student o1, Student o2) {return o1.name.compareTo(o2.name);}
}

❗运行 

 public static void main(String[] args) {Student[] students=new Student[3];students[0]=new Student(18,"chenle");students[1]=new Student(13,"zhang");students[2]=new Student(9,"hao");NameCompare nameCompare=new NameCompare();Arrays.sort(students,nameCompare);System.out.println(Arrays.toString(students));}



我个人更推荐第二种,我们自己单独设置个类,然后我们直接利用比较器比较,如果比较整型直接return俩者相减即可,如果比较字符串,我们需要利用到compareTo方法进行比较。


🚩抽象类和接口的区别

核心区别 : 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用 ( 不必重写 ), 接口中不能包含普通方法,只能是抽象方法 , 子类必须重写所有的抽象方法。
如之前写的 Animal 例子 . 此处的 Animal 中包含一个 name 这样的属性 , 这个属性在任何子类中都是存在的 . 因此此处的 Animal 只能作为一个抽象类 , 而不应该成为一个接口
再次提醒 :
抽象类存在的意义是为了让编译器更好的校验 , Animal 这样的类我们并不会直接使用 , 而是使用它的子类 .万一不小心创建了 Animal 的实例 , 编译器会及时提醒我们


祝你逃出苦难向春山。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/668752.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

力扣刷题之旅:进阶篇(一)

力扣&#xff08;LeetCode&#xff09;是一个在线编程平台&#xff0c;主要用于帮助程序员提升算法和数据结构方面的能力。以下是一些力扣上的入门题目&#xff0c;以及它们的解题代码。 --点击进入刷题地址 题目1&#xff1a;三数之和 题目描述&#xff1a; 给定一个包含n个…

代码随想录算法训练营第41天 | 343.整数拆分 + 96.不同的二叉搜索树

今日任务 343. 整数拆分 96.不同的二叉搜索树 343.整数拆分 - Medium 题目链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0…

springboot+vue实现excel导出

后端 导入pom依赖 <dependency>x<groupId>cn.afterturn</groupId><artifactId>easypoi-spring-boot-starter</artifactId><version>4.2.0</version> </dependency> Entity实体类 这里以User为例&#xff0c;可按照自己实际…

vulhub中AppWeb认证绕过漏洞(CVE-2018-8715)

AppWeb是Embedthis Software LLC公司负责开发维护的一个基于GPL开源协议的嵌入式Web Server。他使用C/C来编写&#xff0c;能够运行在几乎先进所有流行的操作系统上。当然他最主要的应用场景还是为嵌入式设备提供Web Application容器。 AppWeb可以进行认证配置&#xff0c;其认…

【CSS】css如何实现字体大小小于12px?

【CSS】css如何实现字体大小小于12px? 问题解决方案transform: scale(0.5)&#xff08;常用&#xff09;SVG 矢量图设置text 问题 文字需要显示为12px&#xff0c;但是小于12px的&#xff0c;浏览器是显示不来的 解决方案 transform: scale(0.5)&#xff08;常用&#xff0…

树莓派-Ubuntu22.04

树莓派 1 安装Ubuntu系统2 ssh登录3 配置3.1 安装软件3.2 换源3.3 安装桌面3.4 开机脚本 1 安装Ubuntu系统 通过制作sdk&#xff0c;使系统在sdk中运行&#xff1a; 下载制作软件&#xff1a;https://www.raspberrypi.com/software/ 下载Ubuntu镜像&#xff1a;https://cn.ub…

【Day40】代码随想录之动态规划_343. 整数拆分_96.不同的二叉搜索树

文章目录 动态规划理论基础动规五部曲&#xff1a;出现结果不正确&#xff1a; 343. 整数拆分96.不同的二叉搜索树 动态规划理论基础 动规五部曲&#xff1a; 确定dp数组 下标及dp[i] 的含义。递推公式&#xff1a;比如斐波那契数列 dp[i] dp[i-1] dp[i-2]。初始化dp数组。…

基于Springboot开发的JavaWeb作业查重系统[附源码]

基于Springboot开发的JavaWeb作业查重系统[附源码] &#x1f345; 作者主页 央顺技术团队 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; &#x1f345; 查看下方微信号获取联系方式 承接各种定制系统 &a…

zlib交叉编译(rv1126)

目录 1.下载 2.解压 3.配置 4.编译 1.下载 1)下载地址 zlib Home Site 2)下载tar.gz版本 下载该版本。 2.解压 1)解压到某个文件夹

探索设计模式的魅力:从单一继承到组合模式-软件设计的演变与未来

设计模式专栏&#xff1a;http://t.csdnimg.cn/nolNS 在面对层次结构和树状数据结构的软件设计任务时&#xff0c;我们如何优雅地处理单个对象与组合对象的一致性问题&#xff1f;组合模式&#xff08;Composite Pattern&#xff09;为此提供了一种简洁高效的解决方案。通过本…

简单的JavaScript去下载转换为Base64的PDF文件

新建一个文件&#xff0c;内容填写如下&#xff0c;然后保存为 .html 类型的文件 再用浏览器打开&#xff0c;就会是下面这样子&#xff1a; 图一红色textarea里面&#xff0c;可以将PDF文件转换成BASE64位后的内容贴进去&#xff0c;点击下载时&#xff0c;就可以直接下载成PD…

进程的基本概念、查看、创建

1. 进程的概念 概念&#xff1a;加载到内存的程序/正在运行的程序称为内存。 我们在玩电脑的时候是可以启动多个程序的&#xff0c;比如边听歌边写博客&#xff0c;根据上篇文章我们知道肯定要将多个.exe文件加载到内存中&#xff0c;作为操作系统肯定是要管理这多个加载到内存…

开源大数据集群部署(十)Ranger usersync部署

作者&#xff1a;櫰木 ranger usersync部署 解压包 [roothd1.dtstack.com ranger]# pwd /opt/ranger [roothd1.dtstack.com ranger]# tar -zxvf ranger-2.3.0-usersync.tar.gz -C /opt/ [roothd1.dtstack.com ranger]# cd ranger-2.3.0-usersync修改配置install.properties…

小白Linux学习笔记-Linux开机启动流程

Linux 开机启动流程 文章目录 Linux 开机启动流程启动流程概览详细讲解开机软件 —— BIOS、Grub名词解释流程解释BIOS 开机文档 —— menu.lst、grub.confGrub 配置文档流程解释 init 程序流程解释init 执行的相关文件 run-level(启动等级) 相关的命令实验rhel6 单用户模式修改…

改变终端安全的革命性新兴技术:自动移动目标防御技术AMTD

自动移动目标防御技术通过启用终端配置的自适应防御来改变终端检测和响应能力。产品领导者可以实施AMTD来确保实时威胁响应&#xff0c;并减少检测和响应安全威胁所需的时间。 主要发现 通过动态修改系统配置、软件堆栈或网络特征&#xff0c;自动移动目标防御&#xff08;AMTD…

MQ,RabbitMQ,SpringAMQP的原理与实操

MQ 同步通信 异步通信 事件驱动优势&#xff1a; 服务解耦 性能提升&#xff0c;吞吐量提高 服务没有强依赖&#xff0c;不担心级联失败问题 流量消峰 ​ 小结: 大多情况对时效性要求较高&#xff0c;所有大多数时间用同步。而如果不需要对方的结果&#xff0c;且吞吐…

性能实测:分布式存储 ZBS 与集中式存储 HDS 在 Oracle 数据库场景表现如何

作者&#xff1a;深耕行业的 SmartX 金融团队 金鑫 在金融客户的基础架构环境中&#xff0c;HDS 是一种被广泛使用的存储解决方案。作为集中式存储的代表之一&#xff0c;HDS 拥有高性能、高可用性和可扩展性的企业级存储特点&#xff0c;适用于实时数据处理、虚拟化和灾难备份…

Python 潮流周刊#38:Django + Next.js 构建全栈项目

△△请给“Python猫”加星标 &#xff0c;以免错过文章推送 你好&#xff0c;我是猫哥。这里每周分享优质的 Python、AI 及通用技术内容&#xff0c;大部分为英文。本周刊开源&#xff0c;欢迎投稿[1]。另有电报频道[2]作为副刊&#xff0c;补充发布更加丰富的资讯&#xff0c;…

开源软件全景解析:驱动技术创新与行业革新的力量

目录 什么是开源 开源的核心 开源软件的特点 为什么程序员应该拥抱开源 1.学习机会&#xff1a; 2.社区支持&#xff1a; 3.提高职业竞争力&#xff1a; 4.加速开发过程&#xff1a; 5.贡献和回馈&#xff1a; 开源软件的影响力 开源软件多元分析&#xff1a; 开源…

蓝桥杯刷题day06——平均

1、题目描述 有一个长度为n 的数组&#xff08;n 是 10 的倍数&#xff09;&#xff0c;每个数ai都是区间 [0,9] 中的整数。 小明发现数组里每种数出现的次数不太平均&#xff0c;而更改第i 个数的代价为bi&#xff0c; 他想更改若干个数的值使得这10 种数出现的次数相等&…