# 关键字、类与方法深入

# 成员变量、返回值与构造器

类的成员变量与类的成员属性这两个称呼指代的都是同一样东西, 由于网上的教程、书籍等称呼各不相同, 本教程我们成其为成员变量.
在正常的Java教程中, 这里有一项重要的内容是成员变量的继承, 但因为太复杂, 这里不再展开, 这并不意味着这部分内容没有用, 如果需要了解, 请在网上搜索自行学习.

每个猫都会有自己的一些属性, 比如它是男是女, 年龄多大.
之前我们使用变量时, 变量都出现在了方法里. 那么现在变量是不是应该出现在类里, 来储存这个对象的特有属性呢?

class Cat extends Animal {
    private int age = 3;
    private boolean sex = true; //我们假设 true 代表女, false 代表男

    @Override
    public void say(){
        System.out.println("喵喵喵");
    }

    public int getAge(){
        return age;
    }

    public boolean getSex(){
        return sex;
    }
}

我们在main方法里创建一个Cat对象:

Cat cat2 = new Cat();
System.out.println("这只猫年龄:"+cat2.getAge());
System.out.println("这只猫性别:"+cat2.getSex()?"女":"男"); //这里应该是一个if判断一下, 但是写起来太占篇幅, 因此使用了这样的略写, 看不懂就可以认为我写了一个if

输出结果

这只猫年龄:3
这只猫性别:女

注意getAgegetSex, 它们没有void, 取而代之的是int. 这代表这两个方法有返回值, 返回值是int类型的数据. 所以cat2.getAge()就代表着这个对象的age成员变量的值, 把它放到System.out.println中就能输出3了.

agesex放在了类里, 而不是某个方法里, 它们叫做Cat类的成员变量.

但是你肯定想到了, 不是每只猫的属性都一样, 每只猫的成员变量有可能不一样!

class Cat extends Animal {
    private int age = 3;
    private boolean sex = true; //我们假设 true 代表女, false 代表男

    @Override
    public void say(){
        System.out.println("喵喵喵");
    }

    public int getAge(){
        return age;
    }

    public boolean getSex(){
        return sex;
    }

    public void setAge(int age){
        this.age = age;
    }

    public void setSex(boolean sex){
        this.sex = sex;
    }
}

在上面的例子中我们又加了两个方法setAgesetSex.
这两个方法是没有返回值的, 但是它们都有参数, 接受的分别是int类型和boolean类型的参数.

我们挑出setAge单独看.
其中有this.age = age;语句, this代表当前的这个对象自己, 说白了意思就是“我自己”, “把我自己的age设置成参数里的age”.

这就产生了一个问题:

public int num = 0; //这是一个成员变量
public void a(){ //这是一个方法, 我临时举了一个例子
    int num = 1; //我方法里又定义了一个变量叫num, 这就很神奇, 并没有因为重名而报错
    System.out.println(num);
    //请问, 这句话输出结果是多少呢?
}

上面的例子答案是1. 你可以认为在使用变量时有这么两条规则, 我们把一对花括号里的内容看做一个区域:

  1. 同一个区域不能有两个名字一样的变量
  2. 在里层使用某个变量, 如果外层区域也有一个名字与之一样的变量, 使用时更优先使用最考进里层的变量.

根据第二条优先规则, 输出语句调用了在方法中的num, 而没有选择外层的num.
如果非要使用成员变量, 就应该加上this., 代表我要用的是“我自己这个对象”本来有的num, 而不是这个方法里临时设的.

System.out.println(this.num); //上面的例子改成这样就能输出0了

这样就好理解了, 参数int age其实就相当于方法里临时设了一个变量age, this.age = age;的作用显然就是把当前对象的age设置成参数收到的age变量的值, 因此setAge方法起到了设置age的作用.

但是这样如果new Cat()这样生成的对象就好像“天生被设定了年龄”一样, 可以不可以让年龄和性别在new的时候被设置?
答案是可以的.

class Cat extends Animal {
    private int age = 3;
    private boolean sex = true; //我们假设 true 代表女, false 代表男

    public Cat(boolean sex, int age){
        this.sex = sex;
        this.age = age;
    }

    @Override
    public void say(){
        System.out.println("喵喵喵");
    }

    public int getAge(){
        return age;
    }

    public boolean getSex(){
        return sex;
    }

    public void setAge(int age){
        this.age = age;
    }

    public void setSex(boolean sex){
        this.sex = sex;
    }
}

上面的例子中我们为这个类加入了构造器, 我们在新建一个Cat对象时必须这样做:

Cat cat3 = new Cat(true, 2); //必须给它参数才可以
//Cat cat4 = new Cat(); //这样就会报错

在上面的例子中你可以发现, 其实方法也可以同时接受多个参数, 英文逗号隔开即可.

需要注意, 还有这种用法:

//现在我创建了一个方法叫a, 参数是i
public void a(int i){}
//这里又弄了一个a, 参数不一样, 这俩本质不是同一个方法, 但是叫一个名
public void a(String str){}
//这样就不行了, 只有返回值不一样不可以
public int a(int i){}

# 关键字

public是干什么用的? 它是一个关键字! Java里有许多关键字, 它是其中一个.

关键字起到了修饰限定的作用. 你可以网上查一查Java中到底有多少关键字.
这里主要介绍publicprivate, 后面我们还会介绍一个关键字.
没有加public和private的成员变量, 其默认带有protected关键字. 但是因为前面提到了不讨论成员变量的继承, 这里不提及, 但这并不意味着它不重要, 也不意味着你不用知道.

正如其字面意思, public代表共有, private代表私有.
我们以变量举例, 如果你在某个方法里定义了一个变量给它带上了public, 这是没意义的(你也不能这么干,会报错), JVM会认为你是个傻憨憨, 因为这些变量是临时变量, 只能在当前的方法执行时使用, 方法执行完了它们就没有了, 等待这个方法被再次调用时重新被定义.
但是你肯定想到了, 如果一个对象一直存在, 那么它的成员变量肯定是一直存在的. 对! 对它们用publicprivate修饰是有意义的.

对于主类, 其实把它的public去掉以后, 可能程序依然能够正常运行. 但是如果你加public, 有且只有主类可以加(还记得主类是什么吗? 在开头提到了主类构成的两个条件!).

class TestClass { //为了解释方便我又临时弄了一个类出来
    private int i=0;
    public int num=0;
}

class OtherClass { //这是别的类
    public void a() { //这是别的类中的方法
        TestClass testClass = new TestClass();
        System.out.println(testClass.num); //我们可以这样直接操控它的成员变量
        System.out.println(testClass.i); //但是i是private的, 无法操控
    }
}

你可能纳闷, 既然可以直接操控, 那为什么刚才猫的年龄和性别不能直接设置成private直接用呢?
下面是我的胡说八道解释, 如果理解不了, 最简单明了的解释就是, 大家都是这么干的, 我们也这么干. 如果有一只猫对象, 你可以yourCat.sex获取它的年龄, 同时, 你随时可以yourCat.sex = 一个值;来随意设置它的性别. 请问现实生活中的猫你会允许它被随意设置性别吗?
当然是不行. 所以我们一般这种变量都把它设置为private, 并且我们只在构造器里提供参数, 一只猫一生只有一次设定性别的时候, 那就是出生的时候.
同时对于age, 假如别人拿到了这个对象, 直接yourCat.age = -1;, 这样就出大问题, 猫的年龄可能是负数吗? 不可能.
所以setAge方法中, 用if就可以防止恶作剧, 判断给的参数是不是正确. 你可以在setAge里加入这样的代码:

if(age<0 || age>100){
    System.out.println("你怕是个傻子. 你设置的年龄这合适吗???你家猫"+age+"岁?逗我玩呢??????");
    return; 
    //顺带一提, void的方法的return语句可以防止该方法继续执行
    //这样setAge方法后面的内容就不会被执行了
}

对于一个方法, 加上private可以让这个方法私有, 这样只有在这个类里才能使用这个方法, 别人用不了.

class TestClass { //为了解释方便我又临时弄了一个类出来
    public void a(){}
    private void b(){}
    void c();
}

class OtherClass { //这是别的类
    public void d(){
        new TestClass().a(); //可以用
        new TestClass().b(); //b方法是private, 只有在TestClass类里能用, 这里报错
        new TestClass().c(); //可以用
    }
}

# 静态

静态是一个比较抽象的内容. 在这里举个例子:

假如有一个人叫做老八, 我们把老八看做一个ChineseHuman对象, 叫做laoBa. 想想看, 你怎么表示老八上厕所?
也许你会说, 我可以给ChineseHuman类加一个方法goWC, 老八上厕所可以用laoBa.goWC();表示.

但是这样很奇怪, 难道只有ChineseHuman才能上厕所? EnglishHuman就不可以?
你也许会说: 简单! 它们都属于Human的子类, 我直接给Human类加一个goWC方法.
如果这样理解, 上厕所的过程很“奇怪”. 我们正常在现实生活中, 厕所是在那里摆着的, 是我们主动把一个人“送”进厕所(你就认为你自己送自己进厕所), 完成的上厕所.

现在我们面临一个问题, 厕所就在那里摆着, 它不是某个人独有的“专利”、“能力”, 而是个“公共设施”. Java为此问题加入了静态的概念, 解决我们上面的上厕所问题.

class LaobaWC { //老八厕所类
    public static void goWC(Human human){
        上厕所代码
    }
}

class Human {}

class ChineseHuman extends Human {}
class EnglishHuman extends Human {}

我们可以发现, 上面的goWC方法被带上了static关键字, 它已经成为了一个静态方法.
静态方法可以直接被调用, 你不必为调用goWC单独创建一个LaobaWC对象, 就像这样:

Human human = new ChineseHuman();
LaobaWC.goWC(human); //这就表示上厕所

现在一切都很自然了, 厕所变成了一个“公共设施”, 哪个对象想“上厕所”, 就把哪个对象丢进厕所的静态方法goWC的参数里进行调用.

同理, 成员变量与成员常量也可以设定为静态.

public static String testStatic = "就像这样";

//在别的地方用的时候
System.out.println(某某Class.testStatic); //这样就可以直接拿来使用

public static final String testFinalStatic = "成员常量这样声明";