《Java编程思想》第三章:操作符

第三章 操作符

更简单的打印语句

在后面的知识会介绍静态导入(static import),这个在Java SE5中新增加的概念。

话不多说,直接上代码

import java.util.Date;

import static net.mindview.util.Print.*; // 静态导入:需要下载引入mindview的jar包
/**
 * @author Tangwenbo
 * @version JDK 1.8
 * @date 2021/7/14 17:29
 */
public class HelloDate {
    public static void main(String[] args) {
        /**
         * 简短的打印语句
         */
        print("hello, it's:");
        print(new Date());

        /**
         * 正常的打印语句
         */
        System.out.println("你好,现在是:");
        System.out.println(new Date());
    }
}

Java操作符和优先级

​ 操作符接受一个或多个参数,并生成一个新值。参数的形式与普通的方法调用不同,但效果是相同的。加号和一元的正号(+)、减号和一元的负号(一)、乘号(*)、除号(/)以及赋值号(=)的用法与其他编程语言类似。

优先级

​ 当一个表达式存在多个操作符时,操作符的优先级就决定了各部分的计算顺序。Java最简单的计算顺序就是:先乘除后加减。程序员会经常忘记其他优先规则,所以应该用括号来明确规定计算顺序:

public class Precedence {
    public static void main(String[] args) {
        int x = 1,y  = 2, z = 3;
        int a = x+y-2/2+z;
        int b = x+(y-2)/(2+z);
        System.out.println("a = "+a + " b = "+b);
    } // a = 5 b = 1
}

​ 这两个语句看起来大体相同,但是从输出就可以看出它们具有迥然不同的含义,而这正是使用括号的结果。

在输出语句中,我们看到了“+”操作符,这意味着字符串的连接,当编译器观察到一个String后面紧跟着一个“+”,而这个“+”后面又紧跟着一个非String类型的元素,就会尝试将这个非String类型的数据转化为String类型的数据。

赋值

​ 赋值使用操作符“=”。它的意思是“取右边的值(即右值),把它复制给左边(即左值)右值可以是任何常数、变量或者表达式(只要它能生成一个值就行)。但左值必须是一个明确的、已命名的变量。也就是说,必须有一个物理空间可以存储等号右边的值。举例来说,可将一个常数赋给一个变量:a = 4;

​ 对基本数据类型的赋值是很简单的。基本类型存储了实际的数值,而并非指向一个对象的引用,所以在为其赋值的时候,是直接将一个地方的内容复制到了另ー个地方。例如,对基本数据类型使用a=b,那么b的内容就复制给a。若接着又修改了a,而b根本不会受这种修改的影响。

​ 但是在为对象“赋值”的时候,情况却发生了变化。对一个对象进行操作时,我们真正操作的是对对象的引用。所以倘若“将一个对象赋值给另一个对象”,实际是将“引用”从一个地方复制到另一个地方。这意味着假若对对象使用c=d,那么c和d都指向原本只有d指向的那个对象。例如:

import static net.mindview.util.Print.*;
/**
 * @author Tangwenbo
 * @version JDK 1.8
 * @date 2021/7/14 17:59
 */
class Tank{
    int level;
}
public class Assignment {
    public static void main(String[] args) {
        Tank t1 = new Tank();
        Tank t2 = new Tank();
        t1.level = 9;
        t2.level= 47;
        print("1.: t1.level = "+t1.level+" t2.level = "+t2.level);

        t1 = t2;
        print("2.: t1.level = "+t1.level+" t2.level = "+t2.level);

        t1.level = 27;
        print("3.: t1.level = "+t1.level+" t2.level = "+t2.level);
    }
}
1.: t1.level = 9 t2.level = 47
2.: t1.level = 47 t2.level = 47
3.: t1.level = 27 t2.level = 27

​ Tank类非常简单,它的两个实例(t1和t2)是在 main()里创建的。对每个Tank类对象的level域都赋予了一个不同的值,然后,将t2赋给t1,接着又修改了t1。在许多编程语言中,我们可能会期望t1和t2总是相互独立的。但由于赋值操作的是一个对象的引用,所以修改t1的同时也改变了t2!

​ 这是由于t1和t2包含的是相同的引用,它们指向相同的对象。(原本t1包含的对对象的引用,是指向一个值为9的对象。在对t1赋值的时候,这个引用被覆盖,也就是丢失了;而那个不再被引用的对象会由“垃圾回收器”自动清理。)
这种特殊的现象通常称作“别名现象”,是Java操作对象的一种基本方式。在这个例子中,如果想避免别名问題应该怎么办呢?可以这样写:

t1.level = t2.level;

​ 这样便可以保持两个对象彼此独立,而不是将t1和t2绑定到相同的对象。但你很快就会意识到,直接操作对象内的域容易导致混乱,并且,违背了良好的面向对象程序设计的原则。这可不是一个小问题,所以从现在开始大家就应该留意,为对象赋值可能会产生意想不到的结果。

方法调用中的别名问题

将一个对象传递给方法时,也会产生别名的问题:

class Letter{
    float f;
}
public class PassObj {

    static void f(Letter y){
        y.f = 1.0f;
    }
    public static void main(String[] args) {
        Letter x = new Letter();
        x.f = 3.14f;
        f(x); 
        System.out.println("x.f = "+x.f);
    }
}
x.f = 1.0

在许多编程语言中,方法f()似乎要在它的作用域内复制其参数 Letter y的一个副本;但实际上只是传递了一个引用。所以代码行:

y.f = 1.0f;

实际上改变的是f()之外的对象。

算术操作符

​ Java的基本算术操作符与其他大多数程序设计语言是相同的。其中包括加号(+)、减号(-)、除号(/)、乘号(*)以及取模操作符(%,它从整数除法中产生余数)。整数除法会直接去掉结果的小数位,而不是四舍五入地圆整结果。

下面这个例子展示了各种算数操作符的用法:

import java.util.Random;
import static net.mindview.util.Print.*;
/**
 * @author Tangwenbo
 * @version JDK 1.8
 * @date 2021/7/14 22:58
 */
public class MathOps {
    public static void main(String[] args) {
         /**
         * 47是“魔幻数字”,至今为止都是这样,要想每次随机的数都不一致,可以移除47
         */
        Random random = new Random(47);
        int i,j,k;
        j = random.nextInt(100)+1;// j的值从1-100随机取
        print("j = "+j);
        k  = random.nextInt(100)+1;// k的值从1-100随机取
        print("k = "+k);
        i = j+k;
        print("j + k = "+i);
        i = j -k;
        print("j -k = "+i);
        i = k/j;
        print("k/j = "+i);
        i = k*j;
        print("k*j = "+i);
        i = k%j;
        print("k%j = "+i);
        /**
         * 此处省略float、byte、short、int、long、double测试,其运算方式都差不多
         */

    }
}
j = 59
k = 56
j + k = 115
j -k = 3
k/j = 0
k*j = 3304
k%j = 56

自动递增和递减

​ 递增和递减这两个操作符各有两种使用方式,通常称为“前缀式”和“后缀式”。“前缀递增”表示操作符位于变量或表达式的前面;而“后缀递增”表示操作符位于变量或表达式的后面。类似地,“前缀递减”意味着“–”操作符位于变量或表达式的前面;而“后缀递减”意味着“–”操作符位于变量或表达式的后面。

对于前缀递增和前缀递减(如++a或–a),会先执行运算,再生成值。而对于后缀递增和后缀递减(如a++或a–),会先生成值,再执行运算。

下面是一个例子:

public class AutoInc {
    public static void main(String[] args) {
        int i = 1;
        System.out.println("i = "+i);
        System.out.println("++i = "+ ++i);
        System.out.println("--i = "+ --i);
        System.out.println("i++ = "+ i++);
        System.out.println("i-- = "+ i--);
    }
}
i = 1
++i = 2
--i = 1
i++ = 1
i-- = 2

​ 从中可以看到,对于前缀形式,我们在执行完运算后才得到值。但对于后缀形式,则是在运算执行之前就得到值。它们是除那些涉及赋值的操作符以外,唯一具有“副作用”的操作符。也就是说,它们会改变操作数,而不仅仅是使用自己的值。

关系操作符

​ 关系操作符生成的是一个boolean(布尔)结果,它们计算的是操作数的值之间的关系。

测试对象的等价性

​ 关系操作符==和!=也适用于所有对象,但是这个两个操作符通常会使第一次接触的人们感到迷惑,以下是一个例子:

public class Equivalence {
    public static void main(String[] args) {
        Integer n1 = new Integer(47);
        Integer n2 = new Integer(47);
        System.out.println(n1==n2);
        System.out.println(n1!=n2);
    }
}
false
true

​ 语句 System.out.println(nl==n2)将打印出括号内的比较式的布尔值结果。读者可能认为输出结果肯定先是tue,再是 false,因为两个 Integer对象都是相同的。但是尽管对象的内容相同,然而对象的引用却是不同的,而=和!=比较的就是对象的引用。所以输出结果实际上先是 false,再是true。这自然会使第一次接触关系操作符的人感到惊奇。

​ 但是如果相比较两个对象的实际内容是否相同又该如何操作呢?此时,必须使用所有对象都适用的特殊方法 equals()。但这个方法不适用于“基本类型”,基本类型直接使用==和!=即可,下面举例说明如何使用:

public class EqualsMethod {
    public static void main(String[] args) {
        Integer n1 = new Integer(47);
        Integer n2 = new Integer(47);
        System.out.println(n1.equals(n2));
    }
}
true

​ 但是其实,事情并非我们想象的那么简单,假如你创建了自己的类:

class Value{
    int i;
}
public class EqualsMethod2 {
    public static void main(String[] args) {
        Value v1 = new Value();
        Value v2 = new Value();
        v1.i = 100;
        v2.i = 100;
        System.out.println(v1.equals(v2));
    }
}
false

​ 这一次却是false,这是由于 equals()的默认行为是比较引用。所以除非在自己的新类中覆盖 equals方法,否则不可能表现出我们希望的行为。

逻辑操作符

​ 逻辑操作符“与”(&&)、“或”(||)、“非”(!)能根据参数的逻辑关系,生成一个布尔值(true或 false),且只可以应用于布尔值。

短路

​ 当使用逻辑操作符时,我们会遇到一种“短路”现象。即一旦能够明确无误地确定整个表达式的值,就不再计算表达式余下部分了。因此,整个逻辑表达式靠后的部分有可能不会被运算。下面是演示短路现象的例子:

import static net.mindview.util.Print.*;
/**
 * @author Tangwenbo
 * @version JDK 1.8
 * @date 2021/7/16 16:05
 */
public class ShortCircuit {
    static boolean test1(int val){
        print("test1("+val+")");
        print("result:"+(val<1));
        return val<1;
    }
    static boolean test2(int val){
        print("test2("+val+")");
        print("result:"+(val<2));
        return val<1;
    }
    static boolean test3(int val){
        print("test3("+val+")");
        print("result:"+(val<3));
        return val<1;
    }
    public static void main(String[] args) {
        boolean b = test1(0)&&test2(2)&&test3(3);
        print("expression is:"+b);
    }
}

test1(0)
result:true
test2(2)
result:false
expression is:false

​ 你会很自然地认为所有这三个测试都会得以执行。但输出显示却并非这样。第一个测试生成结果true,所以表达式计算会继续下去。然而,第二个测试产生了一个 false结果。由于这意味着整个表达式肯定为 false,所以没必要继续计算剩余的表达式,那样只是浪费。“短路”一词的由来正源于此。事实上,如果所有的逻辑表达式都有一部分不必计算,那将获得潜在的性能提升。

直接常量

​ 直接常量后面的后缀字符标志了它的类型。若为大写(或小写)的L,代表long(但是,使用小写字母l容易造成混淆,因为它看起来很像数字1)。大写(或小写)字母F,代表float,大写(或小写)字母D,则代表 double。

指数记法

Java采用了一种很不直观的记数法来表示指数:

public class Exponents {
    public static void main(String[] args) {
        float expFloat = 1.39e-43f;
        System.out.println(expFloat);

        double expDouble = 47e47d;
        double expDouble2 = 47e47;
        System.out.println(expDouble);
    }
}
1.39E-43
4.7E48

其中的基数E表示10,1.39E-43表示1.39x10-43,4.7E48表示4.7x1048;

按位操作符

​ 按位操作符用来操作整数基本数据类型中的单个“比特”(bit),即二进制位。按位操作符会对两个参数中对应的位执行布尔代数运算,并最终生成一个结果。

练习:

​ 编写一个具有两个常量值的程序,一个具有交替的二进制位1和0,其中最低有效位为0,另一个也具有交替的二进制位1和0,但是其最低有效位为1(提示:使用十六进制常量来表示是最简单的方法)。取这两个值,用按位操作符以所有可能的方式结合运算它们,然后用 Integer. toBinarystring()显示。

import static net.mindview.util.Print.*;
/**
 * @author Tangwenbo
 * @version JDK 1.8
 * @date 2021/7/17 13:48
 */
public class OperExp {
    public static void main(String[] args) {
        int a = 1+2+4+16+64;
        int b = 2+8+32+128;
        print("a = "+Integer.toBinaryString(a));
        print("b = "+Integer.toBinaryString(b));
        print("a&b = "+Integer.toBinaryString(a&b));
        print("a|b = "+Integer.toBinaryString(a|b));
        print("a^b = "+Integer.toBinaryString(a^b));
        print("~a = "+Integer.toBinaryString(~a));
        print("~b = "+Integer.toBinaryString(~b));
    }
}

a = 1010111
b = 10101010
a&b = 10
a|b = 11111111
a^b = 11111101
~a = 11111111111111111111111110101000
~b = 11111111111111111111111101010101

位移操作符

​ 移位操作符操作的运算对象也是二进制的“位”。移位操作符只可用来处理整数类型(基本类型的一种)。左移位操作符(<<)能按照操作符右侧指定的位数将操作符左边的操作数向左移动(在低位补0)。“有符号”右移位操作符(>>)则按照操作符右侧指定的位数将操作符左边的操作数向右移动。“有符号”右移位操作符使用“符号扩展”:若符号为正,则在高位插入0,若符号为负,则在高位插入1。Java中增加了一种“无符号”右移位操作符(>),它使用“零扩展”:无论正负,都在高位插入0。这一操作符是C或C++中所没有的。

​ 如果对char、byte或者 short类型的数值进行移位处理,那么在移位进行之前,它们会被转换为int类型,并且得到的结果也是一个int类型的值。只有数值右端的低5位才有用。这样可防止我们移位超过int型值所具有的位数。(译注:因为2的5次方为32,而int型值只有32位。)若对一个long类型的数值进行处理,最后得到的结果也是long。此时只会用到数值右端的低6位,以防止移位超过long型数值具有的位数。

三元操作符 if-else

​ 三元操作符也称为条件操作符,它显得比较特别,因为它有三个操作数;但它确实属于操作符的一种,因为它最终也会生成一个值,这与本章下一节中介绍的普通if-else语句是不同的。其表达式采取下述形式:

boolean-exp ? value0:value1;

​ 如果 boolean-exp(布尔表达式)的结果为true,就计算 value0,而且这个计算结果也就是操作符最终产生的值。如果 boolean-exp的结果为 false,就计算 value1,同样,它的结果也就成为了操作符最终产生的值。

​ 条件操作符和if-else完全不同,因为它会产生一个值,下面是对这两者进行的比较示例:

import static net.mindview.util.Print.print;

/**
 * @author Tangwenbo
 * @version JDK 1.8
 * @date 2021/7/17 16:29
 */
public class TernaryIfElse {
    static int ternary(int i){
        return i<10?  i*100:i*10;
}
    static int ifElse(int i){
        if(i<10)
            return i*100;
        else
            return i*10;
    }
    public static void main(String[] args) {
        print(ternary(9));
        print(ternary(10));
        print(ifElse(9));
        print(ifElse(10));
    }
}
900
100
900
100

​ 可以看出,上面的 ternary()中的代码与 ifElse()中不用三元操作符的代码相比,显得更加紧凑;但 ifElse()更易理解,而且不需要太多的录入。所以在选择使用三元操作符时,请务必仔细考虑。

类型转换操作符

​ **类型转換(cast)**的原意是“模型铸造”。在适当的时候,Java会将一种数据类型自动转换成另一种。例如,假设我们为某浮点变量赋以一个整数值,编译器会将int自动转换成float。类型转换运算允许我们显式地进行这种类型的转换,或者在不能自动进行转换的时侯强制进行类型转换。

import static net.mindview.util.Print.print;

/**
 * @author Tangwenbo
 * @version JDK 1.8
 * @date 2021/7/17 16:35
 */
public class Casting {
    public static void main(String[] args) {
    double above = 0.7,below = 0.4;
    float fabove = 0.7f,fbelow = 0.4f;
    print((int)above);
    print((int)below);
    print((int)fabove);
    print((int)fbelow);
    }
}

以上输出的结果全是0,如果在讲float或double转化为整型值时,总是对数字执行截尾。如果想要得到舍入的结果,那就需要用到Math.round()方法:

import static net.mindview.util.Print.print;

/**
 * @author Tangwenbo
 * @version JDK 1.8
 * @date 2021/7/17 16:35
 */
public class Casting {
    public static void main(String[] args) {
    double above = 0.7,below = 0.4;
    float fabove = 0.7f,fbelow = 0.4f;
    print(Math.round(above));
    print(Math.round(below));
    print(Math.round(fabove));
    print(Math.round(fbelow));
    }
}
1
0
1
0

​ 如果对基本数据类型执行算术运算或按位运算,大家会发现,只要类型比int小(即char、byte或者 short),那么在运算之前,这些值会自动转换成int。这样一来,最终生成的结果就是int类型。如果想把结果赋值给较小的类型,就必须使用类型转换(既然把结果赋给了较小的类型,就可能出现信息丢失)。通常,表达式中出现的最大的数据类型决定了表达式最终结果的数据类型。如果将一个float值与ー个 double值相乘,结果就是 double,如果将一个int和一个long值相加,则结果为long。

练习

​ 编写一个接收两个字符串参数的方法,用各种布尔值的比较关系来比较这两个字符串,然后把结果打印出来。做=和!=比较的同时,用 equals()测试。在main()里面用几个不同的字符串对象调用这个方法。

/**
 * @author Tangwenbo
 * @version JDK 1.8
 * @date 2021/7/17 16:56
 */
public class StringCompare {
    static void f(boolean b){
        if(b==true){
            System.out.println(true);
        }else
            System.out.println(false);
    }
    static void stringTest(String str1,String str2){
        f(str1==str2);
        f(str1!=str2);
        f(str1.equals(str2));
        f(str2.equals(str1));
    }
    public static void main(String[] args) {
        String str1 = "one";
        String str2 = "two";
        stringTest(str1,str2);
    }
}
false
true
false
false
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值