Java SE Day-18

Map

1 概述

现实生活中,我们常会看到这样的一种集合:IP地址与主机名,身份证号与个人,系统用户名与系统用户对象等,这种一一对应的关系,就叫做映射。Java提供了专门的集合类用来存放这种对象关系的对象,即java.util.Map<K,V>接口。

我们通过查看Map接口描述,发现Map<K,V>接口下的集合与Collection<E>接口下的集合,它们存储数据的形式不同。

  • Collection中的集合,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储。
  • Map中的集合,元素是成对存在的(理解为夫妻)。每个元素由键与值两部分组成,通过键可以找对所对应的值。
  • Collection中的集合称为单列集合,Map中的集合称为双列集合。
  • 需要注意的是,Map中的集合不能包含重复的键,值可以重复;每个键只能对应一个值(这个值可以是单个值,也可以是个数组或集合值)。

在这里插入图片描述

2 Map常用方法

1、添加操作

  • V put(K key,V value)
  • void putAll(Map<? extends K,? extends V> m)

2、删除

  • void clear()
  • V remove(Object key)

3、元素查询的操作

  • V get(Object key)
  • boolean containsKey(Object key)
  • boolean containsValue(Object value)
  • boolean isEmpty()

4、元视图操作的方法:

  • Set keySet()
  • Collection values()
  • Set<Map.Entry<K,V>> entrySet()

5、其他方法

  • int size()
import java.util.HashMap;

public class TestMapMethod {
    public static void main(String[] args) {
        //创建 map对象
        HashMap<String, String> map = new HashMap<String, String>();

        //添加元素到集合
        map.put("黄晓明", "杨颖");
        map.put("文章", "马伊琍");
        map.put("文章", "姚笛");
        map.put("邓超", "孙俪");
        System.out.println(map);

        //String remove(String key)
        System.out.println(map.remove("黄晓明"));
        System.out.println(map);

        // 想要查看 黄晓明的媳妇 是谁
        System.out.println(map.get("黄晓明"));
        System.out.println(map.get("邓超"));
    }
}

tips:

使用put方法时,若指定的键(key)在集合中没有,则没有这个键对应的值,返回null,并把指定的键值添加到集合中;

若指定的键(key)在集合中存在,则返回值为集合中键对应的值(该值为替换前的值),并把指定键所对应的值,替换成指定的新值。

Map接口的特点:是一个键值对集合

3 Map集合的遍历

Collection集合的遍历:(1)foreach(2)通过Iterator对象遍历

Map的遍历,不能支持foreach,因为Map接口没有继承java.lang.Iterable接口,也没有实现Iterator iterator()方法。只能用如下方式遍历:

(1)分开遍历:

  • 单独遍历所有key
  • 单独遍历所有value

(2)成对遍历:

  • 遍历的是映射关系Map.Entry类型的对象,Map.Entry是Map接口的内部接口。每一种Map内部有自己的Map.Entry的实现类。在Map中存储数据,实际上是将Key---->value的数据存储在Map.Entry接口的实例中,再在Map集合中插入Map.Entry的实例化对象,如图示:

在这里插入图片描述

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class TestMapIterate {
    public static void main(String[] args) {
        HashMap<String,String> map = new HashMap<>();
        map.put("许仙", "白娘子");
        map.put("董永", "七仙女");
        map.put("牛郎", "织女");
        map.put("许仙", "小青");

        System.out.println("所有的key:");
        Set<String> keySet = map.keySet();
        for (String key : keySet) {
            System.out.println(key);
        }

        System.out.println("所有的value:");
        Collection<String> values = map.values();
        for (String value : values) {
            System.out.println(value);
        }

        System.out.println("所有的映射关系");
        Set<Map.Entry<String,String>> entrySet = map.entrySet();
        for (Map.Entry<String,String> entry : entrySet) {
//			System.out.println(entry);
            System.out.println(entry.getKey()+"->"+entry.getValue());
        }
    }
}

4 Map的实现类们

Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和Properties。其中HashMap是 Map 接口使用频率最高的实现类。

1、HashMap和Hashtable

HashMap和Hashtable都是哈希表。HashMap和Hashtable判断两个 key 相等的标准是:两个 key 的hashCode 值相等,并且 equals() 方法也返回 true。因此,为了成功地在哈希表中存储和获取对象,用作键的对象必须实现 hashCode 方法和 equals 方法。

  • Hashtable是线程安全的,任何非 null 对象都可以用作键或值。
  • HashMap是线程不安全的,并允许使用 null 值和 null 键。

示例代码:添加员工姓名为key,薪资为value

import org.junit.Test;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;

public class TestHashMap {
    @Test
    public void test01(){
        HashMap<String,Double> map = new HashMap<>();
        map.put("张三", 10000.0);
        //key相同,新的value会覆盖原来的value
        //因为String重写了hashCode和equals方法
        map.put("张三", 12000.0);
        map.put("李四", 14000.0);
        //HashMap支持key和value为null值
        String name = null;
        Double salary = null;
        map.put(name, salary);

        Set<Map.Entry<String, Double>> entrySet = map.entrySet();
        for (Map.Entry<String, Double> entry : entrySet) {
            System.out.println(entry);
        }
    }

    @Test
    public void test02(){
        Hashtable<String,Double> map = new Hashtable<>();
        map.put("张三", 10000.0);
        //key相同,新的value会覆盖原来的value
        //因为String重写了hashCode和equals方法
        map.put("张三", 12000.0);
        map.put("李四", 14000.0);
        //Hashtable不支持key和value为null值
        /*String name = null;
        Double salary = null;
        map.put(name, salary);*/

        Set<Map.Entry<String, Double>> entrySet = map.entrySet();
        for (Map.Entry<String, Double> entry : entrySet) {
            System.out.println(entry);
        }
    }
}
2、LinkedHashMap

LinkedHashMap 是 HashMap 的子类。此实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。

示例代码:添加员工姓名为key,薪资为value

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

public class TestLinkedHashMap {
    public static void main(String[] args) {
        LinkedHashMap<String,Double> map = new LinkedHashMap<>();
        map.put("张三", 10000.0);
        //key相同,新的value会覆盖原来的value
        //因为String重写了hashCode和equals方法
        map.put("张三", 12000.0);
        map.put("李四", 14000.0);
        //HashMap支持key和value为null值
        String name = null;
        Double salary = null;
        map.put(name, salary);

        Set<Map.Entry<String, Double>> entrySet = map.entrySet();
        for (Map.Entry<String, Double> entry : entrySet) {
            System.out.println(entry);
        }
    }
}
3、TreeMap

基于红黑树(Red-Black tree)的 NavigableMap 实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。

代码示例:添加员工姓名为key,薪资为value

import java.util.Comparator;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

import org.junit.Test;

public class TestTreeMap {
	@Test
	public void test1() {
		TreeMap<String,Integer> map = new TreeMap<>();
		map.put("Jack", 11000);
		map.put("Alice", 12000);
		map.put("zhangsan", 12000);
		map.put("baitao", 14000);
		map.put("Lucy", 15000);
		
		//String实现了Comparable接口,默认按照Unicode编码值排序
		Set<Entry<String, Integer>> entrySet = map.entrySet();
		for (Entry<String, Integer> entry : entrySet) {
			System.out.println(entry);
		}
	}
	@Test
	public void test2() {
		//指定定制比较器Comparator,按照Unicode编码值排序,但是忽略大小写
		TreeMap<String,Integer> map = new TreeMap<>(new Comparator<String>() {

			@Override
			public int compare(String o1, String o2) {
				return o1.compareToIgnoreCase(o2);
			}
		});
		map.put("Jack", 11000);
		map.put("Alice", 12000);
		map.put("zhangsan", 12000);
		map.put("baitao", 14000);
		map.put("Lucy", 15000);
		
		Set<Entry<String, Integer>> entrySet = map.entrySet();
		for (Entry<String, Integer> entry : entrySet) {
			System.out.println(entry);
		}
	}
}

小结:Map接口的特点:是一个键值对集合,所以遍历的方法就有些不同。值得注意的是如果要同时遍历key与value则需要使用Map.Entry这个方法


泛型

1 泛型的概念

1 泛型的引入

例如:生产瓶子的厂家,一开始并不知道我们将来会用瓶子装什么,我们什么都可以装,但是有的时候,我们在使用时,想要限定某个瓶子只能用来装什么,这样我们不会装错,而用的时候也可以放心的使用,无需再三思量。我们生活中是在使用这个瓶子时在瓶子上“贴标签”,这样就轻松解决了问题。

在这里插入图片描述

还有,在Java中我们在声明方法时,当在完成方法功能时如果有未知的数据需要参与,这些未知的数据需要在调用方法时才能确定,那么我们把这样的数据通过形参表示。那么在方法体中,用这个形参名来代表那个未知的数据,而调用者在调用时,对应的传入值就可以了。

在这里插入图片描述

受以上两点启发,JDK1.5设计了泛型的概念。泛型即为“类型参数”,这个类型参数在声明它的类、接口或方法中,代表未知的通用的类型。例如:

java.lang.Comparable接口和java.util.Comparator接口,是用于对象比较大小的规范接口,这两个接口只是限定了当一个对象大于另一个对象时返回正整数,小于返回负整数,等于返回0。但是并不确定是什么类型的对象比较大小,之前的时候只能用Object类型表示,使用时既麻烦又不安全,因此JDK1.5就给它们增加了泛型。

public interface Comparable<T>{
    int compareTo(T o) ;
}
public interface Comparator<T>{
     int compare(T o1, T o2) ;
}

其中就是类型参数,即泛型。

2 泛型的好处

示例代码:

JavaBean:圆类型

public class Circle{
    private double radius;

    public Circle(double radius) {
        super();
        this.radius = radius;
    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    @Override
    public String toString() {
        return "Circle [radius=" + radius + "]";
    }

}

比较器

import java.util.Comparator;

public class CircleRadiusComparator implements Comparator{

    @Override
    public int compare(Object o1, Object o2) {
        //强制类型转换
        Circle c1 = (Circle) o1;
        Circle c2 = (Circle) o2;
        return Double.compare(c1.getRadius(), c2.getRadius());
    }

}

测试类

public class TestNoGeneric {
    public static void main(String[] args) {
        CircleRadiusComparator com = new CircleRadiusComparator();
        System.out.println(com.compare(new Circle(1), new Circle(2)));

        System.out.println(com.compare("圆1", "圆2"));//运行时异常:ClassCastException
    }
}

那么我们在使用如上面这样的接口时,如果没有泛型或不指定泛型,很麻烦,而且有安全隐患。

因为在设计(编译)Comparator接口时,不知道它会用于哪种类型的对象比较,因此只能将compare方法的形参设计为Object类型,而实际在compare方法中需要向下转型为Circle,才能调用Circle类的getRadius()获取半径值进行比较。

使用泛型:

比较器:

import java.util.Comparator;

public class CircleComparator implements Comparator<Circle> {

    @Override
    public int compare(Circle o1, Circle o2) {
        //不再需要强制类型转换,代码更简洁
        return Double.compare(o1.getRadius(), o2.getRadius());
    }

}

测试类

public class TestHasGeneric {
    public static void main(String[] args) {
        CircleComparator com = new CircleComparator();
        System.out.println(com.compare(new Circle(1), new Circle(2)));

//		System.out.println(com.compare("圆1", "圆2"));//编译错误,因为"圆1", "圆2"不是Circle类型,是String类型,编译器提前报错,而不是冒着风险在运行时再报错
    }
}

如果有了泛型并使用泛型,那么既能保证安全,又能简化代码。

因为把不安全的因素在编译期间就排除了;既然通过了编译,那么类型一定是符合要求的,就避免了类型转换。

3 泛型的相关名词

<类型>这种语法形式就叫泛型。

其中:

  • 是类型变量(Type Variables),而是代表未知的数据类型,我们可以指定为,, 等,那么<类型>的形式我们成为类型参数;
    • 类比方法的参数的概念,我们可以把,称为类型形参,将 称为类型实参,有助于我们理解泛型;
  • Comparator这种就称为参数化类型(Parameterized Types)。

自从有了泛型之后,Java的数据类型就更丰富了:

在这里插入图片描述

Class:Class 类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本的 Java 类型(booleanbytecharshortintlongfloatdouble)和关键字 void 也表示为 Class 对象。

  • GenericArrayType:泛化的数组类型,即T[]
  • ParameterizedType:参数化类型,例如:Comparator,Comparator
  • TypeVariable:类型变量,例如:Comparator中的T,Map<K,V>中的K,V
  • WildcardType:通配符类型,例如:Comparator<?>等

4 在哪里可以声明类型变量<T>

  • 声明类或接口时,在类名或接口名后面声明类型变量,我们把这样的类或接口称为泛型类或泛型接口
【修饰符】 class 类名<类型变量列表>extends 父类】 【implements 父接口们】{
    
}
【修饰符】 interface 接口名<类型变量列表>implements 父接口们】{
    
}

例如:
public class ArrayList<E>    
public interface Map<K,V>{
    ....
}    
  • 声明方法时,在【修饰符】与返回值类型之间声明类型变量,我们把声明(是声明不是单纯的使用)了类型变量的方法称为泛型方法
【修饰符】 <类型变量列表> 返回值类型 方法名(【形参列表】)throws 异常列表】{
    //...
}

例如:java.util.Arrays类中的
public static <T> List<T> asList(T... a){
    ....
}

2 泛型类与泛型接口

1 使用核心类库中的泛型类/接口

自从JDK1.5引入泛型的概念之后,对之前核心类库中的API做了很大的修改,例如:集合框架集中的相关接口和类、java.lang.Comparable接口、java.util.Comparator接口、Class类等等。

下面以Collection、ArrayList集合以及Iterator迭代器为例演示,泛型类与泛型接口的使用。

案例一:Collection集合相关类型

(1)创建一个Collection集合(暂时创建ArrayList集合对象),并指定泛型为

(2)添加5个[0,100)以内的整数到集合中,

(3)使用foreach遍历输出5个整数,

(4)使用集合的removeIf方法删除偶数,为Predicate接口指定泛型

(5)再使用Iterator迭代器输出剩下的元素,为Iterator接口指定泛型。

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Random;
import java.util.function.Predicate;

public class TestNumber {
    public static void main(String[] args) {
        Collection<Integer> coll = new ArrayList<Integer>();
        Random random = new Random();
        for (int i = 1; i <= 5 ; i++) {
            coll.add(random.nextInt(100));
        }

        System.out.println("coll中5个随机数是:");
        for (Integer integer : coll) {
            System.out.println(integer);
        }

        coll.removeIf(new Predicate<Integer>() {
            @Override
            public boolean test(Integer integer) {
                return integer % 2 == 0;
            }
        });

        System.out.println("coll中删除偶数后:");
        Iterator<Integer> iterator = coll.iterator();
        while(iterator.hasNext()){
            Integer number = iterator.next();
            System.out.println(number);
        }

    }
}
案例二:Comparable接口

(1)声明矩形类Rectangle,包含属性长和宽,属性私有化,提供有参构造、get/set方法、重写toString方法,提供求面积和周长的方法。

(2)矩形类Rectangle实现java.lang.Comparable接口,并指定泛型为,重写int compareTo(T t)方法,按照矩形面积比较大小,面积相等的,按照周长比较大小。

(3)在测试类中,创建Rectangle数组,并创建5个矩形对象

(4)调用Arrays的sort方法,给矩形数组排序,并显示排序前后的结果。

public class Rectangle implements Comparable<Rectangle>{
    private double length;
    private double width;

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    public double getLength() {
        return length;
    }

    public void setLength(double length) {
        this.length = length;
    }

    public double getWidth() {
        return width;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    public double area(){
        return length * width;
    }

    public double perimeter(){
        return 2 * (length + width);
    }

    @Override
    public String toString() {
        return "Rectangle{" +
                "length=" + length +
                ", width=" + width +
                ",area =" + area() +
                ",perimeter = " + perimeter() +
                '}';
    }

    @Override
    public int compareTo(Rectangle o) {
        int compare = Double.compare(area(), o.area());
        return compare != 0 ? compare : Double.compare(perimeter(),o.perimeter());
    }
}

import java.util.Arrays;

public class TestRectangle {
    public static void main(String[] args) {
        Rectangle[] arr = new Rectangle[4];
        arr[0] = new Rectangle(6,2);
        arr[1] = new Rectangle(4,3);
        arr[2] = new Rectangle(13,1);
        arr[3] = new Rectangle(5,4);

        System.out.println("排序之前:");
        for (Rectangle rectangle : arr) {
            System.out.println(rectangle);
        }

        Arrays.sort(arr);

        System.out.println("排序之后:");
        for (Rectangle rectangle : arr) {
            System.out.println(rectangle);
        }
    }
}

2 自定义泛型类与泛型接口

当我们在类或接口中定义某个成员时,该成员的相关类型是不确定的,而这个类型需要在使用这个类或接口时才可以确定,那么我们可以使用泛型。

  • 当某个类/接口的非静态实例变量的类型不确定,需要在创建对象或子类继承时才能确定
  • 当某个(些)类/接口的非静态方法的形参类型不确定,需要在创建对象或子类继承时才能确定

语法格式:

【修饰符】 class 类名<类型变量列表>extends 父类】 【implements 父接口们】{
    
}
【修饰符】 interface 接口名<类型变量列表>extends 父接口们】{
    
}

注意:

  • <类型变量列表>:可以是一个或多个类型变量,一般都是使用单个的大写字母表示。例如:、<K,V>等。
  • <类型变量列表>中的类型变量不能用于静态成员上。

示例代码:

例如:我们要声明一个学生类,该学生包含姓名、成绩,而此时学生的成绩类型不确定,为什么呢,因为,语文老师希望成绩是“优秀”、“良好”、“及格”、“不及格”,数学老师希望成绩是89.5, 65.0,英语老师希望成绩是’A’,‘B’,‘C’,‘D’,‘E’。那么我们在设计这个学生类时,就可以使用泛型。

public class Student<T>{
    private String name;
    private T score;

    public Student() {
        super();
    }
    public Student(String name, T score) {
        super();
        this.name = name;
        this.score = score;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public T getScore() {
        return score;
    }
    public void setScore(T score) {
        this.score = score;
    }
    @Override
    public String toString() {
        return "姓名:" + name + ", 成绩:" + score;
    }
}

3 使用泛型类与泛型接口小结

在使用这种参数化的类与接口时,我们需要指定泛型变量的实际类型参数:

(1)实际类型参数必须是引用数据类型,不能是基本数据类型

(2)子类继承泛型父类时,子接口继承泛型父接口、或实现类实现泛型父接口时,

  • 指定类型变量对应的实际类型参数,此时子类或实现类不再是泛型类
//ChineseStudent不再是泛型类
public class ChineseStudent extends Student<String>{

    public ChineseStudent() {
        super();
    }

    public ChineseStudent(String name, String score) {
        super(name, score);
    }

}

public class Rectangle implements Comparable<Rectangle>

  • 指定类型变量(该类型变量可以和原来字母一样,也可以换一个字母),此时子类、子接口、实现类仍然是泛型类或泛型接口
public interface Iterable<T>
public interface Collection<E>extends Iterable<E>
public interface List<E>extends Collection<E>
public class ArrayList<E>extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, Serializable

(3)在创建泛型类的对象时指定类型变量对应的实际类型参数

public class TestStudent {
    public static void main(String[] args) {
        //语文老师使用时:
        Student<String> stu1 = new Student<String>("张三", "良好");
        ChineseStudent chineseStudent = new ChineseStudent("张三", "良好");

        //数学老师使用时:
        //Student<double> stu2 = new Student<double>("张三", 90.5);//错误,必须是引用数据类型
        Student<Double> stu2 = new Student<Double>("张三", 90.5);

        //英语老师使用时:
        Student<Character> stu3 = new Student<Character>("张三", 'C');

        //错误的指定
        //Student<Object> stu = new Student<String>();//错误的
    }
}

JDK1.7支持简写形式:Student stu1 = new Student<>(“张三”, “良好”);

指定泛型实参时,必须左右两边一致,不存在多态现象

3 泛型方法

1 泛型方法的调用

在java.util.Arrays数组工具类中,有很多泛型方法,例如:

  • public static List asList(T… a):将实参对象依次添加到一个固定大小的List列表集合中。
  • public static T[] copyOf(T[] original, int newLength):复制任意对象数组,新数组长度为newLength。
import java.util.Arrays;
import java.util.List;

public class TestArrays {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("java", "world", "hello", "atguigu");
        System.out.println(list);

        String[] arr = {"java", "world", "hello"};
        String[] strings = Arrays.copyOf(arr, arr.length * 2);
        System.out.println(Arrays.toString(strings));
    }
}

泛型方法在调用时,由实参的类型确定泛型方法类型变量的具体类型。

2 自定义泛型方法

前面介绍了在定义类、接口时可以声明<类型变量>,在该类的方法和属性定义、接口的方法定义中,这些<类型变量>可被当成普通类型来用。但是,在另外一些情况下,

(1)如果我们定义类、接口时没有使用<类型变量>,但是某个方法形参类型不确定时,这个方法可以单独定义<类型变量>;

(2)另外我们之前说类和接口上的类型形参是不能用于静态方法中,那么当某个静态方法的形参类型不确定时,静态方法可以单独定义<类型变量>。

语法格式:

【修饰符】 <类型变量列表> 返回值类型 方法名(【形参列表】)throws 异常列表】{
    //...
}
  • <类型变量列表>:可以是一个或多个类型变量,一般都是使用单个的大写字母表示。例如:、<K,V>等。

示例代码:

我们编写一个数组工具类,包含可以给任意对象数组进行从小到大排序,调用元素对象的compareTo方法比较元素的大小关系。

public class MyArrays {
    public static <T> void sort(T[] arr){
        for (int i = 1; i < arr.length; i++) {
            for (int j = 0; j < arr.length-i; j++) {
                if(((Comparable<T>)arr[j]).compareTo(arr[j+1])>0){
                    T temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
    }
}

import com.atguigu.generic.Circle;

import java.util.Arrays;

public class MyArraysTest {
    public static void main(String[] args) {
        int[] arr = {3,2,5,1,4};
//		MyArrays.sort(arr);//错误的,因为int[]不是对象数组

        String[] strings = {"hello","java","chai"};
        MyArrays.sort(strings);
        System.out.println(Arrays.toString(strings));

        Circle[] circles = {new Circle(2.0),new Circle(1.2),new Circle(3.0)};
        MyArrays.sort(circles); //编译通过,运行报错,Circle没有实现Comparable接口
    }
}

4 类型变量的上限与泛型的擦除

1 类型变量的上限

当在声明类型变量时,如果不希望这个类型变量代表任意引用数据类型,而是某个系列的引用数据类型,那么可以设定类型变量的上限。

语法格式:

<类型变量  extends 上限>

如果有多个上限

<类型变量  extends 上限1 & 上限2>

如果多个上限中有类有接口,那么只能有一个类,而且必须写在最左边。接口的话,可以多个。

如果在声明<类型变量>时没有指定任何上限,默认上限是java.lang.Object。

1、定义泛型类的类型变量时指定上限

例如:我们要声明一个两个数算术运算的工具类,要求两个数必须是Number数字类型,并且实现Comparable接口。

import java.math.BigDecimal;
import java.math.BigInteger;

public class NumberTools<T extends Number & Comparable<T>>{
    private T a;
    private T b;

    public NumberTools(T a, T b) {
        super();
        this.a = a;
        this.b = b;
    }

    public T getSum(){
        if(a instanceof BigInteger){
            return (T) ((BigInteger) a).add((BigInteger)b);
        }else if(a instanceof BigDecimal){
            return (T) ((BigDecimal) a).add((BigDecimal)b);
        }else if(a instanceof Byte){
            return (T)(Byte.valueOf((byte)((Byte)a+(Byte)b)));
        }else if(a instanceof Short){
            return (T)(Short.valueOf((short)((Short)a+(Short)b)));
        }else if(a instanceof Integer){
            return (T)(Integer.valueOf((Integer)a+(Integer)b));
        }else if(a instanceof Long){
            return (T)(Long.valueOf((Long)a+(Long)b));
        }else if(a instanceof Float){
            return (T)(Float.valueOf((Float)a+(Float)b));
        }else if(a instanceof Double){
            return (T)(Double.valueOf((Double)a+(Double)b));
        }
        throw new UnsupportedOperationException("不支持该操作");
    }

    public T getSubtract(){
        if(a instanceof BigInteger){
            return (T) ((BigInteger) a).subtract((BigInteger)b);
        }else if(a instanceof BigDecimal){
            return (T) ((BigDecimal) a).subtract((BigDecimal)b);
        }else if(a instanceof Byte){
            return (T)(Byte.valueOf((byte)((Byte)a-(Byte)b)));
        }else if(a instanceof Short){
            return (T)(Short.valueOf((short)((Short)a-(Short)b)));
        }else if(a instanceof Integer){
            return (T)(Integer.valueOf((Integer)a-(Integer)b));
        }else if(a instanceof Long){
            return (T)(Long.valueOf((Long)a-(Long)b));
        }else if(a instanceof Float){
            return (T)(Float.valueOf((Float)a-(Float)b));
        }else if(a instanceof Double){
            return (T)(Double.valueOf((Double)a-(Double)b));
        }
        throw new UnsupportedOperationException("不支持该操作");
    }
}

测试类

public class NumberToolsTest {
    public static void main(String[] args) {
        NumberTools<Integer> tools = new NumberTools<Integer>(8,5);
        Integer sum = tools.getSum();
        System.out.println("sum = " + sum);
        Integer subtract = tools.getSubtract();
        System.out.println("subtract = " + subtract);
    }
}

2、定义泛型方法的类型变量时指定上限

我们编写一个数组工具类,包含可以给任意对象数组进行从小到大排序,调用元素对象的compareTo方法比较元素的大小关系。要求数组的元素类型必须是java.lang.Comparable接口类型。

public class MyArrays {
    public static <T extends Comparable<T>> void sort(T[] arr){
        for (int i = 1; i < arr.length; i++) {
            for (int j = 0; j < arr.length-i; j++) {
                if(arr[j].compareTo(arr[j+1])>0){
                    T temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
    }
}

测试类

import com.atguigu.generic.Circle;

import java.util.Arrays;

public class MyArraysTest {
    public static void main(String[] args) {
        int[] arr = {3,2,5,1,4};
//		MyArrays.sort(arr);//错误的,因为int[]不是对象数组

        String[] strings = {"hello","java","chai"};
        MyArrays.sort(strings);
        System.out.println(Arrays.toString(strings));

        Circle[] circles = {new Circle(2.0),new Circle(1.2),new Circle(3.0)};
//        MyArrays.sort(circles); //编译报错
    }
}

2 泛型擦除

当使用参数化类型的类或接口时,如果没有指定泛型,那么会怎么样呢?

会发生泛型擦除,自动按照最左边的第一个上限处理。如果没有指定上限,上限即为Object。

import java.util.ArrayList;
import java.util.Collection;

public class TestErase {
    public static void main(String[] args) {
        NumberTools tools = new NumberTools(8,5);
        Number sum = tools.getSum();//自动按照Number处理
        System.out.println("sum = " + sum);
        Number subtract = tools.getSubtract();
        System.out.println("subtract = " + subtract);

        Collection coll = new ArrayList();
        coll.add("hello");
        coll.add(1);
        for (Object o : coll) {//自动按照Object处理
            System.out.println(o);
        }
    }
}

5 类型通配符

1 Java泛型指定限制问题

声明一个方法,形参是Collection,但是元素类型不确定,怎么办?

import java.util.ArrayList;
import java.util.Collection;

public class TestProblem {
    public static void m1(Collection<Object> coll){
        for (Object o : coll) {
            System.out.println(o);
        }
    }
    public static void m2(Collection coll){
        for (Object o : coll) {
            System.out.println(o);
        }
    }
    public static <T> void m3(Collection<T> coll){
        for (T o : coll) {
            System.out.println(o);
        }
    }
    
    public static void main(String[] args) {
        m1(new ArrayList<Object>());//Collection<Object> coll = new ArrayList<Object>();
        m1(new ArrayList<>());//Collection<Object> coll = new ArrayList<>();//与上面完全等价
        m1(new ArrayList());//Collection<Object> coll = new ArrayList();//有警告
//        m1(new ArrayList<String>());//Collection<Object> coll = new ArrayList<String>();//错误

        //编译看左边,左边泛型擦除,此处泛型按照Object处理,右边泛型指定啥都没用
        m2(new ArrayList<Object>());//Collection coll = new ArrayList<Object>();
        m2(new ArrayList<>());//Collection coll = new ArrayList<>();//与上面完全等价
        m2(new ArrayList());//Collection coll = new ArrayList();//泛型擦除
        m2(new ArrayList<String>());//Collection coll = new ArrayList<String>();

        m3(new ArrayList<Object>());//Collection<Object> coll = new ArrayList<Object>();
        m3(new ArrayList<>());//Collection<> coll = new ArrayList<>();//与上面完全等价
        m3(new ArrayList());//Collection<Object> coll = new ArrayList();//有警告
        m3(new ArrayList<String>());//Collection<String> coll = new ArrayList<String>();
    }
}

2 类型通配符

当我们声明一个变量/形参时,这个变量/形参的类型是一个泛型类或泛型接口,例如:Comparator类型,但是我们仍然无法确定这个泛型类或泛型接口的类型变量的具体类型,此时我们考虑使用类型通配符 ? 。

import java.util.ArrayList;
import java.util.Collection;

public class TestWildcard {
    public static void m4(Collection<?> coll){
        for (Object o : coll) {
            System.out.println(o);
        }
    }

    public static void main(String[] args) {
        //右边泛型指定为任意类型或不指定都可以
        m4(new ArrayList<Object>());//Collection<?> coll = new ArrayList<Object>();
        m4(new ArrayList<>());//Collection<?> coll = new ArrayList<>();
        m4(new ArrayList());//Collection<?> coll = new ArrayList();
        m4(new ArrayList<String>());//Collection<?> coll = new ArrayList<String>();
    }
}

3 类型通配符的三种使用形式

类型通配符 ? 有三种使用形式:

  • <?>:完整形式为:类名<?> 或接口名<?>,此时?代表任意类型。
  • <? extends 上限>:完整形式为:类名<? extends 上限类型> 或接口名<? extends 上限类型>,此时?代表上限类型本身或者上限的子类,即?代表 <= 上限的类型。
  • <? super 下限>:完整形式为:类名\<? super 下限类型> 或接口名\<? super 下限类型>,此时?代表下限类型本身或者下限的父类,即?代表>= 下限的类型。

案例:

声明一个集合工具类MyCollections,要求包含:

  • public static boolean different(Collection<?> c1, Collection<?> c2):比较两个Collection集合,此时两个Collection集合的泛型可以是任意类型,如果两个集合中没有相同的元素,则返回true,否则返回false。
  • public static void addAll(Collection<? super T> c1, T… args):可以将任意类型的多个对象添加到一个Collection集合中,此时要求Collection集合的泛型指定必须>=元素类型。
  • public static void copy(Collection<? super T> dest,Collection<? extends T> src):可以将一个Collection集合的元素复制到另一个Collection集合中,此时要求原Collection泛型的类型<=目标Collection的泛型类型。
import java.util.Collection;

public class MyCollections {
    public static boolean different(Collection<?> c1, Collection<?> c2){
        for (Object o : c1) {
            if(c2.contains(o)){
                return false;
            }
        }
        return true;
    }

    public static <T> void addAll(Collection<? super T> c1, T... args){
        for (int i = 0; i < args.length; i++) {
            c1.add(args[i]);
        }
    }

    public static <T> void copy(Collection<? super T> dest,Collection<? extends T> src){
        for (T t : src) {
            dest.add(t);
        }
    }

}

测试类

import java.util.ArrayList;
import java.util.Collection;

public class MyCollectionsTest {
    public static void main(String[] args) {
        Collection<Integer> c1 = new ArrayList<Integer>();
        MyCollections.addAll(c1,1,2,3,4,5);
        System.out.println("c1 = " + c1);

        Collection<String> c2 = new ArrayList<String>();
        MyCollections.addAll(c2,"hello","java","world");
        System.out.println("c2 = " + c2);

        System.out.println("c1 != c2 " + MyCollections.different(c1, c2));

        Collection<Object> c3 = new ArrayList<>();
        MyCollections.copy(c3,c1);
        MyCollections.copy(c3,c2);
        System.out.println("c3 = " + c3);
    }
}

4 使用类型通配符来指定类型参数的问题

import java.util.ArrayList;
import java.util.Collection;

public class TestWildcardProblem {
    public static void main(String[] args) {
        Collection<?> c1 = new ArrayList<>();
//        c1.add(new Object());
//        c1.add("hello");
//        c1.add(1);
        //不能添加,c1只读


        Collection<? extends Object> c2 = new ArrayList<>();
//        c2.add(new Object());
//        c2.add("hello");
//        c2.add(1);
        //不能添加,c2只读

        Collection<? super Number> c3 = new ArrayList<>();
//        c3.add(new Object());
//        c3.add("hello");
        c3.add(1);
        //可以添加Number对象或Number子类对象
    }
}

小结:泛型的引入使得我们不需要一直进行一个强制转换类型,省去了一大堆代码,此外泛型还有他独有的方法类型,可直接传入一个类名。


总结:Map遍历的的key,value可以与泛型进行结合,使得遍历时无需进行一个强制转换。

人之所以活得累,是因为放不下架子,撕不开面子,解不开情节。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值