这篇的主要内容是查漏补缺。原因是今天想继续下一部分:继承和多态,结果发现林的书有许多东西讲的太精简,甚至有失偏颇。所以下面我将结合李的书和博客多前面的内容进行回顾。

1.数组

内存中的数组

 数组引用变量只是一个引用,类似于指针,它存放了对象的真是地址,但是这只是类比理解,实际上还是有差别的。实际的数组对象存放在堆中,而引用变量如果是一个局部变量就是在栈内存中。(是不是又像c++)同样的,当一个方法执行时,每个方法都会建立内存栈,然后将变量依次放入栈中,当使用完时也就被销毁了。堆中的对象是不会这样的,只要有引用指向它,它就会存在,当没有任何引用指向它时,它就会被垃圾管理器回收。这和C++不一样,C++中的需要自己手动创建,手动释放。
 引用的指定并不是唯一的,不是我被指定给了某个对象就不能再被指定给别人,只要类型相符就没有问题。

基本类型数组的初始化

 对于基本类型数组,数组元素的值是直接存在对应的数组元素中的,因此,初始化数组时先为该数组分配内存空间,然后将数组的值放入里面就行。注意细节:分配空间后,里面并不是空的,而是默认值,关于默认值前面的笔记提到过。

引用类型数组的初始化

先给出代码:
定义类:

1
2
3
4
5
6
7
class Person{
public int age;
public double height;
public void info(){
System.out.println("我的年龄是:"+age+"我的身高是:"+height);
}
}

测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ReferenceArrayTest {
public static void main(String[] args) {
Person[] students = new Person[2];
Person zhang = new Person();
Person lee = new Person();
zhang.age = 15;
zhang.height = 158;
lee.height = 161;
students[0]=zhang;
students[1]=lee;
lee.info();
student[1].info();
}
}

结果非常简单。问题在于下面这张图:

数组

 注意图的左边部分,是不是觉得很变扭,我总觉得这里不对。后来查到,引用变量是放在栈中的。关于内存空间分配,参看链接(我还没研究过相关内容,不保证文章的正确):

[Java的内存分配]:https://www.cnblogs.com/SaraMoring/p/5687466.html

this的使用

1
2
3
4
5
6
7
8
9
10
public class Dog {
public void jump() {
System.out.println("正在执行jump方法");
}

public void run() {
this.jump();
System.out.println("正在执行run方法");
}
}

 谁在调用这个方法,this就代表谁。
 在上面的代码中,假如我创建了一个dog对象,this.jump()代表调用dog的jump()方法,这种在同一个对象中的两个方法之间的依赖,在用的时候可以省略this。

 省略this前缀只是一种假象,虽然程序员省略了调用jump()方法之前的this ,但实际上这个this依然是存在的。根据汉语语法习惯:完整的语句至少包括主语、谓语、宾语,在面向对象的世界里,主、谓、宾的结构完全成立,例如 “猪八戒吃西瓜”是一条汉语语句,转换为面向对象的语法,就可以写成“猪八戒.吃(西瓜);”,因此本书常常把调用成员变量、方法的对象称为“主调(主语调用者的简称)”。对于Java语言来说,调用成员变量、方法时,主调是必不可少的,即使代码中省略了主调,但实际的主调依然存在。一般来说,如果调用static修饰的成员(包括方法、成员变量)时省略了前面的主调,那么默认使用该类作为主调;如果调用没有static修饰的成员(包括方法、成员变量)时省略了 前面的主调,那么默认使用this作为主调。

关于静态与非静态

1
2
3
4
5
6
7
8
9
public class StaticAccessNoStatic {
public void info() {
System.out.println("调用info()方法");
}

public static void main(String[] args) {
info();
}
}

main()方法是静态的,它是属于类的,info()是属于对象的方法,必须使用对象来调用。类比上一节的代码,main方法调用info方法时,相当于调用this.info()方法,但是再三强调过了,static是类的,不属于对象,也就无从调用。而且就算允许了,那么,this是没法引用有效的对象的。
但是,但是,最不合理的地方就是:

1
Dog dog = new Dog();

假定run()方法是static的,你是可以通过对象dog来调用的。但是,必须杜绝这种写法,正确的做法是:Dog.run;

还有一些特殊情况,例如:当类的成员变量和类的方法的局部变量同名时,就需要用到this。

1
2
3
4
5
6
7
public class Dog{
String aa;
public void func(){
String aa;
System.out.println(this.aa+aa);
}
}

所以说,在构造函数(构造器)中的this就是这么来的。

1
2
3
4
5
6
7
8
9
public class Dog {
String name;
int age;

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

在构造中,this是指new出来的新对象。

1
2
3
4
5
6
7
8
9
10
11
public class CashCard{
private String number;
private int balance;
private int bonus;

public CashCard(String number, int balance, int bonus) {
this.number = number;
this.balance = balance;
this.bonus = bonus;
}
}

构造器是被new调用的,所以this指代的是这个新对象。

1
2
3
public class CashCardTest{
CashCard card = new CashCard("500",20,2);
}

可以得出推测,谁在调用含有this的方法,this就代表了谁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ReturnThis {
public int age;

public ReturnThis grow() {
age++;
return this;
}

public static void main(String[] args) {
ReturnThis rt = new ReturnThis();
rt.grow().grow().grow();
System.out.println("rt的成员变量值是:"+rt.age);
}
}

 从grow()方法的返回值类型也可以看出,this是指代的调用它的rt。当执行了第一次调用后,原本的rt.grow().grow().grow();意思就是rt.grow().grow();,只不过现在的rt中的成员变量值已经改变。因为返回的是一个对象,所以可以连续调用。

关于方法的一些思考

 方法很像函数,在C++中函数是老大,一切功能都是依靠函数,但在Java中类是老大,一切方法都必须在类或对象中。方法的调用是通过“类.方法”或“对象.方法”,必须通过方法和类。即使是调用同一个类中的不同方法,如果是static的,是依靠类,如果是普通的,是依靠this。

关于值传递

 在笔记3中我贴了两篇有关的博客,说的非常清楚了。值传递传递的是值的拷贝,对于基本类型,这会造成你修改了传递过去的拷贝值并不会影响原来的值,但是对于引用对象来说,确实是传递了值——引用,但并没有拷贝对象(没有new),所以修改拷贝值指向的对象是会影响到原来的对象的。

成员变量与局部变量

 一个是类中的变量,另一个是方法中的变量。成员变量也可以加上static,就成了类变量,同样的具有类方法的相关表现。还是上面说过的坚决杜绝通过实例对象访问类的静态的东西,不论方法还是成员。

封装与隐藏

访问控制符

 private,protect,public三个,初次之外其实还有个default。

 private:如果类中的成员,包括成员变量,方法,构造器等,使用这个关键字,则这个成员只能在当前类中使用。用来修饰成员变量最合适。

 default:如果类中的成员,包括成员变量,方法,构造器等或者一个外部类不使用任何访问控制符,它则是包访问权限,这个成员只能在当前包中使用。说的有点绕,其实就是没有任何控制符的成员只能在同一个包中使用。

 protected:如果类中的成员,包括成员变量,方法,构造器等,使用这个关键字,则这个成员既可以被同一个包中的其他类使用,也可以被不同包中的子类使用。

 public:最宽松的访问控制,不论是否在同一个包中,是否具有父子继承关系,不论在哪都可以使用。

private default protected public
同一个类中 Y Y Y Y
同一个包中 Y Y Y
子类中 Y Y
全局范围内 Y

*一个Java源文件中如果有一个public类,那么文件名必须是这个类名,不然,在别的地方要怎么调用这个类呢。

关于访问控制符有以下几个原则:

1.类里的绝大部分成员变量都应该使用private,只有一些static修饰的,类似全局变量的成员变量,才考虑用public。还有一些辅助类的其他方法的方法(工具方法)也应该是private。

2.如果某个类主要用于做为父类,类里的大部分方法只被用来让子类重写,而不像被外界调用,则应该用protected。

关于package

 在没有import package时,在一个父包中使用子包中的类,也必须写完整的包路径和类名。父包和子包确实有内在的逻辑关系,通常子包是父包的一个模块,但这并不代表在用法上有任何关系,使用时必须用完整的名字。

 可以使用import导入包,import java.util.*表示导入这个包下面的所有类,不包括下面的子包。

 但不是全部导入就行,因为可能存在不同的包中有相同的类名,还是需要在使用时显式的指定。

再论构造器

 构造器是构建Java对象的重要手段,但是,要强调的是,不是构造器新建了Java对象。根本的还是new关键字。ReturnThis rt = new ReturnThis();可以看出,最初额内存空间是有new创建的,而构造器进一步对这个空间内的东西进行创建。