3. Java 泛型编程实践

  • Post author:
  • Post category:java



目录


1. java 方法返回值中使用泛型


2. 定义泛型类、接口


3. 定义泛型方法


4. 泛型字母规范


5. Class、T 、T.class 的区别


6. 如何创建一个 Class 类型的实例,


7. 方法中为什么需要 T 修饰


1. java 方法返回值中使用泛型

public class FooService<T> {
	// 使用泛型返回
	public <T> T getFoo() {
		Foo foo = new Foo();
		foo.setId(1);
		foo.setName("1");
		return (T) foo;
	}
	// 使用Object 直接返回
	public Object getFoObject() {
		Foo foo = new Foo();
		foo.setId(2);
		foo.setName("2");
		return foo;
	}
	
	public static void main(String[] args) {
		FooService<Foo> my = new FooService<Foo>();
		Foo foo1 = my.getFoo();
		System.out.println(foo1.getName());
		Foo foo2 = (Foo) my.getFoObject();
		System.out.println(foo2.getName());
	}
}

使用泛型后 getFoo 方法的返回值不需要强制类型转换,避免了发生

ClassCastException

异常,编译时更安全,因为泛型是再运行时才确定类型,而并非再编译时确定。

所以再使用泛型后,可以再同一方法灵活的实现返回值类型。

2. 定义泛型类、接口

定义任意泛型的类、接口,只要在定义它们时用<>来指定类型参数即可。

例如:public class Fruit<T> { … },其中<T>指定了该泛型的类型参数,这个T是一个类型参数名,用户可以任意命名(就像方法参数的形参名一样),只有在使用该泛型的对象时将T替换成指定的具体类型从而产生一个实例化的泛型对象,例如:Fruit<String> fruit = new Fruit<>(…);

类型形参可以在整个接口、类体内当成普通类型使用,集合所有可使用普通类型的地方都可以使用类型形参,例如

public interface MyGneric<E> {  
    E add(E val);  
    Set<E> makeSet();  
    ...  
}  

定义在<> 中的泛型的类型参数在定义他的类、接口里面就可以像普通类一样使用。

定义泛型构造器:泛型的构造器还是类名本身,不用使用菱形语法,定义构造器无需MyGeneric<T>(…) { … }了,只有在new的时候需要用到菱形语法;

public class MyGenric<T> {  
    MyGeneric(...) { ... }  
    ...  
}  

实现/继承泛型接口/泛型类:

泛型也是在定义的时候必须使用形参(虚拟参数,用户自己随意命名),但是在使用泛型的时候(比如定义泛型引用、继承泛型)就必须使用实参,而泛型的实参就是具体的类型,像String、Integer等具体的类型(当然也可以是自定义类型)。Java还支持一种特殊的语法,可以让你从泛型继续派生出泛型,而泛型的类型参数可以继续传承下去;

class Father<T> { ... }  
  
class Son<T> extends Father<T> { ... }  

这个具体可以看 JDK 的 List 类,Collection 相关接口都是这么设计的。 在编译器中,是无法知道K和V具体是什么类型,只有在运行时才会真正根据类型来构造和分配内存。具体可以参考Map 接口的设计。

public class Container <K, V>{
	private K key;
	private V value;
	public Container(K key, V value) {
		this.key = key;
		this.value = value;
	}
	public K getKey() {
		return key;
	}
	public void setKey(K key) {
		this.key = key;
	}
	public V getValue() {
		return value;
	}
	public void setValue(V value) {
		this.value = value;
	}
	public static void main(String[] args) {
		Container<String, String> container = new Container<String, String>("1", "2");
		System.out.println(container.getKey());
	}

3. 定义泛型方法

定义泛型方法需要在返回值前加入 <T> 来声明这是一个泛型方法。当然返回值也可以不是T。如果在类名旁已经定义了<T>, 也可以直接在方法里返回 T ,不需要再方法的返回值前加入<T> 也可以使用。

public class FooService {
	
	public <T> T getFoo() {
		Foo foo = new Foo();
		foo.setId(1);
		foo.setName("1");
		return (T) foo;
	}
}

public class FooService<T> {
	
	public T getFoo() {
		Foo foo = new Foo();
		foo.setId(1);
		foo.setName("1");
		return (T) foo;
	}
}

Class<T>的作用就是指明泛型的具体类型,而Class<T>类型的变量c,作为Class 类型的对象,c 就可以拥有Class 对象的方法,可以根据反射来创建泛型类的对象。

public class FooService<T> {
	public T getT(Class<T> t) throws InstantiationException, IllegalAccessException {
		T a= t.newInstance();
		return a;
	}
}

4. 泛型字母规范

  • E — Element,常用在java Collection里,如:List<E>,Iterator<E>,Set<E>
  • K,V — Key,Value,代表Map的键值对
  • N — Number,数字
  • T — Type,类型,如String,Integer等等

字母是没有特定意义的!只是为了提高可读性

5. Class<T>、T 、T.class 的区别

单独的T 代表一个类型 ,而 Class<T>代表这个类型所对应的类, Class<?>表示类型不确定的类。

6. 如何创建一个 Class<T> 类型的实例,

可以通过 Class.forName() 或者使用类常量 X.class 。 Class.forName() 被定义为返回 Class<?> 。 另一方面,类常量 X.class 被定义为具有类型 Class<X> , 所以 String.class 是 Class<String>  类型的。

7. 方法中为什么需要 <T> T 修饰

泛型的声明,必须在方法的修饰符(public,static,final,abstract等)之后,返回值声明之前。其中第一个<T>是与传入的参数Class<T>相对应的,相当于返回值的一个泛型,后面的T是返回值类型,代表方法必须返回T类型的(由传入的Class<T>决定)





版权声明:本文为Marain123原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。