【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);//编译报错,父类不能转为子类,需要强制转换
    }
}


相关文章
|
1天前
|
安全 Java 编译器
java泛型浅谈
java泛型浅谈
6 1
|
1天前
|
消息中间件 安全 前端开发
字节面试:说说Java中的锁机制?
Java 中的锁(Locking)机制主要是为了解决多线程环境下,对共享资源并发访问时的同步和互斥控制,以确保共享资源的安全访问。 锁的作用主要体现在以下几个方面: 1. **互斥访问**:确保在任何时刻,只有一个线程能够访问特定的资源或执行特定的代码段。这防止了多个线程同时修改同一资源导致的数据不一致问题。 2. **内存可见性**:通过锁的获取和释放,可以确保在锁保护的代码块中对共享变量的修改对其他线程可见。这是因为 Java 内存模型(JMM)规定,对锁的释放会把修改过的共享变量从线程的工作内存刷新到主内存中,而获取锁时会从主内存中读取最新的共享变量值。 3. **保证原子性**:锁
16 1
|
1天前
|
安全 Java 数据安全/隐私保护
Java一分钟之-Java反射机制:动态操作类与对象
【5月更文挑战第12天】本文介绍了Java反射机制的基本用法,包括获取Class对象、创建对象、访问字段和调用方法。同时,讨论了常见的问题和易错点,如忽略访问权限检查、未捕获异常以及性能损耗,并提供了相应的避免策略。理解反射的工作原理和合理使用有助于提升代码灵活性,但需注意其带来的安全风险和性能影响。
22 4
|
1天前
|
Java 开发框架 XML
JDK、JRE、Java SE、Java EE和Java ME有什么区别?
JDK、JRE、Java SE、Java EE和Java ME有什么区别?
|
1天前
|
存储 安全 Java
掌握8条泛型规则,打造优雅通用的Java代码
掌握8条泛型规则,打造优雅通用的Java代码
掌握8条泛型规则,打造优雅通用的Java代码
|
1天前
|
Java 数据安全/隐私保护
java中异常处理机制
java中异常处理机制
13 1
|
1天前
|
算法 安全 Java
深入探索Java中的并发编程:CAS机制的原理与应用
总之,CAS机制是一种用于并发编程的原子操作,它通过比较内存中的值和预期值来实现多线程下的数据同步和互斥,从而提供了高效的并发控制。它在Java中被广泛应用于实现线程安全的数据结构和算法。
22 0
|
1天前
|
Java API 开发者
解密Java反射机制与动态代理
解密Java反射机制与动态代理
11 0
|
1天前
|
安全 Java 调度
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第12天】 在现代软件开发中,多线程编程是提升应用程序性能和响应能力的关键手段之一。特别是在Java语言中,由于其内置的跨平台线程支持,开发者可以轻松地创建和管理线程。然而,随之而来的并发问题也不容小觑。本文将探讨Java并发编程的核心概念,包括线程安全策略、锁机制以及性能优化技巧。通过实例分析与性能比较,我们旨在为读者提供一套既确保线程安全又兼顾性能的编程指导。
|
1天前
|
Java 程序员 调度
Java中的多线程编程:从理论到实践
【5月更文挑战第14天】在现代计算机技术中,多线程编程是一个重要的概念。它允许多个线程并行执行,从而提高程序的运行效率。本文将从理论和实践两个角度深入探讨Java中的多线程编程,包括线程的基本概念、创建和控制线程的方法,以及如何处理线程同步和通信问题。