泛型和异常类

本文深入探讨了Java中的泛型,包括泛型的定义、使用、优势、本质以及自定义泛型类。此外,还阐述了原始类型、类型通配符及其上下限的运用。接着,介绍了异常处理的机制,包括try-catch-finally结构、异常的分类以及处理方式。文章最后提到了泛型与异常处理的结合,以及如何自定义和抛出异常。
摘要由CSDN通过智能技术生成

泛型:(generic)
不用泛型两个严重的问题
A、集合不能记住元素的类型,因此集合元素被取出默认被当成Object类型,因此总是需要强制类型转换。
B、集合不能记住元素的类型,因此程序可能不小心将任意类型的元素添加到集合中。
简而言之,其他任意的类可能都存在泛型问题。所以就需要引入泛型。
在集合中使用泛型:
A、声明集合变量时:集合类型需要用尖括号<>来指定元素的类型
B、创建集合对象时:可使用菱形语法。

泛型的本质:
1、java并不是只有集合类才能支持泛型,所有类都有可能支持泛型——包括自定义的类。
泛型的本质:程序在定义类、方法时,用尖括号定义了一个或者N个类型形参。这样,当程序去使用这些类或方法时,就可以为这1个或N个类型形参传入实际的类型。
由此可见,泛型的本质,和前面的方法参数有很大的相似之处。区别只是:普通参数,需要为之传入实际的值,而泛型参数,需要为之传入实际的类型。

泛型的优势:程序在使用类、或者方法时,不仅传入的值可以动态改变,而且连传入的类型都可以动态改变。

自定义泛型类:
方法是:程序为方法定义了几个形参,调用方法时就传入几个实参。
泛型:程序为类或方法定义了几个泛型,使用类或方法时就传入几个实际的实参类型。
自定义泛型类的语法,泛型类:只是用尖括号列出该类需要动态改变的所有类型(泛型)
public class Apple<T,E>
{
private T name;
private E weight;
}
使用泛型类:程序为类定义了几个泛型,使用类时就传入几个实际类型(实类型)
Apple<String,Float> app1 = new Apple<>();
从泛型类派生子类:在继承时同样需要传入实际类型给泛型,或者父类不带泛型如:
public class A extends Apple<String,Double>或 public class A extends Apple
若不传入实际类型形参又带了泛型,会报错,如:public class A extends Apple会报错
若继承带泛型的父类时不带泛型,编译时会发出警告:使用了未经检查或不安全的操作,此时系统会把父类的泛型形参当成Object处理,使用时需要强转为真实的类型。
如果希望子类也带泛型,则子类后要重新定义泛型,如:
public class Macintoch extends Apple<String,Double>
总结:不管是用泛型类定义变量,还是通过泛型派生子类——都应该为泛型传入实际的类型。
注意:创建带泛型的自定义类,为该类定义构造器时,构造器名还是原来的类名,不需要增加泛型声明,但调用构造器创建对象时需要传入泛型,可以使用灵性与法也可以直接带泛型类型参数。
原始类型:
虽然程序定义了泛型类,但我们使用泛型类,根本不传入实际类型(一定不能有尖括号)这种用法就叫”原始类型”。主要目的是为了与泛型出现之前的代码保持兼容。

原始类型的缺点:
A、只要程序使用了泛型的原始类型,Java编译器就会报警告。
B、如果你使用了泛型类的原始类型,Java编译器就没法确定泛型的实际类型,因此Java编译器只能把它们都当成Object处理。
原始类型并不是一种好的写法,不规范,不推荐。

并不存在的泛型类:List、List很像List的子接口。但实际上,List、List并不是真正存在的类,没有对应class文件,在内存中也没有额外多出来的类。
List、List实际用到的类依然是List. 如:
ArrayList as = new ArrayList<>();
ArrayList is = new ArrayList<>();
表面上看,上面的as和is区别很大:as代表的集合只能添加String元素
//is代表的集合只能添加Integer元素。
//但实际上,泛型类并不存在。因此ArrayList和ArrayList其实都是同一个类
System.out.println(as.getClass()); //class java.util.ArrayList
System.out.println(is.getClass()); //class java.util.ArrayList
System.out.println(as.getClass() == is.getClass()); //true
不管为泛型形参传入哪种类型的实参,它们依然被当成同一类处理,在内存中也只占用一块内存空间,因此在静态方法、静态初始化块或静态变量的声明和初始化中不允许使用泛型形参,除此之外,由于系统不会真正生成泛型类,因此instanceof运算符后不能使用泛型类:
If(xxxx instanceof java.util.ArrayList()) 引起编译报错,instanceof后不能使用泛型

A是B的子类,那么A[]相当于B[]的子类!但List并不是List的子类。

在泛型时,Java对此进行了改进:即使A是B的子类,List也不是List的子类。

类型通配符:当使用泛型类,程序应该为泛型传入实际的类型参数,但由于不确定应该传入哪种类型,此时就使用?来作为通配符。

通配符的限制:
使用通配符之后,List<?>类型的变量可以是List,也可以是List等等任意泛型类型。带通配符?的List是各种泛型List的父类,但Java程序永远无法确定List<?>类型中?实际所代表的类型。如果尝试向List<?>类型的集合添加String元素,但List<?>变量可能引用的是List,故程序报错。如果尝试向List<?>类型的集合添加Integer元素,但List<?>变量可能引用的是List,故程序报错.即使添加Object也会报错,
如:
List<?> list = new ArrayList();
list.add(new Object);
以上代码会报错,因为在调用add(E e)方法时,参数必须是泛型E类型参数的对象或者其子对象,因为该类泛型用通配符?,因此并不知道泛型类型的实际引用类型。
List<?>永远不能添加元素,只能取出元素——取出元素时,该元素的类型总是被当成Object处理。?只能确定是引用类型,唯一只能添加null,因为null可以给所有引用类型赋值。
List<?>与List是不同的:List以及List等都可以被当成List<?>使用,但不能被当成List使用。
List<?>与List(原始类型)也是不同的:,List<?>依然是保留泛型信息的。但List是不保留泛型信息的,因此:List、List集合被赋值给List变量之后,他们的泛型信息就丢失了(擦除),所以跟不使用泛型是一样的。

类型通配符的上限作用:
1、依然可以利用类型通配符的优势
2、可以保证从集合中取出的元素是某个类型或及其任意的子类。
类型通配符上限
List<? extends 上限>
如果类B是类A的子类,那么List就相当于List<? extends A>(只要求集合元素是A的子类,具体是哪个子类不确定)的子类—这种类型通配符的变化是一致的,称为“协变”,协变只能出不能进。

注意:
A、带上限的类型通配符的集合,同样只能取出元素,而且此时取出的元素总是被当成”上限”类型来处理。
B、List<类型>(只要尖括号中的类型是上限或其子类),List<类型>的集合就可以被当成List<? extends 上限>使用,用于被赋值。

对于带上限的通配符而言,是不能添加元素的。除了null
程序只能知道它的集合元素是List<? extends Number>或其子类,但具体是哪个子类,永远不知道。当你试图向List<? extends Number>集合添加Integer时,它的集合元素可能只能是Double。当你试图向List<? extends Number>集合添加Double时,它的集合元素可能只能是Integer。因此添加具体类型的元素都会报错。
不难发现,其实List<?>只是<? extends Object>的特例,即不带上限的通配符,其实只是一种特例,相当于它的上限为Object.
总结:带上限的通配符。
A、只能取出元素,不能添加元素。
B、取出的元素总被当成上限类型处理。

最终总结:带上限的通配符的泛型类实例
A、只能调用返回值为泛型(相当于集合的取出元素)的方法,用上限类型去接收。不能调用参数为泛型(相当于为集合添加元素)的方法。
B、调用返回值为泛型的方法,该方法的返回值(取出的元素)总被当成上限类型处理。

类型通配符的下限:
List<? super 下限>
如果类A是类B的父类,那么List就相当于List<? super B>(只要求集合元素是B的父类,具体是哪个父类不确定)的子类—这种类型通配符的变化是逆向的,称为“逆变” 只能进不能出。
List<类型>(只要尖括号中的类型是下限类型或其父类),List<类型>的集合就可以被当成List<? super 下限>使用,用于被赋值。
总结:带下限的通配符。
A、只能添加元素,不能取出元素(除非取出的元素只被当成Object处理)。
B、添加的元素必须是下限或下限的父类

最终总结:带下限的通配符的泛型类实例
A、只能调用参数为泛型(相当于为集合添加元素)的方法。不能调用返回值为泛型(相当于为集合取出元素)的方法——除非只把返回值当成Object处理
B、调用参数为泛型的方法,传入的参数只能是下限或下限的父类。

泛型形参的上限
比如 class A,这意味着该T只需要是Object的子类,T可以是任意的类型
在某些时候,程序对泛型T的要求只是某个类的子类,此时 可通过泛型上限来实现。
语法:
Class A<T extends 上限>——这种语法表明该T必须是上限或其子类。
泛型的上限可以是多个类型,但多个泛型上限时,只能有一个类,可以有多个接口——原因是因为Java对类是单继承的,对接口才是多继承的。

对泛型的总结:
Java的泛型分两个阶段:
定义泛型:在定义类、方法时,额外为类、方法添加一个类型形参(泛型)。
1、为类定义泛型(额外的类型形参)
2、为方法定义泛型(额外的类型形参)
3、定义泛型时,可以为泛型指定上限。如:T extends Number
使用泛型:当使用泛型类、调用泛型方法的时候,此时需要为泛型传入实际的类型。
1、直接传入一个具体的类型。如:List,它不支持型变,List就不是List的子类。
2、使用通配符。比如:List<?>,支持型变。List和List等都是List<?>的子类。
3、通配符上限:比如:List<? extends 上限>,支持协变,只能取出元素,不能添加元素,List和List都是List<? extends Number>的子类,
4、通配符下限:比如:List<? super 下限>,支持逆变,只能添加元素,不能取出元素。
List、List反而是List<? suepr Integer>的子类。

泛型方法:
定义泛型类的时候,直接在类名后用尖括号定义额外的类型形参(泛型)。
定义方法时,可以在修饰符与返回值类型之间用尖括号定义额外的类型形参(泛型)。
如果在定义方法时定义了泛型,那么这个方法就是泛型方法。
泛型类与泛型方法的区别:
如果在类声明上定义了泛型,那么该类中所有的方法都可使用该泛型。
如果只是在方法声明上定义了泛型,那么只能在该方法中使用该泛型。
可见,本质是相同的,只是作用域不用,泛型类的泛型可以在整个类中可用,但泛型方法的泛型只在该方法中可用。

方法中的泛型,同样可指定上限——与类中的泛型是一样的。
定义泛型方法时,同样可使用通配符——与前面介绍使用通配符的方式基本是相同的
泛型方法中使用通配符,即可使用协变、也可使用逆变。
如泛型方法:
Public static void test(T[]a ,Collection c)
{
for(T o: a){
C.add(o);
}
}
Object o = new Object[10];
Collection co = new ArrayList<>();
Test(o,co);//这行代码中T代表Object类型
String sa = new String[10];
Test(sa,co); //这行代码中T代表Object类型
Integer ia = new Integer[100];
Collection cn = new ArrayList<>()
Test(ia,cn); //这行代码中T代表Number类型

泛型构造器:
构造器也是一个特殊的方法,既然方法中可以定义泛型,那么构造器中也可以定义泛型。——在构造器中定义的泛型,将只能在该构造器中使用。
对于泛型构造器,程序调用构造器时,同样无需显式指定泛型的类型,编译器可以推断出泛型的类型。

构造器中定义的泛型,同样可以指定上限。与在类声明、方法声明中定义泛型时指定上限的语法时完全一样。

注意:如果显式指定了构造器中泛型的类型,就不能同时使用菱形语法。
总结来说,要么都显式指定,要么都不显式指定,让系统进行推断。

泛型方法与方法重载:
由于泛型方法中的形参类型是动态改变的——而Java的方法重载完全是根据形参类型进行区分的,因此可能出现一种情况,Java程序中两个方法的参数列表看上去不同,但实际上是完全一样的,在这种情况,Java编译器无法准确区分两个方法,编译时会报错。如:
Public static void copy(Collection dest,Collection<? extends T> src){…}
Public static T copy(Collection<? super T> dest,Collection src){…}
以上两个方法都是在调用时会引起报错。

注意:当在方法中引入泛型之后,方法签名变得更加灵活,因此要小心去区分何时是方法重载。

泛型的擦除:
泛型是Java 5才引入,早期的程序都没有为泛型指定实际的类型,为了和早期的程序保持兼容,Java依然允许使用泛型类不传入具体的类型——连尖括号及里面的内容都不指定。
当程序将一个带泛型信息的变量赋值给不带泛型信息的变量时,所有的泛型信息都会丢失(擦除)——这就是泛型擦除。
Java的泛型检查只在编译阶段完成,运行阶段是没有泛型信息的,运行时并没有真正的泛型类List,依然只是List,运行时,List和List其实是完全一样的。
其实Java程序只是在编译的时候对泛型做检查,运行的时候其实是没有泛型的。

可能出现的问题:当泛型被擦除时,泛型的类型约束就会丢失。当程序再次希望使用泛型信息时,就可能引发ClassCastException

泛型与数组:
Java的数组:默认就会支持型变,因此可能出现程序编译时没有报错警告,运行时出现ClassCastException。
为了避免数组中的型变问题继续传播泛型,引入泛型,Java泛型制定了两个规定
1、程序不允许创建带泛型的数组,可以定义数组泛型变量,但不能new创建。
结论:Java不支持创建泛型数组对象。
3、Java允许创建带通配符的泛型数组,如果创建带通配符的泛型数组,那么声明变量时也应该声明带通配符的泛型数组变量。

异常分为:Checked异常和Runtime异常,Checked异常都是在编译阶段被处理的异常,所以它强制程序处理所有的Checked异常,而Runtime异常则无需处理。Checked异常可以提醒程序员需要处理所有可能发生的异常。

处理异常:有了异常处理之后,程序会更加健壮(robust),有很好的容错性。
程序可能面临:1、物理设备环境的改变。2、用户错误操作。3、恶意用户的恶意攻击

异常处理的流程:
try
{
//正常的处理逻辑(可能引起错误的代码块)
}
catch(异常)
{
//处理错误(try块代码出现错误时执行的代码)
}
将正常的业务处理代码放在try块里面,将对异常的处理逻辑放在catch块里面。
流程:
一、执行try时如果遇到了异常,Java会自动生成异常对象,该对象提交给了Java运行时环境——这个过程叫“抛出(throw)”异常
二、Java运行时环境收到该异常对象后,会一次判断该异常对象是否是catch块后异常类或其子类的实例,如果是,就将该异常传给catch块——这个过程叫“捕获(catch)”异常,并调用该catch块来处理异常,不再去匹配下面的catch块,如果不是,再次拿该异常对象和下一个catch块的异常类进行比较,如果Java运行时环境找不到匹配的catch块,则运行时环境终止,Java程序退出。
即:try块后最多有一个catch块被执行,除非在循环中使用continue执行下一次try块。
Try块声明的变量只能在try块使用。
规则:子类异常捕获的catch应该放在父类异常捕获的catch块前面,即捕获异常时一定要先捕获小异常,再捕获大异常.
异常的体系:

所有的非正常情况分为两种:异常(Exception)和错误(Error)
所有的异常都继承Throwable父类,Error一般与虚拟机相关,如:系统崩溃、虚拟机错误等。
Error:代表非常严重的、不可恢复的错误,因此程序不要求处理Error
Exception:代表程序中可恢复的错误,一般会要求程序处理Exception

IndexOutOfBoundsException:数组越界异常
NumberFormatException:数字格式异常:若输入的不是数字,而是字母,就会出现
ArithmeticException:除0异常
NullPointerException:空指针异常
以上四种都是运行时异常
Java 7提供的多异常捕捉:catch可以同时捕捉多个异常,用竖线分隔开多个异常类
catch(异常类1 | 异常2 | 异常3 ex)
多异常捕捉注意:
1、catch块中使用竖线隔开多个异常类
2、多异常捕获时,异常变量是有隐式的final修饰(不能对异常变量作修改)
捕获异常后,可以通过异常对象获取异常信息,有如下方法:
getMessage():返回异常的详细描述字符串
printStackTrace():打印异常的跟踪栈信息
getStackTrace():返回该异常的跟踪栈信息。
完整的异常处理流程:
try
{
//业务处理代码,可能出现异常
}
Catch(异常类1 | 异常类2 ex) ——catch块可出现0~N次
{
}
Catch(异常类 ex)
{
}
Finally ——finally块可出现0~1次
{
//用于回收物理资源。如数据库连接、文件关闭等,垃圾回收机制不会回收这些资源。
}
Java会保证无论是否出现异常,甚至有return都不能阻止finally块的执行。无论如何,finally块总会执行(除非用System.exit(1)或runtime.exit(1)来退出虚拟机才可以阻止)

如果有try块,那么catch和finally块中的一个至少要出现一次。

Final、finally、finalize的区别:
Final:修饰符,用于修饰类、方法、变量。
Finally:finally块,异常处理流程中最后一个块.
Finalize:只是Object的一个方法,所有对象被GC之前,系统会自动调用该对象的finalize方法,程序永远不会主动调用finalize方法——让JVM来调用。
public static int test()
{
int a = 5;
try
{
System.out.println(“try:”);
//执行的return语句,原本要结束方法。
//java会去检查是否有finally块,如果有,会执行finally,然后才来返回。
return ++a;
}
finally
{
return ++a;
}
}

除非在try块、catch块中调用了退出虚拟机的方法,否则不管在try块、catch块中执行怎样的代码,出现怎样的情况,异常处理的finally总会被执行。
上方代码中,test()方法先执行到try的return,a自加变为6,此时java会去检查是否有finally,有finally,去执行finally,此时遇到finally的return语句,a自加变为7,方法结束,因此,返回值为a = 7。

异常处理的嵌套:可能在try或catch块或finally块中再次使用try…catch来处理异常。

Java 7的自动关闭资源的try语句:
原来要求:在try块打开的资源,必须用finally块来进行关闭。
try(
//声明、并创建需要自动关闭的资源。
){}

关闭资源的try语句3个注意点:
1、自动关闭的资源必须在try后的圆括号中声明、并创建。
2、自动关闭的资源必须要实现Closeable或AutoCloseable接口并实现close()方法——用于保证该资源是可自动关闭的。Closeable是AutoCloseable的子接口
3、自动关闭资源的try语句自带隐式finally块(这个finalyl块用于关闭资源),因此可以既没有catch块、也没有finally块。
Java7已经几乎把所有的“资源类”(包括文件IO的各种类、JDBC的Connection、Statement等接口)进行了改写,都实现了AutoCloseable或Closeable接口

异常的常用方法:
Exception继承Throwable,所有异常都是Exception的子类。
getMessage():获取异常信息
getStackTrace():获取异常的跟踪栈(堆栈信息)信息(显示异常传递的路线。)
printStackTrace():打印异常的跟踪(堆栈信息)信息
简单小结:

try
{
//正常的业务处理代码(可能报错的代码)
}
catch(异常类1 | 异常类2 ex)  catch可以出现0~N{
//异常处理代码
}
...
Finally        finally块可以出现0~1{
//回收资源的代码
}

try(声明、并创建可自动关闭的资源(Io资源、网络通信的资源等))
{
//正常的业务处理代码
}

自动关闭资源的try语句,这种try语句有一个隐式的finally块,因此可以既没有catch块、也没有finally块。普通的try语句不能独立存在!自动关闭资源的try可以独立存在。

异常分为两大类:checked异常与runtime异常(非常重要)
runtime异常:所有RuntimeException及其子类的实例,都是runtime异常。你想处理runtime异常就处理,不想处理就不处理,编译器编译的时候不会报错,运行时才有可能报错。(即程序在运行时遇到未捕捉的runtime异常或Error,程序还是会终止。)

checked异常:不是RuntimeException类及其子类,就是checked异常。要么显式的用try…catch处理异常,要么声明抛出(throws)。否则编译无法通过!
市面上流传一个观点:runtime异常比checke异常用来更方便。
Hibernate、Spring把checked异常包装成runtime异常。
throws声明抛出异常:当不知道如何处理这种类型异常时,在方法签名上使用,throws可以一次声明抛出多个异常。
Error:代表严重,不可恢复的错误,因此,Java也不强制开发者处理。
throws 异常类1,异常类2…
方法重写的规则:2同2小1大——2小:返回值类型相同或更小,声明抛出的异常相同或更小。所以子类重写的方法抛出的异常必须要比父类方法抛出异常小。
面试题:写出熟悉的5个runtime异常
NullPointerException ArrayIndexOutOfBoundsException ArithmeticException
NumberFormatException IllegalArgumentException

抛出异常:在某些时候,程序中出现了一种与业务不符合的情况,主动抛出异常。
Throw 异常对象(实例);——throw是一个独立使用的语句,用于抛出一个异常对象。

面试题:throws、throw、throwable的区别:
throws:用来声明抛出异常,只能在方法签名上使用,声明抛出异常代表该方法暂时不处理这些异常,这些异常交给该方法的调用者处理。Throws可声明抛出多个异常类。
throw:抛出一个异常实例,是一条独立的执行性语句,throw可放在方法体、构造器、初始化块中使用。throw只能抛出一个异常对象。
Throwable:throwable是Exception和Error共同的父类。

如果方法中throw了一个异常,表明这段代码可能抛出异常。
如果该异常是checked异常,要么声明抛出,要么try…catch,否则编译不通过,——绝大部分都是声明抛出。
如果该异常是runtime异常,编译总会通过,因此可以不用理会。
抛出异常和自定义异常:
自定义异常:要继承Exception或者RuntimeException,如果定义runtime异常,就继承
RuntimeException,如果定义checked异常,就继承Exception,此外,还必须提供两个构造器,一个无参构造器,一个带字符串参数的构造器,字符串将作为该异常对象的描述信息,有参构造器内部将通过super调用父类的构造器,将字符串传给异常对象的message属性,该message属性就是异常对象的详细描述信息,如下面的自定义checked异常类:

Public class AuctionException extends Exception{
Public AuctionException (){}
Public AuctionException (String msg){
Super(msg);
}
}

catch与throw结合使用:
在某些情况下,catch块捕获了异常,程序也对该异常进行了部分处理,但依然不能完全处理,此时就需要在catch块中再次抛出新的异常对象。
大型企业级应用常用:如1、应用后台需要通过日志记录异常发生的详细情况2、应用还需要根据异常向应用使用者传达谋者提示。 此时就需要分两部分共同完成。

Java7以后,当catch使用Exception类型捕获到异常时,捕获的有可能是FileNotFoundException异常,但此时赋值的是Exception,若使用throw将该异常抛出,编译器会执行细致的检查,检查throw抛出的异常实际类型,因此,此时只需要throws抛出FileNotFoundException即可。

异常转译:当业务逻辑访问持久层出现SQLException,不应该直接抛出传到用户界面,用户看不懂,此时应该捕获该异常,然后重新抛出一个用户看得懂的异常,包含对用户的提示信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值