如何在通配符声明中使用泛型类?

时间:2019-05-07 12:59:05

标签: java generics wildcard

我班上有以下成员:

List<? extends SomeObject> list;

当我尝试做时:

list.add(list.get(0));

我得到:

Test.java:7: error: no suitable method found for add(CAP#1)
        list.add(list.get(0));
            ^
    method Collection.add(CAP#2) is not applicable
      (argument mismatch; Object cannot be converted to CAP#2)
    method List.add(CAP#2) is not applicable
      (argument mismatch; Object cannot be converted to CAP#2)
  where CAP#1,CAP#2 are fresh type-variables:
    CAP#1 extends Object from capture of ? extends Object
    CAP#2 extends Object from capture of ? extends Object

我的问题是双重的:

  1. 为什么不编译?为什么我不能将get()的结果传递给add()

  2. 又如何在不借助强制转换的情况下以其他方式实现这一目标?


我了解在使用<T extends SomeObject>的方法中,我不能只是说:

T someObject = list.get(0);
list.add(someObject);

因为我的T可能是?扩展名之外的另一个扩展名。

我也明白我不能说:

List<? extends SomeObject> list1;
List<? extends SomeObject> list2;
list1.add(list2.get(0));

但是由于add和get应该在list.add(list.get(0))中使用相同的泛型类型,所以我不明白为什么编译器不接受它。


我真正需要的是

[something of type T where T is whatever was used to instantiate list] someObject = list.get(0);
list.add(someObject);

以便我以后可以

list.add(someObject);

我认为我不必为整个课程模板化,是吗?

class MyClass<T extends SomeObject> {
List<T> list;

,然后再使用

T someObject = list.get(0);

当然可以,但是要拧紧我代码的其他部分。

所以第一个问题是为什么不起作用,第二个问题是最佳的解决方法是什么?

2 个答案:

答案 0 :(得分:3)

  

我的问题有两个,为什么我不能:

    // Utility function to get the price of a variation from it's attribute 
     value
     function get_the_variation_price_html( $product, $name, $term_slug ){
    foreach ( $product->get_available_variations() as $variation ){
        if($variation['attributes'][$name] == $term_slug ){
            return strip_tags( $variation['price_html'] );
        }`enter code here`
    }
    }

// Add the price  to the dropdown options items.
add_filter( 'woocommerce_dropdown_variation_attribute_options_html', 'show_price_in_attribute_dropdown', 10, 2);
function show_price_in_attribute_dropdown( $html, $args ) {
    // Only if there is a unique variation attribute (one dropdown)
    if( sizeof($args['product']->get_variation_attributes()) == 1 ) :

    $options               = $args['options'];
    $product               = $args['product'];
    $attribute             = $args['attribute'];
    $name                  = $args['name'] ? $args['name'] : 'attribute_' . sanitize_title( $attribute );
    $id                    = $args['id'] ? $args['id'] : sanitize_title( $attribute );
    $class                 = $args['class'];
    $show_option_none      = $args['show_option_none'] ? true : false;
    $show_option_none_text = $args['show_option_none'] ? $args['show_option_none'] : __( 'Choose an option', 'woocommerce' );

    if ( empty( $options ) && ! empty( $product ) && ! empty( $attribute ) ) {
        $attributes = $product->get_variation_attributes();
        $options    = $attributes[ $attribute ];
    }

    $html = '<select id="' . esc_attr( $id ) . '" class="' . esc_attr( $class ) . '" name="' . esc_attr( $name ) . '" data-attribute_name="attribute_' . esc_attr( sanitize_title( $attribute ) ) . '" data-show_option_none="' . ( $show_option_none ? 'yes' : 'no' ) . '">';
    $html .= '<option value="">' . esc_html( $show_option_none_text ) . '</option>';

    if ( ! empty( $options ) ) {
        if ( $product && taxonomy_exists( $attribute ) ) {
            $terms = wc_get_product_terms( $product->get_id(), $attribute, array( 'fields' => 'all' ) );

            foreach ( $terms as $term ) {
                if ( in_array( $term->slug, $options ) ) {
                    // Get and inserting the price
                    $price_html = get_the_variation_price_html( $product, $name, $term->slug );
                    $html .= '<option value="' . esc_attr( $term->slug ) . '" ' . selected( sanitize_title( $args['selected'] ), $term->slug, false ) . '>' . esc_html( apply_filters( 'woocommerce_variation_option_name', $term->name ) . ' ::: ' . $price_html ) . '</option>';
                }
            }
        } else {
            foreach ( $options as $option ) {
                $selected = sanitize_title( $args['selected'] ) === $args['selected'] ? selected( $args['selected'], sanitize_title( $option ), false ) : selected( $args['selected'], $option, false );
                // Get and inserting the price
                $price_html = get_the_variation_price_html( $product, $name, $term->slug );
                $html .= '<option value="' . esc_attr( $option ) . '" ' . $selected . '>' . esc_html( apply_filters( 'woocommerce_variation_option_name', $option ) . ' ::: ' . $price_html ) . '</option>';
            }
        }
    }
    $html .= '</select>';

    endif;

    return $html;
}

因为编译器不够聪明,无法知道您是将list.add(list.get(0)); 中的内容添加回list中。评估后,编译器不认为listlist.get(0)有任何关系:它只是类型为list的“某些表达式”。

要解决此问题,请添加具有自己的类型变量的方法:

? extends SomeObject

,并用以下内容替换原始的private <T> void addFirst(List<T> list) { list.add(list.get(0)); }

list.add(list.get(0));

这仅在方法上定义类型变量,并且不需要在类外部可见,因此您不需要类级类型变量。


也许值得指出的是,这类似于Collections.swap method:使用的是addFirst(list); 而不是set,但是从泛型的角度来看,这是同一件事:

add

这采用了技术上类型安全的方法,并且确实避免了强制类型转换;但这有点麻烦,因为它使用原始类型。

我想这仅仅是出于向后兼容的原因。只要有机会再次编写它,就可以像上面的@SuppressWarnings({"rawtypes", "unchecked"}) public static void swap(List<?> list, int i, int j) { // instead of using a raw type here, it's possible to capture // the wildcard but it will require a call to a supplementary // private method final List l = list; l.set(i, l.set(j, l.get(i))); } 方法中那样定义类型变量。

答案 1 :(得分:0)

当使用通配符时,我们应该遵循Java泛型和集合中介绍的The Get and Put Principle

<块引用>

Get 和 Put 原则:当你只从一个 结构,当您只将值放入结构时使用 extends 通配符,并且不要使用 获取和放置时的通配符。

在您的情况下,不要使用通配符,因为您既从列表中获取元素,又将元素放入列表。