JAVA入门笔记6 —— 类和对象初步

  • 参考书:《Java语言程序设计与数据结构(基础篇)》—— 梁勇
  • 参考视频教程:java教程

一、类和对象相关概念

1. 面向过程的程序设计

  1. 面向过程的程序 = 算法+数据结构

  2. 程序由全局变量函数构成,用函数操作数据结构

  3. 不足:

    1. 函数和其操作的数据结构没有直观的联系,程序长了之后,难以直接看出
      1. 某个数据结构有哪些函数可以对它操作操作
      2. 某个函数到底是操作哪些数据结构的
      3. 任意两个函数间存在怎样的调用关系?
    2. 没有“封装”和“隐藏”的概念,要访问某个数据结构中的某个变量,可以直接访问。当变量定义变化时,就必须找到所有调用处修改,难以检查哪个函数导致错误
    3. 难以进行代码重用
  4. C语言就是典型的面向过程编程语言,只能用面向过程的思想进行开发

2. 面向对象的程序设计

  1. 面向对象的程序 = 类+类+…+类
  2. 设计方法:
    1. 把某类事物的客观属性(共同特点)归纳出来,形成一个数据结构(可以用多个变量描述事物的属性)
    2. 把这类事物能进行的行为也归纳出来,形成一个个函数,用于操作数据结构(这一步叫抽象
    3. 然后通过某种语法形式,把数据结构和操作此结构的函数捆绑到一起,形成一个,从而将数据结构和函数紧密联系起来(这步叫封装
    4. 类相当于一个模板对象是类的实例,可以从一个类构造多个实例(对象)
  3. 抽象的结果:成员变量和成员方法
    1. 成员变量:描述对象的状态/属性,由数据域及其值表示
    2. 成员方法:描述对象的行为,由方法定义
  4. 基本特点:抽象、封装、继承、多态
  5. java是典型的面向对象编程语言,支持面向对象的程序开发

二、java中类和对象的使用及存储

1. 创建和使用类

(1)创建类

  1. java中通过class关键字创建类,在类中直接定义成员变量和成员方法,注意成员变量放在成员方法外边
  2. 类可以嵌套定义
    1. 内部类:在一个类中定义的类,称为内部类(参考:浅谈Java内部类
    2. 外部类:和内部类相对应的,不在其他类内部定义的类称为外部类
  3. 一个.java源码文件中可以定义多个类,但只能有一个public类,且文件名必须和此public类同名
    • Demo.java中必须有且只有一个public公共类,且它叫Demo
    • 编译时,java编译器把每一个类都单独生成一个.class文件
  4. 通常,我们使用IDE开发java程序时,会把每个类单独放在一个同名.java文件中
  5. 默认值:创建类后,类的成员变量会有一个默认值,规则和数组一样
    在这里插入图片描述

(2)使用类

  1. 使用类的一般步骤:

    1. 导包,指出需要使用的类在什么位置,对于和当前类在同一个包的情况,可以省略导包语句

      import 包名称.类名称
      
      //cn.itcast.day01.demo01是包名,Student是类名
      import cn.itcast.day01.demo01.Student	
      
    2. 创建实例(对象):

      类名称 对象名 = new 类名称();
      Student stu = new Student();
      
    3. 使用

      使用成员变量:对象名.成员变量名
      使用成员方法:对象名.成员方法名(参数列表)
      
  2. 类对象是一种引用数据类型,类似C的指针指针类型,可以进行以下操作

    类名 引用变量名 = new 构造函数(参数表);	//简化写法
    类名 引用变量名;							//创建引用变量
    引用变量名 = new 构造函数(参数表);		//创建对象并给引用变量赋值
    引用变量名_1 = 引用变量名_2;				//"引用变量名_1" 指向 "引用变量名_2" 指向的对象	
    
  3. 有时候,对象创建后不需要引用访问,这时可以不把他赋给某个引用变量,这样创建的对象叫匿名对象

    System.out.print(new Circle(5).getArea());	//这里的Circle对象就是匿名对象
    

    匿名数组就是匿名对象的一种,使用匿名对象的策略仅仅就是创建、初始化、应用,因为它没有任何名字因此没法重用它

(3)说明

  1. 关于引用变量赋值和垃圾回收
    • 假设有两个引用变量C1C2C1 = C2; 会使C1指向C2指向的对象,这时如果原来C1指向的对象没有其他引用变量引用了,那个对象就会成为不可访问的,会自动被java垃圾回收机制回收。关于对象的存储详见下文
    • 如果要消除某个对象,可以给引用它的引用变量赋值null,这样它就会被自动回收

2. 内存中的存储情况

  • String或数组相同,类对象是一种引用数据类型,这类似C中的指针类型,其存储的是一个地址值。创建类对象时,在堆区创建类对象,在栈区创建类对象变量,对象变量本质上存储着堆区对象的地址。如果堆区的某个类对象没有任何引用变量指向它,就会被垃圾回收机制自动回收

(1)堆/栈/方法区

  • JAVA的JVM的内存可分为3个区:堆(heap)、栈(stack)和方法区(method)
    • 堆区
      1. 存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)
      2. jvm只有一个堆区,被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身
      3. 通过new关键字,在堆区创建对象
    • 栈区
      1. 每个线程包含一个栈区,栈中只保存基础数据类型的对象自定义对象/引用数据类型的引用。例如String str = "12345";这行代码,它在栈区创建了一个引用str,在堆区创建了一个String对象"12345",并且把str引用到它
      2. 每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
      3. 栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
    • 方法区
      1. 又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
      2. JDK8之前,由永久代实现,主要存放类的信息、常量池、方法数据、方法代码等;JDK8之后,取消了永久代,提出了元空间,并且常量池、静态成员变量等迁移到了堆中;元空间不在虚拟机内存中,而是放在本地内存中。

(2)静态成员和非静态成员

  • 通过static关键字可以设置类成员为静态的
    • 成员变量

      1. 非静态成员变量(实例变量):是属于类的实例的,在jvm加载类的字节码文件到方法区时不创建,只有在类实例创建时才在堆区一起创建。每个类实例中只能直接访问到自己的成员变量,不能直接访问其他同类实例中的同名变量。在使用时,只能通过实例名.变量名的方式使用
      2. 静态成员变量(类变量):是属于类的,在jvm加载类的字节码文件到方法区时即创建,早期(jdk8之前)是放在方法区的,后来改为存放在堆区。所有类的实例中都可以直接访问到类的非静态成员变量,并能对其进行修改。使用时,可以实例名.变量名这样访问,也可以通过类名.变量名的方式使用它,而不需要创建对象。
    • 成员方法

      1. 非静态成员方法(实例方法):在jvm加载类的字节码文件到方法区时,类的实例方法并不分配入口地址,只有当我们创建对象时,类的实例方法才会被分配访问地址。在创建类的第一个对象时,类的实例方法的入口地址就会被分配,之后再创建此对象时不再给实例方法的分配新的地址,而是共享第一个对象被创建时分配的实例方法的入口地址,并且,只有当类的最后一个实例对象被销毁时才会将地址回收。类似实例变量,只能用实例名.方法名()形式使用
      2. 静态成员方法(类方法)在jvm加载类的字节码文件到方法区时,类方法就会被分配入口地址,所以类方法的入口地址分配比实例方法要早。类似实例变量,可以用实例名.方法名()类名.方法名()两种形式使用

(3)静态代码块

  • 静态代码块是类中使用static关键字修饰的代码块。
  • 格式
    public class 类型名{
          
          
    	static {
          
          
    		// 静态代码块内容
    	}
    		
    	// 构造方法....
    	// 成员方法....
    }
    
  • 特点:
    • 第一次用到本类时,静态代码块执行唯一的一次
    • 静态内容总是优先于非静态内容,所以静态代码块优先于构造方法执行
  • 主要用途:一次性地对静态成员变量进行赋值,在涉及jdbc时,静态代码块很有用

3. 示例

(1)单一实例的情况

  • 在package cn.itcast.day01.demo02中建立一个Phone

    package cn.itcast.day01.demo02;
    
    public class Phone {
          
          
        String brand;   //品牌
        double price;   //价格
        String color;   //颜色
    
        public void call(String who){
          
          
            System.out.println("给"+who+"打电话");
        }
    
        public void sendMessage(){
          
          
            System.out.println("群发短信");
        }
    }
    
    
  • 在同一个package cn.itcast.day01.demo02中建立一个DemoPhone类,其中包含main方法。由于和Phone类在同一个package中,所以不需要导包

    package cn.itcast.day01.demo02;
    
    public class DemoPhone {
          
          
        public static void main(String[] args) {
          
          
            Phone one = new Phone();
            System.out.println(one.brand);  //null
            System.out.println(one.price);  //0.0
            System.out.println(one.color);  //null
    
            one.brand = "苹果";
            one.price = 8388.0;
            one.color = "黑色";
    
            System.out.println(one.brand);  //苹果
            System.out.println(one.price);  //8388.0
            System.out.println(one.color);  //黑色
    
            one.call("乔布斯");    	//给乔布斯打电话
            one.sendMessage();   	//群发短信
        }
    }
    	
    
  • 内存示意图如下
    在这里插入图片描述

    • 可见
    1. 类信息存储于方法区类实例存储于堆区
    2. 在运行过程中,每次方法调用会在栈区建立一个栈帧,程序转至此栈帧运行,方法运行完毕后其栈帧弹出
    3. 类的实例变量保存的是堆中类对象的地址,从而可以通过实例变量引用到对象

(2)两个同类实例的情况

  • 修改DemoPhone,增加一个同类实例,如下

    package cn.itcast.day01.demo02;
    
    public class DemoPhone {
          
          
        public static void main(String[] args) {
          
          
            Phone one = new Phone();
            
            one.brand = "苹果";
            one.price = 8388.0;
            one.color = "黑色";
    
            System.out.println(one.brand);  //苹果
            System.out.println(one.price);  //8388.0
            System.out.println(one.color);  //黑色
    
            one.call("乔布斯");    //给乔布斯打电话
            one.sendMessage();    //群发短信
            
            //---------------------------------------
            Phone two = new Phone();
            two.brand = "三星";
            two.price = 5999.0;
            two.color = "蓝色";
    
            System.out.println(two.brand);  //三星
            System.out.println(two.price);  //5999.0
            System.out.println(two.color);  //蓝色
    
            two.call("哈哈哈");    //给哈哈哈打电话
            two.sendMessage();    //群发短信
        }
    }
    
    
  • 此时内存示意图如下
    在这里插入图片描述

    • 可见,同一个类的多个实例,在第一个实例被创建时给实例方法分配入口地址,之后的其他实例共用同一个入口地址

(3)两个引用变量引用到同一实例的情况

  • 再修改DemoPhonetwo不指向和one相同的对象
    package cn.itcast.day01.demo02;
    
    public class DemoPhone {
          
          
        public static void main(String[] args) {
          
          
            Phone one = new Phone();
            
            one.brand = "苹果";
            one.price = 8388.0;
            one.color = "黑色";
    
            System.out.println(one.brand);  //苹果
            System.out.println(one.price);  //8388.0
            System.out.println(one.color);  //黑色
    
            one.call("乔布斯");    //给乔布斯打电话
            one.sendMessage();    //群发短信
            
            //---------------------------------------
            Phone two = one;
            two.brand = "三星";
            two.price = 5999.0;
            two.color = "蓝色";
    
            System.out.println(two.brand);  //三星
            System.out.println(two.price);  //5999.0
            System.out.println(two.color);  //蓝色
    
            two.call("哈哈哈");    //给哈哈哈打电话
            two.sendMessage();    //群发短信
        }
    }
    			
    
  • 此时内存示意图如下
    在这里插入图片描述

三、类和方法

1. 类作为方法的参数

  • java中只有一种传参数方法:值传递
  • 对于引用型变量(String/数组/类对象…)等,传递的是地址值
  • 示例
    在这里插入图片描述
    • 这个示例中,我们假设堆中类对象的地址为0x666,在main方法中创建的对象变量one保存了0x666这个地址,从而可以引用到堆中的对象。当one作为实参传入方法method时,由于是值传递,形参param也保存了0x666,从而可以访问到堆中的对象

2. 类作为方法的返回值

  • java中只有一种传返回值方法:值传递
  • 对于引用型变量(String/数组/类对象…)等,传递的是地址值
  • 示例在这里插入图片描述
    • 这个示例中,我们假设堆中类对象的地址为0x666,在getPhone方法中创建的对象变量one保存了0x666这个地址,从而可以引用到堆中的对象。当one作为返回值赋值给main方法中的对象变量two时,由于是值传递,two也保存了0x666,从而可以访问到堆中的对象

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

  • 定义的位置不同

    1. 局部变量:在方法内部
    2. 成员变量:在方法外部,直接写在类中。类变量只能声明一次,但在类方法中可以出现和类变量同名的局部变量(可以是多个),且方法中局部变量优先
  • 作用范围不一样

    1. 局部变量:只有方法内部可以使用,出了方法就不能用了
    2. 成员变量:整个类全部可以通用
  • 默认值不一样

    1. 局部变量:没有默认值,如果要想使用,必须手动赋值
    2. 成员变量:有默认值,规则和数组一样
  • 内存位置不一样

    1. 局部变量:栈内存
    2. 成员变量:堆内存
  • 生命周期不一样

    1. 局部变量:随着方法进栈而诞生,随着方法出栈而消失
    2. 成员变量:随着对象创建而诞生,随着对象回收而消失

五、构造方法

  • 构造方法是专门用来创建对象的方法,在用new创建对象时,就是在调用构造方法
  • 格式:
    public 类名称(参数列表){
          
          
    	方法体
    }
    
  • 注意:
    • 构造方法的名称必须和所在类的名称完全一样(包括大小写)
    • 构造方法不要写返回值类型(void也不要写),且方法中不能有return
    • 如果没有手动写出构造方法,编译器会隐式定义并使用空构造方法,称为默认构造方法,形如public 类名称(){},它什么也不做
    • 一旦编写了至少一个构造方法,编译器不会生成使用空构造方法了
    • 构造方法可以进行重载

六、一个标准的类(Java Bean)

1. private关键字

  • 使用private关键字修饰的成员成为类的私有成员在本类中可以随意访问,但在类外不能通过示例名,成员名的形式直接访问了

  • 对于每个私有成员变量,定义一对settergetter方法,间接对其进行设置和访问。

    • 通过这种方式,可以对数据进行一些检查和预处理,可以提高代码的安全性
    • 一般将settergetter方法命名为getXxx()setXxx()。对于boolean,其getter方法一般命名为isXxx()
  • 示例

    • 定义一个person类

      package cn.idcast.day04.demo03;
      
      public class Person {
              
              
          String name;
          private int age;    // 私有成员
      
          public void show(){
              
              
              System.out.println("我叫"+name+",年龄"+age);
          }
      
          // 定义一对getter和setter方法,设置/获取私有成员
          public void setAge(int num) {
              
              
              if(num<100 && num>=0)		// 在setter中,可以对数据进行检查
                  age = num;
              else
                  System.out.println("数据不合理");
          }
      
          public int getAge() {
              
              
              return age;
          }
      }
      
    • 在main方法中,通过settergetter处理私有成员变量

      package cn.idcast.day04.demo03;
      
      public class Demo03 {
              
              
          public static void main(String[] args) {
              
              
              Person person = new Person();
      
              person.name = "Bill";
              person.setAge(-20);
              person.show();
          }
      }
      

2. this关键字

  • this是一个对象用来引用自身的引用名,可以引用对象的属性和方法
  • 通过谁调用的方法,谁就是this。比如在main中有Person类实例student,执行student.setAge()调用时,setAge()方法内部的this就指代student(他们保存了同一个堆中Person类对象的地址)
  • this的使用场景
    1. this通常可以省略

      //假设有属性 private radius;
      this.radius = 1;	//等价于radius = 1;
      
    2. 当方法的局部变量和类的成员变量同名时,根据 “就近原则” ,优先使用局部变量。如果要在方法中访问同名的类成员变量,可以通过this.成员变量名的形式

      // 假设有属性 private radius;
      public void setR(int radius)
      {
              
              
      	this.radius = radius;	//this.radius引用了类属性
      }
      
    3. this用来调用同一个类的其他构造方法

      public class Circle
      {
              
              
      	private double r;
      
      	//构造函数1
      	public Circle(double r)	
      	{
              
              
      		this.r = r;
      	}
      	
      	//构造函数2
      	public Circle()		
      	{
              
              
      		this(1.0);	//	这里调用了构造函数1
      		...			// java要求,构造方法中的this(arg-list)调用应在其他可执行语句前出现,所以其他指令...应在这里
      	}
      }
      

      当一个类有多个构造方法时,推荐手动实现参数最多的构造,其他参数少的构造用this调用参数最多的来实现

3. Java Bean

  • 一个标准的类应该包含以下四个部分

    1. 所有成员变量使用private关键字修饰
    2. 每个成员变量编写一对settergetter方法
    3. 编写一个无参数构造方法
    4. 编写一个全参数构造方法
  • 这样的一个标准类称为Java Bean

  • 在IntelliJ IDEA环境中,写完成员变量后,可以通过菜单栏code -> Generate自动生成gettersetter以及构造方法Constructor

七、不可变类

  • 通常创建一个对象,其内容是允许改变的
  • 可以利用 “不可变类” 构造 “不可变对象”其内容不允许改变。例如String类就是不可变的(String相关操作中,都是根据初始化字符串返回新字符串,不能修改String的初始化值)
  • 一个不可变类具有以下特点
    1. 其所有数据域都是private
    2. 没有可以设置数据域的方法(没有setter方法)
    3. 没有返回一个指向数据域的引用的访问器方法

猜你喜欢

转载自blog.csdn.net/wxc971231/article/details/108736962