JavaSE——泛型
1.泛型问题引出
假设需要你定义一个描述坐标的程序类Point,需要提供两个属性x、y。对于这两个属性的内容可能有如下选择:
1.x = 10、y = 20 ;
2. x = 10.1、y = 20.1 ;
3. x = 东经80度、y = 北纬20度
那么现在首先要解决的问题就是Point类中的x、y的属性类型问题,此时需要保存的有int、double、String,所以在java中只有一种类型可以保存所有类型,那就是Object型,实现如下:
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; } } public class Test{ public static void main(String[] args) { //设置整型坐标 // 设置数据 Point p = new Point() ; p.setX(10); // 自动装箱并且向上转型为Object p.setY(20); // 取出数据 int x = (Integer) p.getX() ; // 强制向下转型为Integer并且自动拆箱 int y = (Integer) p.getY() ; System.out.println("x = " +x+",y = "+y); //设置字符串坐标 // 设置数据 Point p1 = new Point() ; p1.setX("东经80度"); p1.setY("北纬20度"); // 取出数据 String x1 = (String) p1.getX() ; String y1 = (String) p1.getY() ; System.out.println("x1 = " +x1+",y1 = "+y1); } } //输出 //x = 10,y = 20 //x1 = 东经80度,y1 = 北纬20度
以上代码看似解决了问题,实际上并没有,如下:
由于设置方的错误将内容设置成了double和String,于是在执行的时候会出现ClassCaseException,该异常是指两个没有关系的对象进行强转的异常,所以说明,向下转型是不安全操作,会带来隐患
2.泛型类的基本使用
泛型:指的就是
在类定义的时候并不会设置类中的属性或方法中的参数的具体类型,而是在类使用时再进行定义,如果要想进行这种泛型的操作,就必须做一个类型标记的声明,语法如下:
class MyClass<T> { T value1; }
尖括号
<> 中的 T 被称作是类型参数,用于指代任何类型。实际上这个T你可以任意写,但出于规范,还是建议用单个大写字母来代表类型参数,常见的用法如下:
- T代表一般的任何类
- E代表 Element 的意思,或者 Exception 异常的意思
- K代表 Key 的意思
- V代表 Value 的意思,通常与 K 一起配合使用
- S代表 Subtype 的意思
使用泛型类:
MyClass<String> myClass1 = new MyClass<String>(); MyClass<Integer> myClass2 = new MyClass<Integer>();
!!!注意:
泛型只能接受类,所有的基本数据类型必须使用包装类,另外,
泛型类可以接收多个类型参数,如下:
class MyClass<T,E> { } public class Test { public static void main(String[] args) { MyClass<String,Integer> myClass1 = new MyClass<String,Integer>(); } }
使用泛型实现上面的Point类:
class Point <T> { // 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; } } public class Test { public static void main(String[] args) { // 设置数据 Point<String> p = new Point<>(); p.setX("东经80度"); p.setY("北纬20度"); // 取出数据 // 避免了向下转型 String x = p.getX() ; String y = p.getY() ; System.out.println("x = " +x+",y = "+y); } } //输出 //x = 东经80度,y = 北纬20度
!!!注意:引入泛型后,如果
明确设置了类型,则为设置类型;如果没有设置类型,则默认为Object类型。
3.泛型方法
泛型不仅可以用于定义类,还可以单独来定义方法,如下:
class MyClass{ public <T> void testMethod(T t) { System.out.println(t); } }
泛型方法与泛型类稍有不同的地方是,类型参数也就是尖括号那一部分是
写在返回值前面的,当然,声明的类型参数,其实
也是可以当作返回值的类型的,如下:
class MyClass{ public <T> T testMethod(T t) { return t; } }
泛型方法与泛型类可以共存,如下:
class MyClass<T>{ public void testMethod1(T t) { System.out.println(t); } public <T> T testMethod2(T t) { return t; } } public class Test { public static void main(String[] args) { MyClass<String> myClass = new MyClass<>(); myClass.testMethod1("hello 泛型类"); Integer i = myClass.testMethod2(100); System.out.println(i); } }
上面代码中,MyClass 是泛型类,testMethod1 是泛型类中的普通方法,而 testMethod2 是一个泛型方法。而泛型类中的类型参数与泛型方法中的类型参数是
没有相应的联系的,
泛型方法始终以自己定义的类型参数为准。泛型类的实际类型参数是 String,而传递给泛型方法的类型参数是Integer,两者不相干。实际开发中,如果在一个泛型类中存在泛型方法,那么两者的类型参数最好不要同名,比如MyClass代码可以更改为如下:
class MyClass<T>{ public void testMethod1(T t) { System.out.println(t); } public <E> E testMethod2(E e) { return e; } }
综上:
当泛型类与泛型方法共存时,泛型方法中的类型参数与反省类的参数无关,泛型方法始终以自己的类型参数为准,如果在一个泛型类中存在泛型方法,那么两者的类型参数不要同名
4.通配符
在程序类中追加了泛型的定义后,避免了ClassCastException的问题,但是又会产生新的情况:参数的统一问题,如下:
class Message<T> { private T message ; public T getMessage() { return message; } public void setMessage(T message) { this.message = message; } } public class Test { public static void main(String[] args) { Message<String> message = new Message() ; message.setMessage("我是小靓仔"); fun(message); } public static void fun(Message<String> temp){ System.out.println(temp.getMessage()); } } //输出 //我是小靓仔
以上程序会带来新的问题:
public class Test { public static void main(String[] args) { Message<Integer> message = new Message() ; message.setMessage(99); fun(message); // 出现错误,只能接收String } public static void fun(Message<String> temp){ System.out.println(temp.getMessage()); } }
我们需要可以接收所有的泛型类型,但是又不能够让用户随意修改,种情况就需要
使用通配符"?",如下:
public class TestDemo { public static void main(String[] args) { Message<Integer> message = new Message() ; message.setMessage(55); fun(message); } public static void fun(Message<?> temp){ System.out.println(temp.getMessage()); } }
在"?"基础上又产生了两个子通配符:
? extends 类
:设置泛型上限
? extends Number,表示只能够设置Number及其子类? super 类
:设置泛型下限
? super String,表示只能设置String及其父类
class Message<T extends Number> { // 设置泛型上限 private T message ; public T getMessage() { return message; } public void setMessage(T message) { this.message = message; } } public class Test { public static void main(String[] args) { Message<Integer> message = new Message() ; message.setMessage(55); fun(message); } // 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改 public static void fun(Message<? extends Number> temp){ //temp.setMessage(100); 仍然无法修改! System.out.println(temp.getMessage()); } }
class Message<T> { //设置泛型下限 private T message ; public T getMessage() { return message; } public void setMessage(T message) { this.message = message; } } public class Test { public static void main(String[] args) { Message<String> message = new Message() ; message.setMessage("Hello World"); fun(message); } public static void fun(Message<? super String> temp){ //此时可以修改 temp.setMessage("aa!"); System.out.println(temp.getMessage()); } } //输出 //aa!
方法参数设置泛型上限依旧只能取得类中属性值而无法设定,因为父类不一定能向下转型为子类,但是设置泛型下限不仅可以取得类中属性值,还可以设置属性值,因为子类可以天然向上转型为父类
5.泛型接口
泛型除了可以定义在类中,也可以定义在接口里面,这种情况我们称之为
泛型接口,如下:
interface IMessage<T> { // 在接口上定义了泛型 public void print(T t) ; }
对于这个接口的实现子类有两种做法:
- 在子类定义时
继续使用泛型
,具体使用时给出类型 - 在子类实现接口的时候
明确给出具体类型
//子类定义时继续使用泛型 interface IMessage<T> { public void print(T t) ; } class MessageImpl<T> implements IMessage<T> { @Override public void print(T t) { System.out.println(t); } } public class Test { public static void main(String[] args) { IMessage<String> msg = new MessageImpl() ; msg.print("Hello World"); } }
//在子类实现接口的时候明确给出具体类型 interface IMessage<T> { public void print(T t) ; } class MessageImpl implements IMessage<String> { @Override public void print(String t) { System.out.println(t); } } public class Test { public static void main(String[] args) { IMessage<String> msg = new MessageImpl() ; msg.print("Hello World"); } }
6.类型擦除
泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,这就是类型擦除,换句话说,
泛型类与普通类在JVM中没有差别。
泛型类的类型参数如果没有指定类型上限,就会被擦除成Object类,如果指定上限,擦除为相应类型上限
class MyClass<T>{ private T message; public T getMessage() { return message; } public void setMessage(T message) { this.message = message; } public void testMethod1(T t) { System.out.println(t); } } public class Test { public static void main(String[] args) { MyClass<String> myClass1 = new MyClass<>(); MyClass<Integer> myClass2 = new MyClass<>(); System.out.println(myClass1.getClass() == myClass2.getClass()); } } //输出 //true
打印的结果为 true 是因为 MyClass 和 MyClass 在 jvm 中的 Class 都是 MyClass.class
class MyClass<T,E>{ private T message; private E text; public E getText() { return text; } public void setText(E text) { this.text = text; } public T getMessage() { return message; } public void setMessage(T message) { this.message = message; } public void testMethod1(T t) { System.out.println(t); } } public class Test { public static void main(String[] args) { MyClass<String,Integer> myClass1 = new MyClass<>(); Class cls = myClass1.getClass(); Field[] fields = cls.getDeclaredFields(); for (Field field : fields) { System.out.println(field.getType()); } } } //输出 //class java.lang.Object //class java.lang.Object
- javaSE集合与泛型练习题
- 【JavaSE系列-基础篇6】——泛型原始类型
- JavaSE 详细了解泛型中的边界问题
- JavaSE基础:学习泛型
- JavaSE_8系列博客——Java语言的特性(六)--泛型(2)--何时何地使用泛型?
- JAVASE之泛型
- JavaSE笔记之<泛型再理解(简化版)>
- Javase之集合泛型
- JavaSE-泛型
- JavaSE_8系列博客——Java语言的特性(六)--泛型(3)--泛型和类型通配符
- JavaSE-泛型的使用练习
- 【JavaSE系列-基础篇6】——数组,集合,泛型宏观把控
- 【JavaSE系列-基础篇6】——泛型方法
- JavaSE入门学习40:Java集合框架之泛型
- JavaSE_8系列博客——Java语言的特性(六)--泛型(1)--宏观把控
- JavaSE_8系列博客——Java语言的特性(六)--泛型(4)--Java中泛型实现的原理
- 黑马程序员----JAVASE高级部分之泛型
- 【JavaSE学习笔记】泛型,jdk5之后新特性
- javaSE(九)之泛型(Generics)
- JavaSE_8系列博客——Java语言的特性(六)--泛型(5)--泛型的使用