本篇文章是《零根底学Java》专栏的第五篇文章,文章采纳通俗易懂的文字、图示及代码实战,从零根底开始带大家走上高薪之路!

类与对象

在哲学体系中,能够分为主体(subject)和客体(object),在面向对象的程序设计语言中,所有的要面对的事物都能够形象为对象(object)。在面向对象的编程过程中,咱们就是应用各种各样的对象互相协同动作来实现咱们的程序性能。

在面向对象的语言中,所有应用中的对象都具备某种类型,这些类型之间也有档次关系,如同生物学中的门、纲、目、科、属、种一样,这种档次关系,咱们能够用继承这个机制来实现。

Java语言为面向对象的语言,所有的对象都能够具备某些属性,以及具备某种行为性能。在Java语言中对象的属性用成员变量(域)形容,行为用办法形容。

类和对象之间的区别和分割就是,类是形象的,它具备一类对象所有的属性和行为,相当于模板,对象是具体的,通过创立相应类的对象来实现相应的性能。咱们在作面向对象的编程的时候,就是形象出类的属性和行为,再创立具体的对象来实现性能。

Date类

定义

在JDK中,有一个用于形容日期的类:java.util.Date,它示意特定的霎时,准确到毫秒。在这里咱们本人定义一个简略的Date类,用来示意具备年、月、日的日期。咱们先来看看如下定义怎么样?

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

在这段程序定义中,类定义为public,同时year、month、day也定义为public,它们是什么含意呢?

咱们后面提到包这个概念,咱们说,在同一个包中的类在性能上是相干的,然而在包内的这些类并非全副都能够被其余包中的类调用的,怎么辨别呢?那些能够被包外应用的类,咱们在定义的时候,在类名前加上public,而不能被包外应用的类,在定义的时候,不要加public。

year、month、day这些在类体中所定义的变量,咱们称之为成员变量(域 field),那么成员变量后面的public的作用是阐明这些变量是公开的,能够被以 对象.变量 这种模式调用,这称为成员变量的拜访权限。比方本例:咱们定义一个Date类型的变量d1,那么咱们能够通过d1.year来应用d1这个对象中的year。在定义成员变量的时候,除了能够定义为public,还能够有protected、缺省(无权限修饰词)、private,它们的限度越来越严格,咱们总结一下:

  • public:被public润饰的成员是齐全公开的,能够被以任何非法的拜访模式而拜访
  • protected:被protected润饰的成员能够在定义它们的类中拜访,也可被同一包中的其余类及其子类(该子类能够同其不在同一个包中)拜访,但不能被其余包中的非子类拜访
  • 缺省:指不应用权限修饰符。该类成员能够在定义它们的类中被拜访,也可被其同一包中的其余类拜访,但不能被其余包中的类拜访,其子类如果与其不在同一个包,也是不能拜访的。
  • private:只能在定义它们的类中拜访,仅此而已。

在类中,咱们岂但能够定义成员变量,还能够定义成员办法,成员办法后面也能够有这四种权限管制,含意是一样的。然而大家千万不要认为在独立类的后面也能够有private、protected这两个修饰词,在类前只能有public或者没有,含意后面已述。

对于public类和源程序文件的关系,咱们在以前学习过,这里再从新提一下:

每个源代码文件中至多定义一个类,若有多个类,最多只有一个类定义为public,若有public类,则该源代码文件的前缀名字要同该类的类名完全一致,若没有public类,则源代码文件的前缀名可不与文件内的任何类统一。

测试

下面的Date类曾经定义好了,咱们怎么测试这个类呢?咱们在这个类中定义main办法了吗?没有main办法的类,能够以 java 类名 这种形式在控制台中执行吗?显然不行,那怎么办呢?有两种计划,一种是咱们给它定义一个main办法;另一种计划,再定义一个类,专门测试这个Date类。咱们顺次来看看。

增加main办法

咱们在Date的定义中退出main办法,代码如下:

public class Date{    public int year;    public int month;    public int day;    public static void main(String[] args){        year = 2016;        month = 9;        day = 5;        System.out.println("year:" + year + " month:" + month + " day:" + day);    }}

编译...,怎么?又错了?
别慌,看看出错提醒,都是无奈从动态上下文中援用非动态 变量这样的提醒,这是什么意思?咱们看看main办法后面是不是有个修饰词static,咱们后面提到凡static润饰的成员变量或者办法,都能够用 类名.成员 这种模式来拜访。如果Date类在控制台中以 java Date这种命令模式来执行,我想请问,零碎是创立了一个Date对象了,而后再执行这个对象中的main办法这种形式来执行的Date类吗?错,零碎是间接找到Date.class这个类文件,把这个类调入内存,而后间接执行了main,这时在内存中并没有Date对象存在,这就是为什么main要定义为static,因为main是通过类来执行的,不是通过对象来执行的。

那下面的解释又同这个编译谬误提醒什么关系?咱们看看 year、month、day后面有没有static这个修饰词?没有,这阐明这些成员变量不能通过类名间接拜访,必须通过创立类的对象,再通过对象来拜访。而main在执行的时候,内存中并没有对象存在,那天然那些没有static润饰的成员就不能被拜访,因为它们不存在,是不是这个逻辑?想明确了吗?还没有?再想想!

咱们得出个能记住的论断,在static润饰的办法中,只能应用本类中那些static润饰的成员(包含成员变量、办法等),如果应用本类中非static的成员,也必须创立本类的对象,通过对象应用。好吧,不明确情理,就先把这个论断记着。

咱们依据下面的论断,把测试代码再改改:

public class Date2{    public int year;    public int month;    public int day;    public static void main(String[] args){        Date2 d1 = new Date2();        d1.year = 2016;        d1.month = 9;        d1.day = 5;        System.out.println("year:" + d1.year + " month:" + d1.month + " day:" + d1.day);    }}

编译...,OK,运行,通过,

那如果非静态方法中应用动态成员,可不可以呢?咱们说能够,简略讲,这是因为先于对象存在,动态成员依赖于,非动态成员依赖于对象

下面的程序中,咱们给这几个成员变量别离给了值,如果不给值间接输入呢?咱们试试:

public class Date3{    public int year;    public int month;    public int day;    public static void main(String[] args){        Date3 d1 = new Date3();        System.out.println("year:" + d1.year + " month:" + d1.month + " day:" + d1.day);    }}

编译,运行,后果: ,都是0。这里有个论断:成员变量如果没有显式初始化,其初始值为0值,或者相当于0值的值,比方援用变量,未显式初始化,其值为null,逻辑变量的未显式初始化的值为false。这里要留神,咱们说的是成员变量,如果是局部变量,没有显式初始化就用,编译是通不过的。比方下面的代码,改为:

public class Date3{    public int year;    public int month;    public int day;    public static void main(String[] args){        Date3 d1;//这里d1为局部变量,没有初始化        System.out.println("year:" + d1.year + " month:" + d1.month + " day:" + d1.day);    }}

编译: 从编译提醒,咱们晓得d1未初始化就用,这是不对的。

创立一个测试类

代码如下:

//本类放在Date.java文件中public class Date{    public int year;    public int month;    public int day;}//本类放在TestDate.java文件中public class TestDate{    public static void main(String[] args){        Date d1 = new Date();        d1.year = 2016;        d1.month = 9;        d1.day = 5;        System.out.println("year:" + d1.year + " month:" + d1.month + " day:" + d1.day);    }} 

这两个类都是public类,所以,这两个类应该写在两个不同的源代码文件中,文件名别离为:Date.javaTestDate.java中。如果大家想将这两个类写在一个源代码文件中,因为Date类在测试实现当前是会被公开应用的,所以咱们应该把Date定义为public,那么源代码文件的名字就是Date.java,而TestDate这个类是用于测试Date类的,所以它后面的public就应该去掉。代码如下:

//这些代码放在Date.java文件中class TestDate{    public static void main(String[] args){        Date d1 = new Date();        d1.year = 2016;        d1.month = 9;        d1.day = 5;        System.out.println("year:" + d1.year + " month:" + d1.month + " day:" + d1.day);    }}public class Date{    public int year;    public int month;    public int day;}

这两个类的定义程序无关紧要。编译后,咱们在命令行上执行:java TestDate即可。

思考

封装

在这个示例中,有个问题:咱们在创立Date对象d1当前,就间接应用d1.这种模式给year、month、day赋值了,如果咱们给它们的值不是一个非法的值怎么办?比方month的值不在1~12之间,等等问题。在如上的Date定义中,咱们没有方法管制其余代码对这些public润饰的变量随便设置值。

咱们能够把这些成员变量用private修饰词爱护起来。由private润饰的成员,只能在类或对象外部自我拜访,在内部不能拜访,把下面的Date代码改为:

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

这样咱们就不能够通过d1.year来拜访d1对象中year了。慢着,不能拜访year,那这个变量还有什么用?是的,如果没有其它伎俩,下面的这个类就是无用的一个类,咱们基本没有方法在其中存放数据。

怎么办?咱们通过为每个private的成员变量减少一对public的办法来对这些变量进行设置值和获取值。这些办法的命名形式为:setXxx和getXxx,setter办法为变量赋值,getter取变量的值,布尔类型的变量的取值用:isXxx命名,Xxx为变量的名字,上例改为:

public class Date{    private int year;    private int month;    private int day;    public void setYear(int year){        //实践上year是没有公元0年的,咱们对传入的值为0的实参解决为1        //year的正值示意AD,负值示意BC        if(year == 0){            //这里有两个year,一个是成员变量,一个是实参,这是容许的            //为了辨别它们,在成员变量year后面加上this.            //实参year不做解决            //如果没有变量和成员变量同名,this是能够不写的            this.year = 1;        } else {                        this.year = year;        }    }    //因为要取的是year的值,所以getter办法的返回值同所取变量的类型统一    public int getYear(){        return year;    }    public void setMonth(int month){        if((month > 0) && (month < 13)){            this.month = month;        } else{            this.month = 1;        }    }    public int getMonth(){        return month;    }    public void setDay(int day){        //这个办法有些简单,因为咱们须要依据year、month的值来判断实参的值是否合规        switch(month){            case 1:            case 3:            case 5:            case 7:            case 8:            case 10:            case 12:if (day < 32 && day > 0) {//在1~31范畴内                        this.day = day;                    }else{                        this.day = 1;//超出日期失常范畴,咱们设为1                    }                    break;            case 4:            case 6:            case 9:            case 11:if (day < 31 && day > 0) {//在1~30范畴内                        this.day = day;                    }else{                        this.day = 1;//超出日期失常范畴,咱们设为1                    }                    break;            case 2:if (isLeapYear()) {                        if (day < 30 && day > 0) {//在1~29范畴内                            this.day = day;                        }else{                            this.day = 1;//超出日期失常范畴,咱们设为1                        }                    } else {                        if (day < 29 && day > 0) {//在1~28范畴内                            this.day = day;                        }else{                            this.day = 1;//超出日期失常范畴,咱们设为1                        }                    }                    break;            default:this.day = 1;//如果month的值不在上述情况下,day设置为1                            break;                }    }    //这个办法判断年份是否为平年,是平年返回true,否则返回false    //该办法只在本类外部应用,所以定义为private    private boolean isLeapYear(){        //可被400整除或者被4整除但不能被100整除的年份为平年,其它年份为平年        if((year % 400 == 0) || ((year % 4 == 0) && (year % 100 != 0))){            return true;        }        return false;//能执行这里,阐明是平年    }}

通过下面的革新,尽管代码长了很多,然而平安了很多。咱们对year、month、day的值不再是直接存取,而是通过相应变量的getter和setter办法来存取,这些办法在进行存取的时候,会判断设置的值是否合规。

下面的代码其实是能够优化的,比方setDay能够优化如下:

    public void setDay(int day){        //这个办法有些简单,因为咱们须要依据year、month的值来判断实参的值是否合规        this.day = 1;//这里先把day设置为1,上面的超范围的状况就不必再写代码了        switch(month){            case 1:            case 3:            case 5:            case 7:            case 8:            case 10:            case 12:if (day < 32 && day > 0) {//在1~31范畴内                        this.day = day;                    }                    break;            case 4:            case 6:            case 9:            case 11:if (day < 31 && day > 0) {//在1~30范畴内                        this.day = day;                    }                    break;            case 2:if (isLeapYear()) {                        if (day < 30 && day > 0) {//在1~29范畴内                            this.day = day;                        }                    } else {                        if (day < 29 && day > 0) {//在1~28范畴内                            this.day = day;                        }                    }                    break;        }    }

咱们通过下面的示例,看到了面向对象的一个概念:封装。咱们将数据用private暗藏起来,通过public存取方法对数据进行了封装,使得数据更加平安与牢靠。

成员变量的初始化

非动态成员变量的初始化

在下面的例子中,如果咱们新建一个Date对象当前,如果不初始化,咱们能够晓得,间接取其中的year、month、day的值的时候,它们的值都是0,这显然是不合理的。要解决这个问题,咱们须要在创立Date对象的时候,让零碎主动初始化一个适合的值,要达到此目标,咱们能够采纳三种形式:

  • 在定义成员变量的时候初始化值,如下面的代码,咱们批改一下如下:

    public class Date{    private int year = 1;    private int month = 1;    private int day = 1;    ...}

    下面的代码,使得新建Date对象的初始值均为1。

  • 第二种形式为应用构造方法

    构造方法是类中一种特地的办法,它的办法名和类名统一,该办法没有返回值,甚至连void都没有,而且构造方法不能被当作一般办法一样被调用,每当生成某个类的对象的时候,构造方法才被调用。构造方法的作用就是在创建对象的时候执行对对象的初始化工作。咱们应用构造方法对成员变量来进行初始化,代码如下:

    public class Date{    private int year;    private int month;    private int day;    public Date(){        year = 1;        month = 1;        day = 1;    }    ...}

    下面的代码使得咱们在新建Date对象时,能够初始化其内的值。下面的构造方法没有参数,这种构造方法,咱们称之为默认构造方法。那是不是用构造方法只能初始化为这种固定的值呢?能不能在创建对象的时候再指定初始化值呢?答案是能够的,就是应用有参数的构造方法。代码如下:

    public class Date{    private int year;    private int month;    private int day;    public Date(int year, int month, int day){        //上面的代码应用setter办法进行初始化是因为,setter办法提供了参数查看,        //如果不必setter办法,咱们就须要从新写对参数的查看代码        setYear(year);        setMonth(month);        setDay(day);    }    ...}

    下面的代码使得咱们能够在创立新的Date对象的时候指定初始化值,比方:Date d = new Date(2016,9,5); 。然而下面的代码没有定义无参构造方法,咱们再用无参构造方法来创立Date对象就不行了,如:Date d = new Date();,编译时会呈现如图的谬误:
    这是为什么呢?这是因为,如果定义类时没有定义构造方法,则编译器会主动创立一个无参的空的public构造方法,作为该类的构造方法。然而,只有你定义了构造方法,不论是有参还是无参,编译器就不再主动生成了,就会呈现下面的谬误提醒。

    另外,在构造方法中产生的异样会被jvm疏忽,即便是在构造方法中应用try也杯水车薪。

    public class Test {    public Test() {        try {            System.out.println("trying to throw an exception");            double x = 1.0/0.0;//此句会产生除0异样        } catch(Exception e) {            System.out.println("Exception captured");           } finally {            System.out.println("inside finally");        }    }       public static void main(String args[]) {        Test test = new Test();    }}

    上述代码在执行当前的后果如图: 从图大家能够看到产生的异样并未被catch。

  • 第三种形式是应用实例语句块。那么什么是实例语句块呢?实例语句块是由一对大括号括起来的能够被执行的语句序列,这个语句块在类的外部,但不在任何办法的外部,如下代码:

    public class Date{    private int year;    private int month;    private int day;        //上面的大括号为实例语句块    {        year = 1;        month = 1;        day = 1;    }    ...}

    下面的实例语句块会在构造方法执行前被执行。如果呈现多个实例语句块,按程序执行。

  • 如果这三种形式同时采纳呢?代码如下:

    public class Date{    private int year = 1;    private int month = 1;    private int day = 1;    {        year = 2;        month = 2;        day = 2;    }    public Date(){        year = 3;        month = 3;        day = 3;    }    ...}

    下面的Date类,如果要新建对象,其初始值是1、2,还是3呢?在创建对象的时候,会在堆中为对象调配存储空间再将所调配空间的所有内容都设为默认初始值0,接着将成员变量定义时的值对成员变量进行初始化,再执行实例语句块,最初执行构造方法。因而,上例的初始最终值为3 。

动态成员变量的初始化
  • 咱们后面所波及到的是对非动态成员变量的初始化,咱们晓得有一类成员变量是用static润饰的,这类成员变量是不依赖对象的,那么这类变量如何初始化呢?
    首先咱们要明确,动态成员变量的存储空间不同任何该类的对象相关联,它们的存储空间是独立的。由此,咱们能够得出两个论断,首先动态成员变量的值只能被初始化一次,其次,动态成员变量的值不能在构造方法中初始化(因为构造方法是在创建对象的时候调用的)。
    那么怎么对动态成员变量进行初始化呢?有两种形式:

    • 象初始化非动态成员变量一样,间接给动态成员变量赋初始值,如:

      public class Test{    public static int first = 1;    public static int second = 2; }
    • 第二种,应用动态语句块,如下:

      public class Test{    public static int first;    public static int second;    //上面的语句就是动态语句块,这种语句块不能放在办法体中    static{        first = 1;        second = 2;    } }

      在类中,能够呈现多个动态语句块,多个动态语句块程序执行。

    • 动态成员变量的初始化产生在该成员变量第一次被应用的时候,之后该成员变量不会再反复初始化。而且类中的动态成员变量的初始化产生在非动态成员变量的初始化之前,这样,上面的代码就不适合了:

      public class Test{    public int first = 1;    public static int second = first;}

类定义中的成员

Java的类中能够蕴含两种成员:实例成员和类成员。

实例成员
  • 实例成员(包含实例成员变量及实例办法)属于对象,通过援用拜访:

    • 援用变量.实例成员名;
    • 定义类时,如果成员未被static润饰,则所定义的成员为实例成员,如:

      int i=10;void f(){…}
  • 实例成员的存储调配

    • 通常,类只是形容,通过应用new,对对象进行存储空间调配。未被static所润饰的成员是对象的一部分,因而,这种实例成员的存储调配随同对象的调配而调配。
    • 例如:

      class T{int i;void f(){}}。。。T t1,t2;t1=new T();t2=new T();

      则此时,i有两个正本,一个在t1中,一个t2中,通过t1.i及t2.i应用

    类成员
  • 类成员(包含类成员变量及类办法)属于类,通过类名拜访,也可通过援用拜访:

    • 类名.类成员名;
    • 定义类时,如果成员被static润饰,则所定义的成员为类成员,如:

      static int count=0;public static void main(String args[]){…}
  • 类成员的存储调配

    • static用于成员变量前,则不论有多少对象,该成员变量只保留一份公共存储,称为类变量(动态变量);
    • 如果在定义方法时,应用static进行润饰,称为类办法(静态方法),则在调用该static办法,则该办法不依赖具体对象,既是你能够调用某个办法,而不必创建对象。
    • 对于类成员的应用,即能够应用类名应用,也能够通过援用应用,如:

      class T{static int i=47;static void f(){i++;}}。。。T t1,t2;t1=new T();t2=new T();

      则此时,i只有一份,既通过t1.i及t2.i应用,也可通过T.i援用,其值均为47。在执行T.i++;后t1.i及t2.i的值均为48;对f的调用既能够以如t1.f();也能够T.f();形式应用。依据java编程标准,咱们倡议对于static成员,只采纳类名援用的形式。

类成员、实例成员总结
  • 当你应用static就意味着,这个成员变量或办法不依赖于任何该类的对象,所以无需创建对象,就可应用该static数据或办法。
  • 对于non-static 数据和办法则必须创建对象,而后应用该对象操作 non-static 数据和办法。
  • 由此,因为 static 办法不须要创建对象,所以static 办法不能直接存取非static成员。
  • 而non-static 办法则能够间接调用static成员

    public class Test{  static int i=0;  int j=10;  static void f(){i++;}  void s(){f(); j++;}  public static void main(String[] args){      Test t=new Test();      t.s();      System.out.println(i);  }}
  • static的一个重要利用就是无需创建对象而调用办法。比方main( )
  • 在定义一个类时,共性的定义为non-static,共性的定义为static

this

在Java中咱们常常看到对this这个关键字地应用。它的应用有两种场合:

  • this作为一个援用用来援用本身,每个对象都有一个this。在类的成员办法中,this用于示意对本对象内的办法或者变量的应用。如果在不会引起歧义的状况下,this是能够省略的。比方下面的一系列setter办法中,因为参数同成员变量同名,为了以示区别,成员变量前的this就不能省略了。

    this不能用在静态方法中。因为静态方法的执行基本不须要java对象的存在,而是间接应用 类名.办法 的形式拜访,而this代表的是以后对象,所以在静态方法中根本无法应用this。

  • this能够用在构造方法中,对本类中其它构造方法进行调用

    • 语法:this([实参]);
    • 作用:在一个构造方法中去调用另一个构造方法。
    • 目标:代码重用。
    • this(实参):必须是构造方法中第一个被执行的语句,且只被调用一次。

    咱们读读上面的代码

    public class Flower {    int petalCount = 0;    String s = new String("null");    Flower(int petals) {        petalCount = petals;        System.out.println("Constructor with int arg only, petalCount= "+ petalCount);    }    Flower(String ss) {        System.out.println("Constructor with String arg only, s=" + ss);        s = ss;    }    Flower(String s, int petals) {        this(petals); //调用Flower(petals),但不能写作Flower(petals)        //! this(s); // Can't call two!        this.s = s; // 这是this的另一种应用形式        System.out.println("String & int args");    }    Flower() {        this("hi", 47);        System.out.println("default constructor (no args)");    }    void print() {        //! this(11); // Not inside non-constructor!        System.out.println("petalCount = " + petalCount + " s = "+ s);    }    public static void main(String[] args) {        Flower x = new Flower();        x.print();    }} ///:~

private构造方法

在构造方法前也能够有public等修饰词用于限定构造方法的存取权限。个别状况下,如果类是public,构造方法也是public,这样通过new能力调用。所以,如果构造方法后面被private 润饰,那会产生什么事件呢?咱们在类外就不能生成该类的对象了。大家能够本人测试一下。

那是不是private就不能用在构造方法后面了呢?当然能够,而且这是一种很有用的状况。在有些状况下,有些类在零碎中只能容许存在一个该类的实例(对象),这时,咱们就能够把该类的构造方法定义为private的。示例代码如下:

public class Handler {    //handler变量用来保留该类对象的援用    private static Handler handler = null;    private Handler() {        /* set something here */    }    //该类的对象只能通过getHandler办法从handler这个公有变量中获取    public static getHandler(/* arglist */) {        //如果handler值非空,阐明曾经在零碎中存在该类的对象,间接取出,不再生成,这就保障了单例        if (!handler)             handler = new Handler();       return handler;    }    public static void main(String args[]) {         Handler.getHandler();    }}