目录
8.4静态代码块 5
一、面向对象的初步认识
1.1什么是面向对象
Java是一门纯面向对象的语言(Object Oriented Program,简称OOP),在面向对象的世界里,一切皆为对象。面向对象是解决问题的一种思想,主要依靠对象之间的交互完成一件事情。用面向对象的思想来涉及程序,更符合人们对事物的认知,对于大型程序的设计、扩展以及维护都非常友好
!一切皆为对象!
1.2面向对象与面向过程
1.传统洗衣服过程
编辑
传统的方式:注重的是洗衣服的过程,少了一个环节可能都不行。而且不同衣服洗的方式,时间长度,拧干方式都不同,处理起来就比较麻烦。如果将来要洗鞋子,那就是另一种方式。按照该种方式来写代码,将来扩展或者维护起来会比较麻烦。
2.现代洗衣服过程
编辑
以面向对象方式来进行处理,就不关注洗衣服的过程,具体洗衣机是怎么来洗衣服,如何来甩干的,用户不用去关心,只需要将衣服放进洗衣机,倒入洗衣粉,启动开关即可,通过对象之间的交互来完成的。
二、类的定义和使用
面向对象程序设计关注的是对象,而对象是现实生活中的实体,比如:洗衣机。但是洗衣机计算机并不认识,需要开发人员告诉计算机什么是洗衣机。
编辑
上图左侧就是对洗衣机简单的描述,该过程称为对洗衣机对象(实体)进行抽象(对一个复杂事物的重新认知),但是这些简化的抽象结果计算机也不能识别,开发人员可以采用某种面向对象的编程语言来进行描述,比如:Java语言
public class Machine { //对象的属性 public String brand="樱花"; public String name="XPB"; //对象的行为 public void washClothes(){ System.out.println("洗衣服"); } public void washshoes(){ System.out.println("洗鞋子"); } }
2.1简单认识类
类是用来对一个实体(对象)来进行描述的。主要描述该实体(对象)具有哪些属性(外观尺寸等),哪些功能(用来干啥),描述完成后计算机就可以识别了。
编辑
2.2类的定义格式
在java中定义类时需要用到class关键字,具体语法如下:
// 创建类 class ClassName{ field; // 字段(属性) 或者 成员变量 method; // 行为 或者 成员方法 }
类 一般包括两部分:
1.成员变量或者字段(属性)
2.成员方法或者行为
其中,class为定义类的关键字,ClassName为类的名字,{ }中为类的主体。
class WashMachine{ public String brand; // 品牌 public String type; // 型号 public double weight; // 重量 public double length; // 长 public double width; // 宽 public double height; // 高 public String color; // 颜色 public void washClothes(){ // 洗衣服 System.out.println("洗衣功能"); } public void dryClothes(){ // 脱水 System.out.println("脱水功能"); } public void setTime(){ // 定时 System.out.println("定时功能"); } }
采用Java语言将洗衣机类在计算机中定义完成,在内存的堆区开辟空间,经过javac编译之后形成.class文件读取到方法区,在JVM的基础上计算机转换为汇编语言就可以识别了。
注意事项:
- 类名注意采用大驼峰定义:首字母大写
- 成员前写法统一为public,后面会详细解释
- 此处写的方法不带 static 关键字. 后面会详细解释
- 一般情况下一个Java文件创建一个类
当我们创建一个类后,可以发现在对应的文件夹中存在一个.class字节码文件。此时可以通过IDEA的Open in Exployer,如下图所示:(每一个类对应一个字节码文件)
编辑
注意事项:
- main方法所在的类一般要使用public修饰
- public修饰的类必须要和文件名相同
- 不要轻易去修改public修饰的类的名称,如果要修改,通过开发工具修改
三、类的实例化
3.1什么是实例化
定义了一个类,就相当于在计算机中定义了一种新的类型。,与int,double类似,只不过int和double是java语言自带的内置类型,而类是用户自定义了一个新的类型。用类类型创建对象的过程,称为类的实例化,在java中采用new关键字,配合类名来实例化对象。
public static void main(String[] args) { //通过类类型创建对象 PetDog dog1=new PetDog(); dog1.name="小黑"; dog1.color="red"; dog1.bark(); PetDog dog2=new PetDog(); dog2.name="小黄"; dog2.color="yellow"; dog2.bark(); }
编辑
- PetDog dog1中的dog1和dog2表示引用,也就是说我们此时创建了两个引用。一旦创建引用,就希望它能与一个对象相关联。new关键字的意思是:给我一个新对象。所以我们可以表示为PetDog dog1=new PetDog(),即类型定义了一个变量赋了一个值
- 一个类可以实例化多个对象(如图的dog1和dog2)
- new创建了一个类型在堆区,堆区存放的是该对象的地址,将地址赋值给了一个变量,这个变量存放该对象的引用--->引用指向对象
3.2类和对象的说明
1.类只是一个模型一样的东西,用来对一个实体进行描述,限定了类有哪些成员和行为
2.类是一种自定义的类型,可以用来定义变量
3.一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量
4.类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间。
对象在内存中的存储位置和存储方式:
(1.)寄存器:这是最快的存储区,因为它位于不同于其他存储区的地方一一处理器内部。但是寄存器的数量极其有限,所以寄存器根据需求进行分配。你不能直接控制,也不能在程序中察觉到寄存器存在的任何迹象(另一方面,C和C++允许您向编译器建议寄存器的分配方式,详看函数栈帧篇)。
(2.)堆栈:位于通用RAM(随机访问存储器)中,通过堆栈指针可以从处理器直接获得支持。堆栈指针向下移动,分配新的内存。堆栈指针向上移动,则释放那些内存。以虽然某些Java数据存堆栈中一特别是对象引用,但是Java对象并不存储于其中。
(3.)堆:一种通用的内存池 (也位于RAM区),用于存放所有的Java对象。堆不同于堆栈的好处是:编译器不需要知道存储的数据在堆里存活多长时间。因此,在堆里分配存储有很大的灵活性。当需要一个对象时,只需用new写一行简单的代码,当执行这行代码时,会自动在堆里进行存储分配。当然,为这种灵活性必须要付出相应的代价:用堆进行存储分配和清理可能比用堆栈进行存储分配需要更多的时间(如果确实可以在Java中像在C++中一样在栈中创建对象)
(4.)常量存储池:常量值通常直接存放在程序代码内部,这样做是安全的,因为它们永远不会被改变。一般存储在ROM中。
(5.)非RAM存储:如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。其中两个基本的例子是流对象和特久化对象。在流对象中,对象转化成字节流,通常被发送给另一台机器。在“持久化对象” 中,对象被存放于磁盘上,因此,即使程序终止,它们仍可以保持自己的状态。这种存储方式的技巧在于:把对象转化成可以存放在其他事物的连接上,在需要的时候,可恢复成常规的,基于RAM的对象。
3.2.1类的注意事项
1.引用可以指向引用吗?---------->不可以
public class Student{ //私有化成员变量 private String name; private String gender; private int age; public static void main(String[] args) { Student stu=new Student(); stu.name="张三"; stu.age=21; stu.gender="男"; Student stu2=new Student(); stu2=stu; } }
编辑
引用只能指向对象,上述代码的意思是:stu2这个引用指向了stu这个引用所指向的对象
2.一个引用能不能指向多个对象? --------->不可以
编辑
3.引用赋值为null
public class Student{ //私有化成员变量 private String name; private String gender; private int age; public static String teacher="王老师"; public static void main(String[] args) { Student student=null; System.out.println(student.teacher); } }
编辑
四、this引用
4.1为什么要有this引用
public class Date { public int year; public int month; public int day; public void setDay(int y, int m, int d){ year = y; month = m; day = d; } public void printDate(){ System.out.println(year + "/" + month + "/" + day); } //---------------------------------------------------------------------- public static void main(String[] args) { // 构造三个日期类型的对象 d1 d2 d3 Date d1 = new Date(); Date d2 = new Date(); Date d3 = new Date(); // 对d1,d2,d3的日期设置 d1.setDay(2020,9,15); d2.setDay(2020,9,16); d3.setDay(2020,9,17); // 打印日期中的内容 d1.printDate(); d2.printDate(); d3.printDate(); } }
编辑
以上代码定义了一个日期类,然后main方法中创建了三个对象,并通过Date类中的成员方法对对象进行设置和打印,但是如果出现以下情况我们该怎么办?
1.形参名不小心与成员变量名相同
public void setDay(int year, int month, int day){ year = year; month = month; day = day; }
那函数体中到底是谁给谁赋值?成员变量给成员变量?参数给参数?参数给成员变量?成员变量参数?setDay中的形参给自己赋值,但是不能给成员变量赋值。
2. 三个对象都在调用setDate和printDate函数,但是这两个函数中没有任何有关对象的说明,setDate和printDate函数如何知道打印的是哪个对象的数据呢
编辑
4.2什么是this引用
this引用指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有成员变量的操作,都是通过该引用去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
public class Date { public int year; public int month; public int day; public void setDay(int year, int month, int day){ this.year = year; this.month = month; this.day = day; } public void printDate(){ System.out.println(this.year + "/" + this.month + "/" + this.day); } }
也就是说:this引用的是调用该成员方法的对象的引用.(谁调用该方法this就表示谁的成员变量)
编辑
4.3this引用的特性
- this的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型
- this只能在"成员方法"中使用
- 在"成员方法"中,this只能引用当前对象,不能再引用其他对象
- this是“成员方法”第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法对象的引用传递给该成员方法,this负责来接收
public class Date { public int year; public int month; public int day; //Date this实际上是存在的,但是编译器把它隐藏了。 public void setDay(Date this,int year, int month, int day){ this.year = year; this.month = month; this.day = day; } public void printDate(Date this){ System.out.println(this.year + "/" + this.month + "/" + this.day); } public static void main(String[] args) { Date date=new Date(); } }
编辑
同样,对于我们没写this的情况,编译器其实已经自动生成:
编辑
五、对象的构造及初始化
通过前面知识点的学习知道,在Java方法内部定义一个局部变量时,必须要初始化,否则会编译失败。
public static void main(String[] args) { int a; System.out.println(a); } // Error:(26, 28) java: 可能尚未初始化变量a
要让上述代码通过编译,非常简单,只需在正式使用a之前,给a设置一个初始值即可。如果是对象:
public static void main(String[] args) { Date d = new Date(); d.printDate(); d.setDate(2021,6,9); d.printDate(); } // 代码可以正常通过编译
需要调用之前写的SetDate方法才可以将具体的日期设置到对象中。通过上述例子发现两个问题:
- 每次对象创建好后调用SetDate方法设置具体日期,比较麻烦,那对象该如何初始化?
- 局部变量必须要初始化才能使用,为什么字段声明之后没有给值依然可以使用?
5.2构造方法
5.2.1概念
构造方法(也称为构造器)是一个特殊的成员方法,名字必须与类名相同,在创建对象时,由编译器自动调用,并且在整个对象的生命周期内只调用一次。
public class Date { public int year; public int month; public int day; // 构造方法: // 名字与类名相同,没有返回值类型,设置为void也不行 // 一般情况下使用public修饰 // 在创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次 public Date(int year, int month, int day){ this.year = year; this.month = month; this.day = day; System.out.println("Date(int,int,int)方法被调用了"); } public void printDate(){ System.out.println(year + "-" + month + "-" + day); } public static void main(String[] args) { // 此处创建了一个Date类型的对象,并没有显式调用构造方法 Date d = new Date(2021,6,9); // 输出Date(int,int,int)方法被调用了 d.printDate(); // 2021-6-9 } }
注意:构造方法的作用就是对对象中的成员进行初始化,并不负责给对象开辟空间
5.2.2特性
- 名字必须与类名相同
- 没有返回值类型,设置为void也不行
- 创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次(相当于人的出生,每个人只能出生一次)
- 构造方法可以重载(用户根据自己的需求提供不同参数的构造方法)
public class Date { public int year; public int month; public int day; // 无参构造方法 //构造同时初始化 public Date(){ this.year = 1900; this.month = 1; this.day = 1; } // 带有三个参数的构造方法 public Date(int year, int month, int day) { this.year = year; this.month = month; this.day = day; } public void printDate(){ System.out.println(year + "-" + month + "-" + day); } public static void main(String[] args) { Date d = new Date(); d.printDate(); } }
上述两个构造方法:名字相同,参数列表不同,因此构成了方法重载。
5.注意:如果用户没有显式定义,编译器会生成一份默认的构造方法,生成的默认构造方法一定是无参的。
public class Date { public int year; public int month; public int day; public void printDate(){ System.out.println(year + "-" + month + "-" + day); } public static void main(String[] args) { Date d = new Date(); d.printDate(); } }
上述Date类中,没有定义任何构造方法,编译器会默认生成一个不带参数的构造方法。接下来给出验证:
编辑
6.注意:一旦用户定义,编译器则不再生成
编辑
7.构造方法中,可以通过this调用其他构造方法来简化代码
public class Date { public int year; public int month; public int day; // 无参构造方法--内部给各个成员赋值初始值,该部分功能与三个参数的构造方法重复 // 此处可以在无参构造方法中通过this调用带有三个参数的构造方法 // 但是this(1900,1,1);必须是构造方法中第一条语句 public Date(){ //System.out.println(year); 注释取消掉,编译会失败 this(1900, 1, 1); //this.year = 1900; //this.month = 1; //this.day = 1; } // 带有三个参数的构造方法 public Date(int year, int month, int day) { this.year = year; this.month = month; this.day = day; } }
编辑
注意:
- this(...)必须放到构造方法中第一条语句,可见this的地位很高
- this()这个写法只能在构造方法中使用
- 构造方法中自己不能调用自己
8.调用this构造不能形成环
public Date(){ this(1900,1,1); } public Date(int year, int month, int day) { this(); } / * 无 参构造器调用三个参数的构造器,而三个参数构造器有调用无参的构造器,形成构造器的递归调用 编译报错:Error:(19, 12) java: 递归构造器调用 */
9.构造方法绝大多数情况下使用public来修饰,特殊场景下会被private修饰(后序讲单例模式时会遇到)
总结 this 的三种用法
1. this . 成员属性
2. this( )
3. this . 成员方法
5.3默认初始化
解决:为什么局部变量在使用时必须要初始化,而成员变量可以不用呢?
public class Date { public int year; public int month; public int day; public Date(int year, int month, int day) { // 成员变量在定义时,并没有给初始值, 为什么就可以使用呢? System.out.println(this.year); System.out.println(this.month); System.out.println(this.day); } public static void main(String[] args) { // 此处a没有初始化,编译时报错: // Error:(24, 28) java: 可能尚未初始化变量a // int a; // System.out.println(a); Date d = new Date(2021,6,9); } }
要搞清楚这个过程,就需要知道 new 关键字背后所发生的一些事情:
在程序层面只是简单的一条语句,在JVM层面需要做好多事情,下面简单介绍下:
- 检测对象对应的类是否加载了,如果没有加载则加载
- 为对象分配内存空间
- 处理并发安全问题
比如:多个线程同时申请对象,JVM要保证给对象分配的空间不冲突- 初始化所分配的空间
- 即:对象空间被申请好之后,对象中包含的成员已经设置好了初始值,比如:(引用类型的值都为null)
编辑6.设置对象头信息(关于对象内存模型后面会介绍)
7.调用构造方法,给对象中各个成员赋值
5.4就地初始化
在声明成员变量时,直接给出初始值:
public class Date { public int year = 1900; public int month = 1; public int day = 1; public Date(){ } public Date(int year, int month, int day){ this.year = year; this.month = month; this.day = day; } public static void main(String[] args) { Date d1 = new Date(2021,6,9); Date d2 = new Date(); } }
编辑
六、封装
6.1封装的概念
当我们需要对实现的细节隐藏,不想被类外看到,就需要进行封装,同时用到private权限修饰符。
面向对象程序三大特性:封装、继承、多态。而类和对象阶段,主要研究的就是封装特性。何为封装呢?简单来说就是套壳屏蔽细节。
比如:对于电脑这样一个复杂的设备,提供给用户的就只是:开关机、通过键盘输入,显示器,USB插孔等,让用户来和计算机进行交互,完成日常事务。但实际上:电脑真正工作却是CPU、显卡、内存等一些硬件元件。
6.2访问限定符
Java中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用。Java中提供了四种访问限定符:
编辑
6.2.1private
注意:前提-->跳到6.3认识包的概念
被private修饰的属性,只能在当前类使用。如何区分是不是当前类:通过大括号:
1.在同一个类中访问:
public class Date { private int age; private String name; public static void main(String[] args) { Date date=new Date(); date.age=10; date.name="张三"; System.out.println(date.age); System.out.println(date.name); //10 //张三 } }
编辑
2.在不同类中访问:
public class Date { private int age; private String name; } class Date2{ public static void main(String[] args) { Date date=new Date(); date.age=10; date.name="张三"; System.out.println(date.age); System.out.println(date.name); //10 //张三 } }
编辑
6.2.2default(包访问权限)
我们什么都不写的情况下默认前面有default:
public class Date { int age; String name; } class Date2{ public static void main(String[] args) { Date date=new Date(); date.age=10; date.name="张三"; System.out.println(date.age); System.out.println(date.name); //10 //张三 } }
编辑
不同包的情况下:
编辑
6.2.3proteted
TODO:在学习子类和继承后才能了解,没学先跳过
6.2.4public
public class Computer { //private私有化,不想被类外看到 private String cpu; // cpu private String memory; // 内存 public String screen; // 屏幕 // 品牌---->default属性 String brand; //构造 public Computer(String brand, String cpu, String memory, String screen) { this.brand = brand; this.cpu = cpu; this.memory = memory; this.screen = screen; } public void Boot(){ System.out.println("开机~~~"); } public void PowerOff(){ System.out.println("关机~~~"); } public void SurfInternet(){ System.out.println("上网~~~"); } } class TestComputer { public static void main(String[] args) { Computer p = new Computer("HW", "i7", "8G", "13*14"); System.out.println(p.brand); // default属性:只能被本包中类访问 System.out.println(p.screen); //System.out.println(p.cpu);// private属性:只能在Computer类中访问,不能被其他类访问 //程序会报错 } // public属性: 可以任何其他类访问 }
注意:一般情况下成员变量设置为private,成员方法设置为public
比如:
public:可以理解为一个人的外貌特征,谁都可以看得到。
default: 对于自己家族中(同一个包中)不是什么秘密,对于其他人来说就是隐私了。
private:只有自己知道,其他人都不知道。
访问权限除了可以限定类中成员的可见性,也可以控制类的可见性
6.3封装扩展之包
6.3.1包的概念
在面向对象体系中,提出了一个软件包的概念,即:为了更好的管理类,把多个类收集在一起成为一组,称为软件包。
在Java中也引入了包,包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式,比如:一个包中的类不想被其他包中的类使用。包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在不同的包中即可。
编辑
6.3.2导入包中的类
Java 中已经提供了很多现成的类供我们使用. 例如Date类:可以使用 java.util.Date 导入 java.util 这个包中的 Date类。
第一种:使用使用 import语句 --->导入 java.util 这个包中的 Date类
import java.util.Date; public class Test { public static void main(String[] args) { Date date=new Date(); //获得当前的日期 System.out.println(date.getDate()); //22 } }
编辑
第二种:可以使用 java.util.Date 导入 java.util 这个包中的 Date类
public static void main(String[] args) { java.util.Date date=new java.util.Date(); System.out.println(date.getDate()); //22 }
第三种: 需要使用 java.util 中的其他类, 可以使用 import java.util.*,当你用到util下的某个类时,java会自动帮你导入你需要用的类,而不是导入全部的类。
编辑
import java.util.*; public class Demo1 { public static void main(String[] args) { Date date=new Date(); System.out.println(date.getDate()); //22 } }
建议显式的指定要导入的类名,不建议使用上述方法,否则还是容易出现冲突。
比如在util和sql的包中都存在Date这个类,如下图:两个包中都有Date对象,编译器无法判断你想用哪个方法,导致报错!
import java.util.*; import java.sql.*; public class Test { public static void main(String[] args) { // util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 编译出错 Date date = new Date(); System.out.println(date.getTime()); } } // 编译出错 //Error:(5, 9) java: 对Date的引用不明确 //java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.Date 都匹配
上图解决方案:需要使用完整的类名:
import java.util.*; import java.sql.*; public class Test { public static void main(String[] args) { java.util.Date date = new java.util.Date(); System.out.println(date.getTime()); } }
第四种:使用import static导入包中静态的方法和字段
import static java.lang.Math.*;
import static java.lang.Math.*; public class Demo2 { public static void main(String[] args) { double a=10; //导入包中的静态方法 System.out.println(pow(a,2)); //导入包中静态的字段 System.out.println(PI); //100.0 //3.141592653589793 } }
第五种:静态导入包中静态方法和字段
public class Demo2 { public static void main(String[] args) { double a=10; //导入包中的静态方法 System.out.println(Math.pow(a,2)); //导入包中静态的字段 System.out.println(Math.PI); //100.0 //3.141592653589793 } }
6.3.3自定义包
基本规则
- 在文件的最上方加上一个 package 语句指定该代码在哪个包中
- 包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.IT.demo )
- 包名要和代码路径相匹配. 例如创建 com.bit.demo1 的包, 那么会存在一个对应的路径 com/bit/demo1 来存储代码(包名必须是全小写的)
- 如果一个类没有 package 语句, 则该类被放到一个默认包中
操作步骤:
- 在 IDEA 中先新建一个包: 右键 src -> 新建 -> 包编辑
- 在弹出的对话框中输入包名
编辑
编辑
6.3.4包的访问权限控制
Computer类位于com.bit.demo1包中,TestComputer位置com.bit.demo2包中:
package com.bit.demo1; //在demo1的包下: public class Computer { //私有化 private String cpu; // cpu private String memory; // 内存 //公开 public String screen; // 屏幕 //默认为default String brand; // 品牌 //带参构造方法 public Computer(String brand, String cpu, String memory, String screen) { this.brand = brand; this.cpu = cpu; this.memory = memory; this.screen = screen; } public void Boot(){ System.out.println("开机~~~"); } public void PowerOff(){ System.out.println("关机~~~"); } public void SurfInternet(){ System.out.println("上网~~~"); } //------------------------------------------------------ //package com.bite.demo2; //在demo2包下导入demo1包下的computer类 import com.bite.demo1.Computer; public class TestComputer { public static void main(String[] args) { //创建对象 Computer p = new Computer("HW", "i7", "8G", "13*14"); System.out.println(p.screen); //private访问权限:同一包的同一类;default访问权限:同一包的不同类 // System.out.println(p.cpu); // 报错:cup是私有的,不允许被其他类访问 // System.out.println(p.brand); // 报错:brand是default,不允许被其他包中的类访问 } } // 注意:如果去掉Computer类之前的public修饰符,代码也会编译失败,去掉默认为default
6.3.5常见的包
1. java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
2. java.lang.reflect:java 反射编程包;
3. java.net:进行网络编程开发包。
4. java.sql:进行数据库开发的支持包。
5. java.util:是java提供的工具程序包。(集合类等) 非常重要
6. java.io:I/O编程开发包
七、static成员
7.1static修饰静态成员变量
static修饰的成员变量,称为静态成员变量,静态成员变量最大的特性:不属于某个具体的对象,是所有对象所共享的。(随类的加载而创建,随类的销毁而消失)
【静态成员变量特性】
- 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中。存储在堆区的静态常量池中。
- 既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问。
- 类的成员变量不通过类名访问,也就是说,静态成员变量不属于对象。
- 生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁)
【变量分类】
- 普通成员变量:在类的内部,方法的外部
- 静态成员变量:在类的内部,方法的外部,但是被static修饰
- 局部变量:在方法内部定义的变量
class Student{ //静态成员变量默认初始化 public static int OnlineNumber; private String name; private int age; public Student(String name,int age){ this.name=name; this.age=age; } public void online(){ System.out.println(this.name+"正在上网!"); } } public class Demo2 { public static void main(String[] args) { Student stu=new Student("张三",21); stu.online(); Student.OnlineNumber++; Student stu2=new Student("王五",19); stu2.online(); Student.OnlineNumber++; System.out.println("在线上网人数:"+Student.OnlineNumber); //张三正在上网! //王五正在上网! //在线上网人数:2 } }
如上代码所示:
1.OnlineNumber静态成员变量被所有对象共享。
2.静态成员变量和静态成员方法可以通过 类名. 访问。
7.2static修饰成员方法
一般类中的数据成员都设置为private,而成员方法设置为public。如果上例的静态成员变量被private修饰,该如何进行访问呢?静态成员一般是通过静态方法来访问的
class Student{ //静态成员变量默认初始化 private static int OnlineNumber; private String name; private int age; public static int getOnlineNumber(){ return OnlineNumber; } public static void setOnlineNumber(){ OnlineNumber++; } public Student(String name,int age){ this.name=name; this.age=age; } public void online(){ System.out.println(this.name+"正在上网!"); } } public class Demo2 { public static void main(String[] args) { Student stu=new Student("张三",21); stu.online(); Student.setOnlineNumber(); Student stu2=new Student("王五",19); stu2.online(); Student.setOnlineNumber(); System.out.println("在线上网人数:"+Student.getOnlineNumber()); //张三正在上网! //王五正在上网! //在线上网人数:2 } }
【静态方法特性】
- 不属于某个具体的对象,是类方法
- 可以通过对象调用,也可以通过类名.静态方法名(...)方式调用,更推荐使用后者
- 不能在静态方法中访问任何非静态成员变量(通过静态方法可以访问私有化静态变量)编辑
- 静态方法中不能调用任何非静态方法,因为静态方法不依赖对象,但是非静态方法依赖对象。编辑
- 静态方法无法重写,不能用来实现多态。因为一个静态方法随类的加载而创建一次,多态继承需要重写子类方法,所以会发生冲突。
- 静态方法内部不可以使用this关键字
7.3static成员变量初始化
注意:静态成员变量一般不会放在构造方法中来初始化,构造方法中初始化的是与对象相关的实例属性,对象和静态是没有任何关系的!
静态成员变量的初始化分为两种:就地初始化 和 静态代码块初始化。
7.3.1就地初始化
编辑
7.3.2静态代码块初始化
八、代码块
8.1 代码块概念以及分类
使用 { } 定义的一段代码称为代码块。根据代码块定义的位置以及关键字,又可分为以下四种:
- 普通代码块
- 构造块
- 静态块
- 同步代码块(多线程)
8.2普通代码块
普通代码块:定义在方法中的代码块
public class Demo3 { public static void main(String[] args) { int num1=10; int num2=20; { int num3=30; System.out.println(num1+num2+num3); num3++; } System.out.println(num1+num2); //60 //30 } }
直接使用{}定义普通代码块,注意该代码块的作用域是在{}内的,出{}后变量等不再起作用。
编辑
8.3构造代码块
构造块:定义在类中的代码块(不加修饰符),也叫实例代码块。构造代码块一般用于初始化实例成员变量。
public class Student2 { private String name; private int age; //实例代码块 { name="小红"; age=21; } public Student2() { System.out.println("无参构造"); } public Student2(String name, int age) { System.out.println("带参构造"); this.name = name; this.age = age; } 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 String toString() { return "Student2{姓名 = " + name + ", 年龄 = " + age + "}"; } public static void main(String[] args) { Student2 student1=new Student2("张三",14); System.out.println(student1.toString()); Student2 student2=new Student2("莉莉",15); System.out.println(student2.toString()); Student2 student3=new Student2(); System.out.println(student3.toString()); } }
编辑
- 通过实例代码块,将静态成员变量进行了初始化。
- 调用带参构造方法将对象实例化。
为什么student1和student2的打印结果不是小红,21呢?再看下面的代码交换构造方法和实例代码块的位置:
public Student2(String name, int age) { System.out.println("带参构造"); this.name = name; this.age = age; } //实例代码块 { name="小红"; age=21; }
交换实例代码块和带参构造的位置后发现输出结果没有发生变化,这是为什么呢?
public class Student2 { private String name; private int age; public Student2() { System.out.println("无参构造"); } public Student2(String name, int age) { System.out.println("带参构造"); this.name = name; this.age = age; } //实例代码块 { System.out.println("实例代码块"); name="小红"; age=21; } public String toString() { return "Student2{姓名 = " + name + ", 年龄 = " + age + "}"; } public static void main(String[] args) { Student2 student1=new Student2("张三",14); System.out.println(student1.toString()); Student2 student2=new Student2("莉莉",15); System.out.println(student2.toString()); Student2 student3=new Student2(); System.out.println(student3.toString()); } }
对程序做如下修改,分析:编辑
8.4静态代码块
使用static定义的代码块称为静态代码块,一般用于初始化静态成员变量。
public class Student{ //私有化成员变量 private String name; private String gender; private int age; private static String classRoom; //实例代码块 { this.name = "小王"; this.age = 12; this.gender = "男"; System.out.println("实例代码块"); } // 静态代码块 static { classRoom = "bit306"; System.out.println("静态代码块"); } public Student(){ System.out.println("构造方法"); } public Student(String name, String gender, int age) { this.name = name; this.gender = gender; this.age = age; System.out.println("带参构造"); } @Override public String toString() { return "Student{" + "姓名='" + name + '\'' + ", 性别='" + gender + '\'' + ", 年龄=" + age + '}'; } public static void main(String[] args) { Student s1 = new Student(); System.out.println(s1.toString()); Student s2 = new Student("小明","女",19); System.out.println(s2.toString()); } }
编辑
【 注意事项】
- 静态代码块不管生成多少个对象,其只会执行一次
- 静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的
- 如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行(合并)
//修改部分内容 private String name; private String gender; private int age; private static String classRoom; private static String teacher; static { classRoom = "bit306"; System.out.println("静态代码块1"); } static{ teacher="王老师"; System.out.println("静态代码块2"); }
- 编辑
- 实例代码块只有在创建对象时才会执行编辑
九、内部类
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么这个内部的完整结构最好使用内部类。在 Java 中,可以将一个类定义在另一个类或者一个方法的内部,前者称为内部类,后者称为外部类。内部类也是封装的一种体现。
public class OutClass { class InnerClass{ } } // OutClass是外部类 // InnerClass是内部类
【注意事项】
1. 定义在class 类名{}花括号外部的,即使是在一个文件里,都不能称为内部类
public class A{ } class B{ } // A 和 B是两个独立的类,彼此之前没有关系
2. 内部类和外部类共用同一个java源文件,但是经过编译之后,内部类会形成单独的字节码文件
9.1内部类的分类
成员位置定义:未被static修饰 --->实例内部类
成员位置定义:被static修饰 ---> 静态内部类
方法中也可以定义内部类 ---> 局部内部类
实现某接口重写方法--->匿名内部类
public class OutClass { // 成员位置定义:未被static修饰 --->实例内部类 public class InnerClass1{ } // 成员位置定义:被static修饰 ---> 静态内部类 static class InnerClass2{ } public void method(){ // 方法中也可以定义内部类 ---> 局部内部类:几乎不用 class InnerClass5{ } } }
根据内部类定义的位置不同,一般可以分为以下几种形式:
1. 成员内部类(普通内部类:未被static修饰的成员内部类 和 静态内部类:被static修饰的成员内部类)
2. 局部内部类(不谈修饰符)、匿名内部类
注意:内部类其实日常开发中使用并不是非常多,大家在看一些库中的代码时候可能会遇到的比较多,日常开始中使用最多的是匿名内部类。
9.2内部类介绍
在外部类中,内部类定义位置与外部类成员所处的位置相同,因此称为成员内部类。
9.2.1实例内部类
public class OutClass { private int a; static int b; int c; public void methodA() { a = 10; System.out.println(a); } public static void methodB() { System.out.println(b); } // 实例内部类:未被static修饰 class InnerClass { int c; public void methodInner() { // 在实例内部类中可以直接访问外部类中:任意访问限定符修饰的成员 a = 100; b = 200; methodA(); methodB(); // 如果外部类和实例内部类中具有相同名称成员时,优先访问的是内部类自己的 c = 300; System.out.println(c); // 如果要访问外部类同名成员时候,必须:外部类名称.this.同名成员名字 OutClass.this.c = 400; System.out.println(OutClass.this.c); } } public static void main(String[] args) { // 外部类:对象创建 以及 成员访问 OutClass outClass = new OutClass(); System.out.println(outClass.a); System.out.println(OutClass.b); System.out.println(outClass.c); outClass.methodA(); outClass.methodB(); System.out.println("=============实例内部类的访问============="); // 要访问实例内部类中成员,必须要创建实例内部类的对象 // 而普通内部类定义与外部类成员定义位置相同,因此创建实例内部类对象时必须借助外部类 // 创建实例内部类对象 OutClass.InnerClass innerClass1 = new OutClass().new InnerClass(); // 上述语法比较怪异,也可以先将外部类对象先创建出来,然后再创建实例内部类对象 OutClass.InnerClass innerClass2 = outClass.new InnerClass(); innerClass2.methodInner(); } }
【注意事项】
1. 外部类中的任何成员都可以在实例内部类方法中直接访问
2. 实例内部类所处的位置与外部类成员位置相同,因此也受public、private等访问限定符的约束
3. 在实例内部类方法中访问同名的成员时,优先访问自己的,如果要访问外部类同名的成员,必须:外部类名称.this.同名成员 来访问
4. 实例内部类对象必须在先有外部类对象前提下才能创建
5. 实例内部类的非静态方法中包含了一个指向外部类对象的引用
6. 外部类中,不能直接访问实例内部类中的成员,如果要访问必须先要创建内部类的对象。
9.2.2静态内部类
被static修饰的内部成员类称为静态内部类。(由于实例内部类得外部类创建对象才能访问,所以出现了静态内部类,不需要外部类创建对象就可以使用)
public class OutClass { private int a; static int b; public void methodA(){ a = 10; System.out.println(a); } public static void methodB(){ System.out.println(b); } // 静态内部类:被static修饰的成员内部类 static class InnerClass{ public void methodInner(){ // 在内部类中只能访问外部类的静态成员 // a = 100; // 编译失败,因为a不是类成员变量 b =200; // methodA(); // 编译失败,因为methodB()不是类成员方法 methodB(); } } public static void main(String[] args) { // 静态内部类对象创建 & 成员访问 OutClass.InnerClass innerClass = new OutClass.InnerClass(); innerClass.methodInner(); } }
【注意事项】
1. 在静态内部类中只能访问外部类中的静态成员
如果确实想访问,我们该如何做?
2. 创建静态内部类对象时,不需要先创建外部类对象
9.2.3局部内部类
定义在外部类的方法体或者{}中,该种内部类只能在其定义的位置使用,一般使用的非常少,此处简单了解下语法格式。
public class OutClass { int a = 10; public void method(){ int b = 10; // 局部内部类:定义在方法体内部 // 不能被public、static等访问限定符修饰 class InnerClass{ public void methodInnerClass(){ System.out.println(a); System.out.println(b); } } // 只能在该方法体内部使用,其他位置都不能用 InnerClass innerClass = new InnerClass(); innerClass.methodInnerClass(); } public static void main(String[] args) { // OutClass.InnerClass innerClass = null; 编译失败 } }
【注意事项】
1. 局部内部类只能在所定义的方法体内部使用
2. 不能被public、static等修饰符修饰
3. 编译器也有自己独立的字节码文件,命名格式:外部类名字$数字内部类名字.class
4. 几乎不会使用
9.2.4匿名内部类
class Student implements Comparable<Student>{ @Override public int compareTo(Student o) { return 0; } } interface Shape { void draw(); } public class Test { public static void main(String[] args) { /* Comparable<Student> comparable = new Student(); new Comparable<Student>(){ @Override public int compareTo(Student o) { return 0; } };*/ int a = 10; new Shape() { @Override public void draw() { //a = 99; System.out.println("矩形!" + a); } }.draw(); } }
十、对象的打印
public class Student{ //私有化成员变量 private String name; private String gender; private int age; public Student(String name, String gender, int age) { this.name = name; this.gender = gender; this.age = age; } public static void main(String[] args) { Student stu=new Student("张三","男",19); System.out.println(stu); } }
输出结果:ByteDemo.Student@4554617c
ByteDemo.Student表示该包下的Student类,@表示分隔符,4554617c表示该对象的地址
如果想要打印对象的详细信息如何进行操作呢?
9.1通过源码查看println对对象的处理
编辑
9.2重写底层的toString方法
public class Student{ //私有化成员变量 private String name; private String gender; private int age; public Student(String name, String gender, int age) { this.name = name; this.gender = gender; this.age = age; } @Override public String toString() { return "Student{" + "姓名='" + name + '\'' + ", 性别='" + gender + '\'' + ", 年龄=" + age + '}'; } public static void main(String[] args) { Student student=new Student("张三","男",21); System.out.println(student); //Student{姓名='张三', 性别='男', 年龄=21} } }
编辑