![](https://i-blog.csdnimg.cn/blog_migrate/6609a2902bea735a2de87f9ddd3584e6.jpeg)
一.面向对象
1.1 什么是面向对象
Java是一门纯面向对象的语言(Object Oriented Program,简称OOP),在面向对象的世界里,一切皆为对象。 面向对象是解决问题的一种思想,主要依靠对象之间的交互完成一件事情。
1.2 面向对象和面向过程
拿洗衣服来举例:
面向过程
拿盆子--》放水--》放衣服--》放洗衣粉--》手搓--》换水--》放洗衣粉--》手搓--》拧干--》晾衣服
按照该种方式来写代码,任何一个环节都不能出错,否则要改写整串代码。将来扩展或者维护起来会比较麻烦。
面向对象
喊:“妈,我衣服该洗了!”
![](https://i-blog.csdnimg.cn/blog_migrate/9bafe1569fe27d4ce4cf7b5837127dc8.png)
孝顺的孩子
总共有三个对象:我,衣服,妈
整个洗衣服的过程就是,我负责衣服交给妈妈,妈妈负责去洗
整个过程是对象之间交互完成的,而不需要关心洗衣服的过程。
二.类的定义
2.1 什么是类
类是用来对一个实体(对象) 进行描述的,实体(对象)具有哪些属性(外观尺寸等),哪些功能(用来干
什么),描述完成后计算机就可以识别了
比如狗类
属性:什么品种,什么颜色,体重大小
功能:撒娇,看家,吃饭
2.2 类的定义
在java中定义类时需要用到class关键字,具体语法如下
class ClassName{
field; // 字段(属性) 或者 成员变量
method; // 行为 或者 成员方法
}
1.类中包含的内容称为类的成员。
2.属性主要是用来描述类的,称之为类的成员属性或者类成员变量。
3.方法主要说明类具有哪些功能,称为类的成员方法。
举例,定义一个狗类
class dog{
//成员属性
public String racial;
public String color;
public int height;
//成员方法
public void play() {
System. out.println("撒娇");
}
public void quard() {
System. out.println("看家");
}
public void eat() {
System. out.println("吃饭");
}
}
注意事项
定义类名一般使用大驼峰,方法名和成员变量名使用小驼峰
此处成员前面写法统一加上public,且不带static(下文解释)
我们再定义一个学生类
class Student {
public String name;
public int age;
public void doHomework() {
System. out.println("做家庭作业");
}
public void getName() {
System. out.println("My name is"+name);
}
}
此时就会发现out目录对应的文件夹下生成了三个字节码文件
![](https://i-blog.csdnimg.cn/blog_migrate/bb496f6c2ad56f656fa1b5a5d29068c6.png)
因此是一个类对应一个.class文件
注意
1.public修饰的类名必须要和文件名相同
2.一般一个文件只定义一个类
3.不能轻易修改public修饰的类名,如果要修改就和文件名一起修改
用idea更改文件名的方式如下图
![](https://i-blog.csdnimg.cn/blog_migrate/91494fef63c6214d0f24ca3f8f596fbd.png)
一个.java文件只有一个public类
![](https://i-blog.csdnimg.cn/blog_migrate/cc84af08fcef26e1c466284296dac383.png)
一个.java文件有多个类
4.多个类可以有多个main函数
三.类的实例化
3.1 什么是实例化
定义了一个类,就相当于在计算机中定义了一种新的类型,与int,double类似,只不过int和double是java语言自带的内置类型,而类是用户自定义了一个新的类型。
用类类型创建对象的过程,称为类的实例化。
1.在Java中实例化需要用到new关键字
2.一个类可以实例化多个对象
如下例
class Student {
public String name;
public int age;
public void doHomework() {
System. out.println("做家庭作业");
}
public void getName() {
System. out.println("My name is "+name);
}
public static void main(String[] args) {
Student st1=new Student();//用new实例化学生1
st1.name="李雷";
st1.age=18;
st1.getName();
Student st2=new Student();
st2.name="韩梅梅";
st2.age=18;
st2.doHomework();
}
}
上图的代码中可以看到用new关键字实例化出的对象通过赋值符“=”传给引用变量,引用变量可以用“.”访问该对象的成员
3.2 类和对象的关系
1. 类只是一个 模型一样的东西,用来对一个实体进行描述,限定了类有哪些成员.
2. 类是一种自定义的类型,可以用来定义变量.
3. 一个类可以实例化出多个对象, 实例化出的对象 占用实际的物理空间,存储类成员变量
如下图,用户自定义了一个房子类,它就相当于一张图纸,根据这张图纸建造出来了房子,就是房子类的实例化
![](https://i-blog.csdnimg.cn/blog_migrate/118a1fe32b0eb82989e8ed74f113189c.png)
四.this引用
4.1 为什么要有this引用
如下例
在学生类中定义一个成员函数,用来给学生类成员变量赋值,然后再把它打印出来
class Student {
public String name;
public int age;
public void doHomework() {
System. out.println("做家庭作业");
}
//定义set函数更改成员变量
public void setInformation(String name,int age) {
name=name;
age=age;
}
//定义get函数获取成员变量
public void getInformation()
{
System. out.println("My name is "+name+" and my age is "+age);
}
public static void main(String[] args) {
Student st1=new Student();
st1.setInformation("李雷",18);
st1.getInformation();
Student st2=new Student();
st2.setInformation("韩梅梅",18);
st2.getInformation();
}
}
结果发现并不是预料中的那样
![](https://i-blog.csdnimg.cn/blog_migrate/c9723f1a1feac56da91e98cf65514e7e.png)
这是因为局部优先原则
实际上赋值符的左右两遍都是我们传入的参数,而不是成员变量
![](https://i-blog.csdnimg.cn/blog_migrate/8ac0a1ffa5d0c5bfbbfc196d8869ca95.png)
如果想解决这个问题,有两种办法,一是给形参改个名字,二是在成员变量前面加上this.
public void setInformation(String name,int age) {
this.name=name;
this.age=age;
}
![](https://i-blog.csdnimg.cn/blog_migrate/1f81c05b4476bf236f584fb6979c8ed4.png)
运行结果
上文中已经提到,实际上成员方法并没有存储在对象中,定义的时候也没有传入对象的信息,这个方法怎么知道要操作谁的成员变量呢?
public static void main(String[] args) {
Student st1=new Student();
st1.setInformation("李雷",18);
st1.getInformation();
Student st2=new Student();
st2.setInformation("韩梅梅",18);
st2.getInformation();
}
对象st1和对象st2都调用了getInformation函数
public void getInformation()
{
System. out.println("My name is "+name+" and my age is "+age);
}
但是getInformation函数在定义的时候不知道哪个对象会使用它
this就是这个问题的答案
4.2 什么是this引用
this引用指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有成员变量的操作,都是通过该引用去访问。只不过不需要用户来传递,由编译器自动完成。
有点懵?看下面代码就一目了然了
Student st1=new Student();
st1.setInformation("李雷",18);
st1.getInformation();
上面的代码在编译器看来是这个样子的
st1.getInformation(Student this);
对象在调用成员方法的时候会把自己的引用传给该方法,但是getInformation方法并没有这个参数呀,实际上是编译器帮忙隐藏了
把编译器的小动作展开就是下面这样
public void getInformation(Student this)
{
System. out.println("My name is "+this.name+" and my age is "+this.age);
}
4.3 this引用的特性
1.this的类型,哪个类型的对象调用就是哪个对象的引用类型
![](https://i-blog.csdnimg.cn/blog_migrate/518a7414820fe286e71fe4df89930b17.png)
2.this只能在非静态成员方法中使用(与static有关,下文会解释)
3.在成员方法中,this只会是当前对象的引用,不会引用其他对象
![](https://i-blog.csdnimg.cn/blog_migrate/244e1bdd1a4aafaa89e27d09bbf4be39.png)
4.this是非静态成员方法的第一个隐藏的参数,在该成员方法执行时,this由编译器自动传递
你看到的代码VS编译器看到的代码
![](https://i-blog.csdnimg.cn/blog_migrate/fb130179faaa20e0041c9fe24b64e6fc.png)
你看到的代码
![](https://i-blog.csdnimg.cn/blog_migrate/2578525f5a66b4078cc381976ba6e4f8.png)
编译器看到的代码
建议在成员方法体内,每一个要操作的成员变量前面都加上this.
五.对象的构造及初始化
每次创建一个对象都要调用set函数设置它的信息,感觉很麻烦,可不可以让它在创建的时候自己初始化呢?
勤劳的编译器回答:Of course!
![](https://i-blog.csdnimg.cn/blog_migrate/5666a87d7aee1040fb10e4701317fb9a.png)
5.1 构造方法
构造方法是一个特殊的成员方法,见下表
普通方法 | 构造方法 | |
返回值 | 可自定义返回类型 | 没有返回值,方法名前面加void也不行 |
方法名 | 可自定义方法名 | 只能是类名 |
调用时间 | 可以在对象创建后随意调用 | 只能在创建对象时调用一次 |
是否可以重载 | 可以 | 可以 |
来写一下学生类的构造方法
class Student {
public String name;
public int age;
//定义了不带参数的构造方法
public Student() {
this.name="王五";
this.age=18;
System. out.println("调用了不带参数的构造");
}
//定义了带两个参数的构造方法
public Student(String name,int age) {
this.name=name;
this.age=age;
System. out.println("调用了带参数的构造");
}
//输出学生信息
public void getInformation()
{
System. out.println("My name is "+this.name+" and my age is "+this.age);
}
public static void main(String[] args) {
Student st1=new Student();//会调用不带参数的构造
Student st2=new Student("韩梅梅",18);//会调用带参数的构造
st1.getInformation();
st2.getInformation();
}
}
来看运行结果验证
![](https://i-blog.csdnimg.cn/blog_migrate/d3987a3b4631e1411c0e86380e05e2c5.png)
可以通过构造方法重载,根据用户的不同需求调用不同的构造方法
5.2 构造方法的特性
特性一.如果用户没有显式定义构造方法,编译器会默认生成一份不带参数的构造方法(什么也没干),一旦用户定义了构造方法,编译器不会生成
比如定义一个狗类(不能可着学生造)
class Dog{
public String name;
public String color;
public int age;
}
在上面的代码中,没有定义任何一个构造方法,编译器会帮助生成一个类似于下面的构造方法
//编译器默认生成的构造方法
Dog() {
}
如果定义了任何一个构造方法,编译器就不会再生成
lass Dog{
public String name;
public String color;
public int age;
//定义了一个带三个参数的构造方法
Dog(String name,String color,int age) {
this.name=name;
this.color=color;
this.age=age;
}
public static void main(String[] args) {
Dog kitty=new Dog("kitty","pink",2);//在创建对象的时候传入参数
Dog piter=new Dog();//报错,已经显式定义了构造方法
//编译器不会再生成无参构造
//因此创建对象的时候必须传参
}
}
特性二.在构造方法中,可以通过this调用其他方法来简化代码
this引用的三种用途
this+成员变量 | 操作当前对象的成员 |
this+成员方法 | 调用该方法 |
this(参数列表) | 只能在构造方法中使用,用来调用另一个构造方法 |
所以,无参的构造方法可以这样使用
public Student() {//无参构造,默认姓名是王五,年龄是18
//this.name="王五";
//this.age=18;
this("王五",18);//在不带参数的构造方法里调用带参数的构造
}
public Student(String name,int age) {
this.name=name;
this.age=age;
}
public static void main(String[] args) {
Student st1=new Student();
}
上面代码的执行顺序如下:
![](https://i-blog.csdnimg.cn/blog_migrate/753ac6a06100b52d2a63be897cf2edd0.png)
但是this(...)这条语句的使用场景有诸多限制
1.只能在构造方法中调用
Student() {
this.name="wangwu";
this.age=18;
}
public void getInformation()
{
this();// error,不可以在普通方法中调用构造函数
System. out.println("My name is "+this.name+" and my age is "+this.age);
}
2.只能放在构造方法的第一行
Student() {
System. out.println("无参构造");
this("王五",18);// error,调用构造函数的this语句只能放在第一行
}
Student(String name,int age) {
this.name=name;
this.age=age;
}
3.不能形成环
Student() {
this("王五",18);
}
Student(String name,int age) {
this();//不可以,两个构造方法无限调用
}
Student() {
this();//不可以,不能自己调用自己
}
5.3 成员变量的初始化
来看下面一组代码
class Student {
public String name;
public int age;
public void getInformation()
{
System. out.println("My name is "+this.name+" and my age is "+this.age);
}
public static void main(String[] args) {
Student st1=new Student();//实例化对象
st1.getInformation();//输出该对象的成员变量
}
}
是不是敏锐地察觉到,成员变量并没有初始化,直接输出会报错?
![](https://i-blog.csdnimg.cn/blog_migrate/2fec4521c2aa8cd4d05650fb3380255c.png)
这里就要谈到局部变量和成员变量的区别了,放表!!!
局部变量 | 成员变量 | |
定义方式 | 定义在方法内的变量 | 定义在类内方法外的变量 |
生存周期 | 只存在于方法的栈帧中 | 从对象的创建到对象的销毁 |
是否自动初始化 | 不会,必须先初始化再使用 | 可以进行默认初始化或者就地初始化 |
5.3.1 默认初始化
Student st=new Student();
在程序层面只是简单的一条语句,在JVM层面需要做好多事情,下面简单介绍下:
1. 检测对象对应的类是否加载了,如果没有加载则加载
2. 为对象分配内存空间
3. 处理并发安全问题
比如:多个线程同时申请对象,JVM要保证给对象分配的空间不冲突
4. 初始化所分配的空间,这一过程就是默认初始化
即:对象空间被申请好之后,对象中包含的成员已经设置好了初始值
数据类型 | 默认值 |
byte | 0 |
char | '\u0000 |
short | 0 |
int | 0 |
long | 0 |
boolean | false |
float | 0.0f |
double | 0.0 |
引用类型 | null |
5. 设置对象头信息(关于对象内存模型后面会介绍)
6. 调用构造方法,给对象中各个成员赋值
5.3.2 就地初始化
在声明成员变量时就给出了初始值,这种方式称为就地初始化
class Student {
public String name="大圣";
int age=10000;
}
这种方式相当于在构造函数执行前先初始化成员变量
六.封装
6.1 封装的概念
面向对象程序三大特性:封装、继承、多态
下面来详细讲解一下封装,何为封装?简单来说就是套壳屏蔽细节。
就好像平时使用的手机,身为一个用户,我们的基本素养是知道怎么用它来打游戏,刷视频,追剧,而不需要关心它是怎么把游戏界面投射到屏幕上的,是怎么放出音乐的
实际上,这就是手机的封装
![](https://i-blog.csdnimg.cn/blog_migrate/36d14496a694f0f541289d8ead208277.png)
![](https://i-blog.csdnimg.cn/blog_migrate/36cb19a09b3cd8ed697d5af77fd91869.png)
Java中,封装通过访问限定符private实现
6.2 访问限定符
使用范围 | private | 默认 | protected | public |
同一包中的同一类 | yes | yes | yes | yes |
同一包中的不同类 | yes | yes | yes | |
不同包中的子类 | yes | yes | ||
不同包中的非子类 | yes |
private表示该成员只能用在当前类中
protected主要是用在继承中,继承部分详细介绍
默认权限指:什么都不写时的权限
public没有使用范围的限制
在理解访问限定符的权限之前,先来看看什么是包
6.3 包
6.3.1 包的概念
在面向对象体系中,提出了一个软件包的概念,即:为了更好的管理类,把多个类收集在一起成为一组,称为软件包。简单来说,包就是一个文件夹,里面装的是类
比如:为了更好的管理电脑中的歌曲,一种好的方式就是将相同属性的歌曲放在相同文件下,也可以对某个文件夹下的音乐进行更详细的分类。
![](https://i-blog.csdnimg.cn/blog_migrate/7b81c3cc860e47222fd35f5f119dfdf6.png)
包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式,比如:一个包中的类不想被其他包中的类使用。
包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在不同的包中即可。
6.3.2 导入包中的类
Java提供了很多包供我们使用,例如Date类,Arrays类
可以通过下面的方式导入
方法一:import 包名.类名
import java.util.Arrays;//导入util包下的Arrays类
import java.util.Date;//导入util包下的Date类
public class test {
public static void main(String[] args) {
int[] array={2,5,8,9,0};
Arrays. sort(array); //使用Arrays类的方法
Date date=new Date();//创建Date对象
}
}
对于静态的成员要使用import static 包名.类名导入
import static java.lang.Math.sqrt ;//sqrt是静态方法
public class test {
public static void main(String[] args) {
double d= sqrt(16);
System. out.println(d);
}
}
方法二:import 包名.*
导入静态成员同样需要加static标识符
其中*是通用符,用到了哪个类就充当哪个类,而不是导入这个包的全部路径
import java.util.*;
public class test {
public static void main(String[] args) {
int[] array={2,5,8,9,0};
Arrays. sort(array); //*在此处充当Arrays类
Date date=new Date();//*在此处充当Date类
}
}
不同包下的不同类可以是同名的,如果把它们都导入,需要用完整的类名显式指定究竟使用哪一个类
比如util和sql包下都有Date类
import java.util.*;
import java.sql.*;
public class test {
public static void main(String[] args) {
java.util.Date date=new java.util.Date();//需要指明
}
}
注意事项: import 并不是C语言中的#include,如果导入的包没有使用,不会在编译时像头文件那样展开,import只是为了写代码的时候更方便
6.3.3 自定义包中的类
下面自定义包的方式是在idea上操作的
可以用package创建一个包
![](https://i-blog.csdnimg.cn/blog_migrate/83d489e0f106dd0e689e4fb8d8b4ed56.png)
注意事项:
1.包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.Baidu.www ).当然也可以自己定义
2.包名和代码路径相匹配. 例如创建 com.Baidu.www 的包, 那么会存在一个对应的路径com/Baidu/www 来存储代码.比如刚刚创建了一个com.Wuyang的包,就会存在这样一个路径
![](https://i-blog.csdnimg.cn/blog_migrate/1a20c506097d7f78a628f20ccd962b8d.png)
在com包和Wuyang包中分别创建类test1和test2
就会出现下面的一行package+包名的代码,用来声明该类放在哪个包下面
![](https://i-blog.csdnimg.cn/blog_migrate/7db8d04b410f346adfa90e64c4894516.png)
![](https://i-blog.csdnimg.cn/blog_migrate/8b4476fdc8db07057a7143c0bc337eee.png)
3.直接从src创建一个类,该类会被放在一个默认包中,开头没有package语句,并且这个类不能被导到其他文件里
![](https://i-blog.csdnimg.cn/blog_migrate/4e22c45c84d589bccb326ac51db92bfa.png)
4.包名要全部小写,此处的com.Wuyang是错误示范!
6.3.4 常见的包
1. java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入
2. java.lang.reflflect:java 反射编程包;
3. java.net :进行网络编程开发包。
4. java.sql:进行数据库开发的支持包。
5. java.util:是java提供的工具程序包。(集合类等) 非常重要
6. java.io:I/O编程开发包。
6.4 详解三种访问权限
下文主要说明private,默认和public(protected继承时说明阐述)
private
由该访问限定符修饰的成员只能在当前类中使用
class Dog {
private String name="旺财";
private int age=2;
public void play() {
System. out.println("汪汪~");
}
}
public class test1 {
public static void main(String[] args) {
Dog pet=new Dog();//创建一只宠物狗
System. out.println(pet.name);//error,该成员只能在Dog内部使用
pet.play();//可以正常输出“汪汪~”
}
}
那么该如何操作这个类的成员变量呢?可以定义多个方法
class Dog {
private String name;
private int age;
public void play() {
System.out.println("汪汪~");
}
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;
}
}
通过调用public修饰的方法就可以更改或查找成员变量的值
idea也提供了快捷键帮助我们生成set和get方法
鼠标右键--》generate--》getter或setter或两种都选
![](https://i-blog.csdnimg.cn/blog_migrate/5da419405d0115c90cc22a580774172b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/2850e4840612e8842e0a67ca51b5cef7.png)
默认权限
可以在同一包下的不同类内使用
Cat类和test1在同一个包下
####################################
public class Cat {
String name="Kitty";//成员变量前没有访问限定符,是默认权限
int age=2;
}
public class test1 {
public static void main(String[] args) {
Cat pet2=new Cat();
System. out.println("我的宠物猫是:"+pet2.name);
}
}
Cat类在.com包下,test在默认包下
####################################
import com.Cat;
public class test {
public static void main(String[] args) {
Cat pet=new Cat();
pet.name="Wendy";
}
}
上面的程序如果运行会出现下面的结果
![](https://i-blog.csdnimg.cn/blog_migrate/ccf9c898673faca5d85d77b14b2c8766.png)
public
只要该类被导入,可以再任意场景下使用,不再举例
需要注意的是,访问限定符不仅可以修饰类内部的成员,还可以修饰类(private除外)
Cat类被放在.com包里,且是默认权限
package com;
class Cat {
String name="Kitty";
int age=2;
}
test2被放在com.Wuyang包里
package com.Wuyang;
import com.Cat;//导入默认权限的Cat类,会发生报错
public class test2 {
}
七.static成员
7.1 static的意义
再把我们的学生类拉出来,给这个类中增加一个成员变量--classRoom
class Student {
private String name;
private int age;
public String classRoom;
public static void main(String[] args) {
//创建对象学生1并赋值
Student st1=new Student();
st1.name="liming";
st1.age=18;
st1.classRoom="301";
//创建对象学生2并赋值
Student st2=new Student();
st1.name="wangwei";
st1.age=18;
st1.classRoom="301";
}
}
可以看到,学生1和学生2的教室都是301,他们的内存分布如下图
![](https://i-blog.csdnimg.cn/blog_migrate/2c2c506380f3286d31ec0d97fa33a7de.png)
之前在Student类中定义的成员变量,每个对象中都会包含一份(称之为实例变量),因为需要使用这些信息来描述具体的学生。而现在要表示学生上课的教室,这个教室的属性并不需要每个学生对象中都存储一份,而是需要让所有的学生来共享。
在Java中,被static修饰的成员,称之为静态成员,也可以称为类成员,其不属于某个具体的对象,是所有对象所共享的。
7.2 static修饰成员变量
static修饰的成员变量,称为静态成员变量,静态成员变量最大的特性: 不属于某个具体的对象,是所有对象所共享的。
因为静态成员变量不依赖于对象存在,访问方式也有所不同
方法一.类名+变量名
class Student {
private String name;
private int age;
public static String classRoom;
}
public class test {
public static void main(String[] args) {
Student. classRoom="301";
System. out.println(Student. classRoom);
}
}
//输出结果
301
方法二.对象名+变量名(不推荐)
class Student {
private String name;
private int age;
public static String classRoom;
}
public class test {
public static void main(String[] args) {
Student st1=new Student();
st1. classRoom="301";
System. out.println(st1. classRoom);
Student st2=new Student();
System. out.println(st2. classRoom);
}
}
//输出结果
301
301
可以看到,通过对象st1给静态成员classRoom赋值,意味着改变这个类的classRoom
这种方式并不能显式说明静态变量是归类所有而不是某一对象,因此不推荐这种写法
变量类型 | 静态成员变量 | 非静态成员变量 |
生存周期 | 从类被加载到类被卸载 | 对象被创建到对象被销毁 |
存储方式 | 存储在方法区上 | 存储在堆上,对象内部 |
访问方式 | 可以直接通过类名访问 | 只能构建对象,通过该对象的引用访问 |
个数 | 只有一个,不属于任何对象 | 一个对象拥有一份 |
7.3 static修饰成员方法
思考一下这个问题:如果classRoom的访问权限是private。如果仍然想在类外访问,需要调用public的方法,见下例
class Student {
private String name;
private int age;
private static String classRoom;//classRoom是私有成员
public void setClassRoom(String classRoom) {
Student. classRoom = classRoom;
}
public String getClassRoom() {
return classRoom;
}
}
public class test {
public static void main(String[] args) {
Student.classRoom="301";//会报错
//通过创建对象,调用public权限的方法访问classRoom
Student st=new Student();
st. setClassRoom("301");
System. out.println(st. getClassRoom());
}
}
既然classRoom不依赖对象存在,能不能不通过对象这种方式而是直接用类名访问classRoom访问呢?
Java中, 被static修饰的成员方法称为静态成员方法,是类的方法,不是某个对象所特有的。静态成员一般是通过静态方法来访问的。
静态成员方法的调用同样分为类名.方法或者对象名.方法(推荐使用前者)
上面的代码就可以改成这样
class Student {
private String name;
private int age;
private static String classRoom;
//定义静态成员方法get和set
public static void setClassRoom(String classRoom) {
Student. classRoom = classRoom;
}
public static String getClassRoom() {
return classRoom;
}
}
public class test {
public static void main(String[] args) {
//通过类名调用静态成员方法
Student. setClassRoom("301");
System. out.println(Student. getClassRoom());
}
}
下面来做一个小测试
class Student {
public static String classRoom;
public static String getClassRoom() {
return classRoom;
}
}
public class test {
public static void main(String[] args) {
Student p=null;//定义引用变量P指向空
p. classRoom="402";//通过p修改静态成员变量classRoom
System. out.println(p. getClassRoom());//通过p访问方法get
}
}
猜猜这个破坏者P是否能成功调用静态方法,将学生们的教室修改成“402”,并成功调用get方法呢?
答案是可以的!
第一个问题的答案:classRoom是属于Student这个类的,所以Student类型的引用变量P是可以对其进行操作的
第二个问题的答案要谈到静态成员方法的特性了
1.普通的成员方法在调用的时候都会传递一个隐藏的this引用,但是静态成员方法不需要
所以即使p是一个空引用,它仍是Student类型的引用变量,是可以调用不需要传this引用的方法的
2.普通成员方法可以通过this.成员访问其他成员(包括静态和非静态),静态成员方法只能访问静态成员
class Student {
private String name;
private int age;
public static String classRoom;
public static String printName() {
System. out.println("My name is "+name);
}//报错,静态成员方法体内不能访问非静态成员
}
7.4 static成员变量的初始化
静态成员变量是在类被加载的时候初始化的,分为两种方式
1.就地初始化
和普通成员变量就地初始化的格式一样,直接在定义静态成员变量的后面添上赋值语句即可
class Student {
public static String classRoom=“301”;
}
2.静态代码块初始化
先来看看什么是代码块
7.4 代码块
7.4.1 代码块的概念及分类
使用 {} 定义的一段代码称为代码块。根据代码块定义的位置以及关键字可分为4种
1.普通代码块
2.构造代码块
3.静态块
4.同步代码块(多线程部分才会用到,此处不提)
7.4.2 普通代码块
普通代码块:定义在方法内部的代码块
举例
public class test {
public static void main(String[] args) {
int a=0;
if(a==1) {
System. out.println("a=1");
} else {
System. out.println(("a!=1"));
}
}
}
if和else语句{}内部的即为普通代码块
7.4.3 构造代码块
构造块:定义在类中的代码块(不加修饰符)。也叫: 实例代码块。 构造代码块一般用于初始化实例成员变量。
举例
public class test {
int number1;
String number2;
//构造代码块对成员number1和number2初始化
{
this.number1=10;
this.number2="Being positive";
}
public static void main(String[] args) {
test test1=new test();
//输出number1和number2的值
System. out.println("num1="+test1.number1);
System. out.println("num2="+test1.number2);
}
}
//输出结果
num1=10
num2=Being positive
注意:
1.构造代码块的执行顺序要先于构造函数
下面来验证一下
public class test {
int number1;
String number2;
//定义构造代码块
{
this.number1=10;
this.number2="Being positive";
}
//定义构造函数
test() {
System. out.println("还未初始化number1和number2");
System. out.println("number1="+number1+", number2="+number2);
this.number1=100;
this.number2="Don't lose heart!";
}
public static void main(String[] args) {
test test1=new test();
System. out.println("num1="+test1.number1);
System. out.println("num2="+test1.number2);
}
}
输出结果如下
![](https://i-blog.csdnimg.cn/blog_migrate/44caa822efc5a68e900953e6ef92cbe2.png)
2.就地初始化的顺序和执行构造代码块的顺序取决于代码先后顺序
参照下面代码就很好理解了
public class test {
//代码块1
{
number1=20;
number2="Cherish everything you own!";
System. out.println("第一次初始化后number1="+this.number1+", number2="+this.number2);
}
//就地初始化
int number1=1000;
String number2="Being happy!";
//代码块2
{
System. out.println("第二次初始化后number1="+this.number1+", number2="+this.number2);
this.number1=10;
this.number2="Being positive";
}
public static void main(String[] args) {
test test1=new test();
System. out.println("第三次初始化后number1="+test1.number1+", number2="+test1.number2);
}
}
运行结果如下
![](https://i-blog.csdnimg.cn/blog_migrate/cd1b5ee0be987aeccbf645cc0f05499b.png)
可以看到,成员变量初始化的顺序是按照就地和构造代码块的先后顺序决定的
7.4.4 静态代码块
使用static定义的代码块称为静态代码块。只能 用于初始化静态成员变量
举例
public class test {
int number1;
String number2;
static double number3=10.24;//静态变量就地初始化
//静态代码块
static {
number1=10;//error,静态代码块内不允许出现非静态成员
System. out.println("静态代码块执行前:"+ number3);
number3=2023;
System. out.println("静态代码块执行后:"+ number3);
}
public static void main(String[] args) {
test test1=new test();
}
}
输出结果
![](https://i-blog.csdnimg.cn/blog_migrate/ebf998a686e7b9aacfa97996a5b0bba1.png)
注意:静态成员变量不能定义在静态代码块的后面,它们都是随着类的加载被加载的,其先后顺序依照代码顺序
7.4.5 代码块总结
由于普通代码块过于简单,在此不做赘述,主要分析构造代码块和静态代码块
构造代码块 | 静态代码块 | |
执行时间 | 创建对象时 | 加载类时 |
执行次数 | 创建多个对象就会被执行多次 | 只会在类被加载时执行一次 |
功能 | 只能初始化普通成员变量 | 只能初始化静态成员变量 |
在此说明一下,类加载就是使用这个类(创建对象或使用静态成员方法)的时候
比如下面的场景
public class test {
static {
System. out.println("执行了静态代码块");
}
static void func() {
System. out.println("调用了func方法");
}
public static void main(String[] args) {
//test test1=new test();通过创建变量加载test类
test. func();//通过使用方法加载test类
}
}
总结执行顺序:静态代码块>构造代码块>构造方法
八.对象的打印
我们已经知道,通过引用变量可以获取到一个对象的虚拟地址,然后就可以使用println方法把打印出来
//定义Person类
class Person {
private String name;
private int age;
}
public class test {
public static void main(String[] args) {
//创建对象
Person yang=new Person();
//输出对象地址
System. out.println(yang);
}
}
输出结果
![](https://i-blog.csdnimg.cn/blog_migrate/02fb582ad4997ea2e863d20416043bca.png)
但是很多时候我们并不需要打印这个对象的地址,而是想要知道它内部成员的值,这就不得不写一个方法来实现,有没有更简单的操作呢?
先来看一下println的实现
![](https://i-blog.csdnimg.cn/blog_migrate/c271681b48b9933e935c36335cda3ddd.png)
可以看到,println是调用了valueOf方法,valueOf方法又调用了toString方法,所以可以通过重写toString方法来打印我们想要的值
class Person {
//就地初始化
private String name="yang";
private int age=18;
@Override//注解,会帮忙检查是否重写了该方法
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class test {
public static void main(String[] args) {
Person yang=new Person();
System. out.println(yang);
}
}
有了这个toString,我们就不需要傻乎乎地再造一个方法了,直接调用包提供的println就行
运行结果如下
![](https://i-blog.csdnimg.cn/blog_migrate/dcc4e072862e74f64a322096385ebf8d.png)
你是不是以为这个toString方法是我自己写的?
当然不是,强大的idea是我懒惰的理由
![](https://i-blog.csdnimg.cn/blog_migrate/2c87eb779b5c4021c7c19e86df17ce51.png)
![](https://i-blog.csdnimg.cn/blog_migrate/2cfa0934a39a5d112418395bca066aa2.png)
关于方法的重写会在下一篇讲解,耐心等待继承和多态吧!
![](https://i-blog.csdnimg.cn/blog_migrate/92c2357f04486985943843bdcf33fbea.png)