Repository: netty/netty
Issue: The method toString() of MqttSubscribePayload throw ArrayIndexOutOfBoundsException

### Expected behavior
print MqttSubscribePayload correctly
### Actual behavior
ArrayIndexOutOfBoundsException was thrown
### Steps to reproduce
new a MqttSubscribePayload which has no topicSubscriptions(size of List<MqttTopicSubscription> is 0)

Though the MqttSubscribePayload couldn't be empty in MQTT protocol. There also some cases like unit test that should cover this situation.
So, I think the code below seems not gracefully enough.

```
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder(StringUtil.simpleClassName(this)).append('[');
        for (int i = 0; i < topicSubscriptions.size() - 1; i++) {
            builder.append(topicSubscriptions.get(i)).append(", ");
        }
        builder.append(topicSubscriptions.get(topicSubscriptions.size() - 1));
        builder.append(']');
        return builder.toString();
    }
```
Could we implement it like this below?
```
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder(StringUtil.simpleClassName(this)).append('[');
        for (int i = 0; i <= topicSubscriptions.size() - 1; i++) {
            builder.append(topicSubscriptions.get(i)).append(", ");
        }
        if (builder.substring(builder.length() - 2).equals(", ")) {
            builder.delete(builder.length() - 2, builder.length());
        }
        builder.append(']');
        return builder.toString();
    }
```
### Minimal yet complete reproducer code (or URL to code)
```
    @Test
    public void test_MqttSubscribePayload_toString() {
        List<MqttTopicSubscription> subscriptions = new ArrayList<>();
        MqttSubscribePayload payload = new MqttSubscribePayload(subscriptions);

        System.out.println(payload.toString());
    }
```
### Netty version
4.1.32.Final
### JVM version (e.g. `java -version`)
java version "1.8.0_201"
Java(TM) SE Runtime Environment (build 1.8.0_201-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode)

### OS version (e.g. `uname -a`)
windows

Issue comments:
I could provide a PR. You can use set Length instead of delete in String Builder btw 
@SplotyCode please if you have time :)
@SplotyCode Thanks for your suggestion, it's much better.

Pull request review comments:
please keep old "for loop" style to reduce GC. 
Please keep using old "for loop" style to reduce GC

Patch that resolved the issue:
diff --git a/codec-mqtt/src/main/java/io/netty/handler/codec/mqtt/MqttSubscribePayload.java b/codec-mqtt/src/main/java/io/netty/handler/codec/mqtt/MqttSubscribePayload.java
index eb1b9c9c572..aa3a3244740 100644
--- a/codec-mqtt/src/main/java/io/netty/handler/codec/mqtt/MqttSubscribePayload.java
+++ b/codec-mqtt/src/main/java/io/netty/handler/codec/mqtt/MqttSubscribePayload.java
@@ -39,11 +39,12 @@ public List<MqttTopicSubscription> topicSubscriptions() {
     @Override
     public String toString() {
         StringBuilder builder = new StringBuilder(StringUtil.simpleClassName(this)).append('[');
-        for (int i = 0; i < topicSubscriptions.size() - 1; i++) {
+        for (int i = 0; i < topicSubscriptions.size(); i++) {
             builder.append(topicSubscriptions.get(i)).append(", ");
         }
-        builder.append(topicSubscriptions.get(topicSubscriptions.size() - 1));
-        builder.append(']');
-        return builder.toString();
+        if (!topicSubscriptions.isEmpty()) {
+            builder.setLength(builder.length() - 2);
+        }
+        return builder.append(']').toString();
     }
 }
diff --git a/codec-mqtt/src/main/java/io/netty/handler/codec/mqtt/MqttUnsubscribePayload.java b/codec-mqtt/src/main/java/io/netty/handler/codec/mqtt/MqttUnsubscribePayload.java
index b032d12067a..9812bd9923c 100644
--- a/codec-mqtt/src/main/java/io/netty/handler/codec/mqtt/MqttUnsubscribePayload.java
+++ b/codec-mqtt/src/main/java/io/netty/handler/codec/mqtt/MqttUnsubscribePayload.java
@@ -39,11 +39,12 @@ public List<String> topics() {
     @Override
     public String toString() {
         StringBuilder builder = new StringBuilder(StringUtil.simpleClassName(this)).append('[');
-        for (int i = 0; i < topics.size() - 1; i++) {
+        for (int i = 0; i < topics.size(); i++) {
             builder.append("topicName = ").append(topics.get(i)).append(", ");
         }
-        builder.append("topicName = ").append(topics.get(topics.size() - 1))
-               .append(']');
-        return builder.toString();
+        if (!topics.isEmpty()) {
+            builder.setLength(builder.length() - 2);
+        }
+        return builder.append("]").toString();
     }
 }


Test patch:
diff --git a/codec-mqtt/src/test/java/io/netty/handler/codec/mqtt/MqttConnectPayloadTest.java b/codec-mqtt/src/test/java/io/netty/handler/codec/mqtt/MqttConnectPayloadTest.java
index 5a929dc1e5e..f1d9dc0db10 100644
--- a/codec-mqtt/src/test/java/io/netty/handler/codec/mqtt/MqttConnectPayloadTest.java
+++ b/codec-mqtt/src/test/java/io/netty/handler/codec/mqtt/MqttConnectPayloadTest.java
@@ -21,6 +21,8 @@
 import io.netty.util.CharsetUtil;
 import org.junit.Test;
 
+import java.util.Collections;
+
 public class MqttConnectPayloadTest {
 
     @Test
@@ -88,4 +90,11 @@ public void testBuilderNullWillMessage() throws Exception {
         assertNull(mqttConnectPayload.willMessageInBytes());
         assertNull(mqttConnectPayload.willMessage());
     }
+
+    /* See https://github.com/netty/netty/pull/9202 */
+    @Test
+    public void testEmptyTopicsToString() {
+        new MqttSubscribePayload(Collections.<MqttTopicSubscription>emptyList()).toString();
+        new MqttUnsubscribePayload(Collections.<String>emptyList()).toString();
+    }
 }