Java编程笔记1:基础

本文介绍了Java编程的基础,包括变量自动初始化规则、数据溢出问题、强制类型转换、switch语句的发展和限制,以及foreach语法的应用。重点突出了Java与其他语言在初始化策略上的区别和Java在数据安全方面的特点。
摘要由CSDN通过智能技术生成

Java编程笔记1:基础

5c9c3b3b392ac581.jpg

图源:Java Switch语句(用法详解)-java教程-PHP中文网

这个新开的系列以《Java编程思想》为主要参考资料。原书成书很早,在05年左右更新了第四版之后至今没有更新。原书基于JavaSE5/6编写,目前JDK的最新版本是17,这里我使用的是JDK16.0.1,所有原书示例和内容也将在这个版本下由我进行验证。如果和原书有出入,说明JDK的相应功能已发生改变,我会额外说明。

和《Java编程思想》一样,这里仅会列出Java有别于其它编程语言的部分,相似的部分不会重复说明。

当然,这种“有别”是主观的,我作为一个很多年前学过Java,后来主要从事PHP开发,最近又学了Python和Go的开发者,或许感受和其它人不一样。

变量初始化

C/C++的所有变量初始化工作都必须由开发者自行完成,编译器并不会在这方面有所帮助。虽然这样做的好处是节省了一部分不需要进行的初始化的性能提升。但更多的是“脏数据”带来的麻烦,相比很多学习C/C++的初学者都遇到过满屏的“烫烫烫…”。

所以对初始化的改进是很多借鉴C/C++发展而来的语言所极力想做的。

在这方面,Java的策略是对类内部定义的属性由编译器进行初始化工作,但对于类定义之外的局部变量,编译器并不会进行自动初始化,仍然需要开发者完成初始化工作:

package ch1.class_define;
class Test{}

class ClassDefine {
    int intMember;
    boolean boolMember;
    double doubleMember;
    Test test;


    public static void main(String[] args) {
        ClassDefine cd = new ClassDefine();
        System.out.println("cd.intMember:"+cd.intMember);
        System.out.println("cd.boolMember:"+cd.boolMember);
        System.out.println("cd.doubleMember:"+cd.doubleMember);
        System.out.println("cd.test:"+cd.test);
        int intLocal;
        boolean boolLocal;
        double doubleLocal;
        Test test;
        // System.out.println(intLocal);
        // System.out.println(boolLocal);
        // System.out.println(doubleLocal);
        // System.out.println(test);
    }
}
// cd.intMember:0
// cd.boolMember:false
// cd.doubleMember:0.0
// cd.test:null

在上边这个示例中,如果对局部变量intLocal等进行打印,程序就无法通过编译,会显示“变量没有初始化”等相关提示。所以必须要手动进行初始化:

package ch1.class_define2;
class Test{}

class ClassDefine {
    int intMember;
    boolean boolMember;
    double doubleMember;
    Test test;


    public static void main(String[] args) {
        ClassDefine cd = new ClassDefine();
        System.out.println("cd.intMember:"+cd.intMember);
        System.out.println("cd.boolMember:"+cd.boolMember);
        System.out.println("cd.doubleMember:"+cd.doubleMember);
        System.out.println("cd.test:"+cd.test);
        int intLocal = 0;
        boolean boolLocal = false;
        double doubleLocal = 0.0d;
        Test test = new Test();
        System.out.println(intLocal);
        System.out.println(boolLocal);
        System.out.println(doubleLocal);
        System.out.println(test);
    }
}

// cd.intMember:0
// cd.boolMember:false
// cd.doubleMember:0.0
// cd.test:null
// 0
// false
// 0.0
// ch1.class_define2.Test@24d46ca6

相比较而言,诞生刚刚超过十年的Go就“先进”的多,变量无论是否属于结构体,都将被编译器初始化:

package main

import "fmt"

type Test struct{}
type StructDefine struct {
	IntMember   int
	FloatMember float64
	BoolMember  bool
	TestMember  Test
}

func main() {
	sd := StructDefine{}
	fmt.Printf("IntMember:%d\n", sd.IntMember)
	fmt.Printf("FloatMember:%.2f\n", sd.FloatMember)
	fmt.Printf("BoolMember:%v\n", sd.BoolMember)
	fmt.Println("TestMember:", sd.TestMember)
	var intLocal int
	var floatLocal float64
	var boolLocal bool
	var testLocal Test
	fmt.Println("intLocal:", intLocal)
	fmt.Println("floatLocal:", floatLocal)
	fmt.Println("boolLocal:", boolLocal)
	fmt.Println("testLocal:", testLocal)
}

// IntMember:0
// FloatMember:0.00
// BoolMember:false
// TestMember: {}
// intLocal: 0
// floatLocal: 0
// boolLocal: false
// testLocal: {}

我本来以为Java发展这么多年后会解决这个问题,毕竟Java一直以来关注的重点并非效率,而连更注重执行效率的Go都能做到这样的程度,Java没理由不行。但现在看来,Java依然如此,似乎是因为Java的开发者已经习惯这样了,所以没必要做出改变?

数据溢出

Java以安全性著称,在大多数情况下的确比C/C++更安全,但某些情况下依然会出现数据溢出等问题:

package ch1.data_overflow;

public class Main {
    public static void main(String[] args){
        int maxInt = Integer.MAX_VALUE;
        maxInt += 3;
        System.out.println(Integer.MAX_VALUE);
        System.out.println(maxInt);
        // 2147483647
        // -2147483646
    }
}

Integer.MAX_VALUEint所能表示的最大整数,在这个基础上再进行加运算,就会数据溢出,结果不可预料。即使这种情况发生,编译器也不会有任何报错和提示。

转型

对于byteshortchar这三个基本类型,对它们使用数学运算,会将其提升为int类型后运算,并且运算结果也是int,所以需要对结果进行“强制类型转换”后才可以赋值给同类型变量:

package ch1.cast;

public class Main {
    public static void main(String[] args){
        char c = 'a';
        c = (char)(c + 'b');
        System.out.println(c);
        byte b = 1;
        byte b2 = 1;
        b = (byte)(b + b2);
        System.out.println(b);
        short s = 1;
        short s2 = 1;
        s = (short)(s + s2);
        System.out.println(s);
        // ?
        // 2
        // 2
    }
}

比较特别的是,对它们使用“赋值运算符”就不需要考虑强制转换:

package ch1.cast2;

public class Main {
    public static void main(String[] args){
        char c = 'a';
        c += 'b';
        System.out.println(c);
        byte b = 1;
        byte b2 = 1;
        b += b2;
        System.out.println(b);
        short s = 1;
        short s2 = 1;
        s += s2;
        System.out.println(s);
        // ?
        // 2
        // 2
    }
}

但实际上运算过程是相同的,都是提升为int后进行运算,后者可以认为是编译器在运算完后进行了隐式转换。

switch

早期Java的switch语句存在C/C++中的限制,即只能对可以转换成int类型的基本数据类型使用switch...case语句,具体来说就是charbyteshortint这几种。其余的字符串、对象、浮点数等都是不可以的。

package ch1.switch1;

import java.util.Random;

public class Main {
    public static boolean isVowel(char c) {
        switch (c) {
            case 'a':
            case 'e':
            case 'i':
            case 'o':
            case 'u':
                return true;
            default:
        }
        return false;
    }

    public static void main(String[] args) {
        Random rand = new Random();
        for (int i = 0; i < 10; i++) {
            char c = (char) ('a' + rand.nextInt(26));
            if (isVowel(c)) {
                System.out.println(c + " is Vowel.");
            } else {
                System.out.println(c + " is not Vowel.");
            }
        }
    }
}
// d is not Vowel.
// w is not Vowel.
// k is not Vowel.
// u is Vowel.
// e is Vowel.
// i is Vowel.
// d is not Vowel.
// e is Vowel.
// x is not Vowel.
// s is not Vowel.

后来Java主键放宽了一些限制,比如添加了对前面所说的几种基本类型对应的包装类的支持:

package ch1.switch3;

import java.util.Random;

public class Main {
    public static boolean isVowel(char c) {
        Character myC = c;
        switch (myC) {
            case 'a':
            case 'e':
            case 'i':
            case 'o':
            case 'u':
                return true;
            default:
        }
        return false;
    }
    ...
}

还添加了对字符串的支持(这的确是个很常见的情况)。

package ch1.switch2;

public class Main {
    public static String callBack(String call) {
        switch (call.toLowerCase()) {
            case "hello":
                return "hello";
            case "bye":
                return "bye";
            default:
                return "I don't know";
        }
    }

    public static void main(String[] args) {
        String call = "hello";
        System.out.println("call:"+call+" back:"+callBack(call));
        call = "Bye";
        System.out.println("call:"+call+" back:"+callBack(call));
        call = "world";
        System.out.println("call:"+call+" back:"+callBack(call));
    }
}
// call:hello back:hello
// call:Bye back:bye
// call:world back:I don't know

但也就这样了,Java中的switch语句依然有很大的局限性,比如你可能期望像包装类那样使用实现了equals方法的自定义类进行匹配:

package ch1.switch4;

class MyClass {
    private int number = 0;

    public MyClass(int number) {
        this.number = number;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj){
            return true;
        }
        if (obj instanceof MyClass){
            MyClass other = (MyClass)obj;
            return this.number == other.number;
        }
        return false;
    }

}

public class Main {
    public static void main(String[] args) {
        MyClass mc1 = new MyClass(1);
        MyClass mc2 = new MyClass(2);
        MyClass mc3 = new MyClass(1);
        switch(mc1){
            case mc2:
                break;
            case mc3:
                break;
            default:
        }
    }
}

但这样是不被允许的,无法通过编译检查。

foreach

基本上比较“新”的语言都会支持某种形式的foreach语法,只不过表现形式上差别很大,Java同样有foreach语法,可以用来遍历数组或者迭代器:

package ch1.foreach;

class Main {

    public static void main(String[] args) {
        char[] chars = { 'a', 'b', 'c', 'd', 'e' };
        for (char c : chars) {
            System.out.print(c + " ");
        }
        System.out.println();
    }
}
// a b c d e 

在Python中,有个很好用的range函数,可以按需要生成一个整数序列,利用range函数和Python的foreach语句,可以很容易实现一个对整数序列的迭代过程:

for i in range(1, 11):
    print(i, sep="", end=" ")
    # 1 2 3 4 5 6 7 8 9 10 

Java中也可以做到类似的事情,不过要借助IntStream

package ch1.foreach2;

import java.util.stream.IntStream;

class Main {

    public static void main(String[] args) {
        int[] numbers = IntStream.range(1, 11).toArray();
        for (int i : numbers) {
            System.out.print(i + " ");
        }
        System.out.println();
    }
}
// 1 2 3 4 5 6 7 8 9 10

IntStream是一个接口,代表一个整数序列,利用IntStream可以构建有限或者无线序列,功能比Python的range函数更强到。

跳转标签

如果在Java中构建了一个多重迭代语句,你可能需要借助跳转标签来直接从内层迭代中跳出到外层:

package ch1.label;

import java.util.Random;

public class Main {
    public static void main(String[] args) {
        Random rand = new Random();
        outer: for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 10; j++) {
                int randInt = rand.nextInt(100);
                System.out.print(randInt+"\t");
                if (randInt == 99){
                    break outer;
                }
            }
            System.out.println();
        }
    }
}

上面这个示例会输出一个10*10的矩阵,其中的值是0~99的随机数,比较特别的是,当产生的随机数正好是99时,整个矩阵产生过程将终止。

这里外层的outer:起到了一个定位外层迭代的标签的作用,类似的,其它的迭代语句(whiledo...while)同样可以使用这种标签。而内层迭代语句中就可以使用break outercontinue outer直接让外层循环跳出或者继续。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值