当前位置: 首页 > news >正文

java解惑你知多少(七)

应用

47. 不可变的引用类型

Java代码   收藏代码
  1. BigInteger total = BigInteger.ZERO;  
  2. total.add(new  BigInteger( "1" ));  
  3. total.add(new  BigInteger( "10" ));  
  4. System.out.println(total);//0   
BigInteger total = BigInteger.ZERO;
total.add(new BigInteger("1"));
total.add(new BigInteger("10"));
System.out.println(total);//0

上面程序的结果为11吗?答案是0。

 

BigInteger实例是不可变的。String、BigDecimal以及包装类型:Integer、Long、Short、Byte、Character、Boolean、Float和Double也是如此。对这些类型的操作将返回新的实例。

 

不可变类型更容易设计、实现与作用;它们出错的可能性更小,并且更加安全。

 

本程序修改如下:

Java代码   收藏代码
  1. BigInteger total = BigInteger.ZERO;  
  2. total=total.add(new  BigInteger( "1" ));  
  3. total=total.add(new  BigInteger( "10" ));  
  4. System.out.println(total);//11   
BigInteger total = BigInteger.ZERO;
total=total.add(new BigInteger("1"));
total=total.add(new BigInteger("10"));
System.out.println(total);//11

 

48. 请同时重写equals()与hashCode()

Java代码   收藏代码
  1. class  T {  
  2.  private  String str;  
  3.   
  4.  T(String str) {  
  5.   this .str = str;  
  6.  }  
  7.   
  8.  public   boolean  equals(Object obj) {  
  9.   if (!(obj  instanceof  T)){  
  10.    return   false ;  
  11.   }  
  12.   T t = (T)obj;  
  13.   return  t.equals( this .str);  
  14.  }  
  15.   
  16.  public   static   void  main(String[] args) {  
  17.   Set set = new  HashSet();  
  18.   set.add(new  T( "str" ));  
  19.   System.out.println(set.contains(new  T( "str" ))); //false   
  20.  }  
  21. }  
class T {
 private String str;

 T(String str) {
  this.str = str;
 }

 public boolean equals(Object obj) {
  if(!(obj instanceof T)){
   return false;
  }
  T t = (T)obj;
  return t.equals(this.str);
 }

 public static void main(String[] args) {
  Set set = new HashSet();
  set.add(new T("str"));
  System.out.println(set.contains(new T("str")));//false
 }
}

上面的程序不会打印true,而是false,为什么?

 

hashCode约定要求相等的对象要具有相同的散列码。

 

无论何时,只要你重写了equals方法,你就必须同时重写hashCode方法。

 

如果将自定的类型对象放入HashSet、HashMap、Hashtable、LinkedHashSet、LinkedHashMap这此散列 集合时,一定需要重写equals与hashCode方法,这样在放入进去之后还能查找出来。如果放入其他非散列类型的集合时,其实只需要

重写equals就可以了。

 

本程序解决办法重写hashCode()方法:

Java代码   收藏代码
  1. public   int  hashCode() {  
  2.  return   37  *  this .str.hashCode();  
  3. }  
public int hashCode() {
 return 37 * this.str.hashCode();
}

 
49. 日期设置

Java代码   收藏代码
  1. Calendar c = Calendar.getInstance();  
  2. c.set(20101231 ); // 月是从0开始的,11其实表示12月   
  3. System.out.println(c.get(Calendar.YEAR) + " "  + c.get(Calendar.MONTH));  
  4. c = Calendar.getInstance();  
  5. c.set(20101131 );  
  6. System.out.println(c.get(Calendar.YEAR) + " "  + c.get(Calendar.MONTH));  
Calendar c = Calendar.getInstance();
c.set(2010, 12, 31);// 月是从0开始的,11其实表示12月
System.out.println(c.get(Calendar.YEAR) + " " + c.get(Calendar.MONTH));
c = Calendar.getInstance();
c.set(2010, 11, 31);
System.out.println(c.get(Calendar.YEAR) + " " + c.get(Calendar.MONTH));

本程序较简单,只需注意月是从0开始的就可以了,如果你设置月为12,则会自动转换为下一年。


50. IdentityHashMap

 

Java代码   收藏代码
  1. class  T {  
  2.  private  String str;  
  3.   
  4.  T(String str) {  
  5.   this .str = str;  
  6.  }  
  7.   
  8.  public   int  hashCode() {  
  9.   return   37  *  this .str.hashCode();  
  10.  }  
  11.   
  12.  public   boolean  equals(Object obj) {  
  13.   return   this .str.equals(((T) obj).str);  
  14.  }  
  15.   
  16.  public   static   void  put(Map m) {  
  17.   m.put("str""1" );  
  18.   /*  
  19.    * 由于上面程序将 "str" 放入了字符串常量池,  
  20.    * 所以str是同一个对象,不管是什么样类型的  
  21.    * Map,即使使用IdentityHashMap都只放入一次  
  22.    */   
  23.   m.put("str""2" );  
  24.   m.put(new  T( "str" ),  "3" );  
  25.   m.put(new  T( "str" ),  "4" );  
  26.  }  
  27.   
  28.  public   static   void  main(String[] args) {  
  29.   Map m = new  HashMap();  
  30.   put(m);  
  31.   System.out.println(m.size());// 2   
  32.   //IdentityHashMap比较时使用==替换equals()方法   
  33.   m = new  IdentityHashMap();  
  34.   put(m);  
  35.   System.out.println(m.size());// 3   
  36.  }  
  37. }  
class T {
 private String str;

 T(String str) {
  this.str = str;
 }

 public int hashCode() {
  return 37 * this.str.hashCode();
 }

 public boolean equals(Object obj) {
  return this.str.equals(((T) obj).str);
 }

 public static void put(Map m) {
  m.put("str", "1");
  /*
   * 由于上面程序将 "str" 放入了字符串常量池,
   * 所以str是同一个对象,不管是什么样类型的
   * Map,即使使用IdentityHashMap都只放入一次
   */
  m.put("str", "2");
  m.put(new T("str"), "3");
  m.put(new T("str"), "4");
 }

 public static void main(String[] args) {
  Map m = new HashMap();
  put(m);
  System.out.println(m.size());// 2
  //IdentityHashMap比较时使用==替换equals()方法
  m = new IdentityHashMap();
  put(m);
  System.out.println(m.size());// 3
 }
}

 

51. 静态导入的优先权

Java代码   收藏代码
  1. import   static  java.util.Arrays.toString;  
  2. import  java.util.Arrays;  
  3. public   class  T {  
  4.  public   static   void  main(String[] args) {  
  5.   prt(123 );  
  6.  }  
  7.  static   void  prt(Object... args) {  
  8.   // 自身继承至Object类的toString的优先级高于静态导入的方法   
  9.   //!! System.out.println(toString(args));//不能编译   
  10.   System.out.println(Arrays.toString(args));  
  11.  }  
  12. }  
import static java.util.Arrays.toString;
import java.util.Arrays;
public class T {
 public static void main(String[] args) {
  prt(1, 2, 3);
 }
 static void prt(Object... args) {
  // 自身继承至Object类的toString的优先级高于静态导入的方法
  //!! System.out.println(toString(args));//不能编译
  System.out.println(Arrays.toString(args));
 }
}

本身就属于某个范围的成员在该范围内与静态导入相比具有优先权。


52. PrintStream对输出结果的缓冲

Java代码   收藏代码
  1. public   static   void  main(String[] args) {  
  2.  String str = "Hello World" ;  
  3.  for  ( int  i =  0 ; i < str.length(); i++) {  
  4.   System.out.write(str.charAt(i));  
  5.  }  
  6. }  
public static void main(String[] args) {
 String str = "Hello World";
 for (int i = 0; i < str.length(); i++) {
  System.out.write(str.charAt(i));
 }
}

上面的程序没有输出结果。

 

这里的问题在于System.out是带有缓冲的。输出的结果被写入了System.out的缓冲区,但是缓冲区从来都没有被刷新。大多数人认为, 当有输出产生的时候System.out和System.err会自动地进制刷新,但这并不完全正确,这两个流都属于PrintStream类型,请看 API DOC描述:一个PrintStream被创建为自动刷新,这意味着当一个字节数组(byte[])被写入、或者某个println方法被调用、或者一个 换行字符或字节('/n')被写入之后,PrintStream类型的flush方法就会被自动调用。

 

令人奇怪的是,如果这个程序用print(char)去替代write(int),它就会刷新System.out并输出结果,这种行为与 print(char)的文档是矛盾的,因为其文档叙述道:“打印一个字符,这个字符将根据平台缺省的字符编码方式翻译成一个或多个字节,并且这些字节将 完全按照write(int)方法的方式输出。”,但这里没有换行符却也自动的刷新了。

 

类似的,如果程序改用print(String),它也会对流进行刷新。所以调用print方法也是会自动刷新的。

 

53. 调用操作系统命令时被阻塞问题

Java代码   收藏代码
  1. public   static   void  main(String[] args)  throws  IOException,  
  2.   InterruptedException {  
  3.  String command = "java ProcessTest exc" ;  
  4.  if  (args.length !=  0 ) {  
  5.   for  ( int  i =  0 ; i <  200 ; i++) {  
  6.    System.out.println(command);  
  7.    System.err.println(command);  
  8.   }  
  9.  } else  {  
  10.   Process process = Runtime.getRuntime().exec(command);    
  11.   int  exitValue = process.waitFor();  
  12.   System.out.println("exit value = "  + exitValue);  
  13.  }  
  14. }  
public static void main(String[] args) throws IOException,
  InterruptedException {
 String command = "java ProcessTest exc";
 if (args.length != 0) {
  for (int i = 0; i < 200; i++) {
   System.out.println(command);
   System.err.println(command);
  }
 } else {
  Process process = Runtime.getRuntime().exec(command);  
  int exitValue = process.waitFor();
  System.out.println("exit value = " + exitValue);
 }
}

执行java ProcessTest发现程序阻塞。

 

Process文档描述:由于某些本地平台只提供有限大小的缓冲,所以如果不能迅速地读取子进程的输出流,就有可能会导致子进程的阻塞,甚至是死 锁。这恰好就是这里所发生的事情:没有足够的缓冲空间来保存这些输出结果。为了结子进程(Process线程),父进程(Main线程)必须排空它的输出 流(标准流与错误流都需要排空),即要去缓存中读取结果:

Java代码   收藏代码
  1. static   void  readResult( final  InputStream is) {  
  2.  new  Thread( new  Runnable() {  
  3.   public   void  run() {  
  4.    try  {  
  5.     // 排空缓存内容   
  6.     while  (is.read() >=  0 );  
  7.    } catch  (IOException e) {  
  8.     e.printStackTrace();  
  9.    }  
  10.   }  
  11.  }).start();  
  12. }  
static void readResult(final InputStream is) {
 new Thread(new Runnable() {
  public void run() {
   try {
    // 排空缓存内容
    while (is.read() >= 0);
   } catch (IOException e) {
    e.printStackTrace();
   }
  }
 }).start();
}

 

然后在process.waitFor()之前加上

Java代码   收藏代码
  1. readResult(process.getErrorStream());  
  2. readResult(process.getInputStream());  
readResult(process.getErrorStream());
readResult(process.getInputStream());

即可输出exit value = 0。

 

另外,只能根据process.waitFor返回的结果来判断操作系统命令执行是否成功(成功:0,失败:1),我们不能根据

错误流中是否有内容来判断是否执行成功。


54. 实现Serializable的单例问题

Java代码   收藏代码
  1. class  Dog  implements  Serializable{  
  2.  public   static   final  Dog INSTANCE =  new  Dog();  
  3.  private  Dog(){}  
  4. }  
class Dog implements Serializable{
 public static final Dog INSTANCE = new Dog();
 private Dog(){}
}

 上面能控制只生成一个单实例吗?

 

如果对实现了Serializable的对象进行序列化后,再反序列化,内中会不只一个实例了,因为反序列化时会重新生成一个对象。

 

既然INSTANCE为静态域,那序列化时返回的对象如果也是INSTANCE就可以解决问题了,而打开API我们发现Serializable接口确实有这样两个特殊的方法描述:
 将对象写入流时需要指定要使用的替代对象的可序列化类,应使用准确的签名来实现此特殊方法:
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
此 writeReplace 方法将由序列化调用,前提是如果此方法存在,而且它可以通过被序列化对象的类中定义的一个方法访问。因此,该方法可以拥有私有 (private)、受保护的 (protected) 和包私有 (package-private) 访问。子类对此方法的访问遵循 java 访问规则。
 在从流中读取类的一个实例时需要指定替代的类应使用的准确签名来实现此特殊方法:
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
此 readResolve 方法遵循与 writeReplace 相同的调用规则和访问规则。

 

上述两个方法的只要出现,就会履盖以下两个方法(这两个方法本质的意义就是用来替换序列与反序列的对象),虽然会执行它们,但最后得到的结果却是writeReplace、readResolve两个方法写入或读出的对象:
 private void writeObject(java.io.ObjectOutputStream out) throws IOException
 private void readObject(java.io.ObjectInputStream in)throws IOException, ClassNotFoundException;

 

另外,writeObject与readObject需成对实现,而writeReplace与readResolve则不需要成对出现,一般单独使用。如果同时出现这四个方法,最后写入与读出的结果以writeReplace和readResolve方法的结果为准。

 

所以下要解决真真单实例问题,我们如下修正:

Java代码   收藏代码
  1. class  Dog  implements  Serializable {  
  2.  public   static   final  Dog INSTANCE =  new  Dog();  
  3.  private  Dog() {}  
  4.  private  Object readResolve() {  
  5.   return  INSTANCE;  
  6.  }  
  7. }  
  8.   
  9. public   class  SerialDog {  
  10.  public   static   void  main(String[] args)  throws  IOException,  
  11.    ClassNotFoundException {  
  12.   ByteArrayOutputStream bos = new  ByteArrayOutputStream();  
  13.   new  ObjectOutputStream(bos).writeObject(Dog.INSTANCE);  
  14.   ByteArrayInputStream bin = new  ByteArrayInputStream(bos.toByteArray());  
  15.   Dog dog = (Dog) new  ObjectInputStream(bin).readObject();  
  16.   System.out.println(dog == Dog.INSTANCE);//true   
  17.  }  
  18. }  
class Dog implements Serializable {
 public static final Dog INSTANCE = new Dog();
 private Dog() {}
 private Object readResolve() {
  return INSTANCE;
 }
}

public class SerialDog {
 public static void main(String[] args) throws IOException,
   ClassNotFoundException {
  ByteArrayOutputStream bos = new ByteArrayOutputStream();
  new ObjectOutputStream(bos).writeObject(Dog.INSTANCE);
  ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray());
  Dog dog = (Dog) new ObjectInputStream(bin).readObject();
  System.out.println(dog == Dog.INSTANCE);//true
 }
}

一个实现了Serializable的单例类,必须有一个readResolve方法,用以返回它的唯一实例。


55. thread. isInterrupted()与Thread.interrupted()

Java代码   收藏代码
  1. public   class  SelfInerruption {  
  2.  public   static   void  main(String[] args) {  
  3.   Thread.currentThread().interrupt();  
  4.   if  (Thread.interrupted()) {  
  5.    // Interruped:false   
  6.    System.out.println("Interruped:"  + Thread.interrupted());  
  7.   } else  {  
  8.    System.out.println("Not interruped:"  + Thread.interrupted());  
  9.   }  
  10.  }  
  11. }  
public class SelfInerruption {
 public static void main(String[] args) {
  Thread.currentThread().interrupt();
  if (Thread.interrupted()) {
   // Interruped:false
   System.out.println("Interruped:" + Thread.interrupted());
  } else {
   System.out.println("Not interruped:" + Thread.interrupted());
  }
 }
}

上面结果走的是第一个分支,但结果却不是Interruped:true?

 

Thread.interrupted()为Thread的静态方法,调用它首先会返回当前线程的中断状态(如果当前线程上调用了 interrupt()方法,则返回true,否则为false),然后再清除当前线程的中断状态,即将中断状态设置为false。换句话说,如果连续两 次调用该方法,则第二次调用将返回 false。

 

而isInterrupted()方法为实例方法,测试线程是否已经中断,并不会清除当前线程中断状态。

 

所以这里应该使用isInterrupted()实例方法,就可以修复该问题。

 

相关文章:

  • css3 TransformZ() 3D缩放
  • java解惑你知多少(八)
  • 多线程总结之旅(8):线程同步之信号量
  • java类初始化顺序
  • bootstrap总结
  • java创建对象的四种方式
  • java基础之String
  • 为什么单例对象的并发调用需要同步?
  • Spring_事务(1)
  • java集合框架总结
  • LeetCode-Count Bits
  • Java中对HashMap的深度分析与比较
  • java 线程小结
  • JAVA中精确计算金额BigDecimal
  • java并发编程实践笔记
  • flask接收请求并推入栈
  • JS进阶 - JS 、JS-Web-API与DOM、BOM
  • KMP算法及优化
  • PV统计优化设计
  • 从零搭建Koa2 Server
  • 什么软件可以提取视频中的音频制作成手机铃声
  • 说说动画卡顿的解决方案
  • 一文看透浏览器架构
  • 用Canvas画一棵二叉树
  • 运行时添加log4j2的appender
  • 阿里云API、SDK和CLI应用实践方案
  • ​​​​​​​Installing ROS on the Raspberry Pi
  • ​Java并发新构件之Exchanger
  • ​创新驱动,边缘计算领袖:亚马逊云科技海外服务器服务再进化
  • ​批处理文件中的errorlevel用法
  • $GOPATH/go.mod exists but should not goland
  • (4) openssl rsa/pkey(查看私钥、从私钥中提取公钥、查看公钥)
  • (C语言)编写程序将一个4×4的数组进行顺时针旋转90度后输出。
  • (Java岗)秋招打卡!一本学历拿下美团、阿里、快手、米哈游offer
  • (附源码)spring boot校园拼车微信小程序 毕业设计 091617
  • (十六)一篇文章学会Java的常用API
  • (十七)devops持续集成开发——使用jenkins流水线pipeline方式发布一个微服务项目
  • (一)spring cloud微服务分布式云架构 - Spring Cloud简介
  • (一)WLAN定义和基本架构转
  • (转)关于如何学好游戏3D引擎编程的一些经验
  • ****** 二十三 ******、软设笔记【数据库】-数据操作-常用关系操作、关系运算
  • ./和../以及/和~之间的区别
  • .NET 中 GetHashCode 的哈希值有多大概率会相同(哈希碰撞)
  • .netcore 6.0/7.0项目迁移至.netcore 8.0 注意事项
  • .考试倒计时43天!来提分啦!
  • /proc/vmstat 详解
  • [ 隧道技术 ] 反弹shell的集中常见方式(二)bash反弹shell
  • [1159]adb判断手机屏幕状态并点亮屏幕
  • [Android]Android P(9) WIFI学习笔记 - 扫描 (1)
  • [autojs]autojs开关按钮的简单使用
  • [BUUCTF 2018]Online Tool(特详解)
  • [C#]C# winform实现imagecaption图像生成描述图文描述生成
  • [CakePHP] 在Controller中使用Helper
  • [ERROR ImagePull]: failed to pull image k8s.gcr.io/kube-controller-manager失败
  • [Flutter]打包IPA