集合:就像是一种容器。用于存储、获取、操作对象的容器。
1. 数组的弊端
①数组的长度不可变
②数组没有提供可以查看有效元素个数的方法
2. 集合的特点
①集合的长度是可变的
②集合可以存储任意类型的对象
③集合只能存储对象
3. 集合框架
java.util.Collection : 集合层次的根接口
|--- java.util.List: 有序的,可以重复的。
|--- ArrayList: 采用数组结构存储元素。 查询操作多时选择
|--- LinkedList: 采用链表结构存储元素。 增删操作多时选择
|--- Vector:
|--- java.util.Set: 无序的,不允许重复。
|--- HashSet : 是 Set 接口的典型实现类。
判断元素是否存在的依据是:先比较 hashCode 值,若 hashCode 存在,再通过 equals() 比较内容
若 hashCode 值不存在,则直接存储
注意:重写 hashCode 和 equals 二者需要保持一致!
|--- LinkedHashSet: 相较于 HashSet 多了链表维护元素的顺序。遍历效率高于 HashSet , 增删效率低于 HashSet
|--- TreeSet : 拥有自己排序方式
|-- 自然排序(Comparable):
①需要添加 TreeSet 集合中对象的类实现 Comparable 接口
②实现 compareTo(Object o) 方法
|-- 定制排序(Comparator)
①创建一个类实现 Comparator 接口
②实现 compare(Object o1, Object o2) 方法
③将该实现类的实例作为参数传递给 TreeSet 的构造器
4. 集合的遍历
① 增强 for 循环
// for(被遍历集合中元素的数据类型 变量名 : 被遍历的集合){ }
ArrayList al = new ArrayList();
al.add("AA");
al.add("BB");
for(Object obj : al){
System.out.println(obj);
}
② 使用 Iterator 迭代器
//1)获取当前集合的迭代器
Iterator it = al.iterator();
while(it.hasNext()){
Object obj = it.next();
System.out.println(obj);
}
/* 错误的做法:通常一个 hasNext() 配合一个 next() 使用
Iterator it = al.iterator();
while(it.hasNext()){
Object obj = it.next();
System.out.println(it.next());
} */
③ ListIterator : 列表迭代器,是List特有的迭代器(了解)
ListIterator li = al.listIterator();
while(li.hasNext()){
Object obj = li.next();
if(obj.equals("BB")){
li.set("BBBBBBBBBBb");
}
}
Map系列集合
java.util.Map : 用于存储成对对象的集合。具有 key(键)-value(值)对映射关系的集合。一个 key 对应着一个 value。 key不允许重复的。
|--- HashMap:是 Map接口的典型实现类
|--- LinkedHashMap : 相较于 HashMap 多了链表维护元素的顺序
|--- Hashtable: 是线程安全的,因此效率低
|--- Properties : 用于操作属性文件
|--- TreeMap : 根据 key 拥有自己的排序方式
|-- 自然排序(Comparable):
|-- 定制排序(Comparator):
//使用 Properties 操作属性文件
@Test
public void test1() throws FileNotFoundException, IOException{
//1. 创建 Properties 对象
Properties props = new Properties();
//2. 通过 load() 方法加载属性文件
props.load(new FileInputStream("hello.properties"));
//3. 通过 getProperty() 方法根据key获取对应的value
String userName = props.getProperty("username");
String password = props.getProperty("password");
System.out.println(userName);
System.out.println(password);
}
1. Map的常用方法:
添加、删除操作:
Object put(Object key,Object value)
Object remove(Object key)
void putAll(Map t)
void clear()
元素查询的操作:
Object get(Object key)
boolean containsKey(Object key)
boolean containsValue(Object value)
int size()
boolean isEmpty()
boolean equals(Object obj)
2. Map 的遍历:
Map map = new HashMap();
map.put("AA", 123);
map.put("BB", 456);
keySet();
//遍历Map的方式一: 获取 Map 中所有的 key
Set set = map.keySet();
values();
//遍历Map的方式二:获取 Map中所有的 value
Collection coll = map.values();
// 遍历Map的方式三: 获取Map中所有的 Entry (是Map 的一个内部类,一个Entry对应着Map中的一个key和一个value)
// entrySet法
Set entrySet = map.entrySet();
for(Object obj : entrySet){
Entry entry = (Entry)obj;
Object key = entry.getKey();
Object value = entry.getValue();
}
// Iterator 法
Iterator it = entrySet.iterator();
while(it.hasNext()){
Entry entry = (Entry)it.next();
Object key = entry.getKey();
Object value = entry.getValue();
}
为什么使用泛型:
若集合中不使用泛型,意味着集合中可以添加任意类型的对象。若需要具体到某一个类型时,需要强制类型转换可能引发 ClassCastException
泛型类和泛型方法同时具备可重用性、类型安全和效率,这是非泛型类和非泛型方法无法具备的。
泛型: 在 Java 中以 "<>" 的形式呈现,<> 中写引用数据类型
用于限制集合中存放元素的类型
1. 在集合中应用泛型
2. 自定义泛型类、接口、方法
class DAO<T>{ // T:Type E:Element K:Key V:Value
private List<T> list = new ArrayList<T>();
public void add(T t){
list.add(t);
}
public T get(int id){
return list.get(id);
}
//自定义泛型方法
// <E>类似泛型类开头的<T>,表示指定这个类或者方法的类型 E[]表示返回值类型
public <E> E[] srot(E[] e){
}
// 上述自定义泛型方法可写成如下
public T[] sort(T[] t){
}
}
3. 通配符 ?
虽然 Person 是 Student 的父类,但是 List<Person> 就不是 List<Student> 的父类
//需求:
//public void show(List<Student> list){}
//public void show1(List<Man> list){}
public void show(List<? extends Person> list){}
List<?> : 可以接收任意带泛型类型的集合
List<? extends Person> : 可以接收 Person 本类类型及 Person子类类型带泛型类型的集合
List<? super Person> : 可以接收 Person 本类类型及 Person父类类型带泛型类型的集合
通配符相关
主要包括无界通配符,通配符上界,通配符下界。
通配符
通配符不是用来定义泛型的,而是用来代表任何一种类型实参!自己一直困在一种误区中,就是以为通配符是可以在定义泛型类、泛型方法或者泛型接口时使用的。如class Generic<?>{}这种语法是错误的。
泛型中没有逻辑上的父子关系,如List<Number>并不是List<Integer>的父类。两者擦除之后都是List,所以形如
void m(List<Number> numbers) { }
void m(List<String> strings) { }
如果想让List<Number>逻辑上成为List<Integer>的父类(实际的应用场景中就是向方法传入的实际参数是方法声明的参数的子类),则可以使用泛型的通配符”?”,它表示任意一种不确定的类型。如:
/**
* 可以传入泛型为任何类型的List实现类
* 但是因为list并不知道你传入的具体会是什么类型,所以只可以使用每个元素从Object继承的方法。
*/
static void genericWildcard(List<?> list){
list.forEach(java.lang.Object::toString);
}
public static void main(String[] args) {
List<Number> list = new ArrayList<>();
genericWildcard(list);
List<Integer> list1 = new ArrayList<>();
genericWildcard(list1);
//还可以传入泛型为String类型
List<String> list2 = new ArrayList<>();
genericWildcard(list2);
}
通配符边界
有时候希望传入的类类型有一个指定的范围,从而可以进行一些允许的操作,这时候就是通配符边界登场的时候了。泛型的边界分两种:上界和下界。
对于通配符的部分,可以从三方面理解(以List为例,方便理解):
含义
查询
与泛型有关的操作
先看三个很简单的类:
public abstract class Animal {
public abstract void animalMethod();
@Override
public String toString() {
return "Animal";
}
}
public class Dog extends Animal {
@Override
public void animalMethod() {
System.out.println("DOG method");
}
@Override public String toString() {
return "Dog";
}
}
public class Fish extends Animal {
@Override
public void animalMethod() {
System.out.println("Fish method");
}
@Override
public String toString() {
return "Fish";
}
}
Animal是一个抽象类,Fish,Dog是其实现。
通配符上界
extends关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
//list中所有的元素都是Animal或者是其子类
List<? extends Animal> list;
/**
* List<? extends Animal> animals;
* 含义:表示animals集合中所有的元素都是animal或者是animal的子类。
* 查询:如方法体中所写,因为animals中所有元素都是其子类,所以可以调用其子类从animal中实现的方法。
* 增加:见方法体
*/
public static void genericUpperWildcard(List<? extends Animal> animals){
animals.forEach(Animal::animalMethod);
/*
* 下面两行都是编译错误,如果了解前面文章中提到的擦除,则很好理解。
* 编译后的方法签名参数List所用的泛型类会被转换成Animal。假如此时向List中添加的是Dog类,那么当我们使用Dog类特有的方法时
* 肯定是不存在的。所以出于类型安全问题,不允许向含有通配符下界的泛型类中添加元素——null除外,但是添加null没有任何意义。
*/
// animals.add(new Dog());
// animals.add(new Animal());
}
public static void main(String[] args) {
List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
genericUpperWildcard(dogs);
}
通配符的下界
super关键字声明了类型的下界,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至Object。
/**
* List<? super Animal> list;
* 含义:表示list中所有的元素都是Animal类或者是其父类
* 查询:因为返回的结果并不能保证是那个当初添加的Animal类或则其父类,返回的查询结果的类型只能是Object.
* 增加:可以向list中添加任何Animal实例或者Animal子类,因为list在编译之后泛型先被擦除然后转换为Animal类,
* 此时向里添加的任何类型都是Animal类,所以调用Animal中的任何非私有方法都是允许的。
*
*/
public static void genericLowerWildcard(List<? super Animal> list) {
list.add(new Animal()); //抽象类无法实例化,这里有问题
list.add(new Dog());
// Animal animal1 = list.get(0); 编译出错,
Object animal = list.get(0);
}
泛型的通配符,尤其是边界,比较难理解,但是明白其原理之后很多问题就迎刃而解了。了解泛型的擦除,以及编译之后泛型转换成的普通Java代码是什么样的很重要。