Java中类和对象

面向对象的初步认知

什么是面向对象

Java是一门纯面向对象的语言(Object Oriented Program,简称OOP),在面向对象的世界里,一切皆为对象。面向对象是解决问题的一种思想,主要依靠对象之间的交互完成一件事情。用面向对象的思想来设计程序,更符合人们对事物的认知,对于大型程序的设计、扩展以及维护都非常友好。

类的定义与使用

面相对象的程序设计关注的是对象,而对象是现实生活中的实体,比如:洗衣机。但是洗衣机这个东西计算机并不认识,需要开发人员告诉给计算机什么是洗衣机。

产品品牌

樱花

产品型号

XPB150-150S

洗涤功率

540W

脱水功率

250W

洗涤容量

15KG

脱水容量

6.8KG

洗涤模式

半自动

内桶材质

PP环保塑料

产品净重

30KG

主机尺寸

850X550X1000mm

类的定义格式

在java中定义类时需要用到class关键字,具体语法如下

// 创建类

class ClassName{

//字段(属性) 或者 成员变量

method; // 行为 或者 成员方法

field;

class为定义类的关键字,ClassName为类的名字,0中为类的主体。

这里规定以后对类进行定义的时候类名采用大驼峰.

总得来说,类和我们在C语言中学习的结构体比较像

class是定义类时候的关键字.

public是一个权限,具体是什么意思我们之后会讲.


属性/字段/成员变量:定义在类当中,但是在方法的外面
方法

注意事项

  • 类名采用大驼峰定义,方法名称都是小驼峰

  • 成员前写法统一为public

  • 此处写的方法不带static关键字

所谓大驼峰:就是名字每个单词的首字母都要大写.

所谓小驼峰:就是第一个单词的首字母不大写,但是第二个单词的首字母要大写

注意事项

  1. 一般一个文件当中只定义一个类

  1. public修饰的类必须要和文件名相同

  1. main方法所在的类一般要使用public修饰(注意:Eclipse默认会在public修饰的类中找main方法)

  1. 不要轻易的去修改public修饰的类的名称,如果要修改,通过开发者工具去修改

与C语言不同之处

在IDEA当中,可以有不止一个的main方法,你鼠标点击哪一个播放按钮,IDEA就帮你执行哪一个main方法.

为什么呢?

在Java中:类+方法是一个组合,那么你说你有多个类,那就可以有多个main方法。

类的实例化

什么是实例化

定义了一个类,就相当于在计算机中定义了一种新的类型,与int, double类似,只不过int和double是java语言自带的内置类型,而类是用户自定义了一个新的类型,比如上述的DogTest类。它们都是类(一种新定义的类型)有了这些自定义的类型之后,就可以使用这些类来定义实例(或者称为对象)。用类类型创建对象的过程,称为类的实例化,在java中采用new关键字,配合类名来实例化对象。

此时 washMachine 这个引用变量存的就是对象的地址.

public static void main(String[] args) {
    DogTest dog1=new DogTest();
    DogTest dog2=new DogTest();
    dog1.name="cj";
    dog1.wow();
    System.out.println("cj");
}

从上面的这几行代码我们能得出什么结论

结论:可以使用new关键字实例化多个对象!.

注意事项

  • new 关键字用于创建一个对象的实例.

  • 使用 . 来访问对象中的属性和方法.

  • 同一个类可以创建对个实例.

类和对象的说明

  1. 类只是一个模型一样的东西,用来对一个实体进行描述,限定了类有哪些成员.

  1. 是一种自定义的类型,可以用来定义变量.

  1. 一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量

  1. 做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间

this引用

为什么要有this引用?

首先我们来写一个日历类、


public class Test {
    public int year;
    public int mouth;
    public int day;

    public void get(int y,int m,int d){
        year=y;
        mouth=m;
        day=d;
    }
 public void print(){
     System.out.println("年:"+year+"月:"+mouth+"日:"+day);
 }
    public static void main(String[] args) {
        Test test1=new Test();
        Test test2=new Test();
        Test test3=new Test();
        test1.get(2222,1,18);
        test2.get(2020,1,18);
        test3.get(2032,1,18);
        test1.print();
        test2.print();
        test3.print();

    }
}

这是它的一个运行结果

那么此时我做一个小小的改动


public class Test {
    public int year;
    public int mouth;
    public int day;

    public void get(int year,int mouth,int day){
        year=year;//我将这里的形参的名字和成员属性名改成一样的
        mouth=mouth;
        day=day;
    }
 public void print(){
     System.out.println("年:"+year+"月:"+mouth+"日:"+day);
 }
    public static void main(String[] args) {
        Test test1=new Test();
        Test test2=new Test();
        Test test3=new Test();
        test1.get(2222,1,18);
        test2.get(2020,1,18);
        test3.get(2032,1,18);
        test1.print();
        test2.print();
        test3.print();

    }
}

此时,代码运行结果如下

为什么?

根据局部变量优先的原则,这里就相当于是局部变量给局部变量赋值,而我们在下方打印的时候给的参数是成员属性的year、mouth、day.

那么,我们该如何强调成员属性名和局部变量的名字呢?

什么是this引用?


public class Test {
    public int year;
    public int mouth;
    public int day;

    public void get(int year,int mouth,int day){
        this.year=year;
        this.mouth=mouth;
        this.day=day;
    }
 public void print(){
     System.out.println("年:"+this.year+"月:"+this.mouth+"日:"+this.day);
 }
    public static void main(String[] args) {
        Test test1=new Test();
        Test test2=new Test();
        Test test3=new Test();
        test1.get(2222,1,18);
        test2.get(2020,1,18);
        test3.get(2032,1,18);
        test1.print();
        test2.print();
        test3.print();

    }

此时我们就用到了this引用.

this引用指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有成员变量的操作,都是通过该引用去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。

this引用的特性

  1. this的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型

  1. this只能在"成员方法"中使用

  1. 在"成员方法"中, this只能引用当前对象,不能再引用其他对象

  1. this是"成员方法"第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法对象的引用传递给该成员方法,this负责来接收.

框起来的就是编译器帮我们隐藏的变量

好,那我发现了一个问题,我们知道,在没有给局部变量初始化的时候就使用局部变量编译器是会报错的,那么

我们在使用的时候也没有给成员变量初始化呀?那为什么它就被初始化为0了呢?

对象的构造及初始化

如何初始化对象

说是初始化对象,实际上就是初始化成员变量.

我们可以像这样给它进行初始化

构造方法

概念

构造方法: 方法名 (参数列表){方法体;}没有返回值的方法,并且方法名必须和类名保持一致!!!我们普通的方法:返回值 方法名 (参数列表) {方法体;}

当然,构造方法也可以不止一个


public class Test {
    //public int year;
    public int year=1990;
    public int mouth;
    public int day;

    public Test(){
        System.out.println("没有参数");
    }
    public Test(int year,int mouth,int day){
        System.out.println("有参数的构造方法");
    }

    public void get(Test this,int year,int mouth,int day){
        this.year=year;
        this.mouth=mouth;
        this.day=day;
    }
 public void print(Test this){
     System.out.println("年:"+year+"月:"+mouth+"日:"+day);
 }
    public static void main(String[] args) {
        Test test1=new Test();
        Test test2=new Test();
        Test test3=new Test();
       /* test1.get(2222,1,18);
        test2.get(2020,1,18);
        test3.get(2032,1,18);
        test1.print();
        test2.print();
        test3.print();*/

    }
}

我在这里分别定义了两个不同的构造方法,而且这两个构造方法构成了我们之前说的重载的现象.

以上代码执行结果如下

但是我们一般的调用不都是要用 . 来调用吗?

但是我们这里并没有用到 . 那么它究竟是怎么调用的呢?

我们在这里打了一个断点,我们可以看到,我们是通过:

Test test1 = new Test();

来直接调用构造方法的

但问题又来了,我们在没有说到构造方法之前写的类都是不带构造方法的,那么编译器为什么不报错呢?


public class Test {
    //public int year;
    public int year=1990;
    public int mouth;
    public int day;

    /*public Test(){
        System.out.println("没有参数");
    }*/
    public Test()
    {
        
    }

在我们没有写构造方法的时候,Java会帮我们写一个如上图一样的不带参数的构造方法.

但如果你这样写的话编译器就会报错,为什么?Java不是会帮我们写一个构造方法吗?

注意:在这里你是写了一个带参数的构造方法的,所以

你将参数给带上,它就不会报错了

结论:你若是没有构造方法,那么Java会帮你写一个没有参数的构造方法,你若是有,那一个都不帮你写.

你也可以同时调用带参数的构造方法和不带参数的构造方法

总结一下:

  1. 为对象分配内存

  1. 调用合适的构造方法.

那要是我们需要很多的不同的构造方法的话,那岂不是我们一个一个写就会很麻烦?

其实IDEA可以帮你写你想要的构造方法的

鼠标右键

(刚刚心情好换了一张壁纸)

选择你要的构造方法的参数

(按住Ctrl键可以多选)

这样IDEA就会帮我们自动生成一个了.

注意:构造方法的作用就是对对象中的成员进行初始化,并不负责给对象开辟空间.

特性

  1. 名字必须与类名相同

  1. 没有返回值类型,设置为void也不行

  1. 创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次(相当于人的出生,每个人只能出生一次)

  1. 构造方法可以重载(用户根据自己的需求提供不同参数的构造方法)

  1. 如果用户没有显示定义,那么编译器会默认生成一个不带参数的构造方法

  1. 一但用户定义,编译器则不会生成

但this可以做到的只有这些吗?

当然不是

  1. 我们可以用this在构造方法中调用其他的构造方法


class Student {
    public String name;
    public int age;

    public Student() {
        /*this.name="alex";
        this.age=20;*/
        this("alex",20);
        System.out.println("这是一个不带参数的构造方法");
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("这是带2个参数的构造方法");
    }

/*public Student(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("这是带2个参数的构造方法");
    }*/

    public void print() {
        System.out.println("姓名: " + this.name + " 年龄: " + this.age);
    }
}

public class Test {
    public static void main(String[] args) {
        Student student1=new Student();
        student1.print();
    }
    }

这是上面这段代码的运行过程,在这里我们就用了this在不带参数的构造方法中调用了带参数的构造方法。

运行结果如下

请注意:

  • 用蓝色框框起来的部分,在调用的时候,参数的类型和数量务必保持一致.

  • this在构造方法中调用其他构造方法的时候请务必放在构造方法的第一条语句.

  • 若要通过this调用构造方法,则this只能在构造方法里面去调用构造方法

  • 通过this不能自己调用自己

  • this还可以在普通方法中调用普通方法

例:

此时this就在构造方法中的第三条语句,此时我们运行代码

在普通的方法中调用他会报错

不能自己调用自己

this可以在普通方法中去调用普通方法


class Student {
    public String name;
    public int age;

    public Student() {
        /*this.name="alex";
        this.age=20;*/
        this("alex",20);
        System.out.println("这是一个不带参数的构造方法");
    }

    public Student(String name, int age) {
        //this("liming",21);
        this.name = name;
        this.age = age;
        System.out.println("这是带2个参数的构造方法");
    }

/*public Student(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("这是带2个参数的构造方法");
    }*/

    public void print() {
        //this("ad",20);
        System.out.println("姓名: " + this.name + " 年龄: " + this.age);
        this.doclass();
    }

    public void doclass() {
        System.out.println(this.name + " " + this.age + "岁 在上课");
    }
}

public class Test {
    public static void main(String[] args) {
        Student student1=new Student();
        student1.print();
    }
    }

且在普通方法中去调用普通方法时this可以不用放在方法的第一条语句.

运行结果如下

  1. 不能形成环



public Date()
this(1900,1,1);
public Date(int year, int month, int day)
this();
  1. 大多数情况下使用public来修饰

默认初始化


class Student {
    public String name;
    public int age;

    public Student() {
        System.out.println("这是一个不带参数的构造方法");
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("这是带2个参数的构造方法");
    }
    public void print() {
        //this("ad",20);
        System.out.println("姓名: " + this.name + " 年龄: " + this.age);
        this.doclass();
    }

    public void doclass() {
        System.out.println(this.name + " " + this.age + "岁 在上课");
    }
}

public class Test {
    public static void main(String[] args) {
        Student student1=new Student();
        student1.print();
    }
    }

现在我并没有对我的name和age进行初始化,我们让代码跑一下看看效果.

为什么会是null和0呢?


student2=null;//student2 不指向任何对象

成员变量在没有初始化的时候都有一个默认值.

数据类型

默认值

byte

0

char

'\u0000'

short

0

int

0

long

0L

float

0.0f

double

0.0

boolean

false

reference(引用类型)

null

就地初始化

字面意思,直接在定义的时候就赋值.但是这种初始化很没用的.

封装

封装的概念

面向对象程序三大特性:封装、继承、多态。而类和对象阶段,主要研究的就是封装特性。何为封装呢?简单来说就是套壳屏蔽细节

比如:

对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的, CPU内部是如何设计的等,用户只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。因此计算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等,让用户可以与计算机进行交互即可。封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互.

那么现在我有这样的一个需求:我不想让类外看到我类里面的实现细节


class Student {
    public String name;
    public int age;

    public Student() {
        System.out.println("这是一个不带参数的构造方法");
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("这是带2个参数的构造方法");
    }

    public void print() {
        //this("ad",20);
        System.out.println("姓名: " + this.name + " 年龄: " + this.age);
        this.doclass();
    }

    public void doclass() {
        System.out.println(this.name + " " + this.age + "岁 在上课");
    }
}

public class Test {
    public static void main(String[] args) {
        Student student=new Student();
        student.name="xiaoming";//这里name与下一行的age都是类里面的东西
        student.age=18;//现在我想把它们隐藏掉而且不影响这段代码本来的功能,该怎么做?
        student.print();
    }
}

要实现这个功能,首先我们将public改为private

在Java中要实现这个功能,在语法层次上,只要使用private关键字就可以了.

此时你在到这个类外去访问的时候

不好意思,报错了!

此时你就不能在类外去访问这两个成员变量了.

被private修饰的变量只能在当前类当中使用.

也就是上图我选中的蓝色区域内才能使用

那么在这里:public和private是什么东西呢?

访问修饰限定符

Java中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用。Java中提供了四种访问限定符:

No

范围

private

default

(什么都不写的时候)

protected

public

1

同一包中的同一类

2

同一包中的不同类

3

不同包中的子类

4

不同包中的非子类

public:可以理解为一个人的外貌特征,谁都可以看到

default:对于自己家族中(同一个包中)不是什么秘密,对于其他人来说就是隐私了

private:只有自己知道,其他人都不知道

说明】

  • protected主要是用在继承中,继承部分详细介绍

  • default权限指:什么都不写时的默认权限

  • 访问权限除了可以限定类中成员的可见性,也可以控制类的可见性

从上图中我们可以看到,用private修饰的cpu不可以在Computer这个类之外的TestComputer这个类中使用,但是用public修饰的screen是可以在Computer这个类之外被使用的.

OK,说了这么多,那么“包”到底是个什么东西呢?

封装扩展之包

包的概念

在面向对象体系中,提出了一个软件包的概念,即:为了更好的管理类,把多个类收集在一起成为一组,称为软件包。有点类似于目录。比如:为了更好的管理电脑中的学习资料,一种好的方式就是将相同科目的学习资料放在相同文件下,也可以对某个文件夹下的学习资料进行更详细的分类。

在Java中也引入了包,包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式,比如:一个包中的类不想被其他包中的类使用。包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在不同的包中即可。

导入包中的类

Java中已经提供了很多现成的类供我们使用,例如Arrays类:可以使用java.util.Arrays导入java.util这个包中的Arrays类.

第一种导包方法

第二种导包方法:用import导包

总之和C语言中的#include一样,你要用就要提前导包

此时如果你想用Date

需要导入java.util.Date这个包

但是你发现Arrays与Date是在一个包下面的不同类,那么此时你就可以这样写

这样也是不会报错的

在这里,*是一个通配符,它可以充当任何类

但是请注意:*不是导入java.util这个包中的所有类,而是你用哪个类我就帮你导入哪个类.

好的,那么仔细回想一下我们其实刚刚在导包的时候遇到了一个很有意思的想象

这两个就是不同包中的类名相同的两个不同的类

我们来看一下接下来的操作

你的*不是通配符么,现在我既要使用util包中的Data也要使用sql包中的类,那我这样写可不可以?

不可以,编译器报错了,你成功的将编译器整不会了,此时编译器已经不知道你在这里究竟要用哪一个包下面的Date了

此时你就必须用这种办法来导包来说明你用的是哪一个包下面的类

你这样写编译器还是分不清你这两个究竟分别要用哪一个包,当然了,编译器也不是那么笨的,当你在使用了属于java.util包中的Date时,如果你还要用属于java.sql这个包中的Date,那么

编译器会自动帮你写成这种格式的

请注意:以上代码之所以报错,是因为我还没学过处于sql包下的Data该怎么用,所以没给他传正确的值所导致的,此处的报错并不是导包错误所引起的.

结论:

java.util.*

这种使用通配符所导包的做法,非必要不使用.

第三种导包的方法:用import static导包

如果你要计算如上图所示的这样的一个值,你就要用到处于java.long.Math这个类中的很多东西

上图是java.long.Math这个类,我们可以看到,这个类的每一个定义基本上都是由public static所修饰的,那么我们可以这样做

此时你的代码就会得到简化

但是由于这种导包比较冷门,所以不建议使用这种导包方式.

注意事项: import和C++的#include差别很大.C++必须#include 来引入其他文件内容,但是Java不需要.import 只是为了写代码的时候更方便. import 更类似于C++的 namespace 和 using

自定义包

基本规则

  • 在文件的最上方加上一个 package 语句指定该代码在哪个包中.

  • 包名需要尽量指定成唯一的名字,通常会用公司的域名的颠倒形式(例如 com.baidu.demo1 ).

  • 包名要和代码路径相匹配.例如创建com.baidu.demo1的包,那么会存在一个对应的路径com/baidu/demo1来存储代码.

  • 如果一个类没有 package 语句,则该类被放到一个默认包中.

  • 一般包名都是要全部小写的

操作步骤

IDEA->scr->鼠标右键->new->package

创建好之后是这个样子的,那么在我们的代码的路径下同样也会创建几个一模一样的文件夹用来保存我们在这个包当中写的类

package声明当前Java文件在哪一个包当中.

此时就可以导包了

包访问权限

我现在在com包下又创建了一个叫做baidu2的包,在这个包中我创建了Testbaidu2这个类,这个类当中我是这样写的

现在我想在baidu这个包中的Testbaidu这个类当中调用在baidu2这个包中的Testbaidu2这个类当中的name(什么都不写,默认权限为default)

报错了,但是如果你在baidu2中Testbaidu2这个类当中的name前面加上public

不报错了

我们分析一下现在的情况

现在我给name前面加上了public,即使Testbaidu2和Testbaidu不在同一个包中,也是可以访问的,但是一但我将public删除,在Testbaidu这个类中就不能访问Testbaudu2中的name了,这就是访问权限

现在我将public删除并且在baidu2这个包中新建一个名叫Test的类,我同样在Test中调用Testbaidu2这个类当中的name

不报错!为什么呢?

这就叫做包访问权限(default,也就是你前面什么也不写的时候),只有在同一个包中的类才可以相互使用(除了用private修饰)你若是要在其他的包中访问,不用public修饰你是访问不了的.

常见的包

  1. java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。

  1. java.lang.reflect:java反射编程包;

  1. java.net:进行网络编程开发包.

  1. java.sql:进行数据库开发的支持包。

  1. java.util:是java提供的工具程序包。(集合类等)非常重要

  1. java.io:l/O编程开发包。

现在让我们回到梦开始的地方


class Student {
    private String name;
    private int age;

    public Student() {
        System.out.println("这是一个不带参数的构造方法");
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("这是带2个参数的构造方法");
    }

    public void setAge(int age) {
        this.age = age;
    }
    public String getName(){
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge(){
        return this.age;
    }
    public void print() {
        //this("ad",20);
        System.out.println("姓名: " + this.name + " 年龄: " + this.age);
        this.doclass();
    }

    public void doclass() {
        System.out.println(this.name + " " + this.age + "岁 在上课");
    }
}

public class Test {
    public static void main(String[] args) {
        Student student =new Student();
        //student.name="zhangsan";
        student.setName("zhangsan");
        //student.age=20;
        student.setAge(20);
        System.out.println(student.getName());
        System.out.println(student.getAge());
        student.print();
    }
    }

尽管我这里用private将name和age封装起来,但是我依然可以通过类中的方法来对他们进行操作,此时就完成了对name与age的封装.

好的又有一个问题:现在我们只有两个属性,我们可以动手敲一敲,但是万一之后我们有100个属性怎么办?也要动手去敲吗?

当然不是

.............

你就说IDEA牛不牛吧!!!

static成员

学生类


class Student {
    private String name;
    private int age;
    private String classroom;

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

    public Student() {
        System.out.println("这是一个不带参数的构造方法");
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("这是带2个参数的构造方法");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    public void print() {
        //this("ad",20);
        System.out.println("姓名: " + this.name + " 年龄: " + this.age);
        this.doclass();
    }

    public void doclass() {
        System.out.println(this.name + " " + this.age + "岁 在上课");
    }
}

public class Test {
    public static void main(String[] args) {
        Student student1=new Student("xiaoming",18,"308");
        Student student2=new Student("xiaomei",17,"308");
        Student student3=new Student("xiaohuang",17,"308");
    }
    }

好的,现在这三个学生都在308这个教室里

这就是这三个变量在内存中的分布,我们有没有看到每一个变量都有一个相同的值:classroom
假设三个同学是同一个班的,那么他们上课肯定是在同一个教室,那既然在同一个教室,那能否给类中再加一个成员变量,来保存同学上课时的教室呢?答案是不行的。

之前在Student类中定义的成员变量,每个对象中都会包含一份(称之为实例变量),因为需要使用这些信息来描述具体的学生。而现在要表示学生上课的教室,这个教室的属性并不需要每个学生对象中都存储一份,而是需要让所有的学生来共享。在Java中,被static修饰的成员,称之为静态成员,也可以称为类成员,其不属于某个具体的对象,是所有对象所共享的

此时对于内存分配图而言应该是这样的

此时被static修饰的classroom我们称它为:类变量/静态成员变量

成员变量:

  1. 普通成员变量(未用static修饰,每个对象都有一份)

  1. 静态成员变量(用static修饰,所有对象共用一份)

  1. static修饰成员变量

static修饰的成员变量,称为静态成员变量,静态成员变量最大的特性:不属于某个具体的对象,是所有对象所共享的。

【静态成员变量特性】

  1. 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中

  1. 既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问

  1. 类变量存储在方法区当中

  1. 生命周期伴随类的一生(即:随类的创建而创建,随类的销毁而销毁)


class Student {
    private String name;
    private int age;
    public static  String classroom="308";
    
    public Student() {
        System.out.println("这是一个不带参数的构造方法");
    }
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("这是带2个参数的构造方法");
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    public void print() {
        //this("ad",20);
        System.out.println("姓名: " + this.name + " 年龄: " + this.age);
        this.doclass();
    }

    public void doclass() {
        System.out.println(this.name + " " + this.age + "岁 在上课");
    }
}

public class Test {
    public static void main(String[] args) {
        Student student1=new Student("xiaoming",18);
        Student student2=new Student("xiaomei",17);
        Student student3=new Student("xiaohuang",17);
        System.out.println("教室是:"+student1.classroom);
        System.out.println("教室是:"+student2.classroom);
        System.out.println("教室是:"+student3.classroom);
    }
    }

此时我们发现,如果我们通过编译器来用对象调用classroom是调用不出的,但是并不影响执行

静态成员的访问方式不建议用对象引用的方式进行访问,建议用类名.的方式进行访问.

代码如下

为什么呢?

因为此时classroom这个变量不是对象的,它是类的,既然是类的,那当然要用类名来访问了.

那既然这样,我们想一下,我们在访问classroom的时候需要对象吗?

不需要,那么既然不需要对象,我们的代码就可以这样写了

通过上述例子可以证明:静态成员变量可以通过类名直接访问说明静态成员变量不属于对象.

灵魂拷问
1. 引用可以指向引用吗? 不可以! Student student1 = new Student( name: "zhangsan", age: 18); Student student2 = student1;引用只能指向对象:上述代码的意思是:student2这个引用 指向了student1这个引用所指向的对象.
2.一个引用 能不能指向多个对象? 不可以
Student student1 = new Student(name: "zhangsan", age:18);student1 = new Student( name: "zhangsan2", age: 18);student1 = new Student( name: "zhangsan3", age:18);
最终student1存的是:"zhangsan",18;
和下边的整形变量是一个道理int a = 10;а= 20;а= 30;
3.下面这行代码会发生什么?
Student student =null;
System.out.println(student.classroom);

为什么这里不会出现空指针异常呢?

因为classroom不属于对象

static修饰方法

此时我们创建了一个用static修饰的方法,这个方法又被叫做类方法

同时我们要知道,被static修饰的方法不依赖于对象.

那么我们如果在这个不依赖于对象的funcStatic方法中调用依赖于对象的doclass会怎么样呢?

会报错的,此时你都没有对象,你怎么调用doclass方法?

结论:

静态方法的内部是不能调用非静态方法的,因为静态方法不依赖于对象,但是非静态的方法依赖于对象.

那么在依赖于对象的方法中可不可以调用不依赖于对象的方法呢?

当然可以了

不会报错

【静态方法特性】

  1. 不属于某个具体的对象,是类方法

  1. 可以通过对象调用,也可以通过类名.静态方法名(….)方式调用,更推荐使用后者

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

  1. 在静态方法内部不能使用this.

static成员变量初始化

注意:静态成员变量一般不会放在构造方法中来初始化,构造方法中初始化的是与对象相关的实例属性静态成员变量的初始化分为两种:就地初始化和静态代码块初始化

1.就地初始化

就地初始化指的是:在定义时直接给出初始值


public class Student{
private String name;
private String gender;
private int age;
private double score;
private static String classRoom = "308"; 
}

2. 静态代码块初始化

代码块

代码块的概念与分类

使用 {} 定义的一段代码称为代码块。根据代码块定义的位置以及关键字,又可分为以下四种:

  1. 普通代码块

  1. 构造块

  1. 静态块

  1. 同步代码块

普通代码块

普通代码块:定义在方法中的代码块


public class Test{
   public static void main(String[] args) {
       {
           //现在所处的位置就是在一个普通的代码块中
           System.out.println("这就是一个普通代码块")       
       }   
   }
}

普通代码块通常不会使用

构造快

构造块:定义在类中的代码块(不加修饰符)。也叫:实例代码块构造代码块一般用于初始化实例成员变量


class Student{
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    public void print(){
        System.out.println("姓名是:"+this.name+" 年龄是:"+this.age);
    }
}


public class Test {
    public static void main(String[] args) {
        Student student=new Student("张三",18);
        //Student student=new Student();
        student.print();
    }
}

我们这里利用带参数的构造方法来初始化成员变量,其实还有另外一种初始化成员变量的方法:利用构造代码块


class Student{
    private String name;
    private int age;

   /* public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }*/
   {
       this.name="lisi";
       this.age=18;
   }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    public void print(){
        System.out.println("姓名是:"+this.name+" 年龄是:"+this.age);
    }
}


public class Test {
    public static void main(String[] args) {
        //Student student=new Student("张三",18);
        Student student=new Student();
        student.print();
    }
}

也是可以初始化的.

那还有一个问题,那就是若是同时从在构造快与构造方法,那么究竟是谁被执行或者说是谁先被执行?

我们在上图中调用了带两个参数的构造方法同时我们还写了构造快

运行结果如上,由此我们发现构造块是要比构造方法先执行的那也就意味着

那其实构造快在有构造方法的时候存在的意义并不大,但是

如果是这样的话我们就可以体现出构造快的初始化成员变量的功能

静态代码块

使用static定义的代码块称为静态代码块,一般用于初始化静态成员变量.

此时我们有了一个静态成员变量classroom并且对它进行就地初始化,现在又定义了一个静态代码块,我们不妨看一下当前代码的情况下classroom的值是什么

我们现在将classroom的权限放开,将private改为public

我们可以发现在我没有实例化对象的时候,它只会执行静态的,也就意味着,构造快和构造方法只有在实例化对象的时候才会使用,也就是说静态的东西在加载的时候就被执行了

也就是在这的时候

我现在将静态代码块写在静态成员变量的前面,我们看一下效果

执行顺序:静态代码块->构造快->构造方法

但是如果是上面代码所呈现的那种将两个静态的放一起的话就要考虑它们之间的先后顺序

也要去考虑实例的顺序

注意:

  • 静态代码块不管生成多少个对象,其只会执行一次

  • 静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的

  • 如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行(合并)

  • 实例代码块只有在创建对象时才会执行

什么是合并呢?也就是说上述代码和下述代码在编译器看来是一样的

对象的打印

直接上例子

我想要知道这个地址是怎么打印出来的

我们一步一步查看它的底层的方法我们可以知道最后它是用一个叫做toString这个方法来做的。(Ctrl+鼠标左键可以点进去)

那么我现在想重新写一个toString方法来使用

这叫做方法重写,只要你自己写了一个一模一样的方法,他就会调用你的方法,具体是什么?请听下回讲解

那么如果你觉得自己重写方法太麻烦的话,你依然可以依靠IDEA

选择你的参数

运行结果如下

当然了你若是不喜欢编译器里面的内容,那也是可以改的么

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值