0%

Java|面向对象学习

image-20210904093908758

面向对象基础

1.class和instance

class即类,instance即实例。

class是一种对象模版,它定义了如何创建实例,因此,class本身就是一种数据类型;

而instance是对象实例,instance是根据class创建的实例,可以创建多个instance,每个instance类型相同,但各自属性可能不相同。

2.实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package oop;

// 定义一个city类
class City{
// 创建类的字段即属性
public String name;
public double lattitude;
public double longitude;
}


public class oop1 {
public static void main(String[] args) {
// 创建实例frist
City frist = new City();
// 对实例的属性赋值
frist.name = "shanghai";
frist.lattitude = 11;
frist.longitude = 111;

// 访问实例属性并打印
System.out.println(frist.name);
System.out.println(frist.lattitude);
System.out.println(frist.longitude);
}
}
3. 实例讲解
  • City类中包含了3个字段(field,有些语言叫做属性),通过class我们实现了数据的封装
  • public可以用来修饰方法和字段,表示其可以被外部访问
  • 创建实例用new,用Class名作为实例的数据类型
  • 访问实例属性时可以用变量.字段的形式,但是如果用private修饰了该属性则会报错

方法

1.方法的引入

上一级中,我们直接把属性用public修饰,把属性暴露给外部可能会破坏封装性。所以我们往往采用private修饰属性,然后通过方法来读取属性赋值

2.参数绑定

基本类型参数的传递,是调用方值的复制。双方各自的后续修改,互不影响

3.实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package oop;

public class opp2 {

public static void main(String[] args) {
Person Jhon = new Person();
// 调用实例的方法
Jhon.setBirth(2000);
System.out.println(Jhon.getage());
}


}

class Person{
// 定义私有属性,外部不可调用
private String name;
private int birth;

// this永远指向当前实例,从而达到赋值的目的
public void setBirth(int birth){
this.birth = birth;
}

public int getage(){
// 调用类内部私有的方法
return calcAge(2020);
}

// 私有方法,仅允许类内部调用
private int calcAge(int currentYear){
return currentYear - this.birth;
}
}

4.实例讲解
  • public int getage()该语句中,public用于修饰方法是否供外部调用,int则是该方法的返回数据类型
  • 通过this.field就可以访问当前实例的字段,如果命名没有冲突实际上可以省略this
  • this.birth = birth前一个birth指向实例的一个属性,后一个birth则是外部传入的参数,因为命名重复,所以要用this修饰

构造方法

1.构造方法应用

创建对象实例时,我们可以使用构造方法让内部字段初始化,其相当于类内部一个和类同名的特殊的方法。

2.默认构造方法

如果一个类没有定义构造方法,编译器会为我们生成一个默认的构造方法,因而我们可以在new Person()调用

如果我们自定义了一个构造方法,那么默认的构造方法将不会被创建

3.实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package oop;

public class opp3 {
public static void main(String[] args) {
// 调用构造方法
Person2 ming = new Person2("小明", 21);
System.out.println(ming.getAge());
System.out.println(ming.getName());


}
}

class Person2{
private String name;
private int age;

public String getName(){
return name;
}

public int getAge(){
return age;
}

// 创建构造方法
public Person2(String name,int age){
this.age = age;
this.name = name;
}

}

方法重载

1.方法重载介绍

在一个类中,如果存在一系列的方法功能方法名一样,但是参数不同。这种方法名相同,但各自的参数不同,称为方法重载(Overload)

方法重载参数不同,但是返回值类型通常相同

2.实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package oop;

public class opp4 {
public static void main(String[] args) {
Person4 ming = new Person4();
ming.setName("xiao", "ming");
System.out.println(ming.getname());
}

}

class Person4{
private String name;

public String getname(){
return name;
}

public void setname(String name) {
this.name = name;
}

public void setName(String fname, String lname) {
this.name = fname + lname;
}
}

继承

1.继承的应用

通过继承子类可以获得父类的所有功能,还可以在父类基础上编写额外的功能。

类自动获得了父类的所有字段,严禁定义与父类重名的字段

父类又可以称为超类,基类

子类又可以称为扩展类

2.继承树

除了object外,任何类都会继承于某个类,一个子类只能有一个父类,一个父类允许存在多个子类

3.protected与private

如果父类中的属性或方法是private修饰,子类同样也是无法访问到的,这时我们可以使用protected修饰,可以把属性和方法的权限控制在继承树内部

4.子类的构造方法

子类是无法继承父类的任何构造方法的,编译器会自动为我们添加super()来调用父类的构造方法,故若是父类存在带参数的构造方法,要手动在子类中调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Main {
public static void main(String[] args) {
Student s = new Student("Xiao Ming", 12, 89);
}
}

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) {
// 调用父类的构造方法Person(String, int)
super(name, age);
this.score = score;
}
}

7.向上转型与向下转型

(1)向上转型

1
2
3
4
//如果一个引用变量的类型是Student,那么它可以指向一个Student类型的实例
Student s = new Student();
// 一个引用类型为Person的变量同样可以指向Student类型的实例
Person p = new Student();

这种把一个子类类型安全地变为父类类型的赋值,被称为向上转型(upcasting)

注意到继承树是Student > Person > Object,所以,可以把Student类型转型为Person,或者更高层次的Object

1
2
3
4
Student s = new Student();
Person p = s;
Object o1 = p;
Object o2 = s;

(2)向下转型

如果把一个父类类型强制转型为子类类型,就是向下转型(downcasting)

1
2
3
4
5
6
Person p1 = new Student();
Person p2 = new Person();
// 向下转型成功,因为p1虽然变量类型是Person,实际内容是子类,故能转型成功
Student s1 = (Student) p1;
// 向下转型失败,实际内容是父类,不能由父类转向子类
Student s2 = (Student) p2;
6.实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class Main {
public static void main(String[] args) {
Person p = new Person("小明", 12);
Student s = new Student("小红", 20, 99);
// 定义PrimaryStudent,从Student继承,新增grade字段:
Student ps = new PrimaryStudent("小军", 9, 100, 5);
System.out.println(ps.getScore());
}
}

class Person {
protected String name;
protected int age;

public Person(String name, int age) {
this.name = name;
this.age = 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; }
}

class Student extends Person {
protected int score;

public Student(String name, int age, int score) {
super(name, age);
this.score = score;
}

public int getScore() { return score; }
}

class PrimaryStudent extends Student {
protected int grade;
public PrimaryStudent(String name, int age, int score, int grade){
// 调用Student的构造函数
super(name, age, score);
this.grade = grade;

}
}

多态

1.覆写

子类中不能存在与父类重名的属性,但子类可以覆写父类的方法,覆写方法时方法名和返回类型要相同

1
2
3
4
5
6
7
8
9
class Person {
public void run() {}
}

public class Student extends Person {
// 加上@Override 可以让编译器帮助检查是否正确覆写
@Override
public void run(String s) {}
}
2.多态

Java的实例方法调用是基于运行时的实际类型的动态调用(new Student()),而非变量的声明类型(Person

多态是指,针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法

多态具有一个强大的功能——允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码

3.实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class Main {
public static void main(String[] args) {
// 数组存储两部分的税收
Income[] incomes = new Income[] {
new Income(3000),
new Salary(7500),
};
System.out.println(totalTax(incomes));
}

public static double totalTax(Income... incomes) {
double total = 0;
// 此处的循环中income的实际指向可以动态改变(Income/Salary)
// getTax由于子类的多态实际上调用的方法不同
for (Income income: incomes) {
total = total + income.getTax();
}
return total;
}
}

class Income {
protected double income;

public Income(double income) {
this.income = income;
}

public double getTax() {
return income * 0.1; // 税率10%
}
}

// 子类
class Salary extends Income {
public Salary(double income) {
super(income);
}

// 重写父类的得税方法
@Override
public double getTax() {
if (income <= 5000) {
return 0;
}
return (income - 5000) * 0.2;
}
}
4.多态补充

(1)调用父类中被覆写的方法用super

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person {
protected String name;
public String hello() {
return "Hello, " + name;
}
}

Student extends Person {
@Override
public String hello() {
// 调用父类的hello()方法:
return super.hello() + "!";
}
}

(2)final修饰可以防止方法被子类覆写如:public final String hello()

抽象类

1.面向抽象编程

抽象类和其中的抽象方法本身是无法执行的,其强制了子类必须实现其定义的抽象方法,即相当于定义了一个规范

这种引用高层类型,避免引用子类型的方式,称为面向抽象编程

有以下优点:

  • 上层代码只定义规范(例如:abstract class Person);
  • 不需要子类就可以实现业务逻辑(正常编译);
  • 具体的业务逻辑由不同的子类实现,调用者并不关心
2.实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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");
}
}

接口

1.接口介绍

接口(interface)中没有字段,接口中定义的所有方法默认为public abstract

2.接口实现

当用一个具体的class实现一个interface时,需要用到implements关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 定义一个接口
interface Person {
void run();
String getName();
}

// 定义一个类实现接口,一个类只能继承一个类,但是可以实现多个接口
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;
}
}
3.接口继承

接口也可以通过继承扩展

1
2
3
4
5
6
7
8
interface Hello {
void hello();
}

interface Person extends Hello {
void run();
String getName();
}

静态

1.静态字段

class中定义的字段我们称为实例字段,其一般有着独立性,各个实例间互不影响

但是如果我们用static一个静态修饰一个字段,那么所有的实例都会共享这个字段的空间

对于静态字段,我们无论修改哪个实例,所有实例的静态字段都会改动

我们往往采用类名.静态字段访问静态对象,如:Person.number = 99;

2.静态方法

静态方法可以通过类名直接调用,不需要一个实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
public static void main(String[] args) {
Person.setNumber(99);
System.out.println(Person.number);
}
}

class Person {
public static int number;

// 定义一个静态方法,因为静态方法不存在实例,所以无法访问this变量(不能用this)
public static void setNumber(int value) {
number = value;
}
}
3.接口的静态字段

因为接口是一个纯抽象类,所以它不能定义实例字段,但是可以有静态字段

1
2
3
4
5
public interface Person {
// 编译器会自动加上public statc final:
int MALE = 1;
int FEMALE = 2;
}

位于同一个包的类,可以访问包作用域的字段和方法。不用publicprotectedprivate修饰的字段和方法就是包作用域

可以用import语句导入其他包的class

作用域

可参考:https://www.liaoxuefeng.com/wiki/1252599548343744/12604662156765122