JAVA泛型

Posted 一只小阿大嗷

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA泛型相关的知识,希望对你有一定的参考价值。


问题引入
问学员一个问题:如果我们需要产生多个对象,每个对象的逻辑完全一样,只是对象内的成员变量类型不同。那我们如何去做?
问题解决

  1. 创建多个类文件,给每个类中的成员变量设置指定的数据类型。
  • 缺点:这种方式会导致类的膨胀,重用性太差。

案例

package 泛型;

class cl1
{
	int a;
	
	public cl1(int a) 
	{
		this.a = a;
	}
	public int getData() 
	{
		return a;
	}
}

class cl2
{
	String a;
	
	public cl2(String a) 
	{
		this.a = a;
	}
	public String getData() 
	{
		return a;
	}
}

public class Test {
	public static void main(String[] args) {
		cl1 cl1 = new cl1(120);
		cl2 cl2 = new cl2("1200");
		
		System.out.println(cl1.getData());
		System.out.println(cl2.getData());
	}
}

结果

120
1200
  1. 创建一个类文件,给这个类中的成员变量设置Object数据类型。
  • 缺点:编译的时候正常,但运行的时候可能会异常。

案例

package 泛型;

class cl1
{
	Object a;
	
	public cl1(Object a) 
	{
		this.a = a;
	}
	public Object getData() 
	{
		return a;
	}
}

public class Test {
	public static void main(String[] args) {
		cl1 cl1 = new cl1(120);
		cl1 cl2 = new cl1("1200");
		
		System.out.println(cl1.getData());
		System.out.println((Integer)cl2.getData());
	}
}

结果

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

泛型简介(JDK1.5以后引入)

  1. 泛型可以在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的。
  2. 泛型的原理就是“类型的参数化”,即把类型看做参数。也就是说把要操作的数据类型看做参数,就像方法的形式参数是运行时传递的值的占位符一样。
  3. 简单来说,类型变量扮演的角色就如同一个参数,它提供给编译器用来类型检查的信息。
  4. 泛型可以提高代码的扩展性和重用性。
示例--泛型类
public class GenClass <T>{
	private T obj;

	public GenClass (T boj){
		this.obj = obj;
	}

	public T getObj(){
		return obj;
	}

	public void setObj(T obj){
		this.obj = obj;
	}
}

案例

package 泛型;

class cl1<T>
{
	T a;
	
	public cl1(T a) 
	{
		this.a = a;
	}
	public T getData() 
	{
		return a;
	}
}

public class Test {
	public static void main(String[] args) {
		cl1<Integer> cl1 = new cl1<Integer>(120);
		cl1<String>  cl2 = new cl1<String>("1200");
		
		System.out.println(cl1.getData());
		System.out.println(cl2.getData());
		//下面语句不能强转,防止操作失误
		//System.out.println((Integer)cl2.getData());
	}
}

结果

120
1200

泛型类

特点

  1. 泛型的类型参数可以是泛型类
  2. 泛型类可以同时设置多个类型参数
  3. 泛型类可以继承泛型类
  4. 泛型类可以实现泛型接口

案例

package 泛型;

//带一个参数的泛型
//可以定义为抽象类,再套一个抽象方法,但这样(1.泛型的类型参数可以是泛型类)就实现不了
/*
abstract class cl1<T>
{
	T a;
	
	public cl1(T a) 
	{
		this.a = a;
	}
	public T getData() 
	{
		return a;
	}
	abstract void print();
} 
*/
class cl1<T>
{
	T a;
	
	public cl1(T a) 
	{
		this.a = a;
	}
	public T getData() 
	{
		return a;
	}
}

//4.泛型类可以实现泛型接口
interface cl3<T>
{
	abstract void printInfo(T t);
}

//带两个参数的泛型
//3.泛型类可以继承泛型类
class cl2<T,T2> extends cl1<T> implements cl3<T>
{
	T2 b;
	
	public cl2(T a,T2 b) 
	{
		super(a);
		this.b = b;
	}

	public T2 getDataT2() 
	{
		return b;
	}

	@Override
	public void printInfo(T t) {
		// TODO 自动生成的方法存根
		System.out.println("接口printInfo方法输出 "+t);
	}
}

public class Test {
	public static void main(String[] args) {
		cl1<Integer> cl1 = new cl1<Integer>(120);
		cl1<String>  cl2 = new cl1<String>("1200");
		
		System.out.println(cl1.getData());
		System.out.println(cl2.getData());
		//下面语句不能强转,防止操作失误
		//System.out.println((Integer)cl2.getData());
		
		//1.泛型的类型参数可以是泛型类
		cl1<cl1<Integer>> cl = new cl1<cl1<Integer>>(new cl1<Integer>(888));
		System.out.println(cl.getData().getData());
		
		//2.泛型类可以同时设置多个类型参数
		cl2<Integer,String>  cl3 = new cl2<Integer,String>(99,"handsome");
		cl2<Integer,Integer> cl4 = new cl2<Integer,Integer>(99,100);
		
		System.out.println(cl3.getData()+cl3.getDataT2());//拼接在一起成99handsome
		System.out.println(cl4.getData()+cl4.getDataT2());//两个数字相加成199
		
		int total;
		total = cl4.getData()+cl4.getDataT2();
		System.out.println("total = "+total);
		
		cl4.printInfo(9999);//这只能Integer,如果要输入字符串或者别的,需要改动接口泛型
		/*
			比如把接口泛型改成T3,还需要在cl2多加个T3泛型,方法也需要改成T3,定义的时候也得定义T3,定义了String,输出也只能String
			把接口泛型改成T3
			interface cl3<T3>
			
			在cl2多加个T3泛型
			class cl2<T,T2,T3> extends cl1<T> implements cl3<T3>
			
			方法也需要改成T3
			public void printInfo(T3 t)
			
			定义的时候也得定义T3
			cl2<Integer,String,String>  cl3 = new cl2<Integer,String,,String>(99,"handsome");
			
			定义了String,输出也只能String
			cl4.printInfo("T3");
		*/
	}
}

结果

120
1200
888
99handsome
199
total = 199
接口printInfo方法输出 9999

限制泛型可用类型

  1. 在定义泛型类别时,默认在实例化泛型类的时候可以使用任何类型,但是如果想要限制使用泛型类型时,只能用某个特定类型或者是其子类型才能实例化该类型时,可以在定义类型时,使用extends关键字指定这个类型必须是继承某个类,或者实现某个接口
  2. 当没有指定泛型继承的类型或接口时,默认使用extends Object,所以默认情况下任何类型都可以作为参数传入。

案例

package 泛型;

class Animal{}
class Dog extends Animal{}

interface Move
{
	abstract void test();
}

class jiekou implements Move
{

	@Override
	public void test() {
		// TODO 自动生成的方法存根
		System.out.println("this is test method");
	}
	
}

//限定了T使用类型为Animal,T2的使用类型为String,T3的使用类型为Move(接口)
class cls2<T extends Animal,T2 extends String,T3 extends Move> extends cls1<T>
{
	T2 b;
	T3 c;
	
	public cls2(T a,T2 b,T3 c) 
	{
		super(a);
		this.b = b;
		this.c = c;
	}

	public T2 getDataT2() 
	{
		return b;
	}
	public T3 getDataT3() 
	{
		return c;
	}
}

class cls1<T>
{
	T a;
	
	public cls1(T a) 
	{
		this.a = a;
	}
	public T getData() 
	{
		return a;
	}
}

public class Test2 {
	public static void main(String[] args) {
		cls2<Dog,String,Move> cl1 = new cls2<Dog,String,Move>(new Dog(),"handsome",new jiekou());
		//cls2<Dog,String,Integer> cl1 = new cls2<Dog,String,Move>(new Dog(),"handsome",new jiekou());
		//边界不匹配:类型 Integer 并不是类型 cls2<T,T2,T3> 的有界参数 <T3 extends Move> 的有效替代项。
		System.out.println(cl1.getData());
		System.out.println(cl1.getDataT2());
		cl1.getDataT3().test();
		
		cls2<Dog,String,Move> cl2 = new cls2<Dog,String,Move>(new Dog(),"kkkkk",new jiekou());
		System.out.println(cl2.getData());
		System.out.println(cl2.getDataT2());
		cl2.getDataT3().test();
	}
}

结果

泛型.Dog@15db9742
handsome
this is test method
泛型.Dog@6d06d69c
kkkkk
this is test method

类型通配声明

  1. 同一泛型类,如果实例化时给定的实际类型不同,则这些实例的类型是不兼容的,不能相互赋值。
Generic<Boolean> f1 = new Generic<Boolean>();
Generic<Integer> f2 = new Generic<Integer>();
f1 = f2;//发生编译错误
Generic<Object> f = f1;//fi和f类型并不兼容,发生编译错误
f = f2;//f2和f类型同样不兼容,也会发生编译错误
  1. 泛型类实例之间的不兼容性会带来使用的不便。我们可以使用泛型通配符(?)声明泛型类的变量就可以解决这个问题。

类型通配声明 例子

泛型通配的方式
"?"代表任意一个类型
Generic<Boolean> f1 = new Generic<Boolean>();
Generic<?> f = f1;
和限制泛型的上限相似,同样可以使用extends关键字限定通配符匹配类型的上限:
Generic<Dog> f1 = new Generic<Dog>();
Generic<? extends Animal> f = f1;
还可以使用super关键词将通配符匹配类型限定为某个类型及其父类型(下限)
Generic<Animal> f1 = new Generic<Animal>();
Generic<? super Dog> f = f1;

案例

package 泛型通配符;

class Animal{}
class Dog extends Animal{}
class Cat extends Animal{}

class cls1<T>
{
	T a;
	
	public cls1(T a) 
	{
		this.a = a;
	}
	public T getData() 
	{
		return a;
	}
}

public class Test {
	public static void main(String[] args) {
		cls1<Integer> c1 = new cls1<Integer>(10);
		cls1<Object>  c2 = new cls1<Object>(8);
		
		//c1 = c3;//类型不匹配:不能从 cls1<Object> 转换为 cls1<Integer>		
		//c3 = c1;//类型不匹配:不能从 cls1<Integer> 转换为 cls1<Object>
		
		//"?"代表任意一个类型
		cls1<?> a1;
		//和限制泛型的上限相似,同样可以使用extends关键字限定通配符匹配类型的上限
		cls1<? extends Dog> a2;
		//使用super关键词将通配符匹配类型限定为某个类型及其父类型(下限)
		cls1<? super Dog> a3;//必须是Dog的父类
		
		a1 = c1;
		//c1 = a1;//类型不匹配:不能从 cls1<capture#3-of ?> 转换为 cls1<Integer>
		//a2 = c1;//类型不匹配:不能从 cls1<Integer> 转换为 cls1<? extends Dog>		
		//a3 = c1;//类型不匹配:不能从 cls1<Integer> 转换为 cls1<? super Dog>
		
		cls1<Dog> s1 = new cls1<Dog>(new Dog());
		a1 = s1;
		a2 = s1;
		a3 = s1;
		
		cls1<Cat> s2 = new cls1<Cat>(new Cat());
		a1 = s2;
		//a2 = s2;//类型不匹配:不能从 cls1<Cat> 转换为 cls1<? extends Dog>
		//a3 = s2;//类型不匹配:不能从 cls1<Cat> 转换为 cls1<? super Dog>
		
		System.out.println("end");
	}
}

结果

end

泛型方法

  1. 不仅类可以声明泛型,类中的方法也可以声明仅用于自身的泛型,这种方法叫做泛型方法。其定义格式为:
访问修饰符 <泛型列表> 返回类型 方法名(参数列表){
	实现代码
}
  1. 在泛型列表中声明的泛型,可用于该方法的返回类型声明、参数类型声明和方法代码中的局部变量的类型声明。
  2. 类中其他方法不能使用当前方法声明的泛型。

提示:是否拥有泛型方法,与其所在的类是否泛型没有关系。要定义泛型方法,只需将泛型参数列表置于返回值前。

什么时候使用泛型方法,而不是泛型类呢?

  • 添加类型约束只作用于一个方法的多个参数之间、而不涉及到类中的其他方法时。
  • 施加类型约束的方法为静态方法,只能将其定义为泛型方法,因为静态方法不能使用其所在类的类型参数。

案例

package 泛型方法;

class Bird
{
	public void eat() 
	{
		System.out.println("鸟吃虫子");
	}
}

class SmallBird extends Bird
{
	public void eat() 
	{
		System.out.println("小鸟吃米");
	}
}

class BigBird extends Bird
{
	public void eat() 
	{
		System.out.println("大鸟啥都吃");
	}
}

class Animal<T>
{
	public void printInfo(T t) 
	{
		System.out.println(t);
	}
}

class Person
{
	//访问修饰符 <泛型列表> 返回类型 方法名(参数列表)
	public <T> void printInfo(T t) 
	{
		System.out.println(t);
	}
	//泛型方法的重载
	public <T,T2> T printInfo(T t,T2 t2) 
	{
		System.out.println(t);
		System.out.println(t2);
		//在这不能进行t+t2,因为不确定类型,需要在调用代码的时候确定类型一样即可加。
		return t;
	}
	//方法重载就改变参数类型,泛型对比重载,可以减少代码沉余。
	
	public <T extends Bird> void printInfo2(T t) 
	{
		t.eat();
	}
	
	public static <T extends Bird> void printInfo3(T t) 
	{
		t.eat();
	}
}

public class Test {
	public static void main(String[] args) {
		Animal<String> a = new Animal<String>();
		a.printInfo("handsome");
		
		Person p = new Person();
		p.printInfo("person handsome");
		p.printInfo(1234);
		p.printInfo(3.14);
		p.printInfo('K');
		
		Person p2 = new Person();
		//p2.printInfo2("person handsome");//类型 Person 中的方法 printInfo2(T)对于参数(String)不适用
		//添加类型约束只作用于一个方法的多个参数之间、而不涉及到类中的其他方法时。
		p2.printInfo2(new Bird());
		p2.printInfo2(new SmallBird());
		
		//施加类型约束的方法为静态方法,只能将其定义为泛型方法,因为静态方法不能使用其所在类的类型参数。
		Person.printInfo3(new BigBird());
	}
}

结果

handsome
person handsome
1234
3.14
K
鸟吃虫子
小鸟吃米
大鸟啥都吃

以上是关于JAVA泛型的主要内容,如果未能解决你的问题,请参考以下文章

201621123062《java程序设计》第九周作业总结

什么意思 在HashMap之前 ? Java中的泛型[重复]

这个嵌套类构造函数片段可以应用于泛型类吗?

201621123037 《Java程序设计》第9周学习总结

Java——泛型

作业09-集合与泛型