类:我们叫做class。

对象:我们叫做Object,instance(实例)。某个类的对象=某个类的实例。

注意

  • 所有类都继承于Object类,包括包装类、自己创建的类、String
  • 基本数据类型与Object无关

总结
  1.对象是具体的事物;类是对对象的抽象;
  2.类可以看成一类对象的模板,对象可以看成该类的一个具体实例。
  3.类是用于描述同一类型的对象的一个抽象概念,类中定义了这一类对象所应具有的共同的属性、方法。

类初始化

初始化过程

  1. public class Person {
  2. String name;
  3. int age;
  4. public void get() {
  5. System.out.println("姓名:" + name + ",年龄:" + age);
  6. }
  7. }
  8. public class TestDemo {
  9. public static void main(String args[]) {
  10. // 第一种
  11. Person per = new Person() ;// 声明并实例化对象
  12. per.name = "张三" ;//操作属性内容
  13. per.age = 30 ;//操作属性内容
  14. per.get() ;//调用类中的get()方法
  15. //第二种
  16. Person per = null;//声明对象
  17. per = new Person() ;//实例化对象
  18. per.name = "张三" ;//操作属性内容
  19. per.age = 30 ;//操作属性内容
  20. per.get() ;//调用类中的get()方法
  21. }
  22. }

具体步骤:

  1. 加载Person.class文件进内存
  2. 栈内存(保存的是堆内存的地址,在这里为了分析方便,可以简单理解为栈内存保存的是对象的名字)为s开辟空间
  3. 堆内存(保存对象的属性内容。堆内存需要用new关键字来分配空间)为学生对象开辟空间
  4. 对学生成员变量进行默认初始化
  5. 对学生成员变量进行显示初始化
  6. 通过构造方法对学生对象的成员赋值
  7. 学生成员对象初始化完毕,将对象地址赋值给s变量。

类和对象 - 图1

任何情况下,只要看见关键字new,都表示要分配新的堆内存空间,一旦堆内存空间分配了,里面就会有类中定义的属性,并且属性内容都是其对应数据类型的默认值。
于是,上面两种对象实例化对象方式内存表示如下:
类和对象 - 图2
两种方式的区别在于①②,第一种声明并实例化的方式实际就是①②组合在一起,而第二种先声明然后实例化是把①和②分步骤来。

初始化顺序

类的代码在初次使用时才会被加载。 这通常指加载发生于创建类的第一个对象之时,但是当访问static域或者static方法时,也会被加载

  • 静态代码块:在类加载的时候就执行,且执行一次。一般用于对对象进行初始化。
  • 构造代码块:每次调用构造方法都执行且在构造方法之前执行。一般是对类进行初始化。

不存在继承的情况下,初始化顺序

  • 静态变量,静态代码块 (按定义的书写顺序来进行初始化)
  • 普通变量,普通代码块(按定义的书写顺序来进行初始化)
  • 构造函数

    1. public class InitializationSequence {
    2. // 静态属性
    3. private static String staticField = getStaticField();
    4. // 静态方法块
    5. static {
    6. System.out.println(staticField);
    7. System.out.println("静态方法块初始化");
    8. }
    9. // 普通属性
    10. private String field = getField();
    11. // 普通方法块
    12. {
    13. System.out.println(field);
    14. System.out.println("普通方法块初始化");
    15. }
    16. // 构造函数
    17. public InitializationSequence() {
    18. System.out.println("构造函数初始化");
    19. }
    20. public static String getStaticField() {
    21. String statiFiled = "Static Field Initial";
    22. System.out.println("静态属性初始化");
    23. return statiFiled;
    24. }
    25. public static String getField() {
    26. String filed = "Field Initial";
    27. System.out.println("普通属性初始化");
    28. return filed;
    29. }
    30. public static void main(String[] args) {
    31. InitializationSequence i =new InitializationSequence();
    32. }
    33. }
    34. /* 输出结果:
    35. 静态属性初始化
    36. Static Field Initial
    37. 静态方法块初始化
    38. 普通属性初始化
    39. Field Initial
    40. 普通方法块初始化
    41. 构造函数初始化
    42. */

存在继承的情况下,初始化顺序

  • 父类(静态变量、静态语句块)(按定义的书写顺序来进行初始化)
  • 子类(静态变量、静态语句块)(按定义的书写顺序来进行初始化)
  • 父类(实例变量、普通语句块)(按定义的书写顺序来进行初始化)
  • 父类(构造函数)
  • 子类(实例变量、普通语句块)(按定义的书写顺序来进行初始化)
  • 子类(构造函数)
  1. class A{
  2. private static int a = printInit("static a");
  3. private int b = 9;
  4. int c =printInit("c");
  5. static {
  6. System.out.print(" X ");
  7. }
  8. private static int d = printInit("static d");
  9. {
  10. System.out.print(" F1 ");
  11. }
  12. int c1 =printInit("c1");
  13. A(){
  14. System.out.println("b:"+b+" c: "+c);
  15. c=39;
  16. }
  17. static int printInit(String s){
  18. System.out.println(s+" initialized");
  19. return 47;
  20. }
  21. }
  22. public class TestInit extends A {
  23. static int e =printInit("static e");
  24. int f=printInit("f");
  25. static {
  26. System.out.print(" Y ");
  27. }
  28. static int g =printInit("static g");
  29. {
  30. System.out.print(" F2 ");
  31. }
  32. TestInit(){
  33. System.out.println("f:"+f+"c:"+c);
  34. }
  35. public static void main(String[] args){
  36. System.out.println("QQQ");
  37. TestInit t = new TestInit();
  38. }
  39. }
  40. static a initialized
  41. X static d initialized
  42. static e initialized
  43. Y static g initialized
  44. QQQ
  45. c initialized
  46. F1 c1 initialized
  47. b:9 c: 47
  48. f initialized
  49. F2 f:47c:39

值传递和引用传递

Java 内的传递都是值传递,Java基本类型的传递是值传递,Java 中实例对象的传递是引用传递,实际是对象地址的值传递。

值传递

Java 语言的方法调用只支持参数的值传递。
当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的。

首先回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。按值调用(call by value)表示方法接收的是调用者提供的值,而按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。 它用来描述各种程序设计语言(不只是Java)中方法参数传递方式。
Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。

  • 例一

    1. public static void main(String[] args) {
    2. int num1 = 10;
    3. int num2 = 20;
    4. swap(num1, num2);
    5. System.out.println("num1 = " + num1);
    6. System.out.println("num2 = " + num2);
    7. }
    8. public static void swap(int a, int b) {
    9. int temp = a;
    10. a = b;
    11. b = temp;
    12. System.out.println("a = " + a);
    13. System.out.println("b = " + b);
    14. }
    15. // output
    16. a = 20
    17. b = 10
    18. num1 = 10
    19. num2 = 20

    类和对象 - 图3
    在swap方法中,a、b的值进行交换,并不会影响到 num1、num2。因为,a、b中的值,只是从 num1、num2 的复制过来的。也就是说,a、b相当于num1、num2 的副本,副本的内容无论怎么修改,都不会影响到原件本身。
    通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样。

  • 例二

    1. public static void main(String[] args) {
    2. int[] arr = { 1, 2, 3, 4, 5 };
    3. System.out.println(arr[0]);
    4. change(arr);
    5. System.out.println(arr[0]);
    6. }
    7. public static void change(int[] array) {
    8. // 将数组的第一个元素变为0
    9. array[0] = 0;
    10. }
    11. // output
    12. 1
    13. 0

    类和对象 - 图4
    array 被初始化 arr 的拷贝也就是一个对象的引用,也就是说 array 和 arr 指向的时同一个数组对象。 因此,外部对引用对象的改变会反映到所对应的对象上。
    通过 example2 我们已经看到,实现一个改变对象参数状态的方法并不是一件难事。理由很简单,方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。

很多程序设计语言(特别是,C++和Pascal)提供了两种参数传递的方式:值调用和引用调用。有些程序员(甚至本书的作者)认为Java程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以下面给出一个反例来详细地阐述一下这个问题。

  • 例三
    1. public class Test {
    2. public static void main(String[] args) {
    3. // TODO Auto-generated method stub
    4. Student s1 = new Student("小张");
    5. Student s2 = new Student("小李");
    6. Test.swap(s1, s2);
    7. System.out.println("s1:" + s1.getName());
    8. System.out.println("s2:" + s2.getName());
    9. }
    10. public static void swap(Student x, Student y) {
    11. Student temp = x;
    12. x = y;
    13. y = temp;
    14. System.out.println("x:" + x.getName());
    15. System.out.println("y:" + y.getName());
    16. }
    17. }
    18. // output
    19. x:小李
    20. y:小张
    21. s1:小张
    22. s2:小李
    交换之前:类和对象 - 图5
    交换之后:类和对象 - 图6
    通过上面两张图可以很清晰的看出: 方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap方法的参数x和y被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝

对象引用

引用传递的精髓:同一块堆内存空间,可以同时被多个栈内存所指向,不同的栈可以修改同一块堆内存的内容。

  1. public class TestDemo {
  2. public static void main(String args[]) {
  3. Person per1 = new Person() ;
  4. per.name = "张三" ;
  5. per.age = 30 ;
  6. Person per2 = per1 ; // 引用传递
  7. per2.name = "李四" ;
  8. per1.get();
  9. }
  10. }

对应的内存分配图如下
类和对象 - 图7

再来看另外一个:

  1. public class TestDemo {
  2. public static void main(String args[]) {
  3. Person per1 = new Person() ;
  4. Person per2 = new Person() ;
  5. per1.name = "张三" ;
  6. per1.age = 20 ;
  7. per2.name = "李四" ;
  8. per2.age = 30 ;
  9. per2 = per1 ;// 引用传递
  10. per2.name = "王五" ;
  11. per1.tell() ;
  12. }
  13. }

对应的内存分配图如下:
类和对象 - 图8

Java程序设计语言对对象采用的不是引用调用,实际上,对象引用是按值传递的。
下面再总结一下Java中方法参数的使用情况:

  • 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型),操作无效
  • 一个方法可以改变一个对象参数的状态。
  • 一个方法不能让对象参数引用一个新的对象。不会抛出异常,但操作无效。

类变量

成员变量和局部变量的区别

区别 成员变量 局部变量
在类中位置不同 类中方法外 方法内或者方法声明上
在内存中位置不同 堆内存 栈内存
声明周期不同 随着对象存在而存在,随着对象消失而消失 随着方法调用而存在,随着方法调用完毕而消失
初始化值不同 有默认的初始值 没有默认的初始值,必须先定义,赋值,才能使用

注意:局部变量名可以和成员变量名一样,在方法中使用的时候,采用就近原则。

静态变量和成员变量的区别

区别 静态变量 成员变量
所属不同 属于类,所以也称为类变量 属于对象,所以也称为实例变量(对象变量)
内存中位置不同 存储于方法区的静态区 存储于堆内存
内存中出现时间不同 随着的加载而加载,随着类的消失而消失 随着对象的创建而存在,随着对象的消失而消失
调用不同 可以通过类名调用,也可以通过对象调用 只能通过的对象来调用