一、顺序容器
案例:记事本功能
A. 接口设计
首先:人机交互部分与业务逻辑部分要分开。
人机交互部分指的是:输入的地方,输出的地方。
业务逻辑部分指的是:进行计算的部分。
记事本的功能需要包括:1.能存储数据;2.不限制能存储的记录的数量;3.能知道已经存储的记录的数量;4.能查看存进去的每一条记录;5.能删除一条记录;6. 能列出所有记录
对应的记事本需要实现的方法包括:1+2. add(String note); 3.getSize(); 4.getNote(int index); 5.removeNote(int index); 6.list();
B. 思路
1. 功能 add(String note)():这里不可以创数组,因为有大小控制 --> 要用其他的容器(引入容器 ArrayList)。
容器类有两个类型:容器的类型 + 元素的类型
// 创建成员变量 ArrayList
ArrayList<String> notes = new ArrayList<String>;
ArrayList<String> (ArrayList of String),代表用来存放 String 的ArrayList,泛型类,notes 是管理者。
【泛型】规范数据类型--容器<数据类型>,如果之前没有规范数据类型<>,那么获取到的都是 Object 类型,若要转换成我们输入的类型,就要强转。【Java编程】面向对象程序设计--接口与对象容器_蜗牛跳码的博客-CSDN博客
Object obj = lst.get(0); // 为保证列表/容器的通用性,所以都是object
String s = (String) obj; // 向下转型,需要强转
调用 add(String note)() 与直接创建 String[] 数组的差异
public static void main(String[] args) {
String[] a = new String[2]; // 直接创建 String[]数组,放进两个元素
a[0] ="first";
a[1] ="second";
NoteBook nb = new NoteBook(); // 创建 ArrayList ,放两个元素
nb.add("first");
nb.add("second");
System.out.println(nb.getSize());
}
增加元素的时候默认在末尾增加,如果想要指定位置,可以再用增加一个方法,利用 add(int index, element),注意转变成自己的方法时候的变换;
public void add(String s, int location){
notes.add(location,s)
}
2. 同理,功能 getSize() 与 getNote(int index)
这里根据位置查询的时候,不能用数组 Array 的方法 nb[1],而需要用 ArrayList 的获取方法 nb.getNote(1);
3. 功能 removeNote()
如果直接返回 return notes.remove(index) 会报错!(Type mismatch:cannot convert from String to boolean)
原来是String 但要求是boolean
根据提示需要将 boolean 改为 String。又因为 ArrayList 这里的 remove 返回值是
Returns: the element that was removed from the list 返回被我们删掉的元素,所以干脆用 void。
4. 功能 list()
这里可以有两种设计:void 后直接打印 sout,或者用字符串类型数组 String[] ,返回我输出的数组。至于要不要输出,让上一层去做。那么第二种方式,首先要创建一个数组String[],大小.size(),这点跟基础知识里的,数组的大小可以用参数来表示一样,再进行遍历。
public String[] list(){
String[] a = new String[notes.size()]; // 创建一个数组
for(int i=0; i < notes.size(); i++){ // 遍历数组元素
a[i] = notes.get(i); // 获取数组的方法 a[i] ,别跟get混了
return a;
}
调用的时候就写:
// 增强 for循环遍历
String[] a = nb.list();
for (String s : a){
System.out.println(s);
另外一种方法: toArray(参数)
public String[] list(){
String[] a = new String[notes.size()]; // 创建一个数组
notes.toArray(a); // toArray(参数)
return a;
}
// 调用语句 增强 for循环遍历
nb.list();
for(String i:nb.list()){
System.out.println(i);
}
first
second
third
增强 for 里面的数据类型,如果没有给的话,例如这里的 String ,Integer ,就得用 Object,所以一般创建容器的时候要增加<泛型>。
整个记事本的完整代码如下:
package Collection;
import java.util.ArrayList;
public class NoteBook {
// 2.创建成员变量 ArrayList(<> 读成 of String,代表用来存放String的ArrayList) 泛型类
// 3.note 是管理者
private ArrayList<String> notes = new ArrayList<String>();
public void add(String s){
// 1.不可以创数组,因为有大小控制--> 要用其他的容器(引入容器 ArrayList)
notes.add(s); // 只接收String 因为提前规定了
}
public int getSize(){
return notes.size();
}
public String getNote(int index){
return notes.get(index);
}
public void removeNote(int index){
notes.remove(index);
}
// 两种设计:void ,sout() 或者 String[] ,返回我输出的数组,至于要不要输出让上一层去做
// 返回 new String[10]
public String[] list(){
String[] a = new String[notes.size()]; // 创建一个数组
/* for(int i=0; i < notes.size(); i++){ // 遍历数组元素
a[i] = notes.get(i); // 获取数组的方法 a[i] ,别跟get 混了
return a;*/
notes.toArray(a);
return a;
}
public static void main(String[] args) {
NoteBook nb = new NoteBook(); // 创建 ArrayList ,放两个元素
nb.add("first");
nb.add("second");
nb.add("third");
System.out.println(nb.getSize());
System.out.println(nb.getNote(0));
nb.removeNote(0);
// 增强 for循环遍历
nb.list();
for(String i:nb.list()){
System.out.println(i);
}
}
}
第一节遗留问题: 究竟把什么放进容器里去了?
当我们用add函数把对象放进容器中去的时候,究竟是把什么放进了容器?放进去的是对象本身吗?放进去以后,那个对象还在外面吗?如果修改了放进去的那个对象,容器中的对象会变化吗?写点代码来验证自己的猜测吧。
二、对象数组
2.1 对象数组的定义
假设我们需要存储数量众多的学生对象(c1,c2,c3...)的信息,我们可以先创建一个学生信息类(students)。这些对象是可以存储在数组中的,即保存多个类对象。该如何定义该数组的元素类型?--> 类名称[ ]
动态初始化:类名称[] 对象数组名称 = new 类名称[长度];([ ] 可前可后)
静态初始化:类名称[] 对象数组名称 = { 对象实例1, 对象实例2, 对象实例3,... }
在Java 中数据类型分为两种:基本数据类型、引用数据类型。所以针对于数组实际上也就存在基本数组、引用数组,对于引用类型当前只学习到了类。
package Collection;
import java.util.HashMap;
public class students {
int age = 0;
String name = "";
public students(String name, int age) {
this.age = age;
this.name = name;
}
public HashMap<String, Integer> obj() {
HashMap<String, Integer> h = new HashMap<>();
h.put(name, age);
return h;
}
public static void main(String[] args) {
students c1 = new students("张三", 20);
students c2 = new students("李四", 25);
students c3 = new students("王二麻子", 30);
students[] a = {c1, c2, c3}; // 静态对象数组初始化之后里面为其具体的对象的那个内容
System.out.println(a[0].name + a[0].age); // 成员变量
System.out.println(a[0].obj()); // 打印一个人
for (students k : a) {
System.out.println(k.obj()); // 打印多个人
}
}
}
张三20 // 成员变量
{张三=20} // 打印一个人
{张三=20} // 打印多个人
{李四=25}
{王二麻子=30}
以上操作是将之前独立的若干个学生对象全部交由对象数组来进行线性的统一的管理。打印一个人和打印多个人的信息。
2.2 对象数组初始化和赋值
所有的静态对象数组初始化之后里面为其具体的对象的那个内容(student 例),动态初始化本身所有的内容全部为 null(如下例 a )。
因为,数组的每一个元素其实只是对象的管理者而不是对象本身。如果仅仅创建数组并没有创建其中的每一个对象。 所以,需要继续进行对象的赋值。
如上图:int 类型的变量 i 和 int[ ] 组类型,都是创造的空间直接存储了数据。但是就像 String 类型变量一样,s 是对象的管理者(参见“【Java 编程】编程基础--字符与字符串”中的“字符串”部分),String[]数组中,每一个元素也是对象的管理者。下面用一个例子,简单表达下如果对对象数组进行赋值。
史博:【Java编程】编程基础--字符与字符串0 赞同 · 0 评论文章正在上传…重新上传取消
public class Price {
private int i = 0;
public void set(int i) {
this.i = i;
}
public int get() {
return i;
}
public static void main(String[] args) {
Price[] a = new Price[10];// a 只是管理者,刚开辟时 All elements are null
for (int i = 0; i < a.length; i++) {
a[i] = new Price(); // 给对象数组赋值
a[i].set(i);
}
for (Price v : a) { // 无法直接打印,需要遍历
System.out.println(v.get());
}
}
}
2.3 内存结构的关系分析
这一部分内容可以参见: 对象数组的定义与引用分析_哔哩哔哩_bilibili
以上文中的 class Price 为例。
堆内存:存储地址
栈内存:存储整个对象的具体内容
遇到 new ,就是开辟一个新的堆内存空间。
Price[] a = new Price[10]
a[i] = new Price()
这个 class 里就一个成员变量 i (可以创建多个成员变量)。本地变量 this里初始值为 0 。再调用一般方法set(),将值传给对象数组 a[0] 、a[1] ...里面的管理者。
整个过程完成了“引用之中,再嵌套“的一个处理环境。类对象本身就属于引用,而数组依然属于引用(int[]是不是除外)。不管程序怎么变,永远是堆跟栈的处理关系,不过有的好画,有的难画。
for - each 循环
对于对象数组来说不一样,每一轮它可以拿到对象数组管理的那个对象 a[i],并对它进行赋值。
for (Price v : a) {
System.out.println(v.get());
v.set(0);
}
第二节遗留问题:如何设计能传递任意数量参数的函数?
另外,除了用对象数组之外,也可以搜搜看Java是否本身就支持任意数量参数的函数。
三、集合容器 Set
集合就是数学中的集合的概念:所有的元素都具有唯一的值,元素在其中没有顺序。【Java编程】面向对象程序设计--接口与对象容器_蜗牛跳码的博客-CSDN博客
class Set {
public static void main(String[] args) {
ArrayList<String> a = new ArrayList<String>();
a.add("first");
a.add("second");
a.add("third");
a.add("third");
System.out.println(a); // 直接打印
System.out.println("-------------");
HashSet<String> s = new HashSet<String>();
s.add("first");
s.add("second");
s.add("third");
s.add("third"); // 除重
System.out.println(s); // 直接打印
}
}
HashSet 和 ArrayList 可以直接打印输出,而不用像 Price 类那样遍历。如何可以让任何一个类都可以直接打印?
public class NoteBook {
private int i = 0;
public void set(int i) {
this.i = i;
}
// Returns a string representation of the object.
public String toString() {
return "" + i;
} // 把 i 变成了字符串
public static void main(String[] args) {
NoteBook s1 = new NoteBook();
s1.set(4);
System.out.println(s1); // 可以直接打印了
}
}
Returns a string representation of the object. It is recommended that all subclasses override this method.
In other words, this method returns a string equal to the value of:
toString方法返回一个字符串,该字符串“文本表示”此对象。建议所有子类都重写此方法。
任何一个Java的类,只要实现了 public String toString(){return "" + i},都可以自动调用 toString 函数,从而可以直接打印。
第三节遗留问题:集合能用get()函数来获得某个位置上的元素吗?
如果不能,说说你的理由。不要说试过了,就是不能的。要说说你认为为什么集合不能做这个操作。
四、散列表 Hash
传统意义上的Hash表,是能以 int 做键,将数据存放起来的数据结构。Java的Hash表可以以任何实现了hash()函数的类的对象做值来存放对象。【Java编程】面向对象程序设计--接口与对象容器_蜗牛跳码的博客-CSDN博客
4.1 常用操作
用法 | 函数 |
---|---|
将指定的值与此映射中的指定键相关联。 | put(K key, V value) (K V都是Object 包裹类型) |
返回此映射中包含的键的Set视图。 | keySet() |
返回指定键映射到的值,如果此映射不包含键的映射,则返回 null 。 | get(Object key) |
如果此映射包含指定键的映射,则返回 true 。 | containsKey(Object key) |
4.2 案例 :美元映射
1 = "penny"
10 = "dime"
25 = "quarter"
50 = "half-dollar"
思路
首先,定义接口,别人给出一个数(scanner类实现) amount,程序 getName(int amount)函数可以给出对应的 coinname。不用switch case ,因为我们希望体现在编码里的硬编码的东西越少越好。
其次,利用构造函数,创建 HashMap<>,将映射信息输入。
然后,利用 if..else来优化判断输入的结果。
另外,如果想要看现在集合里面有多少对元素,需要利用 keySet() 获得键的集合,再求其size()。
最后,遍历存储在 HashMap<> 里的对象coin的所有映射关系:Integer i : coin.coinnames.keySet()。
import java.util.HashMap;
import java.util.Scanner;
public class Coin {
private HashMap<Integer, String> coinnames = new HashMap<>();
int amount = 0;
public Coin() { // 构造函数 利用HashMap装“键值对”
coinnames.put(1, "penny");
coinnames.put(10, "dime");
coinnames.put(25, "quarter");
coinnames.put(50, "half-dollar");
coinnames.put(50, "五毛"); // 键唯一,重复会将之前的值覆盖
// 如果想要看现在集合里面有多少对元素
// System.out.println(coinnames.keySet().size());
}
public String getName(int amount) {
if (coinnames.containsKey(amount)) {
String s = coinnames.get(amount);
return s;
} else {
return "Not Found";
}
}
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int amount = scan.nextInt();
Coin coin = new Coin();
String name = coin.getName(amount); //实现输入一个amount,获得对应的coinname。需要交给一个变量
System.out.println(name);
System.out.println("\ncoin对象里的所有映射: " + coin.coinnames); // 打印c对象里面的成员变量coinnames
// 如果想要遍历集合里面的所有元素 需要通过键 keySet();
System.out.println("\n遍历所有值:");
for (Integer i : coin.coinnames.keySet()) {
String names = coin.coinnames.get(i);
System.out.println(names);
}
}
}
第四节遗留问题:学生成绩的数据结构
如果要写程序表达一个班级的很多个学生的很多门课的成绩,应该如何表达这些数据?
如果我们希望通过学生的姓名,可以找到他的所有的成绩,而每一门课的成绩,是由课程名称和分数构成的。
而如果我们还希望这个程序能找出某一门课的全部学生的成绩应该怎样做呢?
注意,并非所有的学生都参加了所有的课程。