# 关键字、类与方法深入
# 成员变量、返回值与构造器
类的成员变量与类的成员属性这两个称呼指代的都是同一样东西, 由于网上的教程、书籍等称呼各不相同, 本教程我们成其为成员变量.
在正常的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
这只猫性别:女
注意getAge
与getSex
, 它们没有void
, 取而代之的是int
. 这代表这两个方法有返回值, 返回值是int
类型的数据. 所以cat2.getAge()
就代表着这个对象的age
成员变量的值, 把它放到System.out.println
中就能输出3
了.
age
和sex
放在了类里, 而不是某个方法里, 它们叫做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;
}
}
在上面的例子中我们又加了两个方法setAge
和setSex
.
这两个方法是没有返回值的, 但是它们都有参数, 接受的分别是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
. 你可以认为在使用变量时有这么两条规则, 我们把一对花括号里的内容看做一个区域:
- 同一个区域不能有两个名字一样的变量
- 在里层使用某个变量, 如果外层区域也有一个名字与之一样的变量, 使用时更优先使用最考进里层的变量.
根据第二条优先规则, 输出语句调用了在方法中的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中到底有多少关键字.
这里主要介绍public
与private
, 后面我们还会介绍一个关键字.
没有加public和private的成员变量, 其默认带有protected关键字. 但是因为前面提到了不讨论成员变量的继承, 这里不提及, 但这并不意味着它不重要, 也不意味着你不用知道.
正如其字面意思, public
代表共有, private
代表私有.
我们以变量举例, 如果你在某个方法里定义了一个变量给它带上了public
, 这是没意义的(你也不能这么干,会报错), JVM会认为你是个傻憨憨, 因为这些变量是临时变量, 只能在当前的方法执行时使用, 方法执行完了它们就没有了, 等待这个方法被再次调用时重新被定义.
但是你肯定想到了, 如果一个对象一直存在, 那么它的成员变量肯定是一直存在的. 对! 对它们用public
和private
修饰是有意义的.
对于主类, 其实把它的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 = "成员常量这样声明";