Read "java programming ideas" 15 Generics

General classes and methods, can only use a specific type, either basic types or custom types, if you want to write code that can be applied to many types, such rigid restrictions on the code shackles will be enormous.
 
See this sentence, the first reaction is not generic, but polymorphism. Polymorphism is a "generalization." But polymorphism by inheritance limited to a single system or interface. If we want to write more generic code, make the code can be applied "without a specific type." So the introduction of generics.
 
That is parameterized generic types: "applies to many, many types of"
 
Since it is also a generic type parameter, there are arguments and parameters distinction. When defined as a parameter, in actual use the given arguments.
 
 
1, a simple generic class
// a binary tuple class 
public  class TwoTuple <A, B> { // A, B parameter is generic 
    public  Final A First; 
     public  Final B SECOND; 

    public TwoTuple (A A, B B) { 
        First = A; 
        SECOND = B; 
    } 

    public String toString () {
         return "(" + First + "," + SECOND + ")" ; 
    } 
} 
public  class the main {
     public  static  void main (String [] args) { 
        TwoTuple <String , Integer> tuple =new new TwoTuple <> ( "Hello", 123); // omitted here <String, Integer> is generic argument 
        System.out.println (tuple); 
    } 
}
Tuple (tuple), a group of objects is stored in a single package directly objects therein, wherein the container object to allow the read element, but not modified. (Data Transfer Object)
 
 
2, generic interface
//生成器
public interface Generator<T> {
    T next();
}

//实现接口时传入泛型实参
public class NumGen implements Generator<Integer> {
    @Override
    public Integer next() {  //返回值为实参
        return null;
    }
}

//实现接口时不传入泛型实参
public class CoffeeGen<T> implements Generator<T> {
    @Override
    public T next() { //返回值依然为形参
        return null;
    }
}
 
(1)生成器(generator),专门负责创建对象的类,也是工厂设计模式的一种运用。
(2)基本类型不能作为泛型实参,包装类型可以。
 
3、泛型方法
public class GenericMethods {
    public <T> void f(T x) {
        System.out.println(x.getClass().getName());
    }

    public static void main(String[] args) {
        GenericMethods gen = new GenericMethods();
        gen.f("hello"); //类型参数推断
//        gen.<String>f("hello"); //显示的类型说明
    }
}
(1)泛型方法和是否为泛型类无关。
(2)要定义泛型方法,只需要将泛型参数列表置于返回值之前。
(3)相比泛型类,优先使用泛型方法。
(4)static方法无法访问泛型类类型参数,只能使用泛型方法。
(5)当使用泛型类时,必须在创建对象的时候指定类型参数的值。而使用泛型方法的时候,通常不必指定参数类型,因为编译器会找出具体类型,这称为类型参数推断。
(6)也可以显示的指明泛型类型,必须在点操作符与方法名之间插入,如上面的<String>。
 
4、擦除的神秘之处
(1)java中的泛型并不是真正的泛型(如和C++对比),为什么这么说?
java中的泛型是应用于编译阶段的类型检查,和运行阶段的类型转化。这个中间的过程被“擦除”了,变成了Object。
如:
public static void main(String[] args) throws Exception {
    List<Integer> list = new ArrayList<>();
    list.add(0);
    //绕过泛型的静态检查插入字母x
    Method add = list.getClass().getMethod("add", Object.class);
    add.invoke(list, "x");
    System.out.println(list); //列表输出正常
    Integer i = list.get(1); //泛型转型失败ClassCastException: java.lang.String cannot be cast to java.lang.Integer
    System.out.println(i);
}
 
(2)在泛型代码内部,无法获得任何有关泛型参数类型的信息。
public class LostInformation {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        List<Double> list = new ArrayList<>();
        System.out.println(Arrays.toString(map.getClass().getTypeParameters()));
        System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
    }
}
看到输出的只是占位符:<K, V>,<E>。无法获得具体的泛型参数类型。
 
(3)可以给定泛型类的边界,以此告知编译器只能接受遵循这个边界的类型,这里使用extends关键字,
泛型类型参数将擦除到他的第一个边界(可能会有很多边界)
public class Store <T extends Coffee & Generator> {}
 
注意:
第一个边界必须为类,后面可以跟着接口。虽然T 被擦除为Coffee,但是同时可以使用Coffee,Generator的方法。只是如:void set(T t)的方法, 此时编译器提示参数类型为Coffee,只实现Generator的参数传入,编译器会报错提示参数类型不正确。
 
(4)为什么会有擦除?
擦除并不是一个语言特性,他是java引入泛型实现(新特性)的一种折中,即为了“迁移兼容性”。需要泛型代码和非凡性代码共存(互相调用)。如:
public class Test {
    public void method1(List<String> args) { //泛型参数
        System.out.println(args);
    }

    public void method2(List args) { //原生类型
        System.out.println(args);
    }

    public static void main(String[] args) {
        List<String> list1 = new ArrayList<>(Arrays.asList("a", "b", "c"));
        List list2 = new ArrayList(Arrays.asList("a", 1, "c"));

        Test test = new Test();
        test.method1(list1); 
        test.method1(list2); //泛型参数接收原生类型
        test.method2(list1); //原生类型接收泛型参数
        test.method2(list2);
    }
}
在基于擦除的实现中,泛型类型被当做第二类类型处理,即不能再某项重要的上下文环境中使用,泛型类型只有在静态类型检查期间才出现(编译期),在此之后,程序中所有的泛型类型都将被擦除,替换为它们的非泛型上界。(extends 后第一边界,默认是Object)
 
(5)擦除的问题
不能用于显示地引用运行时类型的操作之中,如:转型,instanceOf,new表达式。因为所有关于参数的类型信息都丢失了。
 
6、擦除的补偿
(1)因为擦除带来的问题,任何运行时需要知道确切类型的操作都将无法工作,如:
public class Erased<T> {
    private final int SIZE = 100;

    public void f(Object arg) {
//        if (arg instanceof T) {} //Error 类型判断
//        T var = new T(); //Error 创建实例对象
//        T[] array = new T[SIZE];  //Error 创建泛型数组
        T array = (T) new Object(); //转型
        System.out.println(array); //无论T是传进来什么类型,输出都是Object,已经被擦除
    }

    public static void main(String[] args) {
        Erased<Date> erased = new Erased<>();
        erased.f(new Date());
    }
}
输出:
 
(2)但是我们可以引入类型标签来对擦除进行补偿,这意味着你需要显示的传递你的类型Class对象,以便可以在类型表达式中使用它。如下:
public class ClassTypeCapture<T> {
    private final int SIZE = 100;
    private Class<T> kind; //类型标签


    public ClassTypeCapture(Class<T> kind) {
        this.kind = kind;
    }

    public void f(Object arg) throws Exception {
        System.out.println(kind.isInstance(arg)); //类型判断
        T t = kind.newInstance(); //创建实例对象
        System.out.println(t);
        T[] array = (T[]) Array.newInstance(kind, SIZE); //创建泛型数组
        System.out.println(array);
        T t1 = kind.cast(arg); //转型
        System.out.println(t1);
    }

    public static void main(String[] args) throws Exception {
        ClassTypeCapture<Date> capture = new ClassTypeCapture<>(Date.class);
        capture.f(new Date());
    }
}
输出:
true
Wed Sep 25 15:01:08 CST 2019
[Ljava.util.Date;@1d44bcfa
Wed Sep 25 15:01:08 CST 2019
 
(3)对于kind.newInstance()创建对象实例的方式,如果没有默认的构造函数,运行会报错并且编译时无法检查,如Integer。
因此推荐使用工厂模式:
public interface Factory<T> {
    T create();
}

public class IntegerFactory implements Factory<Integer> {
    @Override
    public Integer create() {
        return new Integer(0);
    }
}

public class StringFactory implements Factory<String> {
    @Override
    public String create() {
        return new String("string");
    }
}

public class FactoryMain {
    static <T, F extends Factory<T>> T newInstance(F factory) {
        return factory.create();
    }

    public static void main(String[] args) {
        Integer i = newInstance(new IntegerFactory());
        System.out.println(i);
        String str = newInstance(new StringFactory());
        System.out.println(str);
    }
}
 
如果说Class.newInstance是一种隐式的工厂,那么FactoryMain.newInstanse就是一种显示的工厂。
 
(4)创建对象实例还可以使用模板方法设计模式
public abstract class Creator<T> {
    private T element;

    public Creator() {
        this.element = create();
    }

    abstract T create(); //模板方法

    public void f() {
        System.out.println(element.getClass().getName());
    }
}

public class IntegerCreator extends Creator<Integer> {
    @Override
    Integer create() {
        return new Integer(0);
    }
}

public class CreatorMain {
    public static void main(String[] args) {
        Creator<Integer> creator = new IntegerCreator();
        creator.f();
    }
}
输出:
java.lang.Integer
 
7、边界
上面已经提到过边界,如:<T extends ArrayList>,它使得你可以在用于泛型参数类型上设置限制条件, 当发生擦除时,会擦除到第一个边界,此时不仅可以使用Object方法,还可以使用边界类ArrayList的方法。
这里重用了extends关键字(super不行),可以有多个边界,如:<T extends C & I1 & I2 & I3>,类必须放第一个,后面为多接口。
 
8、通配符
通配符被限制为单一边界。
 
(1)可以将导出类的数组赋予给基类型的数组引用。
public class Fruit {
}
public class Apple extends Fruit {
}
public class CovariantArrays {
    public static void main(String[] args) {
        Fruit[] fruits = new Apple[10];
        fruits[1] = new Apple();
        fruits[0] = new Fruit(); //可以编译通过,运行报错
    }
}
注意:但是数组的类型实际上是Apple,所以元素可以放入Apple及其子类。放入Fruit编译可以通过,运行会报错ArrayStoreException。泛型正式为了将类型性错误检测移到编译期。于是可以使用泛型容器代替:
 
List<Fruit> fruit = new ArrayList<Apple>(); //Error 编译错误
编译错误的原因是Fruit的list 和 Apple 的list是两种容器的类型,而不是仅仅是不同的持有类型。泛型没有内建的协变类型,他们不能向上转型。
 
备注:
  • 协变:子类能向父类转换
  • 逆变:父类能向子类转换
 
(2)协变通配符
public static void main(String[] args) {
    List<? extends Fruit> fruits = new ArrayList<>();
    fruits.add(new Apple()); //Error 编译错误
    fruits.add(new Fruit()); //Error 编译错误
    fruits.add(null);

    Fruit fruit = fruits.get(0); //安全的
    System.out.println(fruit);
}
这时候如果想要建立向上转型的关系,可以使用导出类通配符<? extends Fruit>,其意义是Fruit及其子类中的任意一种,具体哪一种不清楚,因此Fruit一定可以作为get方法的返回值,但不能set方法的参数(add同理) 。
 
备注:
泛型只是编译器检查,可以有各种方法绕过。比如利用反射向list中添加元素,或者向下面这样:
List<? extends Fruit> fruits = Arrays.asList(new Apple(), new Fruit());
 
(3)逆变通配符
public static void main(String[] args) {
    List<? super Fruit> fruits = Arrays.asList();
    fruits.add(new Apple()); //安全
    fruits.add(new Fruit());  //安全
    fruits.add(null);

    Fruit fruit = fruits.get(0);//Error 编译错误
    System.out.println(fruit);
}
建立关系,还可以使用超类型通配符 <? super Fruit> ,其意义是Fruit及其父类中的任意一种,具体哪一种不清楚,因此Fruit一定可以作为set方法的参数(add同理),但却不能get方法的返回值。
 
注意:
不能对泛型参数给出一个超类型边界,即不能声明<T super MyClass>
 
(4)无界通配符
public static void main(String[] args) {
    List<?> fruits = Arrays.asList();
    fruits.add(new Apple()); //Error 编译错误
    fruits.add(new Fruit()); //Error 编译错误
    fruits.add(null);

    Fruit fruit = fruits.get(0);//Error 编译错误

    fruits.add(new Object()); //Error 编译错误
    Object obj = fruits.get(0); //安全
    System.out.println(obj);
}
List<?> 无界通配符表示“任意事物”中的一种,哪一种还是不知道,因此没有类型能作为set方法的参数(add同理),除了Object,其他类型也都不能作为get方法的返回值。由此可知 List(或List<Object>) 和 List<?> 只是长得很像。
 
 
9、泛型问题
(1)任何基本类型都不能作为类型参数。
 
(2)一个类不能实现同一个泛型接口的两种变体。
如:
interface I1<T> {}
class Base implements I1<String>{}
public class C1 extends Base implements I1<Integer> {} //Error 编译错误
 
(3)转型和警告
使用带有泛型类型的参数的转型或instanceof不会有效果。
 
(4)犹豫擦除原因,泛型不能重载。
如:
public void addAll(List<T> list){}
public void addAll(List<V> list){} //Error 编译错误
 
(5)基类劫持接口
和(2)是一个意思。 Base类确定了接口I1泛型为String,那么C1 继承了Base ,再次实现接口I1,泛型不能是Integer,只能是String(被劫持)
 
10、自限定类型
(1)自限定
public class SelfBounded<T extends SelfBounded<T>> {}
初次看到,可能很难理解,没关系,我们先做下”擦除“,去掉extends 和 T,
public class SelfBounded<SelfBounded> {}
他的意思是SelfBounded的泛型参数是SelfBounded自己,即自己限定自己。
加上<T extends SelfBounded>,只说明泛型参数为SelfBounded及其子类。
 
如:
public class SelfBounded<T extends SelfBounded<T>> {
    T element;

    public SelfBounded<T> set(T element) {
        this.element = element;
        return this;
    }

    public T getElement() {
        return element;
    }
}

class A extends SelfBounded<A> {}
class B extends SelfBounded<A> {} ///也是ok的,因为A 也是SelfBounded
class C {}
class D extends SelfBounded<C> {} //Error 编译错误,因为C并不是SelfBounded 
自限定参数的意义在于:它可以保证类型参数必须与正在被定义的类相同。
 
(2)参数协变
a、自限定类型的价值在于他们可以产生协变参数类型。
b、如果在一对父子类中,某一个方法,方法名称和参数都相同。则为override重写,即子类方法覆盖了父类方法,只有一个方法,多态的体现;方法名称相同,参数不同,则为overload重载,即为两个方法。
c、在方法名称和参数都相同前提下,返回值可以协变,子类方法的返回值 可以是 父类方法 子类(反过来编译报错)。这也算是override重写。
d、正常情况参数是不能协变的,属于overload重载,但是自限定类型的参数却可以做到overload重载。
 
另:
对于”参数协变“这个主题,书上p408的例子SelfBoundingAndCovarianAndCovariantArguments.java,个人觉得更像是一个子类参数自动向上转型为父类,父类参数不能自动向下转型为子类的问题。连同”自限定类型“这一节在内,目前能想到的用途在于做”比较“的业务。有知道其他用途的可以告诉我。
 
11、异常
catch语句不能捕获泛型类型的异常,但可以作为方法签名上throws 异常部分。
如:
interface Test2<T extends Exception> {
    void test() throws T;
}
 
12、混型
顾名思义,就是将多种类型混合起来,使一种类拥有多种能力。
(1)java语法上不支持多继承,那么最简单实现方式,就是单继承多接口 + 代理了(内部类实现多继承也是一种代理)
(2)其次可以使用装饰器模式,类似IO类,缺点是只能有效的工作与装饰中的最后一层。
(3)动态代理可以实现近似的混型。(个人觉得,不如直接使用普通代理)
//说话的能力
interface ISay {
    void say();
}

class SayImpl implements ISay {
    @Override
    public void say() {
        System.out.println("hello");
    }
}

//报时的能力
interface IDate {
    void now();
}

class DateImpl implements IDate {
    @Override
    public void now() {
        System.out.println(new Date());
    }
}

//唱歌的能力
interface ISing {
    void sing();
}

class SingImpl implements ISing {
    @Override
    public void sing() {
        System.out.println("lalalalalala!");
    }
}

class Mix implements InvocationHandler { //动态代理
    private Map<String, Object> delegates = new HashMap<>();

    public Mix(Object ...args) {
        for (Object obj : args) {
            Class<?> clazz = obj.getClass();
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (!delegates.containsKey(method.getName())) {
                    delegates.put(method.getName(), obj);
                }
            }
        }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object delegate = delegates.get(method.getName());
        return method.invoke(delegate, args);
    }

    public static Object newInstance(Class[] clazzes, Object[] objects) {
        return Proxy.newProxyInstance(Mix.class.getClassLoader(), clazzes, new Mix(objects));
    }
}


public class Test1 {
    public static void main(String[] args) {
        //混合三种能力
        Object mixObj = Mix.newInstance(
                new Class[]{ISing.class, IDate.class, ISay.class},
                new Object[]{new SingImpl(), new DateImpl(), new SayImpl()});

        ISing singObj = (ISing) mixObj;
        singObj.sing();

        ISay sayObj = (ISay) mixObj;
        sayObj.say();

        IDate dateObj = (IDate) mixObj;
        dateObj.now();
    }
}
 
13、潜在的类型机制及其补偿
潜在类型机制:只要求实现某个方法子集,而不是特定的类或者接口,使得你可以横跨类继承结构,调用不属于公共接口的方法。简单来说,我不关心你是什么类型,只要你可以say() 和 sing() 就可以了。
java语法上显然不支持,但是可以使用反射达到相同的效果。
public class Test3 {
    public static void perform(Object obj){
        Class<?> clazz = obj.getClass();
        try {
            Method say = clazz.getMethod("say");
            say.invoke(obj);
        } catch (Exception e) {
            System.out.println(obj + " can not say");
        }

        try {
            Method say = clazz.getMethod("sing");
            say.invoke(obj);
        } catch (Exception e) {
            System.out.println(obj + " can not sing");
        }
    }

    public static void main(String[] args) {
        perform(new SingImpl());
        System.out.println("----------");
        perform(new SayImpl());
    }
}
输出
chapter15.w1.t3.SingImpl@5e2de80c can not say
lalalalalala!
----------
hello
chapter15.w1.t3.SayImpl@6f94fa3e can not sing
 
14、将函数作为策略
其实就是泛型在策略模式中的使用。
interface Strategy<T, R> {
    R operation(T obj1, T obj2);
}

//整数相加操作
class IntegerAddOperation implements Strategy<Integer, Integer> {
    @Override
    public Integer operation(Integer obj1, Integer obj2) {
        return obj1 + obj2;
    }
}

//字符串比较操作
class StringCompareOperation implements Strategy<String, String> {
    @Override
    public String operation(String obj1, String obj2) {
        Integer result = obj1.compareTo(obj2);
        if ( result == 0) {
            return obj1 + " == " + obj2;
        } else if (result > 0) {
            return obj1 + " > " + obj2;
        } else {
            return obj1 + " < " + obj2;
        }
    }
}

public class Context {
    public static void execute( Object obj1, Object obj2,Strategy strategy) {
        Object result = strategy.operation(obj1, obj2);
        System.out.println(result);
    }

    public static void main(String[] args) {
        execute(1, 2, new IntegerAddOperation());
        execute("today", "tomorrow", new StringCompareOperation());
    }
}
输出
3
today < tomorrow
 
14、题外话
泛型这一章足足有77页,其实真正泛型的知识点并不多,大概可以只看到”8、通配符“就可以结束了,后面都是作者根据在其他语言如C++,Python语言上的特性 对比 联想 出来应用场景及注意事项
,就如上一章中RTTI一样。
 
 
 
 
 
 
 

Guess you like

Origin www.cnblogs.com/shineon/p/11599403.html