博主决定把Java捡回来。这次学习力求系统理解,从最简单的开始全部过一遍。为了加强记忆,打算将部分共通的内容和Python联系起来。

本系列考虑长期更新。

三元运算

Python
n = -100
x = n if n >=0 else -n
Java
int n = -100;
int x = n >= 0 ? n : -n;

多行字符串

Python
example_string = '''
咲けば散りゆく花のように
  運命は変えられない
'''  # 当然双引号也可以
Java
exampleString = """
                咲けば散りゆく花のように
                  運命は変えられない
                """ //和Python不同的是,这里每行前共有的空格会被略去

数组

严格地说,Python中的列表、元组、字典和Java数组含义均有较大差异,因此这里和NumPy的数组对比。

NumPy数组是同质(所有元素类型必须相同)的,类似于 Java 的原始数据类型数组(如 int[], float[] 等),但不同于 Java 的对象数组(如 Integer[]),后者可以包含不同类型的对象。

NumPy有强大的广播功能,可以让不同大小的数组在一定条件下进行数学运算,这是 Java 数组不具备的。

Python中打印数组直接使用print,而Java需要使用Arrays.toString()(对于多维数组则是Arrays.deepToString())。

Java
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[] ns = { 1, 1, 2, 3, 5, 8 };
        System.out.println(Arrays.toString(ns));
    }
}

输出

Python
print('有為の奥山 今日越えて 浅き夢見じ 酔ひもせず', end='')
print('(俗世凡尘今朝脱,不恋醉梦免蹉跎。)')
Java
System.out.print("有為の奥山 今日越えて 浅き夢見じ 酔ひもせず");
System.out.println("(俗世凡尘今朝脱,不恋醉梦免蹉跎。)");

判断引用类型相等

Python全是引用类型,通用==;Java的引用类型需要用equals()方法,否则就成了判断地址是否一致。

多重匹配

Python的match语句更灵活,不仅可以匹配具体的值,还可以匹配类型、模式,甚至可以解构对象(例如列表和字典)。

Python的match语句中的case分支支持使用if表达式进行更复杂的条件判断。

Java中的switch语句中,如果不加break,会出现“穿透”现象,即继续执行下一个 case 的内容,直到遇到 break;而 Python 的 match 语句每个 case 分支天然就是隔离的,执行完毕后不会“穿透”到下一个 case

Python
def mapping(config):
    match config:
        case {'sub': sub_config, **rest}:
            print(f'Sub: {sub_config}')
            print(f'OTHERS: {rest}')
        case {'route': route}:
            print(f'ROUTE: {route}')
        case _:
            print('Match failed')
Java
switch (option) {
    case 1:
        System.out.println("Selected 1");
        break;
    case 2:
    case 3:
        System.out.println("Selected 2, 3");
        break;
    default: 
        System.out.println("Not selected");
        break;
}

从Java 12开始,有了更简洁的表达式语法,更类似于模式匹配,不需要break语句:

Java
switch (fruit) {
    case "apple" -> System.out.println("Selected apple");
    case "pear" -> System.out.println("Selected pear");
    case "mango" -> {
        System.out.println("Selected mango");
        System.out.println("Good choice!");
    }
    default -> System.out.println("No fruit selected");
}

int opt = switch (fruit) {
    case "apple" -> 1;
    case "pear", "mango" -> 2;
    default -> {
        int code = fruit.hashCode();
        yield code; // switch语句返回值
    }
}; // 注意赋值语句要以;结束

for each 循环

Python
num_array = [1, 4, 9, 16, 25]
for num in num_array:
    print(num)
Java
int[] numArray = {1, 4, 9, 16, 25};
for (int num : numArray) {
    System.out.println(num);
}

可变参数

在Python中,可以使用*args来接收任意数量的位置参数,它们会被解释为一个元组。还可以使用**kwargs来接收任意数量的关键字参数,这些参数会被解释为一个字典。

Python
def func(*args, **kwargs): 
    for arg in args: 
        print(arg) 
    for key in kwargs: 
        print(key, kwargs[key])

func(1, 2, 3, a=4, b=5)

而在Java中,可变参数是用三个点...表示的。只能指定一种类型的可变参数,而且它必须是方法签名中的最后一个参数。在方法内部,这些参数被当作一个数组处理。

Java
public void func(int... numbers) { 
    for(int number : numbers) { 
        System.out.println(number); 
    } 
}

func(1, 2, 3); 

构造方法

Python中,构造方法没有重载的概念。如果定义了多个 __init__(self, ...) 方法,只有最后一个会被使用。

Python
class Person(object):

    def __init__(self, name='Unnamed', age=18):
        self.name = name
        self.age = age

Java中,构造方法的名称和类名相同,并且没有返回类型,它可以重载,允许有多个构造方法。

Java
class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person(String name) {
        this.name = name;
        this.age = 18;
    }

    public Person() {
    }
}

甚至可以利用一个构造方法调用其他构造方法,便于代码复用:

Java
class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person(String name) {
        this(name, 18);
    }

    public Person() {
        this("Unnamed");
    }
}

继承

继承的语法

Python支持多重继承,意味着一个类可以直接继承多个父类的属性和方法。

Python
class Parent:
    def __init__(self):
        self.value = "我是父类"

    def do_something(self):
        print("方法在父类中定义。")
        
class Child(Parent):  # Child 继承 Parent
    def __init__(self):
        super().__init__()
        self.child_value = "我是子类"

    def do_something_else(self):
        print("这个方法只在子类中定义。")

child_instance = Child()
child_instance.do_something()  # 继承自父类的方法
child_instance.do_something_else()  # 子类中定义的方法

Java则只支持单一继承,一个类只能有一个直接父类,不过Java通过接口可以实现多重继承的效果。

Java
class Parent {
    String value;

    public Parent() {
        this.value = "我是父类";
    }

    public void doSomething() {
        System.out.println("方法在父类中定义。");
    }
}

class Child extends Parent {  // Child继承Parent
    String childValue;

    public Child() {
        super();  // 调用父类的构造器
        this.childValue = "我是子类";
    }

    public void doSomethingElse() {
        System.out.println("这个方法只在子类中定义。");
    }
}

Child childInstance = new Child();
childInstance.doSomething();  // 继承自父类的方法
childInstance.doSomethingElse();  // 子类中定义的方法

Protected关键字

在Java中,protected 关键字用于控制方法或变量的访问级别。protected 访问级别允许成员在同一个包内的类以及其他包中该类的子类中被访问。这样,子类可以访问和重写父类中的 protected 方法或变量,也能保护这些成员不被其他不相关的类直接访问。protected 关键字提供了一种介于 public 和 private 访问级别之间的访问控制,有助于实现封装和继承。

对应地,在Python中,没有像Java那样严格定义的 protected 关键字。不过,Python社区有一个约定俗成的做法来表示一个成员变量或方法不应该在外部直接被访问(类似于 protected 的含义),这就是在成员名称前加一个下划线(_)。例如:

Python
class Parent:
    def __init__(self):
        self._protected_attribute = "这是一个受保护的属性"

    def _protected_method(self):
        return "这是一个受保护的方法"

再进一步,Python使用双下划线(__)开始的命名(除了特殊方法)会触发名称改编(name mangling),这样的属性或方法在类外部几乎无法被直接访问,更接近于Java中的 private 访问级别。

覆写(Override)

Python
class Animal:
    def speak(self):
        print("这个动物发出了一种声音")

class Cat(Animal):
    def speak(self):
        print("猫咪喵喵叫")
Java
class Animal {
    public void speak () {
        System.out.println("这个动物发出了一种声音");
    }
}

class Student extends Person {
    @Override
    public void speak () {
        System.out.println("猫咪喵喵叫");
    }
}

重载(Overload)

Python不支持同名函数的多个版本,但可以通过参数默认值、关键字参数、*args 和 **kwargs 来支持不同的参数组合。

Python
def greet(first, last='', middle=''):
    if last and middle:
        print(f"你好,{first} {middle} {last}!")
    elif last:
        print(f"你好,{first} {last}!")
    else:
        print(f"你好,{first}!")
Java
public void greet(String first) {
    System.out.println("你好," + first + "!");
}

public void greet(String first, String last) {
    System.out.println("你好," + first + " " + last + "!");
}

public void greet(String first, String middle, String last) {
    System.out.println("你好," + first + " " + middle + " " + last + "!");
}

多态(Polymorphism)

Python 当然也支持多态,不过它是一种更为自然的形式,因为 Python 是动态类型语言,不需要像 Java 那样显式声明类型。在 Python 中,只要一个对象实现了一个方法,你就可以调用它,不管这个对象是什么类型。这被称为“鸭子类型”(duck typing),意思是“如果它走路像鸭子,叫声像鸭子,那么它就可以被当作鸭子”。

Python
class Dog:
    def speak(self):
        print('汪汪汪!')

class Cat:
    def speak(self):
        print('喵喵喵!')

def animal_speak(animal):
    animal.speak()

my_dog = Dog()
my_cat = Cat()
animal_speak(my_dog)
animal_speak(my_cat)
Java
public class PolymorphismExample {
    public static void main(String[] args) {
        Animal myDog = new Dog();
        Animal myCat = new Cat();
        myDog.speak();
        myCat.speak();
    }
}

interface Animal {
    void speak();
}

class Dog implements Animal {
    public void speak() {
        System.out.println("汪汪汪!");
    }
}

class Cat implements Animal {
    public void speak() {
        System.out.println("喵喵喵!");
    }
}

Final关键字

在Python中,虽然没有直接相当于final的关键字,但如果需要,在Python 3.8及更高版本中,可以使用from typing import final装饰器来修饰类和方法,表明它们不应该被继承或者重写。这主要是为了类型注解,在运行时不会强制执行,但是类型检查工具和IDE会识别这个装饰器,提供相关警告。

Python
from typing import final

@final
class MyFinalClass:
    pass

class DerivedClass(MyFinalClass):  # 类型检查器将会警告这个行为
    pass

class MyClass:
    @final
    def my_final_method(self):
        pass

class DerivedClass(MyClass):
    def my_final_method(self):  # 类型检查器将会警告这个行为
        pass

而对于field而言,可以通过命名约定来表示一个值不应该改变,常见的做法是使用全大写字母来命名常量。比如,MAX_SIZE = 100表明MAX_SIZE应该是一个常量,不应该被修改。