精华内容
下载资源
问答
  • 由于环境的不确定性和用户操作的不可以预知性都可能导致程序出现各种问题,因此异常机制最重要的无非就是:增强程序的健壮性和用户体验,尽可能的捕获所有预知的异常并写好处理的代码,当异常出现的时候,程序自动...

    测试题:


    0.结合你自身的编程经验,总结下异常处理机制的重要性?

    答案:
    由于环境的不确定性和用户操作的不可以预知性都可能导致程序出现各种问题,因此异常机制最重要的无非就是:增强程序的健壮性和用户体验,尽可能的捕获所有预知的异常并写好处理的代码,当异常出现的时候,程序自动消化并恢复正常(不至于崩溃)。

    1.请问以下代码是否会产生异常,如果会的话,请写出异常的名称:

    >>> my_list = [1, 2, 3, 4,,]
    答案:
    语法错误,SyntaxError: invalid syntax

    2.请问以下代码是否会产生异常,如果会的话,请写出异常的名称:

    >>> my_list = [1, 2, 3, 4, 5]
    >>> print(my_list[len(my_list)])
    答案:
    len(my_list)是获得列表的长度,这里长度为5,该列表各个元素的访问索引号分别是:[0,1,2,3,4],因此试图访问my_list(5)会引发 IndexError: list index out of range 异常。

    3.请问以下代码是否会产生异常,如果会的话,请写出异常的名称:

    >>> my_list = [3, 5, 1, 4, 2]
    >>> my_list.sorted()

    答案:
    初学者容易疏忽的错误,列表的排序方法叫 list.sort(),sorted() 是BIF。因此会引发 AttributeError: 'list' object has no attribute 'sorted' 异常。

    4.请问以下代码是否会产生异常,如果会的话,请写出异常的名称:

    >>> my_dict = {'host': 'http://bbs.fishc.com', 'port': '80'}
    >>> print(my_dict['server'])

    答案:
    尝试访问字典中一个不存在的“键”引发 KeyError: 'server' 异常,为了避免这个异常发生,可以使用 dict.get() 方法:
    if not my_dict.get('server'):
            print('您所访问的键【server】不存在!')

    5.请问以下代码是否会产生异常,如果会的话,请写出异常的名称:

    def my_fun(x, y):
            print(x, y)
    
    my_fun(x=1, 2)

    答案:
    如果使用关键字参数的话,需要两个参数均使用关键字参数 my_fun(x=1, y=2)

    6.请问以下代码是否会产生异常,如果会的话,请写出异常的名称:

    f = open('C:\\test.txt', wb)
    f.write('I love FishC.com!\n')
    f.close()

    答案:
    注意 open() 第二个参数是字符串,应该 f = open("C:\\test.txt", 'wb')。wb不加双引号 python 还以为是变量名呢,往上一找,艾玛没找着……引发NameError 异常。由于打开文件失败,接着下边一连串与 f 相关的均会报同样异常。

    7.请问以下代码是否会产生异常,如果会的话,请写出异常的名称:

    def my_fun1():
            x = 5
            def my_fun2():
                    x *= x
                    return x
            return my_fun2()
    
    my_fun1()

    答案:
    闭包的知识大家还记得不? Python 认为在内部函数的 x 是局部变量的时候,外部函数的 x 就被屏蔽了起来,所以执行 x *= x 的时候,在右边根本就找不到局部变量 x 的值,因此报错。
    在 Python3 之前没有直接的解决方案,只能间接地通过容器类型来存放,因为容器类型不是放在栈里,所以不会被“屏蔽”掉。容器类型这个词儿大家是不是似曾相识?我
    们之前介绍的字符串、列表、元祖,这些啥都可以往里的扔的就是容器类型啦。

    于是乎我们可以把代码改造为:
    def my_fun1():
            x = [5]
            def my_fun2():
                    x[0] *= x[0]
                    return x[0]
            return my_fun2()
    
    my_fun1()

    但是到了 python3 的世界里,又有了不少的改进,如果我们希望在内部函数里可以修改外部函数里的局部变量的值,那么也有一个关键字可以使用,就是 nonlocal :
    def my_fun1():
            x = 5
            def my_fun2():
                    nonlocal x
                    x *= x
                    return x
            return my_fun2()
    
    my_fun1()








    展开全文
  • 答:由于环境的不确定性和用户操作的不可以预知性都可能导致程序出现各种问题,因此异常机制最重要的无非就是:增强程序的健壮性和用户体验,尽可能的捕获所有预知的异常并写好处理的代码,当异常出现的时候,程序...

    内容来源于网络,本人只是在此稍作整理,如有涉及版权问题,归小甲鱼官方所有。

    练习题(来自小甲鱼官方论坛)

    0.结合你自身的编程经验,总结下异常处理机制的重要性?

    答:由于环境的不确定性和用户操作的不可以预知性都可能导致程序出现各种问题,因此异常机制最重要的无非就是:增强程序的健壮性和用户体验,尽可能的捕获所有预知的异常并写好处理的代码,当异常出现的时候,程序自动消化并恢复正常(不至于崩溃)。

    1. 请问以下代码是否会产生异常,如果会的话,请写出异常的名称:

    >>> my_list = [1, 2, 3, 4,,]

    答:语法错误

    SyntaxError: invalid syntax.

    2. 请问以下代码是否会产生异常,如果会的话,请写出异常的名称:

    >>> my_list = [1, 2, 3, 4, 5]

    >>> print(my_list[len(my_list)])

    答:列表越界

    Indexerror: list index out of range.

    3. 请问以下代码是否会产生异常,如果会的话,请写出异常的名称:

    >>> my_list = [3, 5, 1, 4, 2]

    >>> my_list.sorted()

    答:尝试访问未知的对象属性

    AttributeError: 'list' object has no attribute 'sorted'.

    4. 请问以下代码是否会产生异常,如果会的话,请写出异常的名称:

    >>> my_dict = {'host': 'http://bbs.fishc.com', 'port': '80'}

    >>> print(my_dict['server'])

    答:键不存在

    KeyError:建议使用dict.get()方法预防

    5. 请问以下代码是否会产生异常,如果会的话,请写出异常的名称:

    def my_fun(x, y):

    print(x, y)

    my_fun(x=1, 2)

    答:SyntaxError: positional argument follows keyword argument.

    要么就都是关键字参数my_fun(x=1, y=2),要么就关键字参数必须跟随在位置参数后面my_fun(1,y=2)。

    6. 请问以下代码是否会产生异常,如果会的话,请写出异常的名称:

    f = open('C:\\test.txt', wb)

    f.write('I love FishC.com!\n')

    f.close()

    答:NameError: name 'wb' is not defined.

    注意 open() 第二个参数是字符串,应该 f = open('C:\test.txt', 'wb') 。wb不加双引号 Python 还以为是变量名呢,往上一找,艾玛没找着……引发 NameError 异常。由于打开文件失败,接着下边一连串与 f 相关的均会报同样异常。

    请问以下代码是否会产生异常,如果会的话,请写出异常的名称:

    def my_fun1():

    x = 5

    def my_fun2():

    x *= x # 错误在这里

    return x

    return my_fun2()

    my_fun1()

    答:UnboundLocalError: local variable 'x' referenced before assignment.

    闭包的知识大家还记得不? Python 认为在内部函数的 x 是局部变量的时候,外部函数的 x 就被屏蔽了起来,所以执行 x *= x 的时候,在右边根本就找不到局部变量 x 的值,因此报错。

    在 Python3 之前没有直接的解决方案,只能间接地通过容器类型来存放,因为容器类型不是放在栈里,所以不会

    被“屏蔽”掉。容器类型这个词儿大家是不是似曾相识?我们之前介绍的字符串、列表、元祖,这些啥都可以往里的扔的就是容器类型啦。

    于是乎我们可以把代码改造为:

    def my_fun1():

    x = [5]

    def my_fun2():

    x[0] *= x[0]

    return x[0]

    return my_fun2()

    my_fun1()

    但是到了 Python3 的世界里,又有了不少的改进,如果我们希望在内部函数里可以修改外部函数里的局部变量的值,那么也有一个关键字可以使用,就是 nonlocal:

    def my_fun1():

    x = 5

    def my_fun2():

    nonlocal x

    x *= x

    return x

    return my_fun2()

    my_fun1()

    8.请写下这一节课你学习到的内容:格式不限,回忆并复述是加强记忆的好方式!

    Python标准异常总结

    异常名称

    解释

    AssertionError

    断言语句(assert)失败:当assert关键字后边的条件为假时,程序将抛出该异常,一般用于在代码中置入检查点

    AttributeError

    尝试访问未知的对象属性:当试图访问的对象属性不存在时抛出的异常

    EOFError

    这个错误基本上是意味着它发现了一个不期望的文件尾。(UNIX上为Ctrl+d,Windows上为Ctrl+Z+Enter)

    FloatingPointError

    浮点计算错误

    GeneratorExit

    Gengeator.close()方法被调用的时候

    ImportError

    导入模块失败的时候

    IndexError

    索引超出序列的范围

    KeyError

    字典中查找一个不存在的关键字

    KeyboardError

    用户输入中断键(Ctrl+c)

    MemoryError

    内存溢出(可通过删除对象释放内存)

    NameError

    尝试访问一个不存在的变量

    NotImplementedError

    尚未实现的方法

    OSError

    操作系统产生的异常(例如打开一个不存在的文件)

    OverflowError

    数值运算超出最大限制

    ReferenceError

    弱引用(weak reference)试图访问一个已经被垃圾回收几只回收了的对象

    RuntimeError

    一般的运行时错误

    StopIteration

    迭代器没有更多的值

    SyntaxError

    Python的语法错误

    IndentationError

    缩进错误

    TabError

    Tab和空格混合使用

    SystemError

    Python编译器系统错误

    SystenExit

    Python编译器进程被关闭

    TypeError

    不同类型间的无效操作

    UnboundLocalError

    访问一个未初始化的本地变量(NameError的子类)

    UnicodeError

    Unicode相关的错误(ValueError的子类)

    UnicodeEncodeError

    Unicode编码时的错误(UnicodeError的子类)

    UnicodeDecodeError

    Unicode解码时的错误(UnicodeError的子类)

    UnicodeTranslateError

    Unicode转换时的错误(UnicodeError的子类)

    ValueError

    传入无效的参数

    ZeroDivisionError

    除数为零

    Python内置异常类的层次结构

    BaseException

    +-- SystemExit

    +-- KeyboardInterrupt

    +-- GeneratorExit

    +-- Exception

    +-- StopIteration

    +-- StopAsyncIteration

    +-- ArithmeticError

    | +-- FloatingPointError

    | +-- OverflowError

    | +-- ZeroDivisionError

    +-- AssertionError

    +-- AttributeError

    +-- BufferError

    +-- EOFError

    +-- ImportError

    | +-- ModuleNotFoundError

    +-- LookupError

    | +-- IndexError

    | +-- KeyError

    +-- MemoryError

    +-- NameError

    | +-- UnboundLocalError

    +-- OSError

    | +-- BlockingIOError

    | +-- ChildProcessError

    | +-- ConnectionError

    | | +-- BrokenPipeError

    | | +-- ConnectionAbortedError

    | | +-- ConnectionRefusedError

    | | +-- ConnectionResetError

    | +-- FileExistsError

    | +-- FileNotFoundError

    | +-- InterruptedError

    | +-- IsADirectoryError

    | +-- NotADirectoryError

    | +-- PermissionError

    | +-- ProcessLookupError

    | +-- TimeoutError

    +-- ReferenceError

    +-- RuntimeError

    | +-- NotImplementedError

    | +-- RecursionError

    +-- SyntaxError

    | +-- IndentationError

    | +-- TabError

    +-- SystemError

    +-- TypeError

    +-- ValueError

    | +-- UnicodeError

    | +-- UnicodeDecodeError

    | +-- UnicodeEncodeError

    | +-- UnicodeTranslateError

    +-- Warning

    +-- DeprecationWarning

    +-- PendingDeprecationWarning

    +-- RuntimeWarning

    +-- SyntaxWarning

    +-- UserWarning

    +-- FutureWarning

    +-- ImportWarning

    +-- UnicodeWarning

    +-- BytesWarning

    +-- ResourceWarning

    展开全文
  • 答:由于环境的不确定性和用户操作的不可以预知性都可能导致程序出现各种问题,因此异常机制最重要的无非就是:增强程序的健壮性和用户体验,尽可能的捕获所有预知的异常并写好处理的代码,当异常出现的时候,程序...

    测试题:版权属于:bbs.fishc.com
    ~8i<apfRzuB5)=XVcT6eW9j^b
    0. 结合你自身的编程经验,总结下异常处理机制的重要性?

    答:由于环境的不确定性和用户操作的不可以预知性都可能导致程序出现各种问题,因此异常机制最重要的无非就是:增强程序的健壮性和用户体验,尽可能的捕获所有预知的异常并写好处理的代码,当异常出现的时候,程序自动消化并恢复正常(不至于崩溃)。>[ s&a)
    c%:IhXH5`xFQ|R"O]WqvL39,@umnJ
    以下题目可以参考(http://bbs.fishc.com/thread-45814-1-1.html),但要求不使用IDLE直接获得答案。sF.fx
    P%i1'UQvM =j5r786F{SC-4RY,kt
    1. 请问以下代码是否会产生异常,如果会的话,请写出异常的名称: .oC2

    >>> my_list = [1, 2, 3, 4,,]

    答:SyntaxError: invalid syntax


    q.;_$9r2JcUV[yFlRahLg%1TP~jEe
    2. 请问以下代码是否会产生异常,如果会的话,请写出异常的名称:y<L#Qt4vI

    >>> my_list = [1, 2, 3, 4, 5]
    
    >>> print(my_list[len(my_list)])

    答:IndexError: list index out of range 


    l>8^Idt|Mz,E:ZuDr;ch[
    3. 请问以下代码是否会产生异常,如果会的话,请写出异常的名称:.q8'k

    >>> my_list = [3, 5, 1, 4, 2]
    
    >>> my_list.sorted()

    答:AttributeError: 'list' object has no attribute 'sorted' 


    WimYJVe(Za30SN)$:k|GML6,tc{
    4. 请问以下代码是否会产生异常,如果会的话,请写出异常的名称:y8(cC~

    >>> my_dict = {'host': 'http://bbs.fishc.com', 'port': '80'}
    
    >>> print(my_dict['server'])

    答:KeyError: 'server' 


    xQta]oDgl*?qh)S#.=^OrBWN}V_Ic
    5. 请问以下代码是否会产生异常,如果会的话,请写出异常的名称:,=R)k!C

    def my_fun(x, y):
    
            print(x, y)
    
    
    
    my_fun(x=1, 2)

    答:如果第一个参数使用关键字参数的话,需要两个参数均使用关键字参数 my_fun(x=1, y=2)

            SyntaxError: positional argument follows keyword argument


    AzuY0DW%~^M'L5r3IvRbZtg{QpH6
    6. 请问以下代码是否会产生异常,如果会的话,请写出异常的名称:j}x{~NGv

    f = open('C:\\test.txt', wb)
    
    f.write('I love FishC.com!\n')
    
    f.close()

    答:注意 open() 第二个参数是字符串,应该 f = open('C:\\test.txt', 'wb') 。wb不加双引号 Python 还以为是变量名呢,往上一找,艾玛没找着……引发 NameError 异常。由于打开文件失败,接着下边一连串与 f 相关的均会报同样异常。


    S=ZVBdCzLIo)x`g.!XP3ab
    7. 请问以下代码是否会产生异常,如果会的话,请写出异常的名称:o!iQd.`-

    def my_fun1():
            x = 5
            def my_fun2():
                    x *= x
                    return x
            return my_fun2()
    
    my_fun1()

    答:NameError

    展开全文
  • 在Java SE 1.5之前,没有泛型情况下,通过类型Object引用来实现参数“任意化”,“任意化”带来缺点是要做显式强制类型转换,而这种转换是要求开发者实际参数类型可以预知的情况下进行。...

    什么是泛型?

    JDK 1.5 的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。Java泛型被引入的好处是安全简单。

    在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

    泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。


    泛型在使用时的一些规则和限制:

    • 泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
    • 同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
    • 泛型的类型参数可以有多个。
    • 泛型的参数类型可以使用extends语句,表示改类型得是继承类或其的子类
    • 泛型的参数类型还可以是通配符类型。

    泛型的作用

    • 限定类型就已经有很大作用了,特别是写基础架构的时候,不需要以前那样的检查,我们的代码量和开发速度都可以提升一大截;
    • Think IN JAVA : 能够进行编译期间类型检查;(官方)
    • 限定类型啊 通俗点比喻 (箱子贴标签)这个箱子是放苹果的 那个箱子是放橘子的
    • 封装一些共性问题,可以简化很多代码,使代码更加有层次,简单
    • 比object类范围明显缩小了,提高了程序运行的效率

    java泛型实现原理:类型擦出

    • Java的泛型是伪泛型。在编译期间,所有的泛型信息都会被擦除掉。正确理解泛型概念的首要前提是理解类型擦出(type erasure)

    • Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。

    举个例子吧

    如在代码中定义的List和List等类型,在编译后都会编程List。**JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。**Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。类型擦除也是Java的泛型实现方法与C++模版机制实现方式之间的重要区别。

        //我们可以从下面例子看出,java定义的泛型并不是真正的泛型,它的实现是在编译时对泛型信息进行擦除而已
    	public static void main(String[] args) throws Exception {
            ArrayList<Integer> arrayList3 = new ArrayList<Integer>();
            arrayList3.add(1);//这样调用add方法只能存储整形,因为泛型类型的实例为Integer
            arrayList3.getClass().getMethod("add", Object.class).invoke(arrayList3, "asd");
            for (int i = 0; i < arrayList3.size(); i++) {
                System.out.println(arrayList3.get(i));
            }
        }
        /**输出:
        		1
        		asd
        */
    

    类型擦除后保留的原始类型,原始类型(raw type)就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型。无论何时定义一个泛型类型,相应的原始类型都会被自动地提供。类型变量被擦除(crased),并使用其限定类型替换。它默认会使用所有添加的对象的最小共同父类( 在不指定泛型的情况下,泛型变量的类型为 该方法中的几种类型的同一个父类的最小级,直到Object)。

    public class Test2{  
        public static void main(String[] args) {  
            /**不指定泛型的时候*/  
            int i=Test2.add(1, 2); //这两个参数都是Integer,所以T为Integer类型  
            Number f=Test2.add(1, 1.2);//这两个参数一个是Integer,以风格是Float,所以取同一父类的最小级,为Number  
            Object o=Test2.add(1, "asd");//这两个参数一个是Integer,以风格是Float,所以取同一父类的最小级,为Object  
      
                    /**指定泛型的时候*/  
            int a=Test2.<Integer>add(1, 2);//指定了Integer,所以只能为Integer类型或者其子类  
            int b=Test2.<Integer>add(1, 2.2);//编译错误,指定了Integer,不能为Float  
            Number c=Test2.<Number>add(1, 2.2); //指定为Number,所以可以为Integer和Float  
        }  
          
        //这是一个简单的泛型方法  
        public static <T> T add(T x,T y){  
            return y;  
        }  
    }  
    
    • list类型对象是否可以赋给一个List类型引用?
    public class Test {
        //编译不通过
        public static void main(String[] args) {
            List<String> a = null;
            List<Object> bNew = a;
        }
    }
    

    这段代码会报编译错误,原因如下:

    这里的Object 和 String 仅仅是给编译器做编译的时候检查用的。这里的List 和List 并没有什么父子类的关系,仅仅是表示一个用来装Obejct型对像,一个用来装String型对像。

    这种转换只能在子类与父类之间转换,虽然Object是String的父类,但是List和List在编译器看来,是两种完全不同的东西,不允许你这样转换。

    如果要转换,可以用下面方法:跳过编译器的检查

        public static void main(String[] args) {
            List<String> a = new ArrayList<>();
            List<Object> b = convert(a);
        }
    
        @SuppressWarnings("unchecked")
        public static <T> List<T> convert(List<?> list) {
            return (List<T>)list;
        }
    

    类型擦除引起的问题及解决方法

    java不能实现真正的泛型,只能使用类型擦除来实现伪泛型,这样虽然不会有类型膨胀的问题,但是也引起了许多新的问题。所以,Sun对这些问题作出了许多限制,避免我们犯各种错误。

    1、先检查,在编译,以及检查编译的对象和引用传递的问题
    public static  void main(String[] args) {  
            ArrayList<String> arrayList=new ArrayList<String>();  
            arrayList.add("111");  
            arrayList.add(111);//编译错误  
    }  
    

    在 String 中添加 integer 时,编译器会检查,如果是在编译后检查,在类型擦除后会使object类型,反而是可以添加的,这说明泛型检查是在编译前检查的。

    我们再看一下下面这个例子,在继续讨论编译时检查是检查哪个地方

        public static void main(String[] args) {  
            ArrayList<String> arrayList1=new ArrayList();  
            arrayList1.add("1");//编译通过  
            arrayList1.add(1);//编译错误  
            String str1=arrayList1.get(0);//返回类型就是String  
              
            ArrayList arrayList2=new ArrayList<String>();  
            arrayList2.add("1");//编译通过  
            arrayList2.add(1);//编译通过  
            Object object=arrayList2.get(0);//返回类型就是Object  
              
            new ArrayList<String>().add("11");//编译通过  
            new ArrayList<String>().add(22);//编译错误  
            String string=new ArrayList<String>().get(0);//返回类型就是String  
        } 
    

    本来类型检查就是编译时完成的。new ArrayList()只是在内存中开辟一个存储空间,可以存储任何的类型对象。而真正涉及类型检查的是它的引用,因为我们是使用它引用arrayList1 来调用它的方法,比如说调用add()方法。所以arrayList1引用能完成泛型类型的检查。而引用arrayList2没有使用泛型,所以不行。

    那么它为什么要检查对象的引用呢?我们看一下下面的例子

    ArrayList<Object> arrayList = new ArrayList<Object>();  
              arrayList1.add(new Object());  
              arrayList1.add(new Object());  
              ArrayList<String> arrayList2=arrayList1;//编译错误
    

    如果编译通过,那我们调用 get 方法时,获得到的对象是 String 类型的,可是实际上它存放的是 Object 类型的,那么我们会遇到 ClassCastException 异常,所以为了避免这样的错误,java不允许这样的引用传递

    那我们能不能反过来呢?我们看一下下面这个例子

    ArrayList<String> arrayList1=new ArrayList<String>();  
              arrayList1.add(new String());  
              arrayList1.add(new String());  
              ArrayList<Object> arrayList2=arrayList1;//编译错误  
    

    也是报错,那为什么还会报错呢?这样不是不会又 ClassCastException 错误吗?我们将 String 转为 Object 有什么意义呢?泛型出现的原因,就是为了解决类型转换的问题。我们使用了泛型,到头来,还是要自己强转,违背了泛型设计的初衷。所以 java 也不允许这样的情况出现。

    2、自动类型转换

    因为类型擦除的问题,所以所有的泛型类型变量最后都会被替换为原始类型。这样就引起了一个问题,既然都被替换为原始类型,那么我们在获取的时候,为什么不需要进行强制类型转换呢?我们看一下 ArrayList 的 get 方法的实现。

        @SuppressWarnings("unchecked")
        E elementData(int index) {
            return (E) elementData[index];//类型强制转换
        }
    
        public E get(int index) {
            rangeCheck(index);//范围判断
            return elementData(index);//返回元素
        }
    

    看以看到,在return之前,会调用另一个方法进行强转。假设泛型类型变量为 String ,虽然泛型信息会被擦除掉,但是会将(E) elementData[index],编译为(String )elementData[index]。所以我们不用自己进行强转。

    3、类型擦除与多态的冲突和解决方法

    我们先来看一个问题来理解这个问题,假设我们有一个泛型类

    class Pair<T> {
    	private T value;
    	public T getValue() {
    		return value;
    	}
    	public void setValue(T value) {
    		this.value = value;
    	}
    }
    

    然后我们再定义一个子类继承它

    class DateInter extends Pair<Date> {
    	@Override
    	public void setValue(Date value) {
    		super.setValue(value);
    	}
    	@Override
    	public Date getValue() {
    		return super.getValue();
    	}
    }
    

    在这个子类中,我们设定父类的泛型类型为Pair,在子类中,我们覆盖了父类的两个方法,我们的原意是这样的:将父类的泛型类型限定为Date,那么父类里面的两个方法的参数都为Date类型,我们再子类中也看到了@Override标签,看起来一点问题都没有,那实际上是这样子的吗?

    实际上,在类型擦除后,父类泛型变成了原始类型 Object ,所以父类编译之后会变成下面的样子:

    class Pair {
    	private Object value;
    	public Object getValue() {
    		return value;
    	}
    	public void setValue(Object  value) {
    		this.value = value;
    	}
    }
    

    而子类重写的两个方法:

        @Override
    	public void setValue(Date value) {
    		super.setValue(value);
    	}
    	@Override
    	public Date getValue() {
    		return super.getValue();
    	}
    

    先来分析setValue方法,父类的类型是Object,而子类的类型是Date,参数类型不一样,这如果实在普通的继承关系中,根本就不会是重写,更像是重载,我们测试一下

    public static void main(String[] args) throws ClassNotFoundException {
    		DateInter dateInter=new DateInter();
    		dateInter.setValue(new Date());                
            dateInter.setValue(new Object());//编译错误,不允许存放 Object 类型
     }
    

    如果是重载,那么子类中会有两个setValue方法,一个是参数Object类型,一个是Date类型,可是我们发现,根本就没有这样的一个子类继承自父类的 Object 类型参数的方法。所以说,确实是重写,而不是重载。那为什么是这种情况呢?底层 JVM 是怎么实现的呢?我们想的是传入父类 Date ,然后将父类变成如下

    class Pair {
    	private Date value;
    	public Date getValue() {
    		return value;
    	}
    	public void setValue(Date value) {
    		this.value = value;
    	}
    }
    

    然后子类再去继承父类重写get和set方法,实现继承中的多态,可是由于种种原因,虚拟机并不能将泛型类型变为Date,只能将类型擦除掉,变为原始类型Object。这样,我们的本意是进行重写,实现多态。可是类型擦除后,只能变为了重载。这样,类型擦除就和多态有了冲突。JVM知道我们的本意吗?知道!!可是它能直接实现吗?不能!!如果真的不能的话,那我们怎么去重写我们想要的Date类型参数的方法呢?

    其实 JVM 采用了一个特殊的方法,来完成这项功能,那就是桥方法。

    首先,我们用javap -c className的方式反编译下DateInter子类的字节码,结果如下:

    class com.tao.test.DateInter extends com.tao.test.Pair<java.util.Date> {
      com.tao.test.DateInter();
        Code:
           0: aload_0
           1: invokespecial #8                  // Method com/tao/test/Pair."<init>"
    :()V
           4: return
     
      public void setValue(java.util.Date);  //我们重写的setValue方法
        Code:
           0: aload_0
           1: aload_1
           2: invokespecial #16                 // Method com/tao/test/Pair.setValue
    :(Ljava/lang/Object;)V
           5: return
     
      public java.util.Date getValue();    //我们重写的getValue方法
        Code:
           0: aload_0
           1: invokespecial #23                 // Method com/tao/test/Pair.getValue
    :()Ljava/lang/Object;
           4: checkcast     #26                 // class java/util/Date
           7: areturn
     
      public java.lang.Object getValue();     //编译时由编译器生成的桥方法
        Code:
           0: aload_0
           1: invokevirtual #28                 // Method getValue:()Ljava/util/Date 去调用我们重写的getValue方法
    ;
           4: areturn
     
      public void setValue(java.lang.Object);   //编译时由编译器生成的桥方法
        Code:
           0: aload_0
           1: aload_1
           2: checkcast     #26                 // class java/util/Date
           5: invokevirtual #30                 // Method setValue:(Ljava/util/Date;   去调用我们重写的setValue方法
    )
           8: return
    }
    

    从编译的结果来看,我们本意重写setValue和getValue方法的子类,竟然有4个方法,其实不用惊奇,最后的两个方法,就是编译器自己生成的桥方法。可以看到桥方法的参数类型都是Object,也就是说,子类中真正覆盖父类两个方法的就是这两个我们看不到的桥方法。而打在我们自己定义的setvalue和getValue方法上面的@Override只不过是假象。而桥方法的内部实现,就只是去调用我们自己重写的那两个方法。

    所以,虚拟机巧妙的使用了桥方法,来解决了类型擦除和多态的冲突。不过,要提到一点,这里面的setValue和getValue这两个桥方法的意义又有不同。setValue方法是为了解决类型擦除与多态之间的冲突。而getValue却有普遍的意义,怎么说呢,如果这是一个普通的继承关系,那么父类的 setValue 方法以及子类 setValue 方法如下:

    //这两个方法同时存在一个类中编译是会报错的,而两个setValue是可以的
    public ObjectgetValue() {
    	return super.getValue();
    }
    //而子类重写的方法是:
    public Date getValue() {
    	return super.getValue();
    }
    

    其实这在普通的类继承中也是普遍存在的重写,这就是协变。并且,还有一点也许会有疑问,子类中的巧方法 Object getValue()和Date getValue()是同时存在的,可是如果是常规的两个方法,他们的方法签名是一样的,也就是说虚拟机根本不能分别这两个方法。如果是我们自己编写Java代码,这样的代码是无法通过编译器的检查的,但是虚拟机却是允许这样做的,因为虚拟机通过参数类型和返回类型来确定一个方法,所以编译器为了实现泛型的多态允许自己做这个看起来“不合法”的事情,然后交给虚拟器去区别。

    4、泛型类型变量不能是基本数据类型

    **不能用类型参数替换基本类型。**就比如,没有ArrayList,只有ArrayList。因为当类型擦除后,ArrayList的原始类型变为Object,但是Object类型不能存储double值,只能引用Double的值。

    5、运行时类型查询

    也就是类型擦除之后,ArrayList只剩下原始类型,泛型信息String不存在了,所以下面第三行会报错,java限定了这种类型查询的方式,所以使用通配符

    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        if( arrayList instanceof ArrayList<String>)//报错
        if( arrayList instanceof ArrayList<?>)//没报错
    }
    
    6、异常中使用泛型的问题
    • 不能抛出也不能捕获泛型类的对象。

    事实上,泛型类扩展Throwable都不合法。例如:下面的定义将不会通过编译:

    public class Problem<T> extends Exception{......}
    

    为什么不能扩展Throwable,因为异常都是在运行时捕获和抛出的,而在编译的时候,泛型信息全都会被擦除掉,那么,假设上面的编译可行,那么,在看下面的定义:

    try{
    }catch(Problem<Integer> e1){
    ...
    }catch(Problem<Number> e2){
    ...
    } 
    

    类型信息被擦除后,那么两个地方的catch都变为原始类型Object,那么也就是说,这两个地方的catch变的一模一样,就相当于下面的这样

    try{
    }catch(Problem<Object> e1){
    。。
    }catch(Problem<Object> e2){
    ...
    

    这个当然就是不行的。就好比,catch两个一模一样的普通异常,不能通过编译一样。

    • 不能在 catch 子句中使用泛型变量
    public static <T extends Throwable> void doWork(Class<T> t){
            try{
                ...
            }catch(T e){ //编译错误
                ...
            }
       }
    

    因为泛型信息在编译的时候已经变成原始类型,也就是说上面的T会变为原始类型Throwable,那么如果可以再catch子句中使用泛型变量,那么,下面的定义呢:

    public static <T extends Throwable> void doWork(Class<T> t){
            try{
                ...
            }catch(T e){ //编译错误
                ...
            }catch(IndexOutOfBounds e){
            }                         
     }
    

    根据异常捕获的原则,一定是子类在前面,父类在后面,那么上面就违背了这个原则。即使你在使用该静态方法的使用T是ArrayIndexOutofBounds,在编译之后还是会变成Throwable,ArrayIndexOutofBounds是IndexOutofBounds的子类,违背了异常捕获的原则。所以java为了避免这样的情况,禁止在catch子句中使用泛型变量。但是在异常声明中可以使用类型变量。下面方法是合法的:

       public static<T extends Throwable> void doWork(T t) throws T{
           try{
               ...
           }catch(Throwable realCause){
               t.initCause(realCause);
               throw t; 
           }
      }
    
    7、数组(这个不属于类型擦除引起的问题)

    不能声明参数化类型的数组。如:

      Pair<String>[] table = new Pair<String>(10); //报错
    

    这是因为擦除后,table的类型变为Pair[],可以转化成一个Object[]。数组可以记住自己的元素类型,下面的赋值会抛出一个ArrayStoreException异常。

    objarray ="Hello"; //ERROR
    

    对于泛型而言,擦除降低了这个机制的效率。下面的赋值可以通过数组存储的检测,但仍然会导致类型错误。

     objarray =new Pair<Employee>();
    

    提示:如果需要收集参数化类型对象,直接使用ArrayList:ArrayList<Pair>最安全且有效。

    8、泛型类型的实例化

    不能实例化泛型类型或者泛型数组。如:

    first = new T(); //ERROR
    public<T> T[] minMax(T[] a){
        T[] mm = new T[2]; //ERROR
        ...
    }
    

    但是,可以用反射构造泛型对象和数组。利用反射,调用Array.newInstance:

     publicstatic <T extends Comparable> T[]minmax(T[] a){
           T[] mm == (T[])Array.newInstance(a.getClass().getComponentType(),2);
            ...
           // 以替换掉以下代码
           // Obeject[] mm = new Object[2];
           // return (T[]) mm;
     
        }
    
    9、类型擦除后的冲突
    • 当泛型类型被擦除后,创建条件不能产生冲突。如果在Pair类中添加下面的equals方法:
    class Pair<T>   {
    //会报错,编译器会让你将public boolean equals(T value)变成继承的方法public boolean equals(Object value)
    	public boolean equals(T value) {
    		return null;
    	}
    }
    

    考虑一个Pair。从概念上,它有两个equals方法:

    boolean equals(String); //在Pair中定义

    boolean equals(Object); //从object中继承

    但是,这只是一种错觉。实际上,擦除后方法 boolean equals(T) 变成了方法 boolean equals(Object)

    这与Object.equals方法是冲突的!当然,补救的办法是重新命名该方法名引发错误的方法。

    • 泛型规范说明提及另一个原则“要支持擦除的转换,需要强行制一个类或者类型变量不能同时成为两个接口的子类,而这两个子类是同一接品的不同参数化。”

    下面的代码是非法的:

    class Calendar implements Comparable<Calendar>{ ... }//ERROR
    class GregorianCalendar extends Calendar implements Comparable<GregorianCalendar>{...} //ERROR
    

    而下面代码则是合法的:

    class Calendar implements Comparable{ ... }
    class GregorianCalendar extends Calendar implements Comparable{...}
    
    10、泛型在静态方法和静态类中的问题

    泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数

    public class Test2<T> {  
        public static T one;   //编译错误  
        public static  T show(T one){ //编译错误  
            return null;  
        }  
    }  
    

    因为泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。对象都没有创建,如何确定这个泛型参数是何种类型,所以当然是错误的。但是要注意区分下面的一种情况:

    public class Test2<T> {  
        public static <T>T show(T one){//这是正确的  
            return null;  
        }  
    }  
    

    因为这是一个泛型方法,在泛型方法中使用的T是自己在方法中定义的T,而不是泛型类中的T。


    总结

    • 泛型的原理
    • 泛型的使用规则
    • 泛型的优点
    • 泛型引起的问题
    展开全文
  • 众所周知,C++中,两个长整形进行乘法并取模,在乘法时得到的中间值有可能会超过long long的范围,从而产生数据溢出,得到不可预知的结果。而在程序设计竞赛中,这种情况是经常会出现的,需要我们掌握处理的办法。...
  • 随机变量理解

    千次阅读 2014-01-03 20:18:55
    随机变量是其值随机会而定变量,正如随机事件其发生与否随机会而定一样,一个随机试验有多种可能的结果,每一个结果出现都有一定概率。随机变量是试验结果函数,和普通函数不同是,试验前,我们无法预知它将...
  • 线程安全问题

    2019-04-09 18:17:38
    首先要明白线程工作原理,jvm有一个main memory,而每个线程有自己working memory,一个...多个线程同时操作同一个variable,就可能出现不可预知的结果。根据上面解释,很容易想出相应scenario。 而用syn...
  • 针对这种大量参数是不可能出现引起Xss和SQL注入漏洞业务场景下,因此可以使用一个适用大多数业务场景通用处理方法,牺牲少量用户体验,来避免Xss漏洞和SQL注入。 那就是利用Servlet过滤器机制,编写定制XssF
  • java各种集合线程安全 1. 什么是线程安全?  想要搞清楚这个问题,首先要明白线程工作原理:jvm有一个main memory,而每个线程有自己...多个线程同时操作同一个variable,就可能出现不可预知的结果...
  • 当多个线程访问同一个资源时,很有可能出现线程安全的问题。比如,多个线程一个数据进行修改时,会导致某着线程数据的修改丢失。而同步机制就是为了解决这种问题。  JAVA中,有三种实现同步机制的方法:  1...
  • 本文以PC200-5型挖掘机经常出现的故障为例,介绍液压传动的工作原理,分析其常见故障现象的诊断和排除方法。 ??????????????? 1; PC200-5型挖机液压系统的工作原理  PC200-5型掘机液压系统是由一些基本回路和辅助...
  •  异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误。java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获...
  • </li><li>造成这个原因可能是自定义CSS的问题,也可能是字体问题,可能并不属于开发者的责任。</li><li>但是POI是基于全平台特色的,希望能考虑到多平台和个性化兼容性。</li><li>该问题主要体现...
  • 最近总结

    2010-06-25 11:35:00
    针对目前版本延迟的问题提出的意见 1,策划要出的内容和模块希望都体现在文档里,这样大家都知道要出的是什么,以便可以提早预知可能出现的问题。 2, 策划要了解自己要做的模块的内容和含义,不要...
  • 针对这种大量参数是不可能出现引起Xss和SQL注入漏洞业务场景下,因此可以使用一个适用大多数业务场景通用处理方法,牺牲少量用户体验,来避免Xss漏洞和SQL注入。 那就是利用Servlet过滤器机制,编写定制Xss
  • 并发(1) 概述

    2018-03-15 12:15:00
    并发问题根源  多线程编程在提升程序性能同时也会带来一些安全性问题,产生一下不可预知的结果。...这里就会出现安全性问题,原因在于数据进行累加过程并非是一个原子操作,他需要先读取现有值,然后进行...
  • JSP过滤器防止Xss漏洞

    2017-04-24 15:49:08
    在用java进行web业务开发时候,对于页面上接收到参数,除了极少数是步可预知的内容外,大量参数名和参数值都是不会出现触发Xss漏洞字符。而通常为了避免Xss漏洞,都是开发人员各自在页面输出和数据入库等...
  • malloc系列是函数,而new是操作符 用new更高效 第二个 对于对象内存分配 使用new可以调用构造函数 而用malloc则不会, 所以有可能出现不可预知问题, 尤其是派生类对象,如果有虚函数表,那么用malloc出来....
  • 在用java进行web业务开发时候,对于页面上接收到参数,除了极少数是步可预知的内容外,大量参数名和参数值都是不会出现触发Xss漏洞字符。而通常为了避免Xss漏洞,都是开发人员各自在页面输出和数据入库等...
  • 在计算机系统中有许多互斥的资源,若两个进程同时使用则会出现不可预知的问题。所谓 **死锁(Dead lock) **即两个以上的进程都要使用对方已经占有的资源导致无法继续运行的现象,造成死锁的可能有很多种。 在了解死锁...
  • Xss漏洞预防

    2016-08-11 17:44:52
     在用java进行web业务开发时候,对于页面上接收到参数,除了极少数是步可预知的内容外,大量参数名和参数值都是不会出现触发Xss漏洞字符。而通常为了避免Xss漏洞,都是开发人员各自在页面输出和数据入库等...
  • 为什么不要用select *

    千次阅读 2019-06-28 10:25:07
    1. 程序变更问题出现不可以预知隐患; 假设某一天修改了表结构,如果用select *,返回数据必然会会变化,客户端是否数据库变化作适配,是否所有地方都做了适配,这都是问题。 2. 性能问题 a. 使用了select ,...
  • 直接在原代码上修改会有风险,可能导致原先功能出现不可预知错误; 如果新需求更改频繁,原先功能就修改越频繁; 随着功能增多,模块大小也越来越臃肿; 实现方式: 当软件需要变化时,尽量通过扩展软件...
  • 一个基本问题就是检测和处理系统中可能出现的故障。在这一章中我们研究了处理分布式系统中的节点与通信故障、拜占庭式故障和软件故障的各种方法。第9章和第10章包括分布式系统中的负载分配问题。负载分配是分布式...
  • 分布式系统设计

    热门讨论 2007-07-12 15:59:47
    一个基本问题就是检测和处理系统中可能出现的故障。在这一章中我们研究了处理分布式系统中的节点与通信故障、拜占庭式故障和软件故障的各种方法。第9章和第10章包括分布式系统中的负载分配问题。负载分配是分布式...

空空如也

空空如也

1 2
收藏数 36
精华内容 14
关键字:

对可能出现的问题的预知