类和对象并不是同一种东西,类是对多个对象共同特征(变量)和行为(方法)的抽象,而对象是通过类这个模板实例化出来的存在内存的堆中的数据。
类的成员
类由变量和方法组成,这些都是类的成员,这些成员在内存中的存储位置不一定相同,有的会存储在堆中,这些被称为非静态成员,有的会存储在方法区中,这些被称为静态成员。
静态成员和非静态成员在内存中的区别:
首先静态成员是属于类的,非静态成员是属于对象的。
因此,当我们对一个类进行实例化时,实例化出来的对象中的成员就是该类的所有非静态成员,他们会一起被存储在内存的堆中,也就是实例化了多少个对象就会在堆中存在几份该类的非静态成员。
而类中的静态成员是存储在内存的方法区中(如果静态成员变量的值是对象,那么实际的对象会保存在堆中,变量存放在方法区中),所有对象都可以访问方法区中这一份静态成员的数据,也就是在对象中的非静态成员可以访问静态成员,但静态成员不可以访问非静态成员的数据,因为那时对象可能还没有创建出来,或者同时有多个对象他也分不清具体要访问哪个对象的数据,这些不确定的点可能造成程序运行出错,因此索性直接不允许访问。
下面我将类中的成员按照是否静态进行分类并说明。
非静态成员
非静态成员可以访问静态成员,也就是比如在非静态成员方法内可以获取到静态成员的数据
可通过 类名.静态成员名
或者 this.静态成员名
或 静态成员名
获取,如下
class People {
public static String country;
public String name;
public People(String name) {
this.name = name;
// 在非静态成员方法内可以访问到静态成员变量
// 1.通过 this.静态成员名 的方式获取
this.country = "中国";
// 2.也可以不添加 this,因为编译期会自动添加 this,所以效果是一样的
country = "中国";
// 3.使用 类名.静态成员名 的方式访问
People.country = "中国";
}
}
非静态成员变量
非静态成员变量的声明语法格式和局部变量一样
class 类名 {
数据类型 成员变量名;
}
成员变量和局部变量也有区别,局部变量需要先初始化值后才能使用,而成员变量一般不需要初始化,但他也有自己的默认值,他的默认值和数组中元素的默认值一样。
非静态成员的特点是,每实例化一个对象时,就需要添加一份非静态成员的数据到该对象中,对于变量来说,也就是,即使这些变量的值是一样的,也需要在每个对象中声明,无法重复使用,因此有个劣势就是当变量的值重复时会浪费内存空间。
非静态成员方法
创建成员方法的语法格式如下
class 类名 {
返回值类型 成员方法名(形参列表) {
成员方法体;
}
}
- 返回值
把方法体内的数据带到方法体外 - 形参
把方法体外的数据带到方法体内 - 形参列表
多个形参组成的列表,也就是把多个形参从方法体外带到方法体内
语法格式:数据类型 形参变量名1, 数据类型 形参变量名2, ...
构造方法
还有一个特殊的方法是构造方法。
如果一个类的方法的方法名和类名完全相同,那么这个方法就是该类的构造方法,该方法会在用 new
对类进行实例化的时候被自动调用。
他的作用是对成员变量的值进行初始化
创建构造方法的语法格式:
class 类名 {
public 类名(形参列表) {
构造方法体;
}
}
被调用的场景:
// 用 new 对 Person 类进行实例化时会自动调用 Person 类中的构造方法
Person p = new Person();
注:
1.当类中没有定义任何构造方法时,编译器会自动添加一个无参数空构造方法体的构造方法,该构造方法叫做缺省/默认构造方法,如
class People {
// 编译器自动添加该默认构造方法
public People() {}
}
2.当我们只要提供了构造方法,编译器不会再自动创建任何形式的构造方法
class People {
String name;
// 代码中提供了一个需要传入参数的构造方法
public People(String name) {
this.name = name;
}
public static void main(String[] args) {
// 这里需要不传入参数的构造方法
// 这里会报错,因为类中没有不需要传参的构造方法,编译器也不会在类有构造方法的时候自动创建无参空构的构造方法
People people = new People();
}
}
方法重载(overload)
当多个成员方法的方法名相同而参数列表不同时(不考虑返回值类型),这些方法之间的关系称为重载关系
构成重载关系的形式如下:
- 参数个数不同 ```java void show() { System.out.println(“show”); }
void show(String s) { System.out.println(s); }
- **参数类型不同**
```java
void show(int i) {
System.out.println(i);
}
void show(String s) {
System.out.println(s);
}
- 参数顺序不同 ```java void show(int i, double b) { System.out.println(i + b); }
void show(double b, int i) { System.out.println(b + i); }
**不构成重载关系**的形式如下:
- **参数名不同**
```java
void show(double a, int b) {
System.out.println(a + b);
}
void show(double b, int i) {
System.out.println(b + i);
}
- 返回值类型不同 ```java void show(int i, double b) { System.out.println(i + b); }
double show(int i, double b) { System.out.println(i + b); return i + b; }
<a name="dKFQH"></a>
###
<a name="E2xId"></a>
#### 构造块
构造块是在类中直接用 {} 括起来的代码块,构造块在每次创建对象时都会执行一次,并且执行顺序在构造方法之前,因此他可以用来在构造方法给成员变量初始化前做些准备工作。
如
```java
class People {
// 这是一个构造块
{
// 构造块中的内容会先于构造方法执行
System.out.println("构造块");
}
public People() {
System.out.println("构造方法体");
}
// 该方法运行多次的执行结果都是一致的
public static void main(String[] args) {
People people = new People(); // 控制台打印:构造块 -> 构造方法体
People people2 = new People(); // 控制台打印:构造块 -> 构造方法体
}
}
静态成员
从内存层面来看,静态成员比非静态成员更省空间,因为静态成员是属于类的,只需要单独存储一份在内存的方法区中就行了,每次用类实例化一个对象的时候,静态成员不会被存储在该对象中,也就是该对象所在的堆中,因此节省了空间,也不影响其他对象使用它,因为静态成员是可以被所有对象访问到的。
静态成员变量
声明语法:
class 类名 {
static 数据类型 成员变量名;
}
使用语法:
引用.静态成员变量名
类名.静态成员变量名
如
class People {
// 声明静态成员变量
public static String name;
public People() {
}
public static void main(String[] args) {
People p1 = new People();
System.out.println(p1.name); // null(引用类型默认值是null)
People p2 = new People();
System.out.println(p2.name); // null
// 使用类名.静态成员变量名方式获取(推荐这种)
People.name = "张三";
// 也可以使用引用.静态成员变量名方式获取
// p1.name = "张三";
System.out.println(p1.name); // 张三
System.out.println(p2.name); // 张三
}
}
// 修改静态成员,实际修改的是所有对象都共享的方法区里的变量,因此不管在哪个对象中获取该变量值都是一致的
静态成员方法
静态成员方法内无法访问非静态成员,只能访问静态成员,并且访问时不能在前面加 this.
来引用,因为在静态方法中没有 this
关键字
如
class People {
public static String country;
public static void test() {
// 这两种是可以的
System.out.println(country);
System.out.println(People.country);
// 这是不行的
System.out.println(this.country);
}
}
静态代码块
使用static
关键字修饰的构造块,先于构造块执行,当加载类的时候就会执行静态代码块,而不是在每次创建对象的时候执行,因此如果类只加载了一次,那么静态代码块最终也只会执行一次,和创建对象无关。
他的作用是随着类的加载做一些准备工作,如加载数据库的驱动包。
如
class People {
{
// 构造块中的执行顺序先于构造方法
System.out.println("构造块");
}
static {
// 静态代码块执行顺序先于构造块
System.out.println("static");
}
public People() {
System.out.println("构造方法体");
}
public static void main(String[] args) {
People people = new People(); // 控制台打印:static -> 构造块 -> 构造方法体
People people2 = new People(); // 控制台打印:构造块 -> 构造方法体
}
}
this
在类里的非静态成员里可以使用 this
关键字,this 本质上相当于是一个引用了当前对象的引用变量,通过 this 可以获取到当前对象内的数据。
我们在类中获取成员变量和成员方法时有时候可以不用加 this 是因为编译器自动帮我们加了 this。
使用场景:
通过在构造方法的构造方法体内的第一行写
this()
调用其他构造方法
如class People {
String name;
People() {
}
People(String name) {
// 通过 this() 调用其他构造方法
this();
this.name = name;
}
public static void main(String[] args) {
People people = new People("s");
}
}
通过
this.xxx
使用该类中的成员变量 & 成员方法- 可作为成员方法中的返回值返回,如
return this;
注:
1.如果方法的形参需要赋值给类中的成员变量,推荐将形参名取的和该成员变量名一样,可以提高可读性
如
class People {
public String name;
// 形参名和对应的成员变量名一样
public People(String name) {
// 用 this. 的方式指明,否则将会赋值给形参自己
this.name = name;
}
}
对象的创建
由于类只是一个抽象的概念,我们无法直接使用,因此我们如果想使用该类,需要用 new
关键字创建该类的对象
,也就是将类实例化成一个客观存在的实体(也称实例/对象)。
用 new 创建对象的本质就是在堆区中申请一块存储空间,用来存放该实例的特征信息(行为存储在这里吗?)
创建对象的语法:
new 类名();
上面创建出来的对象是匿名对象,在堆中创建出来后我们无法找到他并使用他,我们可以创建一个引用变量引用这个对象,然后就可以找到这个对象并使用,语法格式如下:
类名 引用变量名 = new 类名();
/*
如下
用 new 关键字创建的对象存在堆区中,赋值运算符左边的引用变量实际在栈中申请了一块存储空间,
存储的是引用的对象的内存地址,通过这个引用变量,我们可以找到堆区中的这个对象
*/
Person person = new Person();
当我们想通过引用变量访问处在堆区中的对象中存储的特征信息时,语法格式如下
引用变量名.成员变量名;
// 如下
System.out.println(person.name);