JAVA从入门到进阶(十)——泛型

一:
泛型是在JDK1.5版本出现的安全机制。
为什么需要泛型?
1.将运行时期的问题classcastException转化到编译问题。
2.避免了强制转化的麻烦。

public class Genneticdemo1 {
public static void main(String []args)
{
	ArrayList ar=new ArrayList();
	ar.add("dwe");
	ar.add("sads");
	//ar.add(5);
	for(Iterator in=ar.iterator();in.hasNext();)
	{
		String str=(String)(in.next());
		System.out.println(str);
	}
	

在运行编译时的语句是抛出了类型转换异常,我们就可以借助泛型来完成。
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?
顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),
然后在使用/调用时传入具体的类型(类型实参)。
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
什么时候用泛型呢?
当操作的引用数据类型不确定的时候就使用泛型,将要操作的引用数据类型传入即可。其实泛型就是一个用于接受具体应用数据类型的参数范围。

public class Genneticdemo1 {
public static void main(String []args)
{
	ArrayList<String> ar=new ArrayList<String>();
	ar.add("dwe");
	ar.add("sads");
	//ar.add(5);
	for(Iterator<String> in=ar.iterator();in.hasNext();)
	{
		String str=in.next();
		System.out.println(str);
	}
	
}
}
采用泛型写法后,在//1处想加入一个Integer类型的对象时会出现编译错误,通过
List<String>,直接限定了list集合中只能含有String类型的元素,从而在//2处无须进
行强制类型转换,因为此时,集合能够记住元素的类型信息,编译器已经能够确认
它是String类型了。

泛型是用于编译时期,确保了类型的安全。运行时,会将泛型擦除在生成的class文件是不带泛型的。在后期会有泛型的补偿机制,不需要再进行强制类型转换。

二:泛型的基本用法
1.泛型类


class Box<T>
{
	private T data;

	public T getData() {
		return data;
	}

	public void setData(T data) {
		this.data = data;
	}

	public Box(T data) {
		super();
		this.data = data;
	}

	public Box() {
		super();
		// TODO Auto-generated constructor stub
	}
	
}

public class Genneticdemo2 {
public static void main(String []args)
{
	Box<String> b1=new Box<String>("asd");
	Box<Integer> b2=new Box<Integer>(5);
	System.out.println(b1.getClass().equals(b2.getClass()));//返回true
	
}
}

由此,我们发现,在使用泛型类时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,传入不同泛型实参的泛型类在内存上只有一个,即还是原来的最基本的类型(本实例中为Box),当然,在逻辑上我们可以理解成多个不同的泛型类型。也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。
2.泛型接口
两种实现方式;①是在子类明确实例化的类型②是继续使用泛型来实现接口

interface Gendemo1<T>
{
	
	public T  show();
}
class demo1 implements Gendemo1<String>
{
	public String show()
	{
		return "test";
	}
}
class demo3 <T> implements Gendemo1<T>
{
	public T show()
	{
		return null;
	}
}

3.泛型方法

void show_1(T t){
            System.out.println(t.toString());
        }
    //在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
    //由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
    public <E> void show_3(E t){
        System.out.println(t.toString());
    }

    //在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
    public <T> void show_2(T t){
        System.out.println(t.toString());
    }
}

public static void main(String[] args) {
    Apple apple = new Apple();
    Person person = new Person();

    GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();
    //apple是Fruit的子类,所以这里可以
    generateTest.show_1(apple);
    //编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person
    //generateTest.show_1(person);

    //使用这两个方法都可以成功
    generateTest.show_2(apple);
    generateTest.show_2(person);

    //使用这两个方法也都可以成功
    generateTest.show_3(apple);
    generateTest.show_3(person);
}

}
②静态方法
当方法静态时,不能使用类上定义的泛型,如果静态方法需要用到泛型,就定义为静态泛型方法。
原因:静态方法不需要对象来进行,而类上定义的泛型需要指定对象。

```
public class StaticGenerator<T> {
    ....
    ....
    /**
     * 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
     * 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
     * 如:public static void show(T t){..},此时编译器会提示错误信息:
          "StaticGenerator cannot be refrenced from static context"
     */
    public static <T> void show(T t){

    }
}

三 通配符?

类型通配符一般是使用 ? 代替具体的类型实参。注意了,此处是类型实参,而不是类型形参!且Box<?>在逻辑上是Box、Box…等所有Box<具体类型实参>的父类。由此,我们依然可以定义泛型方法,来完成此类需求。

1 public class GenericTest {
 2 
 3     public static void main(String[] args) {
 4 
 5         Box<String> name = new Box<String>("corn");
 6         Box<Integer> age = new Box<Integer>(712);
 7         Box<Number> number = new Box<Number>(314);
 8 
 9         getData(name);
10         getData(age);
11         getData(number);
12     }
13 
14     public static void getData(Box<?> data) {
15         System.out.println("data :" + data.getData());
16     }
17 
18 }
class Person
{
	private String name;
	private int age;
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	public Person() {
		super();
		// TODO Auto-generated constructor stub
	}
	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;
	}
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
	
}
public class Genneticdemo4 {
public static void main(String []args)
{
	ArrayList <String> c1=new ArrayList<String>();
	LinkedList <Integer> c2=new LinkedList<Integer>();
	HashSet<Person> c3=new HashSet<Person>(); 
	c1.add("dawd");
	c1.add("deeg");
	c2.add(23);
	c2.add(34);
	c3.add(new Person("wang",78));
	c3.add(new Person("peng",75));
	CollectionOutput(c1);
	CollectionOutput(c2);
	CollectionOutput(c3);
}
public static void CollectionOutput(Collection<?> coll)
{
	Iterator<?> in=coll.iterator();
	while(in.hasNext())
	{
		System.out.println(in.next());
	}
}
}

四 泛型上限,下限
在使用泛型的时候,我们还可以为传入的泛型类型实参进行上下边界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。
结果:为泛型添加上边界,即传入的类型实参必须是指定类型的子类型

public void showKeyValue1(Generic<? extends Number> obj){
    Log.d("泛型测试","key value is " + obj.getKey());
}
Generic<String> generic1 = new Generic<String>("11111");
Generic<Integer> generic2 = new Generic<Integer>(2222);
Generic<Float> generic3 = new Generic<Float>(2.4f);
Generic<Double> generic4 = new Generic<Double>(2.56);

//这一行代码编译器会提示错误,因为String类型并不是Number类型的子类
//showKeyValue1(generic1);

showKeyValue1(generic2);
showKeyValue1(generic3);
showKeyValue1(generic4);

如果我们把泛型类的定义也改一下:


public class Generic<T extends Number>{
    private T key;

    public Generic(T key) {
        this.key = key;
    }

    public T getKey(){
        return key;
    }
}

 

//这一行代码也会报错,因为String不是Number的子类
Generic<String> generic1 = new Generic<String>("11111");

我们通常在集合中使用泛型,在开发中使用泛型会是代码更加简洁。

猜你喜欢

转载自blog.csdn.net/weixin_43752167/article/details/87380898