class
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;
}
}
this变量
- 如果没有命名冲突,可以省略this
- 如果有局部变量和字段重名,那么局部变量优先级更高,就必须加上this
参数绑定
- 基本类型参数的传递,是调用方值的复制。双方各自的后续修改,互不影响。
- 引用类型参数的传递,调用方的变量,和接收方的参数变量,指向的是同一个对象。双方任意一方对这个对象的修改,都会影响对方
例1
public class Hello {
public static void main(String[] args) {
Person p = new Person();
String[] fullname = new String[] { "Homer", "Simpson" };
p.setName(fullname); // 传入fullname数组
System.out.println(p.getName()); // "Homer Simpson"
fullname[0] = "Bart"; // fullname数组的第一个元素修改为"Bart"
System.out.println(p.getName()); // "Homer Simpson"还是"Bart Simpson"?
}
}
class Person {
private String[] name;
public String getName() {
return this.name[0] + " " + this.name[1];
}
public void setName(String[] name) {
this.name = name;
}
}
output:
Homer Simpson
Bart Simpson
例2
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”。bob指向“Bob”时,将“Bob”的地址传递给了p.setname方法,因为字符串具有不可变性,之后名字的改变并不是将传递地址中的“Bob”变为了“Alice”,而是将重新开辟了一个地址存储“Alice”。之前传递的地址仍然指向“Bob”。
构造方法
- 构造方法的名称就是类名
- 构造方法的参数没有限制
- 构造方法没有返回值
- 如果一个类没有定义构造方法,编译器会自动为我们生成一个默认构造方法,它没有参数,也没有执行语句
- 如果自定义了一个构造方法,那么编译器就不再自动创建默认构造方法
- 如果既要能使用带参数的构造方法,又想保留不带参数的构造方法,那么只能把两个构造方法都定义出来
- 可以定义多个构造方法,在通过new操作符调用的时候,编译器通过构造方法的参数数量、位置和类型自动区分
- 构造方法可以调用其他构造方法,调用其他构造方法的语法是this(…)
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, 18); // 调用另一个构造方法Person(String, int)
}
public Person() {
this("Unnamed"); // 调用另一个构造方法Person(String)
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
}
方法重载
- 参数的类型和个数不同,返回值相同,方法名相同
继承
- Java使用extends关键字来实现继承
- 在Java中,没有明确写extends的类,编译器会自动加上extends Object。所以,任何类,除了Object,都会继承自某个类
- Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。
- 为了让子类可以访问父类的字段,我们需要把private改为protected
- super关键字表示父类(超类),子类引用父类的字段时,可以用super.fieldName
class Person {
protected String name;
protected int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
class Student extends Person {
protected int score;
public Student(String name, int age, int score) {
super(name, age); // 调用父类的构造方法Person(String, int)
this.score = score;
}
}
向上转型
- 父类类型的变量指向子类类型的实例,对它进行操作是没有问题的。
- 向上转型实际上是把一个子类型安全地变为更加抽象的父类型
Student s = new Student();
Person p = s; // upcasting, ok
Object o1 = p; // upcasting, ok
Object o2 = s; // upcasting, ok
向下转型
Person p1 = new Student(); // upcasting, ok
Person p2 = new Person();
Student s1 = (Student) p1; // ok
Student s2 = (Student) p2; // runtime error! ClassCastException!
instanceof
为了避免向下转型出错,Java提供了instanceof操作符,可以先判断一个实例究竟是不是某种类型
Person p = new Person();
System.out.println(p instanceof Person); // true
System.out.println(p instanceof Student); // false
Student s = new Student();
System.out.println(s instanceof Person); // true
System.out.println(s instanceof Student); // true
Student n = null;
System.out.println(n instanceof Student); // false
多态
- 在继承关系中,子类如果定义了一个与父类方法签名完全相同的方法,称为覆写(override)
- 加上@Override可以让编译器帮助检查是否进行了正确的覆写。
public class Hello {
public static void main(String[] args) {
Person p = new Student();
p.run(); // 应该打印Person.run还是Student.run?
}
}
class Person {
public void run() {
System.out.println("Person.run");
}
}
class Student extends Person {
@Override
public void run() {
System.out.println("Student.run");
}
}
output:
Student.run
Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。这个非常重要的特性在面向对象编程中称之为多态。它的英文拼写非常复杂:Polymorphic。
覆写Object方法
因为所有的class最终都继承自Object,而Object定义了几个重要的方法:
- toString():把instance输出为String
- equals():判断两个instance是否逻辑相等
- hashCode():计算一个instance的哈希值
可以覆写Object的这几个方法
class Person {
...
// 显示更有意义的字符串:
@Override
public String toString() {
return "Person:name=" + name;
}
// 比较是否相等:
@Override
public boolean equals(Object o) {
// 当且仅当o为Person类型:
if (o instanceof Person) {
Person p = (Person) o;
// 并且name字段相同时,返回true:
return this.name.equals(p.name);
}
return false;
}
// 计算hash:
@Override
public int hashCode() {
return this.name.hashCode();
}
}
调用super
在子类的覆写方法中,如果要调用父类的被覆写的方法,可以通过super来调用
class Person {
protected String name;
public String hello() {
return "Hello, " + name;
}
}
Student extends Person {
@Override
public String hello() {
// 调用父类的hello()方法:
return super.hello() + "!";
}
}
final
- 如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为final。用final修饰的方法不能被Override
- 如果一个类不希望任何其他类继承自它,那么可以把这个类本身标记为final
- 对于一个类的实例字段,同样可以用final修饰。用final修饰的字段在初始化后不能被修改
抽象类
如果一个class定义了方法,但没有具体执行代码,这个方法就是抽象方法,抽象方法用abstract修饰。因为无法执行抽象方法,因此这个类也必须申明为抽象类(abstract class)。使用abstract修饰的类就是抽象类。无法实例化一个抽象类。
public class Main {
public static void main(String[] args) {
Person p = new Student();
p.run();
}
}
abstract class Person {
public abstract void run();
}
class Student extends Person {
@Override
public void run() {
System.out.println("Student.run");
}
}
面向抽象编程
- 上层代码只定义规范
- 不需要子类就可以实现业务逻辑(正常编译)
- 具体的业务逻辑由不同的子类实现,调用者并不关心
Person s = new Student();
Person t = new Teacher();
// 不关心Person变量的具体子类型:
s.run();
t.run();
// 同样不关心新的子类是如何实现run()方法的:
Person e = new Employee();
e.run();
接口
在抽象类中,抽象方法本质上是定义接口规范:即规定高层类的接口,从而保证所有子类都有相同的接口实现,这样,多态就能发挥出威力。如果一个抽象类没有字段,所有方法全部都是抽象方法,就可以把该抽象类改写为接口:interface。
abstract class Person {
public abstract void run();
public abstract String getName();
}
interface Person {
void run();
String getName();
}
接口定义的所有方法默认都是public abstract。当一个具体的class去实现一个interface时,需要使用implements关键字。
class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(this.name + " run");
}
@Override
public String getName() {
return this.name;
}
}
在Java中,一个类只能继承自另一个类,不能从多个类继承。但是,一个类可以实现多个interface.
class Student implements Person, Hello { // 实现了两个interface
...
}
比较 | abstract class | interface |
---|---|---|
继承 | 只能extends一个class | 可以implements多个interface |
字段 | 可以定义实例字段 | 不能定义实例字段 |
抽象方法 | 可以定义抽象方法 | 可以定义抽象方法 |
非抽象方法 | 可以定义非抽象方法 | 可以定义default方法 |
default方法
实现类可以不必覆写default方法。default方法的目的是,当我们需要给接口新增一个方法时,会涉及到修改全部子类。如果新增的是default方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。
interface Person {
String getName();
default void run() {
System.out.println(getName() + " run");
}
}
静态字段
- static修饰的字段
- 静态字段只有一个共享“空间”,所有实例都会共享该字段
- 推荐用类名来访问静态字段。
静态方法
- 用static修饰的方法称为静态方法
- 静态方法内部,无法访问this变量
- 静态方法无法访问实例字段,它只能访问静态字段。
- 静态方法经常用于工具类,如Arrays.sort(),Math.random()
接口的静态字段
因为interface是一个纯抽象类,所以它不能定义实例字段。但是,interface是可以有静态字段的,并且静态字段必须为final类型。
public interface Person {
public static final int MALE = 1;
public static final int FEMALE = 2;
}
实际上,因为interface的字段只能是public static final类型,所以我们可以把这些修饰符都去掉,上述代码可以简写为:
public interface Person {
// 编译器会自动加上public statc final:
int MALE = 1;
int FEMALE = 2;
}
包
- 使用package解决命名冲突
- 在定义class时,在第一行声明这个class属于哪个包
- 包没有父子关系,com.apache和com.apache.abc是不同的包
package ming; // 申明包名ming
public class Person {
}
包作用域
- 在同一个包的类,可以访问包作用域的字段和方法
- import导入
作用域
public
- 定义为public的class,interface可以被其他任何类访问
- 定义为public的字段,方法可以被其他类访问,前提是首先具有访问class的权限
private
- 定义为private的字段,方法无法被其他类访问
- private访问权限被限定在class内部,推荐把private方法放到后面,因为public方法定义了类对外提供的功能,阅读代码的时候应该先关注public方法
- java支持嵌套类,嵌套类拥有访问private的权限
protected
- protected作用于继承关系,定义为protected的字段和方法可以被子类访问,以及子类的子类
package
- 包作用域是指一个内允许访问同一个package的没有public,private修饰的class,以及没有public,protected,private修饰的字段和方法
package abc;
// package权限的类:
class Hello {
// package权限的方法:
void hi() {
}
}
package abc;
class Main {
void foo() {
// 可以访问package权限的类:
Hello h = new Hello();
// 可以调用package权限的方法:
h.hi();
}
}
局部变量
- 在方法内部定义的变量成为局部变量
- 局部变量作用域从变量声明处开始到对应的块结束
- 方法参数也是局部变量
- 使用局部变量时,应该尽可能把局部变量的作用域缩小,尽可能延后声明局部变量
final
- final修饰class可以阻止被继承
- final修饰的方法可以阻止被子类覆写
- final修饰的字段可以阻止被重新赋值
- final修饰的局部变量可以阻止被重新赋值
注意:一个.java文件只能包含一个public类,但可以包含多个非public类,如果有public类,文件名必须和public类的名字相同