目录
一、变量及其传递
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(); }