而我们在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 创建类的实例(隐式加载)
- 调用类的静态方法
- 子类初始化时
- 其他的情况会在讲到反射时介绍