Java 泛型编程.doc_第1页
Java 泛型编程.doc_第2页
Java 泛型编程.doc_第3页
Java 泛型编程.doc_第4页
Java 泛型编程.doc_第5页
已阅读5页,还剩12页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Java1.5泛型1. 介绍JDK1.5中引入了对java语言的多种扩展,泛型(generics)即其中之一。这个教程的目标是向您介绍java的泛型(generic)。你可能熟悉其他语言的泛型,最著名的是C+的模板(templates)。如果这样,你很快就会看到两者的相似之处和重要差异。如果你不熟悉相似的语法结构,那么更好,你可以从头开始而不需要忘记误解。Generics允许对类型进行抽象(abstract over types)。最常见的例子是集合类型(Container types),Collection的类树中任意一个即是。下面是那种典型用法:List myIntList = new LinkedList();/ 1myIntList.add(new Integer(0);/ 2Integer x = (Integer) myIntList.iterator().next();/ 3第3行的类型转换有些烦人。通常情况下,程序员知道一个特定的list里边放的是什么类型的数据。但是,这个类型转换是必须的(essential)。编译器只能保证iterator返回的是Object类型。为了保证对Integer类型变量赋值的类型安全,必须进行类型转换。当然,这个类型转换不仅仅带来了混乱,它还可能产生一个运行时错误(run time error),因为程序员可能会犯错。程序员如何才能明确表示他们的意图,把一个list中的内容限制为一个特定的数据类型呢?这是generics背后的核心思想。这是上面程序片断的一个泛型版本:List myIntList = new LinkedList(); / 1myIntList.add(new Integer(0); / 2Integer x = myIntList.iterator().next(); / 3注意变量myIntList的类型声明。它指定这不是一个任意的List,而是一个Integer的List,写作:List。我们说List是一个带一个类型参数的泛型接口(a generic interface that takes a type parameter),本例中,类型参数是Integer。我们在创建这个List对象的时候也指定了一个类型参数。另一个需要注意的是第3行没了类型转换。 现在,你可能认为我们已经成功地去掉了程序里的混乱。我们用第1行的类型参数取代了第3行的类型转换。然而,这里还有个很大的不同。编译器现在能够在编译时检查程序的正确性。当我们说myIntList被声明为List类型,这告诉我们无论何时何地使用myIntList变量,编译器保证其中的元素的正确的类型。与之相反,一个类型转换说明程序员认为在那个代码点上它应该是那种类型。实际结果是,这可以增加可读性和稳定性(robustness),尤其在大型的程序中。2. 定义简单的泛型下面是从java.util包中的List接口和Iterator接口的定义中摘录的片断:public interface List void add(E x); Iterator iterator();public interface Iterator E next(); boolean hasNext();这些都应该是很熟悉的,除了尖括号中的部分,那是接口List和Iterator中的形式类型参数的声明。类型参数在整个类的声明中可用,几乎是所有可是使用其他普通类型的地方(但是有些重要的限制,请参考后续内容)。在介绍那一节我们看到了对泛型类型声明List(the generic type declaration List)的调用,如List。在这个调用中(通常称作一个参数化类型a parameterized type),所有出现形式类型参数(formal type parameter,这里是E)都被替换成实体类型参数(actual type argument)(这里是Integer)。你可能想象,List代表一个E被全部替换成Integer的版本:public interface IntegerList void add(Integer x)Iterator iterator();这种直觉可能有帮助,但是也可能导致误解。它有帮助,因为List的声明确实有类似这种替换的方法。它可能导致误解,因为泛型声明绝不会实际的被这样替换。没有代码的多个拷贝,源码中没有、二进制代码中也没有;磁盘中没有,内存中也没有。如果你是一个C+程序员,你会理解这是和C+模板的很大的区别。一个泛型类型的声明只被编译一次,并且得到一个class文件,就像普通的class或者interface的声明一样。类型参数就跟在方法或构造函数中普通的参数一样。就像一个方法有形式参数(formal value parameters)来描述它操作的参数的种类一样,一个泛型声明也有形式类型参数(formal type parameters)。当一个方法被调用,实参(actual arguments)替换形参,方法体被执行。当一个泛型声明被调用,实际类型参数(actual type arguments)取代形式类型参数。一个命名的习惯:我们推荐你用简练的名字作为形式类型参数的名字(如果可能,单个字符)。最好避免小写字母,这使它和其他的普通的形式参数很容易被区分开来。许多容器类型使用E作为其中元素的类型,就像上面举的例子。在后面的例子中还会有一些其他的命名习惯。3. 泛型和子类继承让我们测试一下我们对泛型的理解。下面的代码片断合法么?List ls = new ArrayList(); /1List lo = ls; /2 第1行当然合法,但是这个问题的狡猾之处在于第2行。这产生一个问题:一个String的List是一个Object的List么?大多数人的直觉是回答:“当然!”。好,在看下面的几行:lo.add(new Object(); / 3String s = ls.get(0); / 4: 试图把Object赋值给String这里,我们使用lo指向ls。我们通过lo来访问ls,一个String的list。我们可以插入任意对象进去。结果是ls中保存的不再是String。当我们试图从中取出元素的时候,会得到意外的结果。java编译器当然会阻止这种情况的发生。第2行会导致一个编译错误。总之,如果Student是Person的一个子类型(子类或者子接口),而G是某种泛型声明,那么G是G的子类型并不成立! 例如: List ls = new ArrayList(); / 1 List lo = ls;/ 2 错误的赋值这可能是你学习泛型中最难理解的部分,因为它和你的直觉相反。这种直觉的问题在于它假定这个集合不改变。我们的直觉认为这些东西都不可改变。举例来说,如果一个交通部(DMV)提供一个驾驶员里表给人口普查局,这似乎很合理。我们想,一个List是一个List,假定Driver是Person的子类型。实际上,我们传递的是一个驾驶员注册的拷贝。然而,人口普查局可能往驾驶员list中加入其他人,这破坏了交通部的记录。为了处理这种情况,考虑一些更灵活的泛型类型很有用。到现在为止我们看到的规则限制比较大。4. 通配符(Wildcards)考虑写一个例程来打印一个集合(Collection)中的所有元素。下面是在老的语言中你可能写的代码:void printCollection(Collection c) Iterator i = c.iterator(); for (int k = 0; k c.size(); k+) System.out.println(i.next(); 下面是一个使用泛型的幼稚的尝试(使用了新的循环语法):void printCollection(Collection c) for (Object e : c) System.out.println(e); 问题是新版本的用处比老版本小多了。老版本的代码可以使用任何类型的collection作为参数,而新版本则只能使用Collection,我们刚才阐述了,它不是所有类型的collections的父类。那么什么是各种collections的父类呢?它写作: Collection(发音为:collection of unknown),就是,一个集合,它的元素类型可以匹配任何类型。显然,它被称为通配符。我们可以写:void printCollection(Collection c) for (Object e : c) System.out.println(e);现在,我们可以使用任何类型的collection来调用它。注意,我们仍然可以读取c中的元素,其类型是Object。这永远是安全的,因为不管collection的真实类型是什么,它包含的都是objects。但是将任意元素加入到其中不是类型安全的:Collection c = new ArrayList();c.add(new Object(); / 编译时错误因为我们不知道c的元素类型,我们不能向其中添加对象。add方法有类型参数E作为集合的元素类型。我们传给add的任何参数都必须是一个未知类型的子类。因为我们不知道那是什么类型,所以我们无法传任何东西进去。唯一的例外是null,它是所有类型的成员。另一方面,我们可以调用get()方法并使用其返回值。返回值是一个未知的类型,但是我们知道,它总是一个Object,因此把get的返回值赋值给一个Object类型的对象或者放在任何希望是Object类型的地方是安全的。4.1. 有限制的通配符(Bounded Wildcards)考虑一个简单的画图程序,它可以用来画各种形状,比如矩形和圆形。为了在程序中表示这些形状,你可以定义下面的类继承结构:abstract class Shape public abstract void draw(Canvas c);class Circle extends Shape private int x, y, radius;public void draw(Canvas c) / .class Rectangle extends Shape private int x, y, width, height;public void draw(Canvas c) / .这些类可以在一个画布(Canvas)上被画出来:public class Canvas public void draw(Shape s) s.draw(this);所有的图形通常都有很多个形状。假定它们用一个list来表示,Canvas里有一个方法来画出所有的形状会比较方便:public void drawAll(List shapes) for (Shape s : shapes) s.draw(this); 现在,类型规则导致drawAll()只能使用Shape的list来调用。它不能,比如说对List来调用。这很不幸,因为这个方法所作的只是从这个list读取shape,因此它应该也能对List调用。public class ShapeTest public static void main(String args) List list1 = new ArrayList(); list1.add(new Circle(); list1.add(new Rectangle(); Canvas c = new Canvas(); c.drawAll(list1); / 调用drawAll方法,可以通过 我们真正要的是这个方法能够接受一个任意种类的shape:我们修改Canvas类的drawAll方法如下:public void drawAll(List shapes) for (Shape s : shapes) s.draw(this);这里有一处很小但是很重要的不同:我们把类型 List 替换成了List。现在drawAll()可以接受任何Shape的子类的List,所以我们可以对List进行调用。List是有限制通配符的一个例子。这里?代表一个未知的类型,就像我们前面看到的通配符一样。但是,在这里,我们知道这个未知的类型实际上是Shape的一个子类(它可以是Shape本身或者Shape的子类,或孙子类,而不必是必须extends自Shape)。我们说Shape是这个通配符的上限(upper bound)。参见如下代码:/ 椭圆形继承自圆形,而不是直接继承自Shape类class Ellipse extends Circlepublic static void main(String args) List list1 = new ArrayList();list1.add(new Ellipse();Canvas c = new Canvas();c.drawAll(list1);/ 编译通过,可以运行像平常一样,要得到使用通配符的灵活性有些代价。这个代价是,现在像shapes中写入是非法的。比如下面的代码是不允许的:public void addRectangle(List shapes) shapes.add(0, new Rectangle(); / compile-time error!你应该能够指出为什么上面的代码是不允许的。因为shapes.add的第二个参数类型是? extends Shape 一个Shape未知的子类。因此我们不知道这个类型是什么,我们不知道它是不是Rectangle的父类;它可能是也可能不是一个父类,所以这里传递一个Rectangle不安全。有限制的通配符正是我们解决DMV给人口普查局传送名单的例子所需要的。我们的例子假定数据用一个姓名(String)到people(用Person或其子类来表示,比如Driver)。Map是一个有两个类型参数的泛型类型的例子,表示map的键key和值value。再一次,注意形式类型参数的命名习惯K代表keys,V代表vlaues。/ 人口普查类public class Census public static void addRegistry(Map registry) .Map allDrivers = .;Census.addRegistry(allDrivers);5. 泛型方法考虑写一个方法,它用一个Object的数组和一个collection作为参数,完成把数组中所有object放入collection中的功能。下面是第一次尝试:static void fromArrayToCollection(Object a, Collection c) for (Object o : a) c.add(o); / 编译期错误现在,你应该能够学会避免初学者试图使用Collection作为集合参数类型的错误了。或许你已经意识到使用 Collection也不能工作。回忆一下,你不能把对象放进一个未知类型的集合中去。解决这个问题的办法是使用generic methods。就像类型声明,方法的声明也可以被泛型化就是说,带有一个或者多个类型参数。static void fromArrayToCollection(T a, Collection c) for (T o : a) c.add(o); / correct / 通过泛型方法,我们已经知道数组和集合中存放的都是相同的类型,具体是什么类型,运/行的时候才知道,反正是相同的类型我们可以使用任意集合来调用这个方法,只要集合中元素的类型是数组的元素类型的父类。 Object oa = new Object100;Collection co = new ArrayList();fromArrayToCollection(oa, co);/ T 指ObjectString sa = new String100;Collection cs = new ArrayList();fromArrayToCollection(sa, cs);/ T inferred to be StringfromArrayToCollection(sa, co);/ T inferred to be Object ,String 是Object的子类Integer ia = new Integer100;Float fa = new Float100;Number na = new Number100;Collection cn = new ArrayList();fromArrayToCollection(ia, cn);/ T inferred to be NumberfromArrayToCollection(fa, cn);/ T inferred to be NumberfromArrayToCollection(na, cn);/ T inferred to be NumberfromArrayToCollection(na, co);/ T inferred to be ObjectfromArrayToCollection(na, cs);/ compile-time error,Number 不是String的子类注意,我们并没有传送真实类型参数(actual type argument)给一个泛型方法。编译器根据实参为我们推断类型参数的值。它通常推断出能使调用类型正确的最明确的类型参数现在有一个问题:我们应该什么时候使用泛型方法,又什么时候使用通配符类型呢?为了理解答案,让我们先看看Collection库中的几个方法。public interface Collection boolean containsAll(Collection c); boolean addAll(Collection c);我们也可以使用泛型方法来代替:public interface Collection boolean containsAll(Collection c); boolean addAll(Collection c); / T是E的子类 / hey, type variables can have bounds too!但是,在 containsAll 和 addAll中,类型参数T 都只使用一次。返回值的类型既不依赖于类型参数(type parameter)也不依赖于方法的其他参数(这里,只有简单的一个参数)。这告诉我们类型参数(type argument)被用作多态(polymorphism),它唯一的效果是允许在不同的调用点,可以使用多种实参类型(actual argument)。如果是这种情况,应该使用通配符。通配符就是被设计用来支持灵活的子类化的,这是我们在这里要强调的。泛型函数允许类型参数被用来表示方法的一个或多个参数之间的依赖关系,或者参数与其返回值的依赖关系。如果没有这样的依赖关系,不应该使用泛型方法。一前一后的同时使用泛型方法和通配符也是可能的。下面是方法 Collections.copy():class Collections public static void copy(List dest, List src)./正确的调用过程public static void main(String args) List dest = new ArrayList();List src = new ArrayList();src.add(aaa);src.add(bbb);src.add(ccc);Collections.copy(dest, src); / 可以运行/ 错误的调用过程public static void main(String args) List dest = new ArrayList();List src = new ArrayList();src.add(aaa);src.add(bbb);src.add(ccc);Collections.copy(dest, src);/ 编译错误注意两个参数的类型的依赖关系。任何被从源list从拷贝出来的对象必须能够将其指定为目标list(dest) 的元素的类型T类型。因此源类型的元素类型可以是T的任意子类型,我们不关心具体的类型。copy方法的签名使用一个类型参数表示了类型依赖,但是使用了一个通配符作为第二个参数的元素类型。我们也可以用其他方式写这个函数的签名而根本不使用通配符:class Collections / 我们重新定义的copy方法,和刚才的方法意义是一样的/ src的类型参数S 必须是 T 的子类型public static void copy(List dest, List src) / .System.out.println(调用ok);public static void main(String args) List dest = new ArrayList();List src = new ArrayList();src.add(aaa);src.add(bbb);src.add(ccc);Collections.copy(dest, src);/ 运行正常化这也可以,但是第一个类型参数在dest的类型和第二个参数的类型参数S的上限这两个地方都有使用,而S本身只使用一次,在src的类型中没有其他的依赖于它。这意味着我们可以用通配符来代替S。使用通配符比声明显式的类型参数更加清晰和准确,所以在可能的情况下使用通配符更好。通配符还有一个优势式他们可以在方法签名之外被使用,比如field的类型,局部变量和数组。这就有一个例子。回到我们的画图问题,假定我们想要保持画图请求的历史记录。我们可以把历史记录保存在Shape类的一个静态成员变量里,在drawAll() 被调用的时候把传进来的参数保存进历史记录:static ListList history = new ArrayListList();public void drawAll(List shapes) history.add(shapes);for (Shape s : shapes) s.draw(this);最终,再说一下类型参数的命名习惯。我们使用T 代表类型,无论何时都没有比这更具体的类型来区分它。这经常见于泛型方法。如果有多个类型参数,我们可能使用字母表中T的临近的字母,比如S。如果一个泛型函数在一个泛型类里边出现,最好避免在方法的类型参数和类的类型参数中使用同样的名字来避免混淆。对内部类也是同样。6. 与旧代码交互直到现在,我们的例子中都假定了一个理想的世界,那里所有人使用的都是最新版本的java编程语言,它支持泛型。唉,现实并非如此。百万行代码都是在早先版本的语言下写作的,他们不可能一晚上就转换过来。后面,在第10部分,我们会解决把老代码转换为使用泛型的代码的问题。在这里,我们把注意力放在一个更简单的问题:老代码怎么和泛型代码交互?这个问题包括两部分:在泛型中使用老代码和在老代码中使用泛型代码。6.1. 在泛型代码中使用老代码怎样才能使用老代码的同时在自己的代码中享受泛型带来的好处?作为一个例子,假定你想使用包 com.Fooblibar.widgets。F(完全虚构出来的公司) 的人们出售一种进行库存管理的系统,下面是主要代码:package com.Fooblibar.widgets;import java.util.*;/ 库存类public class Inventory /* * 添加一个新配件到库存数据库 配件有名字name, * 并由零件(Part)的集合组成。 零件由parts 指定. * Collection parts 中的元素必须实现Part接口。 */public static void addAssembly(String name, Collection parts) / .public static Assembly getAssembly(String name) / .return null;/ 零件接口interface Part / 汇总接口interface Assembly Collection getParts(); / Returns a collection of Parts现在,你想使用上述API写新代码。如果能保证调用addAssembly()时总是使用正确的参数会很棒就是说,你传进去的确实时一个Part的Collection。当然,泛型可以实现这个目的:package com.mycompany.inventory;import com.Fooblibar.widgets.*;/ 刀片类public class Blade implements Part /-package com.mycompany.inventory;import com.Fooblibar.widgets.Part;/闸刀public class Guillotine implements Part /-package com.mycompany.inventory;import java.util.*;import com.Fooblibar.widgets.*;public class Main public static void main(String args) Collection c = new ArrayList();c.add(new Guillotine();c.add(new Blade();Inventory.addAssembly(thingee, c);Collection k = Inventory.getAssembly(thingee).getParts();当我们调用addAssembly,它希望第二个参数是Collection类型。而实际参数是Collection 类型。这可以工作,但是为什么?毕竟,大多数集合不包含Part对象,而且总的来说,编译器无法知道Collection指的是什么类型的集合。在严格的泛型代码里,Collection应该总是带着类型参数。当一个泛型类型,比如Collection被使用而没有类型参数时,它被称作一个raw type(自然类型?)。大多数人的第一直觉时Collection实际上意味着 Collection。但是,像我们前面看到的,当需要Collection时传递 Collection是不安全的。类型Collection表示一个未知类型元素的集合,就像Collection,这样说更准确。但是等一下,那也不正确。考虑getParts()这个调用,它返回一个Collection。然后它被赋值给k,而k是Collection。如果这个调用的结果是一个Collection,这个赋值应该是一个错误。事实上,这个赋值是合法的,但是它产生一个未检查警告(unchecked warning)。这个警告是必要的,因为事实是编译器无法保证其正确性。我们没有办法检查getAssembly()中的旧代码来保证返回的确实是一个Collection。代码里使用的类型是Collection,可以合法的向其中加入任何Object。那么,这应该是一个错误么?理论上讲,Yes,但是实际上讲,如果泛型代码要调用旧代码,那么这必须被允许。这取决于你,程序员,在这种情况下来满足你自己。这个赋值是合法的因为getAssembly()的调用约定中说它返回一个Part的集合,即使这个类型声明中没有显示出这一点。因此,自然类型和通配符类型很像,但是他们的类型检查不是同样严格。允许泛型与已经存在的老代码相交互是一个深思熟虑的决定。从泛型代码中调用老代码具有先天的危险性,一旦你把泛型编程和非泛型编程混合起来,泛型系统所提供的所有安全保证都失效。然而,你还是比你根本不用泛型要好。至少你知道你这一端的代码是稳定的。在非泛型代码远比泛型代码多的时候,不可避免会出现两者必须混合的情况。如果你发现你不得不混合旧代码和泛型代码,仔细注意未检查警告(unchecked warnings),仔细考虑你怎样才能证明出现警告的部分代码是正确的。如果你仍然犯了错,而导致警告的代码确实不是类型安全的,那么会发生什么?让我们看一下这种情形。在这个过程中,我们将了解一些编译器工作的内幕。6.2. 擦除和翻译(Erasure and Translation)public class Test public static String loophole(Integer x) List ys = new LinkedList();List xs = ys;xs.add(x); / compile-time unchecked warningreturn ys.iterator().next();public static void main(String args) / 运行时抛出ClassCastException String s =loophole(2);System.out.println(s); 这里,我们用一个老的普通的list的引用来指向一个String的list。我们插入一个Integer到这个list中,并且试图得到一个String。这是明显的错误。如果我们忽略这个警告并且试图运行以上代码,它将在我们试图使用错误的类型的地方失败。在运行的时候,上面的代码与下面的代码的行为一样:public String loophole(Integer x) List ys = new LinkedList(); List xs = ys; xs.add(x); return (String) ys.iterator().next(); / run time error当我们从list中获取一个元素的时候,并且试图通过转换为String而把它当作一个string,我们得到一个 ClassCastException。完全一样的事情发生在使用泛型的代码上。这样的原因是,泛型是通过java编译器的称为擦除(erasure)的前端处理来实现的。你可以(基本上就是)把它认为是一个从源码到源码的转换,它把泛型版本的loophole()转换成非泛型版本。结果是,java虚拟机的类型安全和稳定性决不能冒险,即使在又unchecked warning的情况下。基本上,擦除去掉了所有的泛型类型信息。所有在尖括号之间的类型信息都被扔掉了,因此,比如说一个List类型被转换为List。所有对类型变量的引用被替换成类型变量的上限(通常是Object)。而且,无论何时如果结果代码类型不正确,会插入一个到合适的类型的转换,就像loophole的最后一行那样。擦除的全部的细节超出了本文的范围,但是我们给出的简单描述与事实很接近。知道一点这个有好处,特别是如果你要作一些复杂的事,比如把现有API转换成使用泛型的代码(第10部分)或者仅仅是想理解为什么会这样。6.3. 在老代码中使用泛型代码现在让我们来考虑相反的情形。假定F公司的人决定把他们的代码转换为使用泛型来实现,但是他们的一些客户没有转换。现在代码就像下面:package com.Fooblibar.widgets;public interface Part . /-public class Inventory public static void addAssembly(String name, Collection parts) / public static Assembly getAssembly(String name) ./-public interface Assembly Collection getParts(); / Returns a collection of Parts/-/客户端代码如下:package com.mycompany.inventory;import com.Fooblibar.widgets.*;public class Blade implements Part ./-public class Guillotine implements Part /-public class Main public static void main(String args) Collection c = new ArrayList();c.add(new Guillotine() ;c.add(new Blade();Inventory.addAssembly(”thingee”, c); / 1: unchecked warningCollection k = Inventory.getAssembly(”thingee”).getParts();客户端代码是在泛型被引入之前完成的,但是它使用了包com.Fooblibar.widgets和集合库,它们都使用了泛型。客户端代码中的泛型类的声明都是使用了自然类型(raw types)。第1行产生一个unchecked warning,因为一个自然的Collection被传递到一个需要Collection的地方,而编译器无法保证Collection就是一个Collection。你还有另一种选择,你可以使用source 1.4 标志来编译客户端代码,以保证不会产生警告。但是这种情况下你无法使用jdk1.5 中的任何新特性。7. 要点(The Fine Print)7.1. 一个泛型类被其所有调用共享下面的代码打印的结果是什么? List l1 = new ArrayList(); List l2 = new ArrayList(); System.out.println(l1.getClass() = l2.getClass();或许你会说false,但是那你就错了。它打印出true。因为所有的泛型类型在运行时有同样的类(class),就是说:所有的泛型在运行时共享一个类,而不管他们的实际类型参数。事实上,泛型之所以为泛型就是因为它对所有其可能的类型参数,它有同样的行为;同样的类可以被当作许多不同的类型。7.2. 转型和instanceof泛型类被所有其实例(instances)共享的另一个暗示是检查一个实例是不是一个特定类型的泛型类是没有意义的。Collection cs = new ArrayList();if (cs instanceof Collection) . / 非法类似

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论