java学习笔记——基础16(集合Collecton,Iterator迭代器,增强for循环,泛型)

1、集合
2、Iterator迭代器
3、增强for循环
4、泛型

01集合使用的回顾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
 *A:集合使用的回顾
*a.ArrayList集合存储5int类型元素
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(111);
list.add(222);
list.add(333);
list.add(444);
list.add(555);
for(int i=0; i<list.size(); i++){
System.out.println(list.get(i));
}
}

*b.ArrayList集合存储5个Person类型元素

public class Person {
private String name;
private int age;

public Person(){
super();
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

//重写toString()方法
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}


public class ArraryListDemo {
public static void main(String[] args) {
ArrayList <Person> aa = new ArrayList<Person>();
aa.add(new Person("a",23));
aa.add(new Person("b",52));
aa.add(new Person("c",36));
for(Person p : aa){
System.out.println(p);//默认调用toString()方法
}

}
}

02集合的学习目标

1
2
3
4
5
   集合,集合是java中提供的一种容器,可以用来存储多个数据。
在前面的学习中,我们知道数据多了,可以使用数组存放或者使用ArrayList集合进行存放数据。
那么,集合和数组既然都是容器,它们有啥区别呢?
"数组的长度是固定的。【集合】的【长度】是【可变】的。"
" 集合中存储的元素【必须】是【引用类型数据】"

03集合继承关系图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
A:集合继承关系图
a:ArrayList的继承关系:
查看ArrayList类发现它继承了抽象类AbstractList同时实现接口List,而List接口又继承了Collection接口
"Collection接口为【最顶层】【集合接口】了"
源代码:
interface List extends Collection {
}
public class ArrayList extends AbstractList implements List{
}

b:集合继承体系
"这说明我们在使用ArrayList类时,该类已经把所有抽象方法进行了重写。
那么,实现Collection接口的所有子类都会进行方法重写。
 Collecton接口常用的子接口有:List接口、Set接口
 List接口常用的子类有:ArrayList类、LinkedList类
 Set接口常用的子类有:HashSet类、LinkedHashSet类

Collection 接口
|
----------------------------------------------------------------
| |
List接口 Set接口
| |
---------------- -------------
| | | |
ArrayList类 LinkedList类 HashSet类 LinkedHashSet类
"

04集合Collection的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
A:集合Collection的方法
"/*
* Collection接口中的方法
* 是集合中所有实现类必须拥有的方法
* 使用Collection接口的实现类,程序的演示
* ArrayList implements List
* List extends Collection
* 方法的执行,都是实现的重写
*/"
public class CollectionDemo {
public static void main(String[] args) {
function_2();
}


"/* Collection接口方法
* Object[] toArray() 集合中的元素,转成一个数组中的元素, 集合转成数组
* 返回是一个存储对象的数组, 数组存储的数据类型是Object
*/"
private static void function_2() {
Collection<String> coll = new ArrayList<String>();
coll.add("abc");
coll.add("itcast");
coll.add("itheima");
coll.add("money");
coll.add("123");

Object[] objs = coll.toArray();
for(int i = 0 ; i < objs.length ; i++){
System.out.println(objs[i]);
}
}
"/*
* 学习Java中三种长度表现形式
* ——————————————————————————————————
* 数组.length 属性 返回值 int
* ——————————————————————————————————
* 字符串.length() 方法,返回值int
* ——————————————————————————————————
* 集合.size()方法, 返回值int
*/"

"/*
* Collection接口方法
* boolean contains(Object o) 判断对象是否存在于集合中,对象存在返回true
* 如果此 collection 包含【指定的元素】,则返回 true
* 方法参数是Object类型
*/"
private static void function_1() {
Collection<String> coll = new ArrayList<String>();
coll.add("abc");
coll.add("itcast");
coll.add("itheima");
coll.add("money");
coll.add("123");

boolean b = coll.contains("itcast");
System.out.println(b);
}


"/*
* Collection接口的方法
* void clear() 清空集合中的所有元素
* 集合容器本身依然存在
*/"
public static void function(){
//接口多态的方式调用
Collection<String> coll = new ArrayList<String>();
coll.add("abc");
coll.add("bcd");
System.out.println(coll);

coll.clear();

System.out.println(coll);

}
}

05集合Collection的remove方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
A:05集合Collection的remove方法
"/*
* Collection接口方法
* boolean remove(Object o)移除集合中指定的元素
* 如果集合中包含多个相同的元素,remove方法只删除出现的第一个元素。
*
*
* 另外:
* boolean removeAll(Collection<?> c)
* 移除此 collection 中那些也包含在【指定 collection 】中的【所有元素】(可选操作)。
* 此调用返回后,collection 中将不包含任何与指定 collection 相同的元素。
*/"
private static void function_3(){
Collection<String> coll = new ArrayList<String>();
coll.add("abc");
coll.add("money");
coll.add("itcast");
coll.add("itheima");
coll.add("money");
coll.add("123");
System.out.println(coll);

boolean b = coll.remove("money");
System.out.println(b);
System.out.println(coll);
}

06迭代器的概述

1
2
3
4
5
6
7
8
9
10
11
A:迭代器概述:
a:java中提供了很多个集合,它们在存储元素时,采用的存储方式不同。
我们要取出这些集合中的元素,可通过一种"通用的获取方式"来完成。

b:"Collection集合元素的通用获取方式:在取元素之前先要判断集合中有没有元素,
如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。
一直把集合中的所有元素全部取出。这种取出方式专业术语称为【迭代】。
"
c:"每种集合的底层的数据结构不同,例如ArrayList是数组,LinkedList底层是链表,但是无论使用那种集合,
我们都会有判断是否有元素以及取出里面的元素的动作,
那么Java为我们提供一个【迭代器】定义了【统一的判断元素和取元素的方法】"

07迭代器的实现原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 *A:迭代器的实现原理
" /*
* 集合中的迭代器:
* 获取集合中元素方式
* 接口 Iterator : 两个抽象方法
* boolean hasNext() 判断集合中还有没有可以被取出的元素,如果有返回true
* next() 取出集合中的下一个元素
* ————————————————————————————————————————————————————————————————————
* Iterator接口,找实现类.
* Collection接口定义了iterator方法:
* Iterator<E> iterator()
* 返回在此 collection 的元素上进行迭代的迭代器
*————————————————————————————————————————————————————————————————————
* ArrayList类 重写方法 iterator(),返回了Iterator接口的实现类的对象
* 使用ArrayList集合的对象
* Iterator it =array.iterator(),运行结果就是Iterator接口的实现类ArrayList的对象
* it是接口的实现类对象,调用方法 hasNext 和 next 集合元素迭代
*/"

08迭代器的代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
*A:迭代器的代码实现
public class IteratorDemo {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<String>();
coll.add("abc1");
coll.add("abc2");
coll.add("abc3");
coll.add("abc4");
//迭代器,对集合ArrayList中的元素进行取出

//调用集合的方法iterator()获取出,Iterator接口的实现类的对象
Iterator<String> it = coll.iterator();
//接口实现类对象,调用方法hasNext()判断集合中是否有元素
//boolean b = it.hasNext();
//System.out.println(b);
//接口的实现类对象,调用方法next()取出集合中的元素
//String s = it.next();
//System.out.println(s);

//迭代是反复内容,使用循环实现,循环的条件,集合中没元素, hasNext()返回了false
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
}
}

09迭代器的执行过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
A:迭代器的执行过程
a:迭代器的原理:
while(it.hasNext()) {
System.out.println(it.next());
}

//cursor记录的索引值不等于集合的长度返回true,否则返回false
public boolean hasNext() {
return cursor != size; //cursor初值为0

}

//next()方法作用:
//①返回cursor指向的当前元素
//②cursor++
public Object next() {
int i = cursor;
cursor = i + 1;
return elementData[lastRet = i];

}
b:for循环迭代写法:
for (Iterator<String> it2 = coll.iterator(); it2.hasNext(); ) {
System.out.println(it2.next());
}

10集合迭代中的转型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
A:集合迭代中的转型
a:在使用集合时,我们需要注意以下几点:
"集合中存储其实都是【对象的地址】"
"集合中可以存储基本数值":jdk1.5版本以后可以存储了。
"因为出现了【基本类型】的【包装类】,它提供了【自动装箱】操作(基本类型对象)"
这样,"集合中的元素就是【基本数值】的【包装类】对象"

b:"存储时提升了Object。取出时要使用元素的【特有内容】,必须【向下转型】"
"可以【不指定】【集合的存储类型】,即可以添加 Object 对象(任意对象)",如下所示:
Collection coll = new ArrayList();
coll.add("abc");
coll.add("aabbcc");
coll.add("shitcast");
Iterator it = coll.iterator();
while (it.hasNext()) {
//由于元素被存放进集合后全部被提升为Object类型
//当需要使用子类对象特有方法时,需要向下转型
String str = (String) it.next();
System.out.println(str.length());
}
" 注意:如果集合中存放的是多个对象,这时进行向下转型会发生类型转换异常。"


c:Iterator接口也可以使用<>来控制迭代元素的类型的。代码演示如下:
Collection<String> coll = new ArrayList<String>();
coll.add("abc");
coll.add("aabbcc");
coll.add("shitcast");
Iterator<String> it = coll.iterator();
while (it.hasNext()) {
String str = it.next();
//当使用Iterator<String>控制元素类型后,就不需要强转了。获取到的元素直接就是String类型
System.out.println(str.length());
}

11增强for循环遍历数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
*A:增强for循环遍历数组
a:格式:
"/*
* JDK1.5新特性,增强for循环
* JDK1.5版本后,出现新的接口 java.lang.Iterable
* Collection开是继承Iterable
* Iterable作用,实现增强for循环
*
* 格式:
* for( 数据类型 变量名 : 数组或者集合 ){
* sop(变量);
* }
*/"
public static void function_1(){
"for each对于对象数组遍历的时候,可以【调用】【对象的方法】"
String[] str = {"abc","itcast","cn"};
for(String s : str){
System.out.println(s.length());
}
}

"/*
* 实现for循环,遍历数组
* 好处: 代码少了,方便对容器遍历
* 弊端: 没有索引,不能操作容器里面的元素
*/"
public static void function(){
int[] arr = {3,1,9,0};
for(int i : arr){
System.out.println(i+1);
}
System.out.println(arr[0]);
}

12增强for循环遍历集合

1
2
3
4
5
6
7
8
9
10
11
12
13
A:增强for循环遍历集合  
" /*
* 增强for循环遍历集合
* 存储自定义Person类型
*/"
public static void function_2(){
ArrayList<Person> array = new ArrayList<Person>();
array.add(new Person("a",20));
array.add(new Person("b",10));
for(Person p : array){
System.out.println(p);// System.out.println(p.toString());
}
}

13泛型的引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
A:泛型的引入
"在前面学习集合时,我们都知道集合中是可以【存放任意对象】的,
只要把对象存储集合后,那么这时他们都会被提升成Object类型。
当我们在取出每一个对象,并且进行相应的操作,这时必须采用【类型转换】。"比如下面程序:
public class GenericDemo {
public static void main(String[] args) {
List list = new ArrayList();
list.add("abc");
list.add("itcast");
list.add(5);"//由于集合没有做任何限定,任何类型都可以存放"
"//自动装箱:Object obj=new Integer(5);"

Iterator it = list.iterator();
while(it.hasNext()){
//需要打印每个字符串的长度,就要把迭代出来的对象转成String类型
String str = (String) it.next();//String str=(String)obj;
"//编译时期仅检查语法错误,String是Object的子类可以向下转型
//运行时期String str=(String)(new Integer(5))
//String与Integer没有父子关系所以转换失败
//程序在运行时发生了问题java.lang.ClassCastException
"
System.out.println(str.length());
}
}
}

14泛型的定义和使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
A:泛型的定义和使用
"/*
* JDK1.5 出现新的安全机制,保证程序的安全性
* 泛型: 指明了【集合】中【存储数据的类型】 <类型变量>
*/"

public class GenericDemo {
public static void main(String[] args) {
function();
}

public static void function(){
Collection<String> coll = new ArrayList<String>();
coll.add("abc");
coll.add("rtyg");
coll.add("43rt5yhju");
// coll.add(1);

Iterator<String> it = coll.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s.length());
}
}
}

15Java中的伪泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
A:Java中的【伪泛型】:
"泛型【只在】【编译时】存在,【编译后】就被【擦除】,在编译之前我们就可以限制集合的类型,起到安全作用"
"编译后得到的Class文件是【没有】【泛型】的,实际并不存在泛型类,【系统】【不会】生成泛型类"
例如: ArrayList<String> al=new ArrayList<String>();
"编译后": ArrayList al=new ArrayList();
————————————————————————————————————————————————————————————————————————————————————————
B:"运行时类查询(getClass方法)【只适用】于【原始类型】"
可以理解为ArrayList<String>类是ArrayList的子类,事实上,ArrayList<String>类也确实像一种特殊的ArrayList类:
"该ArrayList<String>对象【只能】添加【String对象】作为【集合元素】"
但实际上,
"系统【并没有】为ArrayList<String>生成【新的class文件】,
而且也【不会】把ArrayList<String>当成【新类】来处理"

例如:
List<String> l1 =new ArrayList<>();
List<Integer> ii = new ArrayList<>();
System.out.println(l1.getClass());"//class java.util.ArrayList"
System.out.println(ii.getClass());"//class java.util.ArrayList"
System.out.println(l1.getClass() == ii.getClass());"true"
————————————————————————————————————————————————————————————————————————————————————————
C: "不管为泛型的【类型形参】传入哪一种一【类型实参】,
对于Java来说,它们依然被当成【同一个类处理】,在内存中也【只占用一块内存空间】"
"因此在【静态方法】、【静态初始化块】或者【静态变量】的【声明和初始化】中【不允许】
使用【类型形参】"。下面程序演示了这种"【错误】"

public class Apple<T> {
private static T age; "【error】"
public static void bar(T mm){ "【error】"

}
————————————————————————————————————————————————————————————————————————————————————————
D:"由于系统中并不会真正生成泛型类,所以【instanceof运算符】后【不能】使用【泛型类】"
List<Integer> ii = new ArrayList<Integer>();
if(ii instanceof ArrayList){//true
}

if(ii instanceof ArrayList<Integer>){"【error】"
}

16泛型类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
A:泛型类:
a:定义格式:
修饰符 class 类名<代表泛型的变量> { }

例如,API中的ArrayList集合:
class ArrayList<E>{
public boolean add(E e){ }
public E get(int index){ }
}

注意:"泛型类的【静态方法】、【静态初始化块】或者【静态变量】的
【声明和初始化】中【不允许】使用【类型形参】T"

private static T age;"【error】"
public static void bar(T mm){"【error】"

}
static{
T gr = 1;"【error】"
}

————————————————————————————————————————————————————————————————————————————————————————

b:使用格式:
"创建对象时,确定泛型的类型"

例如,ArrayList<String> list = new ArrayList<String>();
此时,变量E的值就是String类型
class ArrayList<String>{
public boolean add(String e){ }
public String get(int index){ }
}

例如,ArrayList<Integer> list = new ArrayList<Integer>();
此时,变量E的值就是Integer类型
class ArrayList<Integer>{
public boolean add(Integer e){ }
public Integer get(int index){ }
}

————————————————————————————————————————————————————————————————————————————————————————
c:"可以为【任何类,接口】【增加泛型声明】
(【井不是】【只有】集合类才可以使用泛型声明,虽然集合类是泛型的重要使用场所)"
例如:
public class Apple<T> {
private T info;
private String id;
public Apple(){
super();
}
public Apple(T info){
this.info = info;
}
public Apple(T info, String id){
this.info = info;
this.id =id;
}
public void setInfo(T info){
this.info =info;
}
public T getInfo(){
return this.info;
}
public void setId(String id){
this.id =id;
}
public String getId(){
return this.id;
}
@Override
public String toString(){
return "{"+ "id=: " +this.id+ "|"+" info=: "+ this.info +"}";
}
}

public class AppleTest {
public static void main(String[] args) {
new Apple<Double>();
Apple<Double> a1= new Apple<>(25008.8,"刘鹏001");
Apple<Integer> b2 = new Apple<>(26000,"周莉002");
System.out.println(a1);
System.out.println(b2);
}
}
————————————————————————————————————————————————————————————————————————————————————————
d: "当创建带泛型声明的自定义类,为该类定义【构造器】时,
【构造器名】还是【原来的类名】,【不能】【增加泛型声明】。
例如,为Apple<T>类定义构造器,其【构造器名依然是Apple】,而【不是】Apple<T>!
【调用】(new对象时)该构造器时却可以使用Apple<T>的形式,当然应该为T形参传入实际的类型参数"
如:
public Apple(T info){
this.info = info;
}
public Apple(T info, String id){
this.info = info;
this.id =id;
}

17 泛型类派生子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
A:当创建了带泛型声明的接口、父类之后,可以为该接创建实现类,或从该父类派生子类,
"需要指出的是,当使用这些接口、父类时【不能】再【包含】【类型形参】。例如,下而代码就是【错误】的"
public class SubApple extends Apple<T>{}"//error"

"除非:子类也是泛型类",即:SubApple<T>
public class SubApple<T> extends Apple<T>{}
————————————————————————————————————————————————————————————————————————————————————————
B:定义方法时可以声明数据形参,调用方法(使用方法)时必须为这些数据形参传入实际的数据:
与此类似的是,
"定义类、接口、方法时可以声明类型形参,使用类、接口、方法时应该为【类型形参】传入【实际的类型】"
public class SubApple11 extends Apple<Integer>{}

public class SubApple11 extends Apple<Integer>{
public SubApple11(){
super();
}
public SubApple11(Integer d,String s){
super(d,s);
}
@Override
public Integer getInfo(){
"//父类是Apple<Integer>"
"返回值类型必须与Apple<Integer>的返回值类型完全相同"
return super.getInfo() +5000;
}

}
————————————————————————————————————————————————————————————————————————————————————————
C: 调用方法时必须为所有的数据形参传入参数值,
"与调用方法不同的是"
"使用类、接口时【可以】【不为】类型形参传入【实际的类型参数】,即下面代码也是【正确】的"
"此时,系统会把Apple<T>类里的T形参当成【Object类型】处理"
public class SubApple extends Apple{}

举例:
public class SubApple extends Apple{//系统会把Apple<T>类里的T形参当成【Object类型】处理
public SubApple(){
super();
}
public SubApple(Double d,String s){
super(d,s);
}
@Override
public Double getInfo(){
"//super.getInfo()是Object类型"
return (Double) super.getInfo() +1000;
}

}

18泛型的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
A:泛型的方法
a:"定义格式:修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }"
b:泛型方法的使用:
1:例如,API中的ArrayList集合中的方法:
public <T> T[] toArray(T[] a){ }
//该方法,用来把集合元素存储到指定数据类型的数组中,返回已存储集合元素的数组

"使用格式:调用方法时,确定泛型的类型"
例如:
ArrayList<String> list = new ArrayList<String>();
String[] arr = new String[100];
String[] result = list.toArray(arr);
"此时,变量T的值就是String类型。变量T,可以与定义集合的泛型不同"
public <String> String[] toArray(String[] a){ }


例如:
ArrayList<String> list = new ArrayList<String>();
Integer[] arr = new Integer[100];
Integer [] result = list.toArray(arr);

"此时,变量T的值就是Integer类型。变量T,可以与定义集合的泛型不同"
public <Integer> Integer[] toArray(Integer[] a){
}

19泛型的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
A:泛型的接口:
"/*
* 带有泛型的接口
*
* public interface List <E>{
* abstract boolean add(E e);
* }
* ————————————————————————————————————————————————————————————————————
*A: 实现类,先实现接口,不理会泛型
* public class ArrayList<E> implements List<E>{
* }
* 调用者 : new ArrayList<String>() 后期创建集合对象的时候,指定数据类型
*
* ————————————————————————————————————————————————————————————————————
*B: 实现类,实现接口的同时,也指定了数据类型
* public class XXX implements List<String>{
* }
* new XXX()
*/"
public class GenericDemo2 {

}

20泛型的好处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
A:泛型的好处
a:将运行时期的ClassCastException,转移到了编译时期变成了编译失败。
b:避免了类型强转的麻烦。
演示下列代码:
public class GenericDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("abc");
list.add("itcast");
//list.add(5);//当集合明确类型后,存放类型不一致就会编译报错
//集合已经明确具体存放的元素类型,那么在使用迭代器的时候,迭代器也同样会知道具体遍历元素类型

Iterator<String> it = list.iterator();
while(it.hasNext()){
String str = it.next();
System.out.println(str.length()); //当使用Iterator<String>
//控制元素类型后,就不需要强转了。获取到的元素直接就是String类型
}
}
}

21泛型的继承规则

1
2
3
4
5
6
7
8
9
10
11
12
13
A:"Manager类 是 Employee类 的【子类】。 但 Pair<Manager> 【不是】Pair<Employee> 的【子类】"
"即 Pair<Manager> 与 Pair<Employee> 【没有】【继承关系】"
"无论 S 与 T 有什么联系,通常, Pair<S> 与 Pair<T> 没有联系"

如:Integer是Number的子类
List<Integer> ss = new ArrayList<>();
List<Number> nn= ss; "// Error"

B:"数组和泛型有所不同:,
假设Foo是Bar的一个子类型(子类或者子接口),那么 Foo[]【依然是】Bar[] 的子类型;
但 G<Foo> 【不是】 G<Bar> 的子类型"
Manager[] managerBuddies = { ceo, cfo };
Employee[] employeeBuddies = managerBuddies; // OK

22泛型的通配符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
A:泛型的通配符
"为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号(?),
将一个问号作为类型实参传给List集合,写作:List<?> 意思是元素类型未知的List 。
这个问号(?)被称为【通配符】,它的元素类型可以【匹配任何类型】(Object 类型)"

注意:
"这种带通配符的List仅表示它是【各种泛型List】的【父类】,
【不能】把【元素】加入到其中,因为其类型是无法确定的"
但是程序【可以调用】get() 方法来返回List<?>集合指定索引处的元素,
其返回值是一个未知类型,但可以肯定的是,它总是一个【Object类型】
如:
List<?> c = new ArrayList<>();
"//下面程序引起编译错误"
c.add(new Object());"//error"

——————————————————————————————————————————————————————————————————————————————————————————

/*
* 泛型的通配符
*/
public class GenericDemo {
public static void main(String[] args) {
ArrayList<String> array = new ArrayList<String>();

HashSet<Integer> set = new HashSet<Integer>();

array.add("123");
array.add("456");

set.add(789);
set.add(890);

iterator(array);
iterator(set);
}
"/*
* 定义方法,可以同时迭代2个集合
* 参数: 怎么实现 , 不能写ArrayList,也不能写HashSet
* 参数: 或者共同实现的接口
* 泛型的通配,匹配所有的数据类型 ?
*/"
public static void iterator(Collection<?> coll){
Iterator<?> it = coll.iterator();
while(it.hasNext()){
//it.next()获取的对象是什么类型,就得到什么类型
System.out.println(it.next());
}
}
}

——————————————————————————————————————————————————————————————————————————————————————————

B:设定类型通配符的上限
" List<? extends Shape> 是受限制通配符的例子,此处的问号(?)代表一个未知的类型,就像前面看到的通配符一样。
但是此处的这个未知类型【必须】是【Shape的子类型(也可以是Shape本身)】,因此可以
把Shape称为这个【通配符的上限】(upper bound)"
类似地,
"由于程序【无法确定】这个受限制的通配符的【具体类型】,
所以【不能】把【Employee对象或其子类的对象】加入这个泛型集合中。
例如,下面代码就是【错误】的。"
public static void iterator_func(ArrayList<? extends Employee> list){
list.add(new Manager());"//error"
}

"————————————————————————————————
extends通配符的缺陷
————————————————————————————————"
"当使用 extends 通配符时,我们【无法】向其中【添加】【任何对象】。
【只能】从中【取出】对象"
例如:Apple 类 和 Orange 类 继承自 Fruit类,Fruit类继承自 Food类
Plate<? extends Fruit> plate = new Plate<Apple>();
plate.add(new Apple()); "//Compile Error"
plate.get(); // Compile Success

因为编译时只看父类,运行时看子类,在我们还【未具体运行】时,
1)进行"添加操作"时:
"JVM 并不知道我们要往plate里【添加】什么对象,只知道添加的是Fruit的【子类】,【无法确定】"
2)而执行"取出操作"时,
JVM 知道结果"一定是父类Fruit的子类,可以做自动【向上转型】,用【父类的变量】接收【子类对象】"

即:Fruit apple = plate.get();//多态的向上转型
当然:
如果考虑" Fruit类继承自 Food类",还可以"继续向上转型"
即:Food food = plate.get();//多态的向上转型
同样:Object food = plate.get();
上述3种 赋值 都是"正确"的。



举例:
"方法参数: 控制,可以传递Employee对象,也可以传递Employee的子类的对象"
public static void iterator_func(ArrayList<? extends Employee> list){
Iterator<? extends Employee> it = list.iterator();
while(it.hasNext()){
Employee e = it.next();
e.work();
}
}
}
——————————————————————————————————————————————————————————————————————————————————————————

C:设定类型形参的上限
Java泛型不仅允许在使用通配符形参时设定上限,"而且可以在定义类型形参时设定上限,
用于表示传给该类型形参的实际类型要么是该上限类型,要么是该上限类型的子类。"
下面程序示范了这种用法。
public class GenericDemo2<T extends Number> {
T nn;
public static void main(String[] args) {
GenericDemo2<Integer> ii= new GenericDemo2<>();
GenericDemo2<Double> dd = new GenericDemo2<>();

"//下面代码将引发编译异常,下面代码试图把String类型传给T形参
//但String不是Number的子类型,所以引起编译错误"
GenericDemo2<String> ss = new GenericDemo2<>();//error
}
}

——————————————————————————————————————————————————————————————————————————————————————————
D:设定类型形参的下限
" List<? super Type>表示传入的未知类型【必须】是【Type本身,或者Type的父类型】"

例如:
Apple 类 和 Orange 类 继承自 Fruit类,
Plate<? super Apple> plate = new Plate<Fruit>();

Plate<? super Apple> plate = new Plate<Object>();

上面的声明都是对的,因为 Object 是任何一个类的父类,而 Fruit类 是 Apple类 的父类。



"————————————————————————————————
super通配符的缺陷
————————————————————————————————"
<? super T>
对于使用了 super 通配符的情况,我们"只能【存入】【 T 类型】及【 T 类型的子类】对象"
"【取出】的时候【必须】用【 Object 类型】的属性指向取出的对象"
例如:
Apple 类 和 Orange 类 继承自 Fruit类,Fruit类继承自 Food类

Plate<? super Fruit> plate = new Plate<>();
plate.add(new Fruit());
plate.add(new Apple());
plate.add(new Food()); "//Compile Error"

plate 指向的具体类型可以是任何 "Fruit类及其父类",JVM 在编译的时候肯定"无法判断具体是哪个类型"
但 JVM 能确定的是,
"任何 Fruit 的子类【都可以】自动【向上转型】为 Fruit 类型"
"但任何 Fruit 的父类都【无法直接】转为 Fruit 类型,【只能】【向下强转】"
所以 "只能存入 T 类型及 T 类型的【子类】对象""T 类型的子类对象 能自动 【向上转型】为T 类型"


Object object = plate.get();
Fruit fruit = plate.get(); "//Error"
Food food = plate.get(); "//Error"
plate 指向的具体类型可以是任何 "Fruit类及其父类",
接收类型是不确定的,"只能用Object 类型才能正确接收"


——————————————————————————————————————————————————————————————————————————————————————————
总结:
PECS(Producer Extends, Consumer Super)原则
extends 和 super 通配符的使用和限制:

对于 "extends 通配符",我们"【无法】向其中【加入】任何对象",但是"我们可以进行正常的取出"
对于 super 通配符,我们"可以存入 【T 类型】对象或 【T 类型的子类】对象"
但是我们取出的时候"【只能】用【 Object 类】变量指向取出的对象"

从上面的总结可以看出,"extends 通配符""偏向于内容的获取",而 super 通配符更"偏向于内容的存入"

我们有一个 PECS 原则(Producer Extends Consumer Super)很好的解释了这两个通配符的使用场景。

Producer Extends 说的是当你的情景是"生产者类型",需要"获取资源"以供生产时,
我们建议使用" extends 通配符",因为使用了 extends 通配符的类型更适合获取资源。

Consumer Super 说的是当你的场景是"消费者类型",需要"存入资源"以供消费时,
我们建议使用 "super 通配符",因为使用 super 通配符的类型更适合存入资源。

但如果你"既想存入,又想取出",那么你最好还是不要使用 extends 或 super 通配符。

23泛型的限定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
A:泛型的限定
" /*
* 将的酒店员工,厨师,服务员,经理,分别存储到3个集合中
* 定义方法,可以同时遍历3集合,遍历三个集合的同时,可以调用工作方法
*/"
import java.util.ArrayList;
import java.util.Iterator;
public class GenericTest {
public static void main(String[] args) {
//创建3个集合对象
ArrayList<ChuShi> cs = new ArrayList<ChuShi>();
ArrayList<FuWuYuan> fwy = new ArrayList<FuWuYuan>();
ArrayList<JingLi> jl = new ArrayList<JingLi>();

//每个集合存储自己的元素
cs.add(new ChuShi("张三", "后厨001"));
cs.add(new ChuShi("李四", "后厨002"));

fwy.add(new FuWuYuan("翠花", "服务部001"));
fwy.add(new FuWuYuan("酸菜", "服务部002"));

jl.add(new JingLi("小名", "董事会001", 123456789.32));
jl.add(new JingLi("小强", "董事会002", 123456789.33));

// ArrayList<String> arrayString = new ArrayList<String>();
iterator(jl);
iterator(fwy);
iterator(cs);

}
"/*
* 定义方法,可以同时遍历3集合,遍历三个集合的同时,可以调用工作方法 work
* ? 通配符,迭代器it.next()方法取出来的是Object类型,怎么调用work方法
* 强制转换: it.next()=Object o ==> Employee
* 方法参数: 控制,可以传递Employee对象,也可以传递Employee的子类的对象
* 【泛型的限定】 本案例,父类固定Employee,但是子类可以无限?
* ————————————————————————————————————————————————————————————————————
* ? extends Employee 限制的是【父类】, 【上限限定】, 可以传递Employee,传递他的【子类】对象
* ————————————————————————————————————————————————————————————————————
* ? super Employee 限制的是【子类】, 【下限限定】, 可以传递Employee,传递他的【父类】对象
*/"
public static void iterator(ArrayList<? extends Employee> array){

Iterator<? extends Employee> it = array.iterator();
while(it.hasNext()){
//获取出的next() 数据类型,是什么Employee
Employee e = it.next();
e.work();
}
}
}
-------------本文结束感谢您的阅读-------------
感谢您的支持,我会继续努力的!
0%