




已阅读5页,还剩9页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
Java 基础复习笔记 03 我们不太注意的陷阱 刘岩 Email: 1. 虚拟机对字符串的处理 虚拟机是将字符串直接量(不用 new 声明的)对象放到一个对象池中去缓存的,第一 次使用的时候会将其放入池中,如果下次有其他变量访问一摸一样的直接量的话直接 从对象池中去取该直接量,而不是又再生成一个对象。一般该池中的对象都不会被垃 圾回收器回收。 比如: String str1 = “1“; String str2 = “1“; 实际上这段代码仅仅创建了一个字符串直接对象,保存在对象池中,池中对象就是”1”。 str1 和 str2 指向了该对象池的对象位置。 2. 编译时能确定的值 在编译时就能确定的值,在编译的时候就能够确定是否创建新的对象池对象。比如: String str3 = “suhuanzhen123“; String str4 = “suhuanzhen“ + 1 + “2“ + “3“; System.out.println(str3 = str4); 实际上 str3 和 str4 在内存池中都是指向了“suhuanzhen123“这个字符串对象,所以输出 时 true。因为 str4 在编译期间就能确定值是 suhuanzhen123,所以 JVM 不会再创建新 的对象。直接从对象池中去取。 3. 在编译期间不能确定的值 如果字符串的拼接包含了函数、变量等等编译时不确定因素,那么此时就不能指向对 象池中的变量了。比如 String str5 = “suhuanzhen123“; String str6 = “suhuanzhen“ + “1“.length() + “2“ + “3“; System.out.println(str5 = str6); 4. 在编译时就能确定值的字符串变量创建值 比如代码 String str6 = “suhuanzhen“ + “1“ + “2“ + “3“; 实际上仅仅创建了一个对象suhuanzhen123,缓存到对象池。 5. StringBuilder 和 StringBuffer 的区别 一般在单线程的系统环境下优先使用 StringBuilder,因为它是非线程安全的。而在多线 程的环境下,比如 Web 系统,优先使用 StringBuffer,因为它当中绝大部分方法都使用 了 synchronized 进行修饰,保证了多线程环境下的线程安全问题。 6. 字符串之间的比较 用一个线程池的常量对象和一个变量、函数返回值比较直接使用= 其实就可以了比如: String jqgridparams = request.getParameter(“jqgridparams“); if (jqgridparams != null) if (“getparams“ = jqgridparams) / 更直观一点就是 static String getStr() return “suhuanzhen123“; public static void main(String args) String str7 = getStr(); System.out.println(“suhuanzhen123“ = str7); 输出的是 true; 如果是 2 个字符串变量之间比较就不能用 =了 String str7 = getStr(); String str8 = new String(“suhuanzhen123“); System.out.println(str8 = str7); 输出 false。 应该用 System.out.println(str8.equals(str7); 7. 赋值语句的类型提升 上图程序实际上就是类型自动提升发生了错误,num 本身是 int 型,而 num = num+33.567;表达式中使用了 double 类型的数值,那么此表达式会自动向最高级别的基 本类型转型,但是呢 num 自身是 int,会提示不能向上转换的编译错误。如果程序这么 写 public static void main(String args) int num = 1; double numDoub = num+33.567; 就不会报错了。 赋值运算中基本数据转型的级别从高到低排序是 String-double-float-long-int- chart、 short-byte。 如果运算中含有字符串变量,那么二话不说,此表达式所有的运算最后的返回值就是 一个字符串了,比如 int num = 1; double numDoub = num+33.567; String str = num+numDoub+“; 3 / 14 所以最高级别还是 String。 8. 符合运算符中的隐式类型转换 在符合运算符中实际上存在着一个隐藏的类型转换 int sum =1; sum += 30.44; System.out.println(sum); 实际上会输出 31,如果是之前的例子 int sum =1; sum = sum+30.44; 编译器是会报错的。那么+=实质上是什么意思呢!就是 sum += 30.44; 等价于 sum = (int)(sum+30.44); 也就是说符合运算符自身还隐式地包含了一个强制转型的动作。这样做有时候会失去 精度以及高位段位。 short sum =9999; sum += 999999999.9999999; System.out.println(sum); 输出-3826 9. 泛型引起的陷阱 List list = new ArrayList(); list.add(“1“); list.add(2); list.add(“3q“); for (int i = 0; i listNew = list; for (String strNew : listNew) System.out.println(strNew); 第一个循环输出无任何问题,第二个循环报出转型异常。换几话说,集合本身可以存 储不同类型元素的对象的,只不过自从 JDK1.5 之后出现了泛型,才使我们很多人觉得 集合变量仅仅能存一种类型的对象元素。泛型是为了在编译时更好的排除类型转换带 来的繁琐检查。而在大多数应用中确实集合中就是同类型的元素。而泛型配合上加强 的新循环,它会强制进行单一类型的转型操作。所以第二个循环的的结果就是 java.lang.Integer cannot be cast to java.lang.String 泛型还有可能引起擦除,如下: class MyT private PK id; public PK getId() return id; public void setId(PK id) this.id = id; public class TTrap /* * param args */ public static void main(String args) MyT my = new MyT(); my.setId(100); System.out.println(my.getId() + “:“ + my.getId(); MyT mynew = my; System.out.println(mynew.getId() + “:“ + mynew.getId(); 在 mynew.getId()这一句中实际上编译器这时候仅仅将它识别为 Number 了,直接当做 Integer 使用会报错,除非强制转型方可当做 Integer 来使用。这里就相当于将泛型信息 擦除了,程序只有到了真正运行时环境下才能把其当做 Integer 来用,编译期间他不认 识其实质面目。Java 擦除泛型信息是将该对象所有的泛型信息全部擦除掉,不仅仅是 那个传入的泛型,如果此类本身也定义了其他泛型相关的方法、变量,统统没有了! class MyTT private PK id; private List list; public PK getId() return id; public void setId(PK id) this.id = id; public List findRows() list = new ArrayList(); 5 / 14 list.add(“步惊云 “); list.add(“聂风“); return list; public static void main(String args) MyTT myTT = new MyTT(); for (String str : myTT.findRows() System.out.println(str); MyTT my2 = myTT; List listNoString = my2.findRows(); 执行 MyTT my2 = myTT;实际上是擦除了泛型的所有信息,就连之前定义的 public List findRows()也被无辜的擦除了泛型信息。编译时只能认识 findRows 方法返回 的是 List 而不是 List。 10. 遇到含有.的字符串分割成字符串数组 public static void main(String args) String str = ““; String array = str.split(“.“); System.out.println(array.length); 直接用 String array = str.split(“.“);不起作用,因为”.”代表可以匹配任意字符,必须转义。 11. 什么样的对象存在线程安全问题 当我们刚学 Java 的时候不会考虑多线程的问题,在自己的 IDE 环境下运行成功了就行 了,不会考虑并发使用此程序的时候会出现什么情况。等做程序员一段时间后发现自 己编写的程序确实存在,多线程安全问题。之后走火入魔似地给自己写的方法加上 synchronized。其实我们有时候没搞懂什么情况下会出现线程安全的问题。如果我们开 发的是单机版的 C/S 应用系统,客户的计算机就是软件产品的服务器,那么会不会有 并发现象呢?只要不是恶意恶搞你的软件,一般一个客户就对应着一个服务,这个时 候不存在并发访问的问题,所以一般情况下不会考虑多线程的问题。如果在基于 B/S 的 Web 系统中,可能服务器是一台机器,世界各地的客户都来访问你开发的系统,为 这些世界各地的客户提供服务,这个时候可能会出现线程安全的问题。为什么说可能, 而不是一定呢?如果你的所有的类和类之间的调用都是通过 new 一个新的对象为之服 务(极端情况下还真有) ,那么 new 实际上是在服务器内存中新开辟一块内存空间承载 对象,那么这种极端情况是不存在线程安全问题的,只要您的服务器内存极大1TB 内存,每日客户量在 2 万人左右,估计运行 1 个月应该差不多没什么问题。不过考虑 成本,一般没这样高级的服务器让这种极端的程序运行吧。那么最多的就是类之间的 调用不是永远的 new 一个新的出来,而是为了节省资源,使用对象缓存池或者干脆整 个应用软件调用比较频繁的类就使用一个单例对象就得了,每次使用这个对象都是原 来已有的,不必在内存新建一个,这样垃圾回收器的工作量也不会那么大。而恰恰是 这个时候,这个复用的对象就会出现线程安全的问题。所以在开发 Web 系统的时候大 家一定要小心自己编写的类给别人调用的时候是否存在线程安全的问题。 如下程序: package sy; class Run implements Runnable Integer numIhread = new Integer(0); int num; String numStr = “; public Run() Override public void run() / synchronized (numStr) for (int i = 0; i numIhread = new ThreadLocal(); int num; String numStr = “; public Run() Override public void run() / synchronized (numStr) for (int i = 0; i 100; i+) numStr = “ + i; System.out.println(Thread.currentThread().getName() + “:num=“ + num + “-numStr:“ + numStr + “=numIhread:“ + numIhread.get(); num+; if (numIhread.get() = null) numIhread.set(0); int numTemp = numIhread.get(); numIhread.set(+numTemp); / public class RunClass /* * param args */ public static void main(String args) Run run1 = new Run(); / Run run2 = new Run(); new Thread(run1).start(); new Thread(run1).start(); 运行后效果片断如下 Thread-0:num=0-numStr:0=numIhread:null Thread-1:num=0-numStr:0=numIhread:null Thread-1:num=2-numStr:1=numIhread:1 Thread-1:num=3-numStr:2=numIhread:2 Thread-1:num=4-numStr:3=numIhread:3 Thread-1:num=5-numStr:4=numIhread:4 Thread-1:num=6-numStr:5=numIhread:5 Thread-1:num=7-numStr:6=numIhread:6 Thread-1:num=8-numStr:7=numIhread:7 Thread-1:num=9-numStr:8=numIhread:8 Thread-1:num=10-numStr:9=numIhread:9 Thread-1:num=11-numStr:10=numIhread:10 Thread-1:num=12-numStr:11=numIhread:11 Thread-1:num=13-numStr:12=numIhread:12 9 / 14 Thread-1:num=14-numStr:13=numIhread:13 Thread-1:num=15-numStr:14=numIhread:14 Thread-1:num=16-numStr:15=numIhread:15 Thread-1:num=17-numStr:16=numIhread:16 Thread-1:num=18-numStr:17=numIhread:17 Thread-1:num=19-numStr:18=numIhread:18 Thread-1:num=20-numStr:19=numIhread:19 Thread-1:num=21-numStr:20=numIhread:20 Thread-1:num=22-numStr:21=numIhread:21 Thread-1:num=23-numStr:22=numIhread:22 Thread-0:num=2-numStr:1=numIhread:1 Thread-0:num=25-numStr:2=numIhread:2 Thread-1:num=24-numStr:23=numIhread:23 注意红色字体和蓝色字体,2 个线程互不侵犯,拿到的 numThread 是第一个线程的变 量副本,因此互不干扰,你走你的阳关道,我走我的独木桥。 12. 用 synchronized 和 ThreadLocal 的区别 其实网上已经很多资料介绍这 2 个的区别了,synchronized 表示阻塞,是一种用时间换 取空间的做法,而 ThreadLocal 呢是用资源空间换取时间的做法。 synchronized 表示一 旦某个线程过来了,使用了此修饰后的方法或者代码块,那么其他任何线程不允许访 问,直到此方法或者代码块都走完了,其他那些排队的线程再来执行,也就是说再某 一个时刻,这个方法的权柄完全由单独一个线程把持着,别人别想越雷池一步,谁让 你不早点来的,等老子使用完了,你再用吧。这就好比合租房子的厕所,厕所是个资 源,一旦被人使用了,不好意思,其他人排队等着吧。而 ThreadLocal 呢,相当于大家 上的公用厕所,而且只要是地球空间允许(对应于内存空间) ,那么将是无限个坑位, 嘻嘻,爽吧,想上就上,而且互不打扰,你拉你的,我拉我的,而且大家的坑位都是 从同一个模板构造出来的,不会出现不同的人使用不同样式的坑位,比如残疾人、老 年人。一视同仁!ThreadLocal 底层代码使用一个静态的内部类 ThreadLocalMap 存储副 本变量,好让另一个线程使用(都是爷,它谁也不敢怠慢) 。 13. 线程的启动 至于线程的调用,还有一点就是只要是线程,无论是继承自 Thread 类的还是实现类 Runnable 接口的,都必须使用 start()方法作为启动线程的标识。如果不调用 start()方法, 那么他不能算是以线程为单元执行。 14. 静态同步方法 在静态方法前用 synchronized 修饰实际上是将整个静态类对象的该方法上了锁,也就 是说锁定对象不是实例对象,而是类对象,所有的实例对象都是有内存中的仅有的一 个类对象得来的。如果这个静态方法没有访问(或者说改变更贴切一些)任何的静态 成员变量,那么其实进行加锁限定除了降低时间效率外没太大的作用。 package trap; public class ThreadTrap implements Runnable static int num; public synchronized static void test() int sum = 0; while (sum 100) System.out .println(Thread.currentThread().getName() + “ sum:“+sum); sum+; try Thread.sleep(1); catch (InterruptedException e) / TODO Auto-generated catch block e.printStackTrace(); if (sum = 50) break; Override public void run() test(); /* * param args */ public static void main(String args) ThreadTrap run1 = new ThreadTrap(); / Run run2 = new Run(); new Thread(run1).start(); new Thread(run1).start(); 同步的静态方法中实际上并没有访问静态变量。那么其实加与不加 synchronized 差别 不是很大,加入这个静态方法法十分消耗时间,那么另一个线程会等待很长时间才能 执行此方法。那么这个时候加上 synchronized 还真不行,这就是典型的占着皇帝的宝 座,不为老百姓办事!还不让别人坐,结果很显然,只能造反(系统崩溃) ,才能解决 问题。 15. switch 语句的用法 有人说:“笔者基础也太差了,switch 都得说说?”是啊?哥们得说说啊。试问,自 从你学了分支语句后,你使用 if else 多啊?还是用 switch 的情况多啊?很肯定,很多 人在开发中 switch 几乎没怎么用,使用 if else 不容易出错,之后就渐渐淡忘了 switch 的用法。现在咱们来复习一下。 11 / 14 public static void main(String args) int num = -1; switch (num) case 5: System.out.println(“小学牛逼啊!经常作业得 5分“); break; case 4: System.out.println(“小学还行啊!经常作业得 4分“); break; case 3: System.out.println(“小学贪玩啊!经常作业得 3分“); break; case 2: System.out.println(“小学干嘛呢!经常作业得 2分,坏学生“); break; case 1: System.out.println(“小学胆子够大的!经常作业得 1分“); break; case 0: System.out.println(“小学牛逼啊!敢和老师叫板 “); break; default: System.out.println(“这孩子没上小学“); break; switch 中的字符是欲判断变量,下面的分支就根据此变量。每一个分支 case 代表了该 变量的一种可能性,如果没有任何 case 符合,那么就进入 default 处理。每一个分支都 有一个 break,代表如果符合此分支的情况,处理完毕后会退出 switch 块。如果没有 break,那么会继续往下面的 case 走,无论下面的 case 是否符合都会执行,直到遇到 break 或者整个 switch 块走完方才停止。 比如 int num = 3; switch (num) case 5: System.out.println(“小学牛逼啊!经常作业得 5分“); break; case 4: System.out.println(“小学还行啊!经常作业得 4分“); break; case 3: System.out.println(“小学贪玩啊!经常作业得 3分“); ; case 2: System.out.println(“小学干嘛呢!经常作业得 2分,坏学生“); ; case 1: System.out.println(“小学胆子够大的!经常作业得 1分“); ; case 0: System.out.println(“小学牛逼啊!敢和老师叫板 “); break; default: System.out.println(“这孩子没上小学“); break; 会输出 小学贪玩啊!经常作业得3分 小学干嘛呢!经常作业得2分,坏学生 小学胆子够大的!经常作业得1分 小学牛逼啊!敢和老师叫板 这也是为什么那么多人喜欢使用 if else 了,哪怕一个简单的字符匹配,也用 if else。switch 表达式的类型只有 Java 个别的基本类型和枚举 byte、short、int、char 、enum。其他类型都不能作为预判断变量。 16. 方法的重载 JVM 在识别方法的时候有一定的智能型,传入的实参如果和定义的形参不一样的话, 那么会向上转型以适应方法的调用需要,比如 public static void test(String str, double num) System.out.println(“test(String str,double num):“ + str + “|“ + num); /* * param args */ public static void main(String args) test(“叶“ , 10); 实参第二个参数不是 double 类型的,那么再执行 test 的时候它会向上转型,让它自动 匹配 double,如果重载方法还有更精确的形参,那么自然会去匹配最精确的方法了。 比如 public static void test(String str, double num) System.out.println(“test(String str,double num):“ + str + “|“ + num); 13 / 14 public static void test(String str, int num) System.out.println(“test(String str,int num):“ + str + “|“ + num); /* * param args */ public static void main(String args) test(“叶“ , 10); 实参 10 不必向上转型,直接执行 test(String str, int num)这个方法。 17. 非静态内部类 非静态内部类实际上需要外部类才能顺利创建实例,非静态内部类的构造函数,JVM 都做了特别的处理,将外部类对象作为默认构造器的第一个隐式参数。而且非静态内 部类有个限制,不能拥有静态成员变量,因为非静态的内部类本身就处在一个非静态 的上下文环境中 18. 静态内部类 如果非使用静态类不可,其实大多数都是用静态内部类,因为静态内部类可以访问外 部类的静态成员变量。而换句话说,静态内部类不能访问外部类的非静态成员变量
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025房地产项目室内外装饰设计施工监理合同
- 2025年中小企业融资连带担保合同范本
- 2025年度图书馆图书采购与可持续发展合同
- 2025年风力发电场电线电缆配套供应协议
- 2025版历史遗迹拆除与文化遗产保护合同范本
- 2025年度高新技术项目融资服务合同范本二
- 2025年度塑料包装袋环保回收利用采购合同
- 2025版新型节能电器贴牌生产销售合同
- 2025年二层商铺楼房租赁合同及商业地产市场调研与分析服务协议
- 2025年度健身房室内涂料施工协议
- 高产小麦品种介绍
- HemiView森林冠层图像分析系统
- 创新方法教程题库题库(449道)
- 建设工程消防验收评定规则
- 山东省临沂市兰山区2022-2023学年小升初数学自主招生备考卷含答案
- 电气设备交接试验方案
- D500-D505 2016年合订本防雷与接地图集
- 北邮社电机拖动与调速技术教学包课后题解
- 学校门卫岗位职责及管理制度
- JJG 1105-2015氨气检测仪
- GB/T 17421.7-2016机床检验通则第7部分:回转轴线的几何精度
评论
0/150
提交评论