Java 表达式的计算结果可能为null,但由声明为@NotNull的方法返回

Java 表达式的计算结果可能为null,但由声明为@NotNull的方法返回,java,android,android-studio,Java,Android,Android Studio,创建RecyclerView适配器后,将返回有关my view holder的警告。我已经阅读并理解了一点,但不清楚返回viewHolder中的viewHolder实例是什么;应替换为 表达式的计算结果可能为null,但由声明为@NotNull的方法返回 @NonNull只是一个IDE标志,用于Kotlin交叉兼容性。它所做的仅仅是告诉IDE它应该和不应该警告您潜在的空指针 声明它并不意味着方法将返回非null值。当然,你的将只是因为它的工作方式,但IDE不是那么聪明。它所看到的只是一个矛盾:使

创建RecyclerView适配器后,将返回有关my view holder的警告。我已经阅读并理解了一点,但不清楚返回viewHolder中的viewHolder实例是什么;应替换为

表达式的计算结果可能为null,但由声明为@NotNull的方法返回


@NonNull只是一个IDE标志,用于Kotlin交叉兼容性。它所做的仅仅是告诉IDE它应该和不应该警告您潜在的空指针

声明它并不意味着方法将返回非null值。当然,你的将只是因为它的工作方式,但IDE不是那么聪明。它所看到的只是一个矛盾:使用@NonNull参数,但也有返回null值的情况

解决此问题的最简单方法是将您的默认案例替换为TYPE_EXPANDABLE或TYPE_NONEXPANDABLE下的案例,并删除冗余案例,即:

编辑:根据Ben p的评论,如果当前视图类型是意外的,那么在OnCreateViewHolder方法中抛出异常也是一个好主意

switch (viewType){
    case TYPE_EXPANDABLE:
        layout = R.layout.cardview_a;
        View callsView = LayoutInflater
                .from(parent.getContext())
                .inflate(layout, parent, false);
        viewHolder = new CallViewHolder(callsView);
        break;
    case TYPE_NONEXPANDABLE:
        layout = R.layout.cardview_b;
        View smsView = LayoutInflater
                .from(parent.getContext())
                .inflate(layout, parent, false);
        viewHolder = new SMSViewHolder(smsView);
        break;
    default:
        throw IllegalArgumentException("Invalid View type: " + viewType);
}
它不是最优雅的,但只要您确定getItemViewType永远不会返回-1,您就不会遇到任何奇怪的行为

不过,我想补充一点,这样你就可以确定你会得到你想要的。与此相反:

@Override
public int getItemViewType(int position) {
    if (callSMSFeed.get(position) instanceof Phonecall) {
        return TYPE_EXPANDABLE;
    } else if (callSMSFeed.get(position) instanceof SMSmessage) {
        return TYPE_NONEXPANDABLE;
    }
    return -1;
}
试试这个:

@Override
public int getItemViewType(int position) {
    if (callSMSFeed.get(position) instanceof Phonecall) {
        return TYPE_EXPANDABLE;
    } else if (callSMSFeed.get(position) instanceof SMSmessage) {
        return TYPE_NONEXPANDABLE;
    }
    throw new IllegalArgumentException("Item at position " + position + " is not an instance of either Phonecall or SMSmessage");
}
注意:您可以使用您认为适合该案例的任何异常类型。我正在使用IllegalArgumentException,来自评论的Ben P可能会使用IllegalStateException。。。您甚至可以抛出异常。

您有这个

@NonNull

在onCreateViewHolder之前。警告消息清楚地告诉您,您需要确保返回的内容不是空的。在默认情况下,viewHolder为null。实际上,您可能希望返回一个有效值,或者在这种情况下引发异常。

由于方法

public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
不能返回null,但在默认情况下,您将返回null

default:
                viewHolder = null;
                break;
在生产中,默认情况将导致崩溃,这就是为什么会发出警告。
您必须抛出运行时异常或返回默认的非空视图

我喜欢@TheLoverter的答案,但我想再添加一个,以突出我认为会让你的生活更轻松的东西

与ListView不同,RecyclerView不关心从getItemViewType返回什么值;只要您从这个方法返回不同的值,RecyclerView就会很高兴。这允许您使用布局资源ID作为返回值,这意味着您永远不必定义自己的常量

@Override
public int getItemViewType(int position) {
    Object obj = callSMSFeed.get(position);

    if (obj instanceof Phonecall) {
        return R.layout.cardview_a;
    } else if (obj instanceof SMSmessage) {
        return R.layout.cardview_b;
    }

    throw new IllegalStateException("item at position " + position + " is not a Phonecall or SMSmessage: " + obj);
}
在这里,我们返回R.layout.cardwiew\u a,而不是类型\u EXPANDABLE;两者都是隐藏的INT,但现在我们可以让资源框架为我们定义它们

如果我们碰到的对象不是电话呼叫或SMSmessage,我们也会抛出一个异常,这样我们就可以立即知道我们需要处理一个案例。只有犯错误的开发人员才能看到这种例外情况;应用程序不会在用户身上崩溃,因为在发布应用程序之前,您将继续修复崩溃

@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    LayoutInflater inflater = LayoutInflater.from(parent.getContext());
    View itemView = inflater.inflate(viewType, parent, false);

    switch (viewType) {
        case R.layout.cardview_a:
            return new CallViewHolder(itemView);

        case R.layout.cardview_b:
            return new SMSViewHolder(itemView);

        default:
            throw new IllegalArgumentException("unexpected viewType: " + viewType);
    }
}
因为我们从getItemViewType返回布局ID,所以我们不必担心从我们自己的常量转换为R.layout值;我们可以直接将viewType充气。不过,我们仍然需要switch语句来知道返回哪种类型的ViewHolder

同样,我们在这里抛出了一个异常,以防传递了一个我们不期望的viewType。这种情况不会发生,但如果真的发生了,问题所在就会非常清楚,并且很容易解决

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
    int viewType = holder.getItemViewType();

    switch (viewType) {
        case R.layout.cardview_a:
            Phonecall call = (Phonecall) callSMSFeed.get(position);
            ((CallViewHolder) holder).showCallDetails(call);
            break;

        case R.layout.cardview_b:
            SMSmessage sms = (SMSmessage) callSMSFeed.get(position);
            ((SMSViewHolder) holder).showSmsDetails(sms);
            break;

        default:
            throw new IllegalArgumentException("unexpected viewType: " + viewType);
    }
}
这里唯一的区别是将常量替换为交换机内部的布局ID。没什么大不了的。当然,如果我们得到了意想不到的东西,我们就会崩溃


还要注意,我已经从参数中删除了final关键字。即使编译器很乐意让您添加它们,您也不应该这样做。由于像notifyItemInserted这样的方法,视图持有者的位置可能会随着时间的推移而改变,而不会被重新绑定。处理这个问题超出了您的问题范围,但需要指出。

我同意大部分答案。我特别喜欢在getItemViewType的末尾抛出,尽管我个人会选择IllegalStateException或AssertionError。。。但我想说的是,如果viewType参数是您不期望的,那么您还应该加入onCreateViewHolder。如果你通过了第三个你不希望的选项,那么你的应用程序崩溃会更好,也许你以后会更改getItemViewType,但忘记更改onCreateViewHolder,而不是返回错误的ViewHolder。
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
    int viewType = holder.getItemViewType();

    switch (viewType) {
        case R.layout.cardview_a:
            Phonecall call = (Phonecall) callSMSFeed.get(position);
            ((CallViewHolder) holder).showCallDetails(call);
            break;

        case R.layout.cardview_b:
            SMSmessage sms = (SMSmessage) callSMSFeed.get(position);
            ((SMSViewHolder) holder).showSmsDetails(sms);
            break;

        default:
            throw new IllegalArgumentException("unexpected viewType: " + viewType);
    }
}