分析一下System.out.println(xxx)的几个重载方法

本文通过分析System.out.println()在不同参数类型下的行为,揭示其内部字符化和输出的过程。针对char[]和其他类型数组,解释了转换和输出的原理。同时强调,当参数为char[]时,应确保非null以避免错误。此外,讨论了基本数据类型如何通过包装类的类方法toString(int i)进行字符化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题一:下面调用什么方法?打印结果是什么?

	int[] arr = new int[]{1,2};
	System.out.println(arr);
打印结果:[I@a57993

调用PrintStream的println(Object x)重载方法,除了参数为char[]的其他数组参数都会调用这个方法			   	
public void println(Object x) {
    String s = String.valueOf(x);
     synchronized (this) {
         print(s);
         newLine();
     }
 }	
再看String.valueOf()也有很多的重载方法
public static String valueOf(Object obj) {
	return (obj == null) ? "null" : obj.toString();
}
obj为int[]数组,而所有类型的数组又没有重写toString()方法,默认调用Object的toString()方法,返回的是内存地址值。
public String toString() {
     return getClass().getName() + "@" + Integer.toHexString(hashCode());
  }

  public void print(String s) {
      if (s == null) {
          s = "null";
      }
      write(s);
  }

  private void write(String s) {
      try {
          synchronized (this) {
              ensureOpen();
              textOut.write(s);
              textOut.flushBuffer();
              charOut.flushBuffer();
              if (autoFlush && (s.indexOf('\n') >= 0))
                  out.flush();
          }
      }
      catch (InterruptedIOException x) {
          Thread.currentThread().interrupt();
      }
      catch (IOException x) {
          trouble = true;
      }
  }
字符输出流,将内存地址这个字符串输出。
所以打印结果为:[I@a57993	
System.out.println(arr.length);//数组是length属性,字符串是length()方法,集合是size()方法。		

问题二:下面调用什么方法?打印结果是什么?

char[] arr2 = new char[]{'a','b'};
System.out.println(arr2);
打印结果:ab	
调用PrintStream的println(char x[])重载方法
public void println(char x[]) {
   synchronized (this) {
        print(x);
        newLine();
    }
}
public void print(char s[]) {
  write(s);
}
private void write(char buf[]) {
   try {
       synchronized (this) {
           ensureOpen();
           textOut.write(buf);
           textOut.flushBuffer();
           charOut.flushBuffer();
           if (autoFlush) {
               for (int i = 0; i < buf.length; i++)
                   if (buf[i] == '\n')
                       out.flush();
           }
       }
   }
   catch (InterruptedIOException x) {
       Thread.currentThread().interrupt();
   }
   catch (IOException x) {
       trouble = true;
   }
}
字符输出流,将字符数组的所有字符输出。
所以打印结果为: ab

从上面两个问题的分析可以得出结论:
println()这个方法,内部都是先将参数进行字符化,然后再通过xxxWrite字符输出流将参数字符化后的内容输出。
当参数是char[],这就不需要字符化了,因为String的底层数据存储就是char[],所以直接调用write(char[] s)输出即可。
当参数是非char[]的其他类型数组,就必须先进行字符化,调用String.valueOf(Object obj),返回字符串,再调用write(String s)进行输出。

问题三:下面调用什么方法?打印结果是什么?

char[] arr2 = new char[]{'a','b'};
System.out.println(arr2);
System.out.println("arr2:" + arr2);
第一个打印:ab
第二个打印:arr2:[C@1b84c92	

第一个打印上面讲过,调用的是println(char x[]),输出全部的字符。

第二个打印,因为字符串拼接,所以参数是String,调用的是PrintStream重载方法println(String x)
public void println(String x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}
public void print(String s) {
    if (s == null) {
        s = "null";
    }
    write(s);
}

private void write(String s) {
    try {
        synchronized (this) {
            ensureOpen();
            textOut.write(s);
            textOut.flushBuffer();
            charOut.flushBuffer();
            if (autoFlush && (s.indexOf('\n') >= 0))
                out.flush();
        }
    }
    catch (InterruptedIOException x) {
        Thread.currentThread().interrupt();
    }
    catch (IOException x) {
        trouble = true;
    }
}

问题四:下面调用什么方法?打印结果是什么?

char[] arr3 = null;
System.out.println("arr3:" + arr3);
System.out.println(arr3);	

第一个打印:arr3:null
第二个:报错:Exception in thread "main" java.lang.NullPointerException

第一个打印,调用的是PrintStream重载方法println(String x)
第二个打印,调用的是println(char x[]),但是去看它的方法实现,发现并没有去考虑char[]参数为空的情况。
private void write(char buf[]) {
   try {
       synchronized (this) {
           ensureOpen();
           textOut.write(buf);
           textOut.flushBuffer();
           charOut.flushBuffer();
           if (autoFlush) {
               for (int i = 0; i < buf.length; i++)
                   if (buf[i] == '\n')
                       out.flush();
           }
       }
...
}
仔细查找报错的位置,发现是 textOut.write(buf);,具体方法是
public void write(char cbuf[]) throws IOException {
   write(cbuf, 0, cbuf.length);
}
再具体就是 cbuf.length,我们传入的char[]数组是null,访问length属性就会报空指针异常!

所以,鄙人陋见,这可能是当初大佬们的一个小忽视吧,如果提前在println()或者println()方法内考虑到传入的数组是null,代码也会更健壮一些,你觉得呢?

所以避坑的要点就是:当参数为char[]时,一定要先判断它是非null的。

问题五:下面调用什么方法?打印结果是什么?

System.out.println(123); 
很显然,这个真是太简单了,打印就是123嘛。调用的是PrintStream的println(int i)
那么底层是如何实现的呢?  
 

传入的参数是基本类型,这里我们就分析println(int i)
    public void println(int x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

    public void print(int i) {
        write(String.valueOf(i));
    }

    public static String valueOf(int i) {
        return Integer.toString(i);
    }
看到这里,可以印证上面的结论: println()这个方法,内部都是先将参数进行字符化,然后再通过xxxWrite字符输出流将参数字符化后的内容输出。

就算参数是基本数据类型, 也是需要先将其字符化,底层是通过它对应的包装类调用类方法toString(int i),注意这个特别指出是类方法,也就是包装类自己定义的static方法,而不是重写Object的toString()方法。不过有意思的是,包装类确实重写了Object的toString()方法,内部实现就是调用了自己的类方法toString(int i)

重写Object的toString()方法
    public String toString() {
        return toString(value);
    }
Integer定义的类方法toString(int i)
 public static String toString(int i) {
        if (i == Integer.MIN_VALUE)
            return "-2147483648";
        int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
        char[] buf = new char[size];
        getChars(i, size, buf);
        return new String(buf, true);
    }

关于包装类的类方法toString(int i)分析,可以看我的下篇–分析一下String.valueOf()有很多的重载方法,分析String.valueOf(int i)

<think>我们正在处理Java中使用Collectors.toMap时键重复的合并问题。根据引用,当出现重复键时,我们可以使用Collectors.toMap的重载方法,它接受一个合并函数(merge function)来处理冲突。引用[1]指出,可以通过Collectors.toMap(keyMapper,valueMapper, mergeFunction)来解决重复键问题。引用[2]展示了toMap方法的签名,其中包含四个参数:keyMapper,valueMapper, mergeFunction,mapSupplier。但我们通常使用三个参数的重载方法(省略mapSupplier)即可。引用[3]提供了一个示例,使用合并函数来处理重复键。在示例中,当遇到同一个键(例如“张三”)时,选择使用新值覆盖旧值((oldValue, newValue)-> newValue)。因此,我们可以这样处理:在Collectors.toMap方法中,除了提供keyMapper和valueMapper外,再提供一个合并函数(BinaryOperator)来决定当键重复时如何合并值。例如,如果我们有一个对象列表,每个对象有key和value,我们想按key来映射,当key重复时,我们可以选择:1.保留旧值:(oldValue, newValue)-> oldValue2.保留新值:(oldValue, newValue)-> newValue3.合并为集合:例如,将两个值合并为一个列表,但注意这样值类型会改变,需要调整为集合类型。下面给出具体代码示例:假设有一个类Person,有id和name属性,我们想将id作为键,name作为值,但当id重复时,我们希望取最后出现的那个name。代码示例: ```javaimport java.util.*;import java.util.stream.Collectors;public classMain{public staticvoid main(String[]args){List<Person>persons =Arrays.asList(new Person(1, "Alice"),newPerson(2,"Bob"),new Person(1, "Alex")//重复id=1);Map<Integer,String>idToName =persons.stream().collect(Collectors.toMap(Person::getId,Person::getName,(oldValue, newValue)-> newValue));//合并函数:保留新值System.out.println(idToName); //输出:{1=Alex,2=Bob}} }class Person{private intid;privateString name;public Person(int id, Stringname){this.id= id;this.name= name;}public intgetId(){ returnid;}public StringgetName(){ returnname;} }```如果我们希望保留旧值,则合并函数改为:`(oldValue, newValue)-> oldValue`。另外,如果我们希望将重复键的值合并为一个列表,可以这样: ```javaMap<Integer, List<String>>idToNames =persons.stream().collect(Collectors.toMap(Person::getId,person ->newArrayList<>(Collections.singletonList(person.getName())), //每个元素映射为一个单元素列表(oldList,newList) ->{oldList.addAll(newList);return oldList;})); ```但是,上面的代码在合并时效率可能不高(因为每次合并都要新建列表并添加)。我们可以使用`Collectors.groupingBy`来达到同样的效果,但既然问题要求使用toMap,我们也可以这样做。不过,更常见的做法是使用groupingBy来分组。另外,注意引用[3]中使用了四个参数的toMap方法,但实际上我们上面的例子使用的是三个参数(keyMapper,valueMapper, mergeFunction)的版本。如果需要指定具体的Map实现(如TreeMap),则可以使用四个参数的版本,例如:```javaMap<Integer,String>idToName =persons.stream().collect(Collectors.toMap(Person::getId,Person::getName,(oldValue, newValue)-> newValue,TreeMap::new)); //指定为TreeMap```总结:处理重复键的关键在于提供合并函数(merge function),它决定了当遇到重复键时如何合并两个值。相关问题:1.JavaStream中,除了Collectors.toMap,还有哪些方法可以处理键重复的情况?2.使用Collectors.toMap时,合并函数可以执行哪些复杂的合并操作? 3.如何在使用Collectors.toMap时指定返回的Map类型(如HashMap, TreeMap)?</think>### Java Collectors.toMap 键重复合并处理 在 Java Stream 中使用 `Collectors.toMap` 时,如果遇到键(key)重复的情况,系统会抛出 `IllegalStateException` 异常(错误信息:`Duplicate key xxx`)。为解决此问题,需要使用支持合并函数(merge function)的重载方法: ```java Collectors.toMap( keyMapper, valueMapper, mergeFunction // 处理键冲突的合并策略 ) ``` #### 解决方案示例 假设有用户列表,其中存在重复姓名(键),需要合并性别值(值): ```java import java.util.*; import java.util.stream.Collectors; public class MergeExample { public static void main(String[] args) { List<User> users = Arrays.asList( new User("张三", "男"), new User("李四", "女"), new User("张三", "未知") // 重复键 ); // 方案1:保留新值覆盖旧值 Map<String, String> map1 = users.stream() .collect(Collectors.toMap( User::getName, User::getGender, (oldVal, newVal) -> newVal // 合并函数:取新值 )); System.out.println(map1); // {李四=女, 张三=未知} // 方案2:保留旧值忽略新值 Map<String, String> map2 = users.stream() .collect(Collectors.toMap( User::getName, User::getGender, (oldVal, newVal) -> oldVal // 合并函数:保留旧值 )); System.out.println(map2); // {李四=女, 张三=男} // 方案3:复杂合并(拼接值) Map<String, String> map3 = users.stream() .collect(Collectors.toMap( User::getName, User::getGender, (oldVal, newVal) -> oldVal + "/" + newVal // 合并函数:拼接值 )); System.out.println(map3); // {李四=女, 张三=男/未知} } } class User { private String name; private String gender; // 构造方法/getter/setter省略 } ``` #### 关键说明: 1. **合并函数的作用** 当检测到重复键时,系统会调用 `(oldValue, newValue) -> ...` 定义的合并策略,您可自定义处理逻辑[^1]。 2. **四参数扩展方法** 如需指定具体 Map 实现类型(如 `TreeMap`),可使用四参数版本: ```java Collectors.toMap( keyMapper, valueMapper, mergeFunction, TreeMap::new // 指定Map实现 ) ``` 3. **替代方案** 对于复杂合并场景(如值合并为集合),可考虑改用 `Collectors.groupingBy()`: ```java Map<String, List<String>> grouped = users.stream() .collect(Collectors.groupingBy( User::getName, Collectors.mapping(User::getGender, Collectors.toList()) )); ``` #### 最佳实践: - 明确业务需求,选择覆盖策略(保留首次/末次出现或自定义合并) - 对不可预测的重复键场景,**务必提供合并函数**避免运行时异常[^3] - 复杂对象合并时,可在合并函数中实现深度合并逻辑
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值