Java进阶4表达式中的陷阱
Java进阶4表达式中的陷阱 20131103
表达式是Java中最基本的组成单元,各种表达式是Java程序员最司空见惯的内容,Java中的表达式并不是十分的复杂,但是也有一些陷阱。例如当程序中使用算术表达式的时候,表达式的类型自动提升,复合赋值运算符所隐含的类型转换,给程序带来一些潜在的陷阱。还有就是JDK1.5之后支持泛型也会带来一些陷阱,因为之前的Java版本是不支持泛型的,为了兼容之前的版本,引入了原始类型的概念,而原始类型在泛型编程中存在着极大的陷阱。
1.字符串中的陷阱
JVM对于字符串的处理都会在JVM的字符串缓冲池缓冲字符串,而且java中的字符串类型是不可以改变的,如果经常改字符串的话,使用String类型,效率会非常的低.
对于Java语句: String str = new String(“yang”);其实在底层实现的话,是十分低效的,为什么呢?这一句代码创建了两个字符串对象,一个是”yang”这个直接的字符串对象,另一个是new String()构造器返回的字符串对象。(Java中创建对象的方式有如下四种:通过new 创建对象;通过class的getInstance()方法调用构造器创建对象;通过java的反序列化机制从IO流中恢复一个java对象;通过Java对象提供的clone方法复制一个Java对象;同时对于基本的类型,比如Integer 或者是 String类型的话,可以通过简单的算数表达式直接复制,比如Integer i= 3; String str = “yang”;)
对于Java中的字符串,JVM会使用一个字符串缓冲池来保存他们,当第一次使用某个字符串直接量的时,JVM会将它放入字符串缓冲池中。一般来说,字符串缓冲池中的字符串对象是不被回收的,当程序中再次使用给字符串的时候,无需要重新创建一个新的字符串,而是直接让引用变量指向字符串缓冲池中已经有的字符串。
而对于字符串连接表达式创建字符串的话,可以将一个字符串连接是直接赋值给字符串变量,如果字符串在编译的时候确定下来的话,JVM会在编译的时候计算字符串变量的值,并且让他指向字符串池中的对象。
String str1 = “yangtengfei”;
String str2 = “yang”+”teng”+”fei”;
以上两种是等效的,str1== str 其实指向的是同一个对象。字符串连接必须只能呢个够是直接量,而不能够存在变量,也不能够有函数调用,否则在编译期间就无法确定字符传的内容。
当然还有一种比较特殊的情况,就是执行宏替换,那么JVM一样可以在编译的时候确定字符串的内容,一样会让字符串变量指向JVM字符串池中的对应的字符串。
final int num = 10;
String str1 = "yang10";
String str2 = "yang" + num;
System.out.println(str1==str2); //true
当程序中使用String 或者是基本类型的封装的时候,尽量使用直接量赋值,避免使用new关键字。
对于String而言,它代表的字符串序列是不可以改变的,因此程序中需要会发生改变的字符串的话,应该考虑使用StringBuffer或者是StringBuilder,两者的区别是StringBuffer是线程安全的,如果不是多线程程序的话,就应该优先使用StringBuilder,因为这样的话效率比较高。
2.表达式类型的陷阱
Java是一门强类型的语言,所有的数据都有指定的数据类型,因此表达式一定要注意他的数据类型。所谓的强类型的编程语言,就是所有的变量在使用之前必须实现声明,声明的变量必须指定该变量的数据类型;一旦一个变量的数据类型确定下来,那么这个变量将永远只能够接受该类型的值。
当一个算数运算表达式包含多个基本类型的数据的时候,整个运算符表达式数据类型将会自动提升,提升规则如下:byte char short豆浆提升到int类型;整个算术表达式的数据类型自动提升到于表达式中最高级别的数据类型
short sVal = 2;
short sVal = 10;
//sVal =sVal - 2;编译不通过,因为int无法默认转换成为short
sVal -=2;
E1 op= E2 equals the E1 = (E1) (E1 op E2) not the E1 = E1 op E2
在Java7中新增加了二进制的数据支持,但是也引入了新的陷阱
int it = 0b1010_1010;
3.多线程中的陷阱
3.1不要直接调用线程中的run方法,而是start方法启动新的线程,否则只是当做一个简单的成员函数进行调用,而没有启动另一个线程。
3.2静态同步方法、
在Java中提供了synchronized关键字用于修饰方法,是哟给synchronized修饰的方法被称为同步方法,淡然还可以使用synchronized关键字修饰代码块,称之为同步代码块。
Java语法规定:任何线程进入同步代码块,同步方法之前,必须获得同步代码块、同步方法的同步监视器。对于同步代码块而言,必须显示的指出同步监视器;对于非静态的同步方法,该方法获得的同步监视器是当前对象—机调用该方法的对象;对于静态的同步方法的话,则获得是同步监视器是类本身。其中同步的静态成员函数和this对象是不想冲突的。
public class TestMain implements Runnable{
public static void main(String[] args) throws ClassNotFoundException {
Thread ss = new Thread(new TestMain());
Thread sss = new Thread(new TestMain());
ss.start();
sss.start();
}
static boolean staticFlag = true;
public static synchronized void test0() throws InterruptedException{
for(int i = 0; i < 10; i++){
System.out.println("test0 func " + i );
Thread.sleep(1000);
}
}
public void test1() throws InterruptedException{
synchronized(this){
for(int i = 0; i< 10; i++){
System.out.println("test1 func " + i);
Thread.sleep(1000);
}
}
}
@Override
public void run() {
if(staticFlag){
staticFlag = false;
try {
test0();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else{
staticFlag = true;
try {
test1();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
其实上面两个同步代码块是不会相互影响的因为针对的不是同一个同步监视器。
如何锁住当前类的,使用synchronized(MyClassName.class){}就实现了同步了这个类。
3.3静态初始化代码块启动新线程执行执行初始化
package yang.main;
public class TestMain{
static {
Thread t = new Thread(){
public void run(){
System.out.println("enter run func");
System.out.println("run func : " + name);
name = "huihui";
System.out.println("leave run func");
}
};
t.start();
try{
t.join();
}catch(Exception ex){
ex.printStackTrace();
}
}
static String name = "yangtengfei";
public static void main(String []args){
System.out.println("main thread " + name);
}
}
这个时候就会出现死锁的情况,输出的结果是”enter run func”,之后就一直处于死锁状态。
分析一下代码的执行过程:为该类的所有静态field分配内存,调用静态代码块进行初始化。在这一段代码中首先会为static name 分配内存空间,但是这个时候name的值是null,接着main线程开始执行静态代码块,在静态代码块中启动了一个新的线程,并且在main线程中启动分支线程,并且调用join方法等待分支线程,这一意味着main线程必须等待分支线程结束之后才可以继续往下执行。
在新线程开始的时候,运行输出进入run方法,然后程序试图输出name静态变量的时候,问题就会出现了,因为该类是有main线程进行初始化的,因此新的线程就会等待main线程将Class初始化完成之后,才会继续执行,否则是无法访问该Class的静态成员变量。所以就会出现了我们梦寐以求的死锁情况,是不是很爽啊,其实在代码层面上难以理解,为什么新的线程需要等待main这可能是设计到底层JVM的编译运行机制吧,当我们访问一个Class的静态成员编程,但是有没有初始化好该类,就会等待。
我们在静态代码块中将t.join()方法注释掉,就会执行,但是结果是什么呢,两次访问的静态变量的值都是初始化的时候的值,而不是我们在线程中赋予的值。新的线程好像没有起什么作用。
实际上主线程进入静态代码块之后,同样是创建并且启动了一个新的线程,因为没有调用join方法,main线程就不会等待新的线程,新的线程就是出于就绪的护着你给他,没有运行。Main线程继续执行初始化的操作,将name的值赋值为yangtengfei,至此main线程完成了对于Class的初始化工作,之后主线程进入main方法,输出对象的值,同时在调度新的线程运行。将静态的成员编修进行修改,但是此时main程序已经不再访问了。
让主线程等待的话,虽然可以立马执行新的线程,但是因为访问静态成员变量的时候,阻塞新的线程,又会切换到main线程。
追梦的飞飞
于广州中山大学图书馆 20131103
HomePage: http://yangtengfei.duapp.com
Java进阶4表达式中的陷阱的更多相关文章
- java 8 lambda表达式中的异常处理
目录 简介 处理Unchecked Exception 处理checked Exception 总结 java 8 lambda表达式中的异常处理 简介 java 8中引入了lambda表达式,lam ...
- No.5 表达式中的陷阱
1. 关于字符串的陷阱 JVM对字符串的处理 String java = new String("Java"); 创建了几个对象? 2个."Java"直接量对应 ...
- java、el表达式中保留小数的方法
Java中: import java.math.BigDecimal; import java.text.DecimalFormat; import java.text.NumberFormat; p ...
- Java集合与泛型中的陷阱
List,List<Object>区别 List<Integer> t1 = new ArrayList<>(); // 编译通过 List t2 = t1; // ...
- 在java 8 stream表达式中实现if/else逻辑
目录 简介 传统写法 使用filter 总结 简介 在Stream处理中,我们通常会遇到if/else的判断情况,对于这样的问题我们怎么处理呢? 还记得我们在上一篇文章lambda最佳实践中提到,la ...
- Java向上下转型中的陷阱{详细}
1: 多态 多态时继承下面的产物,之所以存在向上向下转型的目的,就是解决参数传递的不变形,体现面向接口编程的重要性, 1.1 方法的多态性 ①. 方法的重载:同一个方法名称可以根据参数的类型或 ...
- Java 进阶6 异常处理的陷阱
Java 进阶6 异常处理的陷阱 20131113 异常处理机制是 Java语言的特色之一,尤其是 Java的Checked 异常,更是体现了 Java语言的严谨性:没有完善的错误的代码根本就不会被执 ...
- Java进阶5 面向对象的陷阱
Java进阶5 面向对象的陷阱 20131103 Java是一门纯粹面向对象的编程语言,Java面向对象是基础,而且面向对象的基本语法非常多,非常的细,需要程序员经过长时间的学习才可以掌握.本章重点介 ...
- Java语言与JVM中的Lambda表达式全解
Lambda表达式是自Java SE 5引入泛型以来最重大的Java语言新特性,本文是2012年度最后一期Java Magazine中的一篇文章,它介绍了Lamdba的设计初衷,应用场景与基本语法. ...
随机推荐
- SqoopFlume、Flume、HDFS之间比较
Sqoop Flume HDFS Sqoop用于从结构化数据源,例如,RDBMS导入数据 Flume 用于移动批量流数据到HDFS HDFS使用 Hadoop 生态系统存储数据的分布式文件系统 Sqo ...
- 2016-2017 ACM-ICPC Southwestern European Regional Programming Contest (SWERC 2016) B - Bribing Eve
地址:http://codeforces.com/gym/101174/attachments 题目:pdf,略 思路: 把每个人的(x1,x2)抽象成点(xi,yi). 当1号比i号排名高时有==& ...
- hdu3374 String Problem
地址:http://acm.hdu.edu.cn/showproblem.php?pid=3374 题目: String Problem Time Limit: 2000/1000 MS (Java/ ...
- 由浅入深之Tensorflow(4)----Saver&restore
x = tf.placeholder(tf.float32) y = tf.placeholder(tf.float32) w = tf.Variable(tf.zeros([1, 1], dtype ...
- Learning Phrase Representations using RNN Encoder–Decoder for Statistical Machine Translation
1.主要完成的任务是能够将英文转译为法文,使用了一个encoder-decoder模型,在encoder的RNN模型中是将序列转化为一个向量.在decoder中是将向量转化为输出序列,使用encode ...
- Java学习第三周摘要
20145307<Java程序设计>第三周学习总结 教材学习内容总结 认识对象 类类型 Java可区分为基本类型和类类型两大类型系统,其中类类型也称为参考类型.sun就是一个类类型变量,类 ...
- terminal配置
阅读目录 前言 使用 tmux 复用控制台窗口 在命令行中快速移动光标 在命令行中快速删除文本 快速查看和搜索历史命令 快速引用和修饰历史命令 录制屏幕并转换为 gif 动画图片 总结 回到顶部 前言 ...
- The OAuth 2.0 Authorization Framework: Bearer Token Usage
https://tools.ietf.org/html/rfc6750 1.2. Terminology Bearer Token A security token with the property ...
- 【bzoj4976】宝石镶嵌(思维dp)
题目传送门:bzoj4976 不得不说这是道脑洞dp,思路真的清奇. 我们可以发现,虽然n很大,但是k只有100,这里面似乎隐藏了什么玄机. 我们可以发现,设总共有$ tot $个二进制位在这n个数中 ...
- OKR学习总结
OKR学习总结 背景:因为公司最近采用OKR工作法,所以来了解一下. 简介 OKR ——Object Key Results 主要分为两部分:O 和 KR ,就是目标和关键结果. 将这个丰满点描述,就 ...