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表达式中的陷阱的更多相关文章

  1. java 8 lambda表达式中的异常处理

    目录 简介 处理Unchecked Exception 处理checked Exception 总结 java 8 lambda表达式中的异常处理 简介 java 8中引入了lambda表达式,lam ...

  2. No.5 表达式中的陷阱

    1. 关于字符串的陷阱 JVM对字符串的处理 String java = new String("Java"); 创建了几个对象? 2个."Java"直接量对应 ...

  3. java、el表达式中保留小数的方法

    Java中: import java.math.BigDecimal; import java.text.DecimalFormat; import java.text.NumberFormat; p ...

  4. Java集合与泛型中的陷阱

    List,List<Object>区别 List<Integer> t1 = new ArrayList<>(); // 编译通过 List t2 = t1; // ...

  5. 在java 8 stream表达式中实现if/else逻辑

    目录 简介 传统写法 使用filter 总结 简介 在Stream处理中,我们通常会遇到if/else的判断情况,对于这样的问题我们怎么处理呢? 还记得我们在上一篇文章lambda最佳实践中提到,la ...

  6. Java向上下转型中的陷阱{详细}

    1: 多态   多态时继承下面的产物,之所以存在向上向下转型的目的,就是解决参数传递的不变形,体现面向接口编程的重要性, 1.1 方法的多态性   ①. 方法的重载:同一个方法名称可以根据参数的类型或 ...

  7. Java 进阶6 异常处理的陷阱

    Java 进阶6 异常处理的陷阱 20131113 异常处理机制是 Java语言的特色之一,尤其是 Java的Checked 异常,更是体现了 Java语言的严谨性:没有完善的错误的代码根本就不会被执 ...

  8. Java进阶5 面向对象的陷阱

    Java进阶5 面向对象的陷阱 20131103 Java是一门纯粹面向对象的编程语言,Java面向对象是基础,而且面向对象的基本语法非常多,非常的细,需要程序员经过长时间的学习才可以掌握.本章重点介 ...

  9. Java语言与JVM中的Lambda表达式全解

    Lambda表达式是自Java SE 5引入泛型以来最重大的Java语言新特性,本文是2012年度最后一期Java Magazine中的一篇文章,它介绍了Lamdba的设计初衷,应用场景与基本语法. ...

随机推荐

  1. 2017浙江省赛 C - What Kind of Friends Are You? ZOJ - 3960

    地址:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3960 题目: Japari Park is a large zoo ...

  2. spark-sql做ETL时遇到的两个问题

    项目中使用spark-sql来作ETL,遇到两个问题,记录一下. 问题1: spark-sql –master yarn –hiveconf load_date=`date –d ..`  -e 'i ...

  3. eclipse 编译的时候 自动把SDK需要放入libs里面的so文件给删除了

    解决方法: 右击Project,选Properties->Builders, 把CDT Builder 关掉. 这样就不会编译了.包括c++的代码也不会编译.. 治标不治本啊...以后c++代码 ...

  4. 整理一些《纸书科学计算器》的小Tips

    本文最开始是在2016年的文章 Win10应用<纸书科学计算器>更新啦! 发表之后撰写的,当时那篇文章收到了不少人点赞,应用在国内市场的日下载量也突然上涨,让我感到受宠若惊,这里要感谢Wp ...

  5. ultraedit 查看文件

    转自:https://wenda.so.com/q/1481655902726192 1 UltraEdit在打开文件的时候,会对文件类型进行检查.如果是二进制文件,会自动转为16进制显示模式.如下图 ...

  6. Django学习笔记之利用Form和Ajax实现注册功能

    一.注册相关的知识点 1.Form组件 我们一般写Form的时候都是把它写在views视图里面,那么他和我们的视图函数也不影响,我们可以吧它单另拿出来,在应用下面建一个forms.py的文件来存放 2 ...

  7. CSS 分组和嵌套选择器

    CSS 分组和嵌套选择器 一.分组选择器 在样式表中有很多具有相同样式的元素. h1 { color:green; } h2 { color:green; } p { color:green; } 为 ...

  8. Ubuntu 18.04配置机场客户端

    最近把自己的笔记本电脑安装成ubuntu18.04操作系统,为了更方便的查找文档,所以需要配置一下机场(v2ray)的客户端方便查找资料,以下是配置步骤: 1.下载并执行一键脚本: bash < ...

  9. 20145328 《Java程序设计》实验四实验报告

    20145328 <Java程序设计>实验四实验报告 实验名称 Andoid开发基础 实验内容 基于Android Studio开发简单的Android应用并部署测试; 了解Android ...

  10. 10个足以让你成为更优秀的程序员的C语言资源

    一些人觉得编程无聊,一些人觉得它很好玩.但每个程序员都必须紧跟编程语言的潮流.大多数程序员都是从C开始学习编程的,因为C是用来写操作系统.应用程序最常用的语言. · C编程笔记 这些是华盛顿实验学院C ...