泛型
对象和实例是一个意思,类与对象的关系就像数据类型和变量一样。
-
泛型的主要目的之一就是用来指定类(如:容器)要持有什么类型的对象,代码解释如下:
public class Holder3<T> { private T a;//持有了T的对象,此处可以持有任何对象,可以用Object代替但是要向下转型 public Holder3(T a) { this.a = a; } public void set(T a) { this.a = a; } public T get() { return a; } public static void main(String[] args) { Holder3<Automobile> h3 = new Holder3<Automobile>(new Automobile()); Automobile a = h3.get(); // No cast needed // h3.set("Not an Automobile"); // Error // h3.set(1); // Error } } ///:~
-
在有些场景中会有一个方法返回多个对象,你可以使用创建类用它来持有返回的多个对象,如果再 加上泛型技术就会在编译期确保类型安全。代码解释如下:
//: net/mindview/util/TwoTuple.java package net.mindview.util; public class TwoTuple<A,B> { 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 interface Generator<T>
,在写继承的时候T
可以写成任意类型,比如构造一个咖啡工厂public class CoffeeGenerator implements Generator<Coffee>
,构造一个生成Fibonacci数列的类public class Fibonacci implements Generator<Integer>
,咖啡代码如下://... 省略处为一些简单类,如Latte,Mocha,Cappuccino,Americano,Breve这些类都继承于coffee,coffee.java如下 package com.generics.coffee; public class Coffee { private static long counter = 0; private final long id = counter++; public String toString() { return getClass().getSimpleName() + " " + id; } } ///:~ //: generics/coffee/CoffeeGenerator.java // Generate different types of Coffee: package com.generics.coffee; import java.util.*; import net.mindview.util.*; public class CoffeeGenerator implements Generator<Coffee> ,Iterable<Coffee> { @SuppressWarnings("rawtypes") private Class[] types = { Latte.class, Mocha.class, Cappuccino.class, Americano.class, Breve.class, }; private static Random rand = new Random(47); public CoffeeGenerator() {} // For iteration: private int size = 0; public CoffeeGenerator(int sz) { size = sz; } public Coffee next() { try { return (Coffee) types[rand.nextInt(types.length)].newInstance(); // Report programmer errors at run time: } catch(Exception e) { throw new RuntimeException(e); } } //解释:内部类实现迭代器,该实现了Iterator而CoffeeGenerator实现的是Iterable,要实现foreach循环必须实现这两个接口, //从代码看起来foreach循环是看出来了,要理解其本质的原理需要看jvm里面的字节码,new CoffeeGenerator(5)调用后,首先产生 //CoffeeIterator的实例,执行hasNext()->next() //此处可以也可以用匿名内部类 class CoffeeIterator implements Iterator<Coffee> { int count = size; public boolean hasNext() { return count > 0; } public Coffee next() { count--; return CoffeeGenerator.this.next(); } public void remove() { // Not implemented throw new UnsupportedOperationException(); } }; public Iterator<Coffee> iterator() { return new CoffeeIterator(); } public static void main(String[] args) { CoffeeGenerator gen = new CoffeeGenerator(); for(int i = 0; i < 5; i++) System.out.println(gen.next()); for(Coffee c : new CoffeeGenerator(5)) System.out.println(c); } } /* Output: Americano 0 Latte 1 Americano 2 Mocha 3 Mocha 4 Breve 5 Americano 6 Latte 7 Cappuccino 8 Cappuccino 9 *///:~
Fibonacci数列的代码如下:
package com.generics; import net.mindview.util.*; public class Fibonacci implements Generator<Integer> { private int count = 0; public Integer next() { return fib(count++); } private int fib(int n) { if(n < 2) return 1; return fib(n-2) + fib(n-1); } public static void main(String[] args) { Fibonacci gen = new Fibonacci(); for(int i = 0; i < 18; i++) System.out.print(gen.next() + " "); } } /* Output: 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 *///:~
如果想要实现迭代,而且要不用内部类的方式(
CoffeeGenerator.java
使用的是内部类实现的迭代器模式),用适配器模式实现,适配器模式即把两个互不相关的接口或者类相连接,所以可以使用继承或者组合,UML如下:![](https://www.processon.com/chart_image/thumb/58aaf16ee4b071585833fd96.png) 迭代如下:
package com.generics; // Adapt the Fibonacci class to make it Iterable. import java.util.*; //组合来创建适配器 public class IterableFibonacci implements Iterable<Integer> { private Fibonacci fibonacci = new Fibonacci(); private int n; public IterableFibonacci(int count) { n = count; } public Iterator<Integer> iterator() { //匿名内部类的形式 return new Iterator<Integer>() { @Override public Integer next() { // TODO Auto-generated method stub n--; return fibonacci.next();//invoke next() in Fibonacci,for this extends Fibonacci } @Override public boolean hasNext() { // TODO Auto-generated method stub return n > 0; } public void remove() { // Not implemented throw new UnsupportedOperationException(); } }; } public static void main(String[] args) { for(int i : new IterableFibonacci(18)) System.out.print(i + " "); } } /* Output: 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 *///:~
-
泛型应用于方法
应用于方法
public <T> void f(T x){}
,其中<T>一定要写,不然编译器是无法识别出参数的T
-
当可变参数与方法结合:
package com.generics; //: generics/GenericVarargs.java import java.util.*; public class GenericVarargs { //此处的makeList就像是java.util.Arrays里面的asList(T... args); public static <T> List<T> makeList(T... args) { List<T> result = new ArrayList<T>(); for(T item : args) result.add(item); return result; } public static void main(String[] args) { List<String> ls = makeList("A"); System.out.println(ls); ls = makeList("A", "B", "C"); System.out.println(ls); ls = makeList("ABCDEFFHIJKLMNOPQRSTUVWXYZ".split("")); System.out.println(ls); } } /* Output: [A] [A, B, C] [, A, B, C, D, E, F, F, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z] *///:~
从上面代码注释可以看到makeList和asList方法很像,下面来看看asList的源码分析
public static <T> List<T> asList(T... a) { return new ArrayList<>(a);//此处的ArrayList不是你想的java.util.ArrayList,他是Arrays里面的一个静态内部类 } //此处是静态内部类的构造器,返回一个数组,需要说明的是该内部类并没有实现add,remove等方法,因为asList()方法在大多数使用场景中是不用改变的,所以要构造一个可编辑的ArrayList()用类似下面的代码即可List<WaiterLevel> levelList = new ArrayList<WaiterLevel>(Arrays.asList("a", "b", "c")); ArrayList(E[] array) { a = Objects.requireNonNull(array);//判断array是否为空 }
-
利用泛型方法对前一章的生成器进行更一步的抽象,代码如下:
//: net/mindview/util/BasicGenerator.java // Automatically create a Generator, given a class // with a default (no-arg) constructor. package net.mindview.util; //this class can generate any Class which have default constructor by create() function,but there is a limit which is that constructor cannot pass argument(传参) public class BasicGenerator<T> implements Generator<T> { private Class<T> type; public BasicGenerator(Class<T> type){ this.type = type; } public T next() { try { // Assumes type is a public class: return type.newInstance(); } catch(Exception e) { throw new RuntimeException(e); } } // Produce a Default generator given a type token: public static <T> Generator<T> create(Class<T> type) { return new BasicGenerator<T>(type); } } ///:~
更多的,我们可以对前面提到的元组进行进一步的抽象
//: net/mindview/util/Tuple.java // Tuple library using type argument inference. package net.mindview.util; public class Tuple { public static <A,B> TwoTuple<A,B> tuple(A a, B b) { return new TwoTuple<A,B>(a, b); } public static <A,B,C> ThreeTuple<A,B,C> tuple(A a, B b, C c) { return new ThreeTuple<A,B,C>(a, b, c); } public static <A,B,C,D> FourTuple<A,B,C,D> tuple(A a, B b, C c, D d) { return new FourTuple<A,B,C,D>(a, b, c, d); } public static <A,B,C,D,E> FiveTuple<A,B,C,D,E> tuple(A a, B b, C c, D d, E e) { return new FiveTuple<A,B,C,D,E>(a, b, c, d, e); } } ///:~
-
java对泛型的擦除有四句话
- 泛型类型在运行时都是Object类型
- 模板只在编译阶段有效是为了提供编译期的类型安全,通过反射操作可以绕过编译阶段
- 在编译期就可以知道的类型信息是可以操作的
- 所有在运行时才能知道类型信息的操作都将无法工作
package com.generics; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; class Manipulator<T> { public T obj; public Manipulator(T x) { obj = x; } // Error: cannot find symbol: method f(): public void manipulate() { // (obj).f(); } public void erase2(){ //T[] t = new T[4]; //a instanceof T; } } public class Manipulation { public static void main(String[] args) { //1.模板只在编译阶段有效是为了提供编译期的类型安全,通过反射操作可以绕过编译阶段 List<String> list1 = new ArrayList<>(); List list = new ArrayList<>(); list1.add("s"); //list1.add(2); try { Method m = ArrayList.class.getMethod("add",Object.class); m.invoke(list1, 2); System.out.println(list1); } catch (NoSuchMethodException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } //2.在编译期就可以知道的类型信息是可以操作的 HasF hf = new HasF(); Manipulator<HasF> manipulator = new Manipulator<HasF>(hf); manipulator.obj.f(); //在这个函数里面调用的obj.f(),是不可能考虑客户端的类型,即是单独去编译的,在客户端没有调用时,他并不知道T是什么类型,所以有错 manipulator.manipulate(); //3.所有在运行时才能知道类型信息的操作都将无法工作 manipulator.erase2(); } } ///:~
不能创建泛型类型数组的,一般的解决方案是在任何想要创建泛型数组的地方都是用ArrayList去创建。
泛型的主要目标之一是将错误检测移入到编译期
-
编译器直接拒绝对参数列表中涉及到的通配符的方法,即
add(? extends fruit)
如果变成了这样结果如下图 -
下面代码能够实现只能存水果的集合,且在编译期内就能检查类型信息,其中第二种方式称之为逆变。为什么逆变的方式可以实现?答:super关键字表示下界,
List<? super Apple> fruit = new ArrayList<>();
,而?必须要表示一个确切的类型,准确来讲应该是这样声明一个实例即:List<? super Apple> fruit = new ArrayList<在这个括号内部必须是Apple的父类>();
即在比如List<? super Apple> fruit = new ArrayList<Fruit>()
,所以当add()
的时候,可以插入Apple的子类,同样的道理分析List<? extends Apple> flist2 = new ArrayList
<这里面要插入的是Apple的子类>();所以当add(new Apple())
时候,会失败,比如List<? extends Apple> flist2 = new ArrayList<Jonathan>();Jonathan = new Apple()//error;
//1.想要实现一个集合里面能装所有类型的水果,但是在编译期不允许装除了水果以外的其他对象 List<Fruit> flist3 = new ArrayList<>(); flist3.addAll(Arrays.asList(new Apple())); flist3.addAll(Arrays.asList(new Orange())); System.out.println(flist3.get(1)); //2.第一种方式太复杂,下面用逆变的方式实现 List<? super Fruit> fruit = new ArrayList<>(); fruit.add(new Apple()); fruit.add(new Orange());
-
混型即
Timestamped<Serialnumbered<Basic>> mixin
其中mixin
能够调用基类的所有函数,在C++中,这是显然的,但是在java中可以这样声明,但不能调用基类的任何函数只能调用Timestamped类中的函数,所以必须使用有些设计模式来代替,其中涉及到装饰器模式,和用动态代理(即我们可以动态注入类方法)来实现混合,但是结果都没有C++中方便直接。implements
和extends
关键字实现:package com.generics; //: generics/Mixins.java import java.util.*; interface TimeStamped { long getStamp(); } class TimeStampedImp implements TimeStamped { private final long timeStamp; public TimeStampedImp() { timeStamp = new Date().getTime(); } public long getStamp() { return timeStamp; } } interface SerialNumbered { long getSerialNumber(); } class SerialNumberedImp implements SerialNumbered { private static long counter = 1; private final long serialNumber = counter++; public long getSerialNumber() { return serialNumber; } } interface Basic { public void set(String val); public String get(); } class BasicImp implements Basic { private String value; public void set(String val) { value = val; } public String get() { return value; } } // for Mixin2.java,Timestamped<Serialnumbered<Basic>> mixin = new Timestamped();,mixin can not invoke set() function of Basic,but c++ is capable to do it. //so in java, use implements and extends keywords to realize it. class Mixin extends BasicImp implements TimeStamped, SerialNumbered { //if use this,you must have a instance of response to interface private TimeStamped timeStamp = new TimeStampedImp(); private SerialNumbered serialNumber = new SerialNumberedImp(); public long getStamp() { return timeStamp.getStamp(); } public long getSerialNumber() { return serialNumber.getSerialNumber(); } } public class Mixins { public static void main(String[] args) { Mixin mixin1 = new Mixin(), mixin2 = new Mixin(); mixin1.set("test string 1"); mixin2.set("test string 2"); System.out.println(mixin1.get() + " " + mixin1.getStamp() + " " + mixin1.getSerialNumber()); System.out.println(mixin2.get() + " " + mixin2.getStamp() + " " + mixin2.getSerialNumber()); } } /* Output: (Sample) test string 1 1132437151359 1 test string 2 1132437151359 2 *///:~
装饰器模式实现(并没有完全实现):
package com.generics.decorator; //: generics/decorator/Decoration.java import java.util.*; class Basic { private String value; public void set(String val) { value = val; } public String get() { return value; } } class Decorator extends Basic { protected Basic basic; public Decorator(Basic basic) { this.basic = basic; } public void set(String val) { basic.set(val); } public String get() { return basic.get(); } } class TimeStamped extends Decorator { private final long timeStamp; public TimeStamped(Basic basic) { super(basic); timeStamp = new Date().getTime(); } public long getStamp() { return timeStamp; } } class SerialNumbered extends Decorator { private static long counter = 1; private final long serialNumber = counter++; public SerialNumbered(Basic basic) { super(basic); } public long getSerialNumber() { return serialNumber; } } //this is decoration design patterns public class Decoration { public static void main(String[] args) { TimeStamped t = new TimeStamped(new Basic()); // because timestamped extends Basic t.set("fasdfa"); //realize such as TimeStamped<SerialNumbered<Basic>> mixin1, mixin2 TimeStamped t2 = new TimeStamped( new SerialNumbered(new Basic())); //! t2.getSerialNumber(); // Not available, obviously SerialNumbered s = new SerialNumbered(new Basic()); SerialNumbered s2 = new SerialNumbered( new TimeStamped(new Basic())); //! s2.getStamp(); // Not available } } ///:~
动态代理模式实现:
package com.generics; //: generics/DynamicProxyMixin.java import java.lang.reflect.*; import java.util.*; import net.mindview.util.*; import static net.mindview.util.Tuple.*; class MixinProxy implements InvocationHandler { Map<String,Object> delegatesByMethod; public MixinProxy(TwoTuple<Object,Class<?>>... pairs) { delegatesByMethod = new HashMap<String,Object>(); for(TwoTuple<Object,Class<?>> pair : pairs) { for(Method method : pair.second.getMethods()) { String methodName = method.getName(); System.out.println(methodName + "()"); // The first interface in the map // implements the method. if (!delegatesByMethod.containsKey(methodName)) delegatesByMethod.put(methodName, pair.first);// this is the most important, because this inject all functions of pairs } } } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("invoke() is invoked"); String methodName = method.getName(); Object delegate = delegatesByMethod.get(methodName); return method.invoke(delegate, args); } @SuppressWarnings("unchecked") public static Object newInstance(TwoTuple... pairs) { Class[] interfaces = new Class[pairs.length]; for(int i = 0; i < pairs.length; i++) { interfaces[i] = (Class)pairs[i].second;//second represent XXX.class } ClassLoader cl = pairs[0].first.getClass().getClassLoader(); return Proxy.newProxyInstance( cl, interfaces, new MixinProxy(pairs)); } } public class DynamicProxyMixin { public static void main(String[] args) { Object mixin = MixinProxy.newInstance( tuple(new BasicImp(), Basic.class), tuple(new TimeStampedImp(), TimeStamped.class), tuple(new SerialNumberedImp(),SerialNumbered.class)); // Basic b = (Basic)mixin; TimeStamped t = (TimeStamped)mixin; SerialNumbered s = (SerialNumbered)mixin; b.set("Hello"); System.out.println(b.get()); System.out.println(t.getStamp()); System.out.println(s.getSerialNumber()); } } /* get() set() getStamp() getSerialNumber() invoke() is invoked invoke() is invoked Hello invoke() is invoked 1489219456567 invoke() is invoked 1 *///:~
静态类型检查即在程序没有运行时就能够通过检查源代码确定类型安全,与动态类型相对应
-
潜在类型机制即直接可以用模板T,而不用指定该模板属于哪个基类,比如在C++里面就可以直接定义
template<class T> void perform(T anything) { anything.speak(); anything.sit(); }
而在java中必须要指明边界
class Communicate { //must specify the bounds of generic type,but C++ is not necessary public static <T extends Performs> void perform(T performer) { performer.speak(); performer.sit(); } }
-
java对潜在类型机制的补偿的一种方式是反射,如下
class CommunicateReflectively { //接受一个Object,然后看是哪个类 public static void perform(Object speaker) { Class<?> spkr = speaker.getClass(); try { try { Method speak = spkr.getMethod("speak"); speak.invoke(speaker); } catch(NoSuchMethodException e) { print(speaker + " cannot speak"); } try { Method sit = spkr.getMethod("sit"); sit.invoke(speaker); } catch(NoSuchMethodException e) { print(speaker + " cannot sit"); } } catch(Exception e) { throw new RuntimeException(speaker.toString(), e); } } }
15.17中15.17.2与15.17.3,15.17.4没理解
应用于序列的泛型技术很多都会涉及到Iterable<T>接口
数组
在java中数组是一种效率最高的存储和随机访问对象应用序列的方式
-
Comparable接口和Comaprator接口用于排序,jdk中运用策略设计模式将“保持不变的事物与会发生改变的事物相分离”,代码如下:
//Comparable class Student implements Comparable<Student>{ private String name; private int age; private float score; public Student(String name, int age, float score) { this.name = name; this.age = age; this.score = score; } public String toString() { return name+"\t\t"+age+"\t\t"+score; } @Override public int compareTo(Student o) { // TODO Auto-generated method stub if(this.score>o.score)//score是private的,为什么能够直接调用,这是因为在Student类内部 return -1;//由高到底排序 else if(this.score<o.score) return 1; else{ if(this.age>o.age) return 1;//由底到高排序 else if(this.age<o.age) return -1; else return 0; } } } public class ComparableDemo01 { public static void main(String[] args) { // TODO Auto-generated method stub Student stu[]={new Student("zhangsan",20,90.0f), new Student("lisi",22,90.0f), new Student("wangwu",20,99.0f), new Student("sunliu",22,100.0f)}; java.util.Arrays.sort(stu); for(Student s:stu) { System.out.println(s); } } } //Comparator package edu.sjtu.ist.comutil; import java.util.Comparator; class Student { private String name; private int age; private float score; public Student(String name, int age, float score) { this.name = name; this.age = age; this.score = score; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public float getScore() { return score; } public void setScore(float score) { this.score = score; } public String toString() { return name+"\t\t"+age+"\t\t"+score; } } class StudentComparator implements Comparator<Student>{ @Override public int compare(Student o1, Student o2) { // TODO Auto-generated method stub if(o1.getScore()>o2.getScore()) return -1; else if(o1.getScore()<o2.getScore()) return 1; else{ if(o1.getAge()>o2.getAge()) return 1; else if(o1.getAge()<o2.getAge()) return -1; else return 0; } } } public class ComparableDemo02 { public static void main(String[] args) { // TODO Auto-generated method stub Student stu[]={new Student("zhangsan",20,90.0f), new Student("lisi",22,90.0f), new Student("wangwu",20,99.0f), new Student("sunliu",22,100.0f)}; java.util.Arrays.sort(stu,new StudentComparator()); for(Student s:stu) { System.out.println(s); } } }
- 当你使用最近的java版本编程时,应该优先选择容器而不是数组,只有在证明性能成为问题时,你才应该讲程序重构为使用数组
容器源码解读
- 继承结构代码如下:
public interface Iterable<T>{...} public interface Collection<E> extends Iterable<E>{...} public interface Set<E> extends Collection<E>{...} public interface SortedSet<E> extends Set<E>{ Comparator<? super E> comparator(); } public interface List<E> extends Collection<E>{...}
- 抽象类实现接口,可以不用实现其全部的方法即可以筛选一些方法来实现,比如:
interface test{
void m();
void f();
}
abstract class test2 implements test{
@Override
public void m() {
// TODO Auto-generated method stub
}
}