java__基础--泛型(较为全面的)

原创 2018年04月16日 16:41:57

前景

今天学到了泛型,说实话,泛型是我遇到的又一个难点,真的有点难呢,书上的例子过于激进,不太适合我这种小白学,于是到网上又找了一个例子来加深理解

泛型

为什么要用泛型?

哈哈,学习一个新知识点的时候总会问自己一个为什么。泛型泛型在于这个泛字,一段代码适用于不同类型的对象所重用核心概念:告诉编译器你想使用什么类型,然后编译器去帮你实现各种细节–>thinking in java
要细分一下的话,有这么几小点

  • 类型安全
    • 编译期就能够检查出因为类型出错导致的ClassCastExpection异常
  • 消除了强制类型转换
    • 由于一开始就指明了类型,就消除了很多强制类型转换
  • 代码可读性提升,更方便
    • 让程序员少写了类型转换,编译器帮你完成

小实例对比

旧版本的一个类实现坐标

package generic;

public class IntegerPoint 
{
    private Integer x;
    private Integer y;

    public Integer getX()
    {
        return this.x;
    }

    public Integer getY()
    {
        return this.y;
    }

    public void setX(Integer x)
    {
        this.x = x;
    }

    public void setY(Integer y)
    {
        this.y = y;
    }

}

class FloatPoint
{
    private Float x;
    private Float y;

    public Float getX()
    {
        return this.x;
    }

    public Float getY()
    {
        return this.y;
    }

    public void setX(Float x)
    {
        this.x = x;
    }

    public void setY(Float y)
    {
        this.y = y;
    }

}

class ObjectPoint
{
    private Object x;
    private Object y;

    public Object getX()
    {
        return this.x;
    }

    public Object getY()
    {
        return this.y;
    }

    public void setX(Object x)
    {
        this.x = x;
    }

    public void setY(Object y)
    {
        this.y = y;
    }
}
--------
实验代码:
IntegerPoint integerPoint = new IntegerPoint();
        integerPoint.setX(Integer.valueOf(10));
        integerPoint.setY(Integer.valueOf(20));

        ObjectPoint objectPoint = new ObjectPoint();
        objectPoint.setX(Integer.valueOf(10));
        System.out.println(integerPoint.getX().getClass().getName());

上面两个是具体类型Integer和Float实现一个坐标,在没有引入泛型之前是用Object类来实现的也就是第三段代码,我们传递进去一个Integer类型的对象,经过类型转换之后,get方法得到的就是一个Integer类型变量。这种方法就是在泛型之前出现的方法,因为所有的类都是继承Object的,所以可以传递上去。

使用泛型

class Point<T>
{
    private T x;
    private T y;

    public T getX()
    {
        return this.x;
    }

    public T getY()
    {
        return this.y;
    }

    public void setX(T x) 
    {
        this.x = x;
    }

    public void setY(T y)
    {
        this.y = y;
    }

}

通过和上面的例子进行对比,我们可以看出泛型的魔力,我们用一个T来代表任意类型,意思就是说,我接受所有继承自Object类的类型,并且所有方法中的类型自动转变成你传递进来的类型。但是把,实际上泛型只是为了方便人的,虚拟机并不认识什么泛型

多类型变量

多类型变量,支持多个类型参数的传入

class Point<A,B,C,D>

实例:我们现在要在坐标给它命名x1,x2

class Point<T,N>
{
    private T x;
    private T y;
    private N name;

    public T getX()
    {
        return this.x;
    }

    public T getY()
    {
        return this.y;
    }

    public N getName()
    {
        return this.name;
    }

    public void setX(T x) 
    {
        this.x = x;
    }

    public void setY(T y)
    {
        this.y = y;
    }

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

}

例子可能不太好,但是能说明问题

字母规范

字母虽然是可以随便起名字的,但是有些约定俗称的小规定

  • E—-Element,常用在collection里面代表集合的元素
  • K,V–key,value 代表Map的键值对
  • N–Number 数字
  • T—Type,类型

类型擦除

原理

之所以把这个放在最前面是因为,它关乎到原理实现,所以先揪出来看看,快拿出小本儿记下来吧~重点
哈哈,没想到吧!我们把这两段代码拿出来看看

class ObjectPoint
{
    private Object x;
    private Object y;

    public Object getX()
    {
        return this.x;
    }

    public Object getY()
    {
        return this.y;
    }

    public void setX(Object x)
    {
        this.x = x;
    }

    public void setY(Object y)
    {
        this.y = y;
    }
}
---------------分割线------------------
class Point<T>
{
    private T x;
    private T y;

    public T getX()
    {
        return this.x;
    }

    public T getY()
    {
        return this.y;
    }

    public void setX(T x) 
    {
        this.x = x;
    }

    public void setY(T y)
    {
        this.y = y;
    }

}

其实,在定义一个泛型的时候,都自动提供了响应的原始类型,麻叫原始类型?就是类型擦除,类型擦除的意思就是去掉类型变量T,然后用限定类型代替,没有限定类型的就用Object代替。换句话说,下面这段代码的原始类型就是上面的这段代码。之后呢,该进行类型转换的转换,从而得到你想要的那种类型。其实,笔者觉得,泛型只是设计出来给人用的。

类型补偿:边界

我们知道,泛型运行时被擦除成原始类型,这使得很多操作无法进行.
如果没有指明边界,类型参数将被擦除为 Object。
如果我们想要让参数保留一个边界,可以给参数设置一个边界,extends就是,泛型参数将会被擦除到它的第一个边界(边界可以有多个),这样即使运行时擦除后也会有范围。

泛型类、泛型接口

泛型类最常见的作用就是容纳不同数据类型的容器类了。

小实例–thinking in java

持有两个对象的元组

package generic;

public class TwoTuple<A, B> 
{
    private final A first;
    private final B second;

    public TwoTuple(A first, B second)
    {
        this.first = first;
        this.second = second;
    }

    @Override
    public String toString()
    {
        return "first: "+this.first+" ;second: "+this.second;
    }
}

元组数量扩充

package generic;

public class ThreeTuple<A, B, C> extends TwoTuple<A, B> 
{
    private final C third;

    public ThreeTuple(A first, B second,C third) {
        super(first, second);
        this.third = third;
    }

    @Override
    public String toString()
    {
        return super.toString()+"; third: "+this.third;
    }
}

重载实现,构建容器生成器

package generic;

public class TupleGenerator 
{
    public static <A, B> TwoTuple<A, B> tuple(A a, B b)
    {
        return new TwoTuple<A, B>(a, b);
    }

    public static <A, B, C> ThreeTuple<A, B, C> tuple(A a, B b, C c)
    {
        return new ThreeTuple<A, B, C>(a, b, c);
    }

}

测试代码

package generic;
import generic.TupleGenerator;

public class TupleTest 
{
    public static void main(String[] args)
    {
        System.out.println(TupleGenerator.tuple("12", 12));
    }
}

用泛型来设计复杂的模型

package generic;

import java.util.ArrayList;
import java.util.Random;


interface Generator<T>
{
    T generateNext();
}



class Products 
{
    private int id;
    private String description;
    private double price;

    public Products(int id, String description, double price)
    {
        this.id = id;
        this.description = description;
        this.price = price;
    }


    public void changePrice(double price)
    {
        this.price = price;
    }

    @Override
    public String toString()
    {
        return "id: "+this.id+" description:"+this.description+" price"+this.price;
    }

    static Generator<Products> generator = new Generator<Products>() 
    {
        int id = 0;
        public Products generateNext()
        {
            Random rand = new Random();
            double price = rand.nextDouble()*100.0;
            return new Products(id++, "test"+id, price);
        }
    };

}


class shelf extends ArrayList<Products>
{
    public shelf(int numberProducts)
    {
        for (int i = 0; i < numberProducts; i++)
        {
            this.add(Products.generator.generateNext());
        }
    }
}


public class Store extends ArrayList<shelf>
{
    public Store(int numberShelf, int numberProducts)
    {
        for (int i = 0; i < numberShelf; i++) 
        {
            this.add(new shelf(numberProducts));
        }
    }

    @Override
    public String toString()
    {
        StringBuilder builder = new StringBuilder();
        for (shelf s : this) 
        {
            for (Products p : s)
            {
                builder.append(p);
                builder.append('\n');
            }
        }
        return builder.toString();
    }

    public static void main(String[] args)
    {
        Store store = new Store(10, 20);
        System.out.println(store);
    }
}

这个可能稍微有些复杂,意思就是说,有一个商店,有几个货架,每个货架上有多少商品。这个里面也包含了一个生成器,实现了泛型接口,是个挺好的例子,练练手挺不错的。

泛型方法

泛型方法是指使用泛型的方法,如果它所在的类是个泛型类,那就很简单了,直接使用类声明的参数。

如果一个方法所在的类不是泛型类,或者他想要处理不同于泛型类声明类型的数据,那它就需要自己声明类型

小例子

package test;

import java.util.ArrayList;

public class Plus 
{
    public <E> ArrayList<E> plus(ArrayList<E> ex1, ArrayList<E> ex2)
    {
        ArrayList<E> result = new ArrayList<>();
        result.addAll(ex2);
        return result;
    }
}

一个方法里头把两个数组列表相加,但是不是泛型类,所以< E>指明了参数列表类型。

通配符

为什么要用通配符?通配符的用处是什么

当传入类型的时候我们希望不要那么的单一,指定啥就是啥,我们有的时候希望它处于一个范围之中

package tongpeifu;

public class Pair<T> 
{
    private T first;
    private T second;

    public Pair(T first, T second) 
    {
        this.first = first;
        this.second = second;
    }
    public T getFirst()
    {
        return this.first;
    }

    public T getSecond()
    {
        return this.second;
    }

    public void setFirst(T first)
    {
        this.first = first;
    }

    public void setSecond(T second)
    {
        this.second = second;
    }   
}

员工类:

package tongpeifu;

public class Employee
{
    private String name;
    private double salary;

    public Employee(String name, double salary) 
    {
        this.name = name;
        this.salary = salary;
    }

    public String getName()
    {
        return this.name;
    }

}

经理类:

package tongpeifu;

public class Manager extends Employee
{
    private double bonus;

    public Manager(String name, double salary, double bonus) 
    {
        super(name, salary);
        this.bonus = bonus;
    }

    public String getName()
    {
        return super.getName();
    }
}

测试类,用来输出容器中的一对搭档

package tongpeifu;

public class PairTest 
{
    public static void PrintBuddies(Pair<Employee> p)
    {
        Employee first = p.getFirst();
        Employee second = p.getSecond();
        System.out.println(first.getName()+"的搭档是"+second.getName());
    }


    public static void main(String[] args)
    {
        Employee e1 = new Employee("ZhangSan", 1000);
        Employee e2 = new Employee("LiSi", 1000);

        PairTest.PrintBuddies(new Pair<Employee>(e1, e2));
    }
}

理解一下:
正如我们知道的那样,泛型通常和一些容器一起用,Pair这个容器包含了两个相同类型的对象,在PairTest这个类中,输出搭档这个方法的参数是指定了类型为Employee这样的一个容器对象,所以也就只能打印Employee之间的搭档了,那我要打印经理之间的搭档,还能传入类型为Manager的对象吗?答案当然是不行的,重点记住,虽然Manager继承Employee,但是泛型类Pair<Employee>和Pair<Manger>它们两个之间没有继承关系!也就不能用多态了- -,这个时候就轮到我通配符大哥上场了

使用通配符

package tongpeifu;

public class PairTest 
{
    public static void PrintBuddies(Pair<? extends Employee> p)
    {
        Employee first = p.getFirst();
        Employee second = p.getSecond();
        System.out.println(first.getName()+"的搭档是"+second.getName());
    }


    public static void main(String[] args)
    {
        Manager m1 = new Manager("Max", 2000, 100);
        Manager m2 = new Manager("Bob", 10000, 1000);
        PairTest.PrintBuddies(new Pair<Manager>(m1, m2));
    }
}

解读一下:
此时我们可以看到PrintBuddie方法接受的是不再是固定的Employee类型而是一个范围,只要是继承Employee的都行。这便是通配符的用处。

注意点:
当用通配符的时候,由于类型的无法确定性,所以我们无法传递任何具体的类型进去,但是我们可以读取,因为我们已经知道了它的祖先类,向上转型都是可以的
实例

public static void main(String[] args)
    {
        Employee e1 = new Employee("001", 2000);
        Employee e2 = new Employee("001", 3000);
        Employee e3 = new Employee("003", 4000);

        Manager m1 = new Manager("004", 3000, 200);
        Manager m2 = new Manager("005", 30000, 1000);

        //指定类型为Employee,由于已经确定了类型所以可以使用setSecond
        Pair<Employee> p1 = new Pair<Employee>(e1, e2);
        System.out.println(p1.getFirst().getName());
        p1.setSecond(m1);

        // 未明确的指定类型,不能擅自将e1,或者m1这种具体类型的参数传递进去!
        Pair<? extends Employee> p2 = new Pair<Manager>(m1, m2);
        Pair<? extends Employee> p3 = new Pair<? extends Employee>(e1, e2);
        //p3.setSecond(e1);
        System.out.println(p2.getFirst().getName());
        // p2.setSecond(e1);

    }

因此我们可以得到一个关系:Pair< Manager> 和 Pair< Employee>它们有一个共同父类Pair< ? extends Employee>

如果你看上面的例子比较难以理解的话,举个再简单的例子

package tongpeifu;

import java.util.ArrayList;

class fruits{}

class Apple extends fruits{}

class Orange extends fruits{}

public class Test 
{
    public static void main(String[] args)
    {
        ArrayList<fruits> fruits = new ArrayList<>();
        fruits.add(new Apple());
        fruits.add(new Orange());

        ArrayList<? extends fruits> fruits2= new ArrayList<>();

        //  错误Error
        fruits2.add(new Apple());
        fruits2.add(new Orange());

    }
}

这样就比较明了了。其实我们可以发现,要想写入具体对象的时候,就得有一个下限,比如这里我们只知道它是继承自Employee,如果能传递Employee对象,那么它的子类所有的对象都可以传递了(向上转型),那它这里并不知道到底有多少子类了

Pair<? super Manager> p3 = new Pair<Employee>(m1, m2);
        p3.setSecond(m2);

        // error
        p3.setSecond(e1);

可以总结到,Pair< ? super Manager>的两个子类型是Pair< Employee>和Pair< Manager>

上界和下界比较

上界和下界它们都是为了更灵活

  • < ? super xx>上界:方便写入或者比较,可以传递进xx类型及其父类型,这个类型可以使用xx及其子类的写方法或者比较方法。
  • < ? extends xx>下界:方便读数据,可以读取xx和xx子类对象
    一句话概况,读取用下界,操作(写入、比较等)用上界

总结

泛型部分的内容,初次学习还是比较难以理解的,学完了之后就觉得其实还行了,内容多了些,花了几天的时间消化,由于是初次学习,更多的还是总结为主,可能有些方面自己理解的还不够全面

感谢:
常见面试题:https://blog.csdn.net/qq_25827845/article/details/76735277
好的讲解:https://blog.csdn.net/u011240877/article/details/53545041#%E9%80%9A%E9%85%8D%E7%AC%A6%E6%AF%94%E8%BE%83

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_41376740/article/details/79962702

Java__接口、内部类和Java_API基础

  • 2011年06月11日 21:44
  • 591KB
  • 下载

java核心技术卷2

  • 2018年03月03日 15:19
  • 120.6MB
  • 下载

21天学通C#,适合初学者的c#书

  • 2011年07月28日 16:09
  • 12.74MB
  • 下载

java__泛型

package cn.itcast.genrictiry;import java.util.ArrayList; /* 泛型是jdk1.5使用的新特性。 泛型的好处: 1. 将运行时的异...
  • qq_20261343
  • qq_20261343
  • 2015-09-13 17:55:51
  • 216

网格安全概述-较为全面

  • 2008年11月14日 10:11
  • 8.05MB
  • 下载

Java 泛型使用基础

所谓泛型,就是变量类型的参数化。   泛型是JDK1.5中一个最重要的特征。通过引入泛型,我们将获得编译时类型的安全和运行时更小的抛出ClassCastException的可能。   在JDK1....
  • u012468540
  • u012468540
  • 2015-10-28 16:43:40
  • 518

较为全面的Oracle函数总结

附录: 1、SQL 简介 2、SQL 操作符 3、Oracle 常用数据类型 4、Oracle 函数 5、[转] Oracle 常用SQL语法 字符串函数 LENGTH()    字符长度 LE...
  • w405722907
  • w405722907
  • 2017-05-17 17:17:57
  • 254

较为全面的程序部署

一).创建部署项目 1. 在“文件”菜单上指向“添加项目”,然后选择“新建项目”。 2. 在“添加新项目”对话框中,选择“项目类型”窗格中的“安装和部署项目”,然后选择“模板”窗格中的“安装项目”。在...
  • lincomin
  • lincomin
  • 2006-10-20 15:18:00
  • 909

较为全面的ADC驱动

1、ADC硬件原理概述 我们从上面的结构图和数据手册可以知道,该ADC模块总共有8个通道可以进行模拟信号的输入,分别是AIN0、AIN1、AIN2、AIN3、YM、YP、XM、XP。那么AD...
  • Jijiahao95
  • Jijiahao95
  • 2017-07-18 15:25:15
  • 156
收藏助手
不良信息举报
您举报文章:java__基础--泛型(较为全面的)
举报原因:
原因补充:

(最多只允许输入30个字)