如何引用具有多个边界的泛型返回类型

2023-12-24

我最近发现可以声明一个也受接口限制的返回类型。考虑以下类和接口:

public class Foo {
    public String getFoo() { ... }
}

public interface Bar {
    public void setBar(String bar);
}

我可以声明一个返回类型,如下所示:

public class FooBar {
    public static <T extends Foo & Bar> T getFooBar() {
        //some implementation that returns a Foo object,
        //which is forced to implement Bar
    }
}

如果我从某个地方调用该方法,我的 IDE 会告诉我返回类型具有该方法String getFoo()setBar(String),但前提是我在后面点一个点Function像这样:

FooBar.getFooBar(). // here the IDE is showing the available methods.

有没有办法获得这样一个对象的引用?我的意思是,如果我会做这样的事情:

//bar only has the method setBar(String)
Bar bar = FooBar.getFooBar();
//foo only has the getFoo():String method
Foo foo = FooBar.getFooBar();

我想要这样的参考(伪代码):

<T extents Foo & Bar> fooBar = FooBar.getFooBar();
//or maybe
$1Bar bar = FooBar.getFooBar();
//or else maybe
Foo&Bar bar = FooBar.getFooBar();

这在 Java 中是否可能以某种方式实现,或者我只能像这样声明返回类型?我认为 Java 也必须以某种方式输入它。我不想使用这样的包装器,因为这感觉就像作弊:

public class FooBarWrapper<T extends Foo&Bar> extends Foo implements Bar {
    public T foobar;

    public FooBarWrapper(T val){
        foobar = val;
    }


    @Override
    public void setBar(String bar) {
        foobar.setBar(bar);
    }

    @Override
    public String getFoo() {
        return foobar.getFoo();
    }
}

Java 真的发明了这么好的特性,但却忘记了人们想要引用它吗?


而泛型方法的类型参数可以通过边界来限制,例如extends Foo & Bar,它们最终由调用者决定。你打电话时getFooBar(),调用站点已经知道什么T正在解决。通常,这些类型参数将是inferred由编译器指定,这就是为什么您通常不需要指定它们,如下所示:

FooBar.<FooAndBar>getFooBar();

但即使当T被推断为FooAndBar,这确实是幕后发生的事情。

因此,为了回答你的问题,这样的语法如下:

Foo&Bar bothFooAndBar = FooBar.getFooBar();

在实践中永远不会有用。原因是调用者一定已经知道 what T是。任何一个T是一些具体类型:

FooAndBar bothFooAndBar = FooBar.<FooAndBar>getFooBar(); // T is FooAndBar

Or, T是一个未解析的类型参数,我们在它的范围内:

<U extends Foo & Bar> void someGenericMethod() {
    U bothFooAndBar = FooBar.<U>getFooBar(); // T is U
}

另一个例子:

class SomeGenericClass<V extends Foo & Bar> {
    void someMethod() {
        V bothFooAndBar = FooBar.<V>getFooBar(); // T is V
    }
}

从技术上讲,这就是答案。但我还想指出你的示例方法getFooBar本质上是不安全的。请记住,调用者决定什么T是事物的本质,而不是方法。自从getFooBar不接受任何相关参数T,并且因为类型擦除 http://docs.oracle.com/javase/tutorial/java/generics/erasure.html,它唯一的选择就是返回null或者通过未经检查的演员来“撒谎”,冒着风险堆污染 https://softwareengineering.stackexchange.com/a/156008/33756。典型的解决方法是getFooBar采取Class<T>论证,或者是一个FooFactory<T>例如。

Update

事实证明,当我断言调用者时我错了getFooBar必须始终知道什么T是。正如 @MiserableVariable 指出的,在某些情况下,泛型方法的类型参数被推断为通配符捕获,而不是具体类型或类型变量。See 他的回答 https://stackoverflow.com/a/14632995/697449一个很好的例子getFooBar使用代理来实现他的观点T未知。

正如我们在评论中讨论的那样,一个使用的例子getFooBar http://pastebin.com/UGNvgjEZ造成混乱,因为它不需要任何参数来推断T从。某些编译器抛出一个错误 http://ideone.com/3CqrAJ在无上下文的调用中getFooBar()而其他人没问题 http://ideone.com/YuKQLq. I thought不一致的编译错误 - 以及调用的事实FooBar.<?>getFooBar()是非法的 - 验证了我的观点,但这些结果证明是转移注意力的。

根据@MiserableVariable的回答,我把一个新的例子 http://ideone.com/V1VED9它使用带有参数的通用方法来消除混乱。假设我们有接口Foo and Bar和一个实现FooBarImpl:

interface Foo { }
interface Bar { }
static class FooBarImpl implements Foo, Bar { }

我们还有一个简单的容器类,它包装了某种类型的实例,实现Foo and Bar。它声明了一个愚蠢的静态方法unwrap这需要一个FooBarContainer并返回其所指对象:

static class FooBarContainer<T extends Foo & Bar> {

    private final T fooBar;
    
    public FooBarContainer(T fooBar) {
        this.fooBar = fooBar;
    }
    
    public T get() {
        return fooBar;
    }
    
    static <T extends Foo & Bar> T unwrap(FooBarContainer<T> fooBarContainer) {
        return fooBarContainer.get();
    }
}

现在假设我们有一个通配符参数化类型FooBarContainer:

FooBarContainer<?> unknownFooBarContainer = ...;

我们被允许通过unknownFooBarContainer into unwrap。这表明我之前的断言是错误的,因为调用站点不知道什么T是 - 只是它是边界内的某种类型extends Foo & Bar.

FooBarContainer.unwrap(unknownFooBarContainer); // T is a wildcard capture, ?

正如我所指出的,打电话unwrap使用通配符是非法的:

FooBarContainer.<?>unwrap(unknownFooBarContainer); // compiler error

我只能猜测这是因为通配符捕获永远无法相互匹配 -?调用站点提供的参数不明确,无法说它应该专门匹配类型中的通配符unknownFooBarContainer.

因此,这是 OP 询问的语法的用例。呼唤unwrap on unknownFooBarContainer返回类型的引用? extends Foo & Bar。我们可以将该引用分配给Foo or Bar,但不是两者:

Foo foo = FooBarContainer.unwrap(unknownFooBarContainer);
Bar bar = FooBarContainer.unwrap(unknownFooBarContainer);

如果由于某种原因unwrap价格昂贵,而且我们只想调用一次,我们将被迫进行转换:

Foo foo = FooBarContainer.unwrap(unknownFooBarContainer);
Bar bar = (Bar)foo;

因此,这就是假设语法派上用场的地方:

Foo&Bar fooBar = FooBarContainer.unwrap(unknownFooBarContainer);

这只是一个相当模糊的用例。允许这样的语法会产生相当广泛的影响,无论好坏。它会在不需要的地方为滥用开辟空间,并且完全可以理解为什么语言设计者没有实现这样的事情。但我仍然觉得思考起来很有趣。

Note- 从 JDK 10 开始,有var保留类型名称,这使得这成为可能:

var fooBar = FooBarContainer.unwrap(unknownFooBarContainer);

变量fooBar被推断为具有实现两者的类型Foo and Bar并且不能在源代码中明确表示。


关于堆污染的注意事项

(主要是为了@MiserableVariable https://stackoverflow.com/questions/14464226/how-to-reference-a-generic-return-type-with-multiple-bounds#comment20233991_14469627)以下是不安全方法的演练getFooBar造成堆污染及其影响。给出以下接口和实现:

interface Foo { }

static class Foo1 implements Foo {
    public void foo1Method() { }
}

static class Foo2 implements Foo { }

让我们实现一个不安全的方法getFoo, 如同getFooBar但对于这个例子进行了简化:

@SuppressWarnings("unchecked")
static <T extends Foo> T getFoo() {
    //unchecked cast - ClassCastException is not thrown here if T is wrong
    return (T)new Foo2();
}

public static void main(String[] args) {
    Foo1 foo1 = getFoo(); //ClassCastException is thrown here
}

在这里,当新Foo2被投射到T,它是“未经检查的”,意味着由于类型擦除,运行时不知道它应该失败,即使在这种情况下它应该失败,因为T was Foo1。相反,堆被“污染”,这意味着引用指向了它们不应该被允许的对象。

失败发生在方法返回之后,当Foo2实例尝试分配给foo1引用,具有具体化类型Foo1.

您可能会想,“好吧,它是在调用站点而不是方法上爆炸的,这有什么大不了的。”但当涉及更多泛型时,它很容易变得更加复杂。例如:

static <T extends Foo> List<T> getFooList(int size) {
    List<T> fooList = new ArrayList<T>(size);
    for (int i = 0; i < size; i++) {
        T foo = getFoo();
        fooList.add(foo);
    }
    return fooList;
}

public static void main(String[] args) {
    
    List<Foo1> foo1List = getFooList(5);
    
    // a bunch of things happen
    
    //sometime later maybe, depending on state
    foo1List.get(0).foo1Method(); //ClassCastException is thrown here
}

现在它不会在调用站点爆炸。一段时间后它会爆炸,当内容foo1List习惯了。这就是堆污染变得更难调试的原因,因为异常堆栈跟踪不会向您指出实际问题。

当调用者本身处于通用范围内时,情况会变得更加复杂。想象一下而不是得到一个List<Foo1>我们得到了List<T>,将其放入Map<K, List<T>>并将其返回到另一个方法。我希望你能明白我的想法。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何引用具有多个边界的泛型返回类型 的相关文章

随机推荐

  • Oracle:将两个不同的查询合并为一个,LIKE & IN

    我需要实现一个搜索查询 其中我们对数据库 oracle 中的单个列有多个过滤器 值 但是这些多个过滤器 值 是LIKE查询参数 我不确定我是否使用心中的方法预见到了正确的结果 我想要的东西应该像这样 departmentname IN LI
  • 如何使用 UTF8 读取 mysqldump?

    我正在尝试使用命令进行 mysql 转储 mysqldump u xxxx p dbxxx gt xxxx270613 sql 使用 UTF8 获取 mysqldump 的命令是什么 您可以使用以下内容 mysqldump u userna
  • 如何以编程方式设置 iOS 13 字形[重复]

    这个问题在这里已经有答案了 从 iOS 13 开始 Apple 提供了一堆字形 可以used https developer apple com design human interface guidelines sf symbols ov
  • Primefaces 惰性 datascroller 调用加载两次

    我正在尝试将 Datascroller 与 LazyDataModel 一起使用 并且惰性数据模型的加载方法被调用两次 除了认为多次调用加载方法 这可能会执行昂贵的服务器 数据库往返 不太好之外 因为我的惰性数据模型不是幂等的 也就是说 在
  • 用于检查 EC2 实例限制的 API?

    我广泛使用 EC2 实例来测试分布式系统 不幸的是 有时我会达到运行实例的限制 从而导致整个部署失败 我捕获了适当的异常 但我宁愿避免整个部署 也不愿在启动多个实例后失败 为了避免这种情况 我想进行一次飞行前检查 number of run
  • 从单元格中提取最后一个子字符串

    我在一栏中有名字 我需要将该列中的姓氏拆分到另一列中 姓氏从右侧以空格分隔 单元格中的内容A2 Alistair Stevens我在单元格中输入了公式B2 我需要 Stevens 在细胞内B2 我尝试使用以下公式 RIGHT A2 FIND
  • 用于匹配 C++ 字符串常量的正则表达式

    我目前正在开发 C 预处理器 我需要将字符串常量与超过 0 个字母相匹配 如下所示 hey I m a string 我目前正在与此合作 但它在我的一个测试用例上失败了 测试用例 std cout lt lt hello lt lt wor
  • Python 是否有一个“安全”子集可用作嵌入式脚本语言?

    在我创建的许多 Python 应用程序中 我经常创建简单的模块 只包含用作配置文件的常量 此外 因为配置文件实际上是一个 Python 代码文件 所以我可以添加简单的逻辑来根据调试级别等更改变量 虽然这对于内部应用程序非常有效 但我对将此类
  • 使用 form_for 在 Ruby on Rails 中通过 check_box 值传递 id

    我有两个控制器project controller rb and service controller rb 我有一个领域Project命名为service id 当我创建项目时 值service id默认情况下保留为 null 所以现在我
  • node_modules/@types/babel _template/index.d.ts :16:28 - 错误 TS2583:找不到名称“Set”

    我跟着本文 https itnext io step by step building and publishing an npm typescript package 44fe7164964c设置 TypeScript NPM 包 我第一
  • 缺少 iOS 发行版签名身份

    我错误地从 Mac 中删除了所有配置文件 Library Mobile Device Provisioning Profiles 现在 无论我做什么 Xcode 都会给我这个错误 缺少 iOS 发行版签名身份 我怎样才能解决这个问题 这与过
  • Android - 白标应用程序

    NOTE 这是一个老问题 相应的旧的赞成答案可能不相关 请参阅有关构建变体 又名应用程序风味 的新答案 我有一个关于发布到市场的问题 公司 X 为公司 A 和 B 提供类似的服务 并且 A 和 B 都希望在市场上有一个应用程序 X 公司只想
  • CMD 指令中是否允许使用 Docker ARG

    我有一个 Dockerfile 其中ARG用于CMD操作说明 ARG MASTER NAME CMD spark submit deploy mode client master MASTER URL arg 通过 docker compo
  • 电容器存储或科尔多瓦存储

    我正在使用 ionic v5 开发 PWA 应用程序 我需要离线保存一些内容以呈现给用户 我正在考虑使用 Capacitor 将我的 Web 应用程序发布到本机应用程序中作为 Cordova 的替代方案 但数据存储是我的应用程序的一个重要点
  • UIDatePicker 替代 tvOS?

    由于 UIDatePicker 在 tvOS 中不可用 那么使用什么来向用户询问日期 时间呢 是否有一个新的类可以替代UIDatePicker Thanks 由于 Apple 没有为 tvOS 提供日期选择器 因此没有标准解决方案 然而 一
  • 使用 mysqli bind_param 插入多行记录?

    我这里有一个使用 mysql 查询的代码 N count fullname for i 0 i lt N i mysql query INSERT INTO famcomp fullname fage frel fcivil fedu fo
  • 对于小 DIV 尺寸,为什么此 SVG 不适合其父 DIV 内部?

    我的期望是三角形应该位于容器 div 内 对于宽度大于 14 像素左右的情况 这可以按预期工作 但对于较小的 div 尺寸 SVG 会降低 div style width 9px height 9px background color re
  • 如何让谷歌地图信息窗口调整大小以适合放置在其中的内容?

    我一直在尝试使用 Google Maps API 但我在使用 InfoWindow 时遇到了麻烦 它似乎总是太小 无法容纳我放置在其中的内容 因此我的内容溢出到 InfoWindow 之外并溢出到 InfoWindow 上 地图 InfoW
  • 如何从 Windows 应用商店应用程序的隐藏代码绑定到 C# 中的自定义附加属性?

    我有一个我定义的附加属性 namespace Controls public class StateManager DependencyObject public static string GetVisualState Dependenc
  • 如何引用具有多个边界的泛型返回类型

    我最近发现可以声明一个也受接口限制的返回类型 考虑以下类和接口 public class Foo public String getFoo public interface Bar public void setBar String bar