抽象概念——类(class)——对象模板
具体——实例(instance)——对象实例
定义class:
class Person {
public String name;
public int age;
}
字段(field)描述一个类的特征
类把一组数据汇聚到一个对象上,实现数据封装
public
修饰字段,表示该字段可被外界访问
创建实例:
new 根据模板创造对象实例
Person ming = new Person();
访问实例变量可以用变量.字段
方法
为防止外界直接操作内部字段(field
),使用private
修饰
class Person {
private String name;
private int age;
}
此时可使用方法(method
)使外部代码间接修改field
调用方法语法:实例变量.方法名(参数);
定义方法:
修饰符 方法返回类型 方法名(方法参数列表) {
若干方法语句;
return 方法返回值;
}
方法返回值通过return
语句实现,返回类型设为void
时可省略return
private
方法内部调用
方法可以封装一个类的对外接口,调用方不需要知道也不关心Person实例在内部到底有没有age字段
this变量:始终指向当前实例(具体对象)
命名没有冲突时可以省略
局部变量和字段重名时,局部变量优先级高,此时指向字段用this
方法参数用于接收传递给方法的变量值
可变参数用类型...
定义,相当于数组类型,如:
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"); // 传入3个String
g.setNames("Xiao Ming", "Xiao Hong"); // 传入2个String
g.setNames("Xiao Ming"); // 传入1个String
g.setNames(); // 传入0个String
麻烦之处:
调用方可以传入null
,但在此为一个空数组
调用方需要自己先构造String[]
参数绑定
调用方把参数传递给实例方法时,调用时传递的值会按参数位置一一绑定
计算完毕方法返回值后,改变传入参数
基本类型的为复制,不影响
引用类型的会影响
public class Main {
public static void main(String[] args) {
Person p = new Person();
String bob = "Bob";
p.setName(bob); // 传入bob变量
System.out.println(p.getName()); // "Bob"
bob = "Alice"; // bob改名为Alice
System.out.println(p.getName()); // "Bob"还是"Alice"?
}
}
class Person {
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
为何是Bob而不是Alice的解释:
String bob
中this.name = name;
时,是将字符串的地址赋给字段地址,变内容时为直接改变地址,原地址不变,而String[] bob
时,fullname[0] = "Bart"
是将指向地址内的内容改变
构造方法
一个类没有定义构造方法,编译器会自动为我们生成一个默认构造方法,无参数,无执行语句
class Person {
public Person() {
}
}
可定义既无参数的构造方法,又有参数的构造方法
没有在构造方法中初始化字段时,字段为默认类型
也可直接定义字段时初始化
构建对象实例时先初始化字段,再执行构造字段代码进行初始化
多构造方法
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(String name) {
this.name = name;
this.age = 12;
}
public Person() {
}
}
通过new
操作符调用的时候,编译器通过构造方法的参数数量、位置和类型自动区分
重载方法
也依据方法参数输入识别
继承
extends
class Person {
private String name;
private int age;
public String getName() {...}
public void setName(String name) {...}
public int getAge() {...}
public void setAge(int age) {...}
}
class Student extends Person {
// 不要重复name和age字段/方法,
// 只需要定义新增score字段/方法:
private int score;
public int getScore() { … }
public void setScore(int score) { … }
}
子类获得父类的所有字段,但不能定义与父类重名的字段(???为什么不能,如果只是覆盖的话?)
OOP中:
Person
称为超类(super class)
,父类(parent class)
,基类(base class)
,把Student
称为子类(subclass)
,扩展类(extended class)
继承树
若未明确extends
类,编译器会自动加上extends Object
Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类,只有Object
没有父类
protected
继承的子类无法访问父类的private
字段或方法
为了让子类可访问父类的字段、方法用protected
代替private
super表示父类(超类)
子类引用父类的字段时,可以用super.fieldName
一般无需使用
但父类没有默认的构造方法,子类就必须显式调用super(...)
并给出参数以便让编译器定位到父类的一个合适的构造方法
(子类不会继承父类构造方法)
向上转型
子类可直接赋值给父类(?子类的多余字段会不会消失?)
向下转型(downcasting)
Person p1 = new Student(); // upcasting, ok
Person p2 = new Person();
Student s1 = (Student) p1; // ok
Student s2 = (Student) p2; // runtime error! ClassCastException!
向下转型时,只可在于用父类定义,但实例化(指向)为子类时,将其使用强制类型转换(Student)赋值给定义、实例化为子类的子类;不可定义、实例化(指向)都为父类时,赋给子类——因为父类的功能较少,无法凭空产生(会报错“ClassCastException
”)
利用instanceof
,在向下转型前可以先判断:
若变量指向实例为指定类型或其子类,true
否则(包括变量为null
)为flase
Person p = new Student();
if (p instanceof Student) {
// 只有判断成功才会向下转型:
Student s = (Student) p; // 一定会成功
}
从Java 14开始,判断(即经过这个运算符后)instanceof
后,可以直接转型为指定变量,避免再次强制转型(避免下面还要写括号)
使用该运算符时必须打开编译器开关--source 14
和--enable-preview
区分继承、组合
继承是is关系,组合是has关系
虽然book有name,但是student不应该从book继承而应是person,但student可以持有一个book(has)
class Student extends Person {
protected Book book;
protected int score;
}
多态
覆写(Override):继承中,子类定义与父类方法签名完全相同的方法
如:父类Person
定义run()
方法
子类Student
覆写该方法
class Student extends Person {
@Override
public void run() {
System.out.println("Student.run");
}
}
Override
和Overload
不同的是,如果方法签名不同,就是Overload
,Overload
方法是一个新方法;如果方法签名相同,并且返回值也相同,就是Override
返回值不同时,也是不同的方法,但java中编译器会报错
class Person {
public void run() { … }
}
class Student extends Person {
public void run(String s) { … }
// 不是Override,因为参数不同:
public int run() { … }
// 不是Override,因为返回值不同:
}
加上@Override
可以让编译器检查是否进行了正确的覆写,但其非必需
Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型
即若声明为person,但实例化(指向)为student,此时方法覆写时调用的为实例化的方法(?是不是可以调用student的所有字段方法,即使person中没有?),此特征称为多态
多态(Polymorphic)
多态的特性:运行期才能动态决定调用的子类方法。对某个类型调用某个方法,执行的实际方法可能是某个子类的覆写方法