J2SE 8的泛型
泛型的简单使用
1. 泛型一般用E表示集合中元素;k和v表示Map中的key和value;R表示return值;T/U/S表示任意类型
//(1) 简单单个元素的泛型
Box<String> boxString = new Box<>();
boxString.setT("boxString");
System.out.println(boxString.toString());
Box<Integer> boxInteger = new Box<>();
boxInteger.setT(888);
System.out.println(boxInteger.toString());
class Box<T> {
private T t;
public Box() {
t = null;
}
public Box(T t) {
this.t = t;
}
public static <T> Box<T> makeBox(Supplier<T> supplier) {
return new Box<>(supplier.get());
}
public static <T> Box<T> makeBox(Class<T> cl) {
try {
return new Box<>(cl.newInstance());
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
return null;
}
}
public static <E> void append(List<E> list, Class<E> cls) throws Exception {
E elem = cls.newInstance();
list.add(elem);
}
@Override
public String toString() {
return t+"";
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
//(2) 两个元素的泛型
Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Pair.compare(p1, p2);
System.out.println("Pair.compare() same:"+same);
class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
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());
}
public void setKey(K key) {
this.key = key;
}
public void setValue(V value) {
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
//(3) 指定元素 T extends Comparable<T> 边界符
int countGreaterThan = countGreaterThan(new String[]{"111","222"}, "11");
System.out.println("countGreaterThan:"+countGreaterThan);
//可以指定绑定多个限定 T extends Comparable<T>&Serializable
countGreaterThan = countGreaterThan2(new String[]{"111","222"}, "11");
System.out.println("countGreaterThan:"+countGreaterThan);
//多个泛型带绑定 T extends Comparable<T>&Serializable, U extends T
countGreaterThan = countGreaterThan3(new String[]{"111","222"}, "11");
System.out.println("countGreaterThan:"+countGreaterThan);
public static <T extends Comparable<T>> int countGreaterThan(T[] anArray, T elem) {
int count = 0;
for (T e : anArray)
if (e.compareTo(elem) > 0) {
++count;
}
return count;
}
public static <T extends Comparable<T>&Serializable> int countGreaterThan2(T[] anArray, T elem) {
int count = 0;
for (T e : anArray)
if (e.compareTo(elem) > 0) {
++count;
}
return count;
}
public static <T extends Comparable<T>&Serializable, U extends T> int countGreaterThan3(T[] anArray, U elem) {
int count = 0;
for (T e : anArray)
if (e.compareTo(elem) > 0) {
++count;
}
return count;
}
//(4) 指定通配元素 ? 边界符
// <? extends A> 表示 T必须是A的子类
// <? super A> 表示T必须是A的父类
// <?>无限定通配符
//1) ? extends T 可以加 T本身和T的子类 均可以get()
//实现了<? extends T>的集合类只能将它视为Producer向外提供(get)元素,而不能作为Consumer来对外获取(add)元素
List<Apple> apples = Arrays.asList(new Apple());
List<Orange> oranges = Arrays.asList(new Orange());
List<Fruit> fruit = Arrays.asList(new Fruit());
Covariant<Fruit> fruitReader = new Covariant<Fruit>(); //指定通配符 T为Fruit, readCovariant() list元素 extends Fruit
Fruit f = fruitReader.readCovariant(fruit);
Fruit a = fruitReader.readCovariant(apples);
Fruit o = fruitReader.readCovariant(oranges);
System.out.println(f.toString());
System.out.println(a.toString());
System.out.println(o.toString());
//2) ? super T
//要add元素应该怎么做呢?可以使用<? super T> T本身和T的子类 均可以add(), 注意T的类型
fruit = new ArrayList<Fruit>();
Covariant.writeWithWildcard(fruit, new Apple());
Covariant.writeWithWildcard(fruit, new Orange());
Covariant.writeWithWildcard(fruit, new Fruit());
System.out.println("writeWithWildcard size"+fruit.size());
//总结出一条规律,”Producer Extends, Consumer Super”:
//“Producer Extends” - 如果你需要一个只读List,用它来produce T,那么使用? extends T。
//“Consumer Super” - 如果你需要一个只写List,用它来consume T,那么使用? super T。
//如果需要同时读取以及写入,那么我们就不能使用通配符了。
//3) 结合使用
Covariant.copy(apples, fruit);
Covariant.copy(oranges, fruit);
System.out.println("copy size"+fruit.size());
System.out.println();
class Fruit {
}
class Apple extends Fruit {
}
class Orange extends Fruit {
}
static class Covariant<T> {
T readCovariant(List<? extends T> list) {
return list.get(0);
}
static <T> void writeWithWildcard(List<? super T> list, T item) {
list.add(item);
}
public static <T> void copy(List<? extends T> source, List<? super T> dest) {
for (int i=0; i<source.size(); i++)
dest.set(i, source.get(i));
}
}
无边界通配符 ?
通配符的意义就是它是一个未知的符号,可以是代表任意的类。
泛型变量T不能在代码用于创建变量,只能在类,接口,函数中声明以后,才能使用。
无边界通配符?则只能用于填充泛型变量T,表示通配任何类型!!!!
只能出现在声明位置,不能出现在实现位置
Box<?> box;
box = new Box<String>();
<? extends XXX>指填充为派生于XXX的任意子类的话
<? super XXX>则表示填充为任意XXX的父类!
extends通配符,能取不能存
super通配符:能存不能取
通配符?总结
总结 ? extends 和 the ? super 通配符的特征,我们可以得出以下结论:
◆ 如果你想从一个数据类型里获取数据,使用 ? extends 通配符(能取不能存)
◆ 如果你想把对象写入一个数据结构里,使用 ? super 通配符(能存不能取)
◆ 如果你既想存,又想取,那就别用通配符。
构造泛型实例时,如果省略了填充类型,则默认填充为无边界通配符!
http://blog.csdn.net/harvic880925/article/details/49883589
泛型类
class InfoImpl<T> implements Info<String>{
…………
}
非泛型类
class InfoImpl implements Info<String>{
…………
}
两种泛型传参方法
class StaticFans {
//静态函数
public static <T> void StaticMethod(T a){
System.out.println("harvic StaticMethod: "+a.toString());
}
//普通函数
public <T> void OtherMethod(T a){
System.out.println("harvic OtherMethod: "+a.toString());
}
}
//4) 使用方法
//静态方法
StaticFans.StaticMethod("adfdsa");//使用方法一 , 类型没有限制
StaticFans.<String>StaticMethod("adfdsa");//使用方法二 , 限制了String
//常规方法
StaticFans staticFans = new StaticFans();
staticFans.OtherMethod(new Integer(123));//使用方法一 , 类型没有限制
staticFans.<Integer>OtherMethod(new Integer(123));//使用方法二 , 限制了String
// IntFunction 返回R必须是数组?
public static <T> T[] getTArray(IntFunction<T[]> fun, int value) {
T[] apply = fun.apply(value);
return apply;
}
@SuppressWarnings("unchecked")
public static <T> T[] getTArray(T... values) {
return (T[]) Array.newInstance(values.getClass().getComponentType(), values.length);
}
@SuppressWarnings("unchecked")
public static <T> T[] getTArray2(T... values) {
return values;
}
instanceof
2.instanceof只能用来查看原始的类,不能用来查看带泛型的比如ArrayList<String>
无法对泛型代码直接使用instanceof关键字,因为Java编译器在生成代码的时候会擦除所有相关泛型的类型信息
ArrayList<String> arrayString = new ArrayList<>();
ArrayList<Integer> arrayInteger = new ArrayList<>();
System.out.println(arrayString instanceof ArrayList);
System.out.println(arrayInteger instanceof ArrayList);
System.out.println();
类型擦除 带来的问题
// 3.不能new泛型 new T()
//还可以采用Factory和Template两种设计模式解决,感兴趣的朋友不妨去看一下Thinking in Java中第15章中关于Creating instance of types(英文版第664页)的讲解,这里我们就不深入了
// 方式一,提高Supplier<T>接口, 无参数传入,返回类型为T
Box<String> makePair = Box.makeBox(String::new);
System.out.println(makePair.getT());
// 方式二,传入class, class本身是泛型的
makePair = Box.makeBox(String.class);
System.out.println(makePair.getT());
List<String> arrayList = new ArrayList<>();
Box.append(arrayList, String.class);
// 4.不能new泛型数组
//运行时期类型信息已经被擦除,JVM实际上根本就不知道数组里面元素的区别
// 5.不能构造泛型数组
// 方式一,利用IntFunction,
String[] tArray = getTArray(String[]::new, 2);
System.out.println(tArray.length);
// 方式二,利用反射
String[] tArray2 = getTArray("", "", "");
System.out.println(tArray2.length);
泛型相关
1)Type
Class类中泛型相关
public Type getGenericSuperclass();
public Type[] getGenericInterfaces();
Type的五种类型
- Class
- ParameterizedType:代表的是一个泛型类型,比如Point;
- TypeVariable:这个代表的就是泛型变量,例如Point,这里面的T就是泛型变量,而如果我们利用一种方法获得的对象是T,那它对应的类型就是TypeVariable;
- WildcardType:通配符比如:? extends Integer,那它对应的类型就是WildcardType;
- GenericArrayType:如果我们得到的是类似String[]这种数组形式的表达式,那它对应的类型就是GenericArrayType,非常值得注意的是如果type对应的是表达式是ArrayList这种的,这个type类型应该是ParameterizedType,而不是GenericArrayType,只有类似Integer[]这种的才是GenericArrayType类型。
2)getGenericSuperclass()
class Point<T> {
private T x,y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
//PointImpl类的实现
class PointImpl extends Point<Integer> {
}
Class<?> clazz = PointImpl.class;
Type genericSuperclassType = clazz.getGenericSuperclass();
if(genericSuperclassType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclassType;
//返回表示此类型实际类型参数的 Type 对象的数组
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type parameterArgType : actualTypeArguments) {
Class parameterArgClass = (Class) parameterArgType;
System.out.println("填充类型为:" + parameterArgClass.getName());
}
//返回 Type 对象,表示声明此类型的类或接口。
Type type1 = parameterizedType.getRawType();
Class class22 = (Class) type1;
System.out.println("PointImpl的父类类型为:"+class22.getName());
}
填充类型为:java.lang.Integer
PointImpl的父类类型为:t.Point
3)getGenericInterfaces()
class Point<T> {
private T x,y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
interface PointInterface<T,U> {
}
class PointImpl extends Point<Integer> implements PointInterface<String,Double> {
}
Class<?> clazz = PointImpl.class;
Type[] genericInterfaces = clazz.getGenericInterfaces();
for (Type genericSuperclassType : genericInterfaces) {
if(genericSuperclassType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclassType;
//返回表示此类型实际类型参数的 Type 对象的数组
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type parameterArgType : actualTypeArguments) {
Class parameterArgClass = (Class) parameterArgType;
System.out.println("填充类型为:" + parameterArgClass.getName());
}
//返回 Type 对象,表示声明此类型的类或接口。
Type type1 = parameterizedType.getRawType();
Class class22 = (Class) type1;
System.out.println("PointImpl的父类类型为:"+class22.getName());
}
}
填充类型为:java.lang.String
填充类型为:java.lang.Double
PointImpl的父类类型为:t.PointInterface
4)ParameterizedType
- getActualTypeArguments():用来返回当前泛型表达式中,用来填充泛型变量的真正值的列表。像我们这里得到的Point,用来填充泛型变量T的是Integer类型,所以这里返回的Integer类型所对应的Class对象。(有关这一段,下面会补充,这里先看getRawType)
- getRawType():我们从我们上面的代码中,也可以看到,它返回的值是Point,所以它的意义就是声明当前泛型表达式的类或者接口的Class对象。比如,我们这里的type对应的是Point,而声明Point这个泛型的当然是Point类型。所以返回的是Point.Class
5)TypeVariable
type代表的类型是一个泛型变量时,它的类型就是TypeVariable。TypeVariable有两个函数
- getName:就是得到当前泛型变量的名称;
- getBounds:返回表示此类型变量上边界的 Type 对象的数组。如果没有上边界,则默认返回Object;
interface TypeVariablePointInterface<T, U> {
}
class TypeVariableointGenericityImpl<T extends Number & Serializable> implements TypeVariablePointInterface<T, Integer> {
}
Class<?> clazz = TypeVariableointGenericityImpl.class;
Type[] types = clazz.getGenericInterfaces();
for (Type type : types) {
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
// 返回表示此类型实际类型参数的 Type 对象的数组
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type parameterArgType : actualTypeArguments) {
if (parameterArgType instanceof TypeVariable) {
TypeVariable typeVariable = (TypeVariable) parameterArgType;
System.out.println("此接口的填充类型为:" + typeVariable.getName());
// 返回表示此类型变量上边界的 Type 对象的数组。
Type[] typebounds = typeVariable.getBounds();
for (Type bound : typebounds) {
Class<?> boundClass = (Class) bound;
// 如果不写,则默认输出Object,如果写了,则输出对应的
System.out.println("bound为:" + boundClass.getName());
}
}
if (parameterArgType instanceof Class) {
Class parameterArgClass = (Class) parameterArgType;
System.out.println("此接口的填充类型为:" + parameterArgClass.getName());
}
}
}
}
此接口的填充类型为:T
bound为:java.lang.Number
bound为:java.io.Serializable
此接口的填充类型为:java.lang.Integer
6)WildcardType
当type所代表的表达式是类型通配符相关的表达式时,比如<? extends Integer>,<? super String>,或者<?>等,这个type的类型就是WildcardType!
我们先来看看WildcardType的函数:
- getUpperBounds:获取上边界对象列表,上边界就是使用extends关键定所做的的限定,如果没有默认是Object;
- getLowerBounds:获取下边界对象列表,下边界是指使用super关键字所做的限定,如果没有,则为Null
举个例子:
<? extends Integer>:这个通配符的上边界就是Integer.Class,下边界就是null
<? super String>:这个通配符的下边界是String,上边界就是Object;
通配符只能用来填充泛型类来生成对象
interface PointSingleInterface<T> {
}
class PointWildcardImpl implements PointSingleInterface<Comparable<? extends Number>> {
}
Class<?> clazz = PointWildcardImpl.class;
// 此时的type对应PointSingleInterface<Comparable<? extends Number>>
Type[] types = clazz.getGenericInterfaces();
for (Type type : types) {
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
// 得到填充PointSingleInterface的具体参数,即:Comparable<? extends Number>,仍然是一个ParameterizedType
Type[] actualTypes = parameterizedType.getActualTypeArguments();
for (Type actualType : actualTypes) {
if (actualType instanceof ParameterizedType) {
ParameterizedType ComparableType = (ParameterizedType) actualType;
// 对Comparable<? extends Number>再取填充参数,得到的type对应<? extends Number>,这个就是WildcardType了
Type[] compareArgs = ComparableType.getActualTypeArguments();
for (Type Arg : compareArgs) {
if (Arg instanceof WildcardType) {
// 将得到的对应WildcardType的type强转为WildcardType的变量
WildcardType wt = (WildcardType) Arg;
// 利用getLowerBounds得到下界,即派生自Super的限定,如果没有派生自super则为null
Type[] lowerBounds = wt.getLowerBounds();
for (Type bound : lowerBounds) {
Class<?> boundClass = (Class) bound;
System.out.println("lowerBound为:" + boundClass.getName());
}
// 通过getUpperBounds得到上界,即派生自extends的限定,如果没有,默认是Object
Type[] upperBounds = wt.getUpperBounds();
for (Type bound : upperBounds) {
Class<?> boundClass = (Class) bound;
// 如果不写,则默认输出Object,如果写了,则输出对应的
System.out.println("upperBound为:" + boundClass.getName());
}
}
}
}
}
}
}
upperBound为:java.lang.Number
7)GenericArrayType
当type对应的类型是类似于String[]、Integer[]等的数组时,那type的类型就是GenericArrayType;这里要特别说明的如果type对应的是类似于ArrayList、List这样的类型,那type的类型应该是ParameterizedType,而不是GenericArrayType,因为ArrayList是一个泛型表达式。所以当且仅当type对应的类型是类似于String[]、Integer[]这样的数组时,type的类型才是GenericArrayType!
- getGenericComponentType()
这是GenericArrayType仅有一个函数,由于getGenericComponentType所代表的表达是String[]这种的数组,所以getGenericComponentType获取的就是这里的数组类型所对应的Type,比如这里的String[]通过getGenericComponentType获取到的Type对应的就是String.
interface GenericArrayInterface<T> {
}
class GenericArrayImpl<U> implements GenericArrayInterface<U[]> {
}
Class<?> clazz = GenericArrayImpl.class;
Type[] interfaces = clazz.getGenericInterfaces();
for (Type type : interfaces) {
if (type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) type;
Type[] actualArgs = pt.getActualTypeArguments();
for (Type arg : actualArgs) {
if (arg instanceof GenericArrayType) {
GenericArrayType arrayType = (GenericArrayType) arg;
Type comType = arrayType.getGenericComponentType();
System.out.println("数组类型为:" + comType.getTypeName());
}
}
}
}
数组类型为:U
8)通用的类型转换函数
private static void parseClass(Class<?> c) {
parseTypeParameters(c.getGenericInterfaces());
System.out.println();
}
private static void parseTypeParameter(Type type) {
if (type instanceof Class) {
Class<?> c = (Class<?>) type;
System.out.println(c.getSimpleName());
} else if (type instanceof TypeVariable) {
TypeVariable<?> tv = (TypeVariable<?>) type;
System.out.println(tv.getName());
parseTypeParameters(tv.getBounds());
} else if (type instanceof WildcardType) {
WildcardType wt = (WildcardType) type;
System.out.println("?");
parseTypeParameters(wt.getUpperBounds());
parseTypeParameters(wt.getLowerBounds());
} else if (type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) type;
Type t = pt.getOwnerType();
if (t != null) {
parseTypeParameter(t);
}
parseTypeParameter(pt.getRawType());
parseTypeParameters(pt.getActualTypeArguments());
} else if (type instanceof GenericArrayType) {
GenericArrayType arrayType = (GenericArrayType) type;
Type t = arrayType.getGenericComponentType();
parseTypeParameter(t);
}
}
private static void parseTypeParameters(Type[] types) {
for (Type type : types) {
parseTypeParameter(type);
}
}
parseClass(PointImpl.class);
parseClass(TypeVariableointGenericityImpl.class);
parseClass(PointWildcardImpl.class);
parseClass(GenericArrayImpl.class);
PointInterface
String
Double
TypeVariablePointInterface
T
Number
Serializable
Integer
PointSingleInterface
Comparable
?
Number
GenericArrayInterface
U
Object
J2SE 8的泛型的更多相关文章
- Java泛型的历史
为什么Java泛型会有当前的缺陷? 之前的章节里已经说明了Java泛型擦除会导致的问题,C++和C#的泛型都是在运行时存在的,难道Java天然不支持“真正的泛型”吗? 事实上,在Java1.5在200 ...
- 初识JAVA(【面向对象】:pub/fri/pro/pri、封装/继承/多态、接口/抽象类、静态方法和抽象方法;泛型、垃圾回收机制、反射和RTTI)
JAVA特点: 语法简单,学习容易 功能强大,适合各种应用开发:J2SE/J2ME/J2EE 面向对象,易扩展,易维护 容错机制好,在内存不够时仍能不崩溃.不死机 强大的网络应用功能 跨平台:JVM, ...
- [置顶] 【J2SE 】1136 容器之旅
开篇引言 本篇文章我将要详细的介绍一下什么是容器?以及什么是1136?来系统全面的了解容器,以及容器的应用,下面就进入我们的容器之旅吧! 1.什么是容器? 用来存储和组织其他对象的对象.我们也可以这样 ...
- Java 泛型具体解释
在Java SE1.5中.添加了一个新的特性:泛型(日本语中的总称型).何谓泛型呢?通俗的说.就是泛泛的指定对象所操作的类型.而不像常规方式一样使用某种固定的类型去指定. 泛型的本质就是将所操作的数据 ...
- Java1.5泛型指南中文版(Java1.5 Generic Tutorial)
Java1.5泛型指南中文版(Java1.5 Generic Tutorial) 英文版pdf下载链接:http://java.sun.com/j2se/1.5/pdf/generics-tutori ...
- java泛型小问题
几年前当Java5还未正式发布的时候,看到过一些人写的介绍Tiger中的新特性,当时对我第一感觉冲击最大的就是泛型(generics)和注释(annotation),因为它们直接影响了我们编码的语法习 ...
- J2SE 8的注解
1. 注解概念 (1) 注解格式 modifiers @interface AnnotationName { type elementName(); type elementName() defaul ...
- J2SE基本安装和java的环境变量
J2SE基本安装和java的环境变量 1. 首先登录http://www.oracle.com,下载JDK(J2SE) JDK有很多版本其中JDK 1.0,1.1,1.2,1.3,1.4 1.5 ...
- J2SE语言--百度百科
Java2平台包括:标准版(J2SE).企业版(J2EE)和微缩版 (J2ME)三个版本.J2SE,J2ME和J2EE,这也就是SunONE(Open NetEnvironment)体系.J2SE就是 ...
随机推荐
- Linux设备树使用(二)
一.设备树与驱动的匹配1.设备树会被/scripts中的dtc可执行程序编译成二进制.dtb文件,之前设备树中的节点信息会以单链表的形式存储在这个.dtb文件中:驱动与设备树中compatible属性 ...
- stenciljs 学习七 路由
stenciljs路由类似react router 安装 npm install @stencil/router --save 使用 导入包 import "@stencil/router& ...
- apache flink docker-compose 运行试用
apache 是一个流处理框架,官方提供了docker 镜像,同时也提供了基于docker-compose 运行的说明 docker-compose file version: "2.1&q ...
- PHP独立环境搭建细节
一.安装前准备: 准备安装软件此处以以下软件为例: Appache:httpd-2.2.21-win32-x86-openssl-0.9.8r.msi MySQL: mysql-5.5.21-win ...
- 批处理(bat)命令学习的一些总结
这篇笔记是我对批处理学习的一些总结,能在系统帮助里找到的内容我就不写了,太偏门的也不写,只写些个人感觉很好用的技巧,大部分属于整理 一.set 篇: 1.set(无开关) set .=test set ...
- cmd下查看当前登陆用户
cmd下查看当前登陆用户 终端下,自然可以用quser这个命令了.但是在其它如专业版2k下如何查看在线用户呢? C:\Documents and Settings\Administrator>n ...
- 网络基础 TCP/IP
为了理解 HTTP,我们有必要事先了解一下 TCP/IP 协议族.通常使用的网络(包括互联网)是在 TCP/IP 协议族的基础上运作的.而 HTTP 属于它内部的一个子集.接下来,我们仅介绍理解 HT ...
- dongle0
*CLI> -- [dongle0] Trying to connect on /dev/ttyUSB2... 插拔dongle[Jan 13 23:42:20] WARNING[3443]: ...
- JUC集合之 CopyOnWriteArraySet
CopyOnWriteArraySet介绍 它是线程安全的无序的集合,可以将它理解成线程安全的HashSet.有意思的是,CopyOnWriteArraySet和HashSet虽然都继承于共同的父类A ...
- h5 的 audio 标签知识点
因为音频格式有版权,各浏览器使用不同的音频格式. 音频格式兼容性 音频格式 Chrome Firefox IE9 Opera Safari MP3 支持 不支持 支持 不支持 支持 OGG 支持 支持 ...