java8教程-泛型(Generics)

泛型(已更新)

在任何繁琐的(nontrivial)软件项目中,bug是家常便饭。细心的规划,编程和测试可以帮助减少bug的普遍性(pervasiveness),但是无论如何,无论在哪里,bug总会伺机悄悄溜进(creep)你的代码,因为很明显,新的特性会不断的被引入,并且你的代码基数会不断变大和复杂。

幸运的是,一些bug相比其它比较容易检测。编译时bug可以在早期被检测到;你可以利用编译器的错误信息查明是什么问题并且解决,就在那时。然而,运行时bug会更加未预知,他们不会立即展示出来,不知道什么时候发生,可能根本不在程序真正出现问题的点上。

泛型通过更多的在编译时检测bug为你的代码增加了稳定性。

为什么要用泛型

简言之,泛型能够使类型(类和接口)在定义类,接口和方法的时候参数化。非常像方法定义时用到的形式参数(formal parameters),类型参数提供了一种你可以通过不同的输入来复用同一段代码的方法。不同点是,形式参数输入的是,而类型参数输入的是类型

使用泛型比非泛型有很多好处:

  • 编译时更强大的类型检测

    Java编译器对泛型应用了强大的类型检测,如果代码违反了类型安全就会报错。修复编译时错误比修复运行时错误更加容易,因为运行时错误很难查找到。

  • 消除类型转换(Elimination of casts)

    以下代码片段没有泛型需要转型:

    List list = new ArrayList();
    list.add(“hello”);
    String s = (String) list.get(0);
    当我们重新用泛型编写,代码就不需要类型转换了:

    List list = new ArrayList();
    list.add(“hello”);
    String s = list.get(0); // no cast

  • 使开发者实现泛型算法

通过泛型,开发者可以自己实现泛型算法,应用到一系列的不同类型,可以自定义,并且类型安全,易读。

泛型类型

泛型类型是泛型类或者接口被类型参数化。下面的Box类将被更改演示这个概念。

简单的 Box 类

列举一个简单的非泛型 Box操作任意类型的object。它只需要提供两个方法:set,添加一个obejct到box,get,获取这个对象:

public class Box {
private Object object;

public void set(Object object) { this.object = object; }
public Object get() { return object; }
}

因为它的方法接收或返回一个对象,你可以任意传入,只要传入的不是原始数据类型。我们没有在编译时辨别clas如何使用的。一边可能替换一个 Integer到box,另一边获取的不是Integer类型,而可能传入一个String类型,结果会导致运行时错误。

泛型版本的Box

泛型类的定义形式如下:

class name<T1, T2, ..., Tn> { /* ... */ }

类型参数部分被一对尖括号(<>)划分,紧跟类名,它指定了类型参数(也叫作类型变量)T1, T2, ….,和Tn.

把原Box类更新为泛型类,你要通过把“public class Box”改变为“public class Box”创建一个类型声明。这会引入一个类型变量, T,你可以在类中任意地方使用。通过这个改变,Box类就变为:

/**
* Generic version of the Box class.
* @param <T> the type of the value being boxed
*/
public class Box<T> {
// T stands for "Type"
private T t;

public void set(T t) { this.t = t; }
public T get() { return t; }
}

你可以看到,所有Object出现的地方都被替换为T了。一个类型变量可以指定为任意非原始类型的类型:任意的类,任意的接口,任意的数组,甚至其他的类型变量。同样的技术可以应用到创建泛型接口上。

类型参数命名规则(Naming Conventions)

通过规则,类型参数是单独的,大写字母。这个表示鲜明区别了你已知的变量命名规则,一个好的理由是:没有这个规则,你将很难区分类型变量和原生类或接口名的区别。

最普遍使用的类型参数是:

  • E -Element(Java Collections框架大量使用)
  • K -Key
  • N -Number
  • T -Type
  • V -Value
  • S,U,V 等 -第二,第三,第四个类型

你可以在JAVA SE API 看到这些名字的使用。

调用和实例化一个泛型类型

要在你的代码引用泛型类 Box,你必须执行 泛型类型调用,把T替换成具体的值,比如Integer:

Box<Integer> integerBox;

你可以认为泛型类型调用跟原生方法调用大致一样,但是不是传入一个参数到方法,而是传入一个类型蚕食–这个情况下的Integer–给Box类本身。

Type ParameterType Argument术语(Terminology):
很多开发者交换使用这个两个术语,但是这两个术语并不同。敲代码时,
type argument 创建一个参数化类型,因此,Foo< T>中的T是type parameter,Foo< String> f中的String是一个type argument。

就想其他的变量定义,上面的代码不会真正创建一个新的 Box对象。它只是声明,integerBox将持有一个“Box of Integer”的引用,用以读取Box.泛型类型的调用通常称为参数化类型。

为了实例化这个类,用new 关键字,把放在类名和括号之间。

Box<Integer> integerBox = new Box<Integer>();

The Diamond

在Java SE 7及以后版本,可以省去类型参数调用泛型类的构造函数,用一个空的类型参数(<>),编译器可以通过上下文决定,或推测type arguments,这个尖括号非正式得叫作diamond(钻石?这么奇葩),你可以这样创建Box< Integer>的一个实例:

Box<Integer> integerBox = new Box<>();

要查看更多关于diamond 符号和类型推断(inference),请看类型推断

多类型参数

正如前面提到的,泛型类可以有多个类型参数。比如泛型 OrderedPair 类,实现了泛型接口 Pair:

public interface Pair<K, V> {
public K getKey();
public V getValue();
}

public class OrderedPair<K, V> implements Pair<K, V> {

private K key;
private V value;

public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}

public K getKey()   { return key; }
public V getValue() { return value; }
}

下面的语句创建了两个OrderedPair的实例:

Pair<String, Integer> p1 = new OrderedPair<String, Integer>("Even", 8);
Pair<String, String>  p2 = new OrderedPair<String, String>("hello", "world");

new OrderedPair

参数化类型

你也可以用一个参数化的类型(ie List< String>)替换(substitute)类型参数(K ,V),例如用OrderedPair< K,V>:

OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));

原类型(Raw Types)

原类型是指泛型类或泛型接口的名字没有任何参数,比如,给出泛型类Box:

public class Box(T){
public void set(T t){
/* ...... */
}
}

你可以为形参T赋值一个真实的类型参数来创建一个参数化类型的 Box(T):

Box(Ingeter) intBox=new Box<>();

如果真实的类型参数被省略掉了,你就创建了一个原类型的Box:

Box rawBox =new Box();

因此,Box是Box的原类型。然而,非泛型类或非泛型接口没有原类型。
原类型出现在遗赠的代码里是因为大量的API类(比如Collections类)在JDK5之前不是泛型类。当使用原类型的时候,你本质上使用的是泛型之前的表现—Box ->Object.为了向后兼容,赋值参数化类型给他的原类型是允许的:

Box<String> stringBox=new Box<>();
Box rawBox=stringBox;    //OK

但是如果你赋值一个原类型给一个参数化的类型,你将得到警告:

Box rawBox=new Box(); //rawBox是Box<T>()的原类型
Box<Integer> intBox=rawBox;  //warning:unchecked conversion

当你用原类型调用关联的反省类型的泛型方法时,你也会得到警告:

Box<String> stringBox=new Box<>();
Box rawBox=stringBox;
rawBox.set(8);  //waring: unchecked invocation to set(T)

警告显示原类型绕过泛型类型检查,延迟捕获不安全代码到运行时。因此,你需要避免使用原类型。类型擦除部分会有更多关于Java编译器如何使用原类型的内容。

Unchecked Error Messages

正如上面提到的,当混合遗赠代码和泛型代码时,你可能会碰到跟下面相似的警告:

Note: Example.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

这发生在当使用老的API操作原类型时,例如如下代码:

public class WarningDemo {
Box<Integer> bi;
bi=createBox();
}
static Box createBox(){
return new Box();
}

‘unchecked’指的是编译器没有足够的类型信息来执行所有必要的类型检查以保证类型安全。

### Java 全面知识点与基础教程 #### 什么是JavaGenerics)是一种提供编译时类检查的机制,允许在类、接口和方法中定义、传递和操作各种类的对象,而无需明确指定具体类。这种设计可以增强代码的可读性、可维护性,并减少类转换错误[^1]。 --- #### 的关键概念 ##### 1. **类** 类是指可以在类声明时使用一个或多个类参数的类。这些类参数可以用作字段、方法返回值或方法参数的占位符。 - **基本格式**: ```java public class Box<T> { private T content; public void setContent(T content) { this.content = content; } public T getContent() { return content; } } ``` - **注意事项**: - 类的实际类是在创建对象时指定的。 - 编译器会在运行时执行类擦除,因此无法在类中直接使用 `new T()` 创建实例[^3]。 --- ##### 2. **接口** 类似于类,接口也可以接受类参数。 - **基本格式**: ```java public interface Generator<T> { T next(); } ``` - **注意事项**: - 实现接口的类可以选择固定某个类或者保留特性。 ```java // 固定类 public class StringGenerator implements Generator<String> { @Override public String next() { return "Hello"; } } // 保持 public class GenericGenerator<T> implements Generator<T> { @Override public T next() { return null; // 或者其他逻辑 } } ``` --- ##### 3. **方法** 方法是指在其签名中包含自己的类参数的方法。即使该方法属于非类,它仍然可以独立于类的类参数工作。 - **基本格式**: ```java public <T> void method(T param) {} ``` - **示例**: ```java public class Util { public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) { return p1.getKey().equals(p2.getKey()) && p1.getValue().equals(p2.getValue()); } } class Pair<K, V> { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; } } ``` --- ##### 4. **通配符** 通配符用于表示未知类,增强了使用的灵活性。 - **协变 (`<? extends T>`)**:只允许访问子类的数据。 ```java List<? extends Number> list = new ArrayList<>(); Number num = list.get(0); // 可以获取数据 // list.add(new Integer(1)); 不支持添加任何元素 ``` - **逆变 (`<? super T>`)**:只允许访问父类的数据。 ```java List<? super Integer> list = new ArrayList<>(); list.add(new Integer(1)); // 支持添加Integer及其子类 Object obj = list.get(0); // 获取到的是Object类 ``` - **无界通配符 (`<?>`)**:表示任意类。 ```java List<?> list = new ArrayList<>(); Object obj = list.get(0); ``` --- ##### 5. **类擦除** 类擦除是 Java 中的一个重要概念,指的是在编译阶段会移除所有的信息,在字节码层面不再存在具体的类约束。 - **影响**: - 运行时无法判断的具体类- 静态上下文中不允许使用变量。 - **解决方式**: 使用反射或其他工具(如 TypeToken),可以通过间接手段恢复部分类信息。 --- ##### 6. **数组与** 由于数组具有运行时类检查的能力,而依赖于编译期类检查,这使得两者之间存在冲突。因此,不能创建带有的数组。 - 错误示例: ```java List<String>[] stringLists = new List<String>[1]; // 编译报错 ``` - 替代方案: ```java List<String>[] stringLists = (List<String>[]) new List<?>[1]; ``` --- #### 总结 Java 的核心在于提升代码的安全性和复用能力。通过了解其核心概念(如类、接口、方法、通配符和类擦除),开发者能够更高效地构建灵活且健壮的应用程序。 --- ### 示例代码 以下是综合运用的知识点的简单例子: ```java public class Main { public static void main(String[] args) { Box<Integer> integerBox = new Box<>(); integerBox.setContent(10); System.out.println(integerBox.getContent()); Pair<String, Integer> pair = new Pair<>("Key", 123); System.out.println(pair.getKey() + ": " + pair.getValue()); List<Number> numbers = Arrays.asList(1, 2.5f, 3L); printNumbers(numbers); List<Object> objects = Arrays.asList("String", new Object(), 42); addObject(objects, "New Element"); } public static <T> void printNumbers(List<? extends Number> list) { for (Number number : list) { System.out.print(number.doubleValue() + " "); } System.out.println(); } public static <T> void addObject(List<? super T> list, T element) { list.add(element); } } class Box<T> { private T content; public void setContent(T content) { this.content = content; } public T getContent() { return content; } } class Pair<K, V> { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; } } ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值