【Java SE基础 八】Java泛型机制(上)

简介: 【Java SE基础 八】Java泛型机制(上)

泛型,即“参数化类型”。就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数

假定我们有这样一个需求:写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,该如何实现?答案是可以使用 Java泛型。使用 Java泛型的概念,我们可以写一个泛型方法来对一个对象数组排序。然后,调用该泛型方法来对整型数组、浮点数数组、字符串数组等进行排序

如果说Java的重写和重载是对方法的实现进行多态扩展,那么Java泛型就是对方法的参数类型进行多态化扩展。

泛型优势

一种约束—规范类型(常用名字:E = Elememt、T = Type、K = Key、V = Value),可以说任何类型都可以当做参数化的泛型。包括泛型类、泛型接口、泛型方法,它们的成员变量以及参数都可以使用泛型。为什么要使用泛型?

1,使用泛型可以避免过多的方法方法重载,更广泛的说,多种数据类型执行相同的代码时可以实现代码复用

public class NeedGeneric1 {
    private static int add(int a, int b) {
        System.out.println(a + "+" + b + "=" + (a + b));
        return a + b;
    }
    private static float add(float a, float b) {
        System.out.println(a + "+" + b + "=" + (a + b));
        return a + b;
    }
    private static double add(double a, double b) {
        System.out.println(a + "+" + b + "=" + (a + b));
        return a + b;
    }
    private static <T extends Number> double add(T a, T b) {
        System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue()));
        return a.doubleValue() + b.doubleValue();
    }
    public static void main(String[] args) {
        //不使用泛型,add需要三个重载方法
        NeedGeneric1.add(1, 2);
        NeedGeneric1.add(1f, 2f);
        NeedGeneric1.add(1d, 2d);
        //使用泛型,add只需要一个泛型方法
        NeedGeneric1.add(Integer.valueOf(1), Integer.valueOf(2));
        NeedGeneric1.add(Float.valueOf(1), Float.valueOf(2));
        NeedGeneric1.add(Double.valueOf(1), Double.valueOf(2));
    }
}

2,泛型中的类型在使用时指定,不需要强制类型转换(类型安全,编译器会检查类型),将类型的明确工作提前到对象的创建以及方法调用。防止程序不安全泛型 :将类型的明确工作提前到对象的创建以及方法调用,防止程序不安全

public class NeedGeneric1 {
    static class C{
    }
    public static void main(String[] args) {
        List list=new ArrayList();
        list.add("A");
        list.add("B");
        list.add(new C());
        list.add(100);
        //1.当我们将一个对象放入集合中,集合不会记住此对象的类型,当再次从集合中取出此对象时,该对象的编译类型变成了Object类型,但其运行时类型任然为其本身类型。
        //2.因此取出集合元素时需要人为的强制类型转化到具体的目标类型,且很容易出现“java.lang.ClassCastException”异常。
        for (int i = 0; i < list.size(); i++) {
            String value= (String) list.get(i);
            System.out.println(value);
        }
    }
}

以上代码执行为:

Exception in thread "main" java.lang.ClassCastException: 
    packageB.NeedGeneric1$C cannot be cast to java.lang.String
  at packageB.NeedGeneric1.main(NeedGeneric1.java:20)
A
B

如果我们使用泛型的话,将运行期的错误提前到编译期,提前发现问题

泛型的使用

泛型支持泛型类、泛型接口以及泛型方法的调用,接下来详细说说这三种使用方式。

泛型方法

可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。下面是定义泛型方法的规则:

  • 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前
  • 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
  • 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符
  • 泛型方法体的声明和其他方法一样。

注意类型参数只能代表引用型类型,不能是原始类型(像int,double,char的等),以下是一个泛型的定义:

public class GenericMethodTest
{
   // 泛型方法 printArray                         
   public static <E> void printArray( E[] inputArray )
   {
      // 输出数组元素            
         for ( E element : inputArray ){        
            System.out.printf( "%s ", element );
         }
         System.out.println();
    }
    public static void main( String args[] )
    {
        // 创建不同类型数组: Integer, Double 和 Character
        Integer[] intArray = { 1, 2, 3, 4, 5 };
        Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
        Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
        System.out.println( "整型数组元素为:" );
        printArray( intArray  ); // 传递一个整型数组
        System.out.println( "\n双精度型数组元素为:" );
        printArray( doubleArray ); // 传递一个双精度型数组
        System.out.println( "\n字符型数组元素为:" );
        printArray( charArray ); // 传递一个字符型数组
    } 
}

返回结果为:

整型数组元素为:
1 2 3 4 5 
双精度型数组元素为:
1.1 2.2 3.3 4.4 
字符型数组元素为:
H E L L O

泛型类

泛型类的定义方式如下,一般用作变化属性的定义。

public class Box<T> {   
  private T t;
  public void add(T t) {
    this.t = t;
  } 
  public T get() {
    return t;
  } 
  public static void main(String[] args) {
    Box<Integer> integerBox = new Box<Integer>();
    Box<String> stringBox = new Box<String>();
    integerBox.add(new Integer(10));
    stringBox.add(new String("字符串泛型"));
    System.out.printf("整型值为 :%d\n\n", integerBox.get());
    System.out.printf("字符串为 :%s\n", stringBox.get());
  }
}

这样定义好一个泛型类,就可以有多种当前类的类型实现方式,结果为:

整型值为 :10
字符串为 :字符串泛型

泛型接口

定义一个泛型接口,可以实现方法和参数类型的双扩展:

public interface GenericIntercace<T> {
     T getData();
}

接口的实现方式有两种,一种是实现类没有明确数据类型

public class ImplGenericInterface1<T> implements GenericIntercace<T> {
    @Override
    public T getData() {
        return 10;
    }
    public static void main(String[] args) {
        ImplGenericInterface1<String> implGenericInterface1 = new ImplGenericInterface1<>(); //具体使用时再实例化,抽象类的概念
        System.out.println(implGenericInterface1.getData());
    }
}

另外一种为实现类已经明确数据类型

public class ImplGenericInterface2 implements GenericIntercace<String> {
    @Override
    public String getData() {
        return "Generic Interface2";
    }
    public static void main(String[] args) {
        ImplGenericInterface2 implGenericInterface2 = new ImplGenericInterface2();
        System.out.println(implGenericInterface2.getData());
    }
}

泛型实例

了解了泛型类和泛型方法后可以看这样一个示例,dog继承自animal,可以看到在泛型类和泛型方法中有不同的表现。

public class GenericMethod3 {
    static class Animal {
        @Override
        public String toString() {
            return "Animal";
        }
    }
    static class Dog extends Animal {
        @Override
        public String toString() {
            return "Dog";
        }
    }
    static class Fruit {
        @Override
        public String toString() {
            return "Fruit";
        }
    }
    static class GenericClass<T> {
        public void show01(T t) {
            System.out.println(t.toString());
        }
        public <T> void show02(T t) {
            System.out.println(t.toString());
        }
        public <K> void show03(K k) {
            System.out.println(k.toString());
        }
    }
    public static void main(String[] args) {
        Animal animal = new Animal();
        Dog dog = new Dog();
        Fruit fruit = new Fruit();
        GenericClass<Animal> genericClass = new GenericClass<>();
        System.out.println("泛型类在初始化时限制了参数类型=========");
        genericClass.show01(dog);
        //genericClass.show01(fruit); //编译报错,fruit类型不正确
        System.out.println("泛型方法的参数类型在使用时指定=========");
        genericClass.show02(dog);
        genericClass.show02(fruit);
        genericClass.show03(animal);
        genericClass.<Animal>show03(dog);
        genericClass.show03(fruit);
        //genericClass.<Dog>show03(animal);//编译报错,父类不能转为子类,需要强制转换
    }
}


相关文章
|
3天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
14 2
|
7天前
|
Java 编译器
探索Java中的异常处理机制
【10月更文挑战第35天】在Java的世界中,异常是程序运行过程中不可避免的一部分。本文将通过通俗易懂的语言和生动的比喻,带你了解Java中的异常处理机制,包括异常的类型、如何捕获和处理异常,以及如何在代码中有效地利用异常处理来提升程序的健壮性。让我们一起走进Java的异常世界,学习如何优雅地面对和解决问题吧!
|
6天前
|
Java 数据库连接 开发者
Java中的异常处理机制及其最佳实践####
在本文中,我们将探讨Java编程语言中的异常处理机制。通过深入分析try-catch语句、throws关键字以及自定义异常的创建与使用,我们旨在揭示如何有效地管理和响应程序运行中的错误和异常情况。此外,本文还将讨论一些最佳实践,以帮助开发者编写更加健壮和易于维护的代码。 ####
|
12天前
|
安全 IDE Java
Java反射Reflect机制详解
Java反射(Reflection)机制是Java语言的重要特性之一,允许程序在运行时动态地获取类的信息,并对类进行操作,如创建实例、调用方法、访问字段等。反射机制极大地提高了Java程序的灵活性和动态性,但也带来了性能和安全方面的挑战。本文将详细介绍Java反射机制的基本概念、常用操作、应用场景以及其优缺点。 ## 基本概念 ### 什么是反射 反射是一种在程序运行时动态获取类的信息,并对类进行操作的机制。通过反射,程序可以在运行时获得类的字段、方法、构造函数等信息,并可以动态调用方法、创建实例和访问字段。 ### 反射的核心类 Java反射机制主要由以下几个类和接口组成,这些类
30 2
|
17天前
|
存储 缓存 安全
🌟Java零基础:深入解析Java序列化机制
【10月更文挑战第20天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
22 3
|
13天前
|
Java 开发者
深入理解Java异常处理机制
【10月更文挑战第29天】在Java的世界中,异常处理如同生活的调味品,不可或缺。它确保了程序在遇到错误时不会崩溃,而是优雅地继续运行或者给出提示。本文将带你领略异常处理的奥秘,从基础的try-catch语句到高级的自定义异常,让你在面对程序中的各种“意外”时,能够从容应对。
|
15天前
|
SQL Java
探索Java中的异常处理机制
【10月更文挑战第26天】 在本文中,我们将深入探讨Java编程语言的异常处理机制。通过分析不同类型的异常、异常的捕获与抛出方式,以及如何自定义异常类,读者将能够更好地理解并应用Java中的异常处理机制来提高代码的健壮性和可读性。
23 0
|
8天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
4天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
23 9
|
7天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####