Java笔记之泛型(上)
来源:互联网 发布:阿里云费用提现 编辑:程序博客网 时间:2024/06/14 09:51
1.泛型入门
Java集合有个缺点——把一个对象“丢进”集合里之后,集合就会“忘记”这个对象的数据类型,当再次取出该对象时,该对象的编译类型就变成了Object类型(其运行时类型没变)。
1.1编译时不检查类型的异常
public class ListErr { public static void main(String[] args) { //创建一个只想保存字符串的List集合 List strList = new ArrayList(); strList.add("test1"); strList.add("test2"); //若把一个Integer对象“丢进”集合 //将引发ClassCastException异常 strList.add(5); strList.forEach(str -> System.out.println(((String)str).length())); }}
1.2使用泛型
从Java5之后,Java引入了“参数化类型”的概念,允许程序在创建集合时指定集合元素的类型。Java的参数化类型被称为泛型(Generic)。
使用泛型改进上一个程序:
public class GenericList { public static void main(String[] args) { //创建一个只想保存字符串的List集合 List<String> strList = new ArrayList<String>(); strList.add("test1"); strList.add("test2"); //下面代码将引起编译错误 strList.add(5); strList.forEach(str -> System.out.println(str.length())); }}
1.3Java7泛型的“菱形”语法
从Java7开始,Java允许在构造器后不需要带完整的泛型信息,只要给出一对尖括号(<>)即可,Java可以推断尖括号里应该是什么泛型信息。
public class DiamondTest { public static void main(String[] args) { //Java自动推断出ArrayList的<>里应该是String List<String> books = new ArrayList<>(); books.add("test1"); books.add("test2"); books.forEach(ele -> System.out.println(ele.length())); //Java自动推断出HashMap的<>里应该是String,List<String> Map<String,List<String>> schoolsInfo = new HashMap<>(); List<String> schools = new ArrayList<>(); schools.add("test3"); schools.add("test4"); schoolsInfo.put("hello",schools); schoolsInfo.forEach((key,value) -> System.out.println(key + "-->" + value)); }}
2.1定义泛型接口、类
包含泛型声明的类型可以在定义变量、创建对象时传入一个类型实参,从而可以动态的生成无数多个逻辑上的子类,但这种子类在物理上并不存在。
可以为任何类、接口增加泛型声明。
//定义Apple类时使用了泛型声明public class Apple<T> { //使用T类型形参定义实例变量 private T info; public Apple(){} public Apple(T info) { this.info = info; } public void setInfo(T info) { this.info = info; } public T getInfo() { return this.info; } public static void main(String[] args) { //由于传给T形参的时String,所以构造器参数只能是String Apple<String> a1 = new Apple<>("苹果"); System.out.println(a1.getInfo()); //由于传给T形参的时Double,所以构造器参数只能是Double或double Apple<Double> a2 = new Apple<>(5.55); System.out.println(a2.getInfo()); }}
注意:当创建带泛型声明的自定义类,为该类定义构造器时,构造器名还是原来的类名,不要增加泛型声明。
2.2从泛型类派生子类
当创建了带泛型声明的接口、父类之后,可以为该接口创建实现类,或从该父类派生子类,但当使用这些接口、父类时不能再包含类型形参。例如,下面代码就是错误的。
//定义类A继承Apple类,Apple类不能再跟类型形参public class A extends Apple<T>{ }
如果想从Apple类派生一个子类,由如下两种方式:
//使用Apple类时为T形参传入String类型public class A extends Apple<String>//使用Apple类时,没有为T形参传入实际的类型参数public class A extends Apple
如果从Apple<String>
类派生子类 ,则在Apple类中所有使用T类型形参的地方都将被替换成String类型,即它的子类将会继承到String getInfo()
和void setInfo(String info)
两个方法,如果子类需要重写父类的方法,就必须要注意这一点。
public class A1 extends Apple<String> { //正确重写了父类的方法,返回值 //与父类Apple<String>的返回值完全相同 public String getInfo() { return "子类" + super.getInfo(); } //下面方法时错误的,重写父类方法时返回值类型不一致 public Object getInfo() { return "子类"; }}
如果使用Apple类时没有传入实际的类型参数,Java编译器会发出泛型检查警告。
public class A2 extends Apple { //重写父类的方法 public String getInfo() { //super.getInfo()方法返回值时Object类型 //所以加toString()才返回String类型 return super.getInfo().toString(); }}
2.3并不存在泛型类
//分别创建List<String>对象和List<Integer>对象List<String> l1 = new ArrayList<>();List<Integer> l2 = newArrayList<>();System.out.println(l1.getClass() == l2.getClass());
运行上面代码将输出true
,因为不管泛型的实际类型参数是什么,它们在运行时总有同样的类。
因此在静态方法、静态初始化块或者静态变量的声明和初始化中不允许使用类型形参。
public class R<T> { //下面代码错误,不能在静态变量声明中使用类型形参 Static T info; T age; public void foo(T msg){} //下面代码错误,不能在静态方法中中使用类型形参 public static void bar(T msg){}}
由于系统中并不会真正生成泛型类,所以instanceof运算符不能使用泛型类。
3类型通配符
public void test(List<Object> c){ for(int i = 0;i < c.size() ; ++i) { System.out.println(c.get(i)); }}//创建一个List<String>对象List<String> strList = new ArrayList<>();//调用前面的方法test(strList);
上面的程序将出现编译错误,表明List<String>
对象不能被当成List<Object>
对象使用,即List<String>
类并不是List<Object>
的子类。
注意:如果Foo时Bar的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,G并不是G的子类型!
与数组进行对比,先看一下数组是如何工作的。
public class ArrayErr { public static void main(String[] args) { Integer[] ia = new Integer[5]; //可以把一个Integer[]数组赋给Number[]变量 Number[] na = ia; //下面代码编译正常,但运行时会引发ArrayStoreException异常 //因为0.5不是Integer na[0] = 0.5; }}
在Java的早期设计中,允许Integer[]
数组赋值给Number[]
变量存在缺陷,因此Java在泛型设计时进行了改进,不再允许把Integer[]
数组赋值给Number[]
变量。如下代码将会导致编译错误。
List<Integer> iList = new ArrayList<>();//下面代码导致编译错误List<Number> nList = iList;
注意:数组和泛型有所不同,假设Foo是Bar的一个子类型,那么Foo[]依然是Bar[]的子类型;但G不是G的子类型。
3.1使用类型通配符
为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号,它的元素类型可以匹配任何类型。如下所示:
public void test(List<?> c){ for(int i = 0;i < c.size() ; ++i) { System.out.println(c.get(i)); }}
现在使用任何类型的List来调用它,程序依然可以访问集合c中的元素,其类型是Object。
这种写法可以适应于任何支持泛型声明的接口和类。
但这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素加入其中。
List<?> c = new ArrayList<String>();//下面程序引起编译错误c.add(new Object());
因为程序无法确定c集合中元素的类型,所以不能向其中添加对象。
3.2设定类型通配符的上限
List
//定义一个抽象类Shapepublic abstract class Shape { public abstract void draw(Canvas c);}//定义Shape的子类Circlepublic class Circle extends Shape { public abstract void draw(Canvas c) { System.out.println("在画布" + c + "上画一个圆"); }}//定义Shape的子类Rectanglepublic class Rectangle extends Shape { public abstract void draw(Canvas c) { System.out.println("把一个矩形画在画布" + c + "上"); }}
上面定义了三个形状类。接下来定义一个Canvas类,该画布类可以画数量不等的形状。
public class Canvas { public void drawAll(List<Shape> shapes) { for(Shape s : shapes) { s.draw(this); } }}
此时List<Circle>
并不是List<Shape>
的子类型,所以将引起编译错误。改写为如下形式:
public class Canvas { public void drawAll(List<?> shapes) { for(Object obj : shapes) { Shape s = (Shape)obj; s.draw(this); } }}
这种方式虽然没错,但极为繁琐。我们需要一种泛型表示方法,可以表示所有Shape泛型List的父类。Java泛型提供了被限制的泛型通配符。
//它表示所有Shape泛型List的父类List<? extends Shape>
有了这种被限制的泛型通配符,我们就可以把上面程序改写为如下形式:
public class Canvas { public void drawAll(List<?extends Shape> shapes) { for(Shape s : shapes) { s.draw(this); } }}
此处的问号代表一个未知的类型,但是此处的这个未知类型一定是Shape的子类型(也可以是Shape本身),因此把Shape称为这个通配符的上限。
类似地,由于程序无法确定这个受限制的通配符的具体类型,所以不能把Shape对象或其子类的对象加入这个泛型集合中。
public void addRectangle(List<? extends Shape> shapes){ //下面代码引起编译错误 shapes.add(0,new Rectangle());}
3.3设定类型形参的上线
Java泛型不仅允许在使用通配符形参时设定上限,而且可以在定义类型形参时设定上限,用于表示传给该类型形参的实际类型要么是该上限类型,要么是该上限类型的子类。
public class Apple<T extends Number> { T col; public static void main(String[] args) { Apple<Integer> ai = new Apple<>(); Apple<Double> ad = new Apple<>(); //因为String不是Number的子类型,所以引起编译错误 Apple<String> as = new Apple<>(); }}
在一种更极端的情况下,程序需要为类型形参设定多个上限(最多一个父类上限,可以有多个接口上限),表明该类型形参必须是其父类的子类(父类本身也行),并且实现多个上限接口。
public class Apple<T extends Number & java.io.Serializable>{ ...}
所有的接口上限必须位于类上限之后。
- Java笔记之泛型(上)
- java虚拟机学习笔记之垃圾收集(上)
- Java学习笔记之泛型(二):在方法上自定义泛型
- Java学习笔记之泛型(五):泛型的上下限
- java笔记之泛型
- java基础巩固之泛型(上)
- Java编程思想之泛型(上)
- java学习笔记(上)
- 《Effective Java》笔记(上)
- (35)21.3.6 在其它对象上同步---Java编程思想之并发笔记
- android开发笔记之java的reflect的理解(在android平台上)
- 学习笔记之java.io包中的字符流(上)—— Reader和Writer
- 《浪潮之巅(上)》阅读笔记
- Java笔记之泛型接口
- java学习笔记之泛型
- JAVA学习笔记之泛型接口
- (6)Java笔记6之泛型
- Java编程笔记之泛型
- mysql主从同步故障解决
- mysql 使用set names 解决乱码问题的原理 本人亲测
- 自旋锁和互斥锁
- js实现在线查看和编辑office文件
- LEACH算法无线传感器网络路由协议
- Java笔记之泛型(上)
- C#笔记(13)解决:任务栏图标不消失
- vsftpd配置PASV模式下指定端口范围,以便配置防火墙
- 数字字符串和数字互转
- 二叉树深度优先遍历和广度优先遍历
- 反射_程序集_版本转换的两种方法(.Net Framework的版本)
- MyBatis的foreach语句详解
- 使用SSH连接Linux上的Oracle
- GPUImage的125种滤镜种类