java基础(九)

这次记录的是泛型和容器,有点相爱相杀的感觉。

泛型

在写泛型,我们先回忆八大基本数据类型和引用数据类型。它们都可以做参数的数据类型和方法的返回类型。有没有一种数据类型可以解决重载时代码的冗余呢?那就是我们最喜欢的Object类,它是所有类的超类(父类),根据多态的特性,很好解决了重载代码的冗余,我们先实验以下。

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
public class OTest {
public static void main(String[] args) {
T t = new T(12); //存入整数12
System.out.println(t.getO());
int a = (Integer)t.getO(); //多态的实例,向下转型,所以要用Integer类
System.out.println(a);

t = new T("Hello"); //存入字符串Hello
System.out.println(t.getO());
String str = (String)t.getO(); //也要转型为String类
System.out.println(str);

}
}
class T{
private Object o; //把Object当做数据类型使用

public T() {
super();
}

public T(Object o) {
this.o = o;
}

public Object getO() {
return o;
}
}

我们在放数据的时候挺轻松的,整数,字符,布尔,小数,往里放就行,但是,取数据却要每次从Object类转为相应数据类型的包装类。如果我定义的是这个数组呢,一千一万,每次取都要很麻烦,浪费时间。

所以就有了泛型,不仅解决了重载代码的冗余,也解决了类型转换的问题,也定义了一种规范吧。

泛型类

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
public class genericTest {

public static void main(String[] args) {

generic g = new generic(12);
int a = (int)g.getNum(); //因为这里的泛型没有定义具体类型
//一样也要转型,但可以用基本数据类型

g = new generic("Hello");
String str = (String)g.getNum();
}
}

class generic<T> //定义一个泛型类,< >里可以填任意值,可以自己定义
{ //常用的有T(Type),E(Element),K(Key),V(Value)
private T num; //把Object改为 T

public generic() {
super();
}

public generic(T num){ //修改参数类型
this.num = num;
}

public T getNum() { //修改返回类型
return num;
}
}

可以看到,我们只是在类加了,把Object修改为T,就变成了泛型类。当然,由于没有具体指明泛型具体参数,所以也可以乱放数据,也要转型为具体数据类型。下面就是泛型规范化的使用。

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
public class genericTest {

public static void main(String[] args) {

//generic<Integer> g = new generic<Integer>(12);
//generic<Integer> g = new generic<>(12);
generic<Integer> g = new generic(12); //上面是泛型声明的变化,都可以使用,但里面填的也是包装类
int a = g.getNum(); //不用在转型
//g = new generic("hello"); //报错

}
}

class generic<T>
{
private T num;

public generic() {
super();
}

public generic(T num){
this.num = num;
}

public T getNum() {
return num;
}
}

泛型抽象类和接口

现在想想,既然有泛型类,那么抽象类、接口呢?它们也是类,所以它们自然而然也可以泛型化。简单带过了。

1
2
3
4
5
6
7
8
9
10
interface InterGeneric<T>{						//泛型接口

}
abstract class absGeneric<T> implements InterGeneric<T>{ //泛型抽象类实现泛型接口

}
class generic<T> extends absGeneric<T> //泛型类继承泛型抽象类
{

}

泛型方法

泛型方法一定要注意你使用的是哪个,类定义的类型,还是方法新定义的类型,而方法新定义的类型可以为任意类型,不受约束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class genericTest {

public static void main(String[] args) {
generic<Integer> g = new generic(); //定义泛型为integer型
g.printNa(12); //因为printNa()为新的类型,所有可以输入其他数据类型
g.printNa("12");
System.out.println(g.printName(25));
//System.out.println(g.printName("hw")); //报错,因为类定义的类型是integer。

}
}

class generic<T>
{

public <T>void printNa(T name) //<T>声明这里的泛型T为全新的类型
{ //因为类没有声明,而且不受类定义泛型类影响
System.out.println(name);
}

public T printName(T name) { //这里的T受类影响,如果修改为<T>T,则不受类影响
return name;
}
}

泛型还有一个通配符?和其他注意点,但没弄明白……

容器

我们之前也接触过容器,那就是数组,数组是有序列表,有相同的数据类型。今天我们学的是其他容器,Collection和Map。

Collection

1
public interface Collection<E> extends Iterable<E>

Collection实现了Iterable(迭代器)接口,可以看到,它们都是泛型类,而被继承的也都是泛型。Iterable本身最主要定义了forEach循环,迭代和循环也没多少差异。

而Collection又被哪些继承呢?

可以看到,主要的有抽象类和接口,而这些可以细分为三类List(链表)、Set(集合)、Queue(队列)。这也就是Collection所学的。

List

List被实现的主要类有ArrayList和Vector,它们都是单链表,而且方法都差不多。只是,它们和StringBuffer和StringBulder一样,Vector是线程安全的类。而它们都是单链表,学过数据结构应该都知道,还要双向链表、循环链表等等其他复杂的链表。

ArrayList

顾名思义:数组链表。它是由数组实现的。看看以下原代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private static final int DEFAULT_CAPACITY = 10;

transient Object[] elementData; // non-private to simplify nested class access

public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}

public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

第一块是整型常量,大小为10。第二块定义一个Object[]数组,那个transient不知道谁啥,但也不影响我们看代码。再看最后两个构造方法。如果使用的是默认构造方法或给的值为0,则Object数组大小为10,或者指定它的大小,但小于0,抛出异常。

以下则是声明ArrayList,容量为10

1
2
3
4
5
6
7
8
9
10
11
import java.util.ArrayList;		//别忘了引入相应的包
import java.util.List;

public class CollectionTest {
public static void main(String[] args) {
List list1 = new ArrayList(); //多态的特性
List list2 = new ArrayList(0);
List list3 = new ArrayList(10);
ArrayList list4 = new ArrayList(10);
}
}

如果把容器大小变为-10,则编译报错

学容器或学数据结构或java学的是啥,都是为了数据的增删改查。

增加元素,链表在C语言的数据结构中,可以在链表范围内随意增加一个元素,那么链表如何呢?

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
import java.util.ArrayList;
import java.util.List;

public class CollectionTest {
public static void main(String[] args) {
List list = new ArrayList(5); //定义链表长度为5
list.add("Hello"); //添加元素
list.add(13); //因为没指定链表的具体数据类型,可以任意添加
list.add(true);
list.add(new CollectionTest());

System.out.println("链表中元素长度(个数):"+list.size()); //打印链表的元素长度

for(Object l:list) //前面写过的foreach循环,因为没指定具体类型,用Object
{
System.out.println(l);
}
System.out.println("-------------------");
list.add(1,"java"); //也可以在指定位置加元素
list.add(2,"!"); //链表就是高级数组,下标也是从0开始

for(Object l:list)
{
System.out.println(l);
}
System.out.println("链表中元素长度(个数):"+list.size());
}
}

我们可以发现,定义带链表长度是5,但是我们输入了六个元素,没有报数组越界,那是因为在ArrayList中,每次添加一个元素,都进行了判断,如果超过指定的范围,则给它的范围扩容,从新copy一个新数组给原有的数组。

1
2
int newCapacity = oldCapacity + (oldCapacity >> 1);
elementData = Arrays.copyOf(elementData, newCapacity);

这是它扩容的其中两行代码,每次扩容原数组的一半,把值复制给扩容后的数组,但变量名还是原数组名。

在链表中,没有删除这个方法,只有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
27
28
29
30
31
32
33
34
35
import java.util.ArrayList;
import java.util.List;

public class CollectionTest {
public static void main(String[] args) {
List list = new ArrayList(5);
list.add("Hello");
list.add(13);
list.add(true);
list.add(new CollectionTest());
list.add(15);
list.add(3);
for(Object l:list) System.out.println(l);
/*
* Hello
* 13
* true
* io.github.java09.CollectionTest@7852e922
* 15
* 3
*/

list.remove(3); //把下标为3移除,对象没了
list.remove(Integer.valueOf(3)); //把值为3移除
list.remove("Hello"); //把"Hello"移除
for(Object l:list) System.out.println(l);
/*
* 13
* true
* 15
*/
list.clear(); //清空链表
System.out.println(list.isEmpty()); //链表是否为空, true
}
}

链表添加到某个位置的元素和删除某个位置的元素,都是在数组里“整体来回移动”得到的,也就是顺序表。所以它修改和查找某个元素是最方便的

修改和查找提下方法

1
2
set(index, element) 设置元素值,下标名,新的元素值。
get(int index) 获取元素值,下标名

我们还应该遍历元素,有for循环遍历,也有容器特有的迭代器遍历。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
for(int i=0; i<list.size(); i++) {
System.out.println(list.get(i));
}

int k=0;
while(k<list.size())
{
System.out.println(list.get(k++));
}

for(Object l:list) System.out.println(l);

Iterator it = list.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}

for(Iterator it2 = list.iterator();it2.hasNext();) {
System.out.println(it2.next());
}

ArrayList就讲那么多,而Vector的方法很多都一样,无非加了线程安全。当然还有其他方法,很多方法Arrays就接触过,虽然可能结果不同,但意思是差不多的。

Stack

Stack是Vector的一个子类,我们称为栈,栈的特性就是先进后出,后进先出,就像羽毛球桶一样,先放进去的最后拿出来。理解它,递归也就差不多想通了。

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
import java.util.List;
import java.util.Stack;

public class CollectionTest {
public static void main(String[] args) {

List<Integer> s = new Stack(); //多态的特性,和泛型具体化
StackInteger> stack = (Stack)s; //但我们想使用栈的方法,向下转型
stack.push(1); //stack的入栈方法
stack.add(2); //入栈可以用List中的add()
stack.push(3);
stack.push(4);
System.out.println(stack.pop()); //出栈,并返回值, 打印 4
stack.push(5);
stack.push(6);
stack.add(7);
stack.peek();
//stack.add(1, 8); //这就不是很栈,能用,但不是栈该有的特性
//stack.remove(Integer.valueOf(0)); //如果用这两种方法,栈就没存在的必要,不建议使用

while(stack.isEmpty()==false) //如果栈不为空,继续循环 isEmpty()是为空为true
{
System.out.print(stack.pop()+" "); //7 6 5 3 2 1
}
}
}

Queue

Queue是一个接口,也继承了List接口,而实现它全部方法的类主要有LinkedList(一开始找队列的类没找到,没想到在java名字不一样),我们常用的队列就是LinkedList。它的特点是先进先出,后进后出,就像买票一样,先到先得。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class CollectionTest {
public static void main(String[] args) {

List<Integer> q = new LinkedList(); //具体化泛型
Queue<Integer> queue = (LinkedList)q;
queue.add(1);
queue.offer(2); //入队方法
queue.offer(3);
System.out.println(queue.poll()); //出队方法,并返回一个值, 1
System.out.println(queue.peek()); //出队,返回 2
queue.offer(4);
queue.offer(5);
while(!queue.isEmpty()) {
System.out.print(queue.poll()+" "); //2 3 4 5
}
}
}

Set

集合,在集合里元素是无序的,不能有重复的值。而链表则是有序,可以有重复的值。因为继承的是在是太多了,这里找了个HashSet类学习。

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
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class CollectionTest {
public static void main(String[] args) {

Set s = new HashSet();
s.add(14);
s.add("14");
s.add(12);
s.add("Hello");
s.add("12");
s.add("Hello");
s.add(12);
s.add("He"+"llo");
s.add(new String("Hello")); //即使new一个新的对象Hello,但比较的还是值
s.add(12.3456);
s.add(12.3456);
s.add(12.31+0.0356); //出现误差打印 12.345600000000001

//因为没有误差,存入的还是第一次出现的12.3456
s.add((new BigDecimal("12.31").add(new BigDecimal("0.0356"))).doubleValue());

Iterator it = s.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
/*
* 12 //第一个整型12
* 14
* Hello //第一个Hello
* 12 //字符串12
* 12.3456 //浮点
* 14
* 12.345600000000001 //有误差
*/

}
}

可以看出,我们存入的和打印出来的位置不一样,因为在Set是无序的,也不知道它具体怎么排列的,好像是字符串优先。
而且,如果存入的数据类型和值一样,则集合只存一个,即使它们地址不同值相同,也只存一个值。

Map

Map原本的意思是地图,当然,在这不是这个翻译,它翻译为映射。它里面有两个泛型,一个是键(Key),另一个是值(Value),就好像我们的书或字典,键就是目录的序列,值就是该序列下具体的内容。所以有的语言也称它为字典。

HashMap

应该是最常用的Map,也是最基础的,它里面有两个值<Key,Value>。我们通过找Key(键),来获取(Value)值,键在值在,键亡值亡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.HashMap;
import java.util.Map;

public class MapTest {
public static void main(String[] args) {
Map map = new HashMap(); //声明Map
map.put("Java基础1", "初学数据类型"); //插入元素
map.put("Java基础2", "基本语句与数组");
map.put(3, "方法的调用");
map.put(12,13);
System.out.println(map.get("Java基础1")); //打印 初学数据类型
map.remove(12); //移除键为12和它的值13
System.out.println(map.get(12)); //打印 null,因为这里泛型没定义,为Object类的数据类型,空为null
}
}

看到在容器这里,应该就能体现面向对象的优越性,因为子继承父类,而父类可能继承其他类或其他接口,这样下来,子类很多方法的用法都是差不多的。会一个,其他看看就会用了。

而泛型呢?一开始说相爱相杀呢?无论是Collection还是Map<K,V>,它们的作用就是增删改查。而我们的数据类型有八种基本数据类型和3种引用类型共11种。如果重写方法,那代码量太冗余了。而泛型则减轻了代码,还能避免取值转型问题,也提供了规范性,你要整数就定义整型,字符串就定义字符串,乱放就默认,让其他开发人员知道,这个到底直接用,还是判断数据类型,提高了开发效率。

泛型和容器的记录就到这,有错请在下方提出问题。

谢谢您对我的支持
0%