Stream # уменьшить результаты неожиданного типа, что я делаю не так? - PullRequest
0 голосов
/ 10 июня 2019

У меня есть простой проект Gradle:

plugins {
    id 'java'
    id 'application'
}

repositories {
    mavenCentral()
}

sourceCompatibility = '11'
targetCompatibility = '11'

mainClassName = 'demo.Main'

dependencies {
    compile 'net.bytebuddy:byte-buddy:1.9.13'
}

В проекте только один файл Java:

package demo;

import java.awt.*;
import java.util.stream.*;
import net.bytebuddy.agent.builder.*;
import net.bytebuddy.matcher.*;

import static net.bytebuddy.matcher.ElementMatchers.*;

public class Main {
    public static void main(String[] args) {
        // This compiles
        new AgentBuilder.Default().
            type(nameStartsWith("com.demo").or(is(Point.class))
        );


        var typeSpec = Stream.of(nameStartsWith("com.demo"), is(Point.class)).reduce(ElementMatcher.Junction::or).orElseThrow();

        // This does not compile
        new AgentBuilder.Default().type(typeSpec);
    }   
}

Я ожидал, что Stream.of(nameStartsWith("com.demo"), is(Point.class)).reduce(ElementMatcher.Junction::or).orElseThrow() выдаст тот же тип, что и type(nameStartsWith("com.demo").or(is(Point.class)), однако это не делает и приводит к сбою компиляции:

/code/compile-error/src/main/java/demo/Main.java:18: error: incompatible types: invalid method reference
                var typeSpec = Stream.of(nameStartsWith("com.demo"), is(Point.class)).reduce(ElementMatcher.Junction::or).orElseThrow();
                                                                                             ^
    method or in interface Junction<S> cannot be applied to given types
      required: ElementMatcher
      found: Junction<? extends NamedElement>,Junction<? extends NamedElement>
      reason: inference variable U has incompatible upper bounds CAP#2,CAP#1
  where S,U are type-variables:
    S extends Object declared in interface Junction
    U extends CAP#1 declared in method <U>or(ElementMatcher<? super U>)
  where CAP#1,CAP#2 are fresh type-variables:
    CAP#1 extends NamedElement from capture of ? extends NamedElement
    CAP#2 extends NamedElement from capture of ? extends NamedElement
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error

Есть идеи, почему результат Stream/reduce не компилируется?

1 Ответ

1 голос
/ 12 июня 2019

Когда вы пишете type(nameStartsWith("com.demo").or(is(Point.class))), вызов метода type предоставляет контекст для метода or, который обеспечивает контекст для метода is.

Существует большая гибкость вAPI-интерфейс ByteBuddy использует преимущества того факта, что это сопоставители, поэтому сопоставитель, способный проверять экземпляры типа X, может также проверять экземпляры подтипов X.

Например, сигнатура метода

<T extends TypeDefinition> ElementMatcher.Junction<T> is(Type type)

позволяет вывести TypeDescription для T, который является подтипом TypeDefinition.

Но также сигнатура

<U extends S> ElementMatcher.Junction<U> or(ElementMatcher<? super U> other);

позволяет вывести более конкретный тип U расширяет оба типа соответствия, поэтому, если вы используете

ElementMatcher.Junction<NamedElement> a = nameStartsWith("com.demo");
ElementMatcher.Junction<TypeDefinition> b = is(Point.class);
var combined = a.or(b);
new AgentBuilder.Default().type(combined);

, компилятор может определить тип ElementMatcher.Junction<TypeDefinition> как тип для combined, тогда как сигнатура

void type(ElementMatcher<? super TypeDescription> typeMatcher)

будетпринять совпадение этого типа, так как TypeDefinition является супертипом TypeDescription.


В отличие от этого, когда вы создаете Stream через

Stream.of(nameStartsWith("com.demo"), is(Point.class))

, существуетнет возможности использовать особый характер спичек;компилятор должен найти общий базовый тип для ElementMatcher.Junction<NamedElement> и ElementMatcher.Junction<TypeDefinition>, который в конечном итоге будет равен ElementMatcher.Junction<? extends NamedElement>, поскольку NamedElement является супертипом TypeDefinition.

Затем, последующий reduce пытается применить or с подстановочным типом и, что еще хуже, для сигнатуры reduce(BinaryOperator<T>) требуется функция, принимающая два ввода одного типа, возвращающая этот тип, поскольку она может быть снова передана в качестве ввода в функцию сокращения.Так что он не способен использовать ослабленную сигнатуру типа метода or.

Если вы применяете более специфический тип для вызова метода nameStartsWith, такой как

var typeSpec = Stream.of(
        ElementMatchers.<TypeDefinition>nameStartsWith("com.demo"),
        is(Point.class))
    .reduce(ElementMatcher.Junction::or).orElseThrow();
new AgentBuilder.Default().type(typeSpec);

, он будетРабота.Он также будет работать при применении типа в потоке, например,

var typeSpec = Stream.<ElementMatcher.Junction<TypeDefinition>>of(
        nameStartsWith("com.demo"),
        is(Point.class))
    .reduce(ElementMatcher.Junction::or).orElseThrow();

, поскольку тип вызова метода Stream.of может использоваться для определения типов для вызова nameStartsWith.

Как правило, типы могут распространяться через вложенные вызовы методов, но не через цепочечные вызовы, поэтому при использовании цепочки Stream.of(…).reduce() функция, используемая в reduce, не может помочь определить типы для вызова of.

...