而我们在Java中, 也可以像这样进行编程, 我们可以定义一个类, 然后进一步创建许多这个类的实例对象。像这种编程方式, 我们称为面向对象编程

命名规范:一般使用英文单词, 并且首字母大写, 跟变量命名一样, 不能出现任何的特殊字符。

类的创建

每一个 .java 文件就是一个 java 类。

类的属性

直接在类内的最外围定义变量, 这就是类的属性。

public class Person {
	String name;
	int age;
	String Sex;
}

创建类的实例

//in main()
Person person = new Person();

person 是一个类型为对象引用的变量, 作为刚才创建的对象的别名。

类似于 C 的指针, 也可以用 p2 = p1 来传递引用。

Person p1 = new Person();
Person p2 = p1;

此时 p1, p2 都是指代同一个对象, p1 修改后 p2 也跟着改变。

引用类型的变量也可以设初值为 null

Person p1 = null;
System.out.println(p1.name);

此时如果访问执行println, 会报空指针异常:

Exception in thread "main" java.lang.NullPointerException...

在对象创建完成后, 如果没有构造函数, 默认情况下基本类型(int, boolean等)初值为0, 引用类型(String等)为 null。

方法创建与使用

方法是语句的集合, 是为了完成某件事情而存在的。

public class Person {
	String name;
	int age;
	String sex;
 
	void hello() {
		System.out.println("我叫 "+name+" 今年 "+age+" 岁了!");
	}
}

hello 函数就是定义在 Person 类的函数。

此时可以将对象实例化, 然后进行调用该方法。

Person p1  = new Person();
p1.name = "小明";
p1.sex = "man";
p1.age = 18;
p1.hello(); // 调用

传参的时候可以直接像正常函数一样传参:

int sum(int a, int b) {
	return a + b;
}
// ------
p1.sum(10, 20);

传参时, 默认基本类型是进行浅拷贝, 方法内的修改对外面的类型无作用。但若传入引用类型, 则会被修改, 因为引用类型所引用的对象只有一份。 除非定义一个 Copy 方法并调用来生成当前对象的一份拷贝。

也可以传入可变长参数, 对应就是一个数组, 但如果存在其他参数, 可变长参数只能放到最后。

public void test(int a, int b, String... strings){   //strings这个变量就是一个String[]类型的
    for (String string : strings) {
        System.out.println(string);   //遍历打印数组中每一个元素
    }
}

进阶使用

属性重名

public class Person {
	String name;
	void setName(String name) {
		name = name;
	}
}

此时并不能通过调用 setName 来修改实例的name 属性。 因为默认情况下会优先使用作用域最近的变量。

我们如果想要在方法中访问到当前对象的属性, 那么可以使用this关键字, 来明确表示当前类的示例对象本身:

void setName(String name) {
	this.name = name;
}

当然如果不重名的话就可以省略。

参数重载

void sum(int a, int b) {
	return a + b;
}

这里只能相加整数, 如果还想实现浮点数相加, 除了新定义 void floatsum(float a, float b), 还可以直接用 sum, 但修改一下参数。

void sum(int a, int b) {
	return a + b;
}
void sum(float a, float b) {
	return a + b;
}

此时会根据调用时候的参数类型自动调用对应符合的方法。

构造方法

对象在创建之后, 各种属性都是默认值, 那么能否实现在对象创建时就为其指定名字、年龄、性别呢?要在对象创建时进行处理, 我们可以使用构造方法(构造器)来完成。

实际上默认每个类都有自己的空构造器, 反编译后:

public class Person {
    String name;
    int age;
    String sex;
 
    public Person() {    //反编译中,多出来了这样一个方法,这其实就是构造方法
    }
}

构造方法不需要填写返回值, 并且方法名称与类名相同, 默认情况下每个类都会自带一个没有任何参数的无参构造方法(只是不用我们去写, 编译出来就自带)当然, 我们也可以手动声明, 对其进行修改。

也可以自己创建带参数的构造方法:

Person(String name, int age, String sex){
    System.out.println(age);    //在赋值之前看看是否有初始值
    this.name = name;
    this.age = age;
    this.sex = sex;
}

此时会发现没有无参构造, 只剩下了我们自己写的。

要给成员变量设定初始值, 我们不仅可以通过构造方法, 也可以直接在定义时赋值。

我们也可以在类中添加代码块, 代码块同样会在对象构造之前进行, 在成员变量初始化之后执行:

public class Person {
    String name;
    int age;
    String sex;
 
    {
        System.out.println("我是代码块");   //代码块中的内容会在对象创建时仅执行一次
    }
 
    Person(String name, int age, String sex){
        System.out.println("我被构造了");
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
}

静态变量和静态方法

静态的内容, 我们可以理解为是属于这个类的, 也可以理解为是所有对象共享的内容。我们通过使用static关键字来声明一个变量或一个方法为静态的, 一旦被声明为静态, 那么通过这个类创建的所有对象, 操作的都是同一个目标, 也就是说, 对象再多, 也只有这一个静态的变量或方法。一个对象改变了静态变量的值, 那么其他的对象读取的就是被改变的值。

public class Person {
    String name;
    int age;
    String sex;
    static String info;    //这里我们定义一个info静态变量
}

一般情况下, 我们并不会通过一个具体的对象去修改和使用静态属性, 而是通过这个类去使用:

public static void main(String[] args) {
    Person.info = "让我看看";
    System.out.println(Person.info);
}

同样的, 我们可以将方法标记为静态:

static void test(){
    System.out.println("我是静态方法");
}

成员变量是某个具体对象拥有的属性, 就像小明这个具体的人的名字才叫小明, 而静态方法是类具有的, 并不是具体对象的, 肯定是没办法访问到的。同样的, 在静态方法中, 无法使用this关键字, 因为this关键字代表的是当前的对象本身。

但是静态方法是可以访问到静态变量的:

static String info;
 
static void test(){
    System.out.println("静态变量的值为:"+info);
}

那么, 静态变量, 是在什么时候进行初始化的呢?

我们在一开始介绍了, 我们实际上是将.class文件丢给JVM去执行的, 而每一个.class文件其实就是我们编写的一个类, 我们在Java中使用一个类之前, JVM并不会在一开始就去加载它, 而是在需要时才会去加载(优化)一般遇到以下情况时才会会加载类:

  • 访问类的静态变量,或者为静态变量赋值
  • new 创建类的实例(隐式加载)
  • 调用类的静态方法
  • 子类初始化时
  • 其他的情况会在讲到反射时介绍