`
lwbbupt
  • 浏览: 36159 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

java基础之泛型

阅读更多

         

       这几天粗略的学习了java中的泛型,勉勉强强的把书中“泛型”这一章看完,其中很多的概念还不甚理解,这里只是简单的说一下自己所了解的泛型,不正确的地方还请大家指正。

         泛型是JAVA SE5之后才出现的概念,“泛型”其实就是指类型的参数化。我们知道Java是单继承体系的,这就使得我们在编写程序时,很容易受到限制,不能实现代码的复用。泛型一个重要的应用就是实现了“容器类”,如果不使用泛型,我们只能编写下面的程序:

 

public class Holder {
	  private Object a;
	  public Holder(Object a) { this.a = a; }
	  public void set(Object a) { this.a = a; }
	  public Object get() { return a; }
	  public static void main(String[] args) {
	    Holder h2 = new Holder("The String");
	    String s = (String)h2.get();
	    h2.set(1);
	    Integer x = (Integer)h2.get();
	  }
	} 

 虽然我们可以实现了可以同时持有不同的类型,但是我们取出时必须进行强制类型转化,如果不小心还容易产生CLASSCASTEXCEPTION,如果我们使用泛型的话,可以采用下面的实现方式。

 

class Automobile{}

public class GenericHolder<T> {
	  private T a;
	  public GenericHolder(T a) { this.a = a; }
	  public void set(T a) { this.a = a; }
	  public T get() { return a; }
	  public static void main(String[] args) {
		  GenericHolder<Automobile> h3 =
	      new GenericHolder<Automobile>(new Automobile());
		  Automobile a = h3.get(); // No cast needed
//		  h3.set("The String"); // Error
//		  h3.set(1); // Error
	  }
}

 我们通过将想要持有的对象预先放在尖括号内,编译器就会对放置的类型进行检测,防止出错。泛型应用于接口与泛型类差不多,这里就不在解释了。

        泛型也可以应用于方法,泛型方法使得方法能够独立于类产生变化。泛型方法与其所处的类是否为泛型是没有任何关系的。

 

public class GenericMethods {
	public <T> void f(T x) {
		System.out.println(x.getClass().getName());
	}

	public static void main(String[] args) {
		GenericMethods gm = new GenericMethods();
		gm.f("");
		gm.f(1);
		gm.f(1.0);
		gm.f(1.0F);
		gm.f('c');
		gm.f(gm);
	}
}

       对比以上的泛型方法与泛型类,可以看出:当使用泛型类是,必须在创建对象的时候指定类型参数的值,而使用泛型方法时,通常不用指明参数的类型;编译器可以通过类型推断为我们找出具体的类型(只在赋值操作时起作用)。其他的时候也需要显示的指明类型,此时可以在点操作符与方法名之间插入尖括号例如:

gm.<GenericMethods>f(gm);

 可以看出泛型方法较泛型类更加的简洁和容易理解,所以我们在编程时应当尽量的使用泛型方法。

          以上为泛型的基础应用,在学习泛型时,看到下面一处奇怪的代码:

 

public class ErasedTypeEquivalence {
	 @SuppressWarnings("rawtypes")
	public static void main(String[] args) {
		    Class c1 = new ArrayList<String>().getClass();
		    Class c2 = new ArrayList<Integer>().getClass();
		    System.out.println(c1 == c2);
		  }
}

         对于ArrayList<String>和ArrayList<Integer>我们很容易认为它们是不同的类型,的确它们是不同的类型然而程序的输出却是"TRUE";这是由于Java的泛型是使用擦出来实现的,当我们使用泛型时,任何具体的类型信息都被擦除了,List<String>和List<Integer>在运行时都被擦出为成原生类型List了。

        然而Java为什么要这么做呢?我们前面提到泛型在JAVA SE5之后才出现的,Java泛型使用擦除的原因在于向前兼容。即即使类库进行了代码改造使用了新的泛型实现,客户端依然可以在不改变代码的前提下继续使用这个类库,使得在不破坏现有类库的前提下,将泛型融入到了java语言中。

       然而通过擦除实现了Java中的泛型,必然会付出一定的代价,使得Java中的泛型不能用于显示地引用运行时类型的操作之中,例如:转型、instanceof操作和new表达式,而这些在C++中是可以使用的,在java中我们可以通过反射来间接的实现。由于擦出的存在下面这种方式试图重载方法是不正确的:

 

public class UseList<W, T> {
//错误:Method f(List<T>) has the same erasure f(List<E>) as another method in type UseList<W,T>
	void f(List<T> v){}
	void f(List<W> v){}
}

 

所以我们应当注意的是java中的泛型只有在静态类型检查期间才会出现,在此之后,程序中所有的泛型类型都将被擦出,替换为它的非泛型边界。

      什么边界?

 边界使得我们可以在用于泛型的参数类型上设置限制条件:一方面使得我们可以规定泛型参数可以使用的类型,另一方面使得我们可以按照自己的边界类型来调用方法。例如:

 

class HasF {
	public void f() {
		System.out.println("HasF.f()");
	}
}

class Manipulator<T> {
	private T obj;

	public Manipulator(T x) {
		obj = x;
	}

	// Error: 未发现方法f()
	public void manipulate() {
		obj.f();
	}
}

public class Manipulation {
	public static void main(String[] args) {
		HasF hf = new HasF();
		Manipulator<HasF> manipulator = new Manipulator<HasF>(hf);
		manipulator.manipulate();
	}
} 

 由于使用了擦除,Java编译器不能obj可以调用方法f()映射到HasF拥有方法f()这个情况上,此时我们可以使用边界,例如:

 

 

class Manipulator2<T extends HasF> {
	private T obj;
	public Manipulator2(T x) {
		obj = x;
	}
	public void manipulate() {
		obj.f();
	}
}

 此时obj可以直接调用方法f()了。然后我们可以看出此时这样的泛型并没有给我们任何好处,我们不使用泛型,只需将Manipulator<T>中的T替换为HasF并且使用原生类Manipulator也可以实现以上的功能。所以使用泛型时我们最好自己的思考一下,我们的问题是否足够的复杂到必须使用泛型的程度,也就是说我们编写的这部分代码能够跨多个类进行工作。

 

       我们看到

class Manipulator2<T extends HasF>

这里的extends与我们在普通情况下看到的是不同的,因为java泛型重用了extends关键字。

 

     我们继续分析下面一个比较难以理解的例子:

 

class Fruit {}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Orange extends Fruit {}

public class GenericsAndCovariance {
	 public static void main(String[] args) {
		    //编译错误:Type mismatch: cannot convert from ArrayList<Apple> to List<Fruit>
    	 	//List<Fruit> flist = new ArrayList<Apple>();
		    List<? extends Fruit> flist = new ArrayList<Apple>();
		    // 编译错误:不能添加任何类型
		    //flist.add(new Apple());
		    // flist.add(new Fruit());
		    // flist.add(new Object());
		    flist.add(null); // Legal but uninteresting
		    Fruit f = flist.get(0);
		    
		    List<Fruit> flist2 = new ArrayList<>();
		    flist2.add(new Apple());
		  }
}

 

 

 如下:List<Fruit>表示持有Fruit以及Fruit的子类型,我们可以向flist2中添加Fruit的子类型Apple,然而Apple的List在类型上并不等价于Fruit的List

List<Fruit> flist = new ArrayList<Apple>();

再看flist类型为List<? extends Fruit>我们可以理解为“具有任何从Fruit继承的某种类型的列表” 通配符表示flist指向某种没有指定的具体类型,听着有些拗口,这里?指某种而不是某些。如果允许以下操作:

flist.add(new Apple());
flist.add(new Fruit());
flist.add(new Object());

 我们在取出时由于不知道flist中的具体类型,又必须进行强制类型转换,这与泛型的思想又是背道而驰的。

所以我们在使用<? extends SomeClass>时,意味着我们只能通过这个名称来获取或者移除某些信息,而不能增加它的信息。

       如果想要写入或者增加信息我们可以使用超类行通配符<? super SomeClass>,例如:

public class SuperTypeWildcards {
	static void writeTo(List<? super Apple> apples) {
	    apples.add(new Apple());
	    apples.add(new Jonathan());
	    // apples.add(new Fruit()); // Error
	  }
}

 泛型中还存在另一种无界通配符<?>,意味着任何事物”

public class UnboundedWildcards1 {
	  @SuppressWarnings("rawtypes")
	static List list1;
	  static List<?> list2;
	  static List<? extends Object> list3;
	static void assign1(@SuppressWarnings("rawtypes") List list) {
	    list1 = list;
	    list2 = list;
	    list3 = list; // Warning: unchecked conversion
	    // Found: List, Required: List<? extends Object>
	  }
	  static void assign2(List<?> list) {
	    list1 = list;
	    list2 = list;
	    list3 = list;
	  }	
	  static void assign3(List<? extends Object> list) {
	    list1 = list;
	    list2 = list;
	    list3 = list;
	  }
	  @SuppressWarnings("rawtypes")
	public static void main(String[] args) {
		list1.add(new Integer(1));
		
//		错误list2与list3中只能添加null
//		list2.add(new Integer(1));
//		list3.add(new Integer(1));
		
	    assign1(new ArrayList());
	    assign2(new ArrayList());
	    assign3(new ArrayList()); // Warning:
	    // Unchecked conversion. Found: ArrayList
	    // Required: List<? extends Object>
	    assign1(new ArrayList<String>());
	    assign2(new ArrayList<String>());
	    assign3(new ArrayList<String>());
	    // Both forms are acceptable as List<?>:
	    List<?> wildList = new ArrayList();
	    wildList = new ArrayList<String>();
	    assign1(wildList);
	    assign2(wildList);
	    assign3(wildList);
	  }
	}

 List表示“持有任何Object类型的原生List”,List<?>表示“具有某种特定类型的非原生List”。

List<?>和List<? extends Object>大致是等价的。

    所以我们应当理解泛型的含义就是:泛化的类型,适用于很多的类型。通过泛型我们可以编写出更“泛化”的代码,这些代码对于它能作用的类型具有更少的限制,使得单个的代码段可以应用到更多的类型上。

  • 大小: 7.7 KB
2
0
分享到:
评论
1 楼 LiangXunfly 2015-12-17  
楼主好棒,我看java编程思想的泛型看了好久都没看懂,擦除不是分两次吗,我对这两次的界定很不清楚,而且我不清楚为什么List<Fruit> list1 = new ArrayList<Apple>()有编译错误,而List list2 = new ArrayList<Apple>()能通过编译,希望能指导下

相关推荐

    关于java基础的泛型的练习

    Java泛型是Java SE 5.0引入的一个重要特性,它极大地增强了代码的类型安全...在进行"关于Java基础的泛型的练习"时,可以尝试编写不同的泛型类、泛型方法,体验泛型带来的便利,并理解其背后的类型系统和类型擦除机制。

    【Java基础】泛型方法 - 右撇子 - 博客频道 - CSDN.NET

    【Java基础】泛型方法 - 右撇子 - 博客频道 - CSDN.NET

    java基础-泛型通配符

    java基础-泛型通配符

    大学课程讲义-Java基础-泛型.docx

    Java泛型是Java编程语言中的一种特性,它允许在数据结构(如集合)中存储特定类型的元素,从而提供了编译时的类型安全性和更清晰的代码。泛型引入的主要目标是...理解并熟练使用泛型是每个Java开发者的基础技能之一。

    java集合 框架 泛型

    泛型是Java 5引入的一项创新特性,极大地增强了集合框架的安全性和效率。本讲解将深入探讨这两个主题,以及与之相关的枚举类型。 首先,Java集合框架包括List、Set、Queue等接口,以及ArrayList、LinkedList、...

    关于C#、java泛型的看法

    在Java中,泛型同样使用尖括号表示,但它的类型擦除特性使得编译后的字节码并不包含类型参数信息,而是使用Object或其他基础类型作为替代。这意味着Java的泛型不支持协变和逆变,但可以通过通配符(如?)来放宽类型...

    Java基础篇:泛型.pdf

    泛型是Java编程语言中用于减少类型转换错误和增强代码安全性的机制,它允许在定义类、接口和方法时使用类型参数。通过这种方式,可以在编译时期捕获那些只有在运行时期才会暴露的类型错误,提高了代码的健壮性。 ...

    Java基础入门四泛型反射注解.pdf

    Java基础入门系列是帮助初学者掌握Java编程的重要学习材料,本篇主要介绍了泛型、反射和注解这三大高级特性。 泛型是Java SE 5版本引入的一个新特性,它的主要目的是允许在使用类、接口和方法时能够引用到任何类型...

    java 继承非泛型类示例

    在Java编程语言中,继承是面向对象特性之一,它允许一个类(子类)继承另一个类(父类)的属性和方法。当我们谈论继承非泛型类时,意味着子类继承了一个没有使用泛型的父类。泛型是Java SE 5.0引入的新特性,用于...

    Java基础笔记之集合框架和泛型

    详细的介绍了集合框架的用法,及其语法规则,剖析了使用的使用注意事项,帮助更牢靠的掌握集合框架的知识及泛型内容。谢谢

    java基础-泛型学习总结

    思维导图

    java基础泛型 学习全文件

    Java基础泛型是Java编程语言中的一个重要特性,它允许在类、接口和方法中使用类型参数,从而增强了代码的类型安全性和重用性。在Java中,泛型的主要目标是提高效率,避免运行时的类型转换,并且在编译时期就能发现...

    java泛型的内部原理及更深应用

    这意味着在编译完成后,所有的泛型信息都会被擦除,替换为Object或者其他基础类型。因此,泛型在运行时并不存在,所有关于泛型的操作都在编译期间完成。 2. **边界通配符**:在处理泛型时,我们经常遇到边界通配符...

    Java课件 (包括基础语法,数组与语句,面向对象编程,java异常处理,java常用基础类,java集合与泛型

    1. **基础语法**:Java的基础语法是学习任何编程语言的起点。这包括变量声明、数据类型(如整型、浮点型、字符型和布尔型)、运算符(算术、比较、逻辑等)、流程控制(如if语句、switch语句、for循环、while循环)...

    java零基础自学 之 JAVA泛型

    在Java编程中,泛型是一种强大的特性,它允许我们在编写代码时指定容器(如列表、队列或映射)所容纳的数据类型。这为程序带来了更高的类型安全性和可读性,同时减少了运行时类型转换异常的可能性。本文将深入探讨...

    java泛型学习ppt

    泛型基础: * 在定义泛型类或声明泛型类的变量时,使用尖括号来指定形式类型参数。 * 当声明或者实例化一个泛型的对象时,必须指定类型参数的值。 自定义简单泛型: * public class Gclass&lt;T&gt;{ private T a; ...

Global site tag (gtag.js) - Google Analytics