集合类
一、概述
* 为什么出现集合类?
面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就对对象进行存储,集合就是存储对象最常用的一种方式
数据结构:就是容器中存储数据的方式。
对于集合容器,有很多种。因为每一个容器的自身特点不同,其实原理在于每个容器的内部数据结构不同。
集合容器在不断向上抽取过程中。出现了集合体系。
Java中集合类关系图
* 数组集合类同的区别
数组虽然也可以存储对象,但长度是固定的,可以存储基本数据类型;
集合长度是可变的,集合只能存储对象。
* 集合类的特点
集合只用于存储对象,集合长度是可变的,集合可以存储不同类型的对象。
二、【Collection接口】
|--List:元素是有序的(元素存入集合的顺序和取出的顺序一致),元素可以重复,因为该集合体系有索引。
--ArrayList:
--LinkedList:
--Vector:
|--Set:元素是无序的(存入和取出顺序有可能不一致),元素不可以重复,必须保证元素唯一性。
List所有的特有方法就是:凡是可以操作角标的方法都是该体系的特有方法。
常见操作:
1,添加:
add(object):添加一个元素
addAll(Collection) :添加一个集合中的所有元素。
2,删除:
clear():将集合中的元素全删除,清空集合。
remove(obj) :删除集合中指定的对象。注意:删除成功,集合的长度会改变。
removeAll(collection) :删除部分元素。部分元素和传入Collection一致。
3,判断:
boolean contains(obj) :集合中是否包含指定元素 。
boolean containsAll(Collection) :集合中是否包含指定的多个元素。
boolean isEmpty():集合中是否有元素。
4,获取:
int size():集合中有几个元素。
5,取交集:
boolean retainAll(Collection) :对当前集合中保留和指定集合中的相同的元素。如果两个集合元素相同,返回flase;如果retainAll修改了当前集合,返回true。
6,获取集合中所有元素:
Iterator iterator():迭代器
7,将集合变成数组:
toArray();
三、Iterator接口:
迭代器: 就是在集合中取出元素的方式(和遍历一样)。因为每一个容器中装的对象,都是需要取出然后再操作。不仅仅是打印。这个操作只有在上课中用到。一次把每一个元素都打印出来,所以局限性很大。
提供三种方法:
boolean hasNext() 如果仍有元素可以迭代,则返回 true。
E next() 返回迭代的下一个元素。
void remove() 从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。
接口型引用只能指向自己的子类对象。通过集合的方法获取出来。
把取出方式定义在集合的内部,这样去除方式就可以直接访问集合内部的元素。
那么取出方式就被定义成了内部类。
而每一个容器的数据结构不同,所以取出的动作细节也就不一样。但是都有共性内容判断和取出。那 么可以将写出性抽取。
那么这些内部累都符合一个规则:该规则是Iterator。如何获取集合的取出对象呢?
通过一个对外提供的方法:Iterator();
优化写法:
for(Iterator it=al.iterator();it.hasNext();){
sop(it.next());
}
【操作角标的都是数组原理】
还有的几个:
indexOf()----获取对象的位置。al.indexOf(“java02”);
listIterator();------列表迭代器。【由于迭代器只有三种方法:hasNext,next,remove。】
在迭代时,不可能通过集合对象的方法操作集合中的元素。
因为会发生ConcurrentModificationExc异常。【集合和迭代都会操作集合中的元素,并发】
所以,在迭代器时,只能用迭代器的方法操作元素,可是Iterator方法是有限的,只能对元素进行判断,取出,删除的操作。
如果想要其他的操作如:添加,修改等,就需要使用其子接口,ListIterator。
该接口只能通过List集合的ListIterator方法获取 。
添加:是在制定元素后面添加;
修改:li.set(“..”);在制定位置上换成set中的对象。
例子:
ListIterator li = al.listIterator();
Object obj=li.next();
if(obj.equals(“java002”))-----li.set(“java0006”)[修改]-----li.add(“java0009”)[添加];
只有listIterator具有此特点,因为它有角标。
hasNest,和 hasPrevious,后前。
Next 和previous 分别是返回下一个元素和上一个元素。返回值是对象。
listIterator:对集合遍历过程中的增删该查,就使用这个。
四、List集合:
|-----ArrayList:底层数据结构使用的是数组结构。 特点:查询很快。但是添加删除都是稍慢。|线程不同步(1.2版本)
ArrayList的两个小点:
1,add方法的参数类型是Object,以便于接收任意类型对象
2,集合中存储的都是对象的引用(地址)
|----LinkedList:底层使用的是链表数据结构。 特点:增删很快。但是查询稍慢。
|-----Vector:底层是数组数据结构。线程同步(1.0版本的,那是框架还没有出现【被替代】作为了解)
Vector的特殊:
elements();---->返回此向量的组件的枚举。可以拿到很多的元素。返回值是Enumeration接口...
枚举就是Vector特有的取出方式,(迭代器,遍历,for..)
Enumeration en = v.elements();
While(en.hasMoreElements()){
sop(en.nextElement());
}
发现枚举和迭代器很像。
其实枚举和迭代是一样的,因为枚举的名称以及方法的名称都很长,所以被迭代器取代了。
枚举就挂了、、优先考虑:Iterator,而不是Enumeration。。。
【io有个对象使用了枚举,所以没有完全 消失】
常见操作:
1,添加:
add(index,element) :在指定的索引位插入元素。
addAll(index,collection) :在指定的索引位插入一堆元素。
2,删除:
remove(index) :删除指定索引位的元素。 返回被删的元素。
3,获取:
Object get(index) :通过索引获取指定元素。
int indexOf(obj) :获取指定元素第一次出现的索引位,如果该元素不存在返回-1;
所以,通过-1,可以判断一个元素是否存在。
int lastIndexOf(Object o) :反向索引指定元素的位置。
List subList(start,end) :获取子列表。
4,修改:
Object set(index,element) :对指定索引位进行元素的修改。
5,获取所有元素:
ListIterator listIterator():list集合特有的迭代器。
List集合支持对元素的增、删、改、查。
List集合因为角标有了自己的获取元素的方式: 遍历。
for(int x=0; x<list.size(); x++){
sop("get:"+list.get(x));
}
注意:对于list集合,底层判断元素是否相同,其实用的是元素自身的equals方法完成的。所以建议元素都要复写equals方法,建立元素对象自己的比较相同的条件依据。
——>在进行list列表元素迭代的时候,如果想要在迭代过程中,想要对元素进行操作的时候,比如满足条件添加新元素。会发生.ConcurrentModificationException并发修改异常。
原因是:
——>集合引用和迭代器引用在同时操作元素,通过集合获取到对应的迭代器后,在迭代中,进行集合引用的元素添加,迭代器并不知道,所以会出现异常情况。
如何解决?
——>既然是在迭代中对元素进行操作,找迭代器的方法最为合适.可是Iterator中只有hasNext,next,remove方法.通过查阅的它的子接口,ListIterator,发现该列表迭代器接口具备了对元素的增、删、改、查的动作。
LinkedList:
连接列表。
特有方法:
addFirst();----倒序
addLast();-----正序
removeFiest();
|------获取元素,删除元素。为空抛异常。
removeLast();
|------可以获取全部元素,便是删取。。。
getFirst();
|-----获取元素,不删除元素。为空抛异常。
getLast();
【以上方法是旧版本的方法】
在JDK1.6出现了替代方法。
offerFirst();
offerLast();
peekFirst();
peekLast();
获取元素,不删除元素。为空返回null。
pollFirst();
pollLast();
获取元素,删除元素。为空返回null。
五、Set集合:
Set:元素是无序(存入和取出的顺序不一定一致),元素不可以重复。
|--HashSet:底层数据结构是哈希表。线程不同步。
HashSet集合保证元素唯一性:通过元素的hashCode方法,和equals方法完成的。
当元素的hashCode值相同时,才继续判断元素的equals是否为true。
如果为true,那么视为相同元素,不存。如果为false,那么存储。
如果hashCode值不同,那么不判断equals,从而提高对象比较的速度。
|--TreeSet:可以对Set集合中的元素进行排序。默认按照字母的自然排序。
底层数据结构是二叉树。保证元素唯一性的依据:compareTo方法return 0。
Set集合的方法和Collection中方法一致的。Set接口取出方式只有一种,迭代器。
HashSet
HashSet:线程不安全,存取速度快。
可以通过元素的两个方法,hashCode和equals来完成保证元素唯一性。如果元素的HashCode值相同,才会判断equals是否为true。如果元素的hashCode值不同,不会调用equals。
注意:HashSet对于判断元素是否存在,以及删除等操作,依赖的方法是元素的hashCode和equals方法。
对于ArrayList集合,判断元素是否存在,或者删元素底层依据都是equals方法。 补充知识: 哈希表(Hash table),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一 个位置来访问记录,以加快查找的速度。这个映射函数叫做哈希(散列)函数,存放记录的数组叫做哈希表。 给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈 希(Hash)表,函数f(key)为哈希(Hash) 函数。对对象元素中的关键字(对象中的特有数据),进行哈希算法的运算,并得出一个具体的算法值,这个值 称为哈希值,哈希值就是这个元素的位置。
如果哈希值出现冲突,再次判断这个关键字对应的对象是否相同。如果对象相同,就不存储,因为元素重复。如果对象不同,就存储,在原来对象的哈希值基础 +1顺延。
存储哈希值的结构,我们称为哈希表。
HashSet范例:HashSetDemo.java打印的结果是无序的。
import java.util.*;
public class HashSetDemo{
public static void main(String args[]){
// 创建 HashSet 对象
HashSet hs = new HashSet();
// 加入元素到 HastSet 中
hs.add("B");
hs.add("A");
hs.add("D");
hs.add("E");
hs.add("C");
hs.add("F");
System.out.println(hs);
}
}
TreeSet:
TreeSet底层的数据结构为二叉树结构,为使用树来进行存储的 Set 接口提供了一个工具,对象按升序存储。访问和检索是很快的。在存储了大量的需要进行快速检索的排序信息的情况下, TreeSet是一个很好的选择。
TreeSet用于对Set集合进行元素的指定顺序排序,排序需要依据元素自身具备的比较性。
如果元素不具备比较性,在运行时会发生ClassCastException异常,所以需要元素实现Comparable接口,复写compareTo方法,强制让元素具备比较性。
依据compareTo方法的返回值,确定元素在TreeSet数据结构中的位置。
TreeSet方法保证元素唯一性的方式:就是参考比较方法的结果是否为0,如果return 0,视为两个对象重复,不存。
注意:在进行比较时,如果判断元素不唯一,在判断时,需要分主要条件和次要条件,当主要条件相同时,再判断次要条件,再按照次要条件排序。
TreeSet集合排序有两种方式,Comparable和Comparator区别:
1:让元素自身具备比较性,需要元素对象实现Comparable接口,覆盖compareTo方法。
2:让集合自身具备比较性,需要定义一个实现了Comparator接口的比较器,并覆盖compare方法,并将该类对象作为实际参数传递给TreeSet集合的构造函数。
第二种方式较为灵活。
TreeSet范例:
/*
* 练习:按照字符串长度排序
*
* 字符串本身具备比较性,但是它的比较方式不是所需要的;这时就只能使用比较器
*/
package tree;
import java.util.*;
public class TreeSetTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
TreeSet ts=new TreeSet(new StrLenComparator());
ts.add("abcd");
ts.add("abef");
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;
* return -1;
*/
//写法简化
int num=new Integer(s1.length()).compareTo(new Integer(s2.length()));
if(num==0)//加入判断和下一比较语句,增加次要条件,避免因为字符串长度相同没有存入
return s1.compareTo(s2);
return num;
}
}
六、Map集合
接口 Map<K,V> K(key)——>键;V(value)——>值。
Map集合:该集合存储键值对。一 对一对地 存储。而且要保证键的唯一。
方法解析:Map<String,String> map = new HashMap<String,String>();
Map :
|---Hashtable:底层是哈希表数据结构,不可以存入null键null值。该集合是线程同步[1.0,低效]
|---HashMap:底层是哈希表数据结构,允许使用null键和null值,该集合是不同步的。[1.2,高效]
|---TreeMap:底层是二叉树数据结构。线程不同步。可以用于给map集合中的键进行排序。
(和set很像,其实Set底层就是使用了Map集合。)
Map集合存储和Collection有着很大不同:
Collection一次存一个元素;Map一次存一对元素。
Collection是单列集合;Map是双列集合。
Map中的存储的一对元素:一个是键,一个是值,键与值之间有对应(映射)关系。
特点:要保证map集合中键的唯一性。
Map集合常见操作方法:
1,添加。
put(K key , V value)----将指定的值与此映射中的指定键关联(可选操作)。
Map.put(“04”,”null”);----根据返回值是:返回旧值,植入新值。
putAll(Map<? extends K , ? extends V> m)----从指定映射中将所有映射关系复制到此映射中(可选操作)。
2,删除。
clear()----从此映 射中移除所有映射关系(可选操作)。
Remove(Object key)----如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。
3,判断。
containsValue(Object value)----如果此映射将一个或多个键映射到指定值,则返回 true。
containsKey(Objedt key)----如果此映射包含指定键的映射关系,则返回 true。
isEmpty()----如果此映射未包含键-值映射关系,则返回 true。
4,获取。
get(Object key)----返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。
Map.get(“04”);//可以通过get方法的返回值来判断一个键是否存在。
size()----返回此映射 中的键-值映射关 系数 。
values()----返回此映射中包含的值的 Collection 视图。
---Collection<String> coll=map.values();
[重点]
Map集合的两种取出方式:
Map集合的取出原理:将Map集合转成Set集合。再通过迭代器取出。
1、Set<K> keySet():将Map中所以的键存入到Set集合。因为Set具备迭代器。所以可以通过迭代方式取出所以键的值,再通过get方法。获取每一个键对应的值。
2、Set<Map.Entry<K,V>> entrySet():将Map集合中的映射关系存入到Set集合中,而这个关系的数据类型就是:Map.Entry其实,Entry也是一个接口,它是Map接口中的一个内部接口。
Entry就是Map接口中的内部接口;
为什么要定义在map内部呢?entry是访问键值关系的入口,是map的入口,访问的是map中的键值对。
两种取出方式代码范例:
/*
* 每一个学生都有对应的归属地,学生Student,地址String;
* 学生属性:name,age
* 注意:name和age相同的视为同一学生,保证学生唯一性
*/
package mappackage;
import java.util.*;
class Student implements Comparable<Student>
{
//同时创建多个对象需要被调用时,一定要做实现,覆盖这两个方法
private String name;
private int age;
Student(String name,int age)//学生属性:姓名,年龄
{
this.name=name;
this.age=age;
}
public int compareTo(Student s)
{
int num=new Integer(this.age).compareTo(new Integer(s.age));
//调用compareTo方法比较年龄
if(num==0)//如果年龄相同比较姓名
return this.name.compareTo(s.name);
return num;
}
//hashCode方法
public int hashCode()
{
return name.hashCode()+age*17;
}
public boolean equals(Object obj)
{
if(!(obj instanceof Student))
throw new ClassCastException("类型不匹配");
Student s=(Student)obj;
return this.name.equals(s.name)&&this.age==s.age;
}
//想到hash表或hashCode,就需要复写这两个方法:hashCode和equals方法
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
public String toString()
{
return "姓名:"+name+"年龄:"+age;
}
}
public class MapTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
HashMap<Student,String>hm=new HashMap<Student,String>();
hm.put(new Student("lisi1",20),"北京");
hm.put(new Student("lisi2",23),"上海");
hm.put(new Student("lisi3",23),"广州");
hm.put(new Student("lisi2",23),"南京");
hm.put(new Student("lisi4",23),"深圳");
//第一种取出方式keySet
Set<Student> keySet=hm.keySet();
Iterator<Student> it=keySet.iterator();
while(it.hasNext())
{
Student stu=it.next();
String addr=hm.get(stu);
System.out.println(stu+"----"+addr);
}
//第二种取出方式entrySet
Set<Map.Entry<Student,String>>entrySet=hm.entrySet();
Iterator<Map.Entry<Student,String>>iter=entrySet.iterator();
while(iter.hasNext())
{
Map.Entry<Student,String> me = iter.next();
Student stu=me.getKey();
String addr=me.getValue();
System.out.println(stu+"----"+addr);
}
}
}
Map扩展知识。
注意:
当发现有映射关系时,可以选择map集合。因为map集合中存放就是映射关系。
如范例:
/*
* 需求:获取字符串"addcqhgiuqofoqjgf"中每个字母出现的次数。
* 希望打印结果:a(1)d(2)...
* 通过结果发现,每个字母都有对应次数,说明字母和次数之间都有映射关系,
* 当发现有映射关系时,可以选择Map集合,因为Map集合中就是存放映射关系
* 思路:
* 1,将字符串转换成字符数组,因为要对每一字母进行操作
* 2,定义一个map集合,因为打印结果的字母有顺序,所以使用Treemap集合
* 3,遍历字符数组:
* 将每一个字母作为键去查map集合
* 如果返回null,将该字母和1存入到map集合中
* 如果返回不是null,说明该字母在map集合中已经存在并偶对应的次数
* 那么久获取该次数并进行自增,然后将该字母和自增后的次数存入到map集合中,覆盖调用原来键所对应的值
* 4,将Map集合中的数据变成指定的字符串形式返回
*/
package mappackage;
import java.util.*;
public class NumberOfLetters {
public static void main(String[] args) {
// TODO Auto-generated method stub
String s=charCount("addcqhgiuqofoqjgf");
System.out.println(s);
}
public static String charCount(String str)
{
char[] chs=str.toCharArray();//将字符串转换成字符数组
TreeMap<Character,Integer> tm=new TreeMap<Character,Integer>();//泛型类里面接收的都是引用数据类型,必须使用基本数据类型类character,Intger
int count=0;//定义在循环外好,避免不断开辟、释放内存空间操作
for(int x=0;x<chs.length;x++)
{
if(!(chs[x]>='a'&& chs[x]<='z'||chs[x]>='A'&&chs[x]<='Z'))//避免非字母字符造成干扰
continue;
Integer value = tm.get(chs[x]);
//Integer value = tm.get(chs[x]);//字母chs[x]作为键去找集合返回一个值
if(value!=null)//存储的时候可以实现排序因为Character implements了Comparable
count=value;
count++;
tm.put(chs[x],count);
count=0;
/*上述if语句优化此部分语句
* if(value==null)
{
tm.put(chs[x], 1);//存入
}
else
{
value = value+1;
tm.put(chs[x], value);
}*/
}
//System.out.println(tm);
StringBuilder sb=new StringBuilder();//使用缓冲区存储
Set<Map.Entry<Character,Integer>>entrySet=tm.entrySet();//建立Map集合
Iterator<Map.Entry<Character,Integer>> it=entrySet.iterator();//使用迭代器
while(it.hasNext())
{
Map.Entry<Character,Integer> me=it.next();//取出
Character ch=me.getKey();
Integer value=me.getValue();
sb.append(ch+"("+value+")");
}
return sb.toString();//返回值一定要写
}
}
为什么使用map集合呢?
当数据之间存在着映射关系时,就要先想map集合。
Map集合中存储的都是键值对,打印时就是打印
当应用的是一对多的映射关系,这时就可以通过嵌套的形式将多个映射定义到一个大的集合中,并将大的集合分级处理,形成一个体系。(循环嵌套)两个while。
如范例:
import java.util.*;
class Students
{
private String id;
private String name;
Students(String id,String name)
{
this.name=name;
this.id=id;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public String toString(){
return id+"----"+name;
}
}
public class Mapqiantao {
//将学生存入List
public static void demo()
{
HashMap<String,List<Students>> ithm=new HashMap<String,List<Students>>();
List <Students>yure = new ArrayList<Students>();
List <Students>jiuye = new ArrayList<Students>();
ithm.put("yureban", yure);
ithm.put("jiuyeban", jiuye);
yure.add(new Students("01","zhangsan"));
yure.add(new Students("02","lisi"));
jiuye.add(new Students("01","wangwu"));
jiuye.add(new Students("02","liuyi"));
//遍历ithm集合,获取所有教室
Iterator<String> it =ithm.keySet().iterator();
while(it.hasNext())
{
String roomName=it.next();
List<Students> room = ithm.get(roomName);
System.out.println(roomName);
getInfos(room);
}
}
public static void getInfos(List<Students> list)
{
Iterator<Students> it =list.iterator();
while(it.hasNext())
{
Students s=it.next();
System.out.println(s);
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
demo();
/*HashMap<String,HashMap<String,String>> ithm=new HashMap<String,HashMap<String,String>>();
HashMap<String,String> yure=new HashMap<String,String>();
HashMap<String,String> jiuye=new HashMap<String,String>();
ithm.put("yureban",yure);
ithm.put("jiuyeban",jiuye);
yure.put("01", "zhangsan");
yure.put("02", "lisi");
jiuye.put("01","wangwu");
jiuye.put("02","zhaoliu");
//遍历ithm集合,获取所有教室
Iterator<String> it =ithm.keySet().iterator();
while(it.hasNext())
{
String roomName=it.next();
HashMap<String,String> room = ithm.get(roomName);
System.out.print(roomName);
//getStudentInfo(room);
}*/
/*public static void getStudentInfo(HashMap<String,String> roomMap)
{
Iterator<String> it=roomMap.keySet().iterator();
while(it.hasNext())
{
String id=it.next();
String name=roomMap.get(id);
System.out.println(id+";"+name);
}
}*/
}
}
使用集合的技巧:
看到Array就是数组结构,有角标,查询速度很快。
看到link就是链表结构:增删速度快,而且有特有方法。addFirst; addLast; removeFirst(); removeLast(); getFirst();getLast();
看到hash就是哈希表,就要想要哈希值,就要想到唯一性,就要想到存入到该结构的中的元素必须覆盖hashCode,equals方法。
看到tree就是二叉树,就要想到排序,就想要用到比较。
两种比较的方式:
一个是Comparable:覆盖compareTo方法;
一个是Comparator:覆盖compare方法。
LinkedHashSet,LinkedHashMap:这两个集合可以保证哈希表有存入顺序和取出顺序一致,保证哈希表有序。
集合什么时候用?
当存储的是一个元素时,就用Collection。当存储对象之间存在着映射关系时,就使用Map集合。
保证唯一,就用Set。不保证唯一,就用List。
七、泛型
泛型:JDK1.5版本以后出现的新特性。用于解决安全问题,是一个类型安全机制。
比较记忆模型<泛型的由来>:数组在定义时都明确的存储数据的类型,所以在编译时期就可以发现错 误。但是集合在初始化的时候我们没有明确其类型,所以会发生安全隐患。(编译正常,运行出错)
好处:
1,将运行时期的问题ClassCastException转到了编译时期。
2,避免了强制转换的麻烦。
泛型类前期做法:工具类(方法有:设置获取)接收Object类型对象,为了调用者传入的数据都能接收(set 易懂)。然后调用类想取出工具类中的对象就需要强转(也就是调用者类型)。这时没有泛型。
泛型类出现后:改变了格局,是程序员方便了很多。工具类前期指定类型,调用时就避免了强转。
泛型"<T>"什么时候用?
当操作的引用数据类型不确定的时候:早期使用Object完成扩展,现在使用泛型类<>来完成。
将要操作的引用数据类型传入即可。(基本数据类型不可以)。
范例:
class Demo<T>{//泛型类
public void show(T t){
sop("show:"+t);
}
public void print(T t){
sop("print:"+t);
}
}
class GenerricDemo{
Public static void main(String [] args){
Demo<Integer> d=new Demo<Integer>();
D.show(new Integer(4));
D.print(9);//有个自动装箱
}
}//缺点:就是类确定了类型,那么它的方法就确定了。若想操作String,那么就需要在此创建Demo对象,为String类型。
为了让不同方法可以操作不同类型,而且类型还不确定。那么就可以将泛型定义在方法上。
class Demo{
public <T> void show(T t){//泛型方法,泛型方法的泛型定义在返回值类型标识符前
sop(“show:”+t);
}
public <T> void prit(T t){
sop(“print:”+t);
}//T是局部变量所以可以一样。
}//这时在调用的时候就可以传任意类型,泛型的好处显而易见。
泛型类和泛型方法并用时,方法泛型为主。很牛!
注意:静态方法不可以访问类上定义的泛型,
如果静态方法操作的应用数据类型不确定, 可以将泛型定义在方法上。
泛型接口:
把泛型定义在接口上,实现有两个方式:
①:class InterImpl inplements Inter<T>{} --->程序员指定泛型中的类型
②:class InterImpl<T> inplements Inter<T>{}----> 调用者指定泛型中类型
错误提醒:ArrayList<Student> al=new ArrayList<Person>();[左右两边不匹配]。
ArrayList al = new ArrayList<Integer>();
泛型问题的提升:
解决类型安全问题,------------------出现泛型。
解决指定类型,------------------------出现泛型类。
解决指定的方法类,和静态。------出现了泛型方法。
解决类型不确定,---------------------出现了泛型通配符?
解决类不确定,但是不准出现范围以外的类型,[术语:泛型限定]
-----------出现了泛型通配符继承了指定类型。<? extends Person>
泛型方法:
其实<>就是一个用于接收具体引用数据类型的参数范围。
在程序中,只要用到了带有<>的类或者接口,就要明确传入的具体引用数据类型 。
泛型技术是给编译器使用的技术,用于编译时期。确保了类型的安全。
运行时,会将泛型去掉,生成的class文件中是不带泛型的,这个称为泛型的擦除。
为什么擦除呢?因为为了兼容运行的类加载器。
泛型的补偿:在运行时,通过获取元素的类型进行转换动作。不用使用者在强制转换了。
泛型的通配符:? 未知类型。
泛型的限定:
? extends E: 接收E类型或者E的子类型对象。--------[上限](用的多)
一般存储对象的时候用。-----比如添加元素 addAll.
? super E: 接收E类型或者E的父类型对象。-----------[下限](用的很少)
一般取出对象的时候用。-----比如比较器。【难点】
class WorkerComp implements Comparator<Worker>{
public int compare(Worker s1,Worker s2){
return s1.getName().compareTo(s2.getName());
}
}
class StudentComp implements Comparator<Student>{
public int compare(Student s1,Student s2){
return s1.getName().compareTo(s2.getName());
}
}
class Comp implements Comparator<Person>{
public int compare(Person p1,Person p2){
return p1.getName().compareTo(p2.getName());
}//比较器的泛型,有扩展性;
}//其中Person是Worker和Student的父类。
—— >多态的弊端就是不能预先使用子类的特有方法。
——>泛型的弊端也是,不能使用类型特有的方法,但是tostring可以使用。
八、Collections:集合框架的工具类
是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,于Java的Collection框架。
public static <T extends Comparable<?Super T>> void sort(List<T> list)
Public static <T> void sort(List<T> list){ }//T具有比较性
可以根据元素的自然顺序对指定列表进行排序。
静态方法:
Collections.sort(list);//list集合进行元素的自然顺序排序。
Collections.sort(list,new ComparatorByLen());//按指定的比较器方法排序。
Collections.max(list); //返回list中字典顺序最大的元素。
int index = Collections.binarySearch(list,"zz");//二分查找,返回角标。
Collections.reverseOrder();//逆向反转排序。
Collections.shuffle(list);//随机对list中的元素进行位置的置换。
swap(list,1,2)表示的是把list集合中的角标1的元素和角标2的元素换位置。
shuffle(list)表示随机源的将list排序 [类似洗牌]
将非同步集合转成同步集合的方法:Collections中的 XXX synchronizedXXX(XXX);
List synchronizedList(list);
Map synchronizedMap(map);
原理:定义一个类,将集合所有的方法加同一把锁后返回。
static Collection<T> synchronizedCollection(Collection<T> c)
static List<T> synchronizedList(List<T> list)返回指定列表支持的同步(线程安全)列表
static Map<T> synchronizedMap(Map<K,V> map)返回由指定映射支持的同步(线程安全)列表
底层如何实现的:<源码>
SynchronizedList(List<E> list){
super(list);
this.list = list
}
SynchronizedList(List<E> list,Object mutex){
super(list,mutex);
this.list = list
}//覆盖了equals,hashcode方法。
public void add(int index , E element){
Synchronized(mutex){return list.set(index,element);
}
//封装是你传递的list,mutex是同步代码块,使用的是同一个锁。
Collection 和 Collections的区别:
Collections是个java.util下的类,是针对集合类的一个工具类,提供一系列静态方法,实现对集合的查找、排序、替换、线程安全化(将非同步的集合转换成同步的)等操作。
Collection是个java.util下的接口,它是各种集合结构的父接口,继承于它的接口主要有Set和List,提供了关于集合的一些操作,如插入、删除、判断一个元素是否其成员、遍历等。
Arrays:用于操作数组的工具类。
里面都是静态方法。
方法有:查找(二分),对象二分(equals);复制(头尾-->新数组);
比较(比较数组是否一样;比较容器元素是否一样;)fill(替换,头尾);hashcode;
Sort(排序,局部排序);toString(返回指定数组内容的字符串表示形式。);
asList:将数组变成list集合。
String[] arr = {"abc","defg","hijkl"};
List<String> list = Array.asList(arr);
sop(list);
----->把集合变成list集合有什么好处?--可以使用集合的思想和方法来操作数组。
注意:将数组变成集合,不可以使用集合的增删方法。因为数组的长度是固定。否则就会产生UnsupportedOperationException不支持此操作的异常。
---->注意如果数组中的元素都是对象,那么变成集合时,数组中的元素就直接转成集合中的元素。
如果数组中的元素都是基本数据类型,那么会将该数组作为集合中的元素存在。
int[] nums = {1,43,3};
Integer[] num = {1,3,46};
List lin = Array.asList(nums);
Integer<Integer> li = Array.asList(nums);
集合变数组:(toArray)
方法:toArray(T[] a)返回包含此Collection中所有元素的数组;返回数组的运行时类型与指定数组的 运行时的类型相同。
①:指定类型的数组到底要定义多长呢?
当指定类型的数组长度小于了集合的size,那么该方法内部会创建一个新的数组。长度为集合的size。 当指定类型的数组长度大于了集合的size,就不会新创建数组了,而是使用传递进来的数组。
所以创建一个刚刚好的数组最优。
String[] arr = al.toArray(new String[al.size()]);
②:为什么要将集合变成数组呢?
为了限定对元素的操作,不需要进行增删了。
迭代器升级(高级for循环):
格式:
for(数据类型 变量名 : 被遍历的集合(Collection)或者数组){}
例如:
for(String s:al)
{
System.out.println(s);
}
代码:for(String s : al){sop(s);} 遍历al集合,并打印出来。底层原理还是迭代器,优化了书写。
局限:就是只能对集合中的元素进行取出,不能做修改删除等动作。
迭代器:除了遍历,还可以进行remove集合中的元素的动作。
如果使用ListIterator,还可以在遍历过程中对集合进行增删改查的动作。
传统for和高级for有什么区别?
高级for有一个局限性:必须有一个被遍历的目标。
建议,在遍历数组的时候,还是使用传统的for。因为传统for可以定义角标。
HashMap集合的元素取出来,使用高级for(键和值都想取出来):
凡是支持迭代器的集合都可以使用高级for,Map不支持迭代,所以转换成Set。
HashMap<Integer,String> hm = new HashMap<Integer,String>();
hm.put(“1”,”a”);hm.put(“2”,” b”);hm.put(“3”,”c”);
Set<Integer> keySet = hm.keySet();
For(Integer i : keySet){
System.out.println(i+”.......”+hm.get(i));
}
Set<Map.Entry<Integer,String>> entrySet = hm.entrySet();
For(Map.Entry<Integer,String> me : entrySet)
For(Map.Entry<Integer,String> me : hm.entrySet()){
System.out.println(me.getKey()+”------------”+me.getValue());
}
JDK1.5版本出现的新特性:
-----》加法的重载,可以使用传递数组就可以,但是每次都得新建一个数组。一方法=多方法。
------》不传递数组了,直接传递数据。可变参数。其实就是上一种数组参数的简写形式。不用每一次都手动的建立数组对象。只要将要操作的元素作为参数传递即可。隐式将这些参数封装成了数组。
show(2,3,4,5,6,5);
public static void show(int...arr){sop(arr.length);}
①方法的可变参数:
可变参数其实就是数组参数的简写形式,不用每一处都手动建立数组对象,只要将操作元素作为参数传递即可
注意:在使用是,可变参数一定要定义在参数列表最后面。
public static void show(String str , int...arr){sop(arr.length);}//int为类型均可。
②静态导入:StaticImport
import static java.util.Arrays.*;//导入的是Arrays这个类中的所有静态成员。
当导包之后, 当类名重名时,需要指定具体的包名。
当方法重名时,指定具备所属的对象或者类。(不可以出现可混淆的可能)
很好的例子:
import static java.lang.System.*;//导入的是System这个类中的所有静态成员。
然后就可以是:System.out.println();--->out.println();
九、几个重要的对象:
1、System:不可以被实例化。其中的方法和属性都是静态的。
out:标准输 出,默认是控制台。
in:标准输入,默认是键盘。
方法:Properties getProperties() 获取系统属性信息。
因为Properties是HashMap的子类,也就是Map集合的一个子类对象。
那么就可以通过map的方法取出该集合中的元素。
properties ni = System.getProperties();
获取所有属性信息:
for(Object obj = ni.keySet()){
String value = (String)ni.get(obj);
sop(...);
}
强转了类型---Object->String。
//如何在系统中自定义一些特有信息呢?
System.setProperty(“makey”,”mayvalue”);
//获取指定属性的信息
String value = System.getProperty(“os.name”);
sop(...);//当不存在时则为null;
//可不可以在java虚拟机启动时,动态的加载一些信息呢?
在cmd中:
java -Dhaha=qqqq SystemDemo//为java文件,前面为动态的加载然后就可以使用了。
2、Runtime:
类,没有构造函数时,那么就是不能创建对象。然后有方法时就是静态的,然而不是静态时,那么该类就应该给我提供一个可以使用的对象:获取该对象的方法必须是静态的而且返回值类型是该类类型
由此看出,该类使用了单例设计模式
方式为:static Runtime getRuntime();
每一个java应用程序都有一个Runtime类实例,使应用程序能够与其运行的环境相连。可以通过 getRuntime方法获取对当前运行时 。应用程序不可以创建自己的Runtime类实例。[单例设计模式]
方法:
//在单独的进程中执行指定命令和变量。
Process exec(String command)
--->可以打开任意的win下的.exe文件:Runtime.getRuntime().exec(“winmine.exe”);//需要抛出异常。\>\\
Process:
ProcessBuilder.start()和Runtime方法创建一个本机进程,并返回Process子类的一个实例,该实例可用来控制进程并获得相关信息。Process类提供了执行从进程输入、执行输出到进程、等待进城完成、检查进城推出状态以及销毁进程的方法
方法:
【Process destroy();杀掉子进程、很牛的一个。速度很快、由于速度快所以可以:Thread.sleep(4000);等待四秒。】
-----------只能删能获取进程对象的线程。拿不到对象就杀不了。-----------
很牛----->:Process p = Runtime.getRuntime().exec(“notepad.exe SystemDemo.java”);//选择性打开文件
Date:
构造方法 Date() 分配Date对象并初始化此对象,以表示分配给他的时间。
Date(long date)表示自从标准基准时间以来的指定毫秒数。
Date d = new Date();
System.out.println(d);
//将模式封装到SimpleDateFormat对象中。
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy年MM月dd日E hh:mm:ss”);//E本地化
//调用format方法让模式格式化指定Date对象。
String time = sdf.format(d);
System.out.println(“time=”+time);
单独获取年月日等信息。
Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy”);
System..out.println(sdf.format(d));//由于打印的是字符串,所以需要转成数。[老方法]
class CalendarDemo{
Public static void main(String[] args){
Calender c = Calendar.getInstance();
sop(“星期”+c.get(Calendar.DAY_OF_WEEK));
}
}
查表法:
String[] mons = {“一月”,”二月”,”三月”,”四月”,”五月”,”六月”,”七月”,”八月”,”九月”};
Int index = c.get(Calendar.YEAR)+”年”);sop(mons[index]);
对实例的时间获取,除了get还有set()设置时间。
如:c.set(2012,2,23);
c.add(Calendar.MONTH,-1); 往前推一个月。
C.add(Calendar.DAY_OF_MONTH,18);18天的日期。
Math:
包含用于执行基本数学运算的方法。
abs()///返回绝对值
double d = Math.ceil(16.34);//ceil 返回大于指定数据的最小整数;
double d1 = Math.floor(16.34);//floor 返回小于指定数据的最小整数;
Long l = Math.round(12.34);//四舍五入
Double d2 = Math.pow(2,3);//次幂运算
Public static double random() 返回代正号的double值,[该值>=0&<1]伪随机数,借助了随机数生成器。
double d = Math.random();数字之间转换,(int)。
Random r = new Random(); int d = r.nextInt(10)+1;Random类的使用。