------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
集合类
面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就对对象进行存储,集合就是存储对象最常用的一种方式。
对象用于封装特有数据,对象多了需要存储,如果对象个数不确定,就用集合容器存储。
集合容器因为内部的数据结构不同,就形成多种具体容器,这些容器不断向上抽取,就形成了集合框架。
数组和集合区别:
数组虽然也可以存储对象,但长度是固定的;数组中可以存储基本数据类型
集合长度是可变的;集合只能存储对象
集合中不能存放基本数据类型
集合中存放的都是对象的引用,实际内容都在堆上面或者方法区里面,但是基本数据类型是在栈上分配空间的。随时就被收回的,是不确定的,集合只存储确定的元素。但是通过自动包装类就可以把基本类型转为对象类型,存放引用就解决了这个问题。
集合类的特点:
集合只用于存储对象,集合长度是可变的,集合可以存储不同类型的对象。
集合框架的构成及分类
Collection接口有两个子接口:
|--List:有序(存入和取出的顺序一致),元素都有索引(角标),元素可以重复。
|--Set:不能存放重复元素,元素存放时无序的
Collection接口中常用操作:
1、添加
boolean add(Object obj):向集合中添加对象
boolean addAll(Collection coll):
2、删除
boolean remove(object obj):
boolean removeAll(Collection coll);
void clear();
3、判断
boolean contains(object obj):
boolean containsAll(Colllection coll);
boolean isEmpty():判断集合中是否有元素
4、获取
int size():
Iterator iterator():取出元素的方式:迭代器
5、其他
boolean retainAll(Collection coll);取交集。
Object[] toArray():将集合转成数组。
注意:add方法的参数类型是object,以便于接受任意类型对象
List
List:元素是有序的,元素可以重复,因为该集合体系有索引。
ArrayList:底层的数据结构使用的是数组结构。特点:查询速度很快,但是增删稍慢,线程不同步。
LinkedList:底层使用的链表数据结构。特点:增删速度很快,查询稍慢,线程不同步。
Vector:底层是数组数据结构。线程同步。被ArrayList替代了,因为效率低。
List集合特有方法
1、增加
void add(index,element);在指定位置添加元素
boolean addAll(index,Collection);将collection中所有元素插入到列表指定位置
2、删除
remove(index);按照角标位置移除元素
3、修改
set(index,element);设置index角标位置上的元素
4、查找
get(index):返回列表指定位置元素
subList(from,to);返回列表中指定位置的元素(包左不包右)
int indexOf(obj):获取指定元素的第一次出现位置,没有返回-1
ListIterator listIterator();List集合特有的迭代器ListIterator
注意:在迭代时,不可以通过集合对象的方法操作集合中的元素。
因为会发生ConcurrentModificationException异常;所以,在迭代器时,只能用迭代器的方法操作元素,可是Iterator方法是有限的,只能对元素进行判断,取出,删除的操作,如果想要其他的操作如添加,修改等,就需要使用其子接口ListIterator。该接口只能通过List集合的listIterator方法获取。
LinkedList通过迭代器获取集合所有元素
Iterator it =al.iterator();
while(it.hasNext())
{
sop("next:"+it.next());
}
需求:去除ArrayList集合中的重复元素
public static ArrayList singleElement(ArrayList al)
{
ArrayList newAl =new ArrayList(); //定义一个临时容器
Iterator it =al.iterator();
while(it.hasNext())
{
Object obj =it.next();
if(!newAl.contains(obj))//用contains判断是否有重复元素,没有添加到新容器中;
newAl.add(obj);
}
return newAl;
}
底层使用的链表数据结构。特点:增删速度很快,查询稍慢。线程不同步。
LinkedList特有方法:
1、添加
addFirst();在头部添加
addLast();在尾部添加
2、获取
获取元素,但不删除元素。如果集合中没有元素,会出现NoSuchElementException
getFirst();获取第一个元素
getLast();获取最后一个元素
3、删除
获取元素,但是元素被删除。如果集合中没有元素,会出现NoSuchElementException
removeFirst();
removeLast();
JDK1.6新特性:
1、增
offerFirst();
offerLast();
2、获取
获取元素,但是不删除。如果集合中没有元素,会返回null。
peekFirst();
peekLast();
3、删除
获取元素并删除。如果集合中没有元素,会返回null。
pollFirst();
pollLast();
需求:使用LinkedList模拟一个堆栈或者队列数据结构。
堆栈:先进后出 如同一个杯子。
队列:先进先出 First in Firstout FIFO 如同一个水管。
import java.util.*; class DuiLie { private LinkedList link; DuiLie() { link = new LinkedList(); } public void myAdd(Object obj) { link.addFirst(obj); } // 先进后出 public Object myGet_1() { return link.removeFirst();// 即从第一个元素开始取 } // 先进先出 public Object myGet_2() { return link.removeLast();//从最后一个元素开始取 } public boolean isNull() { return link.isEmpty(); } } public class LinkedListTest1 { public static void main(String[] args) { DuiLie dl = new DuiLie(); dl.myAdd("java01"); dl.myAdd("java02"); dl.myAdd("java03"); dl.myAdd("java04"); while (!dl.isNull()) { // 栈—先进后出 System.out.println(dl.myGet_1()); } while (!dl.isNull()) { // 队列—先进先出 System.out.println(dl.myGet_2()); } } }
Set
元素是无序(存入和取出的顺序不一定一致),元素不可以重复。Set集合的功能和Collection是一致的。
HashSet:底层数据结构是哈希表,是线程不安全的,不同步。
HashSet保证元素唯一性原理:
是通过元素的两个方法,hashCode和equals来完成。如果元素的HashCode值相同,才会判断equals是否为true。如果元素的hashcode值不同,不会调用equals。
注意:对于判断元素是否存在以及删除等操作,依赖的方法是元素的hashcode和equals方法
TreeSet:底层数据结构是二叉树(红黑树结构)
保证元素唯一性的依据:compareTo方法return 0。通过compareTo方法的返回值,是正整数、负整数或零,则两个对象较大、较小或相同,相等时则不会存入。
可对Set集合中的元素进行排序
因为TreeSet类实现了Comparable接口,该接口强制让增加到集合中的对象进行了比较,需要复写compareTo方法,才能让对象按指定需求(如人的年龄大小比较等)进行排序,并加入集合。
java中的很多类都具备比较性,其实就是实现了Comparable接口。
注意:排序时,当主要条件相同时,按次要条件排序。
二叉树排序原理:
图中是按年龄以22为树根进行排序的,当年龄相同的时候比较姓名字母顺序,即默认的字母顺序,打印的时候从19开始遍历二叉树
当compareTo返回值为1的时候,即在复写compareTo()方法时return 1,则表示此二叉树一直在向右边发散,这是打印的即原来输入的对象的顺序;如果compareTo返回-1,则打印原对象的倒序
TreeSet排序两种方式
第一种方式:(自然排序)
让元素自身具备比较性。元素需要实现Comparable接口,覆盖compareTo方法。也种方式也成为元素的自然顺序,或者叫做默认顺序。
第二种方式:(比较器)
当元素自身不具备比较性,或者具备的比较性不是所需要的,这时就需要让集合自身具备比较性。定义比较器,将比较器对象作为参数传递给TreeSet集合的构造函数,在集合初始化时,就有了比较方式。
比较器构造方法:
定义一个类,实现Comparator接口,覆盖compare方法。
注意:当两种排序都存在时,以比较器为主。
需求:往hashSet集合中存入自定对象,姓名和年龄相同为同一个人,去掉重复元素
import java.util.*; public class HashSetTest { public static void sop(Object obj) { System.out.println(obj); } public static void main(String[] args) { HashSet hs = new HashSet(); hs.add(new Person("a1", 11)); hs.add(new Person("a2", 12)); hs.add(new Person("a3", 13)); hs.add(new Person("a2", 12)); hs.add(new Person("a4", 14)); Iterator it = hs.iterator(); while (it.hasNext()) { Person p = (Person) it.next(); sop(p.getName() + "::" + p.getAge()); } } } class Person { private String name; private int age; Person(String name, int age) { this.name = name; this.age = age; } public int hashCode() { System.out.println(this.name + "....hashCode"); /* * return 20;也行,但循环次数太多 * 按条件设置hashcode值,字符串也有自己的哈希值,字符串对象有自己的hashcode方法,这样可以减少比较的次数 */ return name.hashCode() + age * 37; // 乘以37是为了避免对象的名字跟hashcode组合碰巧和相等 } public boolean equals(Object obj) { if (!(obj instanceof Person)) return false; Person p = (Person) obj; System.out.println(this.name + "...equals.." + p.name); return this.name.equals(p.name) && this.age == p.age; } public String getName() { return name; } public int getAge() { return age; } }
需求:往TreeSet集合中存储自定义对象学生
import java.util.*; //方法一实例:按照年龄排序,实现Comparable接口 class Student implements Comparable// 该接口强制让学生具备比较性。 { private String name; private int age; Student(String name, int age) { this.name = name; this.age = age; } // 复写compareTo以便TreeSet集合调用 public int compareTo(Object obj) { // return 0;//只打印一个数据 if (!(obj instanceof Student)) throw new RuntimeException("不是学生对象"); Student s = (Student) obj; System.out.println(this.name + "....compareto....." + s.name); if (this.age > s.age) return 1; if (this.age == s.age) { /* * 记住,排序时,当主要条件相同时,一定判断一下次要条件。 判断两人姓名是否相同,相同则为同一个人,可以删掉一个 * 不同则为两个人,继续按照名字字母顺序排序 */ return this.name.compareTo(s.name); } return -1; } public String getName() { return name; } public int getAge() { return age; } } class TreeSetDemo2 { public static void main(String[] args) { TreeSet ts = new TreeSet();// 年龄排序 // TreeSet ts = new TreeSet(new MyCompare());//姓名排序 ts.add(new Student("lisi02", 22)); ts.add(new Student("lisi02", 21)); ts.add(new Student("lisi007", 20)); ts.add(new Student("lisi09", 19)); ts.add(new Student("lisi06", 18)); ts.add(new Student("lisi06", 18)); ts.add(new Student("lisi007", 29)); ts.add(new Student("lisi007", 20)); ts.add(new Student("lisi01", 40)); Iterator it = ts.iterator(); while (it.hasNext()) { Student stu = (Student) it.next(); System.out.println(stu.getName() + "..." + stu.getAge()); } } } // 方法二实例,按照名字排序,实现Comparator接口 class MyCompare implements Comparator { public int compare(Object o1, Object o2) { Student s1 = (Student) o1; Student s2 = (Student) o2; int num = s1.getName().compareTo(s2.getName()); // 两个字符串直接调用comoareTo方法进行比较,当返回值不为0是即按照默认字母数字进行排序 if (num == 0) { // 姓名相同时则按照年龄顺序排序 return new Integer(s1.getAge()).compareTo(new Integer(s2.getAge())); /* * 等价于 if(s1.getAge()>s2.getAge()) return 1; * if(s1.getAge()==s2.getAge()) return 0; return -1; */ } return num; } }
练习一、按照字符串长度排序。
分析:字符串本身具备比较性。但是它的比较方式不是所需要的。
这时就只能使用比较器。
import java.util.*; class TreeSetTest { public static void main(String[] args) { TreeSet ts = new TreeSet(new StrLenComparator()); ts.add("abcd"); ts.add("cc"); ts.add("cba"); ts.add("aaa"); ts.add("z"); ts.add("hahaha"); Iterator it = ts.iterator(); while (it.hasNext()) { System.out.println(it.next()); } } } class StrLenComparator implements Comparator { public int compare(Object o1, Object o2) { String s1 = (String) o1; String s2 = (String) o2; /* * if(s1.length()>s2.length()) return 1; if(s1.length()==s2.length()) * return 0; */ int num = new Integer(s1.length()).compareTo(new Integer(s2.length())); if (num == 0)// 当字符串长度相同时,比较字符串中首字母开始不相同字母顺序 return s1.compareTo(s2); return num; } }
练习二、将字符串中的数值进行排序。使用TreeSet完成。"90 -7 0 18 2 454"
思路
1.将字符串切割。
2.可以将这些对象存入TreeSet集合。因为TreeSet自身具备排序功能。
import java.util.*; class TreeSetTest2 { public static void main(String[] args) { ArrayList al = new ArrayList(); String str = "90 -7 0 18 2 45 4"; String[] arr = str.split(" "); TreeSet ts = new TreeSet(); for (int x = 0; x < arr.length; x++) { // ts.add(new Integer(arr[x])); ts.add(Integer.parseInt(arr[x]));// } System.out.println(ts); } }
泛型
jdk1.5出现的安全机制, 用于解决安全问题,是一个类型安全机制.
好处:
1、将运行时期的问题ClassCastException转到了编译时期。
2、避免了强制转换的麻烦。
格式:
1、定义ArrayList容器用来存放String类型的元素
ArrayList<String> al = new ArrayList<String>();
2、 iterator()方法将数据存放到Iterator迭代器中,因此迭代器也要明确数据类型
Iterator<String> it = al.iterator();
<>:什么时候用?
当操作的引用数据类型不确定的时候,就使用<>。将要操作的引用数据类型传入即可.其实<>就是一个用于接收具体引用数据类型的参数范围。
在程序中,只要用到了带有<>的类或者接口,就要明确传入的具体引用数据类型 。
1、泛型类
2、泛型方法当类中要操作的引用数据类型不确定的时候,就定义泛型来完成扩展。
泛型类定义的泛型,在整个类中有效。如果被方法使用,那么泛型类的对象明确要操作的具体类型后,所有要操作的类型就已经固定了。为了让不同方法可以操作不同类型,而且类型还不确定。那么可以将泛型定义在方法上。
特殊之处:
静态方法不可以访问类上定义的泛型。
如果静态方法操作的引用数据类型不确定,可以将泛型定义在方法上。
当在Demo<T>中有静态方法时,编译失败,无法从静态上下文中引用非静态类T, 原因静态存在时还没有建立对象,即静态方法不可以访问类上定义的泛型,如果静态方法操作的引用数据类型不确定,可以将泛型定义在方法上class Demo<T> // 泛型类 { public void show(T t)// 该方法返回值元素必须和泛型类定义的元素类型一致 { System.out.println("show:" + t); } // 泛型方法 ,泛型定义在方法上时放到返回值类型前面,修饰符后面 public <Q> void print(Q q) { System.out.println("print:" + q); } public static <W> void method(W t) { System.out.println("method:" + t); } } class GenericDemo { public static void main(String[] args) { Demo<String> d = new Demo<String>(); d.show("haha"); // d.show(4);//编译失败,show类型随着对象变化,对象为String类 d.print(5);// 运行通过,泛型方法 d.print("hehe"); Demo.method("hahahahha"); } }
3、泛型定义在接口上:
interface Inter<T> { void show(T t); } class InterImpl<T> implements Inter<T> { public void show(T t) { System.out.println("show :" + t); } }
泛型通配符:?
? 通配符。也可以理解为占位符。
泛型的限定:
?extends E: 可以接收E类型或者E的子类型。上限。
一般存储对象的时候用。比如 添加元素 addAll.
?super E: 可以接收E类型或者E的父类型。下限
一般取出对象的时候用。比如比较器。
上限应用:
public static void printColl(ArrayList<Person> al) { Iterator<Person> it = al.iterator(); while (it.hasNext()) { System.out.println(it.next().getName()); } } ArrayList<Person> al = new ArrayList<Person>(); al.add(new Person("abc1")); ArrayList<Student> al1 = new ArrayList<Student>(); al1.add(new Student("abc--1"));
printColl(al1);//编译失败
等价于:ArrayList<Person>al=new ArrayList<Student>();//安全问题
ArrayList< Student> al=new ArrayList< Person>();//安全问题
结论:左右两边要一致
要解决上述问题需要用到?(通配符)
public static void printColl(ArrayList<? extends Person>al)//上限
Iterator<? extends Person> it = al.iterator();
printColl(al1);// abc—1
下限应用
需求: 在Person中定义比较姓名的类,在学生,工人上调用
class Comp implements Comparator<Person>
{
public int compare(Person p1,Person p2)
{
return p2.getName().compareTo(p1.getName());
}
}
TreeSet<Student>ts = new TreeSet<Student>(new Comp());
ts.add(new Student("abc03"));
ts.add(newStudent("abc02"));
TreeSet<Worker>ts1 = new TreeSet<Worker>(new Comp());
ts1.add(newWorker("wabc--03"));
ts1.add(newWorker("wabc--02"));
泛型技术是给编译器使用的技术,用于编译时期。确保了类型的安全。运行时,会将泛型去掉,生成的class文件中是不带泛型的,这个称为泛型的擦除。为什么擦除呢?因为为了兼容运行的类加载器。
泛型的补偿:在运行时,通过获取元素的类型进行转换动作。不用使用者在强制转换了。
Map
Map<K,V>集合是一个接口,和List集合及Set集合不同的是,它是双列集合,并且可以给对象加上名字,即键(Key)
特点:
1、该集合存储键值对,一对一对往里存
2、要保证键的唯一性
map集合的框架
Hashtable:底层是哈希表数据结构,不可以存入null键和null值。该集合是线程同步的。JDK1.0,效率低。
HashMap:底层是哈希表数据结构。允许使用null和键null值,该集合是不同步的。JDK1.2,效率高。
TreeMap:底层是二叉树数据结构。线程不同步。可以用于给Map集合中的键进行排序。
Map和Set很像。其实Set底层就是使用了Map集合。
Map常用方法
1、添加
put(K key,V value);添加元素;如果在添加时,出现相同的键,后添加的值会覆盖原有键对应值,并put方法会返回被覆盖的值。
void putAll(Map <? extends K,? extendsV> m);添加一个集合
2、删除
clear();清空
remove(Object key);删除指定键值对,删除成功返回V
3、判断
containsKey(Objectkey);判断键是否存在
containsValue(Objectvalue)判断值是否存在
isEmpty();判断是否为空
4、获取
get(Object key);通过键获取对应的值
size();获取集合的长度
Collection<V>value();获取Map集合中所以得值,返回一个Collection集合
Map集合的两种取出方式
Map集合的取出原理:将Map集合转成Set集合。再通过迭代器取出。
第一种:
Set<K> keySet():将Map中键存入到Set集合。因为Set具备迭代器。所以可以通过迭代方式取出所以键的值,再通过get方法。获取每一个键对应的值。
Map<String,String> map = new HashMap<String,String>();
map.put("02","zhangsan2");
//先获取map集合的所有键的Set集合,keySet();
Set<String> keySet = map.keySet();
//有了Set集合。就可以获取其迭代器。
Iterator<String> it = keySet.iterator();
while(it.hasNext())
{
String key = it.next();
//有了键可以通过map集合的get方法获取其对应的值。
String value = map.get(key);
System.out.println("key:"+key+",value:"+value);
}
第二种:
Set<Map.Entry<K,V>> entrySet():将Map集合中的映射关系存入到Set集合中,而这个关系的数据类型就是:Map.Entry
//将Map集合中的映射关系取出。存入到Set集合中。
Set<Map.Entry<String,String>> entrySet =map.entrySet();
Iterator<Map.Entry<String,String>> it = entrySet.iterator();
while(it.hasNext())
{
Map.Entry<String,String> me = it.next();
String key = me.getKey();
String value = me.getValue();
System.out.println(key+":"+value);
}
Map.Entry 其实Entry也是一个接口,它是Map接口中的一个内部接口。
interface Map
{
public static interface Entry
{
public abstract Object getKey();
public abstract Object getValue();
}
}
为何要将Entry定义Map内部呢?
1、Map集合中是映射关系这样的两个数据,是先有Map这个集合,才可有映射关系的存在,而且此类关系是集合的内部事务。
2、并且这个映射关系可以直接访问Map集合中的内部成员,所以定义在内部。
Collections
Collections:集合框架的工具类。里面定义的都是静态方法。
Collections和Collection有什么区别?
Collection是集合框架中的一个顶层接口,它里面定义了单列集合的共性方法。它有两个常用的子接口,List和Set。
List:对元素都有定义索引。有序的。可以重复元素。
Set:不可以重复元素。无序。
Collections是集合框架中的一个工具类,该类中的方法都是静态的。
Arrays
用于操作数组的工具类。里面都是静态方法。
数组变集合
asList:将数组变成list集合
String[] arr ={"abc","cc","kkkk"};
List<String> list = Arrays.asList(arr);
把数组变成list集合有什么好处?
可以使用集合的思想和方法来操作数组中的元素。
注意:将数组变成集合,不可以使用集合的增删方法。因为数组的长度是固定。如果增删那么会反生UnsupportedOperationException,
如果数组中的元素都是对象。那么变成集合时,数组中的元素就直接转成集合中的元素。
如果数组中的元素都是基本数据类型,那么会将该数组作为集合中的元素存在。
集合变数组
Collection接口中的toArray方法。
指定类型的数组到底要定义多长呢?
当指定类型的数组长度小于了集合的size,那么该方法内部会创建一个新的数组。长度为集合的size。当指定类型的数组长度大于了集合的size,就不会新创建了数组。而是使用传递进来的数组。所以创建一个刚刚好的数组最优。
ArrayList<String> al = new ArrayList<String>();
String[] arr = al.toArray(new String[al.size()]);
为什么要将集合变数组?
为了限定对元素的操作。不需要进行增删了。
高级for循环
格式:
for(数据类型 变量名 : 被遍历的集合(Collection)或者数组){}
注意:
对集合进行遍历。只能获取集合元素。但是不能对集合进行操作。
高级for和迭代器区别:
迭代器除了遍历,还可以进行remove集合中元素的动作。
如果是用ListIterator,还可以在遍历过程中对集合进行增删改查的动作。
传统for和高级for区别:
高级for有一个局限性。必须有被遍历的目标。
传统for遍历数组时有索引
建议在遍历数组的时候,还是希望是用传统for。因为传统for可以定义角标。
高级for应用
1、对于ArrayList集合
ArrayList<String> al = newArrayList<String>();
for(String s : al)
{
System.out.println(s);
}
2、对于HashMap集合
HashMap<Integer,String> hm = newHashMap<Integer,String>();
//方式一:
Set<Integer> keySet =hm.keySet();
for(Integer i : keySet)
{
System.out.println(i+"::"+hm.get(i));
}
//方式二:
for(Map.Entry<Integer,String> me: hm.entrySet())
{
System.out.println(me.getKey()+"------"+me.getValue());
}
可变参数(...)
如果一个方法在参数列表中传入多个参数,个数不确定,那么每次都要复写该方法。这时可以用数组作为形式参数。但是在传入时,每次都需要定义一个数组对象,作为实际参数。在JDK1.5版本后,就提供了一个新特性:可变参数。
用…这三个点表示,且这三个点位于变量类型和变量名之间,前后有无空格皆可。
可变参数其实就是数组参数的简写形式。不用每一次都手动的建立数组对象,只要将要操作的元素作为参数传递即可,隐式将这些参数封装成了数组。
注意:可变参数一定要定义在参数列表的最后面。
需求:比如要打印"haha",2,3,4,5,6
public static void show(String str,int... arr)//先匹配固定参数,然后写可变参数
{
System.out.println(arr.length);//数组长度
}
静态导入
可以使被导入的静态变量和静态方法在当前类直接可见,使用这些静态成员无需再给出它们的类名
注意:
当类名重名时,需要指定具体的包名。
当方法重名是,指定具备所属的对象或者类。
import java.util.*; import static java.util.Arrays.*;//导入的是Arrays这个类中的所有静态成员。 import static java.util.Collections.*; import static java.lang.System.*;//导入了System类中所有静态成员。 class StaticImport { public static void main(String[] args) { out.println("haha"); int[] arr = { 3, 1, 5 }; sort(arr); int index = binarySearch(arr, 1);// 二分查找 out.println(Arrays.toString(arr));// 指定所属类 // out.println(toString(arr));//编译失败 /* * 原因:StaticImport默认继承Object类,而Object类中toString方法没有参数, * 因此当方法重名是,指定具备所属的对象或者类 */ out.println("Index=" + index); ArrayList al = new ArrayList(); al.add(1); al.add(3); al.add(2); out.println(al); sort(al); out.println(al); } }
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------