目录
1.java泛型
2. 泛型类
3.泛型接口
4.泛型方法:
5.元组:
1.java泛型
需要理解泛型的边界在哪里?只有理解边界所在,你才能成为程序高手,因为只有知道了某个技术不能做什么,你才能更好的做到所能做的。
只要带有<>的类或者接口,都属于带有类型参数的类或者接口,在使用这些类或者接口时,必须给<>中传递一个具体的引用数据类型。
2. 泛型类
泛型的类的定义:
泛型类的使用:
泛型类的注意点:泛型类的类型约束只在编译的时候有效,JVM编译正确后会将泛型的信息擦除掉,并且在对象进入和离开方法的边界处,添加类型检查和类型转化的方法。
泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛形的java程序后,生成的class文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。
验证泛型类的类型约束只在编译的时候有效:
进一步 验证运行时没有泛型类型的信息:先编译java文件
泛型类的继承:
- 实现类的要是重写父类的方法,返回值的类型是要和父类一样的!
- 类上声明的泛形只对非静态成员有效
这样使用继承可定是不行的,因为Integer虽然继承自Number,但是GenericClassExample<Integer>并不继承自GenericClassExample<Number>
那么泛型类如何使用继承呢?有三种方法
1.使用?,但是这样会使泛型失去意义
2.给泛型加上上边界 ? extends E
3.加入下边界 ? super E
泛型通配符的使用准则:
有上限下限的泛型通配符:
,当我们使用?号通配符的时候:就只能调对象与类型无关的方法,不能调用对象与类型有关的方法。无论是设定通配符上限还是下限,都是不能操作与对象有关的方法,只要涉及到了通配符,它的类型都是不确定的!
- 如果参数之间的类型有依赖关系,或者返回值是与参数之间有依赖关系的。那么就使用泛型方法
- 如果没有依赖关系的,就使用通配符,通配符会灵活一些.
3.泛型接口
上面讲的规则同样适用泛型接口
泛型可以应用于接口,例如生成器(常用的场景),他也是一种专门负责创建对象的类,这是工厂方法设计模式的一种应用,不过当使用生成器来创建新的对象时,他不需要任何的参数,而工厂方法一般需要参数,也就是说生成器无需额外的信息就知道如何创建新的对象。
泛型接口的定义:
泛型类实现泛型接口:
泛型接口的第二个例子:
泛型接口的用处,可以编写一个类来实现泛型接口,它能够随机生成不同类型的Coffee对象。
public class Coffee {
private static long counter = 0;
private final long id = counter++;
@Override
public String toString() {
return getClass().getSimpleName() + " " + id;
}
}
public class Latte extends Coffee {
}
public class Mocha extends Coffee {
}
public class Americano extends Coffee {
}
不仅实现了泛型的接口,
public class CoffeeGenerator implements Generator<Coffee> {
private Class[] types = {Latte.class, Mocha.class, Americano.class};
private static Random random = new Random(47);
@Override
public Coffee next() {
try {
return (Coffee) types[random.nextInt(types.length)].newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
CoffeeGenerator gen = new CoffeeGenerator();
for (int i = 0; i < 3; i++) {
System.out.println(gen.next());
}
}
}
结果:
但是现在我觉得这种生成的方式我不喜欢,我要编写一个实现了Iterable的Coffee对象生成器,我们的一个选择时重写这个类,令其实现Iterable接口,不过你并不是总能拥有源代码的控制权,并且除非必须这么做,否则我们也不愿意重写这个类,我们还有另外的一种选择就是创建一个适配器,来实现所需的接口。
public class IterableCoffeeGeneratorAdapter extends CoffeeGenerator implements Iterable<Coffee> {
private int size = 0;
public IterableCoffeeGeneratorAdapter(int size) {
this.size = size;
}
@Override
public Iterator<Coffee> iterator() {
return new Iterator<Coffee>() {
int count = size;
@Override
public boolean hasNext() {
return count > 0;
}
@Override
public Coffee next() {
count--;
return IterableCoffeeGeneratorAdapter.super.next();
}
};
}
public static void main(String[] args) {
for (Coffee coffee : new IterableCoffeeGeneratorAdapter(3)) {
System.out.println(coffee);
}
}
}
实现了Iterable接口,就必须要实现iterator()方法,这个方法返回的是一个Iterator对象,有了这个Iterator对象才能在循环语句中去是使用这个适配器对象。ok,这个使用的是匿名内部类的方式来实现的Iterator的逻辑。当然可以使用内部类的方式来实现这个Iterator对象。
public class IterableCoffeeGeneratorAdapter extends CoffeeGenerator implements Iterable<Coffee> {
private int size;
public IterableCoffeeGeneratorAdapter(int size) {
this.size = size;
}
class CoffeeIterator implements Iterator<Coffee> {
int count = size;
@Override
public boolean hasNext() {
return count > 0;
}
@Override
public Coffee next() {
count--;
return IterableCoffeeGeneratorAdapter.super.next();
}
}
@Override
public Iterator<Coffee> iterator() {
return new CoffeeIterator();
}
public static void main(String[] args) {
for (Coffee coffee : new IterableCoffeeGeneratorAdapter(3)) {
System.out.println(coffee);
}
}
}
测试:
4.泛型方法:
在任何时候,如果使用泛型方法可以取代将整个类泛型化,那么就应该只使用泛型方法。因为它可以使事情更清楚明白,另外对于一个static的方法而言,无法访问泛型类的类型参数,所以如果static方法需要泛型的能力,就必须使其成为泛型的方法。
泛型方法必须要有泛型标示符,泛型标示符和方法返回值没啥关系。
泛型方法的使用:泛型方法传入的参数类型和泛型类的没啥关系,泛型类中的非泛型方法比如handleSomething,他的返回值类型就和泛型类相关。
泛型方法里面的字母和泛型类的是一样的。但是他们真的没啥关系
泛型字母的含义:
public class New {
public static <K, V> Map<K, V> map() {
return new HashMap<K, V>();
}
public static <T> List<T> list() {
return new ArrayList<T>();
}
public static <T> List<T> llist() {
return new LinkedList<T>();
}
public static <T> Set<T> set() {
return new HashSet<T>();
}
public static void main(String[] args) {
List<String> strList = New.list();
}
}
泛型方法与可变参数也能够很好的共存,
欧克,之前的生成器我们使用泛型的方式来实现:
public interface Generator<T> {
T next();
}
public class BaseGenerator<T> implements Generator<T> {
private Class<T> type;
public BaseGenerator(Class<T> type) {
this.type = type;
}
@Override
public T next() {
try {
return type.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static <T> Generator<T> create(Class<T> type){
return new BaseGenerator<T>(type);
}
}
测试:
public class Test4 {
public static void main(String[] args) {
Generator<Coffee> generator = BaseGenerator.create(Coffee.class);
Coffee coffee = generator.next();
System.out.println(coffee);
}
}
5.元组:
将一组对象直接打包成一个单一的对象,这个容器对象允许读取,但是不允许向其中存放新的对象。
public class TwoTuple<A, B> {
public final A first;
public final B second;
public TwoTuple(A a, B b) {
this.first = a;
this.second = b;
}
public String toString() {
return "(" + first + "," + second + ")";
}
}
final使上面的两个对象first和second不能被修改。
如果想要实现更长长度的元组,可以使用继承的方式:
public class ThreeTuple<A, B, C> extends TwoTuple<A, B> {
public final C third;
public ThreeTuple(A a, B b, C c) {
super(a, b);
this.third = c;
}
public String toString() {
return "(" + first + "," + second + third + ")";
}
}
public class FourTuple<A, B, C, D> extends ThreeTuple<A, B, C> {
public final D fourth;
public FourTuple(A a, B b, C c, D d) {
super(a, b, c);
this.fourth = d;
}
public String toString() {
return "(" + first + "," + second + "," + third + "," + fourth + ")";
}
}
那这个元组到底又什么用呢?它可以返回多个对象值。比如:
public class Test1 {
static TwoTuple<String, Integer> f() {
return new TwoTuple<String, Integer>("hi", 47);
}
static ThreeTuple<Amphibian, String, Integer> g() {
return new ThreeTuple<Amphibian, String, Integer>(new Amphibian(), "hi", 47);
}
static FourTuple<Vehicle, Amphibian, String, Integer> h() {
return new FourTuple<Vehicle, Amphibian, String, Integer>
(new Vehicle(), new Amphibian(), "hi", 47);
}
}
class Amphibian {
}
class Vehicle {
}
类型擦除:
问题:
public class Test5 {
public static void main(String[] args) {
Class aClass = new ArrayList<String>().getClass();
Class bClass = new ArrayList<Integer>().getClass();
System.out.println(aClass == bClass);
}
}
在泛型的内部,无法获得任何有关泛型参数类型的信息。泛型类型参数将擦除到他的第一个边界,当你希望代码能够跨多个类工作时,使用泛型才有帮助。
使用泛型创建数组的时候,推荐使用Array.newInstance(),
public class ArrayMaker<T> {
private Class<T> kind;
public ArrayMaker(Class<T> kind) {
this.kind = kind;
}
public T[] create(int size) {
return (T[]) Array.newInstance(kind, size);
}
}
public class Test1 {
public static void main(String[] args) {
ArrayMaker<String> arrayMaker = new ArrayMaker<String>(String.class);
String[] strings = arrayMaker.create(3);
}
}
因为有类型擦除,kind实际将被存储为Class,所以Array.newInstance()并没有拥有kind所蕴含的类型信息,所以他必须要转型,
就像下面这样:
如果创建的是一个容器而不是一个数组,情况又有所不同,
泛型中的所有动作都发生在边界处,对传递进来的值进行额外的编译检查(就是说我们传值进去的时候,不需要做转型的操作,作,编译器帮我们做了),并插入对传递出去的值的转型(这也是一样),所以这样代码的噪声就更小了。
泛型的擦除导致丢失了在泛型代码中执行某些操作的能力,任何在运行时需要知道的确切类型信息的操作都无法工作:例如instanceof,new ,因为所有关于参数的类型信息都丢失了,无论何时必须提醒自己,他只是一个Object。
这种情况这么办呢?如果可以绕过这样的编程,当然可以,如果绕不过那么就需要引入类型标签来对擦除进行补偿,instanceof关键字可以转为使用动态的isInstance()方法,instanceof:这个对象是不是这种类型,isInstance()方法:对象能不能被转化为这个类。
class A {
}
class B extends A {
}
public class Test {
public static void main(String[] args) {
B b = new B();
A a = new A();
A ba = new B();
System.out.println("1------------");
System.out.println(b instanceof B);
System.out.println(b instanceof A);
System.out.println(b instanceof Object);
System.out.println(null instanceof Object);
System.out.println("2------------");
System.out.println(b.getClass().isInstance(b));
System.out.println(b.getClass().isInstance(a));
System.out.println("3------------");
System.out.println(a.getClass().isInstance(ba));
System.out.println(b.getClass().isInstance(ba));
System.out.println(b.getClass().isInstance(null));
System.out.println("4------------");
System.out.println(A.class.isInstance(a));
System.out.println(A.class.isInstance(b));
System.out.println(A.class.isInstance(ba));
System.out.println("5------------");
System.out.println(B.class.isInstance(a));
System.out.println(B.class.isInstance(b));
System.out.println(B.class.isInstance(ba));
System.out.println("6------------");
System.out.println(Object.class.isInstance(b));
System.out.println(Object.class.isInstance(null));
}
}
泛型解决了什么问题?
1:将运行时期的问题ClassCastException问题转换成了编译失败,体现在编译时期,程序员就可以解决问题。
2:避免了强制转换的麻烦。
比如这种情况:
ArrayList al = new ArrayList();
al.add("hello");
al.add(4);自动装箱
String s1 = (String)al.get(0);
String s2 = (String)al.get(1);在编译时没问题,但在运行时出现问题
泛型擦除:其实应用在编译时期,是给编译器使用的技术,到了运行时期,泛型就不存在了。在JAVA的虚拟机中并不存在泛型,泛型只是为了完善java体系,增加程序员编程的便捷性以及安全性而创建的一种机制,在JAVA虚拟机中对应泛型的都是确定的类型,在编写泛型代码后,java虚拟中会把这些泛型参数类型都擦除,用相应的确定类型来代替,代替的这一动作叫做类型擦除,而用于替代的类型称为原始类型,在类型擦除过程中,一般使用第一个限定的类型来替换,若无限定则使用Object.
对泛型类的翻译:
泛型类:
class Test<T>{
private T t;
public void show(T t){
}
}
虚拟机进行翻译后的原始类型:
class Test{
private Object t;
public void show(Object t){
}
}
也就是说,编辑器检查了泛型的类型正确后,在生成的类文件中是没有泛型的。
在运行时,如何知道获取的元素类型而不用强转呢?
因为存储的时候,类型已经确定了是同一个类型的元素,所以在运行时,只要获取到该元素的类型,在内部进行一次转换即可,所以使用者不用再做转换动作了。
什么时候用泛型类呢?
当类中的操作的引用数据类型不确定的时候,以前用的Object来进行扩展的,现在可以用泛型来表示。这样可以避免强转的麻烦,而且将运行问题转移到的编译时期。
public static <T> void function(T t) {
System.out.println("function:"+t);
}
泛型中的通配符
可以解决当具体类型不确定的时候,这个通配符就是 ? ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。?和T又有什么区别呢?
比如一下的例子:
public void print(ArrayList<?> al)
{
Iterator<?> it = al.iterator();
while(it.hasNext())
{
System.out.println(in.next());
}
}
public <T> void print(ArrayList<T> al)
{
Iterator<T> it = al.iterator();
while(it.hasNext())
{
T t = it.next(); "区别就在此处,T可以作为类型来使用,而?仅能作为接收任意类型"
System.out.println(t);
}
}
泛型通配符
1.什么时候会用到泛型通配符
在java中,数组是可以协变的,比如dog extends Animal,那么Animal[] 与dog[]是兼容的。而集合是不能协变的,也就是说List<Animal>不是List<dog>的父类,这时候就可以用到通配符了。
?extends Fruit:表示Fruit或者Fruit子类的集合,因为编译器不知道具体是那种子类,所以不能往里面插数据。但是可以使用get取值。
?extends Fruit:,表示的是Fruit的某种基类的一个集合,所以可以添加T或者Fruit的子类,但是get的时候他不能准确的判断返回的是什么子类。
泛型限定:
上限:?extends E:可以接收E类型或者E的子类型对象。
下限:?super E:可以接收E类型或者E的父类型对象。
上限什么时候用:往集合中添加元素时,既可以添加E类型对象,又可以添加E的子类型对象。为什么?因为取的时候,E类型既可以接收E类对象,又可以接收E的子类型对象。举个例子,比如:由于泛型参数类型可以表示任意类型的类类型,若T要引用compareTo方法,如何保证在T类中定义了compareTo方法呢?
public <T extends Comparable> shwo(T a, T b){
int num = a.compareTo(b);
}
此处用于限定T类型继承自Comparable,因为T类型可以调用compareTo方法.
还有这种写法:可以有多个类型限定
<T extends Comparable & Serializable>
下限什么时候用:当从集合中获取元素进行操作的时候,可以用当前元素的类型接收,也可以用当前元素的父类型接收。
泛型的一些基本规则约束:
*泛型的类型参数必须为类的引用,不能用基本类型(int, short, long, byte, float, double, char, boolean)
*泛型是类型的参数化,在使用时可以用作不同类型(此处在说泛型类时会详细说明)
*泛型的类型参数可以有多个.
泛型的细节:
1)、泛型到底代表什么类型取决于调用者传入的类型,如果没传,默认是Object类型;
2)、使用带泛型的类创建对象时,等式两边指定的泛型必须一致;
原因:编译器检查对象调用方法时只看变量,然而程序运行期间调用方法时就要考虑对象具体类型了;
3)、等式两边可以在任意一边使用泛型,在另一边不使用(考虑向后兼容);
ArrayList<String> al = new ArrayList<Object>(); //错
//要保证左右两边的泛型具体类型一致就可以了,这样不容易出错。
ArrayList<? extends Object> al = new ArrayList<String>();
al.add("aa"); //错
//因为集合具体对象中既可存储String,也可以存储Object的其他子类,所以添加具体的类型对象不合适,类型检查会出现安全问题。 ?extends Object 代表Object的子类型不确定,怎么能添加具体类型的对象呢?
public static void method(ArrayList<? extends Object> al) {
al.add("abc"); //错
//只能对al集合中的元素调用Object类中的方法,具体子类型的方法都不能用,因为子类型不确定。
}