Java学习之集合
对象数组
数组既可以存储基本数据类型,也可以存储引用类型。它存储引用类型的时候的数组就叫对象数组。
实现例子
在数组中存储5个学生对象,并遍历数组
//Student类中存储姓名和年龄
public class ArrayDemo {
public static void main(String[] args) {
//创建学生数组
Student[] array = new Student[3];
//创建学生对象
Student s0 = new Student("孙悟空",500);
Student s1 = new Student("猪八戒",200);
Student s2 = new Student("沙悟净",100);
//装入对象数组
array[0] = s0;
array[1] = s1;
array[2] = s2;
//遍历对象那个数组(已实现toString)
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
}
}
public static List asList(T… a):把数组转成集合
注意事项:虽然可以把数组转成集合,但是集合的长度不能改变。
Collection(集合)
介绍
我们学习的是面向对象语言,而面向对象语言对事物的描述是通过对象体现的,为了方便对多个对象进行操作,我们就必须把这多个对象进行存储。而要想存储多个对象,就不能是一个基本的变量,而应该是一个容器类型的变量。对象数组又不能适应变化的需求,因为数组的长度是固定的,这个时候,为了适应变化的需求,Java就提供了集合类供我们使用。Collection是个
数组和集合的区别?
A:长度区别
数组的长度固定
集合长度可变
B:内容不同
数组存储的是同一种类型的元素
而集合可以存储不同类型的元素
C:元素的数据类型问题
数组可以存储基本数据类型,也可以存储引用数据类型
集合只能存储引用类型
Collection:是集合的顶层接口,它的子体系有重复的,有唯一的,有有序的,有无序的。(后面会慢慢的讲解)
集合的功能
- 添加功能
boolean add(Object obj):添加一个元素
boolean addAll(Collection c):添加一个集合的元素
- 删除功能
void clear():移除所有元素
boolean remove(Object o):移除一个元素
boolean removeAll(Collection c):移除一个集合的元素(是一个还是所有)
- 判断功能
boolean contains(Object o):判断集合中是否包含指定的元素
boolean containsAll(Collection c):判断集合中是否包含指定的集合元素(是一个还是所有)
boolean isEmpty():判断集合是否为空
- 获取功能
Iterator iterator()(重点)
- 长度功能
int size():元素的个数
- 交集功能
boolean retainAll(Collection c): 假设有两个集合A,B。A对B做交集,最终的结果保存在A中,B不变。返回值表示的是A是否发生过改变。
- 把集合转换为数组
Object[] toArray()
集合的体系结构图
由于需求不同,Java就提供了不同的集合类。这多个集合类的数据结构不同,但是它们都是要提供存储和遍历功能的,我们把它们的共性不断的向上提取,最终就形成了集合的继承体系结构图。
Collection
|--List 有序,可重复
|--ArrayList
底层数据结构是数组,查询快,增删慢。
线程不安全,效率高
|--Vector
底层数据结构是数组,查询快,增删慢。
线程安全,效率低
|--LinkedList
底层数据结构是链表,查询慢,增删快。
线程不安全,效率高
|--Set 无序,唯一
|--HashSet
底层数据结构是哈希表。
如何保证元素唯一性的呢?
依赖两个方法:hashCode()和equals()
开发中自动生成这两个方法即可
|--LinkedHashSet
底层数据结构是链表和哈希表
由链表保证元素有序
由哈希表保证元素唯一
|--TreeSet
底层数据结构是红黑树。
如何保证元素排序的呢?
自然排序
比较器排序
如何保证元素唯一性的呢?
根据比较的返回值是否是0来决定
集合遍历
转数组遍历
把集合转为数组,可以实现遍历
public class CollectionDemo3 {
public static void main(String[] args) {
// 创建集合对象
Collection c = new ArrayList();// 添加元素
c.add("hello"); // Object obj = "hello"; 向上转型
c.add("world");
c.add("java");
// 遍历
// Object[] toArray():把集合转成数组,可以实现集合的遍历
Object[] objs = c.toArray();
for (int x = 0; x < objs.length; x++) {
String s = (String) objs[x];
System.out.println(s + "---" + s.length());
}
}
}
迭代器遍历
Iterator iterator():迭代器,集合的专用遍历方式
Object next():获取元素,并移动到下一个位置。
boolean hasNext():如果仍有元素可以迭代,则返回 true。
//遍历字符串
public class IteratorDemo {
public static void main(String[] args) {
// 创建集合对象
Collection c = new ArrayList();
// 创建并添加元素
// String s = "hello";
// c.add(s);
c.add("hello");
c.add("world");
c.add("java");
// Iterator iterator():迭代器,集合的专用遍历方式
Iterator it = c.iterator(); // 实际返回的肯定是子类对象,这里是多态
while (it.hasNext()) {
// System.out.println(it.next());
String s = (String) it.next();
System.out.println(s);
}
}
}
//遍历对象类
public class CollectionTest2 {
public static void main(String[] args) {
// 创建集合对象
Collection c = new ArrayList();
// 创建学生对象
Student s1 = new Student("貂蝉", 25);
Student s2 = new Student("小乔", 16);
Student s3 = new Student("黄月英", 20);
Student s4 = new Student();
s4.setName("大乔");
s4.setAge(26);
// 把学生对象添加到集合对象中
c.add(s1);
c.add(s2);
c.add(s3);
c.add(s4);
c.add(new Student("孙尚香", 18)); // 匿名对象
// 遍历集合
Iterator it = c.iterator();
while (it.hasNext()) {
Student s = (Student) it.next();
System.out.println(s.getName() + "---" + s.getAge());
}
}
}
LIst
List是Collection的子接口
特点:有序(存储顺序和取出顺序一致),可重复。
功能
- 添加功能
void add(int index,Object element):在指定位置添加元素
- 获取功能
Object get(int index):获取指定位置的元素
- 列表迭代器
ListIterator listIterator():List集合特有的迭代器
特有方法
Object previous():获取上一个元素
boolean hasPrevious():判断是否有元素
- 删除功能
Object remove(int index):根据索引删除元素,返回被删除的元素
- 修改功能
Object set(int index,Object element):根据索引修改元素,返回被修饰的元素
List遍历
size()和get()结合使用
for (int x = 0; x < list.size(); x++) {
// System.out.println(list.get(x));
String s = (String) list.get(x);
System.out.println(s);
}
迭代器遍历
同Collection
Ventoy
特有功能
-
添加
public void addElement(E obj) – add() -
获取
public E elementAt(int index) – get()
public Enumeration elements() – iterator()
LinkedList
特有方法
- 添加
addFirst()
addLast() - 删除
removeFirst()
removeLast() - 获取
getFirst()
getLast()
泛型
介绍
是一种把明确类型的工作推迟到创建对象或者调用方法的时候才去明确的特殊的类型。一般在集合内使用。
好处:
A:把运行时期的问题提前到了编译期间
B:避免了强制类型转换
C:优化了程序设计,解决了黄色警告线
泛型类
//泛型类
public class ObjectTool<T> {
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
//泛型类实现
public class ObjectToolDemo {
public static void main(String[] args) {
ObjectTool<String> o = new ObjectTool<String>();
o.setObj("haha");
System.out.println(o.getObj());
ObjectTool<Integer> i = new ObjectTool<Integer>();
i.setObj(new Integer("336"));
Integer i2 = (Integer)i.getObj();
System.out.println(i2);
}
}
泛型方法
把泛型定义在方法上
public class ObjectTool {
//泛型方法
public <T> void show(T t) {
System.out.println(t);
}
}
//泛型方法使用
public class ObjectToolDemo {
public static void main(String[] args) {
// 定义泛型方法后
ObjectTool ot = new ObjectTool();
ot.show("hello");
ot.show(100);
ot.show(true);
}
}
泛型接口
把泛型定义在接口上
//泛型接口
public interface Inter<T> {
public abstract void show(T t);
}
public class InterImpl<T> implements Inter<T> {
//实现类在实现接口的时候
//第一种情况:已经知道该是什么类型的了
//public class InterImpl implements Inter<String> {
//
// @Override
// public void show(String t) {
// System.out.println(t);
// }
// }
@Override
public void show(T t) {
System.out.println(t);
}
}
public class InterDemo {
public static void main(String[] args) {
// 第一种情况的测试
// Inter<String> i = new InterImpl();
// i.show("hello");
// // 第二种情况的测试
Inter<String> i = new InterImpl<String>();
i.show("hello");
Inter<Integer> ii = new InterImpl<Integer>();
ii.show(100);
}
}
泛型高级
通配符
?:任意类型,如果没有明确,那么就是Object以及任意的Java类了
? extends E:向下限定,E及其子类
? super E:向上限定,E极其父类
public class GenericDemo {
public static void main(String[] args) {
// 泛型如果明确的写的时候,前后必须一致
Collection<Object> c1 = new ArrayList<Object>();
//以下三个不行
// Collection<Object> c2 = new ArrayList<Animal>();
// Collection<Object> c3 = new ArrayList<Dog>();
// Collection<Object> c4 = new ArrayList<Cat>();
// ?表示任意的类型都是可以的
Collection<?> c5 = new ArrayList<Object>();
Collection<?> c6 = new ArrayList<Animal>();
Collection<?> c7 = new ArrayList<Dog>();
Collection<?> c8 = new ArrayList<Cat>();
// ? extends E:向下限定,E及其子类
// Collection<? extends Animal> c9 = new ArrayList<Object>(); 不行
Collection<? extends Animal> c10 = new ArrayList<Animal>();
Collection<? extends Animal> c11 = new ArrayList<Dog>();
Collection<? extends Animal> c12 = new ArrayList<Cat>();
// ? super E:向上限定,E极其父类
Collection<? super Animal> c13 = new ArrayList<Object>();
Collection<? super Animal> c14 = new ArrayList<Animal>();
// Collection<? super Animal> c15 = new ArrayList<Dog>();不行
// Collection<? super Animal> c16 = new ArrayList<Cat>();不行
}
}
class Animal {
}
class Dog extends Animal {
}
class Cat extends Animal {
}
HashSet
它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。底层数据结构是哈希表(是一个元素为链表的数组)
注意:
虽然Set集合的元素无序,但是,作为集合来说,它肯定有它自己的存储顺序,
而你的顺序恰好和它的存储顺序一致,这代表不了有序,你可以多存储一些数据,就能看到效果。
public class SetDemo {
public static void main(String[] args) {
Set<String> s = new HashSet<String>();
s.add("hello");
s.add("world");
s.add("hello");
s.add("java");
s.add("haha");
for(String s1 :s){
System.out.println(s1);
}
}
}
输出
hello
haha
java
world
唯一性
哈希表底层依赖两个方法:hashCode()和equals()
执行顺序:
首先比较哈希值是否相同
相同:继续执行equals()方法
返回true:元素重复了,不添加
返回false:直接把元素添加到集合
不同:就直接把元素添加到集合
如何保证元素唯一性的呢?
由hashCode()和equals()保证的
开发的时候,代码非常的简单,自动生成即可。
HashSet存储字符串并遍历
HashSet存储自定义对象并遍历(对象的成员变量值相同即为同一个元素)
如果你认为对象的成员变量值相同即为同一个对象的话,你就应该重写这两个方法。如何重写呢?不同担心,自动生成即可。
TreeSet
底层数据结构是红黑树(是一个自平衡的二叉树)
保证元素的排序方式
自然排序(元素具备比较性)
让元素所属的类实现Comparable接口(List也可以使用)
/*
* 如果一个类的元素要想能够进行自然排序,就必须实现自然排序接口
*/
public class Student implements Comparable<Student> {
private String name;
private int age;
@Override
public int compareTo(Student s) {
// return 0;
// return 1;
// return -1;
// 这里返回什么,其实应该根据我的排序规则来做
// 按照年龄排序,主要条件
int num = this.age - s.age;
// 次要条件
// 年龄相同的时候,还得去看姓名是否也相同
// 如果年龄和姓名都相同,才是同一个元素
int num2 = num == 0 ? this.name.compareTo(s.name) : num;
return num2;
}
}
比较器排序(集合具备比较性)
让集合构造方法接收Comparator的实现类对象
public class MyComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
// int num = this.name.length() - s.name.length();
// this -- s1
// s -- s2
// 姓名长度
int num = s1.getName().length() - s2.getName().length();
// 姓名内容
int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
// 年龄
int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2;
return num3;
}
}
public class TreeSetDemo {
public static void main(String[] args) {
// 创建集合对象
// TreeSet<Student> ts = new TreeSet<Student>(); //自然排序
// public TreeSet(Comparator comparator) //比较器排序
// TreeSet<Student> ts = new TreeSet<Student>(new MyComparator());
// 如果一个方法的参数是接口,那么真正要的是接口的实现类的对象
// 而匿名内部类就可以实现这个东西
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
// 姓名长度
int num = s1.getName().length() - s2.getName().length();
// 姓名内容
int num2 = num == 0 ? s1.getName().compareTo(s2.getName())
: num;
// 年龄
int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2;
return num3;
}
});
// 创建元素
Student s1 = new Student("linqingxia", 27);
Student s2 = new Student("zhangguorong", 29);
Student s3 = new Student("wanglihong", 23);
Student s4 = new Student("linqingxia", 27);
Student s5 = new Student("liushishi", 22);
Student s6 = new Student("wuqilong", 40);
Student s7 = new Student("fengqingy", 22);
Student s8 = new Student("linqingxia", 29);
// 添加元素
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
ts.add(s7);
ts.add(s8);
// 遍历
for (Student s : ts) {
System.out.println(s.getName() + "---" + s.getAge());
}
}
}
Collections工具类
是针对集合进行操作的工具类
和Collection区别
Collection 是单列集合的顶层接口,有两个子接口List和Set
Collections 是针对集合进行操作的工具类,可以对集合进行排序和查找等
常用方法
- public static void sort(List list):排序 默认情况下是自然顺序。
- public static int binarySearch(List<?> list,T key):二分查找
- public static T max(Collection<?> coll):最大值
- public static void reverse(List<?> list):反转
- public static void shuffle(List<?> list):随机置换
Map
将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。
和Collection区别
Map 存储的是键值对形式的元素,键唯一,值可以重复。夫妻对
Collection 存储的是单独出现的元素,子接口Set元素唯一,子接口List元素可重复。光棍
常用方法
-
添加功能
V put(K key,V value):添加元素。
如果键是第一次存储,就直接存储元素,返回null
如果键不是第一次存在,就用值把以前的值替换掉,返回以前的值
-
删除功能
void clear():移除所有的键值对元素
V remove(Object key):根据键删除键值对元素,并把值返回
-
判断功能
boolean containsKey(Object key):判断集合是否包含指定的键
boolean containsValue(Object value):判断集合是否包含指定的值
boolean isEmpty():判断集合是否为空
-
获取功能
Set<Map.Entry<K,V>> entrySet():同时获取键和值
V get(Object key):根据键获取值
Set keySet():获取集合中所有键的集合
Collection values():获取集合中所有值的集合
-
长度功能
int size():返回集合中的键值对的对数
HashMap
Hashtable和HashMap的区别?
- Hashtable:线程安全,效率低。不允许null键和null值
- HashMap:线程不安全,效率高。允许null键和null值
唯一性
同HashSet一样需要实现hashcode()和equals()方法
遍历
-
键找值
获取所有键的集合,遍历键的集合,得到每一个键,根据键到集合中去找值 -
:键值对对象找键和值
获取所有的键值对对象的集合,遍历键值对对象的集合,获取每一个键值对对象,根据键值对对象去获取键和值
Map<String,String> hm = new HashMap<String,String>();
hm.put("it002","hello");
hm.put("it003","world");
hm.put("it001","java");
//方式1 键找值
Set<String> set = hm.keySet();
for(String key : set) {
String value = hm.get(key);
System.out.println(key+"---"+value);
}
//方式2 键值对对象找键和值
Set<Map.Entry<String,String>> set2 = hm.entrySet();
for(Map.Entry<String,String> me : set2) {
String key = me.getKey();
String value = me.getValue();
System.out.println(key+"---"+value);
}
TreeMap
自动排序
和TreeSet一样那两种比较方法
嵌套遍历
- HashMap嵌套HashMap
- HashMap嵌套ArrayList
- ArrayList嵌套HashMap
- 多层嵌套
JdK5新特性
学到集合时,需要用到JDK5新特性,列举一下
增强for遍历
在明确泛型类型之后,可以用增强for
格式:
for(元素数据类型 变量 : 数组或者Collection集合) {
使用变量即可,该变量就是元素
}
好处:简化了数组和集合的遍历。
弊端: 增强for的目标不能为null。对增强for的目标先进行不为null的判断,然后在使用。
// 增强for
for (String s : array) {
System.out.println(s);
}
if (list != null) {
for (String s : list) {
System.out.println(s);
}
}
静态导入
格式:import static 包名….类名.方法名;
可以直接导入到方法的级别
静态导入的注意事项:
A:方法必须是静态的
B:如果有多个同名的静态方法,容易不知道使用谁?这个时候要使用,必须加前缀。由此可见,意义不大,所以一般不用,但是要能看懂。
import static java.lang.Math.abs;
import static java.lang.Math.pow;
import static java.lang.Math.max;
//错误
//import static java.util.ArrayList.add;
public class StaticImportDemo {
public static void main(String[] args) {
// System.out.println(java.lang.Math.abs(-100));
// System.out.println(java.lang.Math.pow(2, 3));
// System.out.println(java.lang.Math.max(20, 30));
// 太复杂,我们就引入到import
// System.out.println(Math.abs(-100));
// System.out.println(Math.pow(2, 3));
// System.out.println(Math.max(20, 30));
// 太复杂,有更简单
// System.out.println(abs(-100));
System.out.println(java.lang.Math.abs(-100));
System.out.println(pow(2, 3));
System.out.println(max(20, 30));
}
public static void abs(String s){
System.out.println(s);
}
}
可变参数
定义方法的时候不知道该定义多少个参数
格式:
修饰符 返回值类型 方法名(数据类型… 变量名){
}
注意:
这里的变量其实是一个数组
如果一个方法有可变参数,并且有多个参数,那么,可变参数肯定是最后一个
public class ArgsDemo {
public static void main(String[] args) {
// 2个数据求和
int a = 10;
int b = 20;
int result = sum(a, b);
System.out.println("result:" + result);
// 3个数据的求和
int c = 30;
result = sum(a, b, c);
System.out.println("result:" + result);
// 4个数据的求和
int d = 30;
result = sum(a, b, c, d);
System.out.println("result:" + result);
// 需求:我要写一个求和的功能,到底是几个数据求和呢,我不太清楚,但是我知道在调用的时候我肯定就知道了
// 为了解决这个问题,Java就提供了一个东西:可变参数
result = sum(a, b, c, d, 40);
System.out.println("result:" + result);
result = sum(a, b, c, d, 40, 50);
System.out.println("result:" + result);
}
public static int sum(int... a) {
// System.out.println(a);
//return 0;
int s = 0;
for(int x : a){
s +=x;
}
return s;
}