JavaSE学习笔记 3

        接上回总结,这部分笔记主要用来记录关于类和对象的基础学习内容,但由于我自己也有很多不理解的地方,如果有说错的地方,敬请谅解。

        (一)面向对象和面向过程

        在学习C语言之初的时候,我们就经常能看到在最开头的地方,会有一句,C语言是一种面向过程的程序语言,而在学习Java的时候,往往也会在开头有那么一句话,Java是一种面向对象的程序语言,这确实比较难理解,从我个人角度出发,我觉得面向对象也好,面向过程也好,去除掉“面向”两个词,后面的对象和过程表示的是两种语言的侧重点。

        面向过程,表示这个语言更侧重于关注解决问题的过程;面向对象,表示这个对象更侧重于关注解决问题的对象,这么一说,可能很难理解,这边举一个简单的例子,或许能更好的理解。

        现在假设我们要洗碗:

        面向过程的思考过程:拿碗→放水→放洗洁精→用洗洁精擦碗→用水冲洗碗→用布擦干碗→晾干碗。

        面向对象的思考过程:拿碗→放入洗碗机→取出洗好烘干的碗。

        有人会说这是两种不同的洗法,那么我再补充假如都用洗碗机,面向过程会如何思考。

        面向过程的思考过程:拿碗→放入洗碗机→洗碗机开始喷喷洒高温水→旋转喷洒头→释放蒸汽→烘干碗→拿出洗好的碗。

        可以看到,同样是用洗碗机,两者思考的模式是不一样的,面向过程会思考该怎么洗,而面向对象则只会考虑找谁(对象)解决这个问题,面向过程会关心洗碗机内到底是如何运作,把我们的碗洗干净的,假如换了一个洗碗机,里面的工艺不同,面向过程的思考模式就会关心这个工艺到底是什么;而面向对象不会考虑那么多,面向对象只会想,我只要找到洗碗机,我把碗放进去,这个洗碗机能给我洗干净就行,并不会关心工艺,同样的,如果换了一台洗碗机,也不会关心新的洗碗机到底是什么工艺,我们只思考这个洗碗机能给我们洗干净碗就行。

        相比于面向过程的思考模式,其实面向对象的思维模式更接近于我们的日常,我们用电脑、看电视、玩手机的时候,并不会关心这个手机内部到底是如何运作实现我们的要求的,我们只关心这些东西能满足我们的要求。

        (二)类和对象

        通过上面对面向过程和面向对象两者的解释,我们大致就能明白两者的区别了,因为Java是面向对象的语言,所以Java给出了两个概念,就是类和对象的概念。

                ①类

                因为Java是面向对象的,那么如何表达对象就是一个问题,所以Java给出了一种用于描述对象的方式,也就是定义一个“类”,利用类,我们能完成对某个对象的描述,类从形式上来看,跟C语言的结构体很类似。

                定义一个类,我们能定义属性(成员变量)和行为(成员方法),属性和行为这一说法可以说是十分贴切了,因为我们就是希望能用类定义一个对象,比方说我们要定义一个屏幕的类,这个屏幕,我们可以用长、宽、高等来作为其属性,用开机、显示等行为作为其行为。

                那么具体到Java语言中,定义一个类的格式是什么呢?Java内,定义类有一个关键字“class”,利用这一关键字,我们能实现对类的定义,包含定义属性、行为的格式如下:

class 类名 {
    属性(成员变量)
    ………………
    行为(成员方法)
    ………………
}
// 举例:显示器
class Screen {
    int width;
    int long;
    int high;
    …………
    public static void show() {
    System.out.println("开机");
    }

    public static void output() {
    System.out.println("显示");
    }
    …………
}

                需要注意的一点是,类名需要采用大驼峰的命名原则,至于上面例子内的修饰符public、static等,都涉及到权限的问题,当前不展开说。在常规的程序涉及过程中,一般一个文件当中只定义一个类,public修饰的类,类名要与文件名一致,所以最好不要轻易地去修改public修饰的类。

                ②对象

                第一个部分,我们主要说了类,这个类相当于是对某个对象的描述,是一种概念,比方说我们描述了狗、猫、显示器、洗碗机都有哪些属性和行为,但是从我们个人角度说,如果要洗碗,我们肯定需要一个实体化的洗碗机来帮助我们完成洗碗的工作,光有洗碗机的概念是不足的。

比如我们要用尺子的长度属性去量别的东西,我们要用洗碗机的洗碗行为去帮我们洗碗,所以我们需要先将这些类所代表的对象,将他实体化出来,在Java内,我们将这一过程称之为实例化。在java中,我们使用new关键字,来配合类名代表的对象概念,来实例化一个对象。

                就跟我们所想的一样,类代表了某一类对象的属性、行为,所以我们可以用一个类来实例化多个对象,我可以用显示器的类来实例化多个品牌的显示器,并赋予他们不同的属性值,启用他们不同的功能。上面的操作自然就又涉及到对实例化后对象的成员变量赋值,以及对成员方法的调用,那么如何实现呢?用下面一段代码作为例子进行说明。

class Screen {
    int width;
    int long;
    int high;

    public static void show() {
    System.out.println("开机");
    }

    public static void output() {
    System.out.println("显示");
    }
}

public static Main {
    public static void main(String[] args) {
        Screen screen1 = new Screen();
        screen1.width = 100;
        screen1.long = 200;
        screen1.high = 300;

        screen.show();// 结果是输出了“开机”并换行
        screen.output();// 结果是输出了“显示”并换行

        Screen screen2 = new Screen();
        screen2.width = 300;
        screen2.long = 200;
        screen2.high = 100;

        screen2.show();// 结果是输出了“开机”并换行
        screen2.output();// 结果是输出了“显示”并换行
    }
}

                这段代码,实例化了screen1和screen2两个显示器,他们的长宽高有所不同,同时都通过了对象名来调用方法,说明我们可以用“.”来实现对对象成员属性和方法的访问,并且我们可以用一个类实例化多个对象,这里要注意一点,一个new关键字只能实例化一个对象。最后插一嘴,实际上因为当前显示器类内的方法都是static修饰的静态方法,所以我们可以直接通过类名加“.”号来调用,当前用实例化后的成员名加“.”号来引用静态方法实际上是不恰当的。

                ③this关键字

                这个关键字的作用,体现在能让类内的成员方法,能够识别出具体是哪个对象在调用他,this本身代表的就是调用某个方法的对象。这里举个例子

class Screen {
    int long;
    int width;
    int high;

    public void setData(int l, int w, int h){
        long = l;
        width = w;
        high = h;
    }

    public void showPrint()
    {
        System.out.println("长:" + long + "  宽:" + width + "  高:" + high);
}

public static Main {
    public static void main(String[] args) {
        Screen screen1 = new Screen();
        screen1.setData(200,200,200);

        Screen screen2 = new Screen();
        screen2.setData(300,300,300);

        screen1.showPrint();// 结果是输出了“长:200  宽:200  高:200”并换行
        screen2.showPrint();// 结果是输出了“长:300  宽:300  高:300”并换行
    }
}

                通过这一串代码,我们有两个疑问产生,首先在Main类的main方法里,我们实例化了连两个对象,分别是screen1和screen2,并且我们通过setData方法,对这两个对象的成员变量进行赋值,同时也通过showPrint方法将这两个对象的成员变量值打印了出来。

                但是我们在调用setData方法和showPrint方法时,我们并没有将这两个对象名字相关的数值传入到这两个方法内,我们在main方法里能知道我们是用哪个screen去调用的方法,但是进入到具体方法内时,这个方法内并没有能表示是哪个对象的数值存在,那么程序是如何知道,具体是哪个对象在调用这些方法的呢?

                此外,在类内,假如我们方法内的形参名称跟成员变量名称一致,那么赋值时,是赋值给形参还是赋值给成员变量?(如下),这边的结果实际上是形参自己给自己赋值,并不会赋值给成员变量。

class Screen {
    int long;
    int width;
    int high;

    public void setData(int long, int width, int high){
        long = long;
        width = width;
        high = high;
    }
}

                上面这两个问题,就是通过this这一关键字来解决的,实际上Java在所有非静态方法内,都有一个隐藏的形参在内,也就是this,实际上上面的这一串代码隐藏了这么一个this的形参

class Screen {
    int long;
    int width;
    int high;

    public void setData(Screen this, int long, int width, int high){
        long = long;
        width = width;
        high = high;
    }
}

                 this引用指向当前的对象,也就是引用该方法的对象,他的数据类型是当前的类名,通过this,这些方法就能知道是哪个对象引用他们,自然也就知道是给哪个对象的成员变量赋值等。并且上面代码中形参跟类内成员变量名重复的问题,也可以解决。只需要如下这么修改即可

class Screen {
    int long;
    int width;
    int high;

    public void setData(Screen this, int long, int width, int high){
        this.long = long;
        this.width = width;
        this.high = high;
    }
}

                 其实不难理解,首先this是引用类型,他引用的是Screen类的对象,所以当某个对象引用了setData方法后,进入当前setData方法的同时,也隐性地将这个对象的地址传递给了this,然后因为this内存储了对应对象的地址,自然就可以通过地址去访问对应对象的各种数值。

                接下来说一下this的特性,this显然只能在成员方法内使用,并且只能引用当前对象,不能引用其他对象;this是“成员方法”第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法对象的引用传递给该成员方法,this负责来接收。

                ④对象的初始化

                首先我们要知道,java的局部变量必须要初始化,如果不初始化就直接调用会报错,但是java实例化对象的成员变量是可以不用我们来初始化的,当实例化对象后,程序会自动给这些成员变量进行默认赋值,大多为0,布尔变量为false,这叫做默认初始化。

                我们也可以直接在类内,定义成员变量的同时给他们赋值,但是要注意,只允许在定义的同时赋值,而不能先定义,再赋值,这个方式叫做就地初始化,一般是用不大上的,因为意义不大。

                通常来说,我们给对象的成员变量初始化,可以通过上面的setData方法来进行赋值,但是显然,如果我们在实例化一个对象后,都调用一次setData方法,会使代码显的很冗余,那么有没有办法直接在实例化对象的同时,对对象内的成员变量进行初始化呢?

                答案是肯定的,Java提供了一种方法,叫做构造方法,所有的对象实例化同时,都会调用构造方法。构造方法在对象的整个生命周期内,只会调用一次;构造方法只有方法名,并且与类名相同;没有返回值(即使没有返回值,也不用写void);构造方法可以重载。

                上面一段说了,所有对象实例化的同时,程序会自动调用构造方法,那我上面的类内,并没有一个像上面一段所说特点一样的方法,那是不是没有构造方法也行?实则不然,Java在我们没有编写构造方法的时候,会默认存在一个没有形参,没有任何语句的构造方法存在,假如我们没有写对应的构造方法,那么程序就会默认调用该构造方法。接下来我们将上面实例化显示器的代码更改增加构造方法。

class Screen {
    int long;
    int width;
    int high;

    public Screen(int l, int w, int h) {
        this.long = l;
        this.width = w;
        this.high = h;
    }
    
    public Screen() {
        this.long = 300;
        this.width = 300;
        this.high = 300;
    }

    public void showPrint()
    {
        System.out.println("长:" + long + "  宽:" + width + "  高:" + high);
}

public static Main {
    public static void main(String[] args) {
        Screen screen1 = new Screen(200, 200, 200);
        Screen screen2 = new Screen();

        screen1.showPrint();// 结果是输出了“长:200  宽:200  高:200”并换行
        screen2.showPrint();// 结果是输出了“长:300  宽:300  高:300”并换行
    }
}

                可以看到,我们写了两个构造方法,第一个构造方法有三个形参,screen1对象实例化时传入了三个形参,所以调用了第一个构造方法,同时分别给长宽高赋值200;第二个构造方法没有形参,实例化screen2的时候也并没有给定实参,所以screen2会调用第二个构造方法,第二个构造方法内给长宽高赋值300,所以最后screen1长宽高为200,而screen2长宽高为300。这就是构造方法的重载,根据传入的实参类型、数量来选择对应的构造方法。

                假如上面的代码,去除掉第二个无形参的构造方法,那么screen2的实例化将会失败,因为当我们写了任意一个构造方法后,程序将不再提供默认的构造方法,即使是没有任何形参的构造方法,也需要我们写上去。

                在构造方法内,我们也可以使用this关键字,来调用其他构造方法。上面的代码可以改成下述的形式:

class Screen {
    int long;
    int width;
    int high;

    public Screen(int l, int w, int h) {
        this.long = l;
        this.width = w;
        this.high = h;
    }
    
    public Screen() {
        this(300, 300, 300);
        System.out.println("没有参数的构造方法");
    }

    public void showPrint()
    {
        System.out.println("长:" + long + "  宽:" + width + "  高:" + high);
}

public static Main {
    public static void main(String[] args) {
        Screen screen1 = new Screen(200, 200, 200);
        Screen screen2 = new Screen();// 会输出“没有参数的构造方法”并换行

        screen1.showPrint();// 结果是输出了“长:200  宽:200  高:200”并换行
        screen2.showPrint();// 结果是输出了“长:300  宽:300  高:300”并换行
    }
}

                 原本的无形参构造方法内,内容与有三个形参的构造方法一样,所以我们实例化对象时,虽然时采用的无参构造方法,但我们可以通过this来调用有三个参数的构造方法,来实现初始化。使用this来调用其他构造方法时要注意this要放在当前构造方法的最上面,以上面代码为例,则this要放在System这个语句的上面,不能放在下面。

                还有就是,我们知道对象实在完成构造后才生成的,既然对象还没生成,那么this哪来的呢?这个问题就涉及到对象的默认初始化过程了,大致流程就是先加载类,然后分配内存空间,之后解决线程冲突问题,再然后初始化所分配的内存空间(默认初始化在这一步进行),设置对象头信息,最后才是调用构造方法。

                可以看到在调用构造方法前,对应对象的内存空间就已经被分配了,并且完成了默认初始化,既然已经有了内存空间,自然就已经有了地址,我们也就可以通过this引用类型所保存的内存地址,来访问这片内存空间。

        (三)封装

                面向对象程序有三个特性,分别是封装、继承和多态,由于我目前的学习进度只学习了封装和一点点继承,所以这次也仅仅把封装的一部分知识说一下。之前也说了面向对象,我们是不在乎对象内部是如何运作帮我们实现功能的,为了简化我们看到的内容,我们需要将对象内部进行的操作进行隐藏,好比我们电脑主机装个机箱,手机外面装个手机壳,洗碗机也有柜子。用户最后看到的就是机箱外面的接口、手机上的屏幕按钮、洗碗机外面的按钮,通过封装,可以隐藏对象的属性、行为细节,让对应的人只需要与对象提供的接口进行交互,就可以实现我们的需求。

                ①访问限定符

                Java内主要是通过类和访问限定符(访问的权限)来实现封装这一功能的。类可以将属性和方法结合到一起,更符合人类的认知,好比洗碗机就是既有属性也有行为,而不像C语言,结构体只有属性;访问限定符则能控制方法和属性能否在类外使用。

                访问限定符一共三种,实际有四个权限等级,从低到高为private、default、protected、public,其中default是不需要写的,是默认的权限。

                private的权限规定只能在同一个包的同一个类内使用,default默认权限则允许可以在同一个包的不同类内使用,protected允许在不同包的子类中使用,public允许在不同包的不同子类内使用,每一个权限都比上一个权限等级多了一些权限。

                注意一下访问限定符是不允许用在局部变量上的。通常来说,为了封装性,会将成员变量的权限定位private,而成员方法的权限定位public,但正式使用的时候,还是需要根据自己的需求来设定相关的权限。

                ②包

                软件包也是体现Java封装性的概念之一,一个包是多个类的集合,有点类似于我们通常收纳文件的文件夹概念,有了包之后,不同包内就允许有相同名字的类了。之前我们代码中调用Arrays类和Scanner类,使用了import关键字,这就是导入包的关键字,这两个类都属与java.util包。所以写出来前面的部分都是import java.util。

                java.sql和java.util两个包内都有Date这个类,那么在使用对应类时,就需要将其完整的类名写出来,形式如下:

public static void main(String[] args) {
    java.util.Date date1 = new java.util.Date();
    java.sql.Date date2 = new java.sql.Date();
}

                我们也可以用import static导入包中的静态方法和成员变量,当导入静态包后,我们就可以进一步简写代码。

                Java的import与C++的#include有本质上的不同,import并不是导入其他文件内容,仅仅只是为了减少代码的书写量。

                上面说到的都是Java提供的包,那么如何自定义一个包呢?首先需要我们新建一个包文件夹,包名通常来说时公司域名的颠倒,创建成功包后,我们把我们想要放到这个包内的文件移动到新生成的包文件夹内,同时在这个类的最上面写上关键字package,后面接上包名,这样就完成了一个自定义包的制作,包的导入方式跟上面调用java提供的包方式是一样的。

        (四)static 成员

                ①static 修饰的成员变量

                用static修饰的成员变量、成员方法,他们都将不在同具体对象是谁关联,static修饰的成员变量和方法是属于全体对象共有的,不受具体对象影响,他们的生命周期是同类一起的,类加载了就存在。从内存角度看,他们不是存储在堆区内,也不是存储在某个对象的空间内,static修饰的成员变量和方法是存储在内存的方法区内的。

                被static修饰的成员变量称之为静态成员变量,成员方法称之为静态成员方法,静态成员变量我们可以通过类名来访问,也可以通过对象名来访问,但通过对象名来访问,本质上是程序根据对象名找到对应的类来访问,所以更推荐使用类名来访问。

                ②static 修饰的成员方法

                被static修饰的成员方法称之为静态成员方法,是类方法,不是某个对象所拥有的,当静态成员变量被private修饰时,我们就只能通过静态成员方法来进行访问修改。跟静态成员变量一样,静态成员方法也是既能通过成员名访问,也能通过类名访问,更推荐类名访问,原因同静态成员变量的访问一致。然后最重要的一点,那就是静态成员方法内是不允许使用非静态的成员变量的,原因很简单,因为静态成员方法属于全体,并且不依附与某个具体的对象,所以并不像非静态方法一样有隐形的this形参存在,好比一个班级里的饮水机是大家共有的,这个饮水机上就不能属名某个单独的同学。因为上述原因,所以非静态的方法也同样不允许调用,因为一旦调用就相当于引入了this,此外静态成员方法是不允许重写的(多态部分会讲到)。

                ③static 修饰的成员变量初始化

                静态成员变量可以通过构造方法去初始化,但是一般不这么做,因为构造方法初始化的变量一般与对象息息相关,既然与对象息息相关,自然就不是静态成员变量。

                通常来说静态成员变量是通过就地初始化或者静态代码块初始化两种方式来实现的。

                就地初始化前面也将到过,就是在定义这个静态成员变量的同时,就进行赋值初始化。

                而静态代码块初始化,就又涉及到代码块的知识内容了,代码块分为普通代码块、构造代码块(实例代码块)和静态代码块,还有一个同步代码块因为我也还没学过,所以先不讨论。

                所谓代码块,按C语言的说法就是一个复合语句,简单的说就是一个大括号括起来的多段语句,普通代码块是定义在方法内的代码块,也就是在方法大括号内再增加一个大括号,主要作用是产生不受方法影响的局部变量。

                构造代码块是定义在类内的代码块,主要用于初始化非静态成员变量,在程序角度看,当构造生成实例化对象调用构造方法时,程序会将构造代码块的内容,直接附到构造方法的最上面。并且每次实例化对象时,都会调用该构造代码块。当有多个构造代码块时,会依照先后顺序,依次执行。

                静态代码块也是定义在类内的代码块,主要用于初始化静态成员变量,静态代码块不管实例化多少个对象,他都只在加载对应类的同时执行一次,有多个静态代码块时,也是依照静态代码块的先后顺序依次执行。他与构造代码块的区别在于静态代码块是在加载类的同时就已经执行,与对象无关,所以只执行一次,而构造代码块时在实例化对象的时候才执行,他与对象关联,所以每次实例化对象,他都会执行。

                上面我们就能总结出这些代码块和构造方法的执行先后顺序,静态代码块→构造代码块→构造方法→普通代码块。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值