1、泛型就是广泛的类型,在定义类的时候,某些方法的参数列表或者返回值类型不确定,就使用一个符号,来表示那些尚未确定的类型,这个符号就是泛型。
2、使用:对于具有泛型的类型,在类型后面加上<>,<>中写上泛型的确定类型
3、
泛型必须书写成引用数据类型,不能写基本数据类型
泛型是写的时候,必须保持前后一致
左必写,右可选择不写
public class Dome {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();//创建一个数组并且泛型定义为String
list.add("abc");
list.add("def");
list.add("ghi");
list.add("jkl");
list.add("mno");
list.add("pqr");
Iterator<String> it = list.iterator();//迭代器也必须是String
while(it.hasNext()) {
String str = it.next();
System.out.println(str.length());
}
}
}
泛型类的定义
1、格式:class 类名<泛型1,泛型2,泛型3…>{ }
2、说明:
(1)类后面跟着的泛型类型,是泛型的声明,一旦泛型声明出来,就相当于这个类未来会有一个已知类型,这个类型就可以在类中去使用了
(2)泛型类型的声明:只要是一个合法的标识符即可,一般使用单个的大写字母表示:T(Type)、E(Element)、K(Key)、V(Value)
(3)泛型确定的时机:将来使用这个类创建对象的时候,对象的泛型必须确定为具体的类型,对象的泛型确定为什么类型,类的泛型就跟着变成什么类型
public class Dome {
public static void main(String[] args) {
Student<String> s = new Student<String>();
s.show("123");
s.test(null);
}
}
class Student<T>{
public void show(T t) {
System.out.println(t);
}
public T test(T t) {
return null;
}
}
模拟栈
package ceshi01;
import java.util.LinkedList;
public class Dome {
public static void main(String[] args) {
MyStack<String> ms = new MyStack<>();
ms.show("aaa");
ms.show("bbb");
ms.show("ccc");
ms.show("ddd");
ms.show("eee");
System.out.println(ms);
while(ms.cap() > 0) {
System.out.println(ms.test());
}
}
}
class MyStack<T>{
//创建一个栈对象使用链表,因为双链表模拟进栈弹栈
LinkedList<T> l = new LinkedList<>();
public void show(T t) {//进栈方法
l.addFirst(t);
}
public T test() {//弹栈方法,弹出的内容作为返回值
return l.removeFirst();
}
public int cap() {//计算集合的长度
return l.size();
}
}
泛型方法的定义
格式
权限修饰符<泛型1,泛型2,…> 返回值类型 方法名称(参数列表){方法体 }
3、说明:
(1)在方法上声明泛型,可以在整个方法中作为已知类型来使用
(2)如果【非静态】方法没有声明任何泛型,可以直接使用类的泛型,此时泛型就会和类泛型确定时机一样,随着对象泛型的确定而确定;
如果【非静态】方法上自己定义了泛型,泛型就会随着参数的类型进行变化
(3)【静态】方法,不能使用类的泛型,如果想要使用泛型,必须自己在方法声明上定义泛型。因为类泛型随对象确定,静态优先于对象存在,静态方法使用泛型的时候,对象很可能还未创建,泛型极有可能还不确定。
package ceshi01;
import java.util.LinkedList;
public class Dome {
public static void main(String[] args) {
Person<String> p = new Person<>();
p.show3("qwe");
Person<Integer> p1 = new Person<>();
p1.show3("qwe");
System.out.println(p1.show4(123));
}
}
//1.方法使用类的泛型
//2.方法自己定义泛型
//1.非静态方法
//2.静态方法
class Person<T> {
//1.方法使用类的泛型
public void show1(T t) {
System.out.println(t);
}
public T show2() {
return null;
}
//----------------
//2.方法自己定义泛型
//2.1 非静态方法自己定义泛型
public <E> void show3(E e) {//Object
System.out.println(e);
}
public <E> E show4(E e) {//Object
return e;
}
public <E> void show5(E e, T t) {
}
//2.2 静态泛型方法
//类泛型随着对象的泛型确定而确定,静态方法随着类的加载而加载优先于对象存在
//如果静态方法使用类的泛型,对象就极有可能不存在,泛型也就确定不下来
//所以静态方法要想使用泛型,只能自己定义
public static <E> void show6(E e) {
System.out.println(e);
}
}
交换
package ceshi01;
import java.util.Arrays;
public class Dome {
public static void main(String[] args) {
String[] arr = {"aaa", "bbb", "ccc", "ddd"};
//Integer[] arr = {111, 222, 333, 444};
System.out.println(Arrays.toString(arr));
swap(arr, 0, 3);
System.out.println(Arrays.toString(arr));
}
/**
* 交换数组两元素的位置
*
* @param arr 待交换元素位置的数组
* @param index1 要交换位置的元素索引
* @param index2 要交换位置的元素索引
*/
public static <T> void swap(T[] arr, int index1, int index2) {
T temp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = temp;
}
}
泛型接口的定义和使用
格式:
interface 接口名<泛型1, 泛型2, 泛型3…>{ }
3、说明:
(1)在接口声明上,定义好泛型,整个接口中都可以将接口的泛型拿来使用
(2)泛型接口被其他类实现:
1)类实现接口的时候,泛型确定为了具体类型:
class 类名 implements 接口名<具体类型> {
实现接口的方法,方法使用了泛型,也都成了具体类型
}
2)类实现接口的时候,泛型依然不确定:
class 类名<泛型1, 泛型2…> implements 接口名<泛型1, 泛型2…> {
实现接口的方法,方法使用的泛型依然不确定
}
注意事项:类后面和接口后面的泛型符号要保持一致,表示同一个泛型;类实 现接口的时候,和原接口的泛型符号可以不一样
泛型的通配符
1、符号:?
2、使用泛型的时候,没有使用具体的泛型声明【T】,而是使用了和T有关的类型,如果要表示和T有关的类型,就需要使用到泛型通配符【?】
3、第一种形式:使用?来表示可以使任意类型
在Collection中,removeAll(Collection<?> c)方法的参数,就表示可以接受任意类型泛型的集合,参数集合的泛型可以和调用者集合的泛型没有任何关系
package com.offcn.demos;
import java.util.ArrayList;
import java.util.Collection;
public class Demo07 {
public static void main(String[] args) {
//removeAll(Collection<?> c)
Collection<String> list = new ArrayList<>();
Collection<Integer> list1 = new ArrayList<>();
//假设调用者集合的泛型是A类型,参数集合的泛型可以是任意类型,此时?的作用就是通配
list.removeAll(list1);
}
}
3、第二种格式:? extends E
在Collection集合中,方法addAll(Collection<? extends E> c)泛型所表示的就是:确定泛型的上边界。即:参数泛型是调用者泛型的本类或者子类,不能是父类,更不能是无关类
package com.offcn.demos;
import java.util.ArrayList;
import java.util.Collection;
public class Demo07 {
public static void main(String[] args) {
//addAll(Collection<? extends E> c)
Collection<String> list = new ArrayList<>();
Collection<Integer> list1 = new ArrayList<>();
Collection<Object> list2 = new ArrayList<>();
Collection<String> list3 = new ArrayList<>();
//list.addAll(list1);//String和Integer是无关类
list.addAll(list2);//参数的泛型是调用者的泛型的父类
list2.addAll(list1);//参数泛型是调用者泛型的子类
list2.addAll(list);
list.addAll(list3);//参数泛型和调用者泛型是同类
}
public static void test1() {
//removeAll(Collection<?> c)
Collection<String> list = new ArrayList<>();
Collection<Integer> list1 = new ArrayList<>();
//假设调用者集合的泛型是A类型,参数集合的泛型可以是任意类型,此时?的作用就是通配
list.removeAll(list1);
}
}
4、第三种情况:? super E
确定泛型的下边界:表示泛型是E的父类或者是E本身,不能是E的子类,更不能是无关类。
Set
1、Set是Collection的子接口
2、特点:无序,不可重复
3、实现类:
1、HashSet,底层是哈希表
2、LinkedHashSet,底层是链表加哈希表
4、存储特点:存取顺序不一致,不可重复
package ceshi01;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
public class Dome {
public static void main(String[] args) {
//创建对象的时候,创建的是实现类的对象
Set s1 = new HashSet();
Set s2 = new LinkedHashSet();
s1.add("a");
s2.add("b");
syso
}
}
Set集合的遍历
1、没有自己特有的方法,使用Collectino中的方法
2、第一种:toArray()转数组,遍历数组
3、第二种:T[ ] toArray(T[ ] a) 转数组,遍历
(1)当数组的长度等于元素个数时,就是用提供的数组
(2)当数组的长度小于元素个数时,底层创建新的数组存储集合元素
(3)当数组长度大于元素个数时,依然使用提供的数组,但是数组剩余的部分就按照默认值填充
4、迭代器
5、增强for循环
for(元素的数据类型 元素名称: 要遍历的集合或者数组) {
使用元素名称操作元素
}
(2)说明:增强for循环的底层是迭代器,如果增强for循环遍历几个的过程中,使用了集合对象修改集合,会出现并发修改异常
package ceshi01;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
public class Dome {
public static void main(String[] args) {
Set<Integer> set = new HashSet<>();
set.add(0);
set.add(0);
set.add(324);
set.add(324);
set.add(11);
set.add(11);
set.add(-97);
firstWay();//第一种:toArray()转数组,遍历数组
secondWay(set);//第二种:T[ ] toArray(T[ ] a) 转数组,遍历
thirdWay(set);//迭代器
public static void firstWay() {
Set<Integer> set = new HashSet<>();
set.add(0);
set.add(0);
set.add(324);
set.add(324);
set.add(11);
set.add(11);
set.add(-97);
//1.调用toArray()方法,将集合转为数组
Object[] arr = set.toArray();
//2.遍历数组
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
public static void secondWay(Set<Integer> set) {
/*
* 1.当数组的长度等于元素个数时,就是用提供的数组
* 2.当数组的长度小于元素个数时,底层创建新的数组存储集合元素
* 3.当数组长度大于元素个数时,依然使用提供的数组,但是数组剩余的部分就按照默认值填充
*
* */
//1.准备数组
//Integer[] arr = new Integer[set.size()];
//Integer[] arr = new Integer[3];
Integer[] arr = new Integer[10];
//2.调用toArray(T[] arr)
Integer[] arr1 = set.toArray(arr);
//3.遍历数组
for (int i = 0; i < arr1.length; i++) {
System.out.print(arr1[i] + " ");
}
System.out.println(arr);
System.out.println(arr1);
}
public static void thirdWay(Set<Integer> set) {
//1.获取集合中的迭代器对象
Iterator<Integer> it = set.iterator();
//2.迭代器遍历集合
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
LinkedHashSet
1、是HashSet的一个子类,和HashSet保证元素唯一性的原理相同
2、和HashSet的不同之处在于:存取顺序一致
3、特点:有序,不可重复
4、应用:既要保证顺序,又要去重的时候,可以考虑使用
LinkedHashSet<String> set = new LinkedHashSet<>();