泛型

1.引入

情景模式描述,假设完成一个学生的成绩的情况:

  • 整数: math=80,english=70
  • 小数: math=85.6,englisth=77.8
  • 字符串: math="66分",english="90.5分"

那么我们应该如何处理呢?我们需要设计的成员变量可以接收不同的数据类型,我们第一想到的是Object类型

定义Student类,使用Object类型

package com.shxt.demo01;

public class Student {

    private Object math;
private Object english; public Student(Object math, Object english) {
this.math = math;
this.english = english;
} public Object getMath() {
return math;
} public void setMath(Object math) {
this.math = math;
} public Object getEnglish() {
return english;
} public void setEnglish(Object english) {
this.english = english;
}
}

示例1:设置整型, int → 自动装箱(Integer) → 对象上转型Object

package com.shxt.demo01;

public class Demo01 {
public static void main(String[] args) {
Student student = new Student(80,70); //实例化对象并且对数据初始化
//请注意这种强制只有在JDK7以后才能使用
int math = (int) student.getMath();
//JDK7以下的标准写法
int english = (Integer) student.getEnglish();// Object -> Integer->自动拆箱 System.out.println("数学成绩:"+math+",英语成绩:"+english);
}
}

示例2:设置小数, double → 自动装箱(Double) → 对象上转型Object

package com.shxt.demo01;

public class Demo02 {
public static void main(String[] args) {
Student student = new Student(80.7,77.8); //实例化对象并且对数据初始化
//请注意这种强制只有在JDK7以后才能使用
double math = (double) student.getMath();
//JDK7以下的标准写法
double english = (Double) student.getEnglish();// Object -> Double->自动拆箱 System.out.println("数学成绩:"+math+",英语成绩:"+english);
}
}

示例3:设置字符串, 字符串 → 对象上转型Object

package com.shxt.demo01;

public class Demo03 {
public static void main(String[] args) {
Student student = new Student("66分","90.5分"); //实例化对象并且对数据初始化
String math = (String) student.getMath();
String english = (String) student.getEnglish();// Object -> String System.out.println("数学成绩:"+math+",英语成绩:"+english);
}
}

代码分析:

上述的三个测试,我们感觉程序应该没有什么大的问题了,真的没有问题了吗? 请关注下面的测试


示例4:设置整数和字符串,并且转换为字符串的操作过程

package com.shxt.demo01;

public class Demo04 {
public static void main(String[] args) {
Student student = new Student(80,"66分"); //实例化对象并且对数据初始化 String math = (String) student.getMath();
String english = (String) student.getEnglish(); System.out.println("数学成绩:"+math+",英语成绩:"+english);
}
}

代码分析: 类型转换错误

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

​ at com.shxt.demo01.Demo04.main(Demo04.java:7

说明我们上述的代码是存在安全隐患的,数据没有达到统一,我们可以通过判断改进代码,这个不符合我们的要求

判断数据的类型instanceof

package com.shxt.demo01;

public class Demo05 {
public static void main(String[] args) {
Student student = new Student(80,"66分"); //实例化对象并且对数据初始化 String math = null;
if(student.getMath() instanceof Integer){
math = String.valueOf(student.getMath());
} String english = (String) student.getEnglish(); System.out.println("数学成绩:"+math+",英语成绩:"+english);
}
}

对数据这样繁琐的判断我们是不想看到的,我们可以通过泛型解决问题

2.初识泛型

  • 概念:泛型就是参数化类型,使用广泛的类型
  • 起因:数据类型不明确
    • 装入数据的类型都被当作Object类型,从而"丢失"了自己实际的类型
    • 获取数据时往往需要转型,效率低,容易产生错误
  • 作用:
    • 安全: 在编译的时候检查数据类型的安全
    • 省心: 所有的强制转换都是自动和隐式的,提高代码的重用率

如何定义泛型?在定义类使用泛型

  • 定义格式,使用<>符号

    class 类名<字母列表>{
    修改符 字母 属性 ;
    修饰符 构造函数(字母 参数名){ }
    修饰符 返回类型 方法(字母 参数名){ }
    }
  • 修改Student类如下

    package com.shxt.demo02;
    
    public class Student<T> {
    private T math;
    private T english; public Student(T math, T english) {
    this.math = math;
    this.english = english;
    } public T getMath() {
    return math;
    } public void setMath(T math) {
    this.math = math;
    } public T getEnglish() {
    return english;
    } public void setEnglish(T english) {
    this.english = english;
    }
    }
  • 泛型中常见的字母列表说明

    • T → Type表示类型
    • K,V → 代表键值中的Key和value
    • E → 代表Element
    • N → 代表Number数字
    • ? → 表示不确定性
  • 泛型的使用规则

    • 不能使用静态属性和静态方法上
    • 使用的时候需要指定数据类型
      • 编译时会检查数据的类型
      • 获取数据不再需要强制类型转换
    • 泛型使用是不能指定基本数据类型,只能使用起对应的包装类

示例1:设置统一的数据Float类型

package com.shxt.demo02;

public class Demo01 {
public static void main(String[] args) {
Student<Float> student = new Student<Float>(80.7F,77.8F);
//获取数据不再需要强制类型转换,完成了数据的统一
Float math = student.getMath();
Float english = student.getEnglish(); System.out.println("数学成绩:"+math+",英语成绩:"+english); }
}

3.泛型的使用

(1) 泛型类

对于学生的分数,我们可能需要多样的数据类型,那么我们可以设置多个泛型,代码修改属性

package com.shxt.demo03;

public class Student<T1,T2> {
private T1 math;
private T2 english; public Student(T1 math, T2 english) {
this.math = math;
this.english = english;
} public T1 getMath() {
return math;
} public void setMath(T1 math) {
this.math = math;
} public T2 getEnglish() {
return english;
} public void setEnglish(T2 english) {
this.english = english;
}
}

示例1:设置整型和字符串

package com.shxt.demo03;

public class Demo01 {
public static void main(String[] args) {
Student<Integer,String> student = new Student<Integer,String>(80,"99分"); int math = student.getMath();
String english = student.getEnglish(); System.out.println("数学成绩:"+math+",英语成绩:"+english);
}
}

(2) 泛型接口

使用泛型定义接口我们称之为泛型接口,因为接口中只能有抽象方法和静态的公共常量(不能使用泛型修饰)

interface Info<T>{        	// 在接口上定义泛型
public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
public void setVar(T x);
}

####A.非泛型接口

但是在使用的时候,就出现问题了,我们先看看下面这个使用方法:

class InfoImpl implements Info<String>{   	// 定义泛型接口的子类
private String var ; // 定义属性
public InfoImpl(String var){ // 通过构造方法设置属性内容
this.setVar(var) ;
}
@Override
public void setVar(String var){
this.var = var ;
}
@Override
public String getVar(){
return this.var ;
}
} public class Demo01{
public void main(String arsg[]){
InfoImpl i = new InfoImpl("hanpang");
System.out.println(i.getVar()) ;
}
};

代码分析:

先看InfoImpl的定义:

class InfoImpl implements Info<String>{
…………
}
要清楚的一点是InfoImpl不是一个泛型类!因为他类名后没有<T>!

####B.泛型接口

在非泛型接口中,我们在类中直接把Info<T>接口给填充好了,但我们的类,是可以构造成泛型类的,那我们利用泛型类来构造填充泛型接口会是怎样呢?

interface Info<T>{        // 在接口上定义泛型
public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
public void setVar(T var);
}
class InfoImpl<T> implements Info<T>{ // 定义泛型接口的子类
private T var ; // 定义属性
public InfoImpl(T var){ // 通过构造方法设置属性内容
this.setVar(var) ;
}
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
}
public class Demo01{
public static void main(String arsg[]){
InfoImpl<String> i = new InfoImpl<String>("harvic");
System.out.println(i.getVar()) ;
}
};

代码分析:

在这个类中,我们构造了一个泛型类InfoImpl,然后把泛型变量T传给了Info,这说明接口和泛型类使用的都是同一个泛型变量。然后在使用时,就是构造一个泛型类的实例的过程,使用过程也不变。

使用泛型类来继承泛型接口的作用就是让用户来定义接口所使用的变量类型,而不是像方法一那样,在类中写死。

那我们稍微加深点难度,构造一个多个泛型变量的类,并继承自Info接口:

class InfoImpl<T,K,U> implements Info<U>{   // 定义泛型接口的子类
private U var ;
private T x;
private K y;
public InfoImpl(U var){ // 通过构造方法设置属性内容
this.setVar(var) ;
}
public void setVar(U var){
this.var = var ;
}
public U getVar(){
return this.var ;
}
}

在这个例子中,我们在泛型类中定义三个泛型变量T,K,U并且把第三个泛型变量U用来填充接口Info。所以在这个例子中Info所使用的类型就是由U来决定的。 使用时是这样的:泛型类的基本用法,不再多讲,代码如下:

public class Demo01{
public void main(String arsg[]){
InfoImpl<Integer,Double,String> i = new InfoImpl<Integer,Double,String>("harvic");
System.out.println(i.getVar()) ;
}
}

###(3) 泛型方法

只能访问对象的信息,不能修改对象的信息

讲解了类和接口的泛型使用,下面我们再说说,怎么单独在一个函数里使用泛型。比如我们在新建一个普通的类StaticFans,然后在其中定义了两个泛型函数:

package com.shxt.demo03;

public class StaticFans {
//静态函数
public static <T> void staticMethod(T a){
System.out.println("静态方法: "+a.toString());
}
//普通函数
public <T> void otherMethod(T a){
System.out.println("普通函数,无返回值: "+a.toString());
} public <T> T returnOtherMethod(T a){
System.out.println("返回泛型类型:"+a);
return a;
} }

测试代码:

public class Demo01 {
public static void main(String[] args) {
StaticFans.staticMethod(100);
StaticFans.<String>staticMethod("悟空"); //常规方法
StaticFans sf = new StaticFans();
sf.otherMethod(999F);
sf.<Double>otherMethod(100.1); //返回值中存在泛型
sf.returnOtherMethod("八戒");
sf.<Integer>returnOtherMethod(999); }
}

代码说明:

方法一,隐式传递了T的类型,与上面一样,不建议这么做。 方法二,显示将T赋值为Integer类型,这样OtherMethod(T a)传递过来的参数如果不是Integer那么编译器就会报错。

(4) 泛型数组

在写程序时,大家可能会遇到类似String[] list = new String[8];的需求,这里可以定义String数组,当然我们也可以定义泛型数组,泛型数组的定义方法为 T[],与String[]是一致的,下面看看用法:

  • 没有泛型数组,不能创建泛型数组
  • 可以只有声明,可以使用 ?
//定义
public static <T> T[] fun1(T...arg){ // 接收可变参数
return arg ; // 返回泛型数组
}
//使用
public static void main(String args[]){
Integer i[] = fun1(1,2,3,4,5,6) ;
Integer[] result = fun1(i) ;
}

4.泛型的擦除规则

我们上面的讲述过程中可以看出来可以使用子类或者实现类实现泛型,那么它们之间需要遵循一定的使用规则:

  • 子类与父类(接口)一样使用泛型
  • 子类指定具体的类型
  • 子类与父类(接口)同时擦除泛型类型
  • 子类使用泛型,而父类(接口)擦除泛型类型
  • 注意错误: 不能子类擦除,而父类(接口)使用泛型

擦除后统一使用Object对象泛型

继承演示规则过程

package com.shxt.demo04;

/**
* 父类为泛型类
* 1.属性
* 2.方法
*
* 1.要么同时擦除,要么子类大于等于父类的类型(泛型的个数)
* 2.不能子类擦除,父类泛型
* A.属性类型
* 父类随父类而定
* 子类随子类而定
* B.方法重写:
* 随父类而定
*/
public abstract class Father<T> {
protected T name;
public abstract void test01(T t);
} /**
* 子类声明时指定了具体类型Father<String>
* 属性类型为具体类型
* 方法类型为具体类型
*/
class Child01 extends Father<String>{
String name; @Override
public void test01(String s) { }
} /**
* 子类为泛型类,需要跟父类保持一致
* 规则可以大于等于父类的类型
*/
class Child02<T> extends Father<T>{
T name;
@Override
public void test01(T t) { }
} /**
* 子类为泛型类,父类不指定类型,
* 这个就是泛型擦除,使用Object替换
* @param <T>
*/
class Child03<T> extends Father{
T name;
@Override
public void test01(Object o) { }
} /**
* 子类与父类同时擦除
*/
class Child04 extends Father{
String name;
@Override
public void test01(Object o) { }
} /**
* 错误:子类擦除,父类使用泛型
*/
class Child05 extends Father<T>{
String name;
@Override
public void test01(T o) { }
}

接口演示规则过程

package com.shxt.demo04;

public interface Comparable<T> {
void compare(T t);
} //声明子类,指定具体类型
class Comp00 implements Comparable<String>{
@Override
public void compare(String s) { }
} //子类与父类同时擦除,使用Object
class Comp01 implements Comparable{
@Override
public void compare(Object o) { }
} //子类泛型,父类擦除
class Comp02<T> implements Comparable{
@Override
public void compare(Object o) { }
} //子类泛型>=父类泛型
class Comp03<T,U> implements Comparable<T>{
@Override
public void compare(T t) { }
} //错误代码,父类泛型,子类擦除

5.通配符

通配符:? extends super

  • ?类型不定,使用时确定类型
  • 可以用在声明类型以及声明方法参数上,不能用在声明类上
  • ? 可以接收泛型的任意类型,只能接收和查看,不能修改
  • ? extends 泛型上限 <=
  • ? super 泛型下限 >=

###(1) 匹配任意类型的通配符

示例1:使用泛型传递数据

package com.shxt.demo05;

public class Demo01 {
public static void main(String[] args) {
Student<String> student = new Student<String>();//指定String为泛型
student.setName("胖先森"); //设置数据
//传递数据
test01(student); // 错误,无法传递数据
} public static void test01(Student<Object> temp){// 此处可以接收 Object 泛型类型的Student对象
System.out.println("内容:"+temp);
} }
class Student<T>{
private T name; public T getName() {
return name;
} public void setName(T name) {
this.name = name;
} @Override
public String toString() {
return "Student{" +
"name=" + name +
'}';
}
}

程序编译错误:

Error:(8, 16) java: 不兼容的类型: com.shxt.demo05.Student<java.lang.String>无法转换为com.shxt.demo05.Student<java.lang.Object>

代码分析:

程序中尽管String是Object类的子类,但是在进行引用数据传递时也是同样无法进行操作,如果此时想让程序正常执行,可以将test01()方法中的定义的Student修改为Student,即不指定泛型

代码修改,编译通过

public class Demo01 {
public static void main(String[] args) {
Student<String> student = new Student<String>();//指定String为泛型
student.setName("胖先森"); //设置数据
//传递数据
test01(student); // 错误,无法传递数据
} public static void test01(Student temp){// 此处可以接收Student对象
System.out.println("内容:"+temp);
} }

代码分析:

程序编译时不会出现任何的错误,也可以正常使用,但是编写test01()方法时Student中并没有指定任何的泛型类型,这样做有一些不妥当,所以为了解决这个问题,Java中引入了通配符"?",表示可以接收此类型的任意泛型对象(不能使用在定义类上)

使用通配符"?"修改代码

public class Demo01 {
public static void main(String[] args) {
Student<String> student = new Student<String>();//指定String为泛型
student.setName("胖先森"); //设置数据
//传递数据
test01(student);
} public static void test01(Student<?> temp){// 此处可以接收Student对象
System.out.println("内容:"+temp);
} }

(2) 受限泛型

在引用传递中,在泛型操作中也可以设置一个泛型对象的范围上限和范围下限.

  • 上限通配符:使用extends关键字,表示这个类型必须是继承某个类或者实现某个接口,也可以是这个类或接口本身

    • 声明对象格式:

      类名称<? extends 类> 对象名称
    • 定义类格式:

      [访问权限] 类名称<泛型标识 extends 类>{}

  • 下限通配符:使用super关键字,表示这个类型必须是是某个类的父类或者是某个接口的父接口,也可以是这个类或接口本身

    • 声明对象格式:

      类名称<? super 类> 对象名称
    • 定义类格式:

      [访问权限] 类名称<泛型标识 super 类>{}

上限通配符

假设一个方法中只能接收的泛型对象是数组(Byte/Short/Integer/Long/Float/Double)类型,此时在定义方法参数接收对象时,就必须指定泛型的上限,之前将包装类的时候我们说过Number类是数字的父类

示例1:方法定义,只能接收泛型为Number或者Number类型的子类

package com.shxt.demo06;

public class Demo01 {
public static void main(String[] args) {
Student<Integer> s1 = new Student<Integer>(); //声明Integer的泛型对象
s1.setName(999); // 设置整数,自动装箱
test01(s1); // 继承了Number类,符合规范 Student<Float> s2 = new Student<Float>(); //声明Float的泛型对象
s2.setName(888.99F);// 设置小数,自动装箱
test01(s2);// 继承了Number类,符合规范 Student<Boolean> s3 = new Student<Boolean>();//声明Boolean的泛型对象
s3.setName(true);// 设置布尔,自动装箱
test01(s3);//继承了Object,没有继承Number,错误!错误!错误! Student<String> s4 = new Student<String>();//声明String的泛型对象
s4.setName("悟空");
test01(s4);//继承了Object,没有继承Number,错误!错误!错误! }
// 接收student对象,范围的上限设置为Number,只能接收数字类型
public static void test01(Student<? extends Number> temp){
System.out.println("temp=>"+temp);
} } class Student<T>{
private T name; public T getName() {
return name;
} public void setName(T name) {
this.name = name;
} @Override
public String toString() {
return "Student{" +
"name=" + name +
'}';
}
}

示例2:类定义,只能接收泛型为Number或者Number类型的子类

package com.shxt.demo06;

public class Demo01 {
public static void main(String[] args) {
Student<Integer> s1 = new Student<Integer>(); //声明Integer的泛型对象
s1.setName(999); // 设置整数,自动装箱
test01(s1); // 继承了Number类,符合规范 Student<Float> s2 = new Student<Float>(); //声明Float的泛型对象
s2.setName(888.99F);// 设置小数,自动装箱
test01(s2);// 继承了Number类,符合规范 Student<Boolean> s3 = new Student<Boolean>();//Boolean不是Number的子类,无法声明 Student<String> s4 = new Student<String>();//String不是Number的子类,无法声明 }
// 接收student对象,范围的上限设置为Number,只能接收数字类型
public static void test01(Student<?> temp){
System.out.println("temp=>"+temp);
} } class Student<T extends Number>{ // 泛型只能是数字类型
private T name; // 此变量的类型由外部决定
// 返回值的类型由外部指定
public T getName() {
return name;
}
// 设置的类型由外部决定
public void setName(T name) {
this.name = name;
} @Override
public String toString() {
return "Student{" +
"name=" + name +
'}';
}
}

下限通配符

package com.shxt.demo06;

public class Demo01 {
public static void main(String[] args) {
Student<Integer> s1 = new Student<Integer>(); //声明Integer的泛型对象
s1.setName(999); // 设置整数,自动装箱
test01(s1); // 不是String的父类,错误 Student<Boolean> s2 = new Student<Boolean>();//声明Boolean的泛型对象
s2.setName(true);
test01(s2); // 不是String的父类,错误 Student<String> s3 = new Student<String>();
s3.setName("悟空");
test01(s3); // 满足下限的范围 Student<Object> s4 = new Student<Object>();
s4.setName("悟空");
test01(s4); // 满足下限的范围 }
// 接收student对象,范围的下限设置为String,只能接收String或者Object
public static void test01(Student<? super String> temp){
System.out.println("temp=>"+temp);
} } class Student<T>{ // 泛型只能是数字类型
private T name; // 此变量的类型由外部决定
// 返回值的类型由外部指定
public T getName() {
return name;
}
// 设置的类型由外部决定
public void setName(T name) {
this.name = name;
} @Override
public String toString() {
return "Student{" +
"name=" + name +
'}';
}
}

JavaSE基础:泛型的更多相关文章

  1. 基础1 JavaSe基础

    JavaSe基础 1. 九种基本数据类型的大小,以及他们的封装类 boolean 无明确指定 Boolean char 16bits Character byte 8bits Byte short 1 ...

  2. JavaSE基础:集合类

    JavaSE基础:集合类 简单认识类集 我们学习的是面向对象语言,而面向对象语言对事物的描述是通过对象体现的,为了方便对多个对象进行操作,我们就必须把这多个对象进行存储. 而要向存储多个对象,就不能是 ...

  3. javaSE基础07

    javaSE基础07 一.static静态修饰符 用了static修饰的变量就会变成共享的属性,只会初始化一次,在内存中只存在一个,并且每个对象都可以访问,存放在方法区(数据共享区) 1.1 stat ...

  4. javaSE基础06

    javaSE基础06 一.匿名对象 没有名字的对象,叫做匿名对象. 1.2匿名对象的使用注意点: 1.我们一般不会用匿名对象给属性赋值的,无法获取属性值(现阶段只能设置和拿到一个属性值.只能调用一次方 ...

  5. javaSE基础05

    javaSE基础05:面向对象 一.数组 数组的内存管理 : 一块连续的空间来存储元素. Int [ ] arr = new int[ ]; 创建一个int类型的数组,arr只是一个变量,只是数组的一 ...

  6. javaSE基础04

    javaSE基础04 一.三木运算符 <表达式1> ? <表达式2> : <表达式3> "?"运算符的含义是: 先求表达式1的值, 如果为真, ...

  7. javaSE基础03

    javaSE基础03 生活中常见的进制:十进制(0-9).星期(七进制(0-6)).时间(十二进制(0-11)).二十四进制(0-23) 进制之间的转换: 十进制转为二进制: 将十进制除以2,直到商为 ...

  8. javaSE基础02

    javaSE基础02 一.javac命令和java命令做什么事情? javac:负责编译,当执行javac时,会启动java的编译程序,对指定扩展名的.java文件进行编译,生成了jvm可以识别的字节 ...

  9. JavaSE基础01

    JavaSE基础篇01 ------从今天开始,我就学习正式java了,O(∩_∩)O哈哈~,请大家多指教哦 一.Windows常见的dos命令 操作dos命令: win7 --->开始 --- ...

随机推荐

  1. sqli-labs(38)

    0X01 ?id=' and 1=1%23 正确 ?id=1' and 1=2%23 错误 存在注入 0x1 堆叠注入讲解 (1)前言 国内有的称为堆查询注入,也有称之为堆叠注入.个人认为称之为堆叠注 ...

  2. 数据重塑图解—Pivot, Pivot-Table, Stack and Unstack

    Pivot pivot函数用于创建一个新的派生表,该函数有三个参数:index, columns和values.你需要在原始表中指定这三个参数所对定的列名,接下来pivot函数会创建一个新的表格,其中 ...

  3. VS2015 ASP.NET MVC5 EntityFramework6 Oracle 环境篇

    //来源:https://www.cnblogs.com/lauer0246/articles/9576940.html Asp.Net MVC EF各版本区别 2009年發行ASP.NET MVC ...

  4. IDEA 无法自动导入相关Maven jar包

    仔细看看项目右边有个很骚的"Maven Projects"按钮,点击一下 再点击这个刷新按钮,现在知道技术为何物了吗?

  5. Zookeeper(六)服务器

    Zookeeper(六)服务器 zkServer.cmd中声明 首先启动QuorumPeerMain set ZOOMAIN=org.apache.zookeeper.server.quorum.Qu ...

  6. 【Spark机器学习速成宝典】模型篇03线性回归【LR】(Python版)

    目录 线性回归原理 线性回归代码(Spark Python) 线性回归原理 详见博文:http://www.cnblogs.com/itmorn/p/7873083.html 返回目录 线性回归代码( ...

  7. shell编程常用命令

    Linux中常用的命令 #nl  filename   使用nl命令打印文件内容并显示行号 #sed   '/nw/,$d'   filename     使用sed命令删除匹配nw至最后一行的内容 ...

  8. spring整合activeMQ遇到异常:Error creating bean with name 'connectionFactory'

    异常详情 org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'connect ...

  9. zookeeper源码分析:选举流程和请求处理

    集群启动: QuorumPeerMain. runFromConfig() quorumPeer.start(); loadDataBase(); cnxnFactory.start();       ...

  10. jqGrid细节备注—jqGrid中自定义格式,URL格式

    本文来自:http://cnn237111.blog.51cto.com/2359144/782137 jqGrid中自定义格式,URL格式 当官方自带的showlink用起来不是十分顺手,因此可以考 ...