泛型和集合
泛型即参数化类型,也就是说数据类型变成了一个可变的参数,在不使用泛型的情况下,参数的数据类型都是写死了的,使用泛型之后,可以根据程序的需要进行改变。
定义泛型的规则: 只能是引用类型,不能是简单数据类型。 泛型参数可以有多个。 可以用使用 extends 语句或者 super 语句 如 表示类型的上界,T 只能是 superClass 或其子类, 表示类型的下界,K 只能是 childClass 或其父类。 可以是通配符类型,比如常见的 Class<?>。单独使用 ? 可以表示任意类型。也可以结合 extends 和 super 来进行限制。
接下来我们来定义一个泛型类,它有一个成员,成员的类型待定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 /* 使用T代表类型,无论何时都没有比这更具体的类型来区分它。如果有多个类型参数,我们可能使用字母表中T的临近的字母,比如S。 */ class Test<T> { private T ob; /* 定义泛型成员变量,定义完类型参数后,可以在定义位置之后的方法的任意地方使用类型参数,就像使用普通的类型一样。 注意,父类定义的类型参数不能被子类继承。 */ //构造函数 public Test(T ob) { this.ob = ob; } //getter 方法 public T getOb() { return ob; } //setter 方法 public void setOb(T ob) { this.ob = ob; } public void showType() { System.out.println("T的实际类型是: " + ob.getClass().getName()); } } public class TestDemo { public static void main(String[] args) { // 定义泛型类 Test 的一个Integer版本 Test<Integer> intOb = new Test<Integer>(88); intOb.showType(); int i = intOb.getOb(); System.out.println("value= " + i); System.out.println("----------------------------------"); // 定义泛型类Test的一个String版本 Test<String> strOb = new Test<String>("Hello Gen!"); strOb.showType(); String s = strOb.getOb(); System.out.println("value= " + s); } }
泛型使用操作例子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 // Animal.java public class Animal { public Animal(){ System.out.println("我是动物"); } } // Dog.java public class Dog extends Animal { public Dog(){ System.out.println("我是狗"); } } // Test.java public class Test { /* 注意:定义带类型参数的方法,其主要目的是为了表达多个参数以及返回值之间的关系。例如本例子中T和S的继承关系, 返回值的类型和第一个类型参数的值相同。 */ public<T, S extends T> T testDemo(T t, S s){ System.out.println("我是 T 类型,我的类型是" + t.getClass().getName()); System.out.println("我是 S 类型,我的类型是" + s.getClass().getName()); return t; } public static void main(String[] args) { // TODO Auto-generated method stub Test test = new Test(); Dog d = new Dog(); Animal a0 = new Animal(); Animal a1 = test.testDemo(a0, d); System.out.println("我是对象 a1,我的类型是" + a1.getClass().getName()); } }
编译执行:
1 2 3 4 5 6 7 8 $ javac Test.java Dog.java Animal.java $ java Test 我是动物 我是狗 我是动物 我是 T 类型,我的类型是Animal 我是 S 类型,我的类型是Dog 我是对象 a1,我的类型是Animal
上面的例子中我们对类型参数赋予了具体的类型,当然我们有时候也无法确定类型参数的类型,这个时候我们便可以使用通配符。如果仅仅是想实现多态,请优先使用通配符解决
修改 Test 类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import java.util.List; import java.util.ArrayList; public class Test { // List<?> 表示接受一个元素为任意类型的列表 List。 public void testDemo(List<?> s){ for(Object obj:s){ System.out.println("我的类型是" + obj.getClass().getName()); } } public static void main(String[] args) { // TODO Auto-generated method stub Test test = new Test(); Dog a0 = new Dog(); Animal a1 = new Animal(); // 声明一个接收元素类型是 Animal 的列表 List s。 // 然后创建一个元素类型是 Animal 的 ArrayList 赋值给 s。 List<Animal> s = new ArrayList<Animal>(); // Dog a0 是 Animal 的子类,可以向上转型为 Animal 类型 s.add(a0); s.add(a1); test.testDemo(s); } }
运行结果:
1 2 3 4 5 6 7 $ javac Test.java Dog.java Animal.java $ java Test 我是动物 我是狗 我是动物 我的类型是Dog 我的类型是Animal
Collection
集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。任何集合框架都包含三大内容:对外的接口、接口的实现和对集合运算的算法。
因为集合框架中的很多类功能是相似的,所以我们用接口来规范类。Collection 接口是 Java 集合框架里的一个根接口。它也是 List、Set 和 Queue 接口的父接口。Collection 接口中定义了可用于操作 List、Set 和 Queue 的方法——增删改查。
方法
返回值
说明
add(E e)
boolean
向 collection 的尾部追加指定的元素(可选操作)
addAll(Collection<? extend E> c)
boolean
将指定 collection 中的所有元素都添加到此 collection 中(可选操作)
clear()
void
移除此 collection 中的所有元素(可选操作)
contains(Object o)
boolean
如果此 collection 包含指定的元素,则返回 true
containsAll(Collection<?> c)
boolean
如果此 collection 包含指定 collection 的所有元素,则返回 true
equals(Object o)
boolean
比较此 collection 与指定对象是否相等
hashCode()
int
返回此 collection 的哈希码值
isEmpty()
boolean
如果此 collection 不包含元素,则返回 true
iterator()
Iterator
返回在此 collection 的元素上进行迭代的迭代器
remove(Object o)
boolean
移除此 collection 中出现的首个指定元素(可选操作)
removeAll(Collection<?> c)
boolean
移除此 collection 中那些也包含在指定 collection 中的所有元素(可选操作)
retainAll(Collection<?> c)
boolean
仅保留此 collection 中那些也包含在指定 collection 的元素(可选操作)
size()
int
返回此 collection 中的元素数
toArray()
Object[]
返回包含此 collection 中所有元素的数组
toArray(T[] a)
T[]
返回包含此 collection 中所有元素的数组;返回数组的运行时类型与指定数组的运行时类型相同
List List 是一个接口,不能实例化,需要一个具体类来实现实例化。
List 集合中的对象按照一定的顺序排放,里面的内容可以重复。 List 接口实现的类有:ArrayList(实现动态数组),Vector(实现动态数组),LinkedList(实现链表),Stack(实现堆栈)。
List 在 Collection 基础上增加的方法:
方法
返回值
说明
add(int index, E element)
void
在列表的指定位置插入指定元素(可选操作)
addAll(int index, Collection<? extends E> c)
boolean
将指定 collection 中的所有元素都插入到列表中的指定位置(可选操作)
get(int index)
E
返回列表中指定位置的元素
indexOf(Object o)
int
返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1
lastIndexOf(Object o)
int
返回此列表中最后出现的指定元素的索引;如果列表不包含此元素,则返回 -1
listIterator()
ListIterator
返回此列表元素的列表迭代器(按适当顺序)
listIterator(int index)
ListIterator
返回此列表元素的列表迭代器(按适当顺序),从列表的指定位置开始
remove(int index)
E
移除列表中指定位置的元素(可选操作)
set(int index, E element)
E
用指定元素替换列表中指定位置的元素(可选操作)
subList(int fromIndex, int toIndex)
List
返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图
ArrayList ArrayList 类实现一个可增长的动态数组,位于 java.util.ArrayList。实现了 List 接口,它可以存储不同类型的对象(包括 null 在内),而数组则只能存放特定数据类型的值。
ArrayList 编程实例 学校的教务系统会对学生进行统一的管理,每一个学生都会有一个学号和学生姓名,我们在维护整个系统的时候,大多数操作是对学生的添加、插入、删除、修改等操作。
创建一个学生类 Student.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /** * 学生类 */ public class Student { public String id; public String name; public Student(String id, String name){ this.id = id; this.name = name; } @Override public String toString() { return "Student{" + "id='" + id + '\'' + ", name='" + name + '\'' + '}'; } }
创建一个 ListTest.java,其中包含了一个学生列表,通过操作学生列表来管理学生
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 import java.util.*; public class ListTest { //集合后面的<>代表泛型的意思 //泛型是规定了集合元素的类型 /** * 用于存放学生的List */ public List<Student> students; public ListTest() { this.students = new ArrayList<Student>(); } /** * 用于往students中添加学生 */ public void testAdd() { // 创建一个学生对象,并通过调用add方法,添加到学生管理List中 Student st1 = new Student("1", "张三"); students.add(st1); // 取出 List中的Student对象 索引为0 也就是第一个 Student temp = students.get(0); System.out.println("添加了学生:" + temp.id + ":" + temp.name); Student st2 = new Student("2", "李四"); //添加到list中,插入到索引为0的位置,也就是第一个 students.add(0, st2); Student temp2 = students.get(0); System.out.println("添加了学生:" + temp2.id + ":" + temp2.name); // 对象数组的形式添加 Student[] student = {new Student("3", "王五"), new Student("4", "马六")}; // Arrays类包含用来操作数组(比如排序和搜索)的各种方法,asList() 方法用来返回一个受指定数组支持的固定大小的列表 students.addAll(Arrays.asList(student)); Student temp3 = students.get(2); Student temp4 = students.get(3); System.out.println("添加了学生:" + temp3.id + ":" + temp3.name); System.out.println("添加了学生:" + temp4.id + ":" + temp4.name); Student[] student2 = {new Student("5", "周七"), new Student("6", "赵八")}; students.addAll(2, Arrays.asList(student2)); Student temp5 = students.get(2); Student temp6 = students.get(3); System.out.println("添加了学生:" + temp5.id + ":" + temp5.name); System.out.println("添加了学生:" + temp6.id + ":" + temp6.name); } /** * 取得List中的元素的方法 */ public void testGet() { int size = students.size(); for (int i = 0; i < size; i++) { Student st = students.get(i); System.out.println("学生:" + st.id + ":" + st.name); } } /** * 通过迭代器来遍历 * 迭代器的工作是遍历并选择序列中的对象,Java 中 Iterator 只能单向移动 */ public void testIterator() { // 通过集合的iterator方法,取得迭代器实例 Iterator<Student> it = students.iterator(); System.out.println("有如下学生(通过迭代器访问):"); while (it.hasNext()) { Student st = it.next(); System.out.println("学生" + st.id + ":" + st.name); } } /** * 通过for each 方法访问集合元素 * */ public void testForEach() { System.out.println("有如下学生(通过for each):"); for (Student obj : students) { Student st = obj; System.out.println("学生:" + st.id + ":" + st.name); } //使用java8 Steam将学生排序后输出 students.stream()//创建Stream //通过学生id排序 .sorted(Comparator.comparing(x -> x.id)) //输出 .forEach(System.out::println); } /** * 修改List中的元素 * */ public void testModify() { students.set(4, new Student("3", "吴酒")); } /** * 删除List中的元素 * */ public void testRemove() { Student st = students.get(4); System.out.println("我是学生:" + st.id + ":" + st.name + ",我即将被删除"); students.remove(st); System.out.println("成功删除学生!"); testForEach(); } public static void main(String[] args) { ListTest lt = new ListTest(); lt.testAdd(); lt.testGet(); lt.testIterator(); lt.testModify(); lt.testForEach(); lt.testRemove(); } }
编译运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 $ javac Student.java ListTest.java $ java ListTest 添加了学生:1:张三 添加了学生:2:李四 添加了学生:3:王五 添加了学生:4:马六 添加了学生:5:周七 添加了学生:6:赵八 学生:2:李四 学生:1:张三 学生:5:周七 学生:6:赵八 学生:3:王五 学生:4:马六 有如下学生(通过迭代器访问): 学生2:李四 学生1:张三 学生5:周七 学生6:赵八 学生3:王五 学生4:马六 有如下学生(通过for each): 学生:2:李四 学生:1:张三 学生:5:周七 学生:6:赵八 学生:3:吴酒 学生:4:马六 Student{id='1', name='张三'} Student{id='2', name='李四'} Student{id='3', name='吴酒'} Student{id='4', name='马六'} Student{id='5', name='周七'} Student{id='6', name='赵八'} 我是学生:3:吴酒,我即将被删除 成功删除学生! 有如下学生(通过for each): 学生:2:李四 学生:1:张三 学生:5:周七 学生:6:赵八 学生:4:马六 Student{id='1', name='张三'} Student{id='2', name='李四'} Student{id='4', name='马六'} Student{id='5', name='周七'} Student{id='6', name='赵八'}
在上面的代码中,用到了 Arrays 类, Arrays 包含用来操作数组(比如排序和搜索)的各种方法,asList() 方法用来返回一个受指定数组支持的固定大小的列表。
Map Map 接口也是一个非常重要的集合接口,用于存储键 / 值对。Map 中的元素都是成对出现的,键值对就像数组的索引与数组的内容的关系一样,将一个键映射到一个值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。我们可以通过键去找到相应的值。
value 可以存储任意类型的对象,我们可以根据 key 键快速查找 value。Map 中的键 / 值对以 Entry 类型的对象实例形式存在。
看一看 Map 中的方法吧:
方法
返回值
说明
clear()
void
从此映射中移除所用映射关系(可选操作)
containsKey(Object key)
boolean
如果此映射包含指定键的映射关系,则返回 true
containsValue(Object value)
boolean
如果此映射将一个或多个键映射到指定值,则返回 true
entrySet()
Set<Map.Entry<K,V>>
返回此映射中包含的映射关系的 Set 视图
equals(Object o)
boolean
比较指定的对象与此映射是否相等
get(Object key)
V
返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null
hashCode()
int
返回此映射的哈希码值
isEmpty()
boolean
如果此映射未包含键 - 值映射关系,则返回 true
keySet()
Set
返回此映射中包含的键的 Set 视图
put(K key, V value)
V
将指定的值与此映射中的指定键关联(可选操作)
putAll(Map<? extends K, ? extends V> m)
void
从指定映射中将所有映射关系复制到此映射中(可选操作)
remove(Object key)
V
如果存在一个键的映射关系,则将其从此映射中移除(可选操作)
size
int
返回此映射中的键 - 值映射关系数
values()
Collection
返回此映射中包含的值的 Collection 视图
HashMap HashMap 是基于哈希表的 Map 接口的一个重要实现类。HashMap 中的 Entry 对象是 无序 排列的,Key 值和 value 值都可以为 null,但是一个 HashMap 只能有一个 key 值为 null 的映射(key 值不可重复)。
下面我们通过代码来学习 Map 中的方法吧。同学们都有过选课经历吧,我们就用 Map 来管理课程吧。
创建一个 Course 类:
1 2 3 4 5 6 7 8 9 // Course.java public class Course { public String id; public String name; public Course(String id, String name){ this.id = id; this.name = name; } }
创建一个 MapTest 类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 // MapTest.java import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Scanner; import java.util.Set; public class MapTest { /** * 用来承装课程类型对象 */ public Map<String, Course> courses; /** * 在构造器中初始化 courses 属性 * @param args */ public MapTest() { this.courses = new HashMap<String, Course>(); } /** * 测试添加:输入课程 ID,判断是否被占用 * 若未被占用,输入课程名称,创建新课程对象 * 并且添加到 courses 中 * @param args */ public void testPut() { //创建一个 Scanner 对象,用来获取输入的课程 ID 和名称 Scanner console = new Scanner(System.in); for(int i = 0; i < 3; i++) { System.out.println("请输入课程 ID:"); String ID = console.next(); //判断该 ID 是否被占用 Course cr = courses.get(ID); if(cr == null){ //提示输入课程名称 System.out.println("请输入课程名称:"); String name = console.next(); //创建新的课程对象 Course newCourse = new Course(ID,name); //通过调用 courses 的 put 方法,添加 ID-课程映射 courses.put(ID, newCourse); System.out.println("成功添加课程:" + courses.get(ID).name); } else { System.out.println("该课程 ID 已被占用"); continue; } } } /** * 测试 Map 的 keySet 方法 * @param args */ public void testKeySet() { //通过 keySet 方法,返回 Map 中的所有键的 Set 集合 Set<String> keySet = courses.keySet(); //遍历 keySet,取得每一个键,在调用 get 方法取得每个键对应的 value for(String crID: keySet) { Course cr = courses.get(crID); if(cr != null){ System.out.println("课程:" + cr.name); } } } /** * 测试删除 Map 中的映射 * @param args */ public void testRemove() { //获取从键盘输入的待删除课程 ID 字符串 Scanner console = new Scanner(System.in); while(true){ //提示输出待删除的课程 ID System.out.println("请输入要删除的课程 ID!"); String ID = console.next(); //判断该 ID 是否对应的课程对象 Course cr = courses.get(ID); if(cr == null) { //提示输入的 ID 并不存在 System.out.println("该 ID 不存在!"); continue; } courses.remove(ID); System.out.println("成功删除课程" + cr.name); break; } } /** * 通过 entrySet 方法来遍历 Map * @param args */ public void testEntrySet() { //通过 entrySet 方法,返回 Map 中的所有键值对 Set<Entry<String,Course>> entrySet = courses.entrySet(); for(Entry<String,Course> entry: entrySet) { System.out.println("取得键:" + entry.getKey()); System.out.println("对应的值为:" + entry.getValue().name); } } /** * 利用 put 方法修改Map 中的已有映射 * @param args */ public void testModify(){ //提示输入要修改的课程 ID System.out.println("请输入要修改的课程 ID:"); //创建一个 Scanner 对象,去获取从键盘上输入的课程 ID 字符串 Scanner console = new Scanner(System.in); while(true) { //取得从键盘输入的课程 ID String crID = console.next(); //从 courses 中查找该课程 ID 对应的对象 Course course = courses.get(crID); if(course == null) { System.out.println("该 ID 不存在!请重新输入!"); continue; } //提示当前对应的课程对象的名称 System.out.println("当前该课程 ID,所对应的课程为:" + course.name); //提示输入新的课程名称,来修改已有的映射 System.out.println("请输入新的课程名称:"); String name = console.next(); Course newCourse = new Course(crID,name); courses.put(crID, newCourse); System.out.println("修改成功!"); break; } } public static void main(String[] args) { MapTest mt = new MapTest(); mt.testPut(); mt.testKeySet(); mt.testRemove(); mt.testModify(); mt.testEntrySet(); } }
Set && HashSet Set 接口也是 Collection 接口的子接口,它有一个很重要也是很常用的实现类——HashSet,Set 是元素无序并且不包含重复元素的 collection(List 可以重复),被称为集。
HashSet 由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。
接下来我们通过代码的形式来详细看一看吧!
假设现在学生们要做项目,每个项目有一个组长,由组长来组织组员,我们便来实现项目组的管理吧。
因为项目组的组长由一个老师担任 创建一个 PD 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // PD.java import java.util.HashSet; import java.util.Set; /* * 项目组长类 */ public class PD { public String id; public String name; //集合后面的<>代表泛型的意思 //泛型是规定了集合元素的类型 public Set<Student> students; public PD(String id, String name){ this.id = id; this.name = name; this.students = new HashSet<Student>(); } }
创建一个学生类 Student.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 /** * 学生类 */ // Student.java public class Student { public String id; public String name; public Student(String id, String name){ this.id = id; this.name = name; } @Override public String toString() { return "Student{" + "id='" + id + '\'' + ", name='" + name + '\'' + '}'; } }
接下来我们便创建一个 SetTest 类,用来管理项目成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 // SetTest.java import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Scanner; public class SetTest { public List<Student> students; public SetTest() { students = new ArrayList<Student>(); } /* * 用于往students中添加学生 */ public void testAdd() { //创建一个学生对象,并通过调用add方法,添加到学生管理List中 Student st1 = new Student("1", "张三"); students.add(st1); //添加到List中的类型均为Object,所以取出时还需要强转 Student st2 = new Student("2","李四"); students.add(st2); Student[] student = {new Student("3", "王五"),new Student("4", "马六")}; students.addAll(Arrays.asList(student)); Student[] student2 = {new Student("5", "周七"),new Student("6", "赵八")}; students.addAll(Arrays.asList(student2)); } /** * 通过for each 方法访问集合元素 * @param args */ public void testForEach() { System.out.println("有如下学生(通过for each):"); for(Object obj:students){ Student st = (Student)obj; System.out.println("学生:" + st.id + ":" + st.name); } } public static void main(String[] args){ SetTest st = new SetTest(); st.testAdd(); st.testForEach(); PD pd = new PD("1","张老师"); System.out.println("请:" + pd.name + "选择小组成员!"); //创建一个 Scanner 对象,用来接收从键盘输入的学生 ID Scanner console = new Scanner(System.in); for(int i = 0;i < 3; i++){ System.out.println("请输入学生 ID"); String studentID = console.next(); for(Student s:st.students){ if(s.id.equals(studentID)){ pd.students.add(s); } } } st.testForEachForSer(pd); // 关闭 Scanner 对象 console.close(); } //打印输出,老师所选的学生!Set里遍历元素只能用foreach 和 iterator //不能使用 get() 方法,因为它是无序的,不能想 List 一样查询具体索引的元素 public void testForEachForSer(PD pd){ for(Student s: pd.students) { System.out.println("选择了学生:" + s.id + ":" + s.name); } } }