您的位置:首页 > 编程语言 > Java开发

JavaSE——泛型

2019-06-11 23:22 309 查看

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: