类和对象详解

在学习类和对象前,我们可以抛出一个问题,什么是面向过程?什么是面向对象?

在学习到类和对象这里时,我们就能java与c语言的区别了,一个是面向对象,一个是面向过程。比如我们在买手机时,我们会关注手机的cpu、内存、电池容量等信息,这样将手机拆分成多个部分去分析,就是面向过程;当你然你朋友去帮你买手机时,你只是说你需要一部手机,而如何挑选手机,是交给你的朋友去完成的,你只是关注于买手机,这就是面向对象

一、类的定义和使用

1.类的定义

class 类名{
    //1.字段/属性/成员变量
    //2.方法
}
 注意事项
 类名注意采用大驼峰定义
 成员前写法统一为 public ,后面会详细解释
 此处写的方法不带 static 关键字 . 后面会了解到

2.类的使用

<1>类中成员变量以及成员方法的定义

我们以定义一个洗衣机类为例:

class WashMachine{
    //字段,属性,成员变量
    public String brand; // 品牌
    public String type; // 型号
    public double weight; // 重量
    public double length; // 长
    public double width; // 宽
    public double height; // 高
    public String color; // 颜色

    //行为,成员方法
    public void washClothes(){ // 洗衣服
        System.out.println("洗衣功能");
    }
    public void dryClothes(){ // 脱水
        System.out.println("脱水功能");
    }
    public void setTime() { // 定时
        System.out.println("定时功能");
    }

}

需要了解的是,类也属于一个类型,它是一个自定义类型,那么,它也可以创建一个变量(对象),代码如下:

class WashMachine{
    int size;

}
public class Main{
    public static void main(String[]args){
        //通过类名创建了一个变量(对象)
        WashMachine washMachine;
    }
}

为了有一个良好的代码习惯,我们一般要做到一个java文件只有一个类,但是这里为了方便,在一个java文件中有多个类。

上面我们知道了如何定义一个类,以及类里面可以有上面内容,那么我们如何使用这个类?

<2>类的使用(类的实例化)

由类生成对象的过程叫做实例化,要使用类里面定义的变量或者方法,就是通过类实例化一个对象来完成的。实例化对象需要使用new关键字,使用方法如下:

class WashMachine{
    int size;

}
public class Main{
    public static void main(String[]args){
        //实例化一个对象的方法
        WashMachine washMachine=new WashMachine();
    }
}

想在我们已经实例化了一个对下,现在我们可以通过这个对象去访问类里面的变量或者方法了,访问的方法是通过对象.变量名或者对象.方法名(),在这之前,我们还需要了解我们在创建对象的时候,内存中是如何体现的:

可以看到,引用指向了数据,因此我们可以上述方法使用成员变量和成员方法,下面我们通过一段代码来了解

class Dog{
   String name;
   int age;
   void bark(){
       System.out.println("汪汪");
   }

}
public class Main{
    public static void main(String[]args){
        //通过类名创建了一个变量(对象)
        Dog dog=new Dog();
        //对成员变量赋值
        dog.age=18;
        dog.name="花花";
        //调用成员方法
        dog.bark();
        System.out.println("名字"+dog.name+"年龄"+dog.age);
    }
}

运行结果如下: 

 3.类和对象的说明

    1. 类只是 一个 模型 一样的东西,用来对一个实体进行描述,限定了类有哪些成员 .
    2. 类是一种自定义的类型 ,可以用来定义变量 .
    3. 一个类可以实例化出多个对象, 实例化出的对象 占用实际的物理空间,存储类成员变量
    4. 做个比方。 类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图 ,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间。

二、this引用

1.this关键字的引入

我们先看一段代码:

public class Date {
    public int year;
    public int month;
    public int day;

    public void setDay(int y, int m, int d) {
        year = y;
        month = m;
        day = d;
    }

    public void printDate() {
        System.out.println(year + "/" + month + "/" + day);
    }

    public static void main(String[] args) {
// 构造三个日期类型的对象 d1 d2 d3
        Date d1 = new Date();
        Date d2 = new Date();
        Date d3 = new Date();
// 对d1,d2,d3的日期设置
        d1.setDay(2020, 9, 15);
        d2.setDay(2020, 9, 16);
        d3.setDay(2020, 9, 17);
// 打印日期中的内容
        d1.printDate();
        d2.printDate();
        d3.printDate();
    }
}

可以看到,在上述代码中创建了三个对象,每个对象都调用了setday方法,没有任何问题,但是有两个疑问,

<1>.当形参名与成员变量名相同时

public void setDay(int year, int month, int day){
year = year;
month = month;
day = day;//究竟是谁对谁赋值?
}

<2>.三个对象都在调用setDateprintDate函数,但是这两个函数中没有任何有关对象的说明,那这两个方法如何确定打印的是哪个对象的数据呢? 

为了避免以上两种情况,我们可以使用this关键字解决

2.this关键字的使用

this关键字有一个重要内涵,它表示的是当前对象的引用,前面我们知道,访问成员变量和成员方法可以使用对象.成员变量/成员方法()的方式,this也是引用,那么它的使用方法也是类似,即this.成员变量/成员方法()。即:

public void setDay(int year, int month, int day){
this.year = year;
this.month = month;
this.day = day;
}

 现在我们通过this来访问成员方法和成员变量,它表示该变量是当前对象的成员变量/成员方法

3. 如何知道this表示的是当前对象的引用

其实非常简单,我们只需要通过调试,看一看this和当前所创建的对象是否相同即可!


我们可以看到,this的值和当前我们创建的对d1的哈希值相同,这就证明this表示的就是当前对象的引用,因此也就同样可以使用this.成员变量/成员方法()来访问。 

4.成员方法中隐藏的this

前面我们说到了不使用关键字this是存在的另一个问题:三个对象都在调用setDateprintDate函数,但是这两个函数中没有任何有关对象的说明,那这两个方法如何确定打印的是哪个对象的数据呢? 其实答案很简单,每一个成员方法的形参列表中都包含了一个隐藏的this,用来接收对象的地址。我们可以通过下面的代码来观察:

5.通过this访问当前对象的其它构造方法

6.this引用的特性

1. this 的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型
2. this 只能在 " 成员方法 " 中使用
3. " 成员方法 " 中, this 只能引用当前对象,不能再引用其他对象

三、对象的构造及初始化

1.对象的初始化

我们知道,对于局部变量,必须在创建的时候初始化,否则,编译器会报错,如

public static void main(String[] args) {
int a;
System.out.println(a);
}
// Error局部变量没有初始化

那对于一个对象呢?

public static void main(String[] args) {
Date d = new Date();
d.printDate();
d.setDate(2021,6,9);
d.printDate();
}
//可以通过编译

当我们要对对象初始化时,需要调用setDate方法,但是我们会发现

1. 每次对象创建好后调用 SetDate 方法设置具体日期,比较麻烦,那对象该如何初始化?
2. 局部变量必须要初始化才能使用,为什么字段声明之后没有给值依然可以使用?
因此,我们引入了构造方法。

2.构造方法

1.概念

构造方法是一种特殊的成员方法,名字必须与类名相同,在创建对象时编译器会自动调用构造方法,并且在整个对象的生命周期中只调用一次

public class Date {
    public int year;
    public int month;
    public int day;

    // 构造方法:
// 名字与类名相同,没有返回值类型,设置为void也不行
// 一般情况下使用public修饰
// 在创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次
    public Date(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
        System.out.println("Date(int,int,int)方法被调用了");
    }

    public void printDate() {
        System.out.println(year + "-" + month + "-" + day);
    }

    public static void main(String[] args) {
// 此处创建了一个Date类型的对象,并没有显式调用构造方法
        Date d = new Date(2021, 6, 9); // 输出Date(int,int,int)方法被调用了
        d.printDate(); // 2021-6-9
    }
}

!!!构造方法只是给对象初始化,并不给对象开辟空间 

2.构造方法的特性
1. 名字必须与类名相同
2. 没有返回值类型,设置为 void 也不行
3. 创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次 ( 相当于人的出生,每个人只能出生一次 )
4. 构造方法可以重载 ( 用户根据自己的需求提供不同参数的构造方法 )
public class Date {
    public int year;
    public int month;
    public int day;
    // 无参构造方法
    public Date(){
        this.year = 1900;
        this.month = 1;
        this.day = 1;
    }
    // 带有三个参数的构造方法
    public Date(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }
    public void printDate(){
        System.out.println(year + "-" + month + "-" + day);
    }
    public static void main(String[] args) {
        Date d = new Date();
        d.printDate();
    }
}

 在上面的两个构造方法中,方法名相同,返回值相同(没有),参数列表不同,构成了方法的重载

!!!如果没有定义构造方法,那么系统会自动提供一个无参的构造方法

3.通过this简化构造方法(同目录 二、5.)
public class Date {
    public int year;
    public int month;
    public int day;
    // 无参构造方法--内部给各个成员赋值初始值,该部分功能与三个参数的构造方法重复
// 此处可以在无参构造方法中通过this调用带有三个参数的构造方法
// 但是this(1900,1,1);必须是构造方法中第一条语句
    public Date(){
//System.out.println(year); 注释取消掉,编译会失败
        this(1900, 1, 1);
//this.year = 1900;
//this.month = 1;
//this.day = 1;
    }
    // 带有三个参数的构造方法
    public Date(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }
}

注意:在使用this简化构造方法时,不可以成环,即在一个构造方法中使用this调用另一个构造方法,又在另一个构造方法中通过this调用上一个构造方法。如:

 public Date(){
        this(2021,1,1);
    }
    public Date(int year, int month, int day) {
        this();
        this.year = year;
        this.month = month;
        this.day = day;
    }
}

this在当前对象中调用其它构造方法时,必须放在构造方法的第一句!!!

3.默认初始化

我们回到上文的问题,为什么局部变量需要初始化,而成员变量可以不初始化呢?

public class Date {
    public int year;
    public int month;
    public int day;
    public Date(int year, int month, int day) {
// 成员变量在定义时,并没有给初始值, 为什么就可以使用呢?
        System.out.println(this.year);
        System.out.println(this.month);
        System.out.println(this.day);
    }
    public static void main(String[] args) {
// 此处a没有初始化,编译时报错:
// Error:(24, 28) java: 可能尚未初始化变量a
// int a;
// System.out.println(a);
        Date d = new Date(2021,6,9);
    }

要搞清楚这个问题,我们需要了解我们在通过new关键字创建对象时,发生的一些事情: 

在程序层面只是简单的一条语句,在 JVM 层面需要做好多事情,下面简单介绍下:
1. 检测对象对应的类是否加载了,如果没有加载则加载
2. 为对象分配内存空间
3. 处理并发安全问题
比如:多个线程同时申请对象, JVM 要保证给对象分配的空间不冲突
4.初始化所分配的内存空间
即:对象空间被申请好之后,对象中包含的成员已经设置好了初始值
5. 设置对象头信息 ( 关于对象内存模型后面会介绍 )
6. 调用构造方法,给对象中各个成员赋值

四、封装

1.封装的概念

面向对象的程序有三大特性:封装、继承和多态。在类和对象中,研究的就是封装。引入一个生活上的例子,当我们购买一台电脑时,我们能看到的是整台电脑,它的电池、内存条、cpu等我们是无法看到的,也就是说,把这些零件封装起来,就形成了一台电脑,但是,电脑也给我们留下了一些接口可以用来访问电脑内部(如usb接口等)。

对于Java,我们需要先了解一些访问权限修饰符:

在java中,是通过类来实现封装的,我们使用private关键字对类的成员变量或者成员方法进行修饰,把它们打包成一个类,再提供一些公开的接口,就实现了对类的封装。

简单来说,封装就是对外隐藏类内部的实现细节

public class Computer {
    private String cpu; // cpu
    private String memory; // 内存
    public String screen; // 屏幕
    String brand; // 品牌---->default属性
    //公开的接口,可以用来访问private修饰的成员变量
    public Computer(String brand, String cpu, String memory, String screen) {
        this.brand = brand;
        this.cpu = cpu;
        this.memory = memory;
        this.screen = screen;
    }
    public void Boot(){
        System.out.println("开机~~~");
    }
    public void PowerOff(){
        System.out.println("关机~~~");
    }
    public void SurfInternet(){
        System.out.println("上网~~~");
    }
}
public class TestComputer {
    public static void main(String[] args) {
        Computer p = new Computer("HW", "i7", "8G", "13*14");
        System.out.println(p.brand); // default属性:只能被本包中类访问
        System.out.println(p.screen); // public属性: 可以任何其他类访问
        // System.out.println(p.cpu); // private属性:只能在Computer类中访问,不能被其他类访问
    }
}

2.封装扩展— —包

1.包的概念
在面向对象体系中,提出了一个软件包的概念,即: 为了更好的管理类,把多个类收集在一起成为一组,称为软件 。有点类似于目录。比如:为了更好的管理电脑中的歌曲,一种好的方式就是将相同属性的歌曲放在相同文件下,也可以对某个文件夹下的音乐进行更详细的分类, Java 中也引入了包, 包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式 ,比如一
个包中的类不想被其他包中的类使用。包还有一个重要的作用: 在同一个工程中允许存在相同名称的类,只要处在 不同的包中即可。(包的本质是一个文件夹)
2.导入包中的类

java中提供了很多现成的类给我们使用,比如导入Date类,可以使用java.util.Date导入java.util这个包中的Date类:

public class Test {
    public static void main(String[] args) {
        java.util.Date date = new java.util.Date();
// 得到一个毫秒级别的时间戳
        System.out.println(date.getTime());
    }
}

但是这个写法非常麻烦,如果我们后面要实例化多个Data类的话,就要写很多次重复的java.util.Date,对此,我们可以使用import语句导入一个包。如

import java.util.Date;
public class Test {
    public static void main(String[] args) {
        Date date = new Date();
// 得到一个毫秒级别的时间戳
        System.out.println(date.getTime());
    }
}

如果需要导入java.util的其他类,可以使用java.util.*(通配符)(这样就可以不用写具体要导入的类了)

import java.util.*;
public class Test {
    public static void main(String[] args) {
        Date date = new Date();
// 得到一个毫秒级别的时间戳
        System.out.println(date.getTime());
    }

但是,不建议使用通配符,还是建议导入一个具体的类,否则会导致一些问题,比如要导入两个包,但是这两个包中都有相同的类,这个时候我们在使用这个类时,编译器会报错

import java.util.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
// util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 编译出错
Date date = new Date();
System.out.println(date.getTime());
}
}
// 编译出错
Error:(5, 9) java: 对Date的引用不明确
java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.Date 都匹配

java.util和java.sql两个包中都有Data类,这就会导致我们在实例化Data时编译器不知道我们导入的是哪个包中的类而出现报错。

我们还可以使用import static来导入一个包,这样的优点是可以不用实例化一个对象,可以直接使用包中的方法或者字段。如

import static java.lang.Math.*;
public class Test {
    public static void main(String[] args) {
        double x = 30;
        double y = 40;
// 静态导入的方式写起来更方便一些.
// double result = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
        double result = sqrt(pow(x, 2) + pow(y, 2));
        System.out.println(result);
    }
}
3.自定义包
   基本规则
    在文件的最上方加上一个 package 语句指定该代码在哪个包中.
   包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.bit.demo1 ) .
   自定义包的方法

 

在输入框中写入自定义包的名称即可创建一个包。

五、static成员 

1.static关键字的引入

我们以学生类为例(同一个班级):

public class Date{
    public static void main(String [] args){
        Student student1 = new Student(12,"小米","123","1班");
        Student student2 = new Student(12,"小黄","134","1班");
        Student student3 = new Student(13,"小刘","167","1班");

    }
}
class Student{
    int age;
    String name;
    String number;
    String className;

    public Student(int age, String name, String number, String className) {
        this.age = age;
        this.name = name;
        this.number = number;
        this.className = className;
    }
}

通过观察,上述代码并没有什么问题,但是仔细看看我们发现,既然student1,student2,studnt3都是同一个班的学生,那我们是否可以使用某种方法可以让className只赋值一次,并且在后面创建的对象都能直接访问它呢?

2.static修饰成员变量

对于上面Student类的问题,我们引入关键字static可以解决,我们可以使用static修饰成员变量和成员方法,被修饰的成员变量或者成员方法就不在需要通过对象来访问了,可以直接通过类名来访问(在同一个类中则可以直接访问),并且static修饰的成员变量或成员方法只有一份,存在方法区中。

public class Date{
    public static void main(String [] args){
        Student student1 = new Student(12,"小米","123");
        Student student2 = new Student(12,"小黄","134");
        Student student3 = new Student(13,"小刘","167");
        student3.className="1班";//可以通过对象来访问,但是不建议,因为className属于类,已经不依赖于对象来访问了
        Student.className="1班";//这是最准确的访问方式,通过类名来访问

    }
}
class Student{
    int age;
    String name;
    String number;
    //static修饰className
    static String className="1班";

    public Student(int age, String name, String number) {
        this.age = age;
        this.name = name;
        this.number = number;
    }
}
【静态成员变量特性】
1. 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中
2. 既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问
3. 类变量存储在方法区当中
4. 生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁)

3.static修饰成员方法

一般类中的数据成员都设置为 private ,而成员方法设置为 public ,那设置之后, Student 类中 classRoom 属性如何 在类外访问呢?
public class Student{
    private String name;
    private String gender;
    private int age;
    private double score;
    private static String classRoom = "Bit306";
// ...
}
public class TestStudent {
    public static void main(String[] args) {
        System.out.println(Student.classRoom);
    }
}
//编译失败:Error:(10, 35) java: classRoom 在 extend01.Student 中是 private 访问控制
Java 中, 被static修饰的成员方法称为静态成员方法,是类的方法,不是某个对象所特有的,静态成员一般是通过静态方法来访问的
public class Student{
    // ...
    private static String classRoom = "Bit306";
    // ...
    public static String getClassRoom(){
        return classRoom;
    }
}
public class TestStudent {
    public static void main(String[] args) {
        System.out.println(Student.getClassRoom());
    }
}

4.使用static修饰成员方法的注意事项

1. 不属于某个具体的对象,是类方法
2. 可以通过对象调用,也可以通过类名.静态方法名(...)方式调用,更推荐使用后者

3. 不能在静态方法中访问任何非静态成员变量

public static String getClassRoom(){
        System.out.println(this);
        return classRoom;
        }
// 编译失败:Error:(35, 28) java: 无法从静态上下文中引用非静态 变量 this
public static String getClassRoom(){
        age += 1;
        return classRoom;
        }
// 编译失败:Error:(35, 9) java: 无法从静态上下文中引非静态变量age

其实理解起来非常简单,因为静态的是不依赖于对象的,如果在静态方法中使用了任何非静态的成员变量或者成员方法,都违背了不依赖于对象这一特性,因为一旦使用了非静态成员变量或成员方法,都需要通过实例化一个对象来访问

因此,在非静态方法中可以调用静态方法,在静态方法中不能使用任何非静态的变量或方法,如果一定要使用,只能通过对象.成员变量/成员方法() 来使用。

5.static成员变量的初始化

1.就地初始化

即在类中创建静态成员变量是顺带赋值

2.通过get/set方法初始化
3.通过构造方法初始化
4.静态代码块初始化(下面提到)

6.静态代码块

静态代码块即static修饰的代码块,即

static{
    
        }

静态代码块一般用来初始化静态成员变量

​
public class Test{
    public static void main(String[] args){
          Student student=new Student(18,"小","5班");
          student.age=18;
          Student.className="1bang";

    }
}
class Student{
    int age;
    String name;
    static String className;
    String number;
//构造代码块,又叫示例代码块,在创建对象时才会调用
    {  
        
        System.out.println("实例代码块被调用了。。。。");
    }

    //static代码块,当类加载的时候调用,并且只会被调用一次
    static{
       System.out.println("静态代码块被调用了。。。。。");
    }

    public Student(int age, String name, String number) {
        this.age = age;
        this.name = name;
        this.number = number;
        System.out.println("构造方法被调用了......");
    }
}

​

可以看到,它们执行的先后顺序。

六、对象的打印

public class Person {
    String name;
    String gender;
    int age;
    public Person(String name, String gender, int age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }
    public static void main(String[] args) {
        Person person = new Person("Jim","男", 18);
        System.out.println(person);
    }
}
// 打印结果:day20210829.Person@1b6d3586

由于对象名是一个引用,所以打印的时一个引用,那么如何通过对象名打印对象中的属性呢?只需要重写toString方法即可。

public class Person {
    String name;
    String gender;
    int age;

    public Person(String name, String gender, int age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }
//对toString方法进行重写,打印结果为对象的成员
    @Override
    public String toString() {
        return "[" + name + "," + gender + "," + age + "]";
    }

    public static void main(String[] args) {
        Person person = new Person("Jim", "男", 18);
        System.out.println(person);
    }
}

class Test{
    public static void main(String[] args){
        Person person=new Person("小于","哈哈",18);
        System.out.println(person);//直接用对象名打印,打印结果为哈希值
   
    }
}

为什么重写之后就可以打印对象中的成员了?看下图:

 

 可以看到,println方法里有一个valueof方法,valueof方法里又有一个toString方法,因此我们对toString方法重写可以达到打印对象成员的目的。

  • 21
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值