一、泛型
(一)泛型的概述
1、泛型:广泛的类型,在定义类的时候,某些方法的参数列表或者返回值类型不确定的时候,就是用一个符号来表示那些尚未确定的类型
2、格式
ArrayList<String> list = new ArrayList<>();
3、泛型的好处
(1)提高了数据的安全性,将运行期的问题提前到编译期
(2)避免了强转的麻烦
4、注意事项
(1)泛型必须书写为引用数据类型
(2)泛型书写的时候,前后必须保持一致
(3)
JDK7
以后赋值符号右侧的泛型可以省略
import java.util.ArrayList;
import java.util.Iterator;
public class Demo01_Use {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("qwe");
list.add("asd");
//list.add(123);
//list.add('r');
list.add("ert");
list.add("ghj");
list.add("^&*");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String next = it.next();
System.out.println(next);
}
}
}
(二)泛型类
1、概述:带泛型的类
2、格式
class 类名<泛型1, 泛型2, 泛型3...> {
}
3、说明
(1)类后面跟着的泛型类型是泛型的声明,一旦泛型声明出来,就相当于未来会有一个已知类型,这个类型就可以在类中使用
(2)泛型类型的声明:只要是一个合法标识符即可,一般使用单个的大写字母表示:T
(
Type
)、
E (Element
)、
K
(
Key
)、
V
(
Value
)
public class Demo02_Class {
public static void main(String[] args) {
MyClass<String> mc = new MyClass<>();
MyClass<Integer> mc2 = new MyClass<>();
mc.test01("qwe");
System.out.println(mc.test02());
}
}
class MyClass<T> {
public void test01(T t) {
System.out.println(t);
}
public T test02() {
return null;
}
}
(三)泛型方法
1、在方法的声明上,带上泛型,就是泛型方法
2、格式
修饰符 <泛型1, 泛型2, 泛型3...> 返回值类型 方法名(参数列表) {
}
3、注意事项:
(1)在方法上声明泛型,可以在整个方法中使用,出了这个方法就没有用了
(2)如果【非静态方法】没有声明任何泛型,可以直接使用类的泛型,此时泛型类型的确定与创建对象时的泛型类型保持一致即可;如果【非静态方法】自己定了泛型,泛型就会随着参数的类型进行变化
(3)【静态方法】不能使用类的泛型,只能自己定义泛型,然后在当前静态方法中进行使用
import com.hqyj.homework.Student;
public class Demo03_Method {
public static void main(String[] args) {
MyClass01<String> mc01 = new MyClass01<>();
mc01.test03("qwe");
mc01.test03(123);
mc01.test05("qwe", "asd");
mc01.test05(123, "asd");
MyClass01.test06(new Student());
}
}
class MyClass01<T> {
//1、方法的参数列表为泛型
public void test01(T t) {
}
//2、方法的返回值类型为泛型
public T test02() {
return null;
}
//方法自己定义泛型
public <E> void test03(E e) {
}
public <E> E test04() {
return null;
}
public <E> void test05(E e, T t) {
//方法使用自己泛型的同时还可以使用类的泛型
}
//静态方法使用泛型
//静态方法只能使用自己的泛型
/**
* 泛型类随着对象的创建而确定泛型,静态方法优先于对象存在
* 如果静态方法使用类的泛型,对象就极有可能不存在,泛型类型就不能确定
* 所以静态方法要使用泛型就需要自己定义
*/
public static <E> void test06(E e) {
}
public static <E> E test07() {
return null;
}
}
(四)练习
定义一个方法,可以交换任意数据类型数组的两元素位置
import java.util.Arrays;
public class Demo04_Exercise {
/**
* 定义一个方法,可以交换任意数据类型数组的两元素位置
*/
public static void main(String[] args) {
//Integer[] arr = {23, 56, 78, 42, 67, 41, 89, 54};
String[] arr = {"23", "we", "rt", "sd", "gh", "xc", "ui"};
System.out.println(Arrays.toString(arr));
swap(arr, 2, 5);
System.out.println(Arrays.toString(arr));
}
public static <T> void swap(T[] arr, int index1, int index2) {
T temp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = temp;
}
}
(五)泛型接口
1、概述:带泛型的接口
2、格式
interface 接口名<泛型1, 泛型2, 泛型3...> {
}
3、说明
(1)在接口的声明上,定义好泛型,整个接口中都可以使用该泛型
(2)泛型接口被其它类实现
类实现接口的时候,接口的泛型确定为具体类型
class 类名 implements 接口名<具体类型> {
实现类实现接口的方法时方法确定为具体类型;
}
类实现接口的时候,接口的泛型尚未确定
class 类名 implements 接口名<泛型1, 泛型2, 泛型3...> {
实现类实现接口的方法时方法的类型还尚未确定;
}
类实现接口的时候,接口的泛型尚未确定;类还有自己的泛型
class 类名<泛型1, 泛型2, 泛型3...> implements 接口名<泛型1, 泛型2, 泛型3...> {
实现类实现接口的方法时方法的类型还尚未确定;
类不仅可以使用自己的泛型,还可以使用接口的泛型;
}
public class Demo05_Inter {
public static void main(String[] args) {
MyClass03<String> mc = new MyClass03<>();
MyClass04<Integer, String> mc4 = new MyClass04<>();
mc4.test04(1001, "张三");
}
}
interface MyInter<T> {
public abstract void test01(T t);
public abstract T test02();
public abstract T test03(T t);
}
class MyClass05<T, String> implements MyInter<String> {
public void test04(T t, String str) {
}
@Override
public void test01(String string) {
}
@Override
public String test02() {
return null;
}
@Override
public String test03(String string) {
return null;
}
}
//类在实现接口泛型的同时,自己也有泛型,此时类中的方法既可以使用类的泛型,可以使用接口的泛型
class MyClass04<T, E> implements MyInter<T> {
public void test04(T t, E e) {
}
@Override
public void test01(T t) {
}
@Override
public T test02() {
return null;
}
@Override
public T test03(T t) {
return null;
}
}
/**
* 类实现接口泛型的时候,泛型类型没有确定
* 注意:在类的声明中,类名之后也必须跟上泛型类型
* 类名和接口名后面的泛型类型名字必须保持一致
*/
class MyClass03<T> implements MyInter<T> {
@Override
public void test01(T t) {
}
@Override
public T test02() {
return null;
}
@Override
public T test03(T t) {
return null;
}
}
//1、实现类实现接口的时候,接口泛型的类型已经确定
class MyClass02 implements MyInter<String> {
@Override
public void test01(String s) {
}
@Override
public String test02() {
return null;
}
@Override
public String test03(String s) {
return null;
}
}
(六)泛型通配符
1、符号:
?
2、使用泛型的时候,没有具体类型的泛型声明【T】,而是使用了和【T】有关的类型,如果要表示和【T】有关的类型,就需要使用泛型通配符【?】
3、第一种形式:使用【?】来表示任意类型
import java.util.ArrayList;
import java.util.Collection;
public class Demo06 {
//containsAll(Collection<?> c)
//如果此 collection 包含指定 collection 中的所有元素,则返回 true。
public static void main(String[] args) {
Collection<String> list01 = new ArrayList<>();
list01.add("qwe");
list01.add("asd");
list01.add("zxc");
list01.add("fgh");
list01.add("ert");
Collection<String> list02 = new ArrayList<>();
list02.add("qwe");
list02.add("asd");
list02.add("ggg");
System.out.println(list01.containsAll(list02));;
}
}
4、第二种形式:【
? extends E
】:确定上边界,表示参数类型是调用者类型的子类或者参数类型和调用者类型保持一致
//【? extends E】确定上边界
private static void test02() {
//addAll(Collection<? extends E> c)
//将指定 collection 中的所有元素都添加到此 collection 中(可选操作)。
Collection<Student> list01 = new ArrayList<>();
Collection<Person> list02 = new ArrayList<>();
Collection<Student> list03 = new ArrayList<>();
//list01.addAll(list02);
//参数类型是调用者类型的子类
list02.addAll(list01);
//参数类型和调用者类型保持一致
list01.addAll(list03);
}
5、第三种形式【
? super E
】:确定下边界;表示泛型是
E
的父类或者
E
泛型本身,不能是
E
类型的子类或者是无关类
public static void main(String[] args) {
//确定下边界
//fill(List<? super T> list, T obj)
//使用指定元素替换指定列表中的所有元素。
ArrayList<Student> li01 = new ArrayList<>();
ArrayList<String> li02 = new ArrayList<>();
ArrayList<Student> li03 = new ArrayList<>();
Collections.fill(li01, new Student());
Collections.fill(li02, "new Person()");
}
二、Set
(一)概述
1、
Set
是
Collection
的子接口
2、特点:不可重复:不能存储重复的元素
3、实现类:
(1)
HashSet
:底层是哈希表
(2)
LinkedHashSet
:底层是哈希表
+
链表
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
public class Demo07_Set {
public static void main(String[] args) {
Set<String> set = new LinkedHashSet<>();
set.add("asd");
set.add("qwe");
set.add("asd");
set.add("xcv");
set.add("asd");
set.add("ghj");
Iterator<String> it = set.iterator();
while (it.hasNext()) {
String next = it.next();
System.out.println(next);
}
}
}
(二)Set遍历
1、没有特有的方法,使用
Collection
接口中的方法遍历
Set
集合
2、第一种:
toArray
()
:转数组,遍历数组
3、第二种:
toArray
(T[] a)
:转数组,遍历数组
4、第三种:迭代器遍历
5、第四种:增强
for
循环遍历
(1)格式
for(元素的数据类型 元素名称 : 要遍历的集合或者数组) {
}
(2)说明:增强
for
循环的底层也是迭代器,如果使用增强
for
循环遍历的时候,使用集合进行操作,就会发生并发修改异常
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
public class Demo08_ToArray {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("asd");
set.add("qwe");
set.add("xcv");
set.add("ghj");
//先给定一个数组,将数组传进方法中,
// 当数组的长度不足时,新建数组存储集合元素;
// 当长度超出时,超出部分使用默认值进行填充
String[] arr = new String[1];
String[] array = set.toArray(arr);
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
}
}
import java.util.HashSet;
import java.util.Set;
public class Demo09_Foreach {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("asd");
set.add("qwe");
set.add("xcv");
set.add("ghj");
for (String ele : set) {
//不能在这里使用集合操作元素
//set.add("qqq");
System.out.print(ele + " ");
}
//增强for循环遍历数组
int[] arr = {12, 54, 76, 43, 98, 51, 34, 50};
for (int ele : arr) {
System.out.print(ele + " ");
}
}
}
(三)练习
随机生成10
个
20-40
之间的随机数,随机数不能重复,存储在一个集合中,遍历集合,使用
HashSet
集合
import java.util.HashSet;
import java.util.Random;
public class Demo10_Exercise {
/**
* 随机生成10个20-40之间的随机数,随机数不能重复,存储在一个集合中,遍历集合,使用HashSet集合
*/
public static void main(String[] args) {
//创建随机数生成器
Random ran = new Random();
//创建集合存储数据
HashSet<Integer> set = new HashSet<>();
while (set.size() < 10) {
int num = ran.nextInt(21) + 20;
set.add(num);
}
for (Integer ele : set) {
System.out.print(ele + " ");
}
}
}
三、HashSet去重原理
(一)存储自定义类型元素
1、某个
obj
对象将要存储进
HashSet
集合的时候,首先要计算对象的哈希码值
2、在集合中所有的哈希码值都和
obj
对象的码值不同的时候,进行存储
3、如果存在和obj
对象相同的哈希码值,并不能说明
obj
对象就存储在集合中
4、需要进一步重写
equals
方法,判断
obj
对象和集合中的对象是否相等
5、如果不相等直接存储,如果相等,进行覆盖
import java.util.HashSet;
public class Demo11_HashSet {
public static void main(String[] args) {
HashSet<Person> set = new HashSet<>();
set.add(new Person("张三", 23));
set.add(new Person("张三", 23));
set.add(new Person("李四", 23));
set.add(new Person("张三", 24));
/**
* 1、重写hashCode方法,让不同的对象尽量拥有相同的地址值
* 2、重写equals方法,比较属性值,如果相等进行覆盖,如果不相等进行存储
*/
for (Person p : set) {
System.out.println(p.hashCode());
}
}
}
(二)原理总结
1、重写重写
hashCode
方法,让不同的对象尽量拥有相同的哈希码值
2、重写
equals
方法,比较属性值,如果相等进行覆盖,如果不相等进行存储
四、Map
(一)概述
1、体系位置:双列集合的顶层接口
2、数据结构:描述的是一个数据(
Key
)到另一个数据(
Value
)的映射关系
(1)
Key
(键):有规律的,容易记忆的,不可重复的
(2)
Value
(值):没有规律的,不容易记忆的,可重复的
(3)操作:通过键寻找值
import java.util.HashMap;
import java.util.Map;
public class Demo12_Map {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1001, "张三");
map.put(2345, "张三");
map.put(2345, "李四");
map.put(6788, "张三");
map.put(4762, "张三");
map.put(4762, "王五");
map.put(8563, "张三");
System.out.println(map);
}
}
(二)常用方法
1、
put
(K key, V value)
:添加功能;当集合中不存在指定键时,将键值对添加到
map
集合中;修改功能;当集合中存在指定键的时候,根据键修改对应的值
2、
remove
(Object key)
:根据键删除键值对;如果键不存在,则不操作
3、
clear
()
:清空集合
4、
size
()
:返回集合中键值对的个数
5、
containsKey
(Object key)
:判断集合中否包含执行键
6、
containsValue
(Object value)
:判断集合中否包含执行值