欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

泛型

程序员文章站 2022-07-15 17:53:49
...

泛型

1.泛型

1.1.泛型的引入

需求:打印ArrarList集合中所有字符串的长度;
解决问题的代码:

public class Demo1 {
	public static void main(String[] args) {
		//需求:打印ArrarList集合中所有字符串的长度;
		//新功能,需要定义函数实现
		ArrayList list = new ArrayList();
		list.add("1");
		list.add("22");
		list.add("333");
		//调用函数输出结果
		print(list);
	}
	/*
 * 功能:打印ArrayList集合中的所有字符串的长度
 * 两个明确:
 * 	1、返回值类型: 因为只需要打印输出所有字符串的长度,不需要返回什么结果,所以返回值类型是void
 * 	2、参数列表:ArrayList  list
 * */
public static void print(ArrayList  list){
		//要输出所有字符串的长度,所以需要遍历整个集合
		for (Object object : list) {
			//因为要当作字符串操作,所以要强制向下转型
			String str = (String)object;
			System.out.println(str.length());//输出字符串的长度
		}
	}
}

1、在添加数据的时候人为的注意只能保存字符串;问题是:人为的注意,总有疏忽的时候;
2、在强制类型转换之前先做判断,判断这个对象确实是String类型;
问题是:不符合面向对象的一个原则:谁的工作谁负责做;

自从JIK1.5之后,出现一个新的技术,叫做泛型,就可以解决这个问题;

1.2.泛型介绍

泛型,又叫做参数化类型,就是一种在编译期对数据类型(只能是引用数据类型)进行检查的技术;
泛型的写法是:
<数据类型>

这个字母E,就是一个标识符,表示一个具体类型的参数;
使用泛型解决刚才的问题:

使用泛型的时候需要注意:

泛型只在编译期起作用;编译后的字节码里面没有泛型;
泛型只存在编译期,编译后泛型消失,叫做泛型的擦除;

1.3.泛型的简单应用

需求:向集合中添加学生对象,要求按照学生的年龄进行从小到大排序

public class GenericTest {
	public static void main(String[] args) {
		//需求:向集合中添加学生对象,要求按照学生的年龄进行从小到大排序
		//因为没有映射关系,有要求保存后排序,所以可以使用TreeSet集合
		//TreeSet排序,必须有比较的功能,所以要么让Student实现Comparable接口,要么提供一个比较器对象
		//这里使用比较器对象
		//因为这里只使用一次,所以可以使用匿名内部类的方式;
		Comparator<Student> c = new Comparator<Student>(){
			public int compare(Student o1, Student o2) {
				if(o1 == null || o2 == null){
					throw new NullPointerException("保存的数据不能是null");
				}
				return o1.age - o2.age;
			}

		};
		TreeSet<Student> ts = new TreeSet<>(c);
		ts.add(new Student("小明",23));
		ts.add(new Student("小张",21));
		ts.add(new Student("小王",22));
		ts.add(new Student("王五",22));
		ts.add(new Student("小李",25));
		for (Student object : ts) {
			System.out.println(object);
		}
	}
}
//描述学生对象
class Student{
	String name;//姓名
	int age;//年龄
	
	public Student(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;
	}
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}
}

结论:使用泛型,可以在编译期就对数据的类型进行强制要求,可以提高程序的安全性;
同时避免使用强制类型转换,减小出错的几率;

为了提高程序的安全性,一般使用集合时都不会保存多种类型的数据,而是使用泛型限制只保存同一种类型的数据;

1.4.泛型类

使用泛型时,泛型可以直接定义在类上;如果在定义一个类时使用了泛型,那么这个类就叫做泛型类,泛型的具体类型在创建对象时确定;
泛型

需求:定义一个工具类,可以保存和获取一个任意类型的对象;

泛型
使用泛型类需要注意:
1、静态函数不能使用类的泛型
泛型

2、如果一个类要定义多个泛型,都写在同一个尖括号中,不同的泛型之间使用逗号隔开;
泛型

1.5.泛型方法

如果方法要接受的参数不确定,而且和类上的泛型不一致,就可以在方法上自己定义泛型;
泛型

案例:修改上面的泛型工具类,提供一个功能,可以接收任何引用类型的参数;
泛型

在一个函数中,参数列表中可以混合使用类的泛型和方法的泛型;

方法的泛型实在调用方法的时候传参的时候确定的,调用方法传参和有没有对象无关,所以静态函数一样可以定义方法的泛型;
泛型

方法上的泛型是定义在方法上的,属于局部范围的,所以:
注意:在方法中定义的泛型,只能在这个方法里面使用;

1.6.泛型接口

如果在定义一个接口时在接口上使用的泛型,这个接口就成为泛型接口;

泛型

1、接口的泛型,在实现接口时可以确定;
泛型

2、在一个类实现一个泛型接口的时候,可以不指定泛型的具体类型;
泛型
像这样,在实现接口或者继承类的时候,把泛型也继承下来,叫做泛型的传递;

1.7.泛型通配符

1.7.1.泛型中的通配符

需求:定义功能,输出不同集合中的不同元素;
泛型
以后要使用泛型,需要适配各种不同的类型的时候,就应该使用泛型的通配符;

public class GenericDemo2 {
	public static void main(String[] args) {
		//需求:定义功能,输出不同集合中的不同元素;
		/*
		 * 1、简单点,简单的功能,输出List集合中的元素,元素类型是String类型
		 * */
		List<String> list = new ArrayList<>();
		list.add("11");
		list.add("22");
		printList(list);
		
		//2、提升功能,使这个函数可以输出Set集合,集合中的元素也是String类型
		Set<String> set = new HashSet<>();
		set.add("aaa");
		set.add("bbb");
		printList(set);
		Set<Integer> set2 = new HashSet<>();
		set2.add(123);
		set2.add(456);
		printList(set2);
	}
	//要让函数既能接收List类型的集合,也能接收Set类型的集合,所以要将参数的类型提升为他们共同的父类型
	public static void printList(Collection<?>/*?表示泛型的通配符*/ list){
		for (Object string : list) {
			System.out.println(string);
		}
	}
}

1.7.2.通配符的上限和下限

通配符的上限,表示只能匹配某一个类或者它的子类的类型;写法是:<? extends 上限类的类型>;
泛型
泛型

通配符的下限,表示只能匹配某一个类或者它的父类的类型;写法是:<? super 下限类的类型>

泛型
在使用通配符的上限和下限时,如果是在集合容器上使用,应该注意:
1、如果容器使用的是上限,此时只能从集合中取出数据,不能向集合中存放数据;

public class GenericDemo4 {
	public static void main(String[] args) {
		List<Z> list1 = new ArrayList<>();
		list1.add(new Z());
		list1.add(new Z());
		test1(list1);
		List<S> list2 = new ArrayList<>();
		list2.add(new S());
		list2.add(new S());
		test1(list2);
	}
//表示接收的参数是一个list集合,集合中只能保存Z类或者Z类的子类
public static void test1(List<? extends Z> list){
		/*
		 * 这个函数接收的参数是一个List集合对象,实际接收到的对象,可能只能保存Z类,
		 * 可能只能保存S类,或者它们还有其他子类,只能保存某一个子类;
		 * 如果实际接收的对象,只能保存S类,此时向容器中添加Z类的对象,就添不进去,会报错;
		 * 为了避免出现这种情况,所以就不允许向使用泛型的上限的集合中添加数据;
		 * */
//		list.add(new Z());
		for (int i = 0; i < list.size(); i++) {
			/*
			 * 因为实际接收到的集合对象中保存的数据,类型要么是Z类,要么是Z的子类,
			 * 都可以使用Z类指代
			 * */
			Z z = list.get(i);
			System.out.println(z);
		}
	}
}
class F{}
class Z extends F{}
class S extends Z{}

2、如果容器使用的是下限,可以向容器中添加数据,添加的数据的类型只能是下限的类型或者它的子类的对象;如果要从里面取出数据,必须使用强制类型转换,或者使用Object类型接收;

1.8.泛型总结

1、泛型是一种在编译期就可以进行数据类型的检查的技术,只能检查引用类型的数据类型;
2、泛型使用一对尖括号表示:<标识符>;尖括号中的标识符,表示一个引用数据类型;
3、泛型可以定义在类、方法和接口上;
a)定义在类上:书写在类名后面,在创建类的实例对象时确定泛型的具体类型;在类中的非静态函数中可以使用;
b)定义在方法上:书写在方法的返回值类型前面,在方法调用时确定泛型的具体类型,静态和非静态函数都能定义;只能在当前定义的这个方法里使用;
c)定义在接口上:书写在接口名后面,实现接口时可以明确泛型的具体类型,或者通过泛型类,在创建类的对象时才明确具体类型(这个叫做泛型的传递);
4、泛型的通配符:当需要使用的泛型的数据类型是不确定的时候,就需要使用泛型的通配符;
通配符的写法:<?>
5、泛型的通配符的上限和下限:
a)在定义泛型的通配符的时候,如果只能匹配某个类和这个类的子类,就需要使用通配符的上限,写法是:<? extends 上限的类型>
在使用通配符的上限时,只能从容器中取出数据,不能向容器中存放数据;
b)在定义泛型的通配符的时候,如果只能匹配某个类和这个类的父类,就需要使用通配符的下限,写法是:<? super 下限的类型>
在使用通配符的下限的时候,直接向容器中保存对象,对象的类型只能是下限的类型或者下限类型的子类型;
要从容器中取出对象,不能直接使用下限类型的变量接受,需要强制向下转型,或使用Object类型的变量接受;