Java SE 9th day
1、本次课程知识点
1、泛型的作用
2、泛型的基本使用
2、具体内容
2.1 泛型的产生
要求设计一个可以表示坐标的类,(X、Y)
但是此坐标可以同时满足以下几种要求:
● x = 10、y = 100
● x = 10.3、y = 50.2
● x = “东经180度”、y = “北纬210度”
问,这样的坐标类该如何设计?
分析:
因为现在的程序中可以接收三种数据类型的数据,所以为了保证程序的正确性,最好使用Object完成,因为Object可以接收任意的引用数据类型:
现在有三种类型的数据:int、float、String,回想自动装箱的操作
● int → Integer → Object
● float → Float → Object
按照以上特点,完成程序:
package org.util;
public class Point { // 表示坐标 private Object x; private Object y;
public Object getX() { return x; }
public void setX(Object x) { this.x = x; }
public Object getY() { return y; }
public void setY(Object y) { this.y = y; } } |
此时,Point类完成了。
此时,设置一个整形数字,那么来观察是否可以操作:
package org.util;
public class Math { public static void main(String args[]) { Point p = new Point(); p.setX(11); // int →Integer →Object p.setY(20); // int →Integer →Object int x = (Integer) p.getX(); // 取出x坐标 int y = (Integer) p.getY(); // 取出y坐标 System.out.println("x的坐标是:" + x); System.out.println("y的坐标是:" + y); } } |
x的坐标是:11 y的坐标是:20 |
此时,达到了设置整数的目的,那么下面继续完成设置小数的操作。
package org.util;
public class Math { public static void main(String args[]) { Point p = new Point(); p.setX(11.3f); p.setY(20.3f); float x = (Float) p.getX(); // 取出x坐标 float y = (Float) p.getY(); // 取出y坐标 System.out.println("x的坐标是:" + x); System.out.println("y的坐标是:" + y); } } |
x的坐标是:11.3 y的坐标是:20.3 |
下面继续设置字符串作为x和y的坐标
public class Math02 { public static void main(String args[]) { Point p = new Point(); p.setX("东经180度"); p.setY("北纬220度"); String x = (String) p.getX(); // 取出x坐标 String y = (String) p.getY(); // 取出y坐标 System.out.println("x的坐标是:" + x); System.out.println("y的坐标是:" + y); } } |
x的坐标是:东经180度 y的坐标是:北纬220度 |
此时,基本的功能已经实现了,但是此种操作是否存在问题呢?
在此操作之中,可以发现所有的内容都是以Object进行操作的,那么就意味着,可以设置任意的类型,即:X可以是整形,Y可以是字符串。
package org.util;
public class Math03 { public static void main(String args[]) { Point p = new Point(); p.setX(11); p.setY("北纬220度"); int x = (Integer) p.getX(); // 取出x坐标 int y = (Integer) p.getY(); // 取出y坐标 System.out.println("x的坐标是:" + x); System.out.println("y的坐标是:" + y); } } |
此时程序在编译的时候没有任何的问题,但是在执行的时候出现了以下的错误提示:
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer at org.util.Math03.main(Math03.java:9) |
之所以会这样,因为在程序中的所有的属性都可以向Object进行转换,那么此时程序的入口就显得不那么规范了,而且存在安全的漏洞。
但是,从之前所学习过的全部代码来讲,此处只能应用到这里了,没有更好的方法了。
2.2 JDK 1.5新特性 —— 泛型
JDK 1.5之后出现了新的技术 —— 泛型,此技术的最大特点是在类中的属性的类型可以由外部决定。
泛型类定义格式:
[访问权限] class类名称<泛型类型1,泛型类型2,……泛型类型n>{ [访问权限] 泛型类型标识 变量名称; [访问权限] 泛型类型标识 方法名称(){} [访问权限] 返回值类型声明 方法名称(泛型类型标识 变量名称){} } |
泛型对象定义:
类名称<具体类>对象名称 = new 类名称<具体类>(); |
注意:在泛型的指定中是无法指定基本数据类型的,必须设置成一个类,这样在设置一个数字的时候就必须使用包装类。
那么使用如上的操作格式来修改之前的操作类。
package genericsdemo;
public class Point<T> { // 表示坐标 private T x; private T y;
public T getX() { return x; }
public void setX(T x) { this.x = x; }
public T getY() { return y; }
public void setY(T y) { this.y = y; } } |
此时,程序中加入泛型操作之后,可以发现一切的操作类型此时都不再由程序固定设置,而是由实例化对象的时候在外部进行了指定。
package genericsdemo;
public class Math { public static void main(String args[]) { Point<Integer> p = new Point<Integer>(); p.setX(11); p.setY(20); float x = p.getX(); // 取出x坐标 float y = p.getY(); // 取出y坐标 System.out.println("x的坐标是:" + x); System.out.println("y的坐标是:" + y); } } |
x的坐标是:11.0 y的坐标是:20.0 |
发现此时在使用Point类的时候,需要加入一个属性类型的声明,而且加入之后再取出属性的时候本身也变得非常容易,不用再使用向下转型了。
而且,使用上面的操作有一点最方便之处,如果此时设置的内容不是整形,那么程序中将出现错误。
package genericsdemo;
public class Math { public static void main(String args[]) { Point<Integer> p = new Point<Integer>(); p.setX(11); p.setY("北纬220度"); // 错误,不能设置String类型 float x = p.getX(); // 取出x坐标 float y = p.getY(); // 取出y坐标 System.out.println("x的坐标是:" + x); System.out.println("y的坐标是:" + y); } } |
加入泛型之后,可以对程序的操作起到更加安全的目的。
2.3 泛型的其他注意点
在使用泛型操作的时候,实际上有很多小的注意点,例如:构造方法上依然可以使用泛型或者有一种称为泛型的擦除。
2.3.1 在构造方法上应用泛型
一般开发中,经常使用构造方法设置属性的内容。那么此时实际上构造方法上依然可以使用泛型的类型。
格式:
[访问权限] 构造方法 ( [<泛型类型参数名称>] ){} |
范例:
package genericsdemo;
public class Point<T> { // 表示坐标 private T x; private T y;
public Point(T x, T y) { this.setX(x); this.setY(y); }
public T getX() { return x; }
public void setX(T x) { this.x = x; }
public T getY() { return y; }
public void setY(T y) { this.y = y; } } |
那么此时在调用的时候就需要使用构造方法设置内容,当然,设置的内容本身依然由泛型指定。
package genericsdemo;
public class Math { public static void main(String args[]) { Point<Integer> p = new Point<Integer>(10, 20); int x = p.getX(); // 取出x坐标 int y = p.getY(); // 取出y坐标 System.out.println("x的坐标是:" + x); System.out.println("y的坐标是:" + y); } } |
x的坐标是:10 y的坐标是:20 |
2.3.2 擦除泛型
如果在使用的时候没有指定泛型的话,则表示擦除泛型。
泛型一旦擦除后,将按照Object进行接收,以保证程序不出现任何错误。
package genericsdemo;
public class Math { public static void main(String args[]) { Point p = new Point(10, 20); int x = (Integer) p.getX(); // 取出x坐标 int y = (Integer) p.getY(); // 取出y坐标 System.out.println("x的坐标是:" + x); System.out.println("y的坐标是:" + y); } } |
但是在以上的操作代码中依然会存在警告信息,那么该如何去掉警告信息呢?
package genericsdemo;
public class Math { public static void main(String args[]) { Point<Object> p = new Point<Object>(10, 20); int x = (Integer) p.getX(); // 取出x坐标 int y = (Integer) p.getY(); // 取出y坐标 System.out.println("x的坐标是:" + x); System.out.println("y的坐标是:" + y); } } |
但是,以上的操作虽然去掉了警告信息,但是没有意义。
不指定泛型流程图:
2.4 通配符
在泛型中通配符使用较多,而且在日后的系统类库中有很多的地方都要使用这些操作
例如:现在有如下的操作代码
package genericsdemo03; public class Test { public static void main(String[] args) { Object obj = "Hello"; } } |
以上的语法实际上是表示进行了向上的转型操作,因为String是Object的子类,但是现在在泛型中却没有此概念。
2.4.1 ?
在进行对象转换的时候可以使用自动的向上转型,但是在使用泛型的时候却没有此种操作。
package genericsdemo03; public class Point<T> { // 表示坐标 private T x; private T y;
public T getX() { return x; } public void setX(T x) { this.x = x; }
public T getY() { return y; } public void setY(T y) { this.y = y; } } |
那么下面定义两个Point类的对象。
package genericsdemo03;
public class Math { public static void main(String args[]) { Point<Object> p1 = new Point<Object>(); Point<Integer> p2 = new Point<Integer>(); p1 = p2; // 此时无法转型 } } |
D:\javatest>javac Math.java Math.java:28: 错误: 不兼容的类型 p1 = p2; // 此时无法转型 ^ 需要: Point<Object> 找到: Point<Integer> 1 个错误 |
此时的程序发现,根本就无法进行转换操作。
引用传递时同样存在以上无法转换问题,如下,对主方法进行修改。
范例:泛型类中使用引用传递
public class Math { public static void main(String args[]) { Point<Integer> p = new Point<Integer>(); fun(p); }
public static void fun(Point<Object> temp) {// 如果去掉<Object>则程序可正常运行 System.out.println("内容:" + temp); } } |
D:\javatest>javac Math.java Math.java:27: 错误: 无法将类 Math中的方法 fun应用到给定类型; fun(p); ^ 需要: Point<Object> 找到: Point<Integer> 原因: 无法通过方法调用转换将实际参数Point<Integer>转换为Point<Object> 1 个错误 |
此时的程序实际上已经不完全属于对象的转型操作了,属于一个大的类型和小的类型的划分。
例如:将“Point<Object> p1 = new Point<Object>();”表示为整个商场的全部商品,而“Point<Integer> p2 = new Point<Integer>();”表示每一个顾客购买的商品。如果现在执行“p1 = p2;”那么就意味着,本顾客所购买的商品就是商场中的全部商品。这样肯定说不通,所以不能接收。
不能使用以上的方式接收最大的影响在于方法的参数接收上。
package genericsdemo03; public class Math { public static void main(String args[]) { Point<Object> p1 = new Point<Object>(); Point<Integer> p2 = new Point<Integer>(); fun(p1); fun(p2); }
public static void fun(Point<?> po) { // 表示此时可以接收任意的类型 System.out.println(po.getX()); System.out.println(po.getY()); } } |
null null null null |
程序中的“?”表示可以接收任意的泛型类型,但是只是接收输出,并不能修改。
2.4.2 泛型上限
上限就指一个的操作泛型最大的操作父类,例如,现在最大的上限设置成“Number”类型,那么此时,所能够接收到的类型只能是Number及其子类(如Integer)。
泛型上限设置格式:
声明对象: | 类名称<? extends 类> 对象名称 |
定义类: | [访问权限] 类名称<泛型标识 extends 类>{} |
范例:在Point类中只能设置数字的坐标
package genericsdemo03; public class Point<T extends Number> { // 表示坐标,最高只能是Number private T x; private T y;
public T getX() { return x; }
public void setX(T x) { this.x = x; }
public T getY() { return y; }
public void setY(T y) { this.y = y; } } |
以上的泛型类型明确的指出,最大的操作父类是Number,能设置的内容只能是其子类Integer、Float等等。
package genericsdemo03; public class Math { public static void main(String args[]) { Point<Integer> p1 = new Point<Integer>(); // 设置的是Number的子类 } } |
如果此时设置了泛型是字符串的话,则会出现错误。
而且,使用泛型的上限也可以在方法上使用,例如:接收参数。
范例:传递中声明对象
class Point<T> { // 此处不设置上限,由方法设置 …… }
public class Math { public static void main(String args[]) { Point<Integer> p2 = new Point<Integer>(); fun(p2); }
public static void fun(Point<? extends Number> po) { // 表示此时可以接收Number的子类 System.out.println(po.getX()); System.out.println(po.getY()); } } |
null null |
当然,也可以用于直接声明对象:
class Point<T> { // 此处不设置上限,由方法设置 …… }
public class Math { public static void main(String args[]) { Point<? extends Number> p = null; p = new Point<Integer>(); } } |
2.4.3 泛型下限
泛型下限指的是只能设置其具体的类或者父类。
泛型下限设置格式:
声明对象: | 类名称<? super 类> 对象名称 |
定义类: | [访问权限] 类名称<泛型标识 extends 类>{} |
注意:以上的定义类存在问题,待考究!!!
范例:定义一个方法,此方法只能接收String或Object类型的泛型对象。
package genericsdemo03; public class Point<T> { private T x; private T y;
public T getX() { return x; }
public void setX(T x) { this.x = x; }
public T getY() { return y; }
public void setY(T y) { this.y = y; } } |
在方法中设置泛型的下限:
package genericsdemo03; public class Math { public static void main(String args[]) { Point<String> p1 = new Point<String>(); Point<Object> p2 = new Point<Object>(); fun(p1); fun(p2); } public static void fun(Point<? super String> po) { System.out.println(po.getX()); System.out.println(po.getY()); } } |
2.5 泛型接口
泛型不光可以在类上使用,还可以在接口中进行定义。操作的语法如下:
Interface 接口名称<泛型类型,泛型类型,……>() |
范例:定义泛型接口
package genericsdemo04;
public interface Demo<T> { // 定义泛型接口 public void print(T param); // 此抽象方法中使用了泛型类型 } |
泛型接口定义完成之后,下面就需要定义子类实现此接口,实现的方法有两种。
范例:第一种实现手段(在子类的定义上声明泛型类型)
package genericsdemo04;
public class DemoImp<T> implements Demo<T> { public void print(T param) { System.out.println("param = " + param); } } |
下面对以上的程序进行测试:
package genericsdemo04;
public class Test { public static void main(String args[]) { Demo<String> demo = new DemoImp<String>(); demo.print("hello"); } } |
param = hello |
范例:第二种实现手段(直接在接口中指定具体类型)
package genericsdemo04; public class DemoImp2 implements Demo<DemoImp2> { public void print(DemoImp2 param) { System.out.println("param = " + param); } } |
此时print()方法中只能接收DemoImp2对象实例。
package genericsdemo04; public class Test { public static void main(String args[]) { Demo demo = new DemoImp2(); demo.print(new DemoImp2()); } } |
param = genericsdemo04.DemoImp2@1ff61bcf |
2.6 泛型方法
泛型除了在类中定义之外,还可以在方法上定义,而且在方法上使用泛型,此方法所在的类不一定是泛型的操作类。
泛型方法的简单定义:
[访问权限] <泛型标识>泛型标识 方法名称([泛型标识 参数名称]) |
范例:定义一个泛型方法
package genericsdemo05; public class Demo { public <T> T print(T param) { // 定义泛型方法 return param; } } |
Demo类中的print()方法里面接收泛型的类型,而且此方法的返回值也是指定的泛型类型。下面使用以上的类型操作。
package genericsdemo05; public class Test { public static void main(String[] args) { Demo d = new Demo(); System.out.println(d.print(1)); // 如果输入1表示类型是Integer } } |
1 |
当然,也可以将此方法的返回值定义成一个泛型的数组。
范例:泛型数组的应用(重要)
public class Math { public static void main(String args[]) { Integer i[] = fun(1, 2, 3, 4, 5);// 注意此处同样不能使用基本数据类型,如int String j[] = { "A", "B", "C", "D" }; Object k[] = fun(1, 0.1, 'a', "Hello", true); // 接收各种数据类型 print(i); print(fun(j)); print(fun(k)); System.out.println("主方法输出:"); for (Integer t : i) { // 此处可以为比i大的操作父类,如Number、Object System.out.print(t + "、"); } System.out.println(); for (String t : j) { // 此处可该为Object System.out.print(t + "、"); } System.out.println(); for (Object t : k) { // 此处只能是Object System.out.print(t + "、"); } }
public static <T> T[] fun(T... param) { // 接收可变数组,此处不能写出 //(Tparam[]),否则不能接收数组i和k return param; }
public static <T> void print(T param[]) { // 此处也可以写成(T...param) for (T x : param) { // 注意与主方法输出做比较,此处只能为T System.out.print(x + "、"); } System.out.println(); } } |
1、2、3、4、5、 A、B、C、D、 1、0.1、a、Hello、true、 主方法输出: 1、2、3、4、5、 A、B、C、D、 1、0.1、a、Hello、true、 |
2.7 泛型的嵌套的设置
现在只是突出语法,具体的操作意义需要等待后面的类库之中才能够更加明白。
package genericsdemo06; public class Info<T> { private T param;
public T getParam() { return param; }
public void setParam(T param) { this.param = param; } } |
之后定义一个Person类型。
package genericsdemo06; public class Person<T> { private T info;
public T getInfo() { return info; }
public void setInfo(T info) { this.info = info; } } |
此时如果要将Info的类型设置到Person之中,那么同时即要指定Person的泛型类型,又要指定Info中的泛型类型。
package genericsdemo06; public class Test { public static void main(String[] args) { Person<Info<String>> per = new Person<Info<String>>(); per.setInfo(new Info<String>()); per.getInfo().setParam("MLDN.java"); System.out.println(per.getInfo().getParam()); } } |
MLDN.java |
以上的操作在后面将会有所应用。
2.8 泛型的操作范例
现在有如下的题目要求:
要求设计一个程序,定义一个Person类,Person类中要存放具体的信息,但是信息分为基本信息或联系方式等等,那么此时该如何设计呢?
此时最好的设计是需要定义一个表示信息的操作标准。但是此时这个标准肯定使用接口实现,但是现在在接口并不编写任何的操作。
范例:定义标识接口——信息
package genericsdemo07; public interface Info { } |
接口没有任何的操作代码,所以,此种接口在设计上称为标识接口,表示一种能力。
之后定义Person类,Person类中的信息只能由Info的子类决定,所以此时指定了上限。
范例:定义Person类
package genericsdemo07; class Person<T extends Info> { // 此处指定了上限,必须是Info接口的子类 private T info; // 此变量的类型由外部决定
public Person() {
}
public Person(T info) { setInfo(info); }
public T getInfo() { return info; }
public void setInfo(T info) { this.info = info; }
public String toString() { // 覆写Object类中的toString()方法 return this.info.toString(); } } |
以上的操作中,能设置的内容只能是Info的子类。
范例:定义第一个表示信息的类——个人基本信息,此类实现接口
package genericsdemo07; public class Basic implements Info { private String name; private int age;
public Basic() { super(); }
public Basic(String name, int age) { super(); this.name = name; this.age = age; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String toString() { return "人的信息:\n" + "\t|- 姓名:" + this.getName() + "\n" + "\t|- 年龄:"+ this.getAge(); } } |
以上只是基本信息,但是在人中还有联系方式的子类。
范例:定义第二个表示信息的类——联系方式,此类实现接口
package genericsdemo07; public class Contact implements Info { private String address; private String zipcode;
public Contact() { super(); }
public Contact(String address, String zipcode) { super(); this.address = address; this.zipcode = zipcode; }
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
public String getZipcode() { return zipcode; }
public void setZipcode(String zipcode) { this.zipcode = zipcode; }
public String toString(){ return "地址信息:\n"+ "\t|- 地址:"+ this.getAddress()+ "\n" + "\t|- 邮编:"+ this.getZipcode(); } } |
范例:设置测试类
public class Math { public static void main(String[] args) { // 将Basic类型设置为泛型类型,声明并实例化Person对象 Person<Basic> per = new Person<Basic>(); // 实例化Basic匿名对象,并将其对象同serInfo()方法传入Person对象中 per.setInfo(new Basic("张三", 30)); // 通过调用Person类中的getInfo()方法取得信息并输出 System.out.println(per.getInfo());
// 实例化Person对象,同时设置info属性的信息 Person<Contact> perc = new Person<Contact>(new Contact("高州", "525200")); // 通过调用toString()方法直接输出 System.out.println(perc); } } |
人的信息: |- 姓名:张三 |- 年龄:30 地址信息: |- 地址:高州 |- 邮编:525200 |
以上的Person中的信息属性只能是Info的子类,从而保证了操作的正确性。
注意:比较以上两种实例化对象的方法,以及输出方法。
3、总结
1、本章只是阐述了泛型的基本操作及基本语法。
2、可以通过“?”、“? extends 类”、“? super 类”指定泛型的操作界限
3、泛型如果没有设置的话,则将会进行擦除,擦除之后按照Object进行接收
4、泛型可以在接口上设置,指定泛型接口的子类需要明确的给出操作类型
4、作业 —— 宠物商店
实现一个宠物店,一个宠物商店中存在多种宠物。
定义宠物标准:Pet
package org.petshopdemo; public interface Pet { // 如果有需要则 扩展此处接口即可 public String getName(); // 得到宠物的名字
public int getAge(); // 得到宠物的年龄
public float getPrice(); // 得到宠物的价格 } |
宠物标准定义完成之后,下一步最关心的是宠物商店。
一个宠物商店要包含多种宠物。
定义宠物商店:PetShop
package org.petshopdemo; public class PetShop { private Pet pets[]; // 表示有多个宠物 private int foot; // 定义脚标
public Petshop(int len) { // 宠物的个数可以通过外部指定 if (len > 0) { // 判断输入的个数长度是否大于0 this.pets = new Pet[len]; } else { this.pets = new Pet[1];// 至少保留一个宠物 } }
public boolean add(Pet pet) {// 增加宠物,返回是否增加成功的信息 if (this.foot < this.pets.length) { // 可以增加宠物 this.pets[this.foot] = pet; // 保存宠物 this.foot++; // 修改脚标 return true; // 增加成功 } else { return false; // 增加失败 } }
// 因为查询的时候可能返回多个内容,所以还应该以数组的形式表示 public Pet[] search(String keyWord) { // 根据关键字查询 Pet[] result = null;// 声明数组,但是,大小不指定 int count = 0; // 记录有多少中宠物符合信息 for (int i = 0; i < this.pets.length; i++) { if (this.pets[i] != null) { // 表示有宠物信息 if (this.pets[i].getName().indexOf(keyWord) != -1) {// 查询到结果 count++; // 修改查询个数 } } } result = new Pet[count]; // 根据查找的个数开辟数组空间 count = 0; for (int i = 0; i < this.pets.length; i++) { if (this.pets[i] != null) { // 表示有宠物信息 if (this.pets[i].getName().indexOf(keyWord) != -1) {// 查询到结果 result[count] = this.pets[i]; // 返回查询的内容 count++; // 修改查询个数 } } } return result; } } |
定义宠物猫:
package org.petshopdemo; public class Cat implements Pet { private String name; private int age; private float price;
public Cat(String name, int age, float price) { super(); this.name = name; this.age = age; this.price = price; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public float getPrice() { return price; }
public void setPrice(float price) { this.price = price; }
public String toString() { return "宠物猫的名字:" + this.name + ",年龄:" + this.age + ",价格:" + this.price; } } |
定义宠物狗:
package org.petshopdemo; public class Dog implements Pet { private String name; private int age; private float price;
public Dog(String name, int age, float price) { super(); this.name = name; this.age = age; this.price = price; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public float getPrice() { return price; }
public void setPrice(float price) { this.price = price; }
public String toString() { return "宠物狗的名字:" + this.name + ",年龄:" + this.age + ",价格:" + this.price; } } |
建立测试类,测试类之间的关系。
package org.petshopdemo; public class TestPetShop { public static void main(String[] args) { Petshop shop = new Petshop(5); // 定义能存放5个宠物的商店 shop.add(new Cat("黑猫", 3, 89.4f)); // 增加宠物,成功 shop.add(new Cat("白猫", 2, 19.4f)); // 增加宠物,成功 shop.add(new Cat("花猫", 3, 89.4f)); // 增加宠物,成功 shop.add(new Dog("白狗", 3, 89.4f)); // 增加宠物,成功 shop.add(new Dog("黑狗", 3, 89.4f)); // 增加宠物,成功 shop.add(new Dog("猪狗", 3, 89.4f)); // 增加宠物,失败 Pet p[] = shop.search("白"); for (int x = 0; x < p.length; x++) { System.out.println(p[x]); } } } |
实际上从开发中此种模式也是经常使用到的。所以一定要理解其代码的意义,使用接口进行解耦合操作。所有类之间的关联使用接口完成,使程序具有更大的灵活性。