java基础--07(java泛型)

目录

 

 1.java泛型

2. 泛型类

3.泛型接口

4.泛型方法:

  5.元组:


 1.java泛型

      需要理解泛型的边界在哪里?只有理解边界所在,你才能成为程序高手,因为只有知道了某个技术不能做什么,你才能更好的做到所能做的。

      jdk1.5版本以后出现的一个安全机制。表现格式:< >  只要带有<>的类或者接口,都属于带有类型参数的类或者接口,在使用这些类或者接口时,必须给<>中传递一个具体的引用数据类型。

2. 泛型类

     泛型的类的定义:

     

     泛型类的使用:

        

      泛型类的注意点:泛型类的类型约束只在编译的时候有效,JVM编译正确后会将泛型的信息擦除掉,并且在对象进入和离开方法的边界处,添加类型检查和类型转化的方法。

     泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛形的java程序后,生成的class文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。

     

       验证泛型类的类型约束只在编译的时候有效:

       

     

 进一步 验证运行时没有泛型类型的信息:先编译java文件    

  泛型类的继承:

  • 实现类的要是重写父类的方法,返回值的类型是要和父类一样的!

  • 类上声明的泛形只对非静态成员有效

​​​​​​​     这样使用继承可定是不行的,因为Integer虽然继承自Number,但是GenericClassExample<Integer>并不继承自GenericClassExample<Number>

 那么泛型类如何使用继承呢?有三种方法

   1.使用?,但是这样会使泛型失去意义

   

   

  2.给泛型加上上边界 ? extends E

   

3.加入下边界  ? super E

 

 

 泛型通配符的使用准则:

  有上限下限的泛型通配符:
   ,当我们使用?号通配符的时候:就只能调对象与类型无关的方法,不能调用对象与类型有关的方法。无论是设定通配符上限还是下限,都是不能操作与对象有关的方法,只要涉及到了通配符,它的类型都是不确定的!

  • 如果参数之间的类型有依赖关系,或者返回值是与参数之间有依赖关系的。那么就使用泛型方法

  • 如果没有依赖关系的,就使用通配符,通配符会灵活一些.

 

3.泛型接口

     上面讲的规则同样适用泛型接口    

泛型可以应用于接口,例如生成器(常用的场景),他也是一种专门负责创建对象的类,这是工厂方法设计模式的一种应用,不过当使用生成器来创建新的对象时,他不需要任何的参数,而工厂方法一般需要参数,也就是说生成器无需额外的信息就知道如何创建新的对象。

   泛型接口的定义:

   

 

   

    泛型类实现泛型接口:

    

   泛型接口的第二个例子:

   

 泛型接口的用处,可以编写一个类来实现泛型接口,它能够随机生成不同类型的Coffee对象。

 

 

public class Coffee {
    private static long counter = 0;
    private final long id = counter++;

    @Override
    public String toString() {
        return getClass().getSimpleName() + " " + id;
    }
}


public class Latte extends Coffee {
}

public class Mocha extends Coffee {
}

public class Americano extends Coffee {
}

 不仅实现了泛型的接口,

public class CoffeeGenerator implements Generator<Coffee> {

    private Class[] types = {Latte.class, Mocha.class, Americano.class};

    private static Random random = new Random(47);

    @Override
    public Coffee next() {
        try {
            return (Coffee) types[random.nextInt(types.length)].newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        CoffeeGenerator gen = new CoffeeGenerator();
        for (int i = 0; i < 3; i++) {
            System.out.println(gen.next());
        }
    }
}

 结果:

  

 但是现在我觉得这种生成的方式我不喜欢,我要编写一个实现了Iterable的Coffee对象生成器,我们的一个选择时重写这个类,令其实现Iterable接口,不过你并不是总能拥有源代码的控制权,并且除非必须这么做,否则我们也不愿意重写这个类,我们还有另外的一种选择就是创建一个适配器,来实现所需的接口。

public class IterableCoffeeGeneratorAdapter extends CoffeeGenerator implements Iterable<Coffee> {
    private int size = 0;

    public IterableCoffeeGeneratorAdapter(int size) {
        this.size = size;
    }

    @Override
    public Iterator<Coffee> iterator() {
        return new Iterator<Coffee>() {
            int count = size;

            @Override
            public boolean hasNext() {
                return count > 0;
            }

            @Override
            public Coffee next() {
                count--;
                return IterableCoffeeGeneratorAdapter.super.next();
            }
        };
    }

    public static void main(String[] args) {
        for (Coffee coffee : new IterableCoffeeGeneratorAdapter(3)) {
            System.out.println(coffee);
        }
    }
}

 实现了Iterable接口,就必须要实现iterator()方法,这个方法返回的是一个Iterator对象,有了这个Iterator对象才能在循环语句中去是使用这个适配器对象。ok,这个使用的是匿名内部类的方式来实现的Iterator的逻辑。当然可以使用内部类的方式来实现这个Iterator对象。

 

public class IterableCoffeeGeneratorAdapter extends CoffeeGenerator implements Iterable<Coffee> {
    private int size;

    public IterableCoffeeGeneratorAdapter(int size) {
        this.size = size;
    }

    class CoffeeIterator implements Iterator<Coffee> {
        int count = size;

        @Override
        public boolean hasNext() {
            return count > 0;
        }

        @Override
        public Coffee next() {
            count--;
            return IterableCoffeeGeneratorAdapter.super.next();
        }
    }

    @Override
    public Iterator<Coffee> iterator() {
        return new CoffeeIterator();
    }

    public static void main(String[] args) {
        for (Coffee coffee : new IterableCoffeeGeneratorAdapter(3)) {
            System.out.println(coffee);
        }
    }
}

 测试:

  

4.泛型方法:

  在任何时候,如果使用泛型方法可以取代将整个类泛型化,那么就应该只使用泛型方法。因为它可以使事情更清楚明白,另外对于一个static的方法而言,无法访问泛型类的类型参数,所以如果static方法需要泛型的能力,就必须使其成为泛型的方法。

  泛型方法必须要有泛型标示符,泛型标示符和方法返回值没啥关系。

  

  泛型方法的使用:泛型方法传入的参数类型和泛型类的没啥关系,泛型类中的非泛型方法比如handleSomething,他的返回值类型就和泛型类相关。

  

  泛型方法里面的字母和泛型类的是一样的。但是他们真的没啥关系

  

  泛型字母的含义:

  

     

public class New {

    public static <K, V> Map<K, V> map() {
        return new HashMap<K, V>();
    }

    public static <T> List<T> list() {
        return new ArrayList<T>();
    }

    public static <T> List<T> llist() {
        return new LinkedList<T>();
    }


    public static <T> Set<T> set() {
        return new HashSet<T>();
    }

    public static void main(String[] args) {
        List<String> strList = New.list();
    }
}

  泛型方法与可变参数也能够很好的共存,

  

  欧克,之前的生成器我们使用泛型的方式来实现:

public interface Generator<T> {
    T next();
}

public class BaseGenerator<T> implements Generator<T> {
    private Class<T> type;

    public BaseGenerator(Class<T> type) {
        this.type = type;
    }

    @Override
    public T next() {
        try {
            return type.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
    public static <T> Generator<T> create(Class<T> type){
        return new BaseGenerator<T>(type);
    }
}

 测试:

public class Test4 {
    public static void main(String[] args) {
        Generator<Coffee> generator = BaseGenerator.create(Coffee.class);
        Coffee coffee = generator.next();
        System.out.println(coffee);
    }
}

 

 

  5.元组:

   将一组对象直接打包成一个单一的对象,这个容器对象允许读取,但是不允许向其中存放新的对象。

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

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

    public String toString() {
        return "(" + first + "," + second + ")";
    }
}

  final使上面的两个对象first和second不能被修改。

  如果想要实现更长长度的元组,可以使用继承的方式:

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

    public ThreeTuple(A a, B b, C c) {
        super(a, b);
        this.third = c;
    }

    public String toString() {
        return "(" + first + "," + second + third + ")";
    }
}



public class FourTuple<A, B, C, D> extends ThreeTuple<A, B, C> {
    public final D fourth;

    public FourTuple(A a, B b, C c, D d) {
        super(a, b, c);
        this.fourth = d;
    }

    public String toString() {
        return "(" + first + "," + second + "," + third + "," + fourth + ")";
    }
}

  那这个元组到底又什么用呢?它可以返回多个对象值。比如:

public class Test1 {
    static TwoTuple<String, Integer> f() {
        return new TwoTuple<String, Integer>("hi", 47);
    }

    static ThreeTuple<Amphibian, String, Integer> g() {
        return new ThreeTuple<Amphibian, String, Integer>(new Amphibian(), "hi", 47);
    }

    static FourTuple<Vehicle, Amphibian, String, Integer> h() {
        return new FourTuple<Vehicle, Amphibian, String, Integer>
                (new Vehicle(), new Amphibian(), "hi", 47);
    }

}

class Amphibian {
}

class Vehicle {
}

 

类型擦除:

问题:

public class Test5 {
    public static void main(String[] args) {
        Class aClass = new ArrayList<String>().getClass();
        Class bClass = new ArrayList<Integer>().getClass();
        System.out.println(aClass == bClass);
    }
}

  

 在泛型的内部,无法获得任何有关泛型参数类型的信息。泛型类型参数将擦除到他的第一个边界,当你希望代码能够跨多个类工作时,使用泛型才有帮助。

 使用泛型创建数组的时候,推荐使用Array.newInstance(),

public class ArrayMaker<T> {
    private Class<T> kind;

    public ArrayMaker(Class<T> kind) {
        this.kind = kind;
    }

    public T[] create(int size) {
        return (T[]) Array.newInstance(kind, size);
    }
}

public class Test1 {
    public static void main(String[] args) {
        ArrayMaker<String> arrayMaker = new ArrayMaker<String>(String.class);
        String[] strings = arrayMaker.create(3);
    }
}

 因为有类型擦除,kind实际将被存储为Class,所以Array.newInstance()并没有拥有kind所蕴含的类型信息,所以他必须要转型,

就像下面这样:

 

 如果创建的是一个容器而不是一个数组,情况又有所不同,

泛型中的所有动作都发生在边界处,对传递进来的值进行额外的编译检查(就是说我们传值进去的时候,不需要做转型的操作,作,编译器帮我们做了),并插入对传递出去的值的转型(这也是一样),所以这样代码的噪声就更小了。

泛型的擦除导致丢失了在泛型代码中执行某些操作的能力,任何在运行时需要知道的确切类型信息的操作都无法工作:例如instanceof,new ,因为所有关于参数的类型信息都丢失了,无论何时必须提醒自己,他只是一个Object。

这种情况这么办呢?如果可以绕过这样的编程,当然可以,如果绕不过那么就需要引入类型标签来对擦除进行补偿,instanceof关键字可以转为使用动态的isInstance()方法,instanceof:这个对象是不是这种类型,isInstance()方法:对象能不能被转化为这个类。

class A {
}

class B extends A {
}

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

        B b = new B();
        A a = new A();
        A ba = new B();
        System.out.println("1------------");
        System.out.println(b instanceof B);
        System.out.println(b instanceof A);
        System.out.println(b instanceof Object);
        System.out.println(null instanceof Object);
        System.out.println("2------------");
        System.out.println(b.getClass().isInstance(b));
        System.out.println(b.getClass().isInstance(a));
        System.out.println("3------------");
        System.out.println(a.getClass().isInstance(ba));
        System.out.println(b.getClass().isInstance(ba));
        System.out.println(b.getClass().isInstance(null));
        System.out.println("4------------");
        System.out.println(A.class.isInstance(a));
        System.out.println(A.class.isInstance(b));
        System.out.println(A.class.isInstance(ba));
        System.out.println("5------------");
        System.out.println(B.class.isInstance(a));
        System.out.println(B.class.isInstance(b));
        System.out.println(B.class.isInstance(ba));
        System.out.println("6------------");
        System.out.println(Object.class.isInstance(b));
        System.out.println(Object.class.isInstance(null));
    }
}

  

 

 

泛型解决了什么问题?

     1:将运行时期的问题ClassCastException问题转换成了编译失败,体现在编译时期,程序员就可以解决问题。

     2:避免了强制转换的麻烦。

     比如这种情况:

ArrayList al = new ArrayList();
al.add("hello");
al.add(4);自动装箱
String s1 = (String)al.get(0);
String s2 = (String)al.get(1);在编译时没问题,但在运行时出现问题

    泛型擦除:其实应用在编译时期,是给编译器使用的技术,到了运行时期,泛型就不存在了。在JAVA的虚拟机中并不存在泛型,泛型只是为了完善java体系,增加程序员编程的便捷性以及安全性而创建的一种机制,在JAVA虚拟机中对应泛型的都是确定的类型,在编写泛型代码后,java虚拟中会把这些泛型参数类型都擦除,用相应的确定类型来代替,代替的这一动作叫做类型擦除,而用于替代的类型称为原始类型,在类型擦除过程中,一般使用第一个限定的类型来替换,若无限定则使用Object.

    对泛型类的翻译:

   泛型类:   

class Test<T>{
    private T t;
    public void show(T t){

    }
}

 虚拟机进行翻译后的原始类型:

class Test{
    private Object t;
    public void show(Object t){
        
    }
}

  也就是说,编辑器检查了泛型的类型正确后,在生成的类文件中是没有泛型的。

  在运行时,如何知道获取的元素类型而不用强转呢?

      因为存储的时候,类型已经确定了是同一个类型的元素,所以在运行时,只要获取到该元素的类型,在内部进行一次转换即可,所以使用者不用再做转换动作了。 

  什么时候用泛型类呢? 

   当类中的操作的引用数据类型不确定的时候,以前用的Object来进行扩展的,现在可以用泛型来表示。这样可以避免强转的麻烦,而且将运行问题转移到的编译时期。

 

public static <T> void function(T t) {
    System.out.println("function:"+t);
  }

  泛型中的通配符

   可以解决当具体类型不确定的时候,这个通配符就是 ?  ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。?和T又有什么区别呢?

   比如一下的例子:

public void print(ArrayList<?> al)
{
    Iterator<?> it = al.iterator();
    while(it.hasNext())
    {
        System.out.println(in.next());
    }

}

public <T> void print(ArrayList<T> al)
{
    Iterator<T> it = al.iterator();
    while(it.hasNext())
    {
        T t = it.next();    "区别就在此处,T可以作为类型来使用,而?仅能作为接收任意类型"
        System.out.println(t);
    }
}

 泛型通配符

  1.什么时候会用到泛型通配符

     在java中,数组是可以协变的,比如dog extends Animal,那么Animal[] 与dog[]是兼容的。而集合是不能协变的,也就是说List<Animal>不是List<dog>的父类,这时候就可以用到通配符了。

    ?extends Fruit:表示Fruit或者Fruit子类的集合,因为编译器不知道具体是那种子类,所以不能往里面插数据。但是可以使用get取值。

     

   ?extends Fruit:,表示的是Fruit的某种基类的一个集合,所以可以添加T或者Fruit的子类,但是get的时候他不能准确的判断返回的是什么子类。

     

泛型限定:

     上限:?extends E:可以接收E类型或者E的子类型对象。

  下限:?super E:可以接收E类型或者E的父类型对象。

     上限什么时候用:往集合中添加元素时,既可以添加E类型对象,又可以添加E的子类型对象。为什么?因为取的时候,E类型既可以接收E类对象,又可以接收E的子类型对象。举个例子,比如:由于泛型参数类型可以表示任意类型的类类型,若T要引用compareTo方法,如何保证在T类中定义了compareTo方法呢?

public <T extends Comparable> shwo(T a, T b){
    int num = a.compareTo(b);
}

 此处用于限定T类型继承自Comparable,因为T类型可以调用compareTo方法.

还有这种写法:可以有多个类型限定
<T extends Comparable & Serializable>

 下限什么时候用:当从集合中获取元素进行操作的时候,可以用当前元素的类型接收,也可以用当前元素的父类型接收。

 泛型的一些基本规则约束:

      *泛型的类型参数必须为类的引用,不能用基本类型(int, short, long, byte, float, double, char, boolean)

      *泛型是类型的参数化,在使用时可以用作不同类型(此处在说泛型类时会详细说明)

      *泛型的类型参数可以有多个.

泛型的细节:

1、泛型到底代表什么类型取决于调用者传入的类型,如果没传,默认是Object类型

2、使用带泛型的类创建对象时,等式两边指定的泛型必须一致;

      原因:编译器检查对象调用方法时只看变量,然而程序运行期间调用方法时就要考虑对象具体类型了;

3、等式两边可以在任意一边使用泛型,在另一边不使用(考虑向后兼容)

ArrayList<String> al = new ArrayList<Object>();  //错

//要保证左右两边的泛型具体类型一致就可以了,这样不容易出错。

ArrayList<? extends Object> al = new ArrayList<String>();

al.add("aa");  //错

//因为集合具体对象中既可存储String,也可以存储Object的其他子类,所以添加具体的类型对象不合适,类型检查会出现安全问题。 ?extends Object 代表Object的子类型不确定,怎么能添加具体类型的对象呢?

public static void method(ArrayList<? extends Object> al) {

al.add("abc");  //错

//只能对al集合中的元素调用Object类中的方法,具体子类型的方法都不能用,因为子类型不确定。

}

 

参考博客:

https://www.cnblogs.com/wxw7blog/p/7517343.html

https://blog.csdn.net/wang252949/article/details/80583093

 

 

  参考博客: https://www.cnblogs.com/hq233/p/7227887.html

                     https://www.cnblogs.com/fantasy01/p/3963593.html

                     https://www.cnblogs.com/lucky_dai/p/5589317.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

时空恋旅人

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

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

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

打赏作者

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

抵扣说明:

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

余额充值