java中的泛型

一.概述和使用

  1. 泛型:广泛的类型,在定义一个类的时候,类型中有些方法参数,返回值不确定的时候,就是用一个符号,来表示那些尚未确定的类型,这个符号就称为泛型。
  2. 使用:对于有泛型的类型,在这些类型后面跟上尖括号,尖括号里面写上泛型的确定类型(若在使用某个类创建对象时,已经确定了这个具体的类型了,那么就直接写出具体类型)。
  3. 泛型包括:泛型类、泛型接口、泛型方法。

例如:

ArrayList<Integer> al = new ArrayList<Integer>(); //<Integer>这个就称为泛型。

3.泛型的好处:
(1).提高了数据的安全,将运行时的问题,提前暴露在编译时期
(2).避免了强转的麻烦。

示例代码:

package 泛型;

import java.util.ArrayList;

public class Test1 {
	public static void main(String[] args) {
		ArrayList al = new ArrayList();
		al.add("asd");
		al.add(1);
		for (int i = 0; i < al.size(); i++) {
			String s = (String) al.get(i);
			System.out.println(s);
		}
	}

}

ArrayList可以存放任意类型,我在里面存放了一个String类型的数据,又存放了一个Integer类型的数据,但是在取出的时候都使用String类型的方式使用,所以程序就会报错。

Exception in thread "main" java.lang.ClassCastException: 
java.lang.Integer cannot be cast to java.lang.String at 泛型.Test1.main(Test1.java:13)

所以泛型的好处就是在编译的阶段就避免了这种问题的出现。

	ArrayList<String> al = new ArrayList<String>();//添加String泛型
		al.add("asd");
		al.add(1.2);//这一行会报错
		//这里修改成1.2是为了防止调用add的重载方法add(int,String);

在我们添加了泛型之后 在al.add(1.2);这一行,程序会提示出错。
在这里插入图片描述
这里就提示,我们填入的这个double类型的数据不适用于这个add()方法。

所以泛型的好处就是在于规定了固定的数据类型,说明这个类中仅能存储这种类型的数据,所以也就避免了强转的麻烦,并且也把在运行时可能出现的问题暴露在编译阶段。

4.注意事项
(1).前后一致,在创建对象时,赋值符号前面和后面的类型饿泛型,必须一致
(2).泛型推断,如果前面的引用所述类型已经写好了泛型,后面创建对象的类型就可以只写一个尖括号,尖括号中可以不写任何的内容。因为<>特别像菱形,也成为“菱形泛型“(jdk1.7的特性)。
(3).泛型仅在编译时有效。

示例代码:

package 泛型;

import java.util.ArrayList;

public class Test2 {
	public static void main(String[] args) {
		//泛型的类型测试
		ArrayList<String> sal = new ArrayList<String>();
		ArrayList<Integer> ial = new ArrayList<Integer>();
		
		Class salclass = sal.getClass();
		Class ialclass = ial.getClass();
		
		System.out.println(sal.equals(ial));
		//打印结果true
	}

}

二.泛型类的定义

  1. 泛型类:带着泛型定义的类
  2. 格式:
class 类名 <泛型类型1,泛型类型2,泛型类型3......>{
	}

3.效果:这些泛型一旦在类定义的时候声明,在整个类中都可以将这些泛型当做已知的类型来使用。
4.泛型类的要求:
(1)泛型必须是一个合法的标识符。
(2)泛型使用的符号一般是单个的大写字母: E T W Q K V。
(3)泛型确定的时机:创建的时候才能确定。
(4)尖括号中只能写引用类型的参数,不能使用基本类型

示例代码:

class MyTest<T>{
	public void test(T t) {//泛型可以当做已知的类型来使用。
	}
}

练习:定义一个带泛型的集合,只能在集合的头部进行增删

package 泛型;

import java.util.LinkedList;

public class Test3 {

	public static void main(String[] args) {
		MyCollection<String> mc = new MyCollection<>();
		mc.add("qwer");
		mc.add("asdf");
		mc.add("zxcv");

		mc.print();
		mc.add("jkl");
		System.out.print("在头部添加 ");
		mc.print();

		mc.remove();
		System.out.print("在头部删除 ");
		mc.print();

		MyCollection<Integer> mc2 = new MyCollection<>();

		mc2.add(1);
		mc2.add(2);
		mc2.add(3);

		mc2.print();
		mc2.add(666);
		System.out.print("在头部添加 ");
		mc2.print();

		mc2.remove();
		System.out.print("在头部删除 ");
		mc2.print();

	}
}

class MyCollection<T> {
	LinkedList<T> ls = new LinkedList<>();

	public void add(T t) {
		ls.addFirst(t);//linkedlist中的添加在头部方法
	}

	public void remove() {
		ls.removeFirst();//linkedlist中的删除头部节点方法
	}
	//遍历打印
	public void print() {
		for (T t : ls) {
			System.out.print(t + " ");
		}
		System.out.println();

	}
}

-------------------------------------------------------------------------------------------
打印结果:
zxcv asdf qwer 
在头部添加 jkl zxcv asdf qwer 
在头部删除 zxcv asdf qwer 
3 2 1 
在头部添加 666 3 2 1 
在头部删除 3 2 1 

注意: 定义了泛型了泛型类,并不一定要传入确定的泛型类型(在尖括号里面填写),也可以不传入泛型类型。泛型只有在传入了泛型参数之后才能起到泛型应有的约束作用,如果不传入泛型参数,那么该泛型类中的方法或变量类型可以定义为任何类型。

示例代码:

package 泛型;

public class Test4 {

	public static void main(String[] args) {
		Demo d1 = new Demo("asd");//创建一个泛型类对象,但未传入泛型参数
		Demo d2 = new Demo(123);
		Demo d3 = new Demo(1.23);
		Demo d4 = new Demo(true);
		
		System.out.println(d1.get());//打印
		System.out.println(d2.get());
		System.out.println(d3.get());
		System.out.println(d4.get());
		
	}

}

class Demo<T> {
	private T test;

	public Demo(T test) {
		super();
		this.test = test;
	}

	public T get() {
		return test;
	}
}

三.泛型方法

同理,泛型方法就是带着泛型的方法

1.定义格式:

修饰符 <泛型类型1, 泛型类型2...> 返回值类型 方法名称(参数列表) {
 	 	
}

2.注意事项:
(1)在声明上定义的泛型,可以在整个方法中当做已知类型使用。
(2)泛型类上的泛型是在创建对象时确定的所以:【非静态】方法,可以直接使用类上声明的泛型
【静态】方法,可以在创建对象之前,调用,所以不能使用类上声明的泛型,如果静态方法需要使用泛型,必须声明自己特有的泛型。

代码示例:

class Demo2<T> {
	// 一个普通的泛型类

	private T mem;// 定一个泛型T类型的成员变量。

	public Demo2(T mem) {
		super();
		this.mem = mem;
	}

	public T getMem() {
		return mem;
	}

	/*
	 * 在上述方法中,虽然使用到了泛型,但本质上其实不是一个泛型方法, 其实只是一个普通的成员方法,
	 *  因为类上声明了泛型T,所以这里可以 使用T这个泛型。
	 * 
	 * public E getE() { return null; }
	 * 因为使用了类型中未声明的泛型E,所以导致报错
	 * “E cannot be resolved to a type”
	 * 
	 */
	
	//真正的泛型方法:
	
	public <K> K show(T t) {
		
		K k = null;
		return k;
	}
	
}

	//静态方法使用泛型
	
	public static void test(T t){
		
	}
	
	//提示Cannot make a static reference to the non-static type T错误,
	//原因是类上面的泛型是在创建对象的时候才能确定的,而静态方法优先于对象的创建,
	//如果静态方法要想使用泛型,就必须在自己的方法上声明。
	
	public static <T> void  demo(){
		
	}

练习:定义一个方法,可以交换任意引用类型数组中的指定索引的两个元素

package 泛型;

import java.util.Arrays;

public class Test6 {

	public static void main(String[] args) {
		String[] s = { "a", "b", "c", "d", "e" };
		swap(s, 1, 3);
		System.out.println(Arrays.toString(s));
		Person[] p = { 
						new Person(11, "aaa"), 
						new Person(12, "bbb"), 
						new Person(13, "ccc"), 
						new Person(14, "ddd"),
						new Person(15, "eee") };
		
		swap(p,1,3);
		System.out.println(Arrays.toString(p));

	}

	private static <T> void swap(T[] t, int a, int b) {
		T temp = t[a];
		t[a] = t[b];
		t[b] = temp;
	}

}

----------------------------------
打印结果:
[a, d, c, b, e]
[Person [age=11, name=aaa],
 Person [age=14, name=ddd], 
 Person [age=13, name=ccc], 
 Person [age=12, name=bbb], 
 Person [age=15, name=eee]]

四.泛型接口

1、泛型接口:
带着泛型定义的接口
2、格式:

  interface 接口名称 <泛型类型1, 泛型类型2...> {
        }

3、实现类的两种情况:
(1)实现类不是一个带泛型的类,泛型类型已经被确定了,如下:

class 实现类类名 implements 接口名称<具体的已知类型> {   	 	 	
 }

(2)实现类还是一个带泛型的类,泛型类型和接口的泛型一致,格式:

class 实现类类名<泛型声明符号1> implements 接口名称<泛型声明符号1> {   
 }

示例代码:

interface MyInter<W>{
	public abstract void test();
}

//普通的没有泛型的类实现接口,泛型已经确定

class MyImp1 implements MyInter<String>{

	@Override
	public void test() {
	}
}
//泛型类实现泛型和接口的泛型一致
class MyImp2<Q> implements MyInter<Q>{

	@Override
	public void test() {	
	}
	
}

注意事项:

interface MyInter<W>{
	public abstract void test(W w);
}

//泛型类实现泛型和接口的泛型一致
class MyImp2<String> implements MyInter<String>{

	@Override
	public void test(String str) {
		System.out.println(str.length);
	}
}

注意:这里重写的接口中的test方法,并且“看起来定义了一个String类型的str”;其实这个str并不是String类型的变量。

在这里插入图片描述
str中并未拥有String类型拥有的一些方法。
在这里插入图片描述

所以如果是使用泛型类来实现泛型接口,那么类声明上尖括号中无论写的是什么,都代表一个不确定的泛型,而不具有特定的意义,所以尽量要避开使用java中的关键字

五.泛型的通配符

1、注意点:
通配符并不是想表示简单的任意类型,而要表达的是:当前通配符和一个已知的泛型的【关系】
2、?通配符:
用于表达和一个已知泛型的关系:没关系,例如:
Collection 中的removeAll(Collection<?> c),通配符 ? 表达的含义就是,当前通配符泛型和已知的泛型E没有任何关系,参数集合的泛型,可以是任意类型
3、? extends E通配符:确定了泛型的上边界
用于表达和一个已知泛型的关系:通配符泛型必须是E的子类或者是E本身
Collection 中的addAll(Collection<? extends E> c),通配符表达的含义是:参数集合的泛型必须是调用者集合泛型E的子类。
4、? super E通配符:确定了泛型的下边界
用于表达和一个已知泛型的关系:通配符泛型必须是E的父类或者是E本身,例如:
Arrays类型中的sort(T[], Comparator<? super T>),通配符表达的含义是:第二个比较器参数的泛型,必须是第一个数组元素类型T泛型的父类或者T本身,才能确保该比较器有足够的能力对T数组中的元素进行比较。
在这里插入图片描述

感谢可以看到这里,如果喜欢请点个赞谢谢

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值