Java是一种面向对象的编程语言。面向对象编程,英文是Object-Oriented Programming,简称OOP。

现实世界中,我们定义了“人”这种抽象概念,而具体的人则是“小明”、“小红”、“小军”等一个个具体的人。所以,“人”可以定义为一个类(class),而具体的人则是实例(instance):

现实世界 计算机模型 Java代码
类 / class class Person { }
小明 实例 / ming Person ming = new Person()
小红 实例 / hong Person hong = new Person()
小军 实例 / jun Person jun = new Person()
1
2
3
4
5
6
7
8
9
10
11
12
            ┌──────────────────┐
ming ──────>│Person instance │
├──────────────────┤
│name = "Xiao Ming"│
│age = 12 │
└──────────────────┘
┌──────────────────┐
hong ──────>│Person instance │
├──────────────────┤
│name = "Xiao Hong"│
│age = 15 │
└──────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 longitude;
public double latitude;
}

定义一个类为City,定义一个实列为bj,bj赋值name,longitude,打印对象值

一个class可以包含多个字段(field),字段用来描述一个类的特征。上面的Person类,我们定义了两个字段,一个是String类型的字段,命名为name,一个是int类型的字段,命名为age。因此,通过class,把一组数据汇集到一个对象上,实现了数据封装。

方法(field)

private 内部访问

显然,直接操作field,容易造成逻辑混乱。为了避免外部代码直接去访问field,我们可以用private修饰field,拒绝外部访问:

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
public class Hello {
public static void main(String[] args) {
Person bj = new Person();
bj.setName("myself");
bj.setAge(88);
String myselfs = bj.getName() + " " +bj.getAge();
System.out.println(myselfs);
}
}

class Person{
private String name;
private int age;

//取值
public String getName(){
return this.name;
}

public int getAge(){
return this.age;
}

//传值
public void setName(String name){
this.name = name;
}

public void setAge(int age){
if (age < 0 || age >100){
throw new IllegalArgumentException("invalid age value");
}
this.age = age;
}
}

定义一个类为Person,定义一个实列为bj,bj赋值name,longitude,定义field,定义接受对象参数,定义返回类型,return 定义返回对象结果

private方法

public方法,自然就有private方法。和private字段一样,private方法不允许外部调用,那我们定义private方法有什么用?

定义private方法的理由是内部方法是可以调用private方法的。例如:

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
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;
}
}

传递数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Hello {
public static void main(String[] args) {
Person bj = new Person();
String[] fullname = new String[]{"myself","c26"};
bj.setName(fullname);
System.out.println(bj.getName());
}
}

class Person{
private String[] name;

public String getName(){
return this.name[0];
}
public void setName(String[] name){
this.name = name;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
p.setName(bob); // 传入bob变量
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;
}
}

传值

多构造方法

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
import com.sun.scenario.effect.impl.prism.PrImage;

public class Hello {
public static void main(String[] args) {
Person bj = new Person("myself",16);
System.out.println(bj.getName()+ " " + bj.getAge());
}
}

class Person{
private String name;
private int age;

public Person(String name ,int age){
this.name = name;
this.age = age;
}

public String getName(){
return this.name;
}
public int getAge(){
return this.age;
}

}

方法重载

在一个类中,我们可以定义多个方法。如果有一系列方法,它们的功能都是类似的,只有参数有所不同,那么,可以把这一组方法名做成同名方法。例如,在Hello类中,定义多个hello()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Hello {
public void hello() {
System.out.println("Hello, world!");
}

public void hello(String name) {
System.out.println("Hello, " + name + "!");
}

public void hello(String name, int age) {
if (age < 18) {
System.out.println("Hi, " + name + "!");
} else {
System.out.println("Hello, " + name + "!");
}
}
}

这种方法名相同,但各自的参数不同,称为方法重载(Overload)。

注意:方法重载的返回值类型通常都是相同的。

方法重载的目的是,功能类似的方法使用同一名字,更容易记住,因此,调用起来更简单。

举个例子,String类提供了多个重载方法indexOf(),可以查找子串:

  • int indexOf(int ch):根据字符的Unicode码查找;
  • int indexOf(String str):根据字符串查找;
  • int indexOf(int ch, int fromIndex):根据字符查找,但指定起始位置;
  • int indexOf(String str, int fromIndex)根据字符串查找,但指定起始位置。
1
2
3
4
5
6
7
8
9
10
11
public class Main {
public static void main(String[] args) {
String s = "Test string";
int n1 = s.indexOf('t');
int n2 = s.indexOf("st");
int n3 = s.indexOf("st", 4);
System.out.println(n1);
System.out.println(n2);
System.out.println(n3);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Main {
public static void main(String[] args) {
Person ming = new Person();
Person hong = new Person();
ming.setName("Xiao Ming");
// TODO: 给Person增加重载方法setName(String, String):
hong.setName("Xiao", "Hong");
System.out.println(ming.getName());
System.out.println(hong.getName());
}
}
class Person {
private String name;

public String getName() {
return name;
}
public void setName(String name , String name1){
this.name = name + " " + name1
}
public void setName(String name) {
this.name = name;
}
}

继承

Java使用extends关键字来实现继承:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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) { … }
}

继承树

注意到我们在定义Person的时候,没有写extends。在Java中,没有明确写extends的类,编译器会自动加上extends Object。所以,任何类,除了Object,都会继承自某个类。下图是PersonStudent的继承树:

1
2
3
4
5
6
7
8
9
10
11
12
13
┌───────────┐
│ Object │
└───────────┘


┌───────────┐
│ Person │
└───────────┘


┌───────────┐
│ Student │
└───────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
       ┌───────────┐
│ Object │
└───────────┘


┌───────────┐
│ Person │
└───────────┘
▲ ▲
│ │
│ │
┌───────────┐ ┌───────────┐
│ Student │ │ Teacher │
└───────────┘ └───────────┘

多态

在继承关系中,子类如果定义了一个与父类方法签名完全相同的方法,被称为覆写(Override)。

例如,在Person类中,我们定义了run()方法:

1
2
3
4
5
class Person {
public void run() {
System.out.println("Person.run");
}
}

在子类Student中,覆写这个run()方法:

1
2
3
4
5
6
class Student extends Person {
@Override
public void run() {
System.out.println("Student.run");
}
}

多态是同一个行为具有多个不同表现形式或形态的能力。

多态就是同一个接口,使用不同的实例而执行不同操作,如图所示

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
import javafx.scene.AmbientLight;

import javax.xml.crypto.dom.DOMCryptoContext;

public class Test {
public static void main(String[] args){
show(new Cat());
show(new Dog());
// Animail a = new Cat();
// a.eat();
// Cat c = (Cat)a;
// c.work();
}
public static void show(Animail a){
a.eat();
if(a instanceof Cat){
Cat c = (Cat)a;
c.work();
}else if(a instanceof Dog){
Dog c = (Dog)a;
c.work();
}
}
}

abstract class Animail{
abstract void eat();
}

class Cat extends Animail{
public void eat(){
System.out.println("吃鱼");
}
public void work(){
System.out.println("抓老鼠");
}
}

class Dog extends Animail{
public void eat(){
System.out.println("吃骨头");
}
public void work(){
System.out.println("看家");
}
}

抽象类

如果一个class定义了方法,但没有具体执行代码,这个方法就是抽象方法,抽象方法用abstract修饰。

因为无法执行抽象方法,因此这个类也必须申明为抽象类(abstract class)。

使用abstract修饰的类就是抽象类。我们无法实例化一个抽象类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class abstarcts {
public static void main(String[] args){
Persons p = new Students();
p.run();
}
}

abstract class Persons{
public abstract void run();
}

class Students extends Persons {
@Override
public void run(){
System.out.println("Student run");
}
}

接口继承

一个interface可以继承自另一个interfaceinterface继承自interface使用extends,它相当于扩展了接口的方法。例如:

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

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

default方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Main {
public static void main(String[] args){
Persion p = new Studensa("xiao ming ");
System.out.println(p.getname());
p.run();
}
}

interface Persion{
String getname();
default void run(){
System.out.println(getname() + " run");
}
}

class Studensa implements Persion{
private String name;
public Studensa(String name){
this.name = name;
}
public String getname(){
return this.name;
}
}

静态字段和静态方法

在一个class中定义的字段,我们称之为实例字段。实例字段的特点是,每个实例都有独立的字段,各个实例的同名字段互不影响。

还有一种字段,是用static修饰的字段,称为静态字段:static field

实例字段在每个实例中都有自己的一个独立“空间”,但是静态字段只有一个共享“空间”,所有实例都会共享该字段。举个例子:

1
2
3
4
5
6
class Person {
public String name;
public int age;
// 定义静态字段number:
public static int number;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Main {
public static void main(String[] args) {
Person ming = new Person("Xiao Ming", 12);
Person hong = new Person("Xiao Hong", 15);
ming.number = 88;
System.out.println(hong.number);
hong.number = 99;
System.out.println(ming.number);
}WW
}

class Person {
public String name;
public int age;

public static int number;

public Person(String name, int age) {
this.name = name;
this.age = age;
}
}

位于同一个包的类,可以访问包作用域的字段和方法。不用publicprotectedprivate修饰的字段和方法就是包作用域。例如,Person类定义在hello包下面:

1
2
3
4
5
6
7
8
package hello;

public class Person {
// 包作用域:
void hello() {
System.out.println("Hello!");
}
}

import

在一个class中,我们总会引用其他的class。例如,小明的ming.Person类,如果要引用小军的mr.jun.Arrays

1
2
3
4
5
6
7
8
// Person.java
package ming;

public class Person {
public void run() {
mr.jun.Arrays arrays = new mr.jun.Arrays();
}
}
1
2
3
4
5
6
7
8
9
10
11
// Person.java
package ming;

// 导入完整类名:
import mr.jun.Arrays;

public class Person {
public void run() {
Arrays arrays = new Arrays();
}
}

导包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.world;

public class Person {

String name;

public Person(String name) {
this.name = name;
}

public String hello() {
return "Hello, " + this.name;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.world;

public class Person {

String name;

public Person(String name) {
this.name = name;
}

public String hello() {
return "Hello, " + this.name;
}
}

模块

module-info.java引入模块

1
2
3
4
module hello.world {
requires java.base; // 可不写,任何模块都会自动引入java.base
requires java.xml;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
oop-module
├── bin
│ ├── com
│ │ └── itranswarp
│ │ └── sample
│ │ ├── Greeting.class
│ │ └── Main.class
│ └── module-info.class
└── src
├── com
│ └── itranswarp
│ └── sample
│ ├── Greeting.java
│ └── Main.java
└── module-info.java

我们把工作目录切换到oop-module,在当前目录下编译所有的.java文件,并存放到bin目录下

1
javac -d bin src/module-info.java src/com/itranswarp/sample/*.java
1
$ java --module-path hello.jmod --module hello.world