泛型面试题库:20道经典题目,助你提升泛型理解(泛型的原理)
泛型
什么是泛型泛型泛型仿制药?它与类型参数化有何不同?
什么是仿制药?
泛型是编程语言的一项功能,允许在定义类、面试目助接口和方法时使用类型参数。题库提升实际使用时指定具体类型,道经典题的原所有使用泛型参数的理解理地方都会统一,保证类型一致。泛型泛型泛型类型参数化是面试目助将类型参数应用于现有代码的过程,例如参数化类或接口的题库提升特定类型。泛型和类型参数化之间的道经典题的原区别在于,泛型是理解理一种特殊类型,它将澄清类型的泛型泛型泛型工作推迟到创建对象或调用方法时,而类型参数化将类型参数应用于已经存在的面试目助类型。有一个编码过程。题库提升
//通用public class Box { private T t;公共无效集(T t){ this.t=t; } 公共T get() { 返回t; } }//类型参数化,道经典题的原使用具体的理解理String类型应用代码过程中,import java.util.ArrayList; public class Main { public static void main(String[] args) { //使用类型参数化创建ArrayList 对象ArrayList stringList=new ArrayList(); //添加元素到ArrayList stringList.add('Hello'); stringList.add('世界'); //遍历ArrayList并输出元素for (String str : stringList) { System.out.println(str); } } }泛型的主要用途是什么呢?
通用用途
提高类型安全性:泛型提供编译时类型检查,可以在早期发现并避免类型错误,提高程序的健壮性和可维护性。提高代码的可重用性:通过参数化类型,泛型可以使类、接口或方法适用于多种数据类型,提高代码的可重用性。提高程序灵活性:泛型使程序能够适应不同数据类型的操作,提高程序的灵活性和可扩展性。什么是通用通配符?它有什么用途?
通用通配符
泛型通配符是一种表示未知类型的语法,可以用来解决泛型不协变的问题。使用泛型时通配符可以提供更大的灵活性。可以是问号( )表示任意类型,也可以是问号后跟上限(extends upper limit)或下限(super lower limit)表示某种类型的范围。通用通配符的用途包括:
通用容器类:通配符允许使用同一个容器类来存储不同类型的对象。例如,List可以存储任何类型的对象。动态类型操作:某些情况下,运行时需要根据实际情况对不同类型的对象进行处理,而通配符可以帮助实现这种动态类型操作。例如,您可以使用通配符来调用接受不同类型参数的方法,并在运行时确定具体类型。类型转换:通配符可以帮助进行类型转换,特别是当您需要将一种类型的对象转换为另一种类型的对象时。例如,您可以使用通配符将父类对象转换为子类对象。导入java.util.ArrayList;导入java.util.List; public class Main { public static void main(String[] args) { //使用泛型通配符创建容器class List list=new ArrayList(); //动态类型操作:根据实际需要添加不同类型的对象list.add('Hello'); //添加字符串类型list.add(123); //添加整数类型list.add(new Object()); //添加Object类型//动态类型操作:根据实际需要调用方法processElements(list); } //使用泛型通配符的方法接受任意类型的参数,并在运行时判断具体类型public static void processElements(List elements) { for (Object obj : elements) { if (obj instanceof String) { System.out. println('String: ' + obj);} else if (obj instanceof Integer) { System.out.println('Integer: ' + obj );} else if (obj instanceof Object) { System.out.println('Object : ' + obj);} } } }什么是通用限定?它能解决什么问题?
通用资格
泛型限定是对泛型类型参数的约束。它通过在泛型类型参数之前添加限定符来限制泛型的使用范围。泛型限定可以解决以下问题: 类型安全问题:使用泛型时,需要对泛型的类型进行一定的限制,以保证类型安全。例如,当需要将字符串赋值给对象类型的变量时,需要确保该变量只能存储特定类型的对象,以避免运行时错误。代码可读性问题:需要限制泛型的类型参数,使代码更具可读性和易理解性。例如,当我们需要定义一个只能存储整数的泛型容器时,我们可以使用泛型限定来限制泛型的类型参数为Integer或其子类。代码复用性问题:不同的泛型类型参数需要进行不同的处理,以实现代码复用。例如,当需要定义一个可以接受不同类型的参数并在运行时确定具体类型的方法时,可以使用泛型限定来限制泛型类型参数的范围。
导入java.util.ArrayList;导入java.util.List; public class Main { public static void main(String[] args) { //创建一个只能存储整数的通用容器List intList=new ArrayList(); intList .add(1); intList.add(2); intList.add(3); //输出整数列表中的元素for (Integer num : intList) { System.out.println(num); } } }什么是原始类型和参数化类型?他们之间有什么区别?原始类型和参数化类型是Java 泛型中的两种类型。原始类型是使用泛型参数之前的类型,例如List、Set 等。参数化类型是使用泛型参数的类型,例如List、Set 等。原始类型和参数化类型之间的主要区别在于编译时类型安全性检查和运行时类型信息。原始类型在编译时不会进行类型安全检查,因此无法保证类型正确性。在运行时,如果发生类型不匹配,则会抛出ClassCastException。参数化类型在编译时进行类型安全检查,这有助于在编译时捕获许多与类型相关的错误。在运行时,由于类型已经参数化,因此不会出现类型不匹配的情况。
//基本类型List list1=new ArrayList(); list1.add('你好');列表1.add(123); //允许将不同类型的元素添加到同一个集合中//参数化类型List list2=new ArrayList(); list2.add('你好'); //允许添加String 类型的元素list2.add(123); //编译时错误,不允许添加Integer类型的元素。泛型有哪些限制?它们的作用是什么?
约束情况
泛型的主要约束如下: 引用类型约束:T必须是引用类型,即T必须是类或接口。它是泛型约束中最基本的约束,保证了泛型参数的类型安全。值类型约束:T必须是值类型,即T必须是结构体(struct)或者基本数据类型(如int、char等)。该约束主要用于限制泛型参数的类型范围,以保证泛型代码的安全性和正确性。构造函数类型约束:T 必须具有公共无参构造函数。该约束主要用于保证在创建泛型实例时能够正确初始化泛型对象。转换类型约束:T必须是指定的类型,比如T必须是实现某个接口的类型,或者T必须是某个类的子类。该约束主要用于限制泛型参数的类型转换范围,以保证泛型代码的安全性和正确性。约束的主要作用是限制泛型参数的类型范围,保证泛型代码的安全性和正确性。通过限制泛型参数的类型范围,可以避免类型不匹配问题,减少运行时错误的发生,提高代码的可维护性和可读性。同时,这些约束也可以帮助开发人员更好地理解和使用泛型,提高开发效率和代码质量。泛型类型参数的默认值是什么?我可以为其指定默认值吗?在Java中,泛型类型参数没有默认值。泛型参数是类型参数化的基础。实例化类或方法时需要显式指定具体类型参数,而不是默认值。例如,对于泛型类List,您不能期望T 有默认值,因为T 可以是任何类型,包括自定义类型。同样,对于通用方法,例如public void printList(List list),不能期望T 具有默认值。不能为泛型类型参数指定默认值。实例化泛型类或调用泛型方法时,必须显式指定泛型参数的类型。什么是泛型方法?它能解决什么问题?泛型方法是一种在方法中使用泛型的技术。泛型方法允许我们在方法中使用一个或多个类型参数,从而使该方法适用于许多不同类型的数据。泛型方法可以解决以下问题: 类型安全问题:泛型方法可以避免运行时的类型转换异常,提高代码的安全性和稳定性。代码复用问题:通过使用泛型,我们可以编写一次代码,然后在不同类型的数据上复用,避免为不同类型的数据编写相似但不同的代码,提高代码的复用性。类型参数化:泛型方法允许我们使用类型参数,使代码更加灵活和可维护。我们可以使用通配符来处理多种不同的类型,使代码更加简洁明了。
导入java.util.*; public class GenericMethodExample { //泛型方法,接受两个泛型参数T和U,返回一个Map类型集合public static Set items(Map map) { return map.entrySet(); } } public static void main(String[] args) { //创建一个Map类型的集合Map map=new HashMap(); map.put('一', 1); map.put('二', 2);地图.put('三', 3); //使用泛型方法获取Map Set中键值对的集合entrySet=entries(map); for (Map.Entry 条目: EntrySet) { System.out.println('Key:' + entry.getKey() + ', Value:' + entry.getValue()); } } } 泛型和继承之间有什么关系?泛型和继承都是提高代码可重用性和灵活性的重要手段。虽然它们在某些方面相似,但它们的目标和机制不同。泛型更注重类型处理,允许在编译时指定类型;而继承更注重类之间的关系,允许子类继承父类的属性和方法。在实际编程中,可以根据具体情况选择使用泛型或继承,或者组合使用,以实现更好的代码组织和设计。泛型是一种编程技术,它在编译时执行类型检查,并允许程序员在定义类、接口或方法时使用类型参数。当实例化对象或调用方法时,这些类型参数将被具体类型替换。泛型的主要目的是提高代码的可重用性和灵活性,同时减少类型转换错误。继承是面向对象编程中的一个概念,它允许一个类继承另一个类的属性和方法。通过继承,子类可以重用父类的代码,扩展或重写父类的行为。继承是实现代码重用和多态性的一种手段。探索泛型和继承之间的关系。
代码可重用性:继承和泛型都可以提高代码的可重用性。通过继承,子类可以复用父类的代码;通过泛型,类或方法可以在不同类型上重用。
灵活性:泛型和继承都增加了代码的灵活性。使用泛型,一个类或方法可以处理许多不同的类型;通过继承,子类可以根据需要扩展或修改父类的行为。
多态性:多态性是面向对象编程中的一个重要概念,它允许不同的对象对同一消息做出不同的响应。对于泛型和继承,它们都可以支持多态性。泛型提供编译时多态性,而继承则提供运行时多态性。类型处理:对于泛型,类型参数在使用前必须显式指定;在继承的情况下,子类会自动继承父类的类型。什么是泛型类型擦除?为什么Java 中需要泛型?
类型擦除
类型擦除是一种在编译期间显式删除程序的类型系统的计算机编程方法。操作语义不要求程序附带类型。这称为“类型擦除语义”。在Java中,泛型是在JDK 5之后引入的,而Java本身并没有参数化类型系统。为了解决这个问题,Java在编译后使用类型擦除从字节码中删除通用信息,从而保持与旧版本Java的兼容性。擦除还确保编译和运行时的相同行为。这意味着每当定义泛型类型时,都会自动提供相应的基元类型(即泛型类型的名称减去类型参数)。类型变量将被删除并替换为其限定类型(或用于非限定变量的Object)。 Java泛型中类型擦除的必要性主要是出于以下原因: 1、与旧版本Java编译器和虚拟机的兼容性。由于泛型的实现需要改变Java类文件格式,类型擦除可以将所有类型转换为Object类型,允许Java编译器在编译时检查类型安全,而不会在运行时造成类型错误。 2. 确保编译和运行时的行为相同。什么是泛型中的类型推断?它是如何工作的?泛型中的类型推断是在调用方法时根据参数类型和返回类型自动确定泛型类型参数的过程。类型推断在编译时自动发生,不需要在代码中显式指定类型参数。类型推断基于以下规则进行:
方法调用参数类型:编译器首先查看方法调用的参数类型。这些参数类型用于推断泛型类型参数的可能范围。例如,如果使用String 类型的参数调用方法,则编译器会将String 视为泛型类型参数的可能选项。
方法返回类型:除了参数类型之外,编译器还会考虑方法的返回类型。返回类型可以进一步限制泛型类型参数的可能范围。例如,如果方法的返回类型是List,编译器会将泛型类型参数限制为String或其子类。
上下文信息:除了方法调用的参数和返回类型之外,编译器还会考虑其他上下文信息,例如方法的签名、变量声明等。这些信息有助于编译器更准确地推断泛型类型参数。
推理过程:编译器根据上述规则推断出可能的泛型类型参数,并选择最具体的类型作为实际使用的泛型类型参数。这个过程基于Java的类型系统,保证类型安全。什么是通用边界?它能解决什么问题?泛型边界是泛型的一个重要概念,它可以限制泛型变量的类型范围。它可以解决以下问题: 类型安全:泛型边界可以保证编译时的类型检查,避免类型转换错误。通过限制泛型变量的类型范围,可以确保在编译时检查类型的合规性,从而避免运行时类型错误。代码复用:泛型边界可以限制泛型变量的类型范围,使代码更加通用和可复用。通过定义通用边界,您可以编写适用于多种类型的代码并提高代码的可重用性。容器类型的约束:容器是编程中重要的数据结构之一。通用边界可以约束容器中元素的类型,使得容器只能存储特定类型的元素。这样可以保证容器内元素类型的正确性和一致性,减少错误,降低维护成本。泛型和反射之间有什么关系?他们如何互动?反射是Java中的一种动态技术,可以在运行时检查类、接口、字段、方法等信息。反射允许程序在运行时根据指定的类名创建对象、调用方法或访问字段,而无需在编译时知道这些信息。泛型是Java 中的一种静态类型技术,允许在编译时定义类型参数化的类、接口或方法。泛型的主要目的是提高代码的可重用性和类型安全性,避免运行时的类型转换。泛型和反射的关系主要体现在以下几个方面: 反射可以用来操作泛型类型:由于反射可以在运行时获取类信息,所以可以用来操作泛型类型。例如,您可以使用反射来获取泛型类的类型参数,或者创建泛型类型的实例。泛型可以提供更好的类型安全性:泛型在编译时执行类型检查,这可以减少运行时类型错误。这减少了反射的使用,因为可以在编译时捕获更多类型错误。反射和泛型可以一起使用:在某些情况下,可能需要一起使用反射和泛型。您可以使用反射来动态创建泛型类型的实例,或操作泛型类型的字段和方法。
类clazz=. //获取泛型类的Class对象Type type=clazz.getGenericSuperclass(); //获取泛型类的实际类型参数if (type instanceof ParameterizedType) { ParameterizedType pType=(ParameterizedType) type; Type[]实际类型参数=pType.getActualTypeArguments(); //获取泛型参数的实际类型//这里可以对实际类型参数进行操作} Class clazz=. //获取泛型类的Class对象Type type=clazz.getGenericSuperclass(); //获取泛型类的实际类型参数if (type instanceof ParameterizedType) { ParameterizedType pType=(ParameterizedType) type; Type[]实际类型参数=pType.getActualTypeArguments(); //获取泛型参数实际类型//这里可以创建泛型实例并指定实际类型参数} 什么是泛型嵌套类型?它能解决什么问题?泛型嵌套类型是指嵌套在另一个泛型类型中的泛型类型。这种嵌套关系可以是一层或多层。 Java 中的泛型嵌套类型可以通过内部类、匿名内部类等方式实现。泛型嵌套类型可以解决以下问题: 提高代码的可重用性:通过将一个泛型类型嵌套在另一个泛型类型中,可以创建更通用、可重用的代码。外部泛型类型可以提供额外的类型参数,内部泛型类型可以使用这些参数来定义自己的类型参数,使内部类型更加具体和具体。这种模式避免了代码的重复并提高了代码的可维护性。提高类型安全性:通用嵌套类型可以提供额外的类型检查,以提高代码的类型安全性。内部泛型类型的类型参数可以继承外部类型参数,从而限制内部类型的具体类型,避免错误的类型使用。此约束可以在编译时捕获潜在的类型错误并提高代码的可靠性。简化代码结构:通过将相关的泛型逻辑组织在嵌套的泛型类型中,可以使代码结构更加清晰,更易于维护。外部类型可以专注于外部逻辑,而内部类型可以专注于内部逻辑,降低代码复杂度。这种结构还有助于提高代码的可读性和可维护性。需要注意的是,泛型嵌套类型要谨慎使用,避免过度使用可能导致代码过于复杂、难以理解。使用时应根据实际需要进行设计和选型,保证代码的清晰性、可读性和可维护性。泛型和多态之间有什么关系?他们如何互动?多态性是面向对象编程的一个基本特性,它允许一个接口由多个不同的类实现或者一个类继承多个接口。多态性是指一个对象可以有多种形态,在特定情况下可以表现出不同的状态和行为。泛型是一种在编译时进行类型参数化的机制,它允许将类、接口或方法应用于多种数据类型。通过使用泛型,您可以编写更通用和可重用的代码,并避免大量的类型转换操作。泛型与多态性的关系主要体现在以下几个方面:
泛型可以提供更好的类型安全性:泛型在编译时执行类型检查,这可以减少运行时类型错误。这减少了多态性的使用,因为可以在编译时捕获更多类型错误。
多态性可以扩展泛型的范围:通过实现多态性,泛型可以用来实现多种不同的数据类型,而不仅仅是基本类型或自定义类。多态性使得泛型类或泛型方法能够对不同类型的对象实现相同的操作逻辑,从而实现更加灵活和可重用的代码。
泛型和多态性可以相辅相成:在某些情况下,泛型和多态性可以相辅相成,以实现更复杂的功能。例如,您可以使用泛型来定义通用算法,然后使用多态性来使用不同的实现。这提高了代码灵活性和可扩展性,同时保持了代码的多功能性。泛型在集合框架中有哪些应用场景?泛型在集合框架中有广泛的应用场景,可以帮助你编写更安全、更清晰、可重用的代码。
创建自定义数据结构:通过泛型,您可以创建自己的数据结构,例如ArrayList、LinkedList等,并指定它们可以存储的数据类型。例如,您可以创建仅存储整数的ArrayList,或仅存储字符串的HashMap。
集合类:Java集合框架中的很多类,如ArrayList、LinkedList、HashMap等,都使用了泛型。通过泛型,可以更方便的使用这些集合类,而且类型更安全,可以避免运行时的ClassCastException。
算法操作:泛型允许您编写适用于不同数据类型的算法。例如,您可以编写一个交换两个数字而不仅仅是整数的算法。
接口定义:您可以使用泛型来定义接口并在实现这些接口的类中指定特定的数据类型。例如,您可以定义一个接口,该接口具有用于获取列表中的元素的方法,然后在使用该接口的类中指定列表元素的类型。
方法参数和返回值:在方法中,可以使用泛型来指定参数和返回值的类型。这使得代码更加清晰易懂,同时也提高了代码的可重用性。在设计模式中,泛型有哪些应用场景?工厂模式:泛型可用于创建可重用的工厂类,以创建不同类型对象的实例。通过泛型,工厂类可以在编译时获取参数类型并相应地创建正确的对象实例。
公共接口形状{ 无效绘制(); } public class Circle 实现Shape { @Override public void draw() { System.out.println('绘制圆.'); } } 公共类矩形实现形状{ @Override public void draw() { System.out.println('绘制矩形.'); } } public class ShapeFactory { public T createShape(String shapeType) { if ('C'.equalsIgnoreCase(shapeType)) { return new Circle( ); } } } else if ('R'.equalsIgnoreCase(shapeType)) { return new Rectangle(); } else { throw new IllegalArgumentException('无效的形状类型'); } } } 模板方法模式:模板方法模式是一种行为类型设计模式,它定义了一个操作中的算法骨架,并将某些步骤推迟到子类中。在模板方法模式中,泛型可以用来定义抽象类和方法来接受或返回不同类型的
参数,从而增加代码的复用性。public abstract class AbstractClass { protected abstract void operation(T t); public void templateMethod() { System.out.println("Template method called"); operation(getTypedObject()); // pass type object to template method } protected abstract T getTypedObject(); } public class ConcreteClass extends AbstractClass { @Override protected void operation(Integer t) { System.out.println("Operation on Integer: " + t); } @Override protected Integer getTypedObject() { return 10; // return Integer object to template method call in AbstractClass.templateMethod() } }观察者模式:观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。在观察者模式中,可以使用泛型来定义观察者和被观察者的类型,以增加代码的灵活性和复用性。import java.util.*; // 抽象主题类 public abstract class Subject { private List> observers = new ArrayList<>(); // 添加观察者 public void addObserver(Observer observer) { observers.add(observer); } // 删除观察者 public void deleteObserver(Observer observer) { observers.remove(observer); } // 通知所有观察者更新 public void notifyObservers(T data) { for (Observer observer : observers) { observer.update(data); } } } // 具体主题类(天气预报) public class WeatherSubject extends Subject { private String weather; public WeatherSubject(String weather) { this.weather = weather; } public String getWeather() { return weather; } public void setWeather(String weather) { this.weather = weather; notifyObservers(weather); // 天气变化,通知观察者更新数据 } } // 抽象观察者接口 public interface Observer { void update(T data); } // 具体观察者类(天气预报员) public class WeatherObserver implements Observer { private String name; public WeatherObserver(String name) { this.name = name; } @Override public void update(String data) { System.out.println(name + " received update: " + data); } }适配器模式:适配器模式是一种结构型设计模式,它通过将一个类的接口转换成客户端所期望的另一个接口形式,来解决不兼容的问题。在适配器模式中,可以使用泛型来定义适配器和被适配者的类型,以增加代码的复用性和灵活性。import java.util.*; // 目标接口 public interface Target { void request(T param); } // 源类 public class Adaptee { public void specificRequest(String param) { System.out.println("Adaptee: " + param); } } // 适配器类 public class Adapter implements Target { private Adaptee adaptee; public Adapter(Adaptee adaptee) { this.adaptee = adaptee; } @Override public void request(T param) { adaptee.specificRequest((String) param); // 适配器将泛型参数转换为字符串类型,传递给源类的方法 } }public class Client { public static void main(String[] args) { Adaptee adaptee = new Adaptee(); Target target = new Adapter<>(adaptee); // 创建适配器对象,将源对象传入适配器构造函数中 target.request("Hello, Adapter!"); // 调用目标接口方法,适配器将参数转换为字符串类型,并调用源类的方法 } }策略模式:策略模式是一种行为型设计模式,它定义了一系列的算法,并将每一个算法封装起来,使它们可以互相替换。在策略模式中,可以使用泛型来定义不同的策略类型,以增加代码的复用性和灵活性。import java.util.*; // 策略接口 public interface Strategy { void execute(T param); } // 具体策略类1 public class ConcreteStrategyA implements Strategy { @Override public void execute(String param) { System.out.println("ConcreteStrategyA: " + param); } } // 具体策略类2 public class ConcreteStrategyB implements Strategy { @Override public void execute(Integer param) { System.out.println("ConcreteStrategyB: " + param); } } // 上下文类,使用泛型参数 public class Context { private Strategy strategy; public Context(Strategy strategy) { this.strategy = strategy; } public void setStrategy(Strategy strategy) { this.strategy = strategy; } public void executeStrategy(T param) { strategy.execute(param); } }public class Client { public static void main(String[] args) { Context context = new Context<>(new ConcreteStrategyA()); // 创建上下文对象,传入具体策略对象A context.executeStrategy("Hello, Strategy!"); // 执行策略方法,输出"ConcreteStrategyA: Hello, Strategy!" context.setStrategy(new ConcreteStrategyB()); // 更换为具体策略对象B context.executeStrategy(10); // 执行策略方法,输出"ConcreteStrategyB: 10" } }