day16
- 泛型
1.1 泛型的概念和使用
泛型: 广泛的类型.定义一个类,类中的方法的参数或者是返回值类型,不能确定,可以使用泛型来进行定义
举例: ArrayList中的add方法,可以存储任意的引用数据类型,因此方法的参数不确定,使用泛型表示
ArrayList arr = new ArrayList();
arr.add();
泛型的定义方式: 类比 ArrayList
<泛型的声明> : E----->Elements 元素,就表示广泛的类型
泛型的声明规范 : 可以使用一个符合规范的大写字母来进行表示
通常,使用E,T----Type,也可以使用W,Q,S…
泛型的使用: 创建类对象时,给出具体的泛型的类型
ArrayList arr1 = new ArrayList();
1.2 泛型的好处和使用注意事项
泛型的好处:
- 泛型提高代码的安全性,将运行时会发生的转型错误,提前到代码的编译环节. 带有泛型的集合,集合中能存储的数据类型就已经确定,保证集合中元素类型一致,不会发生转型的异常
- 泛型可以省略掉强制类型转换过程.存储数据时,已经确定数据类型,获取数据时,直接获取到指定类型
代码
package com.zgjy.fanxing;
import java.util.ArrayList;
import java.util.Iterator;
public class FanXingDemo1 {
public static void main(String[] args) {
//getArr();
getFanXingArr();
}
// 没有泛型的集合定义,的问题:
// 1. 可以在集合中存储多个数据类型,在进行集合遍历时,需要通过强制类型转换得到集合中的元素类型
// 2. 因为集合中存储的存储的类型不一致,强制类型转换时报错
public static void getArr() {
ArrayList arr = new ArrayList();
arr.add(“a”);// String
arr.add(“b”);
arr.add(“c”);
arr.add(12);// Integer
for(int i = 0 ; i < arr.size() ; i ++) {
Object obj = arr.get(i);
String s = (String)obj ;
System.out.println(s);
}
}
// 有泛型的集合定义: 1. 确定集合的数据类型,提高代码的安全性 2. 获取元素不需要强制类型转换,比较方便
public static void getFanXingArr() {
// 定义ArrayList加上泛型
// 表示: String类型就是arr这个对象的泛型,属于一个已知的类型
// 使得集合ArrayList中只能存储String类型
ArrayList<String> arr = new ArrayList<String>();
arr.add("a");
arr.add("b");
arr.add("hello");
//arr.add(1234);
Iterator<String> it = arr.iterator();
while(it.hasNext()) {
String s = it.next();
System.out.println(s);
}
}
}
泛型使用的注意事项:
- 定义的泛型类型,前后必须要保持一致
- 泛型的推测: 从JDK1.7版本开始,后面的泛型类型可以省略,默认与前面泛型保持一致,
甚至,可以将 都省略掉
代码
// 泛型的注意事项
public static void fanXingAttion() {
// 1. 定义的泛型类型,前后必须要保持一致
// ArrayList arr = new ArrayList(); 错误代码
ArrayList arr = new ArrayList();
// ArrayList arr3 = new ArrayList(); 错误代码
// 2. 泛型的推测: 从JDK1.7版本开始,后面的泛型类型可以省略,默认与前面泛型保持一致
// 甚至,可以将<Integer> 都省略掉
ArrayList<String> arr1 = new ArrayList<>();
ArrayList<String> arr2 = new ArrayList();
}
1.3 带有泛型的类
泛型类: 类上是带有泛型的
泛型类的定义方式:
修饰符 class 类名<泛型类型1,泛型类型2…>{
}
泛型类的使用:
- 泛型类型在定义时,定义规则: 是一个符合标识符规范的大写字母,泛型类型可以定义多个
- 泛型定义时机: 在创建类对象时,指定泛型的具体类型
- 泛型声明具体类型之后,就是一个已知的类型,可以在整个类中使用
代码
package com.zgjy.fanxing;
import java.util.ArrayList;
// 定义一个带有泛型的类
// 2. 泛型T在整个类中都可以使用
public class FanXingClass {
// 成员变量 ArrayList
private ArrayList arr = new ArrayList();
// 向成员变量所表示的集合中添加元素,在方法中使用了类上的泛型类型T
public T addElements(T t) {
arr.add(t);
return t;
}
public static void main(String[] args) {
// 1. 创建类对象时,指定泛型T的具体类型
FanXingClass<String> class1 = new FanXingClass<>();
String s = class1.addElements("abc");
String s1 = class1.addElements("world");
System.out.println(s);// abc
System.out.println(s1);// world
System.out.println(class1.arr);// [abc, world]
}
}
1.4带有泛型的方法
泛型的方法: 方法上面带有泛型
泛型方法的定义:
修饰符 <泛型类型1,泛型类型2,…> 返回值类型 方法名(参数列表){
}
泛型方法的使用:
- 方法上的泛型,可以在方法内部当做已知类型使用
- 非静态的方法,如果没有定义方法的泛型,可以使用类中的泛型
- 静态方法,只能在方法上自己定义泛型. 如果静态方法上没有定义泛型,也不能使用类上的泛型(类上的泛型,在创建类对象时确定具体类型,静态优先于对象存在,因此静态不能使用类上面的泛型)
代码
package com.zgjy.fanxing;
import java.util.ArrayList;
public class FanXingMethod {
public static void main(String[] args) {
FanXingMethod.getArr("a");
FanXingMethod.getArr("b");
}
// 静态方法不能使用类上的泛型E,原因: 静态中不能使用非静态的类型
public static <T> void getArr(T t) {
ArrayList<T> list = new ArrayList();
list.add(t);
System.out.println(list);
}
}
1.5带有泛型的接口
典型案例: Collection Iterator
泛型接口的定义:
修饰符 interface 接口名<泛型类型1,泛型类型2…>{
}
泛型接口的使用:
- 接口中定义的泛型类型,可以作为已知类型在接口中使用
- 接口的实现类
- 实现类不带有泛型,要求现实时,接口中的泛型具体类型需要制定好
class impl implements MyInterface{
}
2) 实现类带有泛型的类,接口中不需要指定泛型
class impl implements MyInterface{
}
代码
package com.zgjy.fanxing;
// 定义一个带有泛型的接口
// 1. 没有泛型的实现类 2. 带有泛型的实现类
public interface FanXingInterface {
public abstract void getT(T t);
}
// 没有泛型的实现类
class ShiXian1 implements FanXingInterface{
@Override
public void getT(String t) {
// TODO Auto-generated method stub
}
}
// 带有泛型的实现类
class ShiXian2 implements FanXingInterface{
@Override
public void getT(T t) {
// TODO Auto-generated method stub
}
}
1.6 泛型的通配符和泛型擦除(了解)
- 泛型的通配符: ? , 匹配任意类型的泛型
ArrayList<?> arr = new ArrayList();
ArrayList<?> arr1 = new ArrayList(); - addAll(Collection<? extends E> c) : 方法参数的集合,具有泛型,方法调用时,可以输入泛型E以及泛型的所有的子类
- Comparator<? super E> : 表示输入的泛型可有是E泛型或者是E的父类泛型
泛型擦除:
-
代码编写的时候,定义了泛型,使用了泛型,但是在编译的.class文件中,是没有泛型的,称这种现象为泛型的擦除,泛型就是为了在代码的编写环节提高代码的安全性
-
Set 集合
2.1 Set集合的介绍
Set接口是Collection接口的子接口, 来自JDK java.util.Set
Set 集合的特点: -
无序: 集合中元素的存入顺序和元素的取出顺序不一致
-
没有索引: 只能使用Collection中的方法进行元素的操作
-
不存储重复元素(元素唯一): 相同的元素在set集合中只能存储一个
List 集合特点:
- 有序: 集合中元素的存入顺序与元素的取出顺序保持一致
- 有索引: 可以通过索引获取到集合中的元素, 0 ----- 集合长度(size())-1
- 可存储重复元素 : 根据不同索引位置区分重复元素
Set集合是一个接口,需要通过实现类操作Set中方法, HashSet
Set set = new HashSet();// 接口的多态性
代码
package com.zgjy.set;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
public class SetDemo {
public static void main(String[] args) {
// 创建一个Set集合
Set<String> set = new HashSet<>();
// 向set集合中添加元素
set.add("a");
set.add("cd");
set.add("dd");
// 元素的存取无序
System.out.println(set);// [dd, a, cd]
set.add("cd");
set.add("hello");
set.add("a");
System.out.println("-----"+set);//[dd, a, cd, hello]
ArrayList<String> list = new ArrayList<>();
// 向list集合中添加元素
list.add("a");
list.add("cd");
list.add("dd");
// 元素的存取无序
//System.out.println(list);// [dd, a, cd]
list.add("cd");
list.add("hello");
list.add("a");
System.out.println("++++++++++"+list);//[dd, a, cd, hello]
}
}
2.2 Set集合的遍历方式
- Collection接口中,方法toArray() : 将一个集合转换成一个Object类型的数组,遍历数组回去到集合中的每一个元素
- Collection 接口中,方法toArray(T[] t) : 将一个集合中的元素放置到参数的T[]中,进行T[] 遍历,得到的每一个元素类型都是T类型,避免在数组循环时的强制类型转换
- 迭代器遍历 : 通过集合中重写的iterator() 方法,获取到迭代器对象Iterator<集合中存储的数据类型>, 通过Iterator的方法, hasNext() 集合中是否有下一个元素, 如果有 next() 获取到这个元素
说明: 迭代器,不论集合中是否有索引,都可以使用迭代器
原理: 获取集合中的元素,元素是否带有索引迭代器不关注,只获取元素 - 增强for : for循环的增强表示方式,forEach (重点掌握)
增强for的语法格式:
for(数据类型 变量名 : 集合或者数组){
// 变量就是获取到集合或者是数组中的每一个元素
}
说明:
- 集合(List,Set)或者数组,表示的是将要被遍历的容器
- 数据类型 : 表示集合或者数组中存储的数据类型
- 变量名 : 每次循环获取到的容器中的元素的表示方式
注意: 增强for 对于集合或者是数组是否有索引无要求,将容器中的每一个元素获取到
增强for底层就是使用迭代器的原理来实现
增强for 也会发生并发修改异常(使用增强for不要向集合中添加元素)
代码
package com.zgjy.set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class SetFor {
public static void main(String[] args) {
Set<String> set = new HashSet();
set.add("a");
set.add("b");
set.add("c");
set.add("d");
//getSet1(set);
//getSet2(set);
// getSet3(set);
getSet4(set);
}
// 1. Set集合的遍历: toArray()-----> Object[]
public static void getSet1(Set<String> set) {
Object[] obj = set.toArray();
// 遍历数组
for(int i = 0 ; i < obj.length ; i ++) {
Object ob = obj[i];
// 多态的向下转型
String s = (String)ob;
System.out.println(s);// a b c d
}
}
// 2. Set集合的第二种遍历方式: toArray(T[] t)
public static void getSet2(Set<String> set) {
// 需要参数T[],先创建一个数组,因为set中时String类型的元素,创建String[],
// 数组大小设置为set集合中的元素的大小
String[] ss = new String[set.size()];
String[] ss1 = set.toArray(ss);
for( int i = 0 ; i < ss1.length ; i ++) {
String s = ss1[i];
System.out.println("222---"+s);//
}
}
// 3. 迭代器遍历方式
public static void getSet3(Set<String> set) {
//1 . 获取到迭代器对象
Iterator<String> it= set.iterator();
// 2. 循环获取集合中的每一个元素
while(it.hasNext()) {
String s = it.next();
System.out.println("===="+s);// a b c d
}
}
// 4. 增强for进行set集合的遍历
public static void getSet4(Set<String> set) {
for(String s : set) {// 将集合中的每一个元素直接获取到,元素使用变量s表示
System.out.println("******"+s);
}
}
}
2.3 Set集合保证元素唯一性
2.3.1 Set集合存储JDK已经写好的数据类型
JDK已经预置好的引用数据类型,存储在set集合中可以有效去重复
JDK中的引用数据类型,都是重写过hashCode和 equals方法
代码
package com.zgjy.set;
import java.util.HashSet;
import java.util.Set;
public class SetOnly {
public static void main(String[] args) {
setDemo1();
}
// Set集合存储JDK预置好的数据类型
public static void setDemo1() {
Set<String> set = new HashSet<>();
set.add("a");
set.add("b");
set.add("a");
set.add("a");
set.add("c");
System.out.println(set); // [a, b, c]
Set<Integer> set1 = new HashSet<>();
set1.add(1);
set1.add(11);
set1.add(2);
set1.add(11);
set1.add(1);
System.out.println(set1);// [1, 2, 11]
}
}
2.3.2 Set集合存储自定义类型
Set集合中存储的自定义的引用数据类型,要想做到根据对象中的成员变量的值进行去重复功能,在自定义类型中,重写hashCode 和 equals
-
关注Object类中,hashCode 和 equals 两个方法,因为这两个方法都是判断是否是同一个对象的方法
hashCode : 获取每一个对象的int 类型的数值,Object源代码中,每一个new对象都有一个不同的int 类型的返回值
equals : 用于比较两个对象是否相等,Object源码,比较两个对象的地址, 使用== 比较 -
自定义类型中,重写hashCode 和 equals
重写hashCode功能 : 将类中的成员变量的值获取到,转换成int类型的数值
重写equals 功能: 就是比较两个对象的成员变量值是否相等
发现重写hashCode 和 equals 方法后,自定义的数据类型对象,可以将相同成员变量的对象进行去重复操作
代码
package com.zgjy.set;
public class Person {
private String name;
private int 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;
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public Person() {
super();
// TODO Auto-generated constructor stub
}
@Override
public String toString() {
return “Person [name=” + name + “, age=” + age + “]”;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
package com.zgjy.set;
import java.util.HashSet;
import java.util.Set;
public class SetOnly {
public static void main(String[] args) {
setDemo2();
}
// Set集合中存储自定义的数据类型
public static void setDemo2() {
// 要求: Person 对象存储在set集合时,不同的对象 name 和 age 的值都不相等
Set<Person> set = new HashSet<>();
set.add(new Person("QQ",12));
set.add(new Person("QQ",12));
set.add(new Person("QQ糖",12));
set.add(new Person("大美",25));
System.out.println(set);
}
}
2.3.3 Set集合的保证元素唯一原理
Set集合底层数组 + 链表的结构,通过hashCode和equals两个方法进行元素唯一性的验证
2.4 LinkedHashSet
HsahSet的一个子类,LinkedHashSet 进行元素存储,能够保证存入的顺序和元素取出的顺序保持一致,其他方法,与hashSet基本上一致