类:我们叫做class。
对象:我们叫做Object,instance(实例)。某个类的对象=某个类的实例。
注意
- 所有类都继承于Object类,包括包装类、自己创建的类、String
- 基本数据类型与Object无关
总结
1.对象是具体的事物;类是对对象的抽象;
2.类可以看成一类对象的模板,对象可以看成该类的一个具体实例。
3.类是用于描述同一类型的对象的一个抽象概念,类中定义了这一类对象所应具有的共同的属性、方法。
类初始化
初始化过程
public class Person {
String name;
int age;
public void get() {
System.out.println("姓名:" + name + ",年龄:" + age);
}
}
public class TestDemo {
public static void main(String args[]) {
// 第一种
Person per = new Person() ;// 声明并实例化对象
per.name = "张三" ;//操作属性内容
per.age = 30 ;//操作属性内容
per.get() ;//调用类中的get()方法
//第二种
Person per = null;//声明对象
per = new Person() ;//实例化对象
per.name = "张三" ;//操作属性内容
per.age = 30 ;//操作属性内容
per.get() ;//调用类中的get()方法
}
}
具体步骤:
- 加载Person.class文件进内存
- 在栈内存(保存的是堆内存的地址,在这里为了分析方便,可以简单理解为栈内存保存的是对象的名字)为s开辟空间
- 在堆内存(保存对象的属性内容。堆内存需要用new关键字来分配空间)为学生对象开辟空间
- 对学生成员变量进行默认初始化
- 对学生成员变量进行显示初始化
- 通过构造方法对学生对象的成员赋值
- 学生成员对象初始化完毕,将对象地址赋值给s变量。
任何情况下,只要看见关键字new,都表示要分配新的堆内存空间,一旦堆内存空间分配了,里面就会有类中定义的属性,并且属性内容都是其对应数据类型的默认值。
于是,上面两种对象实例化对象方式内存表示如下:
两种方式的区别在于①②,第一种声明并实例化的方式实际就是①②组合在一起,而第二种先声明然后实例化是把①和②分步骤来。
初始化顺序
类的代码在初次使用时才会被加载。 这通常指加载发生于创建类的第一个对象之时,但是当访问static域或者static方法时,也会被加载
- 静态代码块:在类加载的时候就执行,且执行一次。一般用于对对象进行初始化。
- 构造代码块:每次调用构造方法都执行且在构造方法之前执行。一般是对类进行初始化。
不存在继承的情况下,初始化顺序
- 静态变量,静态代码块 (按定义的书写顺序来进行初始化)
- 普通变量,普通代码块(按定义的书写顺序来进行初始化)
构造函数
public class InitializationSequence {
// 静态属性
private static String staticField = getStaticField();
// 静态方法块
static {
System.out.println(staticField);
System.out.println("静态方法块初始化");
}
// 普通属性
private String field = getField();
// 普通方法块
{
System.out.println(field);
System.out.println("普通方法块初始化");
}
// 构造函数
public InitializationSequence() {
System.out.println("构造函数初始化");
}
public static String getStaticField() {
String statiFiled = "Static Field Initial";
System.out.println("静态属性初始化");
return statiFiled;
}
public static String getField() {
String filed = "Field Initial";
System.out.println("普通属性初始化");
return filed;
}
public static void main(String[] args) {
InitializationSequence i =new InitializationSequence();
}
}
/* 输出结果:
静态属性初始化
Static Field Initial
静态方法块初始化
普通属性初始化
Field Initial
普通方法块初始化
构造函数初始化
*/
存在继承的情况下,初始化顺序
- 父类(静态变量、静态语句块)(按定义的书写顺序来进行初始化)
- 子类(静态变量、静态语句块)(按定义的书写顺序来进行初始化)
- 父类(实例变量、普通语句块)(按定义的书写顺序来进行初始化)
- 父类(构造函数)
- 子类(实例变量、普通语句块)(按定义的书写顺序来进行初始化)
- 子类(构造函数)
class A{
private static int a = printInit("static a");
private int b = 9;
int c =printInit("c");
static {
System.out.print(" X ");
}
private static int d = printInit("static d");
{
System.out.print(" F1 ");
}
int c1 =printInit("c1");
A(){
System.out.println("b:"+b+" c: "+c);
c=39;
}
static int printInit(String s){
System.out.println(s+" initialized");
return 47;
}
}
public class TestInit extends A {
static int e =printInit("static e");
int f=printInit("f");
static {
System.out.print(" Y ");
}
static int g =printInit("static g");
{
System.out.print(" F2 ");
}
TestInit(){
System.out.println("f:"+f+"c:"+c);
}
public static void main(String[] args){
System.out.println("QQQ");
TestInit t = new TestInit();
}
}
static a initialized
X static d initialized
static e initialized
Y static g initialized
QQQ
c initialized
F1 c1 initialized
b:9 c: 47
f initialized
F2 f:47c:39
值传递和引用传递
Java 内的传递都是值传递,Java基本类型的传递是值传递,Java 中实例对象的传递是引用传递,实际是对象地址的值传递。
值传递
Java 语言的方法调用只支持参数的值传递。
当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的。
首先回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。按值调用(call by value)表示方法接收的是调用者提供的值,而按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。 它用来描述各种程序设计语言(不只是Java)中方法参数传递方式。
Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。
例一
public static void main(String[] args) {
int num1 = 10;
int num2 = 20;
swap(num1, num2);
System.out.println("num1 = " + num1);
System.out.println("num2 = " + num2);
}
public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
System.out.println("a = " + a);
System.out.println("b = " + b);
}
// output
a = 20
b = 10
num1 = 10
num2 = 20
在swap方法中,a、b的值进行交换,并不会影响到 num1、num2。因为,a、b中的值,只是从 num1、num2 的复制过来的。也就是说,a、b相当于num1、num2 的副本,副本的内容无论怎么修改,都不会影响到原件本身。
通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样。例二
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
System.out.println(arr[0]);
change(arr);
System.out.println(arr[0]);
}
public static void change(int[] array) {
// 将数组的第一个元素变为0
array[0] = 0;
}
// output
1
0
array 被初始化 arr 的拷贝也就是一个对象的引用,也就是说 array 和 arr 指向的时同一个数组对象。 因此,外部对引用对象的改变会反映到所对应的对象上。
通过 example2 我们已经看到,实现一个改变对象参数状态的方法并不是一件难事。理由很简单,方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。
很多程序设计语言(特别是,C++和Pascal)提供了两种参数传递的方式:值调用和引用调用。有些程序员(甚至本书的作者)认为Java程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以下面给出一个反例来详细地阐述一下这个问题。
- 例三
交换之前:public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
Student s1 = new Student("小张");
Student s2 = new Student("小李");
Test.swap(s1, s2);
System.out.println("s1:" + s1.getName());
System.out.println("s2:" + s2.getName());
}
public static void swap(Student x, Student y) {
Student temp = x;
x = y;
y = temp;
System.out.println("x:" + x.getName());
System.out.println("y:" + y.getName());
}
}
// output
x:小李
y:小张
s1:小张
s2:小李
交换之后:
通过上面两张图可以很清晰的看出: 方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap方法的参数x和y被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝
对象引用
引用传递的精髓:同一块堆内存空间,可以同时被多个栈内存所指向,不同的栈可以修改同一块堆内存的内容。
public class TestDemo {
public static void main(String args[]) {
Person per1 = new Person() ;
per.name = "张三" ;
per.age = 30 ;
Person per2 = per1 ; // 引用传递
per2.name = "李四" ;
per1.get();
}
}
对应的内存分配图如下
再来看另外一个:
public class TestDemo {
public static void main(String args[]) {
Person per1 = new Person() ;
Person per2 = new Person() ;
per1.name = "张三" ;
per1.age = 20 ;
per2.name = "李四" ;
per2.age = 30 ;
per2 = per1 ;// 引用传递
per2.name = "王五" ;
per1.tell() ;
}
}
对应的内存分配图如下:
Java程序设计语言对对象采用的不是引用调用,实际上,对象引用是按值传递的。
下面再总结一下Java中方法参数的使用情况:
- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型),操作无效
- 一个方法可以改变一个对象参数的状态。
- 一个方法不能让对象参数引用一个新的对象。不会抛出异常,但操作无效。
类变量
成员变量和局部变量的区别
区别 | 成员变量 | 局部变量 |
---|---|---|
在类中位置不同 | 类中方法外 | 方法内或者方法声明上 |
在内存中位置不同 | 堆内存 | 栈内存 |
声明周期不同 | 随着对象存在而存在,随着对象消失而消失 | 随着方法调用而存在,随着方法调用完毕而消失 |
初始化值不同 | 有默认的初始值 | 没有默认的初始值,必须先定义,赋值,才能使用 |
注意:局部变量名可以和成员变量名一样,在方法中使用的时候,采用就近原则。
静态变量和成员变量的区别
区别 | 静态变量 | 成员变量 |
---|---|---|
所属不同 | 属于类,所以也称为类变量 | 属于对象,所以也称为实例变量(对象变量) |
内存中位置不同 | 存储于方法区的静态区 | 存储于堆内存 |
内存中出现时间不同 | 随着类的加载而加载,随着类的消失而消失 | 随着对象的创建而存在,随着对象的消失而消失 |
调用不同 | 可以通过类名调用,也可以通过对象调用 | 只能通过的对象来调用 |