面向对象基础
class和instance
定义class
class是一种对象模板,定义了如何创建实例,它本身就是一种数据类型。
class Penson {public String name;public int age;
}
class可以包含很多字段(field),来描述一个类的特征。
public是用来修饰字段的,表示这个字段可以被外部访问。
创建实例
instance是对象实例,根据class创建的实例,可以创建多个instance,每个类型相同,各自属性可能不相同。
用new操作符可以创建一个实例,然后定义一个引用类型的变量来指向这个实例:
Person ming = new Person();
- Person ming 是定义变量
- new Person() 是创建实例
访问实例变量可以用变量.字段 :
Person ming = new Person();
ming.name = "Xiao Ming";
ming.age = 12;
System.out.println(ming,name);Person hong = new Person();
hong.name = "Xiao HOng";
hong.age = 15;
一个Java源文件可以包含多个类的定义,但只能定义一个public类,而且该public类名必须与文件名一致。
如果要定义多个public类,必须拆到多个Java源文件中。
练习
public class Main {public static void main(String[] args) {City bj = new City();bj.name = "Beijing";bj.latitude = 39.903;bj.longitude = 116.401;System.out.println(bj.name);System.out.println("location: " + bj.latitude + ", " + bj.longitude);}
}
class City {public String name;public double latitude;public double longitude;
}
方法
直接把field用public暴露给外部可能会破坏封装性。为了避免这种情况,可以用private修饰字段field,拒绝外部访问。
class Person {private String name;private int age;
}
外部代码不能访问了,那么我们要如何给它赋值或读取它的值呢?
可以使用方法(method)来让外部代码可以间接修改field:
public class Main {public static void main(String[] args) {Person ming = new Person();ming.setName("Xiao Ming");ming.setAge(12);System.out.println(ming.getAge() + "," + ming.getName());}
}
class Person {private String name;private int age;public String getName() {return this.name;}public void setName(String name) {this.name = name;}public int getAge() {return this.age;}public void setAge(int age) {if(age<0||age>100){throw new IllegalArgumentException("invalid age value");}this.age = age;}
}
外部代码可以通过调用方法setName()和setAge()来间接修改private字段。在方法内部就可以写东西判断参数是否符合要求。
对于setName()方法也可以做检查,举个例子:不允许传入null和空字符串:
public void setName(String name) {if(name == null || name.isBlank()){throw new IllegalArgumentException("invalid name");}this.name = name.strip();//去掉首尾空格
}
同样,读取private字段也是用setName()和getName()间接获取。
一个类通过定义方法,可以给外部代码暴露一些操作的接口,同时保证自己逻辑一致性。
定义方法
修饰符 方法返回类型 方法名(方法参数列表) {若干方法语句;return 方法返回值;}
private方法
与private字段一样不允许外部调用,定义private方法理由是内部方法是可以调用private方法的:
public class Main {public static void main(String[] args) {Person ming = new Person();ming.setBirth(2008);System.out.println(ming.getAge());}
}
class Person {private String name;private int birth;public void setBirth(int birth) {this.birth = birth;}public int getAge() {return calcAge(2019);//调用private方法}//private方法private int calcAge(int currentYear) {return currentYear - this.birth;}
}
calcAge()是一个private方法,外部代码不能调用,但是内部方法getAge()可以调用,。
这个Person类没有定义age字段,获取age时,通过方法getAge()返回的是一个实时计算的值,并非存储在某个字段的值。这说明方法可以封装一个类的对外接口,调用方不需要知道也不关心Person
实例在内部到底有没有age
字段.
this变量
如果有命名冲突,会优先为距离进的,如果想要是远的,就可以使用this关键字。
//冲突情况
class Person {private String name;public void setName(String name) {this.name = name;}
}
//不冲突
class Person {private String name;public void setName(String n) {name = n;}
}
方法参数
方法可以包含包含0个或任意个,但是必须要一一对应。
可变参数
可变参数用类型...(这里就是英文格式三个点,可见如下代码)定义,可变参数相当于数组类型:
class Group {private String[] names;public void setNames(string... names) {this.names = names;}
}
调用则这样写:
Group g = new Group();
g.setNames("Xiao MIng","Xiao Hong","Xiao Jun");
g.setNames("Xiao MIng","Xiao Hong");
g.setNames("Xiao MIng");
g.setNames();
也可以把可变参数写为String[]类型:
class Group {private String[] names;public void setNames(String[] names) {this.names = names;}
}
但是调用方法需要自己先构造String[],比较麻烦,
Group g = new Group();
g.setNames(new String[] {"Xiao Ming","Xiao Hong","Xiao Jun:});
另外一个问题是:调用方可以传入null:
Group g = new Group();
g.setNames(null);
而可变参数可以保证无法传入null,因为传入0个参数时,接收到的实际值是一个空数组而不是null。
参数绑定
调用方把参数传递给实例方法时,调用时传递的值会按参数位置一一绑定。
基本类型参数的传递
public class Main {public static void main(String[] args) {Person p = new Person();int n = 15;p.setAge(n);System.out.println(p.getAge());n=20;System.out.println(p.getAge());}
}class Person {private int age;public int getAge() {return this.age;}public void setAge(int age) {this.age = age;}
}
修改外部的局部变量n,不影响实例p的age字段,这是因为setAge()方法获得的参数复制了n的值。所以:p.age和局部变量n互不影响。
结论:
基本类型参数的传递,是调用方值的复制。双方后续各自的修改互不影响。
传递引用参数
public class Main {public static void main(String[] args) {Person p = new Person();String[] fullName = new String[] { "Homer", "Simpson" };p.setName(fullName);System.out.println(p.getName());fullName[0] = "Bart";System.out.println(p.getName());}
}
class Person {private String[] name;public String getName() {return this.name[0] + " " + this.name[1];}public void setName(String[] name) {this.name = name;}
}
引用类型参数的传递中,调用方的变量和接收方的参数变量指向同一个对象。双方的改变会影响另一方。
public class Main {public static void main(String[] args) {Person p = new Person();String bob = "Bob";p.setName(bob);System.out.println(p.getName());bob = "Alice";System.out.println(p.getName());}
}class Person {private String name;public String getName() {return this.name;}public void setName(String name) {this.name = name;}
}
以上程序输出两次bob,分析如下:
字符串是不可变对象,一旦创建,内容不可被修改,变量bob是一个指向字符串对象的引用,重新赋值让bob这个引用指向新的字符串"Alice",但是p对象的name属性还指向之前字符串"Bob"。
练习
public class Main {public static void main(String[] args) {Person ming = new Person();ming.setName("小明");ming.setAge(12);System.out.println(ming.getAge());}
}class Person {private String name;private int 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;}
}
小结
- 方法可以让外部代码安全的访问实例字段
- 方法是一组执行语句,可以执行任意逻辑
- 方法内部遇到return时返回
- 外部代码通过public方法操作实例,内部代码可以调用private方法