찾다
Javajava지도 시간Java 제네릭이 무엇인지 깊이 이해하십니까? 제네릭을 사용하는 방법?

이 기사는 Java 제네릭이 무엇인지에 대한 심층적인 이해를 제공합니다. 제네릭을 사용하는 방법? 도움이 필요한 친구들이 참고할 수 있기를 바랍니다.

1. 제네릭이란 무엇입니까

"제네릭"은 작성된 코드가 다양한 유형의 객체에서 재사용될 수 있음을 의미합니다 . 더 재사용 가능한 코드를 작성하기 위해 제네릭이 제안되었습니다. 제네릭의 핵심은 매개변수화된 유형입니다. 즉, 데이터 유형 매개변수로 지정됩니다.

예를 들어, 공통 컬렉션 클래스 LinkedList:

public class LinkedList<e> extends AbstractSequentialList<e> implements List<e>,Deque<e>,Cloneable,Serializable{
 //.....
 transient Link<e> voidLink;
//.....    
}</e></e></e></e></e>

LinkedList클래스 이름과 클래스가 구현하는 인터페이스 이름 뒤에 특별한 부분이 있음을 알 수 있습니다, 멤버 Link 유형에는 기호 가 포함되어 있어 런타임 시 LinkedList를 생성할 때 다양한 유형이 전달될 수 있습니다.

2. 제네릭을 도입하는 이유

제네릭을 도입하기 전에 다양한 유형을 처리할 수 있는 범용 메소드를 구현하려면

Object 를 속성 및 메소드로 사용해야 합니다. :

public class Generic{
    private Object[] mData;

    public Generic(int capacity){
        mData = new Object[capacity];
    }
    
    public Object getData(int index){
       //.....
       return mData[index];
    }
     
    public void add(int index,Object item){
       //.....
       mData[index] = item;
    }   

}
Object 배열을 사용하여 데이터를 저장하므로 사용 시 다양한 유형의 객체를 추가할 수 있습니다.

Generic generic = new Generic(10);
generic.add(0,"fangxing");
generic.add(1,23);

Object는 모든 클래스의 상위 클래스이며 모든 클래스를 위의 멤버로 추가할 수 있습니다. 클래스를 사용해야 하는 경우 강제 변환을 수행해야 하며 이러한 강제 변환으로 인해 변환 예외가 발생할 가능성이 높습니다.

String item1 = (String) generic.getData(0);
String item2 = (String) generic.getData(1);
코드의 두 번째 줄은 정수를 문자열로 강제 변환하고 다음 위치에 오류가 보고됩니다. 런타임:

Java 제네릭이 무엇인지 깊이 이해하십니까? 제네릭을 사용하는 방법?

객체를 사용하여 일반 및 다양한 유형의 처리를 구현하는 데에는 다음과 같은 두 가지 단점이 있음을 알 수 있습니다.

  1. 매번 원하는 유형으로 캐스팅해야 합니다. 사용하세요

  2. 컴파일러는 컴파일 타임에 유형 변환이 정상적인지 여부를 알지 못합니다. 실행될 때만 알 수 있습니다. 안전하지 않습니다

" Java 프로그래밍 사고", 제네릭 출현의 동기 이유는 다음과 같습니다.

제네릭 출현에 기여한 이유는 여러 가지가 있는데, 가장 눈길을 끄는 이유 중 하나는 컨테이너 클래스를 만드는 것입니다.

JDK 1.5에서 제네릭이 등장한 이후 많은 컬렉션 클래스는 다양한 유형의 요소를 저장하기 위해 제네릭을 사용합니다. 예를 들어 Collection:

public interface Collection<e> extends Iterable<e>{
   
     Iterator<e> iterator();
   
     Object[] toArray();
     
     <t> T[] toArray(T[] a);

     boolean add(E e);
     
     boolean remove(Object o);
 
     boolean  containsAll(Collecion> c);

     boolean addAll(Collection extends E> c);

     boolean removeAll(Collection> c);
   
}</t></e></e></e>

실제로 제네릭 도입의 주요 목표는 다음과 같습니다. 유형 안전성

제네릭의 주요 목표는 Java 프로그램의 유형 안전성을 향상시키는 것입니다.

  • 잘못된 Java 유형으로 인해 발생하는 ClassCastException 예외는 컴파일 중에 확인할 수 있습니다

  • 빠르게 충족될수록 적어집니다. 오류는 비용이 많이 듭니다. 원칙

  • 형변환 제거

제네릭의 부수적 이점은 사용할 때 대상 유형을 직접 얻을 수 있고 많은 형변환을 제거할 수 있다는 것입니다

  • 얻는 것이 필요한 것입니다 , 코드를 더 읽기 쉽게 만들고 오류 가능성을 줄입니다

  • 잠재적인 성능 향상

제네릭이 구현되는 방식으로 인해 제네릭을 지원하려면 JVM이나 클래스 파일 변경이 거의 필요하지 않습니다

  • 모든 작업은 컴파일러에서 완료됨

  • 컴파일러에서 생성된 코드는 제네릭(및 강제 유형 변환)을 사용하지 않고 작성된 코드와 거의 동일하지만 유형 안전성을 더 잘 보장할 수 있습니다

3. 제네릭 사용법

제네릭의 핵심은 매개변수화된 유형

으로, 연산되는 데이터 유형을 매개변수로 지정한다는 뜻입니다.

유형 매개변수의 의미는 컴파일러에게 이 컬렉션에 저장될 인스턴스 유형을 알려주어 다른 유형을 추가할 때 메시지를 표시하고 컴파일 시 유형 안전성을 보장하는 것입니다. 매개변수 유형은 각각 제네릭 클래스, 제네릭 인터페이스 및 제네릭 메서드라고 불리는 클래스, 인터페이스 및 메서드 생성에 사용될 수 있습니다.

public class GenericClass<f>{
    private F mContent;
    
    public GenericClass(F content){
      mContent = content;
    }
  
    /*
      泛型方法
   */
    public F getContent(){
      return mContent;
    }
    
    public void setContent(F content){
      mcontent = content;
    }
   
    /*
       泛型接口
    */
    public interface GenericInterface<t>{
        void  doSomething(T t);
    }

}</t></f>

泛型类  

泛型类和普通类的区别就是类名后有类型参数列表 ,既然叫“列表”了,当然这里的类型参数可以有多个,比如 public class HashMap,参数名称由开发者决定。

类名中声明参数类型后,内部成员、方法就可以使用这个参数类型,比如上面的 GenericClass 就是一个泛型类,它在类名后声明了类型 F,它的成员、方法就可以使用 F 表示成员类型、方法参数/返回值都是 F 类型。

 泛型类最常见的用途就是作为容纳不同类型数据的容器类,比如 Java 集合容器类。

泛型接口

和泛型类一样,泛型接口在接口名后添加类型参数,比如上面的 GenericInterface,接口声明类型后,接口方法就可以直接使用这个类型。

实现类在实现泛型接口时需要指明具体的参数类型,不然默认类型是 Object,这就失去了泛型接口的意义。

未指明类型的实现类,默认是 Object 类型:

public class Generic implements GenericInterface{
    @Override
    public void doSomething(Object o){
      //...
    }
}

指明了类型的实现:

public class Generic implements GericInterface<string>{
     @Override
     public void doSomething(String s){
       //.....
     }
}</string>

泛型接口比较实用的使用场景就是用作策略模式的公共策略, Comparator就是一个泛型接口:

public interface Comparator<t>{
    public int compare(T lhs, Trhs);
    public bollean equals(Object object);
}</t>

泛型接口定义基本的规则,然后作为引用传递给客户端,这样在运行时就能传入不同的策略实现类。

泛型方法

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

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

/*
    传统的方法,会有unchecked ... raw type 的警告 
*/
public Set union(Set s1, Set s2){
    Set result = new HashSet(s1);
    result.addAll(s2);
    return result;
}

/*
    泛型方法,介于方法修饰符和返回值之间的称作 类型参数列表<a>(可以有多个)
    类型参数列表 指定参数、返回值中泛型的参数类型范围,命名惯例与泛型相同。
*/
public <e> Set<e> union2(Set<e> s1, Set<e> s2){
    Set<e> result = new HashSet(s1);
    result.addAll(s2);
    return result;
}</e></e></e></e></e></a>

四、泛型的通配符

通配符:传入的类型有一个指定的范围,从而可以进行一些特定的操作

泛型中有三种通配符形式:

1.>无限制通配符

2. extends E> extends 关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类。

3. super E> super 关键字声明了类型的下界,表示参数化类型可能是指定类型,或者是此类型的父类。

无限制通配符

要使用泛型,但是不确定或者不关心实际要操作的类型,可以使用无限制通配符(尖括号里一个问号,即 > ),表示可以持有任何类型。

? 和 Object 不一样,List> 表示未知类型的列表,而 List 表示任意类型的列表。

如传入个 List ,这时 List 的元素类型就是 String想要往 List 里添加一个 Object这当然是不可以的。

上界通配符

在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:

  • 如果传入的类型不是 E 或者 E 的子类,编辑不成功

  • 泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用

下界通配符

在类型参数中使用 super 表示这个泛型中的参数必须是 E 或者 E 的父类。

private <e> void add(List super E> dst, List<e> Src){
    for (E e : src){
    dst.add(e);
   }
}</e></e>

上面的 dst 类型 “大于等于” src 的类型,这里的“大于等于”是指 dst 表示的范围比 src 要大,因此装得下 dst 的容器也就能装 src。

通配符比较

无限制通配符 和 Object 有些相似,用于表示无限制或者不确定范围的场景。

用于灵活写入或比较,使得对象可以写入父类型的容器,使得父类型的比较方法可以应用于子类对象。

用于灵活读取,使得方法可以读取 E 或 E 的任意子类型的容器对象。

Java 제네릭이 무엇인지 깊이 이해하십니까? 제네릭을 사용하는 방법?

因此使用通配符的基本原则:

  • 如果参数化类型表示一个 T 的生产者,使用 ;(T 的子类)

  • 如果它表示一个 T 的消费者,就使用 ;(T 的父类)

  • 如果既是生产又是消费,那使用通配符就没什么意义了,因为你需要的是精确的参数类型。

小总结一下:

  • T 的生产者的意思就是结果会返回 T,这就要求返回一个具体的类型,必须有上限才够具体;

  • T 的消费者的意思是要操作 T,这就要求操作的容器要够大,所以容器需要是 T 的父类,即 super T;

举个例子:

private <e>> E max(List extends E> e1){

    if(e1 == null){
        return null;
    }

    //迭代器返回的元素属于 E 的某个子类型
   
    Iterator extends E> iterator = e1.iterator();
    E result = iterator.next();
    while (iterator.hasNext()){
     E next = iterator.next();
      if(next.compareTo(result)>0){
       result = next;
      }
    }
    return result;
}</e>

1.要进行比较,所以 E 需要是可比较的类,因此需要 extends Comparable(注意这里不要和继承的 extends 搞混了,不一样)

2.Comparable 要对 E 进行比较,即 E 的消费者,所以需要用 super

3.而参数 List 表示要操作的数据是 E 的子类的列表,指定上限,这样容器才够大

五、泛型的类型擦除

Java 中的泛型和 C++ 中的模板有一个很大的不同:

  • C++ 中模板的实例化会为每一种类型都产生一套不同的代码,这就是所谓的代码膨胀。

  • Java 中并不会产生这个问题。虚拟机中并没有泛型类型对象,所有的对象都是普通类。

在 Java 中,泛型是 Java 编译器的概念,用泛型编写的 Java 程序和普通的 Java 程序基本相同,只是多了一些参数化的类型同时少了一些类型转换。

实际上泛型程序也是首先被转化成一般的、不带泛型的 Java 程序后再进行处理的,编译器自动完成了从 Generic Java 到普通 Java 的翻译,Java 虚拟机运行时对泛型基本一无所知

当编译器对带有泛型的java代码进行编译时,它会去执行类型检查和类型推断,然后生成普通的不带泛型的字节码,这种普通的字节码可以被一般的 Java 虚拟机接收并执行,这在就叫做 类型擦除(type erasure)。

实际上无论你是否使用泛型,集合框架中存放对象的数据类型都是 Object,这一点不仅仅从源码中可以看到,通过反射也可以看到。

List<string> strings = new ArrayList();
List<integer> integers = new ArrayList();
System.out.println(Strings.getClass()==integers.getClass());//true</integer></string>

上面代码输出结果并不是预期的false,而是true。其原因就是泛型的擦除。

六、擦除的实现原理

一直有个疑问,Java 编译器在编译期间擦除了泛型的信息,那运行中怎么保证添加、取出的类型就是擦除前声明的呢?

Java 编辑器会将泛型代码中的类型完全擦除,使其变成原始类型。当然,这时的代码类型和我们想要的还有距离,接着 Java 编译器会在这些代码中加入类型转换,将原始类型转换成想要的类型。这些操作都是编译器后台进行,可以保证类型安全。总之泛型就是一个语法糖,它运行时没有存储任何类型信息。

擦除导致的泛型不可变性

泛型中没有逻辑上的父子关系,如 List 并不是 List 的父类。两者擦除之后都是List,所以形如下面的代码,编译器会报错:

/*
    两者并不是方法的重载,擦除之后就是同一方法,所以编译不会通过。
    擦除之后:
    void m(List numbers){}
    void m(List Strings){}   //编译不通过,已经存在形同方法签名 
*/
   void method(List<object> numbers){}
   void method(List<string> strings){}</string></object>

泛型的这种情况称为 不可变性,与之对应的概念是 协变、逆变:

  • 协变:如果 A 是 B 的父类,并且 A 的容器(比如 List) 也是 B 的容器(List)的父类,则称之为协变的(父子关系保持一致)

  • 逆变:如果 A 是 B 的父类,但是 A 的容器 是 B 的容器的子类,则称之为逆变(放入容器就篡位了)

  • 不可变:不论 A B 有什么关系,A 的容器和 B 的容器都没有父子关系,称之为不可变

Java 中数组是协变的,泛型是不可变的。

擦除的拯救者:边界

我们知道,泛型运行时被擦除成原始类型,这使得很多操作无法进行.

如果没有指明边界,类型参数将被擦除为 Object。

如果我们想要让参数保留一个边界,可以给参数设置一个边界,泛型参数将会被擦除到它的第一个边界(边界可以有多个),这样即使运行时擦除后也会有范围。

比如:

public class GenericErasure {
    interface Game{
      void play();
    }
    interface Program{
      void code();
   }
   public static calss People<t>{
       private T mPeople;
       
       public People(T people){
         mPeople = people;
       }
       public void habit(){
         mPeople.code();
         mPeople.play();
        }
   }
   

}</t>

上述代码中, People 的类型参数 T 有两个边界,编译器事实上会把类型参数替换为它的第一个边界的类型。

七、泛型的规则

  • 泛型的参数类型只能是类(包括自定义类),不能是简单类型。

  • 同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。

  • 泛型的类型参数可以有多个

  • 泛型的参数类型可以使用 extends 语句,习惯上称为“有界类型”

  • 泛型的参数类型还可以是通配符类型,例如 Class

泛型的使用场景

当类中要操作的引用数据类型不确定的时候,过去使用 Object 来完成扩展,JDK 1.5后推荐使用泛型来完成扩展,同时保证安全性。

八、总结

1.上面说到使用 Object 来达到复用,会失去泛型在安全性和直观表达性上的优势,那为什么 ArrayList 等源码中的还能看到使用 Object 作为类型?

      泛型出现时,Java 平台即将进入它的第二个十年,在此之前已经存在了大量没有使用泛型的 Java 代码。人们认为让这些代码全部保持合法,并且能够与使用泛型的新代码互用,非常重要。

这样都是为了兼容,新代码里要使用泛型而不是原始类型。

2.泛型是通过擦除来实现的。因此泛型只在编译时强化它的类型信息,而在运行时丢弃(或者擦除)它的元素类型信息。擦除使得使用泛型的代码可以和没有使用泛型的代码随意互用。

3.如果类型参数在方法声明中只出现一次,可以用通配符代替它。

private <e> void swap(List<e> list, int i, int j){
    //....
}</e></e>

只出现了一次 类型参数,没有必要声明,完全可以用通配符代替:

private void swap(List> list, int i, int j){
    //...
}

对比一下,第二种更加简单清晰吧。

4.数组中不能使用泛型

Array 事实上并不支持泛型,这也是为什么 Joshua Bloch 在 《Effective Java》一书中建议使用 List 来代替 Array,因为 List 可以提供编译期的类型安全保证,而 Array 却不能。

5.Java 中 List 和原始类型 List 之间的区别?

  • 在编译时编译器不会对原始类型进行类型安全检查,却会对带参数的类型进行检查

  • 通过使用 Object 作为类型,可以告知编译器该方法可以接受任何类型的对象,比如String 或 Integer

  • 你可以把任何带参数的类型传递给原始类型 List,但 却不能把 List 传递给接受 List 的方法,因为泛型的不可变性,会产生编译错误。

九、补充

静态资源不认识泛型

接上一个话题,如果把去掉,那么:

private static T ifThenElse(boolean b, T first, T second){
    return b ? first : second;
}

报错,T未定义。但是如果我们再把static去掉:

public class TestMain<t>{
    public static void main(String[] args){}
    
   @SuppressWarnings("unused")
   private List<t> ifThenElse(boolean b,T first, T second){
     return null;
   }
}</t></t>

这并不会有任何问题。两相对比下,可以看出static方法并不认识泛型,所以我们要加上一个,告诉static方法,后面的T是一个泛型。既然static方法不认识泛型,那我们看一下static变量是否认识泛型:

public class TestMain<t>{
    private List<t> notStaticList;
    private static List<t> staticList;
}</t></t></t>

这证明了,static变量也不认识泛型,其实不仅仅是staic方法、static变量、static块,也不认识泛型,可以自己试一下。总结起来就是一句话:静态资源不认识泛型。

요약: 위 내용은 이 글의 전체 내용입니다. 모든 분들의 공부에 도움이 되었으면 좋겠습니다. 더 많은 관련 튜토리얼을 보려면 Java 비디오 튜토리얼, Java 개발 그래픽 튜토리얼, bootstrap 비디오 튜토리얼을 방문하세요!

위 내용은 Java 제네릭이 무엇인지 깊이 이해하십니까? 제네릭을 사용하는 방법?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명
이 기사는 CSDN에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제
고급 Java 프로젝트 관리, 구축 자동화 및 종속성 해상도에 Maven 또는 Gradle을 어떻게 사용합니까?고급 Java 프로젝트 관리, 구축 자동화 및 종속성 해상도에 Maven 또는 Gradle을 어떻게 사용합니까?Mar 17, 2025 pm 05:46 PM

이 기사에서는 Java 프로젝트 관리, 구축 자동화 및 종속성 해상도에 Maven 및 Gradle을 사용하여 접근 방식과 최적화 전략을 비교합니다.

적절한 버전 및 종속성 관리로 Custom Java 라이브러리 (JAR Files)를 작성하고 사용하려면 어떻게해야합니까?적절한 버전 및 종속성 관리로 Custom Java 라이브러리 (JAR Files)를 작성하고 사용하려면 어떻게해야합니까?Mar 17, 2025 pm 05:45 PM

이 기사에서는 Maven 및 Gradle과 같은 도구를 사용하여 적절한 버전 및 종속성 관리로 사용자 정의 Java 라이브러리 (JAR Files)를 작성하고 사용하는 것에 대해 설명합니다.

카페인 또는 구아바 캐시와 같은 라이브러리를 사용하여 자바 애플리케이션에서 다단계 캐싱을 구현하려면 어떻게해야합니까?카페인 또는 구아바 캐시와 같은 라이브러리를 사용하여 자바 애플리케이션에서 다단계 캐싱을 구현하려면 어떻게해야합니까?Mar 17, 2025 pm 05:44 PM

이 기사는 카페인 및 구아바 캐시를 사용하여 자바에서 다단계 캐싱을 구현하여 응용 프로그램 성능을 향상시키는 것에 대해 설명합니다. 구성 및 퇴거 정책 관리 Best Pra와 함께 설정, 통합 및 성능 이점을 다룹니다.

캐싱 및 게으른 하중과 같은 고급 기능을 사용하여 객체 관계 매핑에 JPA (Java Persistence API)를 어떻게 사용하려면 어떻게해야합니까?캐싱 및 게으른 하중과 같은 고급 기능을 사용하여 객체 관계 매핑에 JPA (Java Persistence API)를 어떻게 사용하려면 어떻게해야합니까?Mar 17, 2025 pm 05:43 PM

이 기사는 캐싱 및 게으른 하중과 같은 고급 기능을 사용하여 객체 관계 매핑에 JPA를 사용하는 것에 대해 설명합니다. 잠재적 인 함정을 강조하면서 성능을 최적화하기위한 설정, 엔티티 매핑 및 모범 사례를 다룹니다. [159 문자]

Java의 클래스로드 메커니즘은 다른 클래스 로더 및 대표 모델을 포함하여 어떻게 작동합니까?Java의 클래스로드 메커니즘은 다른 클래스 로더 및 대표 모델을 포함하여 어떻게 작동합니까?Mar 17, 2025 pm 05:35 PM

Java의 클래스 로딩에는 부트 스트랩, 확장 및 응용 프로그램 클래스 로더가있는 계층 적 시스템을 사용하여 클래스로드, 링크 및 초기화 클래스가 포함됩니다. 학부모 위임 모델은 핵심 클래스가 먼저로드되어 사용자 정의 클래스 LOA에 영향을 미치도록합니다.

See all articles

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

인기 기사

R.E.P.O. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
3 몇 주 전By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 최고의 그래픽 설정
3 몇 주 전By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 아무도들을 수없는 경우 오디오를 수정하는 방법
4 몇 주 전By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25 : Myrise에서 모든 것을 잠금 해제하는 방법
1 몇 달 전By尊渡假赌尊渡假赌尊渡假赌

뜨거운 도구

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

Eclipse용 SAP NetWeaver 서버 어댑터

Eclipse용 SAP NetWeaver 서버 어댑터

Eclipse를 SAP NetWeaver 애플리케이션 서버와 통합합니다.

에디트플러스 중국어 크랙 버전

에디트플러스 중국어 크랙 버전

작은 크기, 구문 강조, 코드 프롬프트 기능을 지원하지 않음

DVWA

DVWA

DVWA(Damn Vulnerable Web App)는 매우 취약한 PHP/MySQL 웹 애플리케이션입니다. 주요 목표는 보안 전문가가 법적 환경에서 자신의 기술과 도구를 테스트하고, 웹 개발자가 웹 응용 프로그램 보안 프로세스를 더 잘 이해할 수 있도록 돕고, 교사/학생이 교실 환경 웹 응용 프로그램에서 가르치고 배울 수 있도록 돕는 것입니다. 보안. DVWA의 목표는 다양한 난이도의 간단하고 간단한 인터페이스를 통해 가장 일반적인 웹 취약점 중 일부를 연습하는 것입니다. 이 소프트웨어는