黑马程序员——Java基础之集合框架

时间:2023-02-18 22:40:58
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

集合类

一、概述

为什么出现集合类?

面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就对对象进行存储,集合就是存储对象最常用的一种方式

数据结构:就是容器中存储数据的方式。

对于集合容器,有很多种。因为每一个容器的自身特点不同,其实原理在于每个容器的内部数据结构不同。

集合容器在不断向上抽取过程中。出现了集合体系。


Java中集合类关系图

黑马程序员——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();------列表迭代器。【由于迭代器只有三种方法:hasNextnextremove。】

在迭代时,不可能通过集合对象的方法操作集合中的元素。

因为会发生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的两个小点

1add方法的参数类型是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:线程不安全,存取速度快。

       可以通过元素的两个方法,hashCodeequals来完成保证元素唯一性。如果元素的HashCode值相同,才会判断equals是否为true。如果元素的hashCode值不同,不会调用equals


注意:HashSet对于判断元素是否存在,以及删除等操作,依赖的方法是元素的hashCodeequals方法。

对于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>    Kkey——>键;Vvalue——>值。

Map集合:该集合存储键值对。一 对一对地 存储。而且要保证键的唯一。

方法解析:Map<String,String> map = new HashMap<String,String>();

 

Map  :

|---Hashtable:底层是哈希表数据结构,不可以存入nullnull值。该集合是线程同步[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集合。再通过迭代器取出。

1Set<K> keySet():将Map中所以的键存入到Set集合。因为Set具备迭代器。所以可以通过迭代方式取出所以键的值,再通过get方法。获取每一个键对应的值。

2Set<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(键和值都想取出来)

凡是支持迭代器的集合都可以使用高级forMap不支持迭代,所以转换成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()  获取系统属性信息。

因为PropertiesHashMap的子类,也就是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(“yyyyMMddE 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类的使用。