15、泛型:

15、泛型:

泛型引入:

先来看一个需求:

我们使用我们学过的传统方式来看。

package com.jiangxian.generic_;

import java.util.ArrayList;

/**
 * @author JiangXian~
 * @version 1.0
 */

// 1. 请编写程序,在ArrayList中,添加3个Dog;
// 2. Dog对象含有name和age,并输出name和age
@SuppressWarnings({"all"})
public class Generic01 {
    public static void main(String[] args) {
        ArrayList Dogs = new ArrayList();
        Dogs.add(new Dog("black", 2));
        Dogs.add(new Dog("white", 3));
        Dogs.add(new Dog("yellow", 5));

        // 假设我们不小心向其中添加了一只猫
        Dogs.add(new Cat("red", 6));

        // 遍历:
        for(Object o : Dogs){ // 此处不能使用 for(Dog d : Dog),
            // 向下转型,以使用Dog中的方法getName和getAge
            Dog d = (Dog)o;
            System.out.println(d.getName());
            System.out.println(d.getAge());
        }
    }
}

class Dog{
    public String name;
    public int age;
    public Dog(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;
    }
}

class Cat{
    public String name;
    public int age;
    public Cat(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;
    }
}

使用传统方法的问题分析:

  1. 我们可以看出使用传统方法不能对加入到集合 ArrayList 中的数据类型进行约束(不安全);
  2. 遍历的时候,需要进行类型转换,若集合中的数据量较大,对效率有影响。(在没有使用泛型时,增强for循环只能接受Object)

泛型体验:

我们将原来的ArrayList Dogs = new ArrayList();,更改为ArrayList Dogs = new ArrayList();

这样子,我们只能往ArrayList中加入类型为Dog的对象,所以在我们试图传入Cat类型的对象时,会有报错。

并且,我们使用增强for循环时,不再需要类型转换(传入Object),而是可以直接传入 Dog 类型的对象了

package com.jiangxian.generic_;

import java.util.ArrayList;

/**
 * @author JiangXian~
 * @version 1.0
 */

// 1. 请编写程序,在ArrayList中,添加3个Dog;
// 2. Dog对象含有name和age,并输出name和age
@SuppressWarnings({"all"})
public class Generic01 {
    public static void main(String[] args) {
        // 1. 当我们 ArrayList<Dog> 表示存放到 ArrayList 集合中的元素是 Dog 类型
        // 2. 若编译器发现添加的类型,不满足要求,就会报错
        // 3. 在遍历的时候,可以直接取出Dog类型而不是 Object
        // 4. public class ArrayList<E>{} E称为泛型,那么我们的代码中,是将 Dog -》E
        ArrayList<Dog> Dogs = new ArrayList<Dog>();
        Dogs.add(new Dog("black", 2));
        Dogs.add(new Dog("white", 3));
        Dogs.add(new Dog("yellow", 5));

        // 假设我们不小心向其中添加了一只猫
        Dogs.add(new Cat("red", 6));

        // 遍历:
        for(Dog d : Dogs){
            System.out.println(d.getName());
            System.out.println(d.getAge());
        }
    }
}

class Dog{
    public String name;
    public int age;
    public Dog(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;
    }
}

class Cat{
    public String name;
    public int age;
    public Cat(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;
    }
}

泛型的理解和好处:

泛型的好处:

  1. 编译时,检查添加元素的类型,提高了安全性;
  2. 减少了类型转换的次数,提高了效率,解释说明:
    1. 不使用泛型: Dog类型 ——加入——》 Object——取出——》Dog(存放到 ArrayList 中时,会先转换成 Object , 在取出时,还需要转换成Dog类型);
    2. 使用泛型:Dog类型——加入——》Dog——取出——》Dog,我们可以看到,存放和取出时,都不需要类型转换,提高了效率。
  3. 不再提示编译警告。

泛型介绍:

泛(广泛)型(类型)E——其能够表示一种数据类型,可以由我们指定(表示数据类型的一种数据类型)。

  1. 泛型又称参数化类型,是JDK5出现的新特性,结局数据类型的安全性问题;
  2. 在类声明或实例化时只要指定好需要的具体的类型即可;
  3. Java 泛型可以保证如果程序在编译时没有发生警告,允许时就不会产生ClassSetException异常,同时,代码更加简洁,健壮。
  4. 泛型的作用:可以在类声明时通过一个标识表示类中某个属性的类型,或者是某个方法返回值的类型,或者是参数类型。(举例如下文代码)
package com.jiangxian.generic_;

/**
 * @author JiangXian~
 * @version 1.0
 */


public class Generic03 {
    public static void main(String[] args) {
        Person<String> jiangXian = new Person<String>("JiangXian");
        /*
            上面的这个Person类是长这样的:
            class Person<String>{
                // E到底是什么数据类型呢?是我们在创建Person这个对象时指定的
                // 即在编译期间就确定了E是什么类型了
                String s;

                public Person(String s) { // E可以在参数类型上体现
                    this.s = s;
                }
                public String f(){ // E也可以在返回类型上体现
                    return s;
                }
         */
    }
}

//class Person{
//    String s; // 表示s的数据类型为String,但是我们有的时候希望其为Integer怎么办呢?
//}

//泛型的作用:可以在类声明时通过一个标识表示类中某个属性的类型
// ,或者是某个方法返回值的类型,或者是参数类型。
class Person<E>{
    // E到底是什么数据类型呢?是我们在创建Person这个对象时指定的
    // 即在编译期间就确定了E是什么类型了
    E s;

    public Person(E s) { // E可以在参数类型上体现
        this.s = s;
    }

    public E f(){ // E也可以在返回类型上体现
        return s;
    }
}

泛型的语法:

泛型的声明:

接口:

interface 接口<T,…>{}

class类

class类<K,V,…>{}

说明:

  1. 其中,T,K,V不代表值,而是表示类型;
  2. 任意字母都可以。常用T表示,是Type的缩写;
  3. 接口和类都可以有多个泛型。

泛型的实例化:

要在类名后面指定类型参数的值(类型)。

package com.jiangxian.generic_;

import java.util.*;

/**
 * @author JiangXian~
 * @version 1.0
 */
public class GenericExercise01 {
    public static void main(String[] args) {
        /*
            1. 创建3个学生对象
            2.放入到HashSet中,使用
            3.放入到HashMap中,要求,Key是String对象,Value是学生对象
            4.使用两种方式遍历
         */
        Student jiangXian = new Student("JiangXian", 21);
        Student hsp = new Student("hsp", 40);
        Student king = new Student("king", 100);
        HashSet<Student> set = new HashSet<Student>();
        HashMap<String,Student> map = new HashMap<String,Student>();
        set.add(jiangXian);
        set.add(hsp);
        set.add(king);

        map.put(jiangXian.getName(),jiangXian);
        map.put(hsp.getName(),hsp);
        map.put(king.getName(),king);

        for(Student student : set){
            System.out.println(student);
        }
        Iterator<Student> iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

        // 使用Hashmao的keySet
        Set<String> set1 = map.keySet();
        for(String key : set1){
            System.out.println(key + "-" + map.get(key));
        }

        Iterator<String> iterator1 = set1.iterator();
        while(iterator1.hasNext()){
            String key = iterator1.next();
            System.out.println(key + "-" + map.get(key));
        }

        // 使用entrySet
        Set<Map.Entry<String,Student>> set2 = map.entrySet();
        for(Map.Entry<String,Student> entry : set2){
            System.out.println(entry.getKey() + "-" + entry.getValue());
        }

        // 这段代码会填充是因为HashMap中被定义过了
        /*
        final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
            public final int size()                 { return size; }
            public final void clear()               { HashMap.this.clear(); }
            public final Iterator<Map.Entry<K,V>> iterator() {
                return new EntryIterator();
            }
         */
        Iterator<Map.Entry<String,Student>> iterator2 = set2.iterator();

    }
}

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 +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}


使用细节和注意事项:

  1. interface List{},public class HashSet{}…等等
    1. 其中的T,E只能是引用类型!
  2. 在指定泛型具体类型后,可以传入该类型或者该类型的子类类型(其实很好想,我们之前没用泛型之前,默认是Object,而Object是所有的父类,所以可以传入所有的类型均是子类,但是要使用子类方法的时候需要向下转型才行)
  3. 泛型使用形式:
    1. List list1 = new ArrayList();
    2. List list2 = new ArrayList<>();——这个叫做简化形式,就是简写啦,为什么可以呢?因为编译器会进行类型推断,其会自己判断出<>内是我们填入的Integer,且推荐使用这种形式
    3. List list3 = new ArrayList(); 默认给他的泛型是,E就是Object。(所以之前我们迭代的时候只能接受Object)

泛型课堂练习:

package com.jiangxian.generic_;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Objects;

/**
 * @author JiangXian~
 * @version 1.0
 */

@SuppressWarnings({"all"})
public class GenericExercise02 {
    public static void main(String[] args) {
        ArrayList<Employee> employees = new ArrayList<>();
        employees.add(new Employee("JiangXian",20000, new MyData(2003,5,23)));
        employees.add(new Employee("JiangXian",20000, new MyData(2003,4,23)));
        employees.add(new Employee("JiangXian",20000, new MyData(2004,5,23)));
        employees.add(new Employee("Hsp",50000, new MyData(1990,1,21)));
        employees.add(new Employee("King",100000, new MyData(1900,1,1)));

        employees.sort(new Comparator<Employee>() {
            public int compare(Employee e1, Employee e2) {
                // 按照姓名排序,自然顺序;
                int n1 = (e1.getName()).compareTo(e2.getName());
                if (n1 != 0) return n1;
                // 若姓名相同,则按照出生年月排序
                return (e1.getBirthday()).compareTo(e2.getBirthday());
            }
        });
        System.out.println(employees);
    }
}

class Employee{
    private String name;
    private double sal;
    private MyData birthday;
    public Employee(String name, double sal, MyData birthday) {
        this.name = name;
        this.sal = sal;
        this.birthday = birthday;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSal() {
        return sal;
    }

    public void setSal(double sal) {
        this.sal = sal;
    }

    public MyData getBirthday() {
        return birthday;
    }

    public void setBirthday(MyData birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", sal=" + sal +
                ", birthday=" + birthday +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return Double.compare(sal, employee.sal) == 0 && Objects.equals(name, employee.name) && Objects.equals(birthday, employee.birthday);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, sal, birthday);
    }
}

class MyData{
    private int year;
    private int month;
    private int day;

    public MyData(int year, int month, int day){
        this.year = year;
        this.month = month;
        this.day = day;
    }


    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        this.day = day;
    }

    public int compareTo(MyData other) {
        int res = 0;
        if(this.year == other.getYear() && this.month == other.getMonth() && this.day == other.getDay()){
            res = 0;
        }
        if(this.year < other.getYear()){
            res = -1;
        }else if(this.year > other.getYear()){
            res = 1;
        }else if(this.month < other.getMonth()){
            res = -1;
        }else if(this.month > other.getMonth()){
            res = 1;
        } else if (this.day < other.getDay()) {
            res = -1;
        }else if(this.day > other.getDay()){
            res = 1;
        }
        return res;
    }

    @Override
    public String toString() {
        return "MyData{" +
                "year=" + year +
                ", month=" + month +
                ", day=" + day +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true; // 若两个对象指向同一个位置,自然相等
        if (o == null || getClass() != o.getClass()) return false;
        MyData myData = (MyData) o;
        return year == myData.year && month == myData.month && day == myData.day;
    }

    @Override
    public int hashCode() {
        return Objects.hash(year, month, day);
    }
}


自定义泛型类:

我们经常用的泛型一般都是别人写好的;

那什么是自定义泛型内,就是我们自己写的类,自己定义了泛型,上文其实已经有涉及了:

class Person<E>{
    // E到底是什么数据类型呢?是我们在创建Person这个对象时指定的
    // 即在编译期间就确定了E是什么类型了
    E s;

    public Person(E s) { // E可以在参数类型上体现
        this.s = s;
    }

    public E f(){ // E也可以在返回类型上体现
        return s;
    }
}

基本语法:

class 类名<T,R,....>{
	成员
}

注意细节:

  1. 普通成员可以使用泛型;
  2. 使用泛型的数组,不能初始化;(即不允许new进行实例化,因为类型不确定,不能确定开辟多大的空间)
  3. 静态方法中不能使用类的泛型;(静态是和类相关的,但是类型是在对象创建的时候才能确定的,所以没有办法使用)
  4. 泛型类的类型是在创建对象时确定的;
  5. 若在创建对象时,没有指定对象,默认为Object。

自定义泛型接口:

基本语法:

interface 接口名<T,R,…>{}

注意细节:

  1. 接口中,静态成员也不能使用泛型(这个和泛型类一样);——接口中,属性是public static final的,但是方法默认是 public abstract
  2. 泛型接口的类型,在被其它接口继承时或者实现接口时确定;
  3. 没有指定类型,默认为Object。

自定义泛型方法:

基本语法:

修饰符 <T,R,…> 返回类型 方法名(参数列表){}

注意细节:

  1. 可以定义在普通类和泛型类中;
  2. 当泛型方法被调用时,类型会确定;
  3. public void eat(E e){},修饰符后没有<>所以不是泛型方法,而只是使用了泛型;
  4. 泛型方法,除了可以使用自己的泛型外,也可以使用类的泛型。
package com.jiangxian.generic_;

/**
 * @author JiangXian~
 * @version 1.0
 */
public class GenericMethod {
    public static void main(String[] args) {
        Car car = new Car();
        car.fly("宝马",1); // 当调用方法时,类型会被确定
        car.fly(1,1);
    }
}

class Car{
    public void run(){}

    // 1. 说明<T,R>为泛型标识符
    // 2. 是提供给fly使用的
    public<T,R> void fly(T t,R r){
        System.out.println(t.getClass());
        System.out.println(r.getClass());
    }
}

class Fish<T, R>{
    public void run(){} // 普通方法
    public <U,M> void swim(){
        U u;
        M m;
    } // 不能和泛型类的字母一样!需要区分开

    public void eat(T t){}
}

这个有什么意义呢?此时我们传入的参数可以字节决定了,非常自由。


泛型的继承和通配符:

  1. 泛型不具备继承性;
  2. <?>:表示支持任意泛型类型;
  3. <? extends A>:支持 A 和 A 的子类,不限于直接子类,规定了泛型的上限;
  4. <? super A>:支持A和A的父类,不限于直接父类,规定了泛型的下限。
package com.jiangxian.generic_;

import java.util.ArrayList;
import java.util.List;

/**
 * @author JiangXian~
 * @version 1.0
 */
public class GenericExtends {
    public static void main(String[] args) {
        Object o = new String("xx");

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

        ArrayList<Object> objects = new ArrayList<>();
        ArrayList<String> strings = new ArrayList<>();
        ArrayList<AA> aas = new ArrayList<>();
        ArrayList<BB> bbs = new ArrayList<>();
        ArrayList<CC> ccs = new ArrayList<>();

        // <?>
        printCollection(strings);
        printCollection(aas);
        printCollection(bbs);
        printCollection(ccs);

        // <? extends AA>
        printCollection2(aas);
        printCollection2(bbs);
        printCollection2(ccs);
        // printCollection2(strings);

        // <? super AA>
        printCollection3(aas);
        printCollection3(objects);
        // printCollection3(bbs);
    }

    // <?> 表示任意的类型
    public static void printCollection(List<?> c){
        for(Object o : c){
            System.out.println(o);
        }
    }

    // 只能传入AA即AA的子类,也不限制直接与否
    public static void printCollection2(List<? extends AA> c){
        for(Object o : c){
            System.out.println(o);
        }
    }

    // 只能传入AA和AA的父类,但不限定于直接父类
    public static void printCollection3(List<? super AA> c){
        for(Object o : c){
            System.out.println(o);
        }
    }

}

class AA{}

class BB extends AA{}

class CC extends BB{}


JUnit的使用:

为什么需要JUnit:

  1. 一个类有很多功能代码需要测试,为了测试,就需要写入到main方法中;
  2. 若有多个功能代码测试,就需要来回注销,切换很麻烦;
  3. 若可以直接允许一个方法,就方便很多,并且可以给出相关信息就好了——》JUnit。

基本介绍:

  1. JUnit 是一个Java语言的单元测试框架;
  2. 多数Java开发环境已经集成了Junit作为单元测试工具。

传统方式:

package com.jiangxian.junit_;

/**
 * @author JiangXian~
 * @version 1.0
 */
public class JUnit_ {
    public static void main(String[] args) {
        // 传统方式:
        new JUnit_().m1();
        new JUnit_().m2();
    }
    
    public void m1(){
        System.out.println("m1 is working");
    }
    
    public void m2(){
        System.out.println("m2 is working");
    }
}

使用Junit

package com.jiangxian.junit_;

import org.junit.jupiter.api.Test;

/**
 * @author JiangXian~
 * @version 1.0
 */
public class JUnit_ {
    public static void main(String[] args) {
        // 传统方式:
        // new JUnit_().m1();
        // new JUnit_().m2();
    }

    // 先写@Test,然后输入 alt + Enter
    // 下载失败的,点开setting,搜索http,Http proxy勾选No proxy即可
    // 发现没有小箭头,重启下idea即可。
    @Test
    public void m1(){
        System.out.println("m1 is working");
    }

    @Test
    public void m2(){
        System.out.println("m2 is working");
    }
}

我将一些配置的问题和解决也写在代码的注释中了,有问题的注意看下。


Exercise

package com.jiangxian.exercise_;

import java.util.*;

/**
 * @author JiangXian~
 * @version 1.0
 */

public class HomeWork01 {
    public static void main(String[] args) {
        DAO<User> userDAO = new DAO<>();
        User jiangXian = new User("1", "JiangXian", 21);
        User hsp = new User("2", "hsp", 40);
        User king = new User("3", "King", 100);

        userDAO.save(jiangXian.getId(),jiangXian);
        userDAO.save(hsp.getId(),hsp);
        userDAO.save(king.getId(),king);
        System.out.println(userDAO.list());

        userDAO.update(hsp.getId(),new User("2", "Smith", 50));
        System.out.println(userDAO.list());

        System.out.println(userDAO.get(jiangXian.getId()));
    }
}

class DAO<T>{
    private Map<String,T> map = new HashMap<>();

    public void save(String id, T entity){
        map.put(id,entity);
    }

    public T get(String id){
        return map.get(id);
    }

    public void update(String id, T entity){
        map.put(id,entity);
    }

    public List<T> list(){
        Collection<T> col = map.values();
        ArrayList<T> ts = new ArrayList<>();
        for(T t : col){
            ts.add(t);
        }
        return ts;
    }

    public void delete(String id){
        map.remove(id);
    }
}

class User{ // 因为仅是作为value,所以可以不用重写equals和hashCode
    private String id;
    private String name;
    private int age;
    public User(String id, String name, int age){
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    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 "User{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江弦凤歌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值