北大慕课Java程序设计【五】深入理解Java语言(上)

发布于:2022-11-08 ⋅ 阅读:(308) ⋅ 点赞:(0)

目录

一、变量及其传递

1. 基本类型与引用类型

2. 字段变量与局部变量

3. 变量的传递

4. 方法的返回

二、多态

1. 多态的两种情况

2. 上溯造型与虚方法的调用

3. 动态类型的确定

4. 什么情况不是虚方法调用

三、构造方法

1. 对象构造与初始化

2. 构造方法的执行过程

四、对象清除与垃圾回收

1. 对象清除与 System.gc() 方法

2. finalize() 方法


一、变量及其传递

1. 基本类型与引用类型

① 基本类型:其值直接存储于变量中(在这里)

② 引用类型:变量占一定内存空间,引用的对象实体也占一定空间(在那里)

【说明】引用型变量用 new 来创建

【举例】MyDate 类演示基本类型与引用类型

class MyDate {
    private int day;
    private int month;
    private int year;

    public MyDate(int y, int m, int d) {
        year = y;
        month = m;
        day = d;
    }

    void addYear() {
        ++year;
    }

    public void display() {
        System.out.println(year + "-" + month + "-" + day);
    }

    public static void main(String[] args) {
        MyDate m = new MyDate(2003, 9, 22);
        MyDate n = m; // 引用到同一个对象,复制的是引用,不是对象实体
        n.addYear();
        m.display();
        n.display();
    }
}

2. 字段变量与局部变量

① 字段变量与局部变量的概念

        → 字段变量:在类中定义的变量

        → 局部变量:在方法中定义的变量或方法的参变量

② 从内存角度来看

        → 存储位置:字段变量为对象的一部分,存储于堆中;局部变量存储于栈中

        → 生命周期:字段变量随对象的存在而存在,局部变量随方法的调用而存在

        → 初始值:字段变量可以自动赋初值(new 对象的时候初始化),局部变量必须显式赋值

③ 从语法角度来看

        → 字段变量属于类,可以用 public,private,static,final 来修饰

        → 局部变量不能够被访问控制符以及 static 修饰

        → 都可以被 final 修饰

3. 变量的传递

→ 有调用对象方法时,要传递参数

① 在传递参数时,Java 是值传递,将表达式的值赋值给形式参数

对于引用型变量,传递的值是引用值,而不是复制对象实体,可以改变对象的属性

class TransByValue {
    final int c = 7;

    public static void modify(int a) {
        ++a;
    }

    public static void modify(int[] b) {
        ++b[0];
        System.out.println(b[0]);
        b = new int[5];
        ++b[0];
        System.out.println(b[0]);
    }

    public static void main(String[] args) {
        int a = 0;
        modify(a);
        System.out.println(a);
        int[] b = new int[1];
        modify(b); // 复制了一份引用过去过去
        System.out.println(b[0]);
        TransByValue transByValue = getNewObject();
        System.out.println(transByValue.c);
    }

    public static TransByValue getNewObject() {
        return new TransByValue();
    }

4. 方法的返回

① 返回基本类型

返回引用类型:可以存取对象实体

二、多态

多态(polymorphism):一个程序中相同的名字表示不同的含义的情况

→ 多态的特点:大大提高了程序的抽象程度和简洁性(面向对象的重要特性)

1. 多态的两种情况

编译时多态

        → 重载(overload)多个同名的不同方法,在编译时决定

运行时多态

        → 覆盖(override)子类对父类方法进行覆盖

        → 动态绑定(dynamic binding)虚方法调用,在运行时决定

        → 在调用方法时,程序会自动地确定调用子类对象的方法

2. 上溯造型与虚方法的调用

① 上溯造型(upcasting):把派生类当作基本类来处理

Person p = new Student();
void fun(Person p) {...}
fun(new Student());

虚方法调用:可以实现运行时多态;子类重载了父类的方法时,运行时

        → 系统根据调用该方法的实例的类型来决定选择哪个方法调用

        → 所有的非 final 方法都会自动地进行动态绑定

【举例】Java 中根据实例决定虚方法调用

class Shape {
    void draw() {
        System.out.println("Shape drawing.");
    }
}

class Circle extends Shape {
    @Override
    void draw() {
        System.out.println("Drawing a circle.");
    }
}

class Triangle extends Shape {
    @Override
    void draw() {
        System.out.println("Drawing a triangle.");
    }
}

class TestVirtualInvoke {
    static void func(Shape s) {
        s.draw(); // 虚方法调用,根据实例来决定
    }

    public static void main(String[] args) {
        Shape shape = new Shape();
        Circle circle = new Circle();
        Triangle triangle = new Triangle();
        func(shape);
        func(circle);
        func(triangle);

        Shape shape1 = new Circle();
        func(shape1); // 与声明的类型无关——虚方法
    }
}

3. 动态类型的确定

→ 使用 instanceof 运算符,返回 boolean 类型

class InstanceOf {
    public static void main(String[] args) {
        Object[] objects = new Object[3];
        objects[0] = 4;
        objects[1] = 3.14;
        objects[2] = "2.09";
        double s = 0;
        for (Object object : objects) {
            if (object instanceof Integer) {
                s += (Integer) object;
            } else if (object instanceof Double) {
                s += (Double) object;
            } else {
                s += Double.parseDouble((String) object);
            }
        }
        System.out.println("sum = " + s);

        Shape circle = new Circle();
        System.out.println("circle instanceof Circle: " + (circle instanceof Circle));
        System.out.println("circle instanceof Shape: " + (circle instanceof Shape));

        Circle circle1 = new Circle();
        System.out.println("circle1 instanceof Circle: " + (circle1 instanceof Circle));
        System.out.println("circle1 instanceof Shape: " + (circle1 instanceof Shape));

        Shape shape = new Shape();
        System.out.println("shape instanceof Circle: " + (shape instanceof Circle));
        System.out.println("shape instanceof Shape: " + (shape instanceof Shape));
    }
}

4. 什么情况不是虚方法调用

→ Java 中,普通方法都是虚方法(与 C++ 不同)

① static 方法不是虚方法调用:因为这个方法属于类

② private 方法不是虚方法调用:因为这个方法子类看不见

③ final 方法不是虚方法调用:因为这个方法无法被覆盖

【注意】上述三种方法调用时,以声明的类型为准,与实例类型无关

【举例】三种不是虚方法调用的情况演示

class JavaMethods {
    void f() {
    }

    private void p() {
    }

    static void s() {
    }

    public static void main(String[] args) {
        JavaMethods javaMethods = new JavaMethods();
        javaMethods.f(); // invokevirtual
        javaMethods.p(); // invokespecial
        javaMethods.s(); // invokestatic
    }
}

→ 查看反汇编语句

javap -c JavaMethods
Compiled from "Main.java"
class JavaMethods {
  JavaMethods();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  void f();
    Code:
       0: return

  static void s();
    Code:
       0: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #7                  // class JavaMethods
       3: dup
       4: invokespecial #9                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #10                 // Method f:()V
      12: aload_1
      13: invokevirtual #13                 // Method p:()V
      16: aload_1
      17: pop
      18: invokestatic  #16                 // Method s:()V
      21: return
}

【举例】用形状类来演示 static 方法的调用

class Shape1 {
    static void draw() {
        System.out.println("Shape drawing.");
    }
}

class Circle1 extends Shape1 {
    static void draw() {
        System.out.println("Drawing a circle.");
    }
}

class Triangle1 extends Shape1 {
    static void draw() {
        System.out.println("Drawing a triangle.");
    }
}

class TestVirtualInvoke1 {
    static void func(Shape1 s) {
        s.draw(); // 虚方法调用,根据实例来决定
    }

    public static void main(String[] args) {
        Shape1 shape1 = new Shape1();
        Circle1 circle1 = new Circle1();
        Triangle1 triangle1 = new Triangle1();
        func(shape1);
        func(circle1);
        func(triangle1);

        Shape1 shape = new Circle1();
        func(shape);
        shape.draw(); // 只与声明的类型 Shape1 有关——非虚方法

        Circle1 circle = new Circle1();
        circle.draw();  // 只与声明的类型 Circle1 有关——非虚方法
    }
}

三、构造方法

1. 对象构造与初始化

① 构造方法的概念

→ 构造方法:对象都有构造方法 constructor

如果没有写,编译器会加一个 default 构造方法

【注意】抽象类也有构造方法,子类调用它的构造方法

② 调用本类或父类的构造方法

→ this 调用本类的其他构造方法;super 调用直接父类的构造方法

this 和 super 要放在第一句,且只能有一条

【注意】如果没有 this 及 super,则编译器会自动加上 super(),调用父类不带参数的构造方法

【注意】必须使所有父类的构造方法都得到调用,一直到最高层 Object,否则整个对象的构建可能不正确

【举例】使用 Person 类及其子类来展示构造方法

class Person {
    int age;
    String name;
    String city;

    Person() {
        System.out.println("In person()");
    }

    Person(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("In person(String, int)");
    }

    public void printInfo() {
        System.out.println("name = " + name + ", age = " + age + ", city = " + city);
    }
}

class Student extends Person {
    String school;

    Student() {
        this(null, 0, null);
        System.out.println("In Student()");
    }

    Student(String name, int age, String school) {
        super(name, age);
        this.school = school;
        System.out.println("In Student(String, int, String)");
    }
}

class Graduate extends Student {
    final String teacher = "Chen Xuan";

    Graduate() {
        System.out.println("In Graduate()");
    }
}

class ConstructorCallThisAndSuper {
    public static void main(String[] args) {
        Graduate graduate = new Graduate();
        System.out.println(graduate.teacher);
    
        Person person = new Person();
        person.age = 10;
        person.name = "Chen Xuan";
        person.city = "Jinan";
        person.printInfo();

        // 创建对象时的初始化(双括号)
        //     ——可以针对没有相应构造函数但是要赋值的情况
        Person person1 = new Person() {{
            age = 20;
            name = "*cx2020";
            city = "Jinan"; // 注意最后要有分号
        }};
        person1.printInfo();

        Person person2 = new Person("xxz", 20);
        person2.city = "Jinan";
        person2.printInfo();
    }
}

③ 构造方法调用时可能出现的问题

【举例】继承类的构造方法对 super() 的调用

class A {
    A(int a) {
        System.out.println("In A(int): " + a);
    }
}

class B extends A {
    public final float PI = 3.14f;

    B(String s) {
        // There is no default constructor available in 'A'
        super(3); // 例子中如果去掉这一句代码,则会出现编译错误
        System.out.println("In B(String): " + s);
    }
}

上面问题的解决方法:
→ 加入 super(3) 类似的语句(不会自动调用 super();)
→ 在 A 中加入不带参数的构造方法
→ 删去 A 中的全部构造方法

④ 实例初始化与静态初始化

→ 在类中直接写:{ 语句... }

【注意】实例初始化,先于构造方法中的语句执行(除了 this 和 super 之外的语句)

→ 静态初始化:static { 语句... }

【注意】静态初始化,在第一次使用这个类的时候执行,但其执行的具体时机是不确定的,总是先于实例的初始化执行

【举例】具体例子如下:

class InitialTestBase {
    public static void main(String[] args) {
        new InitialTestDerived(6);
        new InitialTestBase();
        new InitialTestDerived(7);
    }

    int n = 10; // step2

    {
        ++n; // 先于构造方法执行哦
        System.out.println("InitialTest..." + n);
    }

    static int x;

    static {
        ++x;
        System.out.println("static..." + x);
    }
}

class InitialTestDerived extends InitialTestBase {
    InitialTestDerived(int a) {
        this.a = a;
        System.out.println("this.a = " + a);
    }

    int a;

    {
        System.out.println("InitialTest2..." + this.a); // 尽量少用
    }

    static {
        ++x;
        System.out.println("static2..." + x);
    }
}

2. 构造方法的执行过程

① 构造方法的执行过程遵照以下步骤:

→ 调用本类或父类的构造方法,直到最高一层 Object

→ 按照声明顺序执行字段的初始化赋值 + 实例初始化语句

→ 执行构造函数中的语句 不包括 this() 或 super()

【总结】先父类构造,再本类成员赋值,最后执行构造方法中的语句

【举例】下面两个例子演示了上述流程

class JavaConstructor {
    public static void main(String[] args) {
        JavaConstructor javaConstructor = new JavaConstructor();
        // [1] Object 构造
        System.out.println(javaConstructor.a);
    }

    int a = 2000; // [2] 实例初始化 + 字段赋值

    {
        ++a;
        System.out.println("JavaConstructor 实例初始化 " + this.a);
    }

    static {
        System.out.println("JavaConstructor 静态初始化");
    }

    JavaConstructor() {
        System.out.println(this.a);
        // [3] 构造函数其他语句
        this.a = 3000;
    }
}
class Base {
    int b = 10;

    Base() {
        System.out.println("Base 构造函数");
    }

    {
        ++b;
        System.out.println("Base 实例初始化 " + this.b);
    }

    static {
        System.out.println("Base 静态初始化 ");
    }
}

class JavaConstructor2 extends Base {
    public static void main(String[] args) {
        JavaConstructor2 javaConstructor2 = new JavaConstructor2();
        System.out.println(javaConstructor2.a);
    }

    int a = 2000; // [2] 实例初始化 + 字段赋值

    {
        ++a;
        System.out.println("JavaConstructor2 实例初始化 " + this.a);
    }

    static {
        System.out.println("JavaConstructor2 静态初始化");
    }

    JavaConstructor2() {
        super(); // [1] 父类构造 可以不写
        System.out.println("JavaConstructor2 构造函数");
        System.out.println(this.a);
        // [3] 构造函数其他语句
        this.a = 3000;
    }
}

② 关于构造方法内调用别的方法的问题(在构造函数中调用一个动态绑定的方法时)

→ 会使用那个方法被覆盖的定义,但是这个时候对象还没有完全建立好

在构造函数中尽可能避免调用任何方法,用尽可能简单使对象进入就绪状态

→ 唯一能安全调用的是具有 final 属性的方法

四、对象清除与垃圾回收

1. 对象清除与 System.gc() 方法

① Java 中的对象清除

new 创建对象 Java 是自动清除,不需要使用 delete

→ 对象回收是由 JVM 的垃圾回收线程来完成的

【注意】系统怎么判断对象是否为垃圾?

任何对象都有一个引用计数器,当其值为 0 时,说明该对象可以回收

【举例】引用计数示意

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello world!");
    }

    String method() {
        // 引用计数示意
        String a, b;
        a = new String("Hi");
        b = new String("你好");
        System.out.println(a + b + "ok");
        a = null;
        a = b;
        return a;
    }
}

② System.gc() 方法

→ System 类的 static 方法,可以要求系统进行垃圾回收,但仅仅是建议

2. finalize() 方法

① finalize() 方法的定义及用途

finalize() 方法:protected void finalize() throws Throwable {},用于销毁对象

→ Java 中不叫 destructor,Object 的 finalize 方法与 C++ 中的 析构函数类似:

→ 子类的 finalize() 方法:可以在子类的 finalize() 方法中释放系统资源

→ 一般来说,子类的 finalize() 方法应调用父类的 finalize() 方法

② try-with-resources(JDK 1.7 以上)

→ 由于 finalize() 方法调用的时机并不确定,不需自己实现

关闭打开的文件,清除一些非内存资源等工作需要进行处理

→ 对于实现了 java.lang.AutoCloseable 对象,可以使用

try(Scanner scanner = new Scanner(...)) { ... }

这会自动调用其 close() 方法,相当于

finally { Scanner.close(); }


网站公告

今日签到

点亮在社区的每一天
去签到