Java学习(十五)--泛型

发布时间:2024年01月16日

为什么需要

1.不能对加入集合的数据类型进行约束(不安全)

  • ?Java 集合有个缺点,就是把一个对象“丢进”集合里之后,集合就会“忘记”这个对象的数据类型,当再次取出该对象时,该对象的编译类型就变成了 Object 类型(其运行时类型没变);
  • 例如,想创建一个只能保存 Dog 对象的集合,但程序也可以轻易地将 Cat 对象“丢”进去,所以可能引发异常。

2.集合遍历时,需进行类型转换,若集合数据量大,影响效率

  • 由于把对象“丢进”集合时,集合丢失了对象的状态信息;
  • 集合只知道它盛装的是 Object,因此取出集合元素后通常还需要进行强制类型转换;
  • 强制类型转换既增加了编程的复杂度,也可能引发 ClassCastException 异常

介绍

泛型又称参数化类型,是jdk5.0出现的新特性,解决数据类型的安全性问题;

  • ?? ?若程序在编译时候没有异常,则运行时候不会产生ClassCastException异常
  • ?? ?为类、接口或方法指定一个类型参数,通过这个参数限制操作的数据类型,从而保证类型转换的绝对安全

泛型本质上是提供类型的“类型参数”,也就是参数化类型(Integer、String等)

  • ?? ?在类声明或实例化时只要指定好需要的具体的类型即可

作用

  • 可以在类声明时通过一个标识表示类中某个属性的类型
  • 或是某个方法的返回值的类型或参数类型

优点

  • ?? ?1.编译时,检查添加元素的类型,提高安全性
  • ?? ?2.减少类型转换的次数,提高效率
  • ?? ?3.不在提示编译警告?? ?
import java.util.List;

public class Generic01 {
    public static void main(String[] args) {

        Person<String> person = new Person<String>("java课程");
        person.show(); //String

        Person<Integer> person2 = new Person<Integer>(100);
        person2.show();//Integer

    }
}

class Person<E> {
    E s ;//E表示 s的数据类型, 该数据类型在定义Person对象的时候指定,即在编译期间,就确定E是什么类型

    public Person(E s) {//E也可以是参数类型
        this.s = s;
    }

    public E f() {//返回类型使用E
        return s;
    }

    public void show() {
        System.out.println(s.getClass());//显示s的运行类型
    }
}


/*
	你可以这样理解,上面的Person类
	class Person {
		String s ;//E表示 s的数据类型, 该数据类型在定义Person对象的时候指定,即在编译期间,就确定E是什么类型

		public Person(String s) {//E也可以是参数类型
			this.s = s;
		}

		public String f() {//返回类型使用E
			return s;
		}
	}
 */


/*
	class Person {
		Integer s ;//E表示 s的数据类型, 该数据类型在定义Person对象的时候指定,即在编译期间,就确定E是什么类型

		public Person(Integer s) {//E也可以是参数类型
			this.s = s;
		}

		public Integer f() {//返回类型使用E
			return s;
		}
	}
 */

系统提供泛型

import java.util.*;

/**
泛型实例化
需求:
创建  3个学生对象,放入到HashSet中学生对象
创建  3个学生对象,放入到  HashMap中,要求 Key 是 String name, Value 是学生对象
使用两种方式遍历
*/
 
@SuppressWarnings({"all"})
public class GenericExercise {

    public static void main(String[] args) {
        //使用泛型方式给HashSet 放入3个学生对象
        HashSet<Student> students = new HashSet<Student>();

        students.add(new Student("jack", 18));
        students.add(new Student("tom", 28));
        students.add(new Student("mary", 19));

        //遍历HashSet集合
        for (Student student : students) {
            System.out.println(student);
        }

        //使用泛型方式给HashMap 放入3个学生对象
        //K -> String V->Student
        HashMap<String, Student> hm = new HashMap<String, Student>();

        hm.put("milan", new Student("milan", 38));
        hm.put("smith", new Student("smith", 48));
        hm.put("hsp", new Student("hsp", 28));

        Set<Map.Entry<String, Student>> entries = hm.entrySet();

        Iterator<Map.Entry<String, Student>> iterator = entries.iterator();

        System.out.println("==============================");
        while (iterator.hasNext()) {//遍历
            Map.Entry<String, Student> next =  iterator.next();
            System.out.println(next.getKey() + "-" + next.getValue());              
        }
    }
}

class Student {
    private String name;
    private int age;

    public Student(String name, int age) {
        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;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
List<Integer> list = new ArrayList<Integer>();//OK

List<int> list = new ArrayList<int>();//错误;


//因为 E 指定了 A 类型, 构造器传入了 new A()
//在给泛型指定具体类型后,可以传入该类型或者其子类类型
Pig<A> aPig = new Pig<A>(new A());
aPig.f();
Pig<A> aPig2 = new Pig<A>(new B());
aPig2.f();

class A {}
class B extends A {}

自定义泛型

泛型类

//自定义泛型类

语法格式:
public class class_name<data_type1,data_type2,…>{}

class_name 表示类的名称,data_ type1 等表示类型参数;

泛型类一般用于类中的属性类型不确定的情况下。

在声明属性时,使用下面的语句:
private data_type1 property_name1;
private data_type2 property_name2;
该语句中的 data_type1 与类声明中的 data_type1 表示的是同一种数据类型。

在实例化泛型类时,需要指明泛型类中的类型参数,并赋予泛型类属性相应类型的值。

例如,下面的示例代码创建了一个表示学生的泛型类,该类中包括 3 个属性,分别是姓名、年龄和性别。
public class Stu<N, A, S> {
    private N name; // 姓名
    private A age; // 年龄
    private S sex; // 性别

    // 创建类的构造函数
    public Stu(N name, A age, S sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    // 下面是上面3个属性的setter/getter方法
    public N getName() {
        return name;
    }
    public void setName(N name) {
        this.name = name;
    }
    public A getAge() {
        return age;
    }
    public void setAge(A age) {
        this.age = age;
    }
    public S getSex() {
        return sex;
    }
    public void setSex(S sex) {
        this.sex = sex;
    }
}

接着创建测试类。
在测试类中调用 Stu 类的构造方法实例化 Stu 对象,并给该类中的 3 个属性赋予初始值,最终需要输出学生信息。
测试类的代码实现如下:
public class Test14 {
    public static void main(String[] args) {
	
        Stu<String, Integer, Character> stu = new Stu<String, Integer, Character>("张晓玲", 28, '女');
		
        String name = stu.getName();
        Integer age = stu.getAge();
        Character sex = stu.getSex();
        System.out.println("学生信息如下:");
        System.out.println("学生姓名:" + name + ",年龄:" + age + ",性别:" + sex);
    }
}
该程序的运行结果如下:
学生信息如下:
学生姓名:张晓玲,年龄:28,性别:女

在该程序的 Stu 类中,定义了 3 个类型参数,分别使用 N、A 和 S 来代替,同时实现了这 3 个属性的 setter/getter 方法。

在主类中,调用 Stu 类的构造函数创建了 Stu 类的对象,同时指定 3 个类型参数,分别为 String、Integer 和 Character。

在获取学生姓名、年龄和性别时,不需要类型转换,程序隐式地将 Object 类型的数据转换为相应的数据类型。

泛型接口

//2、自定义泛型接口

public class CustomInterfaceGeneric {
    public static void main(String[] args) {
    }
}


interface IUsb<U, R> {

    int n = 10;
	
    //U name; 不能这样使用 ,接口内属性为静态成员变量;

    //普通方法中,可以使用接口泛型
    R get(U u);

    void hi(R r);

    void run(R r1, R r2, U u1, U u2);
	
//在jdk8 中,可以在接口中,使用默认方法, 也是可以使用泛型
    default R method(U u) {
        return null;
    }	
}


//在继承接口 指定泛型接口的类型
//当我们去实现IA接口时,因为IA在继承IUsu 接口时,指定了U 为String R为Double,在实现IUsu接口的方法时,使用String替换U, 是Double替换R
interface IA extends IUsb<String, Double> {
}

class AA implements IA {

    @Override
    public Double get(String s) {
        return null;
    }
    @Override
    public void hi(Double aDouble) {

    }
    @Override
    public void run(Double r1, Double r2, String u1, String u2) {

    }
}

//实现接口时,直接指定泛型接口的类型
//给U 指定Integer ,给 R 指定了 Float。那么当我们实现IUsb方法时,会使用Integer替换U, 使用Float替换R
class BB implements IUsb<Integer, Float> {

    @Override
    public Float get(Integer integer) {
        return null;
    }

    @Override
    public void hi(Float aFloat) {

    }

    @Override
    public void run(Float r1, Float r2, Integer u1, Integer u2) {

    }
}

//没有指定类型,默认为Object
//建议直接写成 IUsb<Object,Object>
class CC implements IUsb { 
//等价 class CC implements Usb<Object,Object> 
    @Override
    public Object get(Object o) {
        return null;
    }
    @Override
    public void hi(Object o) {
    }
    @Override
    public void run(Object r1, Object r2, Object u1, Object u2) {
    }
}



泛型方法

//3、自定义泛型方法
import java.util.*;

public class Test02 {
	public static void main(String[] args) {
		Car car = new Car();
		car.fly("宝马", 100);//当调用方法时,传入参数,编译器,就会确定类型

		System.out.println("=======");
		car.fly(300, 100.1);//当调用方法时,传入参数,编译器,就会确定类型

		//测试
		//T->String, R-> ArrayList
		Fish<String, ArrayList> fish = new Fish<>();
		fish.hello(new ArrayList(), 11.3f);

		System.out.println("=======");

        fish.he("abc")//只能输入String类型的参数

		fish.hi(20); //10 会被自动装箱 Integer10, 输出 Integer
		fish.hi(new Car());//Car
}
}

	//泛型方法,可以定义在普通类中, 也可以定义在泛型类中
	class Car {//普通类
		public void run() {//普通方法
		}

		//说明 泛型方法
		//1. <T,R> 就是泛型
		//2. 是提供给 fly使用的
		public <T, R> void fly(T t, R r) {//泛型方法
			System.out.println(t.getClass().getSimpleName());//String-->Integer
			System.out.println(r.getClass().getSimpleName());//Integer-->Double
		}
	}

	class Fish<T, R> {//泛型类

		public void run() {//普通方法
		}
		public<U,M> void eat(U u, M m) {//泛型方法

		}
		//说明
		//1. 下面he方法不是泛型方法
		//2. 是he方法使用了类声明的 泛型
		public void he(T t) {
			System.out.println(h.getClass().getSimpleName());
		}

		public<H> void hi(H h) {
			System.out.println(h.getClass().getSimpleName());//Integer
		}

		// public void hi(N n) {//错误,因为N没有声明;
		// }

		//泛型方法,可以使用类声明的泛型,也可以使用自己声明泛型
		public<K> void hello(R r, K k) {
			System.out.println(r.getClass().getSimpleName());//ArrayList
			System.out.println(k.getClass().getSimpleName());//Float
		}
	}

泛型的高级用法

创建了一个 ListClass 类,并对该类的类型限制为只能是实现 List 接口的类。

// 限制ListClass的泛型类型必须实现List接口
public class ListClass<T extends List> {
    public static void main(String[] args) {

// 实例化使用ArrayList的泛型类ListClass,正确
        ListClass<ArrayList> lc1 = new ListClass<ArrayList>();

// 实例化使用LinkedList的泛型类LlstClass,正确
        ListClass<LinkedList> lc2 = new ListClass<LinkedList>();

// 实例化使用HashMap的泛型类ListClass错误,因为HasMap没有实现List接口,所以编译时候就报错
        // ListClass<HashMap> lc3=new ListClass<HashMap>();

    }
}

在上述代码中,定义 ListClass 类时设置泛型类型必须实现 List 接口。
例如,ArrayList 和 LinkedList 都实现了 List 接口,所以可以实例化 ListClass 类。
而 HashMap 没有实现 List 接口,所以在实例化 ListClass 类时会报错。
import java.util.ArrayList;
import java.util.List;

//泛型的继承和通配符

public class GenericExtends {
    public static void main(String[] args) {

        Object o = new String("xx");

        //泛型没有继承性
//        List<Object> list = new ArrayList<String>();  //no

        //举例说明下面三个方法的使用
        List<Object> list1 = new ArrayList<>();
        List<String> list2 = new ArrayList<>();
        List<AA> list3 = new ArrayList<>();
        List<BB> list4 = new ArrayList<>();
        List<CC> list5 = new ArrayList<>();

        //如果是 List<?> c ,可以接受任意的泛型类型
        printCollection1(list1);
        printCollection1(list2);
        printCollection1(list3);
        printCollection1(list4);
        printCollection1(list5);

        //List<? extends AA> c: 表示 上限,可以接受 AA或者AA子类
//        printCollection2(list1);//×
//        printCollection2(list2);//×
        printCollection2(list3);//√
        printCollection2(list4);//√
        printCollection2(list5);//√

        //List<? super AA> c: 支持AA类以及AA类的父类,不限于直接父类
        printCollection3(list1);//√
        //printCollection3(list2);//×
        printCollection3(list3);//√
        //printCollection3(list4);//×
        //printCollection3(list5);//×
    }
}


class AA {
}

class BB extends AA {
}

class CC extends BB {
}
public class FatherClass<T1>{}

public class SonClass<T1,T2,T3> extents FatherClass<T1>{}

如果要在 SonClass 类继承 FatherClass 类时保留父类的泛型类型,需要在继承时指定,否则直接使用 extends FatherClass 语句进行继承操作,此时 T1、T2 和 T3 都会自动变为 Object,所以一般情况下都将父类的泛型类型保留。

文章来源:https://blog.csdn.net/hahaha2221/article/details/135508138
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。