------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
day18 01-常用对象API(集合框架-泛型-概述)
1、泛型这种参数类型可以用在类、接口、方法的创建中,分别称为泛型类、泛型接口、泛型方法。
2、泛型的引出,需求:定义一个表示坐标的操作类(Point),这个类可以表示3种类型的坐标。(1)整数坐标:x=10、y=20;(2)小数坐标:x=10.1、y=20.3;(3)字符串数据:x=“东经100度”、y=“北纬20度”
3、分析,类中如果要保存以上的数据,一定需要定义x和y两个属性,而这两个属性可以直接接收3种数据类型,那么使用Object类来定义会比较合适,这样会发生如下几种转换关系:
(1)整数:int→自动装箱为Integer→向上转型为Object;
(2)小数:double→自动装箱为Double→向上转型为Object;
(3)字符串:字符串→向上转型为Object
4、代码示例:
class Point{
private Object x;
private Object y;
public void setX(Object x){
this.x = x;
}
public void setY(Object y){
this.y = y;
}
public Object getX(){
return x;
}
public Object getY(){
return y;
}
}
5、范例一:设置整型
public class Demo34 {
public static void main(String[] args) {
Point point = new Point();
point.setX(10);
point.setY(20);
int x = (Integer)point.getX();
int y = (Integer)point.getY();
System.out.println("X的坐标是:"+x+",Y的坐标是:"+y);
}
}
运行结果:X的坐标是:10,Y的坐标是:20
6、范例二:设置小数
public class Demo34 {
public static void main(String[] args) {
Point point = new Point();
point.setX(10.2);
point.setY(20.3);
double x = (Double)point.getX();
double y = (Double)point.getY();
System.out.println("X的坐标是:"+x+",Y的坐标是:"+y);
}
}
运行结果:X的坐标是:10.2,Y的坐标是:20.3
7、范例三:设置字符串
public class Demo34 {
public static void main(String[] args) {
Point point = new Point();
point.setX("东经100度");
point.setY("北纬20度");
String x = (String)point.getX();
String y = (String)point.getY();
System.out.println("X的坐标是:"+x+",Y的坐标是:"+y);
}
}
运行结果:X的坐标是:东经100度,Y的坐标是:北纬20度
8、但是本程序存在问题,关键就在于Object,所有的类型都可以向Object转换。
范例四:发现程序问题所在
public class Demo34 {
public static void main(String[] args) {
Point point = new Point();
point.setX(10);
point.setY("北纬20度");
String x = (String)point.getX();
String y = (String)point.getY();
System.out.println("X的坐标是:"+x+",Y的坐标是:"+y);
}
}
运行结果:Exception in thread "main" java.lang.ClassCastException: java.lang.Integer
atcom.itheima.Demo34.main(Demo34.java:25)
9、你把程序写完了。用户瞎往里传了个10,但由于是都拿Object接收,没有问题。但其实出现了数据的不统一。而这问题却不会在编译时暴露出来,而是在运行时暴露的。(用户往里传了个int,你向上转型成Object接收,没问题。但你再向下转型成String,不行,挂了。
10、泛型:广泛的类型,类中操作的属性或方法的参数类型不在定义是声明,而是在使用时动态设置。
11、泛型,是JavaSE1.5的新特性。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口、方法的创建中,分别称为泛型类、泛型接口、泛型方法。
在JavaSE1.5之前没有泛型的情况下,通过对类型Object的引用来实现参数的“任意化”。这种“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
12、注意:泛型类型只能设置为类
如果要设置泛型类型,只能将其定义为类的形式。例如,如果要保存的数据为int,则设置的类型必须是其包装类Integer。而此时,自动装箱及拆箱机制就可以给予很好的支持。
13、利用泛型解决问题,代码示例:
class Point<T>{ //T:type,可以使用任意标记,例如:A、B均可
private T x; //属性类型由外部决定
private T y; //属性类型由外部决定
public void setX(T x){
this.x = x;
}
public void setY(T y){
this.y = y;
}
public T getX(){
return x;
}
public T getY(){
return y;
}
}
14、之后再来试验一下数据类型不统一的情况
public class Demo44 {
public static void main(String[] args) {
Point<String> point = new Point<String>(); //实例化对象
point.setX(10); //编译时出现错误
point.setY("北纬20度"); //1、设置y坐标的内容
String x = (String)point.getX(); //2、取得x坐标数据
String y = (String)point.getY(); //3、取得y坐标数据
System.out.println("x的坐标是:"+x+",y的坐标是:"+y);
}
}
由于设置了泛型类型为String,所以本程序中对应的setter方法的苍南数类型就统一设置为了String,而如果设置的数据不是String,则在编译时就会出现语法错误。
15、泛型的好处:
①、将运行时期的问题ClassCastException转到了编译时期,这时由程序员来解决这个问题。即:在编译的时候检查类型安全。
②、避免了强制转换的麻烦。所有的强制转换都是自动和隐式的,提高了代码的重用率。
16、进一步理解编译时期的错误、与运行时期的错误。
编译时出现错误,则在程序员编写程序时就可以很明显的发现,并且该错误可以由程序员立刻进行修改。这样,程序员就可以最早的对这样的问题进行解决。
运行时出现错误,则可能是在用户正常使用、运行该软件时出现错误。而该错误用户无法解决,对用户的使用造成很大不便。所以这是一种安全隐患。
打个比方就是,做一个装置箱,我们肯定希望在设计时就发现其中存在的问题缺陷。而不是在制造出来后,甚至是已近给到用户后,再发现其中的缺陷错误。因此,我们希望错误最好能在编译时期就暴露出来。
17、进一步理解ClassCastException在编译时期和运行时期的问题
异常中说过,编译时出错的问题可以理解为是“语法问题”。而运行时出错问题可以理解为是“逻辑问题”。那么来看本节中出现的ClassCastException。
没用泛型时,我相当于说了一句“小猫变成生物,生物再变成小猪”,这句话在语法上完全没有任何问题,他只是在逻辑上有问题。
用泛型时,我相当于事先就限制了所应传入的数据类型,你传的不对,所以这个错误就暴露在了编译时期。就像method(int i),你写method("字符串"),当然就挂了。
18、如果你定义了反省,但是在使用时没有设置反省。那么程序在编译过程中会出现警告信息。而且为了保证程序不出现错误,所有的类型都将使用Object进行处理。
19、两个有关泛型的其他程序
代码一:
public class Demo37 {
public static void main(String[] args) {
ArrayList<String> al = new ArrayList<String>();
show(al);
}
public static void show(ArrayList al){
al.add("abc");
al.add(100);
System.out.println(al);
}
}
运行结果:[abc, 100]
怎么回事?我的泛型废了。100也进来了。因为你的show方法的参数是ArrayList al。这相当于ArrayList al = new ArrayList<String>();只在后面写泛型是没用的。代码二可证明这一点。
代码二:
public class Demo37 {
public static void main(String[] args) {
ArrayList al1 = new ArrayList<String>();
al1.add("abc");
al1.add(100);
ArrayList<String> al2 = new ArrayList();
al2.add("def");
al2.add(200);//仅在这里编译报错,类型不对
}
}
所以,用泛型,重要的是在前面。JDK1.5和JDK1.7在具体化泛型时稍微有些区别。
JDK1.5具体化泛型方式,Point<String> point = new Point<String>();
JDK1.7具体化泛型的方式,Point<String> point = new Point();
但仍然建议使用完整语法进行编写。
day18 02-常用对象API(集合框架-泛型-擦除&补偿)了解
1、泛型是用在编译时期的安全机制,是给编译器使用的技术,用于编译时期,确保了类型的安全。
2、运行时,会将泛型去掉,生成的class文件是不带泛型的,这个称为泛型的擦除。
那么,为什么要擦除呢?因为为了兼容运行的类加载器。我不擦他,你以前的类加载器运行不了!
3、泛型的补偿:在运行时,通过获取元素的类型进行转换动作,不用使用者再强制转换。
day18 03-常用对象API(集合框架-泛型-在集合中的应用)
day18 04-常用对象API(集合框架-泛型-泛型类)
1、泛型类什么时候用?当类中操作的引用数据类型不确定的时候,就使用泛型来表示。
2、代码示例:
class Tool<QQ>{
private QQ q;
public QQgetObject(){
return q;
}
public void setObject(QQ object){
this.q = object;
}
}
day18 05-常用对象API(集合框架-泛型-泛型方法)
1、同理,当方法中操作的引用数据类型不确定的时候,就使用泛型方法。把泛型也可以定义在方法上。另外,在方法上定义泛型时,这个方法不一定非要在泛型类中定义。
2、代码示例
class Tool<QQ>{
private QQ q;
public QQ getObject(){
return q;
}
public void setObject(QQ object){
this.q = object;
}
public void show(QQ object){
System.out.println("show:"+object);
}
public <W> void print(W w){
System.out.println("print:"+w);
}
}
public class Demo35 {
public static void main(String[] args) {
Tool<String> tool = new Tool<String>();
tool.show("haha");
tool.print(100);
}
}
运行结果:show:haha
print:100
3、上例中,show方法使用了类上定义的泛型QQ,所以只能往里传String。而print方法上面自己有个泛型,所以可以不受约束。
4、当方法静态时,不能访问类上定义的泛型(因为类上定义的泛型需要对象来明确,但是静态方法是不需要对象的)。如果静态方法使用泛型,只能将泛型定义在方法上。
例如:public <Y> static void method(Y obj){}
5、注意,泛型方法在定义时,泛型一定要放在返回值的前面,修饰符的后面。
public <Y> staticvoid method(Y obj){}
这么定义是错误的,<Y>要放到static后面。
day18 06-常用对象API(集合框架-泛型-泛型接口)
1、代码示例:
interfaceMessage<T>{
public String echo(T msg);
}
2、对于泛型接口的实现,在java中有两种方式:
①、方式一:在子类上继续使用泛型,在实例化子类时,设置具体类型
interface Message<T>{
publicString echo(T msg);
}
class MessageImpl <T> implements Message<T>{
publicString echo(T msg){
return "ECHO:"+msg;
}
}
public classDemo36 {
public static voidmain(String[] args) {
Message<String> msg = newMessageImpl<String>();
System.out.println(msg.echo("张三"));
}
}
运行结果:ECHO:张三
②、方式二:在子类上设置具体类型
interface Message<T>{
publicString echo(T msg);
}
class MessageImpl implementsMessage<String>{
publicString echo(String msg){
return "ECHO:"+msg;
}
}
public classDemo36 {
public static voidmain(String[] args) {
Message<String> msg = new MessageImpl();
System.out.println(msg.echo("张三"));
}
}
运行结果:ECHO:张三
day18 06.5-常用对象API(集合框架-泛型-通配符)
1、为什么要出现通配符?
2、为了说明这个问题,定义了一个类:
class Message<T>{
private T info;
public void setInfo(T info){
this.info = info;
}
public T getInfo(){
return info;
}
}
3、代码示例一:
public class Demo45 {
public static void print(Message<String> temp){
System.out.println(temp.getInfo());
}
public static void main(String[] args) {
Message<String> msg = new Message<String>();
msg.setInfo("HelloWorld");
print(msg);
}
}
运行结果:Hello World
4、代码示例二:
public class Demo45 {
public static void main(String[] args) {
Message<Integer> msg = new Message<Integer>();
msg.setInfo(100);
print(msg);
}
public static void print(Message<String> temp){
System.out.println(temp.getInfo());
}
}
print(msg)一句报错。此时的print(Message<String> temp)方法无法再接收Message<Integer>对象的引用,因为这个方法只能接收Message<String>,那么如果将print()方法重载,换成Message<Integer>呢?
5、代码示例三:尝试通过重载解决问题。
public class Demo45 {
public static void main(String[] args) {
Message<Integer> msg = new Message<Integer>();
msg.setInfo(100);
print(msg);
}
public static void print(Message<String> temp){
System.out.println(temp.getInfo());
}
public static void print(Message<Integer> temp){
System.out.println(temp.getInfo());
}
}
此处重载失败了。编译报错:已定义了方法。因为方法重载时观察的不是泛型类型,而是类的名称,或者是数据类型。
6、代码示例四:定义方法时不写泛型
public class Demo45 {
public static void main(String[] args) {
Message<Integer> msg = new Message<Integer>();
msg.setInfo(100);
print(msg);
}
public static void print(Message temp){
System.out.println(temp.getInfo());
}
}
运行结果:100
7、但是又有新的问题,print()方法可以任意设置
public class Demo45 {
public static void main(String[] args) {
Message<Integer> msg = new Message<Integer>();
msg.setInfo(100);
print(msg);
}
public static void print(Message temp){
temp.setInfo("HelloWorld");
System.out.println(temp.getInfo());
}
}
运行结果:Hello World
因为你不写泛型,相当于Message<Object> temp。T成了Object,想接受谁都行。
8、解决方法,通配符
public class Demo45 {
public static void main(String[] args) {
Message<Integer> msg = new Message<Integer>();
msg.setInfo(100);
print(msg);
}
public static void print(Message<?> temp){
System.out.println(temp.getInfo());
}
}
运行结果:100
9、泛型的出现,将一个类又划分成了若干个不同的小类型:
10、对于“?”就记住一点,他表示任意类型,如果有参数返回时也是这个“?”,就当成Object进行理解。
day18 07-常用对象API(集合框架-泛型-上限)
1、写法:? extends 某一个类
2、举例:
public static voidprintCollection(Collection<? Extends Person> al){}
该方法使用了上限,只接收“元素为Person或Person子类的Collection集合”。
所以,如果是:
ArrayList<String> al = new ArrayList<String>();
al.add(“str1”);
al.add(“str2”);
printCollection(al);//这一步挂了,因为al的泛型具体化显示,它里面的元素不是Person或Person的子类。
3、另外,你这么限定之后,就可以用具体方法了。因为printCollection方法只接收“元素为Person或Person子类的Collection集合”。那么我就可以在printCollection方法里使用Person类的特有方法。如下例:
public static voidprintCollection(Collection<? Extends Person> al){
Iterator<? Extend Person> it = al.iterator();
while(it.hasNext()){
Person p = it.next();
System.out.print(p.getName()+”…”+p.getAge());
}
}
4、注意:public static void printCollection(Collection<? Extends Person>al){}这个方法并不是泛型方法。他只是利用通配符,对传入的"存在泛型的参数"(例如Collection<?extends Person>)有所限制。而泛型方法则是:当方法中操作的引用数据类型不确定的时候,就使用泛型方法。要注意区分。
day18 08-常用对象API(集合框架-泛型-下限)
1、上限的写法是:? extends E——接收E类型或者E的子类对象。
2、下限的写法是:? super E——接收E类型或者E的父类型。
3、代码示例:
public static voidprintCollection(Collection<? super Student> al){
Iterator<? super Student> it = al.iterator();
……
}
你<? super Student>一加,可以传Person、Student,但你不能传Worker了。因为Worker不是Student的父类。
4、关于迭代器的泛型
迭代器的泛型,100% 和获取迭代器集合的泛型一致。那么,我集合已经加了泛型,为什么迭代器上还要加?因为你使用迭代器是要便利集合里面的数据。遍历的时候,iterator默认遍历出来的都向上转成了Object类。你加上泛型就不用强转了。
day18 09-常用对象API(集合框架-泛型-泛型限定(上限的体现))
1、上限什么时候用?
答:一般在往里面存元素的时候,用上限(? extends E)。因为这样去除的都是按照上限类型来运算的。不会出现类型安全隐患。
2、JAVA中用上限的例子:
接口Collection<E>
方法摘要:
boolean addAll(Collection<? extends E> c)
3、代码示例:
class MyCollection<E>{
public void add(E e){
}
public void addAll(MyCollection<E> c){
}
}
public class Fanxing {
public static void main(String[] args) {
MyCollection<Person> mc1 =new MyCollection<Person>();
MyCollection<Person> mc2 =new MyCollection<Person>();
MyCollection<Student> mc3 =new MyCollection<Student>();
MyCollection<String> mc4 =new MyCollection<String>();
mc1.addAll(mc3);//此句编译报错
}
}
但如果改成如下,则不会报错。
class MyCollection<E>{
public void add(E e){
}
public void addAll(MyCollection<? extends E> c){
}
}
public class Fanxing {
public static void main(String[] args) {
MyCollection<Person> mc1 =new MyCollection<Person>();
MyCollection<Person> mc2 =new MyCollection<Person>();
MyCollection<Student> mc3 =new MyCollection<Student>();
MyCollection<String> mc4 =new MyCollection<String>();
mc1.addAll(mc3);
}
}
day18 10-常用对象API(集合框架-泛型-泛型限定(下限的体现))
1、什么时候用下限?通常对集合中的元素进行取出操作时,可以使用下限。你存什么类型,我用什么类型接收。
2、JAVA中用下限的例子:
类TreeSet
构造方法摘要:
TreeSet(Comparator<? super E> comparator)
构造一个新的空TreeSet,他根据指定比较器进行排序。
2、你一个容器中,既能添加Person对象,又能添加Student对象,又能添加Worker对象等子对象。那这里我又有Person,又有学生、又有工人。我想对这些对象进行统一排序。排的时候,要将这些对象取出来比较,我拿什么接收?Person!
3、举个例子:
我的容器里面是Person,那么
day18 11-常用对象API(集合框架-泛型-泛型限定(通配符的体现))
1、通配符一般什么时候用?你只要里面全是Object方法,就用这个。
2、JAVA中用通配符的例子
接口Collection
boolean containsAll(Collection<?> c)
如果此Collection包含指定Collection中的所有元素,则返回true。