Java 基础语法:面向对象基础
[TOC]
对象和类
学习目标:
- 掌握对象与类的关系
- 掌握如何定义类
- 掌握对象的创建与使用
引例
张三养了3只狗,一只1岁白色萨摩亚,一只2岁黄白柯基,一只3岁黄色金毛
解决方案:
- 单独变量模拟
- 数组方式模拟
Code
public static void main(String[] args) { // 单独变量解决 // 第一只dog String dogName1 = "萨摩耶"; String dogColor1 = "白色"; int dogAge1 = 1;
// 第二只狗 String dogName2 = "柯基"; String dogColor2 = "黄白色"; int dogAge2 = 2;
// 第三只狗 String dogName3 = "金毛"; String dogColor3 = "黄色"; int dogAge3 = 3; // 数组方式解决
String[] dog1 = {"萨摩耶", "白色", "1"}; String[] dog2 = {"柯基", "黄白色", "2"}; String[] dog3 = {"金毛", "黄色", "3"};}单独变量解决==》不利于数据管理
数组解决==》数据类型无法体现,取值只能通过下标,变量名跟内容很难关联起来
缺点:不利于数据管理,效率低
为了解决上述问题, 我们引入了类与对象.
对象的概念
在生活中,我们每个人都是独立的个体,还有一些客观存在的个体,个体之间通过交互共同组成了这个世界。
面向对象程序的世界就是对现实生活的模拟,就是把现实生活中的场景搬到程序中。
Java 是面向对象的编程语言,独立的个体就是对象(Object),对象就是面向对象程序设计的核心。
对象之间相互协作,共同实现程序的功能。
现实中存在的个体,Ta们具有特定的属性和特定的行为,程序世界中的对象也类似,具有以下特点:
- 属性:个体的状态信息(数据)
- 行为:个体能够做什么(操作)
类的概念
现在我们知道了对象对程序世界的重要性,为了完成程序开发,我们程序员的工作就变成了:
创建一个个对象,并维护对象之间的交互。
在这个过程中,最基本,最先要解决的问题就是——对象的创建,怎么得到一个对象呢?
就像上帝造人,女娲造人是以自身为模板一样。我们程序员创建程序中的对象,也需要一个”模板”。
在Java中把创建对象的”模板”,称之为类(class)。
也就是说,到目前为止,我们终于可以给我们之前天天用的类(class)下一个明确的定义了:
创建对象的模板就是类!类就是创建对象的模板!
既然类是模板,那么:
类就抽取了同类别的所有对象属性和行为上的共性,通过这个模板,你可以创建(实例化)千千万万个对象,每个对象都遵循这个模板的结构和行为规范.
类与对象的关系
类和对象的关系:
-
类描述了,同种类型的对象,在属性和行为上的共性特征。
所以:
类是抽象的,而对象是具体的,所以对象也称为实例(instance)。
-
类只规定了共性,只是描述对象该有什么属性,该有什么行为。
但是:
具体对象属性的取值,即便是同类型对象也可能有差异。一个类可以创建出千千万万个不同的对象。

类的定义
定义类包括定义类本身,和定义类中结构两部分。
定义一个类
语法
[类修饰符列表] class 类名{ // 类体}定义类中的成员
类中成员分为两部分:
- 成员变量: 描述对象的共有属性
- 成员方法: 描述对象的共有行为
如何定义成员变量?
定义在类体中,方法外的变量,就称之为成员变量。
[修饰符列表] 数据类型 成员变量名;注意:
成员变量,在整个类体中生效,在整个类中的成员方法中都可以访问它!
如何定义成员方法?
[访问权限修饰符] 返回值类型 方法名(形参列表){ // 方法体}解释:
- 成员方法和我们之前使用的方法不一样,必须没有static修饰!!
- 访问权限修饰符我们还未学习,这里先默认是public!
- 其它诸如形参列表,方法体,方法名等结构和之前讲的方法一样。
定义一个学生类Demo
class Student{ // 属性 public String name; public int age; // 行为 public void study(){
}}对象的创建与使用
创建
创建类的对象一般需要在方法中进行,在确定能够创建对象的地方,可以使用以下语法创建对象:
类名 对象名 = new 类名();这个语法,我们并不陌生,在Scanner键盘录入的时候,已经见过了,现在做如下解释:
- 类名表示创建是何种对象,对象的类型是什么。
- 对象名和之前讲的数组名是一个概念,都是引用数据类型的引用。作为一个变量名,对象名遵循小驼峰式的命名规范。
- new关键字表示在堆上开辟空间创建对象,注意代码中的new关键字表示一定会在堆上创建一个独立的对象。
比如创建一个Student对象,就应该这么写:
Student s = new Student();使用
请大家思考一个问题:
如果不创建对象,能够直接访问类中的成员变量和成员方法吗?
很明显是不可以的
注意:
-
无论成员变量还是成员方法,都是属于对象的!必须创建对象才能访问它们!
-
实际上不同对象,访问类中同一个成员变量和成员方法的结果完全可能是不同的!!
对象创建出来后,大致可以做以下操作:
-
直接输出对象名
和数组直接输出数组名是一样,默认情况下,直接打印对象名得到的是:
- 该类的全限定类名 + ”@” + 十六进制的地址值
- 可以通过==进行地址值的比较
-
使用对象获取对象的属性和行为:
-
访问属性(获取属性值和修改属性值)
语法:
数据类型 变量名 = 对象名.成员变量;通过上述方式就可以直接获取属性值了,修改属性值也是类似的做法:
对象名.成员变量 = 值;注:对象中的成员变量,类似于数组对象中的元素,它们都具有默认初始化和默认值!!!
而具体默认值是什么,也和数组对象中的元素一致,不再赘述!!
-
调用行为(方法)
语法:
对象名.成员方法名(实参);如果方法有返回值,还可以接收返回值。
-
注意事项
- 类可以嵌套定义称之为内部类, 但是请现在不要嵌套定义类,一个Java文件中定义多个class应该并列而不是包含。
- 一个Java文件中的多个class是同包(文件夹)关系。
- 一个类当中,应该开门见山的定义成员变量,而后再写成员方法。
- 类中没有的属性和行为,对象是不可能有的,类是模板,模板中有才能体现在对象中。
- 使用new关键字就会创建新的对象,两条new语句创建的对象是完全独立的。
- 成员变量,在类的全局生效,不像局部变量仅在作用域内生效!成员变量,在整个类体中生效,在整个类中的成员方法中都可以访问它!
引用数据类型
学习目标:
- 了解类是一种自定义数据类型
- 了解类加载
基本概念
先回顾一下,在Java基础语法部分,我们给出的数据类型的概念/定义:
数据类型: 表示的是一组数据的集合,和基于该数据集合的一组合法操作。
那么这个定义能不能套在引用数据类型中呢?能否用数据类型的概念来统一基本数据类型和引用数据类型呢?
在类的定义中,我们知道类中的成员包括2部分:
- 成员变量
- 成员方法
成员变量本质上就是数据,成员方法本质上就是操作,那么假设做以下类比:
- 数据的集合: 类中成员变量的集合
- 操作的集合: 类中成员方法的集合
于是,就可以做出以下总结:
- 一个类的定义,实际上就是定义了一种全新的数据类型,一种自定义的数据类型。
- 这种完全不同于基本数据类型的数据类型,我们称之为”引用数据类型”。
类加载介绍
当我们在程序中使用一个基本数据类型时,由于基本数据类型是JVM当中已经预先定义好的(所以基本数据类型叫”内置数据类型”),JVM可以清楚的知道这个基本数据类型变量在内存中的存储方式(占用空间大小、结构等等),JVM能够正常开辟空间,正常给变量初始化赋值。
但是类这种引用数据类型,并不是内置数据类型,而是我们自定义的数据类型。
现在我们要根据一个类来创建它的对象,要让JVM帮助我们开辟空间创建引用和对象,JVM怎么知道到底要创建什么呢?难道它未卜先知吗?
显然是不可能的,某个类在一开始并不被JVM”认识”——它不知道类中有什么,必然不可能做任何操作。
所以在对某个类做任何操作之前,都需要让JVM来”认识”这个类型。
在Java中,把JVM”认识”一个类的过程,称之为类加载
关于类加载:
- 类加载的具体过程,我们后面会详细学习。这里我们先不用了解它的详细过程。
- 类加载是通过把某个类的二进制字节码文件(class文件)通过I/O流的形式,读取进JVM内存中的方法区实现的。
- 通过读取二进制字节码文件,并加载进JVM内存。这是JVM了解这个类型的过程。
- 类加载之后,就可以做很多类型相关的操作了。
- 类加载要在创建对象之前进行,换句话说创建一个类的对象必然触发该类的类加载!
我们通过画对象内存图,来展示这一过程:
- 一个对象的内存图,一个对象的创建过程。(创建1个Student对象)
- 三个对象的内存图,其中有两个引用指向同一个对象。(创建3个Teacher对象,并进行显式赋值)


总结:
-
一个类的类加载在一次程序运行过程中,最多只有一次。
-
多个引用指向同一个对象时,某个引用修改了对象的状态(成员变量的取值),再用其它引用访问会得到修改后的结果。
注:这一点实际上和数组是一样的。
-
类加载IO流操作, 很耗费性能,所以JVM在进行类加载时是”懒加载”的, 迫不得已才加载.
我们把一定会触发类加载的场景,称之为类加载的时机,目前已经学过的有:
- 首次创建该类对象
- 启动该类中的main方法
局部变量与成员变量
学习目标:
掌握局部变量与成员变量的区别
局部变量和成员变量是Java程序开发时,最常见的两种变量类型,所以一定要搞清楚,它们之间的不同。
两者的区别
局部变量和成员变量的比较,我们从以下五个方面去比较:
- 在类中定义的位置不同
- 在内存中的位置不同
- 生命周期不同
- 有无默认初始化不同
- 作用范围
在类中定义的位置不同
- 局部变量定义在:方法、方法的形参或者代码块结构等局部位置。
- 成员变量 定义在:类体中、非局部位置的成员位置。
在内存中的位置不同
- 局部变量存储在栈上的栈帧中
- 成员变量存储在堆中的对象中
生命周期不同
-
局部变量随着方法的执行,而被创建,随着方法的执行结束就会被销毁。局部变量和方法”同生共死”。
-
成员变量在对象创建以后就存在了,对象被销毁回收内存自然就不存在了。
但实际上只要该对象栈上的引用被销毁,对象成为”垃圾”,对象中的成员变量也就失去意义了。
有无默认初始化不同
- 局部变量没有默认初始化过程,必须手动初始化赋值。
- 成员变量,有默认的初始值。和数组一样,这里不再赘述。
作用范围
-
局部变量只在作用域的局部生效。
-
成员变量在整个类的成员方法中都可以使用,所以很多书籍也把成员变量称之为”全局变量”。
注意,加static的方法不是成员方法,成员变量不能在其中使用(原因后面再讲)
成员变量的赋值
限于目前的学习进度,其实你只知道两种给成员变量赋值的手段:
-
默认初始化,具有默认值。
-
显式赋值。即在类中定义成员变量时,直接了当的给出该成员变量的取值,就是显式赋值!比如:
成员变量的显式赋值演示
class Student{// 类体String name = "张三";}
显式赋值和默认初始化赋值的顺序,任何时候都要记住,对象中的元素,默认初始化永远是第一步!在任何给成员变量赋值的手段执行前,默认初始化都已经执行了!
可以认为默认初始化是JVM在创建对象的内存结构时,它”顺手”给出的!
this关键字
学习目标:
- 掌握this关键字的使用
引例
学习完对象与类后,做以下练习:
创建一个汽车类,有颜色(color)和速度(speed)两个属性,行为是可以跑(run)。
实现:
- 在run成员方法中访问速度和颜色两个属性,调用该方法查看结果。
- 在run成员方法的形参列表中,添加一个局部变量speed,不修改方法体,调用该方法查看结果。
思考:
- 两次方法调用的结果一样吗?为什么?
- 如果我想在2中得到1的访问结果,怎么办呢?
code
public class Demo { public static void main(String[] args) { // 创建Car对象 car car = new car(); // 调用成员方法 //car.run(); car.run(200); }}
class car{ // 定义成员变量 String color = "黑色";
double speed = 120.0;
//// 定义成员方法 //public void run(){ // System.out.println(color + "的车在高速公路上以" + speed + "公里每小时疾驰!"); //}
// 定义成员方法 public void run(double speed){ System.out.println(color + "的车在高速公路上以" + this.speed + "公里每小时疾驰!"); }
}很明显,由于就近原则的影响:
当方法中存在同名的局部变量和成员变量时,访问该同名变量,访问到的必然是距离访问语句更近的局部变量,而不是同名的成员变量。
这个时候,如果还想访问同名成员变量,普通的手段是做不到了,就需要this关键字来实现访问。
概念
this关键字(重点):
Java类中的每个成员方法的形参列表中都隐含了一个传参(隐式传参),传入的是当前对象,用this关键字指向!(为什么类中的所有成员方法都可以访问到成员变量---->就是因为this的存在)
所以:
-
this是一个引用,这个引用指向当前对象。
-
何为当前对象?
很多同学,在初学this时,会经常疑惑何为当前对象呢?
其实非常简单:
this指向当前对象,是类中成员方法的一个隐式传参。
成员方法总会需要一个对象,使用对象名点来调用,那么这个调用该成员方法的对象,就是当前对象!
Demo
Student stu = new Student();stu.study();stu.sleep();成员方法study()和sleep()的this指向当前对象,指向的就是stu对象。
验证当前对象就是调用方法的那个对象
package _04oop.com.cskaoyan._02this._02verfiy;
/** * @description: * @author: 景天 * @date: 2022/11/8 9:55 **/
public class Demo { public static void main(String[] args) { // 创建对象 Student student = new Student(); // 调用方法 student.printThis(); // 打印对象 System.out.println(student); }}
class Student{ // 定义成员方法 public void printThis(){ System.out.println(this); }}可以发现和在main方法中打印对象名(引用)结果一致,证明当前对象就是调用方法的那个对象。
this关键字的作用
既然this已经指向当前对象,是一个引用,那么它基本的用途就有:
- 在成员方法中,用this引用去访问类中成员变量和调用类中成员方法。由于this本身就是隐含的,所以一般情况下,可以省略this,直接访问类中成员。
- 特殊情况下,当成员方法中的局部变量和成员变量同名时,可以用 “this.”访问 来表示访问同名成员变量,来和同名局部变量做区分。这种情况,this是不能省略的。
- 在成员方法中只要使用 “this.”访问 一个变量,那么该变量一定是成员变量。在代码比较复杂的情况下,可以显著增加代码可读性, 可以使用this.成员变量对成员变量进行赋值--->set方法
注意事项
- this指向当前对象的隐含传参,必须是在普通成员方法中,加static的方法中,没有该this传参。(所以static方法不能直接访问类的成员,需要先创建对象才能访问。)
- 既然this指向当前对象,那么不同的this指向的对象必然不同。
构造方法
引例
创建一个教师类,有课程和年龄两个属性,行为是上课。
现在我们需要创建以下对象:
- 18岁的Java老师对象
- 28岁的C++老师对象
- 30岁的Python老师对象
- …
按照之前我们的做法,需要先创建出对象,再进行成员变量的赋值。
Code
Teacher teacher = new Teacher(); teacher.course = "Java"; teacher.age = 20; teacher.teach();
Teacher teacher2 = new Teacher(); teacher2.course = "C++"; teacher2.age = 21; teacher2.teach();
Teacher teacher3 = new Teacher(); teacher3.course = "Python"; teacher3.age = 22; teacher3.teach();如果属性很多, 需要创建很多对象,就有点过于麻烦了。
对象的属性,能不能”出厂”的时候就设定好呢?想要在创建教师对象时,就直接指定这个对象的属性?
有这种需求时,就需要构造方法(constructor,也叫构造器)来完成了。
构造器语法
构造方法也是方法,但属于一种特殊的方法
[访问权限修饰符] 类名(形参列表){ // 构造方法体}说明:
-
权限修饰符先使用public
-
构造方法名必须跟类名相同(一模一样,包括大小写)
-
构造方法没有返回值, 也不需要写返回值
-
形参列表可以为空, 称为无参构造方法,非空为有参构造方法
-
构造方法体,和一般方法类似,可以写语句
构造器作用与使用
构造器的作用是用来给成员变量赋值的,完成对对象的初始化
说明:
- new关键字去创建对象的时候,JVM自动去调用构造方法,构造方法无法通过普通方法的调用方式调用。
- 构造器的作用不是创建对象,创建对象是JVM的事情。构造器只是告诉JVM在创建对象过程中,给成员变量赋什么值。
使用方式是:
new 类名(实参列表);通过实参列表的不同,来判断调用哪个构造器。这实际也是方法重载的应用!
需求:
使用构造方法改进引例, 在创建教师对象时,就直接指定这个对象的属性(完成成员变量的赋值操作)
Code:
package _04oop.com.cskaoyan._03constructor._02defineuse;
/** * @description: * @author: 景天 * @date: 2022/11/8 10:04 **//*使用构造方法改进引例, 在创建教师对象时,就直接指定这个对象的属性(完成成员变量的赋值操作)
1. 18岁的Java老师对象2. 28岁的C++老师对象3. 30岁的Python老师对象 */public class Demo { public static void main(String[] args) { // 创建对象 Teacher t1 = new Teacher("Java", 18); Teacher t2 = new Teacher("C++", 28); Teacher t3 = new Teacher("Pyhond", 30);
// 调用方法 t1.teach(); t2.teach(); t3.teach(); }}
// 定义教师类class Teacher{ // 定义成员变量 String course; int age;
/* [访问权限修饰符] 类名(形参列表){ // 构造方法体 } */
// 无参构造方法 public Teacher(){ // 方法体 System.out.println("无参的构造方法执行了"); }
// 有参构造方法 public Teacher(String tCourse, int tAge){ // 方法体 System.out.println("有2个参数的构造方法执行了"); // 给成员变量赋值 course = tCourse; age = tAge; }
// 定义成员方法 public void teach(){ System.out.println(age + "的老师讲" + course); }
//public void teach(String name){ // System.out.println(age + "的老师讲" + course); //}}注意事项与使用细节
-
一个类中,是允许同时定义多个构造方法的,即构造方法重载,多个构造器的形参必须不同。
- 比如: 我们可以给Teacher类定义一个构造器, 用来创建对象的时候, 只指定课程, 不指定年龄
-
构造方法的名字必须和类名一模一样
-
构造器没有返回值
-
构造器是完成对象的初始化(给对象赋值), 并不是创建对象, 创建对象时, 系统根据实参列表自动调用该类的构造器
-
类中默认提供的无参构造方法,是在该类没有任何构造器的情况下才有的。但是如果类中有任一构造器(有参/无参),那么就没有默认无参存在了
-
在构造器中也会隐含this传参, 我们可利用this对成员变量进行赋值, 也可以使用快捷键, alt + insert快速生成构造器.
-
构造器中还可以用this表示调用其它构造器,语法:
this(实参列表);表示调用类中的其它构造器,根据实参列表决定调用哪个构造器。
注意,在构造器中使用this(实参)表示调用类中其他构造器时,这行代码一定要处在构造器代码的第一行!
当然,既然必须在第一行,那么也只能用一次了。
构造器的赋值顺序
这里我们不妨总结一下,学完构造器后,三种给成员变量赋值的方式:
- 默认初始化,具有默认值。
- 显式赋值,直接将值写在成员变量声明的后面。
- 构造器赋值。
通过下面的例子分析程序执行流程 , 以上三种赋值方式的执行顺序
class Person{ String name; int age = 20;
public Person(String name, int age) { this.name = name; this.age = age; }
public Person() { }}
// 创建Person对象,成员变量的值分别是什么Person p = new Person("张三", 30);执行流程:
- 加载Person类信息, 只会加载一次
- 堆中分配空间
- 完成对象初始化
- 默认初始化
- 显示赋值
- 构造器赋值
- 堆中的对象的地址返回给p(对象的引用)
在各式各样给成员变量赋值的方式中,构造器是最后执行的!!!
对象创建流程
Debug练习 对于下列程序代码,请指出创建Student对象,程序每一步的执行流程:
构造器Debug模式练习
public class Demo{public static void main(String[] args){Student stu = new Student(18,"王冰冰"); }}class Student {int age = 10;String name = "张三";double a = 100;
public Student(int age) { System.out.println("Student age"); this.age = age;}
public Student() {}
public Student(int age, String name) { this(age); System.out.println("Student age,name"); this.name = name;}}在这个案例中,我们发现Student双参构造器中,有使用this调用单参构造器,那么它们的执行顺序是:
- 类加载不要忘记了。
- 程序会先找到,Student的双参构造器,但是并不会执行,而是
- 先跳转执行this构造器,但是this构造器中的代码,也不会立刻执行(因为构造器赋值一定最后执行),而是
- 先从上到下执行成员变量的显示赋值,然后
- 跳回执行this构造器代码
- 最后执行双参构造器代码
小练习
对象与类
完成以下案例,思考一下怎么能够找到系统中类、对象究竟是哪些?
- 用面向对象的思想模拟LOL当中盖伦的战斗
- 用面向对象的思想模拟植物大战僵尸
如何从需求中找到对象和类呢?
对象描述的是程序世界中的个体/实体,所以一般都是名词。
抽取出全体对象的共性特征,也就是成员变量和方法,就可以定义一个类。
注意事项:
-
对象之间如果需要交互,可以考虑持有对方的引用作为成员变量。
-
日常开发中,不建议随便把很多类定义在同一个Java文件中。
正确情况下,建议一个Java文件中,仅定义一个public class和文件名保持一致。
当然,如果确有需求,也是可以定义非public class的。(少见但不是见不到)
-
一个Java文件下的所有类,属于同包关系。
LOL
public class Demo { public static void main(String[] args) { Legend galen = new Legend("Galen", "沉默打击", "勇气", "爱的魔力转圈圈", "大宝剑"); galen.fight(); }}
// 英雄类class Legend { String name; String skillQ; String skillW; String skillE; String skillR;
/* 定义构造器,从代码规范上来说,构造器写在 成员变量的下面 成员方法的上面 而且都要写在一起(实际上所有构造方法重载的方法都应该写在一起) 写在一起的顺序: 可以按照参数个数的多少去排列,个数少的在上面,多的在下面 */ public Legend() { }
public Legend(String name, String skillQ, String skillW, String skillE, String skillR) { this.name = name; this.skillQ = skillQ; this.skillW = skillW; this.skillE = skillE; this.skillR = skillR; }
public void fight() { System.out.println("名字为" + name + "的英雄,正在使用"+skillR+"技能战斗!"); }
}植物大战僵尸

public class Plant { String name; int price; int hp; // 攻击力 int damage; /* 在开发中为了方便两个对象之间的交互 往往会让某个对象持有另外一个对象的引用作为自身的成员变量 这样对象之间的交互会更加灵活,是常用的做法 */ Zombie plantZ;
public Plant() { }
public Plant(String name, int price, int hp, int damage) { this.name = name; this.price = price; this.hp = hp; this.damage = damage; }
// 行为 public void attack() { System.out.println(name + "正在攻击" + plantZ.name); }
public void beAttacked() { System.out.println(name + "正在被" + plantZ.name + "攻击,现在还剩下" + (hp - plantZ.damage)); }}
public class Zombie { String name; int hp; // 攻击力 int damage; double speed;
Plant zombieP;
public Zombie() { }
public Zombie(String name, int hp, int damage, double speed) { this.name = name; this.hp = hp; this.damage = damage; this.speed = speed; }
// 行为 public void attack() { System.out.println(name + "正在攻击" + zombieP.name); }
public void beAttacked() { System.out.println(name + "正在被" + zombieP.name + "攻击,现在还剩下" + (hp - zombieP.damage)); }
public void move() { System.out.println(name + "正在以" + speed + "的速度,接近你的脑子~~"); }
public void eat() { System.out.println(name + "吃掉了你的脑子~真香~"); }}值传递练习
学习对象与类后,再复习一下如果方法的参数是需要一个对象,值传递是怎样进行的。
练习: 定义一个学生类,该类具有一个int属性age 1.在测试类中写一个方法,交换两个Student对象的age属性 请问能够交换成功吗?原因是什么?
2.在测试类中写一个方法,交换两个Student对象的引用(地址) 请问能够交换成功吗?原因是什么?
结论:
- Java只有值传递
- Java当中的方法可以改变对象的状态(成员变量的取值)但是无法直接交换两个引用(无法改变局部变量)
成员变量赋值顺序练习
public class Demo { public static void main(String[] args) { Student s = new Student(18, "长风"); System.out.println(s.age); System.out.println(s.name); System.out.println(s.var); System.out.println(s.cat.price); }}class Student { int age = 10; String name = "张三"; double var = 20;
public Student(int age) { System.out.println("Student age"); this.age = age; }
public Student() { }
public Student(int age, String name) { this(age); System.out.println("Student age,name"); this.name = name; }
Cat cat = new Cat(1000); Cat c2;}
class Cat { double price;
public Cat() { }
public Cat(double price) { System.out.println("Cat price"); this.price = price; }}思考程序输出的顺序,搞清楚其中赋值的顺序。
1.Demo类加载,main方法执行2.Student类加载,创建Student对象,调用2参构造3.此时2参构造不会执行,会因为this(单参)调用单参构造,4.此时单参构造不会执行,会先对对象中成员变量默认初始化,进行默认赋值5.进行显式赋值6.Cat cat = new Cat(1000);执行到这里,会触发Cat类加载7.调用Cat单参构造方法8.Cat单参构造方法先不执行,先默认赋值,没有显式赋值,执行构造方法进行赋值9.Student类中的成员全部显式赋值成功--->进行构造器赋值10.Student的单参构造器先执行,再执行两参构造器11.Student对象创建成功并完成了赋值--->回到main方法继续执行12.输出相应结果static关键字
学习目标:
- 掌握静态成员变量的使用与特点
- 掌握静态成员方法的使用与特点
- 掌握静态与非静态的区别
引例
场景如下:
一场篮球比赛, 梦之队有5名顶级球员Kobe, James, Stephen… 都是神射手擅长投3分,投篮必进
每进一球, 队伍分数+3, 比赛结束, 统计一下该队伍得分情况, 写程序模拟这个场景.
思路:
- main里面定义int count, 用来记录分数
- 每当有1个球员进球, 分数+3
Code:
package _04oop.com.cskaoyan._04static._01introuction;
/** * @description: * @author: 景天 * @date: 2022/11/11 9:48 **//*一场篮球比赛, 梦之队有5名顶级球员Kobe, James, Stephen...... 都是神射手擅长投3分,投篮必进
每进一球, 队伍分数+3, 比赛结束, 统计一下该队伍得分情况, 写程序模拟这个场景.
思路:
- main里面定义int count, 用来记录分数- 每当有1个球员进球, 分数+3 */public class Demo { public static void main(String[] args) { // 定义一个计数器 int count = 0; Player p1 = new Player("Kobe"); p1.shot(); count += 3; System.out.println("分数: " + count);
Player p2 = new Player("James"); p2.shot(); count += 3; System.out.println("分数: " + count);
Player p3 = new Player("Stephen"); p3.shot(); count += 3; System.out.println("分数: " + count);
}}
// 定义一个Player类class Player { // 定义成员变量 String name;
public Player(String name) { this.name = name; }
// 定义成员方法 public void shot() { System.out.println(name + "进球了!"); }}问题分析:
int count这个值是个局部变量, 独立于对象之外的, 使用起来不是很方便
引出来static关键字.
静态成员
根据static修饰的内容不同, 有以下分类
- 静态成员变量, static修饰成员变量 (有的资料里也称为类变量, 类属性指的都是静态成员变量)
- 静态成员方法, static修饰成员方法(有的资料里也称为类方法, 指的还是静态成员方法)
统称为类的静态成员
静态成员变量
基本语法
[访问权限修饰符] static 数据类型 变量名;使用与特点:
- 和普通成员变量一样,都具有默认值(默认值和普通成员变量是一样的)
- 静态成员变量属于类的,完全不需要创建对象使用。
- 访问和使用静态成员变量不推荐使用”对象名.”,而应该使用”类名.”!
- 静态成员变量的访问/赋值/使用都不依赖于对象, 而是依赖于类
使用案例:
设计一个int 类型的count值来表示球队得分, 每有一个球员得分, 那么count值+3, 要求count被所有对象所共享的即可. 使用静态成员变量修改之前的引例.
class Player{ String name; // 使用static关键字修饰成员变量, 使其成为静态成员变量. static int count;}Code
package _04oop.com.cskaoyan._04static._02static_field;
/** * @description: * @author: 景天 * @date: 2022/11/11 9:58 **//*设计一个int 类型的count值来表示球队得分, 每有一个球员得分,那么count值+3, 要求count被所有对象所共享的即可. 使用静态成员变量修改之前的引例. */public class Demo { public static void main(String[] args) { // 静态成员变量可以通过类名.方式访问 // 推荐使用类名.方式访问 System.out.println("得分: " + Player.count);
// 创建Player对象 Player p1 = new Player("Kobe"); // 投篮 p1.shot(); // 更改分数 + 3 p1.count += 3; System.out.println("得分: " + p1.count);
// 创建Player对象 Player p2 = new Player("James"); // 投篮 p2.shot(); // 更改分数 + 3 p2.count += 3; System.out.println("得分: " + p2.count);
// 创建Player对象 Player p3 = new Player("Stephen"); // 投篮 p3.shot(); // 更改分数 + 3 p3.count += 3; System.out.println("得分: " + p3.count);
}}
// 定义一个Player类class Player { // 定义成员变量 String name;
// 定义一个静态的成员变量,表示球队得分 static int count;
public Player(String name) { this.name = name; }
// 定义成员方法 public void shot() { System.out.println(name + "进球了!"); }}内存及原理解析:
静态成员的访问并不依赖于创建对象,可以直接通过类名访问,其原因在于:
随着类加载完毕,静态成员就存在,并且能够使用了!
某个类的某个静态成员变量只有一份,且被所有对象共享,属于类,无需创建对象使用。

注意事项:
- 只存在静态成员变量,不存在”静态局部变量”
局部变量,已经被方法限制了作用域,不能用static修饰它!
静态成员方法
基本语法
[访问权限修饰符] static 返回值类型 方法名(形参列表){ //方法体}使用与特点
- 无需创建对象就可以直接通过类名点直接调用。
- 同一个类中的static方法互相调用可以省略类名,直接用方法名调用。(这就是我们之前方法的调用)
注意事项:
-
一个类中,静态方法无法直接调用非静态的方法和属性,也不能使用this,super关键字(super后面会讲),静态的方法只能访问静态的
-
经典错误:Non-static field/method xxx cannot be referenced from a static context
-
原因:静态方法调用的时候,完全有可能没有对象,没有对象普通成员就无法访问。
-
普通成员方法当中,既可以访问静态成员的, 也可以访问非静态成员。普通成员方法访问任意的
-
访问静态成员变量的时候,使用类名.变量名的形式访问,以示区别,增加代码可读性
类加载时机
静态成员需要在类加载时期,完成准备,类加载结束就能够使用。
所以访问类的静态成员,一定会触发该类的类加载。
总结,到目前学习过的类加载时机:
- new直接创建该类的对象。(首次)
- 启动该类中的main方法。
- 访问该类的静态成员(方法和变量) (首次)
static VS 非static
当我们了解static成员的特点后,静态成员和非静态成员的区别就很明显
我们从以下四个角度比较(拿成员变量为例)
- 成员的所属
- 在内存中的位置
- 在内存中出现的时间
- 调用方式
其比较的结论如下:
- 所属不同
- 静态成员变量属于类,所以也称为为类变量
- (普通)成员变量属于对象,所以也称为对象变量(实例变量)
- 在内存中的位置不同
- 堆上的这个类所对应的字节码文件对象,即Class对象中,被所有对象共享
- 成员变量存储于堆内存,每个对象独享自己的成员变量
- 在内存中出现时间不同
- 静态变量随着类的加载而加载,比成员变量出现的要早
- 成员变量随着对象的创建而存在
- 调用方式不同
- 静态变量可以通过类名调用,也可以通过对象调用(不推荐)
- 成员变量只能通过对象名调用,必须创建对象
使用场景
这里,根据static关键字的一些特点来明确它的使用场景,给大家以后使用static关键字做一下参考。
-
静态成员变量:
- 属于全体对象所共享而不是独属于某个对象的成员变量
所以当存在需要所有对象共享的变量时,应该使用static修饰的静态成员变量。
- 在整个类全局独一份的(因为类加载只有一次)
所以,如果希望某个变量在类的全局独一份时,应该使用static修饰的静态成员变量。
举例1 :
创建一个学生类,用来描述我们班全体同学
要求:
属性:姓名,性别,年龄,学号,学校信息
行为:吃饭,学习
我们简单思考可以知道, 无论你是张三或者李四, 学校信息这个属性实质上应该是被全体同学所共有的属性,而不是独属于某个对象的, 这种场景下就可以使用static 修饰学校信息
举例2:
创建一个学生类, 有2个属性: 姓名String name , 学号int id
统计外部创建Student类对象的个数 假设给Student类的对象自动编号
这个编号第一次创建对象是10001 随后每创建一个新对象就+1
package _04oop.com.cskaoyan._04static._05use_case;/*** @description:* @author: 景天* @date: 2022/11/11 15:04**//*利用了static成员变量类全局唯一 且被共享统计外部创建Student类对象的个数 假设给Student类的对象自动编号这个编号第一次创建对象是10001 随后每创建一个新对象就+1*/public class Demo {public static void main(String[] args) {Student s1 = new Student();System.out.println(s1.id);Student s2 = new Student();System.out.println(s2.id);Student s3 = new Student();System.out.println(s3.id);Student s4 = new Student();System.out.println(s4.id);System.out.println("共计创建了" + Student.count + "个学生");}}class Student{// 姓名String name;// 学号int id;// 统计学生数量static int count;// 初始学号static int initNumber = 10001;public Student() {// 学生数量+1count++;// 学号递增this.id = initNumber++;}} -
静态成员方法:
- 静态方法的最主要特点就是调用简单,无需创建对象即可调用。
所以如果希望一个方法能够更方便快捷的去调用,可以把它声明为static修饰的静态成员方法。
- 根据静态成员方法调用简单的特点,当一个类中全部都是静态成员方法时,
类中的所有方法的调用都可以使用类名点去完成,这就是Java当中的”工具类”。比较典型的有:数组工具类Arrays、集合工具类Collections、数学工具类Math等等。
static执行顺序练习
请说明程序的输出结果,并分析流程
public class Demo {static Cat cat = new Cat();Dog dog = new Dog();Dog dog2;static Dog dog3;
public static void main(String[] args) { System.out.println("hello world!"); Demo d = new Demo();}
public Demo() { System.out.println("demo");}}
class Cat {static Dog dog = new Dog();
public Cat() { System.out.println("cat");}}
class Dog {public Dog() { System.out.println("dog");}}你能得出什么结论?
1.静态成员变量的显式赋值,是在类加载过程中执行的。不管何种方式触发该类的类加载,这个过程都要执行。2.类加载可以连环触发,一个类可以最先开始类加载,但是不一定会最先完成类加载
3.无论是静态成员变量还是成员变量,只有声明且没有其它任何赋值方式赋值,那就只有默认值。
匿名对象
学习目标:
- 掌握匿名对象的概念
- 掌握匿名对象的用途与特点
什么是匿名对象
在Java当中,匿名对象指的就是没有名字的对象。
或者,说的更清楚一点,就是:在创建对象时,就没有引用指向的对象,就是匿名对象!
匿名对象的语法很简单,只需要在方法等位置中写下列语法:
new 类名();以上语法就得到了一个匿名对象,从实质上看:
该对象没有栈上的引用指向,没有所谓的**“对象名”**,是一个匿名对象。
匿名对象的用途
匿名对象主要有两个用途(常见用途):
-
使用匿名对象作为方法的实参
当定义以下方法时,表示该方法需要传入一个对象(这个对象必须是”类名”的对象或者子类对象)
注:子类对象的概念后面继承中会学习,这里为了文档严谨性写出来,你可以先忽略!
[修饰符列表] 返回值类型 方法名(类名 对象名){//方法体}这个时候,常规的做法是创建对象,然后传入引用。但实际上这里可以直接传入匿名对象。
假如一个test方法需要传入一个Student对象,就可以这么写:
test(new Student()); -
使用匿名对象作为方法的返回值
当定义以下方法时,表示该方法需要传入一个对象(这个对象必须是”类名”的对象或者子类对象)
注:子类对象的概念后面继承中会学习,这里为了文档严谨性写出来,你可以先忽略!
[修饰符列表] 类名 方法名(形参列表){//方法体return (匿名对象);}
匿名对象的优缺点
优点:
-
使用匿名对象,代码简洁(相当于少写了一些代码)这是匿名对象最主要的优点。
-
匿名对象在使用完毕后会立刻成为”垃圾”等待GC回收,从理论角度上来说,可以提升内存利用率。
但是GC不具有即时性,并不是一个对象更早成为”垃圾”就会更好回收,这个优点仅是理论上的。
缺点:
匿名对象由于没有引用指向,所以它是一次性的,用完后就无法再次使用了。
匿名对象使用场景
我们根据匿名对象的优缺点,很容易总结出匿名对象的使用场景:
- 需要一个对象去实现功能,并且该对象仅需使用一次即可,为了代码简洁,推荐使用匿名对象
- 但是,如果一个对象可能会被复用,像以下代码频繁创建匿名对象是得不偿失的
不要滥用匿名对象
test(new Student());test(new Student());test(new Student());...创建对象是需要耗时耗费内存空间的,不要为了一时的方便,频繁创建匿名对象。
总之:
如果某个对象,仅使用一次,使用匿名对象简洁方便。
但如果有多次使用某个类的对象的需求时,频繁使用匿名对象会导致频繁创建对象,降低代码性能,得不偿失!
代码块
学习目标
- 掌握代码块的几种分类
- 掌握代码块的作用与特点
- 掌握几种代码块的执行顺序
概述
之前,我们理解的代码块就是为了限制局部变量的一个大括号,今天来详细的学习一下代码块。
代码块的定义:由若干条Java语句组成,并且用一对大括号括起来的结构,叫做代码块。
代码块的分类,根据其位置和声明方式的不同,可以分为:
-
局部代码块
-
构造代码块
-
静态代码块
-
同步代码块
注:同步代码块,涉及多线程知识,后面多线程再学习,今天略过它。
构造代码块
语法定义
什么是构造代码块 ?
- 定义在类的成员位置,使用以下声明方式声明的代码块,称之为构造代码块。
//成员位置{ // 局部位置}//成员位置这个语法只有一个需要注意的地方:
构造代码块内部属于局部位置,在里面定义变量,就是一个仅在构造代码块中生效的局部变量。
作用
随着构造器的执行,用于在创建对象过程中,给成员变量赋值
这里总结给成员变量赋值的几种方式(创建对象过程中):
- 默认初始化,具有默认值
- 显式赋值
- 构造代码块
- 构造器
学习对象中成员变量的赋值,和赋值顺序要遵循”掐头去尾”的原则:
-
头:默认初始化,具有默认值,在对象结构存在于对象中,对象中的成员变量就已经具有了默认值。
我们程序员所有能干预的赋值方式,都是在默认初始化的基础上进行的。
-
尾:构造器,构造器在整个对象的成员变量赋值过程中,处在最后的阶段,最后被执行。
明确以上两点后,我们现在只需要研究显式赋值和构造代码块的赋值顺序,
经过代码测试,我们发现:
显式赋值和构造代码块的执行顺序,并不是固定的,而是按照代码的书写顺序去执行的:
- 这两个结构,谁写在代码书写顺序的上面,谁就先执行。
- 后执行结构的结构,自然会覆盖先执行结构的结果。
这样,类中显然会出现类似以下代码:
//构造代码块{ a = 10;}int a = 1;这种代码形式,按照从上到下的顺序来看的话,显然有些奇怪——成员变量还未定义,却进行了赋值。
那么构造代码块为什么能够有这种特点呢?它的原理是什么呢?
原理
通过查看反编译class文件(通过IDEA),我们发现编译后的代码中并不存在构造代码块的结构,而是:
直接将成员变量的显式赋值和构造代码块中的代码智能地加入,类所有的构造器中的前几行:
所谓智能是为了保证:成员变量的显式赋值和构造代码块,按照代码的书写顺序从上到下执行!
于是,我们可以得出以下结论:
- 使用new对象的方式创建对象,不论使用哪个构造器,构造代码块都会随之执行。
- 构造器是每一次new对象都会执行一次,所以构造代码块也会随之执行一次。
- 构造代码块中的代码要放入构造器的首几行,所以在同一个类中,构造代码块总是先于它的构造器而执行。
创建对象过程中的执行顺序
总结一下到目前为止,创建对象过程中可能碰到的结构的执行顺序:
new对象过程中,各种结构的执行顺序:
-
对象结构存在后就进行默认初始化,所有成员变量都具有默认值后,再开始其余赋值操作
-
找到new对象的那个构造器
- 如果它的首行显式地调用了另一个构造器this(实参)
(注:显式调用构造器目前指的是this调用自身构造器,其它场景这里先不考虑)
那么程序会先跳转到那个构造器,但是不会立刻执行,而是:
- 按照类中构造代码块和显式赋值的代码书写顺序,从上到下执行其中的代码,执行完毕后:
- 跳转回this语句要指示执行的构造器,执行其中的代码,然后:
- 跳转回new对象构造器,执行完毕后,创建对象结束。
注:整个过程中,构造代码块和显式赋值的代码只会执行一次,不会执行多次!!
- 如果它的首行没有显式调用另一个构造器
那么会先从上到下执行构造代码块和显式赋值代码,执行完毕后:
跳转回new对象构造器,执行完毕后,创建对象结束。
注:整个过程中,构造代码块和显式赋值的代码只会执行一次,不会执行多次!!
-
以上总结,没有引入继承的概念,并不全面,我们会在继承中补全它。
实际用途
构造代码块最大的特点就是其中的代码,最终会加入类的所有构造器中,所以依据这一点:
-
我们可以”提取公因式”一样的,把所有构造器都需要执行的代码,放入构造代码块中。
-
其次,构造代码块毕竟是给成员变量赋值用的,所以:
如果需要很复杂的代码完成成员变量的赋值(如果只是给一个值,用显式赋值和构造器足矣)
比如需要一个算法,需要一定计算等等。在这些场景下,使用构造代码块赋值也是一个不错的选择。
练习
-
创建一个类Student,类中有多个构造器,请写代码统计外部创建Student对象的次数。
-
读程序题:
代码块练习题
public class Demo {public static void main(String[] args) {Cat c = new Cat(28, "紫色");System.out.println(c.age);System.out.println(c.color);}}class Cat {{age = 18;System.out.println("age building block");}int age = 10;String color = "黄色";{color = "黑色";System.out.println("color building block");}public Cat() {}public Cat(int age) {System.out.println("age constructor");this.age = age;}public Cat(int age, String color) {this(age);System.out.println("age color constructor");this.color = color;}}说出程序输出的顺序,并说明原因。
-
最后思考:构造代码块能不能用于给静态成员变量赋值?
静态代码块
语法定义
什么是静态代码块?
定义在类的成员位置,使用以下声明方式声明的代码块,称之为静态代码块
//成员位置static{ // 局部位置}//成员位置这个语法只有一个需要注意的地方:
静态代码块内部属于局部位置,在里面定义变量,就是一个仅在静态代码块中生效的局部变量。
作用
静态代码块在类中的作用
随着类加载的过程而执行,静态代码块可以看成是一个在类加载过程中,会自动调用的静态方法!用于给静态成员变量赋值!
这里还是想再强调一下:一般的静态成员方法是调用才会执行,并不是类加载过程中就会自动调用执行它!
想要一段语句,能够在类加载过程中自动被调用,需要使用静态代码块,而不是静态方法!!
这里总结一下给静态成员变量赋值的几种方式(类加载时期):
- 默认初始化,具有默认值
- 显式赋值
- 静态代码块
在这三个赋值方式中,默认初始化是永远第一步进行的,显式赋值和静态代码块赋值的执行顺序:
- 按照代码的书写顺序去执行,谁写在代码顺序的上面,谁就先执行。
- 后执行结构的结果,会覆盖掉先执行结构的结果。
至于说到原理,静态代码块的执行,是JVM层面进行类加载的一种设计机制,是类加载的特殊设计机制保证的。
实际用途
-
复杂的静态成员变量的赋值。
数据库中加载JDBC驱动(最经典的),也可能是最常见的。
-
如果有一段代码,在类的全局,从始至终,只运行一次,可以写到静态代码块中。(依赖于类加载只有一次的原理)
比如一些初类的始化工作,就可以放在静态代码块中完成。
最常见的就是类System的初始化,源码如下:

System类的初始化,依赖于本地方法registerNatives()的执行。
注意事项
一些细节问题(重要)
-
静态代码块可以近似看成一个,在类加载时期自动调用的静态成员方法,所以不能在里面调用非静态。(没有对象)
包括this关键字,和后面学习的super关键字,都不能使用。
这意味虽然构造代码块可以给静态成员变量赋值,但静态代码块不能给成员变量赋值。
说白了,还是要搞清楚,谁先谁后执行的问题!
-
当需要使用复杂的代码给静态成员变量赋值时,可以使用静态代码块。
但如果仅仅是简单的赋值,直接显式赋值即可。
总得来说,静态代码块用得不多。
-
静态代码块也经常被用来测试类加载的顺序(重要)
一个类的静态代码块如果没有被执行,说明它没有被完全类加载。
补充类加载过程
首先,一个类从被加载到JVM内存中开始,到卸载出内存为止,一个类的生命周期包括:

一共是:
-
加载(Loading)
-
验证(Verification)
-
准备(Preparation)
-
解析(Resolution)
-
初始化(Initialization)
-
使用(Using)
-
卸载(Unloading)
注:其中,验证、准备和解析可以统称为连接(Linking)。
一个类在JVM中的生命周期,共有七个阶段。
其中”加载 —> 连接 ---> 初始化”这三个步骤,即一个类的类加载过程。
这三步主要做:
-
加载主要是做通过类加载器(ClassLoader)将class字节码文件读取进JVM内存的操作, 并在内存中生成这个类对应的Class对象
-
在连接过程中:
-
验证,主要目的是为了确保class文件的字节流中包含的信息符合当前JVM的要求,不会影响JVM的安全。
-
准备,主要目的是进行静态成员变量的默认初始化,设置初始值。
这样,就保证了静态成员变量的默认初始化,永远最先进行。
-
解析,主要目的是将符号引用替换为直接引用。
-
-
初始化是类加载的最后一个步骤。主要目的是执行和static相关的内容,包括:
执行静态成员变量的显式赋值。
执行静态代码块。
以上,关于类加载各步骤,大致的作用,我们就了解了。
所以这里要重新认识一个概念:类加载的时机
类加载的时机,说得更准确一点,应该是类初始化的时机。当然,想要初始化一个类,必然要先进行加载和连接。
练习
类加载过程练习一
public class Demo {static { System.out.println("Demo类开始初始化步骤了!");}
static Cat cat = new Cat();Dog dog = new Dog();
public static void main(String[] args) { System.out.println("hello world!"); Demo d = new Demo();}
public Demo() { System.out.println("demo");}}
class Cat {static { System.out.println("Cat类开始初始化步骤了!");}
static Dog dog = new Dog();
public Cat() { System.out.println("cat");}}
class Dog {static { System.out.println("Dog类开始初始化步骤了!");}
static Demo demo = new Demo();
public Dog() { System.out.println("dog");}}代码块综合练习
public class ExerciseBlock { static { System.out.println("main方法静态代码块!"); } { System.out.println("main方法构造代码块!"); } public static void main(String[] args) { System.out.println("main方法开始执行!"); Star s = new Star(18,"马化腾"); System.out.println(Star.name); System.out.println(s.age); }}class Star{ { age = 18; Star.name = "杨超越"; System.out.println("我喜欢杨超越"); } static String name = "王菲"; int age = 28; static { name = "杨幂"; System.out.println("我喜欢杨幂"); } public Star(int age,String name) { this(age); System.out.println("age,name:构造器!"); Star.name = name; Star.name = "刘亦菲"; } public Star(int age) { System.out.println("age:构造器!"); this.age = age; } public Star() { }}通过上述程序,不难得出,代码块执行的顺序:
- 静态代码块
- 构造代码块
- 构造器
导包
学习目标:
- 会使用import
- 知道什么是全类名
package关键字
package这个关键字我们并不陌生,它写在Java源文件的第一行,用于声明整个Java文件下的所有类的所属包。
语法:
package + 包名当然,一个Java源文件当中,只能有一个public修饰的类。其余非public修饰的类和public类都属于同包的关系。
注意:
package关键字的使用很简单,但是有几个需要注意的地方::
- 包名在书写时,如果存在多级包名,需要使用用.隔开
- package声明必须处在一个Java文件有效代码的第一行,否则会报错
- 注释不算有效代码,将package声明放在注释下面也是可以的
- 建议将package声明永远放在Java源文件真正意义上的第一行
- 多数情况下,我们使用idea新建Java文件是无需关心package声明的,因为idea会自动生成
- 但是当你从网上或者其它途径弄到的一些代码,可能会出现包名错乱的情况
- 这时建议直接删除package声明,然后”Alt + 回车”类名报错的地方即可
全限定类名
什么是全限定类名?
- 可以唯一的、准确的定位到一个类的,由包名加上类名组成的字符串,就是全限定类名。
- 默认情况下,直接输出一个对象的引用,会打印该类的全限定类名
import关键字
引例
请按照以下步骤创建需要的类:
- 在包名为one的包中创建一个public class Student和测试类public class Demo
- 在包名为another的包中创建一个public class Student
- 在同名的Student类中给出同名的方法test,并给出不同的实现
- 创建完毕后,开始进行测试工作
请完成下面测试:
1.在Demo类中直接创建Student对象,然后调用test方法,请问输出的结果是什么? 2.如果想要test方法调用得出anotherpackage包中Student类的结果,需要怎么做?
显然:
在Test类中直接创建Student类对象时,test方法的调用结果是onepackage下Student类的方法执行结果。
而如果想要test方法的调用结果体现为anotherpackage包下的Student类,就需要使用import关键字进行导包操作。
作用
从上述案例中,我们可以总结一下编译器在查找并决定使用某个class时,它的搜索机制:
-
在同包下时,类名是绝对唯一的,有就有,没有就没有,不存在选择的问题。
- 这时,编译器是可以直接通过一个类名去查找到一个类的,不需要额外操作,不需要导包。
- 这可以看成一种”就近原则”,同包已经存在这个类了,自然不需要去外面找。
-
当同包下没有这个类,必须在不同包下寻找时,多个不同包中完全可能存在同名类。
- 这时,编译器肯定是不可能直接通过类名去查找一个类了,需要程序员手动导包。
- 手动导包的目的是明确告诉编译器应该使用哪个包下的类。
使用import关键字手动导包的语法是:
import 全限定类名;注意事项:
-
import导包语句应该放在package声明之后,有效代码之前。
正常情况下,我们使用某个类时,IDEA会自动导包,不用太关心这条语句的位置。
一般只需要注意不要用IDEA导错包就可以了。
-
像String、System、Math等常用类很明显不是同包下的类,但我们并没有进行导包操作。
- 这是因为在Java当中的,“java.lang包”是Java的核心类包,具有隐式的导包。
- 注意”java.lang包”下的所有类,是隐式的导入了每个Java类,而不是没有导入!
-
实际上完全可以不导包去使用不同包下类,这时要明确指出这个类的所属包,也就是要使用全限定类名。
但是一般情况下,全限定类名都很长,导包仍然是更好的手段。
-
但是导包总不是都好用,在极少的情况下:
比如,在Demo类中,想要同时访问两个包onepackage、anotherpackage下的两个Student类。
咋办?
很简单,其中一个Student类使用全限定类名,另一个导包或者就近原则直接使用就可以了。
(当然条件允许的情况下,干脆把其中一个Studnet改名会更好。)
智能导入
import关键字存在智能导包的形式,会智能的**根据需要导入需要的某个类**
它的语法是这样的:
import + 包名.*;其中”*“具有通配的含义,表示该包的类都会根据需要导入。
智能导包是一种十分高效且方便的导包方式,其使用的重点在于理解:何为需要!
所谓根据需求,即是:不导入对应包下的类就会报错,不导入不行。反之如果同包下有同名类则不会导入!
例如:同包中已存在Student类,再想通过智能导包导入别的包下的Student类是无法完成的。
访问权限修饰符
概述
程序的开发,不可能是一个人完成的,而是需要分工协作的。那么怎么保证**“我想要被别人访问的地方,允许访问。不想让别人访问的地方,禁止访问”**这种合情合理的需求呢?
访问权限控制符:
在Java语言中,一切事物(类和类的所有成员)都具有(或显示定义或隐式定义的)访问权限,而这种语言层面的访问权限控制,是由访问权限修饰符实现的。
访问级别
Java的访问权限的级别,是依赖包(package)来实现的。
Java的访问权限级别共分为以下四个级别,访问权限从严格到宽松顺序为:
private: 只能够在同一类中能够访问,私有的,外面谁都不能用。- 缺省(默认): 同一包中的子类或者其它类能够访问,同包中都可以使用。
protected: 不同包的子类能够访问。(这个访问级别继承再学习)public: 不同包的其他类能够访问。
| public | protected | (缺省) | private | |
|---|---|---|---|---|
| 同一类中 | ✔ | ✔ | ✔ | ✔ |
| 同一包其他类 | ✔ | ✔ | ✔ | |
| 不同包子类 | ✔ | ✔ | ||
| 不同包其他类 | ✔ |
分类
类的访问权限修饰符
对于类的访问权限控制,非常简单,只有两种:
public:对其他任意类都可见。- (缺省的)不写任何关键字,表示对同包中的其它类可见。
思考:为什么class没有私有或者protected之类的权限?
这是因为,单独定义的class是一个独立的概念,它没有对谁私有,受谁保护的概念。
所以一般的class的访问权限修饰符只有两种,但是类是可以嵌套定义的。
内部类就有了私有之类的概念,就有了更多的访问权限修饰符。(这个后面会详细学习,先了解一下)
类中结构的访问权限修饰符
对于类中成员和构造器的访问权限,访问权限修饰符总位于它们定义的开头位置,可以使用的修饰符有4种:
public:任意类均能访问,实际就是没有限制访问权限。protected:同包中的其他类都可以访问,不同包下必须是子类才能够访问。- (缺省的)什么关键字都不写,表示同包中的其他类都可以访问。
private:仅对自身类中的其他成员可见。
注意事项(小细节):
-
protected权限,涉及继承的概念,我们留到继承的章节中学习,这里直接跳过不学习!
-
按照以往的经验,有些同学对私有private的权限有疑问,这里还是要强调一下:
私有意味着在类的外部是无法直接访问的,但是在类的内部,只要还是这个类,都是可以随意访问的。
比如参考下列代码:
private小细节
// 类Student的类体中private int age;public void test(Student s){System.out.println(s.age);}// 类Student的类体中方法传入了一个Student对象,虽然这里是外部调用方法传入的对象,但仍然是Student类的对象。
处在Student类中,访问Student类的私有成员,不管这个Student类对象哪里来的,都是完全没问题的!
-
局部变量,已经被作用域限制死了作用范围,访问权限对它而言毫无意义。
局部变量不能使用访问权限修饰符修饰!
作用
告诉代码的使用者,哪些地方不要触碰,哪些地方应该使用。起到警告、约束和指导代码使用者的功能。
举例来说:
-
专门提供给外界使用的,推荐使用的,用public。告诉别人:这里是你需要关注、了解和使用的地方。
所有明确提供给外界使用,实现功能的方法,需要用public修饰。
-
不应该触碰的地方,用私有private修饰,告诉别人:这里你不需要你看,也不需要你管。
具体的案例是:
-
工具类,既然所有的方法,都可以直接用”类名.”去调用,不需要创建对象!
既然不需要对象,那就干脆不要创建对象,直接把这个功能去掉!
所有工具类,都应该私有化无参构造方法!!
-
类中需要提供给外界使用的方法的实现过程中,经常需要一些”中间的过程”方法。
这些方法不需要提供给外界使用,仅作为内部实现功能使用。
所有类中的”中间方法”都应该私有化它们!
-
好处是显而易见的:把代码的开发者和代码的使用者给区分开来了:
所以,我们就可以总结出,我们在实际开发中,使用类中结构的访问权限修饰符的原则:
- 尽量私有化,方便自己修改代码,隐藏实现细节。
- 如果不能私有化,那也应该尽量少的给予访问权限,先从默认权限开始考虑。
- 只有确定这个结构,一定需要在外部(尤其是不同包下)被访问时,才考虑给public,尽量少给public。
权限这个东西,要吝啬,不要”大方”!
单例设计模式
什么是设计模式?
前人总结的经验,套路,模板
好处:
- 代码能够复用
- 提高代码健壮性
- 方便扩展
- 为了解耦
什么是单例设计模式?
- 维护实例(对象)的一种方式
- 保证实例对象是唯一的,全局独一份的
比如: 垃圾回收站
加载模式:
- 懒加载
- 立即加载
实现单例模式的步骤:
- 把构造器私有化(不能在外部new对象)
- 提供一个静态的方法, 给外界作为访问的入口, 获取这个唯一的对象
- 提供一个自身类型的全局的成员变量
懒加载单例(线程不安全的)
package com.cskaoyan._05oop1._14singleton;
/** * @description: * @author: 景天 * @date: 2024/2/19 15:59 **//*1. 把构造器私有化(不能在外部new对象)2. 提供一个静态的方法, 给外界作为访问的入口, 获取这个唯一的对象3. 提供一个自身类型的全局的静态成员变量
实现懒加载单例(线程不安全的) */public class Singleton { // 3. 提供一个自身类型的全局的成员变量 private static Singleton instance; //1. 把构造器私有化(不能在外部new对象) private Singleton() { }
// 2. 提供一个静态的方法, 给外界作为访问的入口, 获取这个唯一的对象 public static Singleton getInstance() { // 做一个判断 if (instance == null) { instance = new Singleton(); } // 最终要返回一个对象 return instance; }
}立即加载单例(线程安全的)
package com.cskaoyan._05oop1._14singleton;
/** * @description: * @author: 景天 * @date: 2024/2/19 16:16 **//*实现安全的立即加载单例 */public class Singleton2 { // 提供一个自身类型的全局的静态的成员变量 private static Singleton2 instance = new Singleton2();
// 私有化构造器 private Singleton2() { }
// 提供一个外界的访问入口 静态的方法 用于获取这个对象 public static Singleton2 getInstance() { // 最终返回同一个对象 return instance; }}文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!