2018-06-01 java自学 泛型程序设计笔记(第一部分)

使用教材:java核心技术 卷一 第十版

8.1

1.在 Java 中增加范型类之前, 泛型程序设计是用继承实现的。

2.ArrayList类出现于泛型类之前,其中有一个Object引用的数组,取出值时必须进行强制类型转换,且其中没有错误检查,因此可以向数组列表中添加任意类的对象(反正都是Object的子类,而且都是引用,不用担心存贮问题)

3.在引入泛型后,ArrayList 类有一个类型参数用来指示元素的类型:

ArrayList<String> files = new ArrayList<String>():

//在 Java SE 7 及以后的版本中, 构造函数中可以省略泛型类型: ArrayList<String>files = new ArrayList<>();省略的类型,可以从变量的类型推断得出,事实上,当这个file实例调用方法时,也不需强制类型转换,而且编译器将进行类型检查。

8.2

public class Pair<T>

{

     private T first;

     private T second;

     public Pair() {first = null; second = null;}

     public Pair(T first, T second) {this.first = first; this.second = second;}

     public T getFirst() {return first;}

     public T getSecond() {return second;}

     public void setFirst(T newValue) {first = newValue;}

     public void setSecond(T newValue) {second = newValue;}

}

1.语法:定义类时:public class Pair<T, U> { . . . }

//C++中是Template<class T, class U> class Pair{…}

2.类定义中的类型变量可以指定方法的返回类型以及域和局部变量的类型

3.用具体的类型替换类型变量就可以实例化泛型类型:Pair<String>,其结果可看成一个普通类。

//在new一个泛型类的变量时,似乎不用声明<>里的东西,编译器会根据其中的参数类型进行推算。

//接上,当定义一个引用时,必须要声明出类型。

8.3泛型方法

1.泛型方法可以定义在普通类中,类型变量放在修饰符(这里是 public static) 的 后面,返回类型(这里是T)的前面。

public static <T> T getMiddle(T... a) { return a[a.length / 2]; }

2.当调用一个泛型方法时’在方法名前的尖括号中放人具体的类型: String middle = ArrayAlg.<String>getMiddle("]ohnM, "Q.n, "Public");

在这种情况(实际也是大多数情况)下,方法调用中可以省略类型参数。编译器有足够的信息能够推断出所调用的方法。它用 names 的类型(即String[ ])(函数参数) 与泛型类型 T[ ] 进行匹配并推断出 T 一定是 String。也就是说,可以调用 String middle = ArrayAlg.getHiddle("]ohn", "Q.", "Public");

3.要注意二义性的问题:double middle = ArrayAlg.getMiddle(B.14, 1729, 0);函数参数里面有int和double, 编译器将会自动打包参数为1个Double和2个Integer对象,而后寻找这些类的共同超类型。事实上;找到2个这样的超类 型:Number 和 Comparable 接口,而这些超类其本身也是一个泛型类型。

//C++是会直接不允许二义性的出现,而不会向上自动寻找超类。

8.4 类型参数的限定

1.有时需要对于类型变量加以约束,比如说我们使用了一个方法,但是类型变量并不能保障一定具有这个方法,比如说可能代码要依赖于某个接口,即:

class ArrayAIg

{

    public static <T> T min(T[] a)

    {

      if (a null || a.length = 0) return null;

      T smallest = a[0];

      for (int i = 1 ; i < a.length; i++)

      if (smallest.compareTo(a[i]) > 0) smallest = a[i];

      return smallest;

     }

}

无法保证T在使用时对应的类型一定会有compareTo方法。可以通过对类型变量 T 设置限定(bound) 实现这一点: public static <T extends Comparable> T min(T[] a) . . .

// Comparable 接口本身就是一个泛型类型???实现此方法说明以对其做了约束。

//在 C++ 中不能对模板参数的类型加以限制。如果程序员用一个不适当的类型 实例化一个模板,将会在模板代码中报告一个(通常是含糊不清的)错误消息。

2.限制关键字使用extends,无论对象是类还是接口。一个类型变量或通配符可以有多个限定, 例如: T extends Comparable & Serializable 限定类型用“ &” 分隔,而逗号用来分隔类型变量。

3.在限定中,可以有多个接口,但只能有一个类,而且如果用一个类作为限定,他必须时限定列表中的第一个。

8.5 泛型代码和虚拟机

1.无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是删去类型参数后的泛型类型名。擦除(erased) 类型变量, 并替换为限定类型(无限定的变量用 Object)。

2.java中的泛型只在程序源码中存在,当编译成字节码之后,就会变为原始类型,java实现泛型的方法是类型擦除。

3.当程序调用泛型方法时,如果擦除返回类型, 编译器插入强制类型转换。例如,下面这个语句序列

Pair<Employee> buddies = . .

Employee buddy = buddies.getFirst();

擦除 getFirst 的返回类型后将返回 Object 类型。编译器自动插人 Employee 的强制类型转换,将返回的Object类型转换为Employee类型。当存取一个泛型域时也要插入强制类型转换。假设 Pair 类的 first 域和 second 域都是公 有的。表达式:

Employee buddy = buddies.first;

就会在结果字节码中插入强制类型转换。

4.类型擦除也会出现在泛型方法中,由此产生一些问题:在一个子类中,如果对于父类的一个函数进性了重载,则在擦除之后就会破坏重载。比如说:

class Datelnterval extends Pair<LocalDate>

{

    public void setSecond(LocalDate second)

    {

        if (second.compareTo(getFirstO) >= 0)

        super.setSecond(second);

    }

}

在被擦除之后,就会变为:

class Datelnterval extends Pair

{

    public void setSecond(LocalDate second)

    {……}

}

而在父类中,则有一个public void setSecond(Object second)方法,原本这个方法应该作为模板方法被实例化为public void setSecond(LocalDate second)而后被子类中的同名方法所重载,但是此时由于擦除,重载被破坏了,父类的方法与子类中的这个方法变为了两个方法,public void setSecond(Object second)和public void setSecond(LocalDate second)共同出现在子类中。

因此,编译器的解决方法是在作为子类的Datelnterval类中,生成一个桥方法,即为:

public void setSecond(Object second) { setSecond((Date) second); },这个方法使用了与父类方法在擦除之后相同的函数名,返回值,与参数列表,从而完全将父类方法覆盖,而这个桥方法的作用则是,将变量强制类型转换之后,传入子类方法,而在实际使用时,如果一个Pair的引用引用了一个Datelnterval类的变量,在接收参数时,即使调用桥函数,也会和以前一样运行。

5.假设Datelnterval类也重载了getSecond()方法,比如说:

class Datelnterval extends Pair

{

    public LocalDate getSecond()

    {

     return (Date) super.getSecond().clone();

    }

    ……

}

那么,在擦除之后,这个类中就会有两个函数:

public LocalDate getSecond()

public Object getSecond()/*这是一个桥方法,它将Pair类中的方法给覆盖掉了*/

由于虚拟机中,是使用参数类型和返回类型唯一确定一个方法,因此虽然在源码中无法写出如此形式,但是在最后生成的字节码中,却会出现以上这种情况。

6.这也导致了其他问题,比如说:

public class TestTheBug

{

public static void method (Pair<String> pairex)  { System.out.println("Pair string pairex"); }

public static void method (Pair<Integer> pairex)  { System.out.println("Pair int pairex"); }

}

这段代码表面上没有问题,但事实上是无法进行编译的,因为在进性擦除之后,这两个函数的函数头部已经完全一致了,由此造成错误。但是,如果你进行这样的修改:

public class TestTheBug

{

public static string method (Pair<String> pairex) 

    {       

    System.out.println("Pair string pairex");

    return "  "

    }

public static int method (Pair<Integer> pairex)

     {       

    System.out.println("Pair int pairex");  

    return 1;

    }

}

这两个方法就可以照常运行,因为通过不同的参数列表,通过了编译器,而后又通过不同的返回值,使得虚拟机也可以区分。

7.桥方法不仅用于泛型类型。 在一个方法覆盖另一个方法时可以指定一个更严格的返回类型。例如:

public class Employee implements Cloneable

    {

        public Employee clone() throws CloneNotSupportedException { ...}

    }

Object.clone 和 Employee.clone 方法被说成具有协变的返回类型(covariant returntypes)。 实际上,Employee 类有两个克隆方法:

Employee clone() // defined above

Object clone() // 合成的桥方法,覆盖了原本的Object.clone方法

合成的桥方法中调用了新定义的方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容

  • 引言:泛型一直是困扰自己的一个难题,但是泛型有时一个面试时老生常谈的问题;今天作者就通过查阅相关资料简单谈谈自己对...
    cp_insist阅读 1,830评论 0 4
  • 泛型程序设计 泛型程序设计意味着编写的代码可以被很多不同类型的对象所重用。例如ArrayList类可以聚集任何类型...
    Steven1997阅读 702评论 1 0
  • 参数类型的好处 在 Java 引入泛型之前,泛型程序设计是用继承实现的。ArrayList 类只维护一个 Obje...
    杰哥长得帅阅读 870评论 0 3
  •   在Effective中讲到泛型之处提到了一个概念,类型擦除器,这是什么呢?接下来我们跟随这篇文章探索类型擦除的...
    凌云_00阅读 2,128评论 0 8
  • 还没上学时,对回家这词没什么慨念,因为爸妈去哪,我就会到哪。 上了小学,家与学校的距离是铃声开始响...
    胡阿博阅读 361评论 0 3