文章目录
类和对象
之前我们学习过Java的基本知识、方法和数组。但是有不少遗留问题,包括调用数组类的方法对数组进行方便的操作。
面向对象
Java是一门面向对象的语言(OOP),在Java中,一切皆对象。面向对象是解决问题的一种思想,注重对象之间的交互。
以洗衣服为例,面向过程(如C语言)会关注洗衣服的过程:
拿盆子——>放水——>放衣服——>放洗衣粉——> … ——>晾衣服
而面向过程,关注的就是不同的对象:
人、衣服、洗衣粉、洗衣机
整个过程是由这4个对象(可以更多)之间的交互完成的,我们不需要关注洗衣机是如何洗衣服的,是如何甩干的
类和对象的初步认识
什么是类?什么是对象?它们的关系?
类是用来对一个实体(对象)来进行描述的。
面向对象的程序设计关注的是对象,对象就是生活中的实体,如某某同学就是一个具体的对象,要描述某某同学,就要知道他的"属性",包括:名字、学号、年龄等,我们将这些属性归纳起来放在一个类中。
类是抽象的一种自定义类型,对象则是一个真实存在的实体。
比如,某某同学就是一个真实存在的对象,我们可以定义一个学生类来描述这一对象。
类的定义
类是来描述一个对象的,对象是一个真正存在的实体。类又可以叫类型,利用这一类型可以创建一个变量。
类是一个自定义的类型,可以用来定义变量
Java的类与C语言的结构体有相似之处,理解了结构体的定义,对理解类的定义有帮助,我们在用C语言做通讯录项目时,需要描述一个联系人的信息,将能描述一个联系人的信息存储在一个结构体中。而Java中的类不仅可以有成员变量,还可以有成员方法。
【创建类】
class 类名 {
字段/属性/成员变量
方法/成员方法/行为
}
- 类名 采用 大驼峰命名方式
- 字段/属性/成员变量 在 类内,成员方法外 定义
- 初期所有的成员变量都要使用
public
修饰,public
是访问修饰限定符的一种,后面会讲到 - 一般一个Java文件中只定义一个类
main
方法所在的类一般要使用public
修饰(注意:Eclipse默认会在public
修饰的类中寻找main
方法)public
修饰的类必须要和文件名相同
我们先尝试定义一个狗类:
public class Dog {
public String name;//狗的名字
public int age;//狗的年龄
public String color;//狗的颜色
public String kind;//狗的品种
//狗的吃行为
public void eat(){
System.out.println(name + "吃骨头");
}
//狗的叫行为
public void bark(){
System.out.println("汪汪汪~~~");
}
}
对于成员变量和成员方法,我们先将前面都加上public
修饰符,public
修饰的成员变量和方法可以随意访问,后面会讲到其他的访问修饰限定符:private
、protected
类的实例化
前面提到,类只是一种类型,有了这个类,我们就能使用这个类定义对象。
用类类型创建对象的过程,称为类的实例化。需要用到new
关键字。
public class Dog {
public String name;
public int age;
public String color;
public String kind;
public void eat(){
System.out.println(name + "吃骨头");
}
public void bark(){
System.out.println("汪汪汪~~~");
}
}
class Main {
public static void main(String[] args) {
Dog dog1 = new Dog();//实例化了一个狗对象
}
}
当我们实例化了一个对象后,这个对象就实际存在了,将会在堆区申请一块空间,存放这个对象的信息,而上代码中的dog1
实际上是引用变量,这一引用指向了实例化的狗对象。
我们可以打印一下,验证dog1
是一个引用变量:
-
Demo1
是自定义的包名,这个Dog
类在此包中,什么是包,后面讲 -
Dog
是类名 -
@
是分隔符 -
1b6d3586
就是引用指向的对象的地址
我们可以实例化多个对象,对象之间的成员变量并不矛盾,因为实例化了多个对象,相当于每个对象在堆区都有一块自己的空间,我们通过下面的一段代码和内存图来理解:
public static void main(String[] args) {
Dog dog1 = new Dog();
Dog dog2 = new Dog();
}
访问对象
有了一个对象后,我们想要对它的成员变量进行赋值和访问,就要用到:
对象的引用
.
成员方法/成员变量
例如:
public class Dog {
public String name;
public int age;
public String color;
public String kind;
public void eat(){
System.out.println(name + "吃骨头");
}
public void bark(){
System.out.println("汪汪汪~~~");
}
}
class Main {
public static void main(String[] args) {
Dog dog1 = new Dog();
dog1.name = "dahuang";
dog1.age = 12;
dog1.color = "黄";
dog1.kind = "田园犬";
dog1.eat();
}
}
补充一个知识点:我们知道,Java中局部变量未初始化就使用,编译器会报错,而成员变量未初始化使用不会报错。
局部变量都是在方法内定义的,而成员变量是在类内、方法外定义的。
【局部变量未初始化】
public static void main(String[] args) {
int a;
System.out.println(a);
}
【成员变量未初始化】
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog.name);
System.out.println(dog.age);
}
我们发现,成员变量没有初始化就使用并没有报错,且打印的值是一个默认值,对于基本类型,默认值是其对应的 0 值(boolean
类型初始为false
);引用变量初始默认值为null
this关键字
this
代表当前对象的引用,谁调用就是谁的引用。
this
引用的是调用成员方法的对象
我们通过一个例子引出this
关键字:我们现在想对成员变量赋值太麻烦了,我们可以定义一个成员方法,每次调用这个成员方法即可完成赋值,同时实现一个打印成员变量的成员方法:
public class Dog {
public String name;
public int age;
public String color;
public String kind;
public void eat(){
System.out.println(name + "吃骨头");
}
public void bark(){
System.out.println("汪汪汪~~~");
}
public void setDog(String name, int age, String color, String kind) {
name = name;
age = age;
color = color;
kind = kind;
}
public void printDog() {
System.out.println("名字:" + name);
System.out.println("年龄:" + age);
System.out.println("颜色:" + color);
System.out.println("品种:" + kind);
}
}
我们看到,上面的成员方法的形参名与当前类的成员变量名重复了,这种情况下,能设置成功吗,我们验证一下:
public static void main(String[] args) {
Dog dog1 = new Dog();
dog1.setDog("dahuang", 12, "huang", "tianyuanquan");
dog1.printDog();
}
为什么会出现这样的情况呢?
//同名
public void setDog(String name, int age, String color, String kind){
name = name;
age = age;
color = color;
kind = kind;
}
在方法内,name
、age
、color
、kind
都是局部变量,此时局部变量优先使用。
按照上面的解释,我们方法体的语句逻辑就是:形参给形参赋值。当然不会改变成员变量的值。
【解决方案一】
改变成员方法形参名(不推荐)
【解决方案二】
使用this
关键字
public void setDog(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
this
关键字就是当前对象的引用,当前对象的引用.
成员变量 就能访问到该对象的成员变量,再进行赋值,就可以。
那么问题来了,我们在调用成员方法时,Java是如何确定当前的对象的呢?
我们看一段代码:
public void setDog(Dog this, String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
对于成员方法,我们在第一个形参位置加上Dog this
,并没有报错,这是因为Java的成员方法默认有隐藏的第一个参数,通过这个参数,this
就能确定当前对象的引用。
我们总结一下有关this
的一部分知识:
- 习惯使用
this
,可以避免很多不必要的错误。基于此,我们上面实现的打印方法体内也要加上this
this
的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型this
只能在成员方法中使用(可以在构造代码块使用,本文后面会讲),不能在静态方法中使用。this
可以在非静态代码块中使用,其实,非静态方法的花括号内部也就是一块非静态代码块。(代码块的知识后面会讲)- 在成员方法中,
this
只能引用当前对象,不能再引用其他对象 this
是成员方法第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法的对象的引用传递给改成员方法,this
负责接收this
的用法:this.成员变量
:访问当前对象的成员变量this.成员方法
:访问当前对象的非静态成员方法this()
:调用当前类的其他构造方法
关于this
访问当前对象的成员变量,我们在引出this
的例子中已经演示了,接下来我们先演示this
访问当前对象的非静态成员方法。
将前面实现的赋值方法与打印方法合并,达到赋值后接着打印的效果:
public class Dog {
public String name;
public int age;
public String color;
public String kind;
public void eat(){
System.out.println(name + "吃骨头");
}
public void bark(){
System.out.println("汪汪汪~~~");
}
public void setDog(Dog this, String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
this.printDog();//this访问成员方法
}
public void printDog() {
System.out.println("名字:" + this.name);
System.out.println("年龄:" + this.age);
System.out.println("颜色:" + this.color);
System.out.println("品种:" + this.kind);
}
}
class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.setDog("dahuang", 8, "huang", "labuladuo");
}
}
用法三需要了解构造方法的知识,我们接下来讲。
构造方法
构造方法(也称为构造器)是一个特殊的成员方法,名字必须与类相同,没有返回值(这里指不需要写返回值,而不是将返回值写成void
),在创建对象时,由编译器自动调节,并且在整个对象的生命周期内只调用一次。
构造方法的作用是给对象的成员变量初始化。
【语法格式】
修饰符 方法名(参数列表) {
方法体
}
- 绝大多数情况下被
public
修饰,极少特殊情况下被private
修饰 - 方法名与类名相同
- 支持重载
其实,我们在实例化对象的时候,就调用了构造方法:
一个对象的生成至少需要两步:
- 为对象分配内存空间
- 调用合适的构造方法
构造方法可以自定义且支持重载,如果没有显性定义构造方法,Java会自动提供一个不带参数的空构造方法,反过来,如果显性定义了构造方法,Java就不自动提供构造方法了。
上面的理论解释了为什么我们没有定义构造方法还能创建对象使用空构造方法。
我们实现两个构造方法:一个不带参数,一个带参数。
public class Dog {
public String name;
public int age;
public String color;
public String kind;
public void eat(){
System.out.println(name + "吃骨头");
}
public void bark(){
System.out.println("汪汪汪~~~");
}
public void setDog(Dog this, String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
this.printDog();
}
public void printDog() {
System.out.println("名字:" + this.name);
System.out.println("年龄:" + this.age);
System.out.println("颜色:" + this.color);
System.out.println("品种:" + this.kind);
}
//带4个参数的构造方法
public Dog(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
//不带参数的构造方法
public Dog() {
}
}
我们可以调用合适的构造方法:
public static void main(String[] args) {
Dog dog1 = new Dog();
Dog dog2 = new Dog("泥鳅", 3, "黑黄相间", "中华田园犬");
dog1.printDog();
System.out.println("==========");
dog2.printDog();
}
了解完构造方法后,我们补充this
的第三种用法:调用当前类的其他构造方法
public class Dog {
public String name;
public int age;
public String color;
public String kind;
public void eat(){
System.out.println(name + "吃骨头");
}
public void bark(){
System.out.println("汪汪汪~~~");
}
public void setDog(Dog this, String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
this.printDog();
}
public void printDog() {
System.out.println("名字:" + this.name);
System.out.println("年龄:" + this.age);
System.out.println("颜色:" + this.color);
System.out.println("品种:" + this.kind);
}
public Dog(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
//this()调用当前类的其他构造方法
public Dog() {
this("皮皮", 3, "黑白相间", "中华田园犬");
}
}
class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.printDog();
}
}
打印结果如下:
打印结果显示,this()
调用了当前类的带4个参数的构造方法。
注意:
- 如果要使用
this()
调用当前类的其他构造方法,这条语句必须是方法体的第一句 - 不能形成环(不允许两个构造方法互相调用)
IDEA快速生成构造方法:
-
在类内部区域,右击鼠标,出现以下选项卡,点击红框选项:
-
继续点击红框区域:
-
选择构造方法的参数列表:(如下例,如果想生成4个参数的构造方法,全选)
-
点击OK,生成成功!
对象的初始化
- 构造方法初始化
- 默认初始化
- 就地初始化
构造方法初始化上面已经介绍了。
【默认初始化】
这就要再提一下前面补充的一个知识点:局部变量在使用时必须要初始化,成员变量不用
那么具体是为什么呢?
我们想搞清楚为什么,就需要知道new
关键字背后发生的事情:
Dog dog = new Dog("泥鳅", 4, "黄黑", "中华田园犬");
执行这条语句,JVM层面做了很多的事情:
-
- 检测对应的类是否被加载,如果没有加载则加载
Java源代码经过Javac编译后生成字节码文件,而字节码文件会被加载到JVM中运行,字节码文件加载到Java虚拟机的过程就叫做加载。
这里涉及到Java的一个优点:被加载过的类不会再被加载
联想到C语言中的预处理过程,会将包含的头文件中的所有数据直接拷贝到源代码中(包括用不到的),并且如果同一个头文件重复出现,也会拷贝两份,从中能够看出Java的一些优势
-
- 为对象分配内存空间
-
- 处理并发问题
-
- 初始化所分配的空间
对象空间被申请好了之后,对象中包含的成员已经设置好了初始值:
-
- 设置对象头信息
-
- 调用构造方法初始化
【就地初始化】
就地初始化即 在声明成员变量时,就直接给出初始值。
public class Dog {
public String name = "大黄";
public int age = "2";
public String color = "黄色";
public String kind = "中华田园犬";
public void eat(){
System.out.println(name + "吃骨头");
}
public void bark(){
System.out.println("汪汪汪~~~");
}
}
注意:代码编译完成后,编译器会将所有给成员初始化的这些语句添加到各个构造方法中
封装
对于 面向对象的语言 来说,有几个重要的特性:封装
、继承
、 多态
而在类和对象阶段,我们主要研究封装性。
我们主要讨论:
什么是封装?(封装的概念)
为什么要封装?(封装的意义)
代码层面实现封装(封装的实现)
【封装的概念】
封装:将数据和操作数据的方法有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互
关于封装,就类似于我们平时使用的手机,手机的内部电路构造被手机壳屏蔽了,我们看不到其中的细节,不过也不是完全封装起来,手机商给用户留了很多接口,如充电接口、耳机接口,还有屏幕,能让用户和手机完成交互即可。
【封装的意义】
-
提高代码安全性、私密性
把对象的私有数据和公共数据分离开,保护了私有数据,减少了模块间的干扰
-
“高内聚”
封装细节,便于修改内部代码,提高可维护性
-
“低耦合”
简化外部调用,便于调用者使用,便于扩展和协作
【封装的实现】
Java中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,而访问权限用来控制方法或者字段能否直接在类内外使用。
Java提供4种访问修饰限定符实现封装,分别是:private
、default
、protected
、public
- 其中
default
就是 没有访问控制修饰符 的情况,表示默认权限 protected
涉及到子类,子类在Java的继承部分,这里不介绍public
即公开权限,没有限制private
是私有权限,被private
修饰的成员变量或方法只能在当前类使用- 注意一点:
private
、protected
不能修饰类,这是语法不允许的
public
不做过多介绍,protected
在笔者之后的有关继承的博客会介绍。我们主要介绍private
以及default(默认权限)
。
private
我们将我们之前实现的Dog
类的所有成员变量和部分成员方法改成由private
修饰,然后测试private
影响的访问权限:
public class Dog {
private String name;
private int age;
private String color;
private String kind;
public void eat(){
System.out.println(name + "吃骨头");
}
public void bark(){
System.out.println("汪汪汪~~~");
}
private void printDog() {
System.out.println("名字:" + this.name);
System.out.println("年龄:" + this.age);
System.out.println("颜色:" + this.color);
System.out.println("品种:" + this.kind);
}
public Dog(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
public Dog() {
}
}
我们测试的方式是:在Dog(当前类)
和Main(其他新建的类)
中分别定义一个main
方法,分别测试,看能否访问成功:
public class Dog {
private String name;
private int age;
private String color;
private String kind;
public void eat(){
System.out.println(name + "吃骨头");
}
public void bark(){
System.out.println("汪汪汪~~~");
}
private void printDog() {
System.out.println("名字:" + this.name);
System.out.println("年龄:" + this.age);
System.out.println("颜色:" + this.color);
System.out.println("品种:" + this.kind);
}
public Dog(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
public Dog() {
}
public static void main(String[] args) {
Dog dog = new Dog("泥鳅", 1, "黑黄", "松狮");
dog.age = 2;//直接方法访问private修饰的成员变量
dog.printDog();//直接访问private修饰的成员方法
}
}
上例证明被private
修饰的成员变量和成员方法可以在当前类访问
class Main {
public static void main(String[] args) {
Dog dog = new Dog("小黄", 3, "黄", "田园犬");
dog.name = 4;
dog.printDog();
}
}
上例证明被private
修饰的成员变量和成员方法不可以在其他类访问
那么,如何在其他类中访问private
修饰的成员变量和成员方法呢?
与手机的处理方式一样,我们给用户提供一个接口,让用户间接地访问。
这里就涉及到get
、set
方法(针对成员变量),而private
修饰的成员方法,我们一般在当前类中新建一个由合适的访问限定修饰符修饰的方法,调用private
修饰的方法,这样就既隐藏了代码,又不妨碍用户使用,我们直接看代码:
public class Dog {
private String name;
private int age;
private String color;
private String kind;
public void eat(){
System.out.println(name + "吃骨头");
}
public void bark(){
System.out.println("汪汪汪~~~");
}
private void printDog() {
System.out.println("名字:" + this.name);
System.out.println("年龄:" + this.age);
System.out.println("颜色:" + this.color);
System.out.println("品种:" + this.kind);
}
//调用private修饰的成员方法
public void printInfo() {
this.printDog();
}
public Dog(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
public Dog() {
}
//get方法
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
//set方法
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public static void main(String[] args) {
Dog dog = new Dog("泥鳅", 1, "黑黄", "松狮");
dog.age = 2;
dog.printDog();
}
}
实现了上述接口,我们就可以在其他类中使用成员变量和成员方法了:
class Main {
public static void main(String[] args) {
Dog dog = new Dog();
//给对象赋值
dog.setName("大哈");
dog.setAge(4);
//打印信息
System.out.println(dog.getName());
System.out.println(dog.getAge());
dog.printInfo();
}
}
IDEA快速生成get
、set
:
-
在当前类的区域右击鼠标,点击
Generate...
-
选择生成
get
、set
或get
和set
方法 -
选择要生成
get
和set
方法的成员变量,IDEA会自动检测没有生成的,且生成的不予显示:
default(默认权限)
默认权限就是包访问权限,只能在当前类所在的包内访问,我们简单介绍一下,之后我们就介绍包。
先将Demo1
包中的Dog
类的成员变量和部分成员方法改成默认权限:
public class Dog {
//修改为默认权限
String name;
int age;
String color;
String kind;
public void eat(){
System.out.println(name + "吃骨头");
}
public void bark(){
System.out.println("汪汪汪~~~");
}
//修改为默认权限
void printDog() {
System.out.println("名字:" + this.name);
System.out.println("年龄:" + this.age);
System.out.println("颜色:" + this.color);
System.out.println("品种:" + this.kind);
}
public void printInfo() {
this.printDog();
}
public Dog(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
public Dog() {
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
我们先验证同包的可访问性:(在Dog
包中新建其他类访问)
class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.name = "大哈";
dog.age = 4;
dog.printDog();
}
}
能够直接访问,证明了默认权限同包的可访问性。
接下来,我们验证一下不同包的不可访问性:
下图显示我们的Dog
类在Demo1
包中:
我们新建一个包:
在新建包Demo2
中新建一个Test
类:
在Test
类中实例化一个对象并尝试直接访问:
package Demo2;//声明该类所在包,后面由类的部分专门讲
import Demo1.Dog;//导入Dog类,后面有类的部分专门讲
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.name = "叶子";
dog.age = 5;
dog.printDog();
}
}
我们只能依靠get
、set
和提供的其他接口访问:
package Demo2;
import Demo1.Dog;
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.setName("叶子");
dog.setAge(5);
dog.printInfo();
}
}
到这里,我们应该体会到了面向对象语言的封装特性。
包
包的概念
在面向对象的体系中,提出了软件包的概念。为了更好地管理类,把多个类收集在一起成为一组,称为软件包。
类比文件夹,为了更好地管理不同的文件,我们选择将具有相同属性的文件放到相同文件下,并可以不断分类。
在Java中也引入了包,包是对类、接口等的封装机制的体现,是一种对类或者接口等很好的组织方式, 比如,我们可以使用包和合适的限定符,达到一个包中的类不能在其他包中的类使用的效果;另外,包还解决了类名命名冲突的问题,在同一个工程中允许存在相同名称的类,只要处在不同的包中即可。
导入包中的类
Java中已经提供了很多现成的类供我们使用,例如我们在数组部分经常使用的Arrays
类,导入这些类需要关键字import
我们介绍三种导入包中类的方式:
- 不使用
import
- 使用
import
导入具体的类 - 使用
import
和通配符*
导入某个包中的所有类
我们想要创建一个Date
类(Java现成类)的对象:
【不使用import
】
public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
System.out.println(date.getTime());
}
}
不导入包,就要在使用Date
类时加上其所在路径(或者理解成包名称、子包名称),不过这太麻烦了。
【使用import
导入具体的类】
import java.util.Date;//导入Date类
public class Test {
public static void main(String[] args) {
Date date = new Date();
System.out.println(date.getTime());
}
}
【使用import
和通配符*
导入包中所有类】
util
包中不止有Date
类,我们常用的数组Arrays
类也在里面,我们可以使用通配符*
导入util
包中的所有类,具体用法如下:
import java.util.*;//使用通配符
public class Test {
public static void main(String[] args) {
Date date = new Date();
System.out.println(date.getTime());
int[] array = new int[]{1, 2, 3};
System.out.println(Arrays.toString(array));
}
}
不过,我们还是建议显式地指定要导入的类名,否则还是容易出现冲突的情况。
说到这,我们举几个例子体会一下导入包时的注意事项:
如我们要使用Date
这个类,而util
和sql
两个包中都有Date
类(重名)
【情况一】
import java.util.Date;
import java.sql.Date;
//报错!!!
【情况二】
import java.util.*;
import java.sql.*;
public class Package {
public static void main(String[] args) {
Date d = new Date();//报错!!!
java.util.Date d2 = new Date();
}
}
【情况三】
import java.util.Date;
import java.sql.*;
public class Package {
public static void main(String[] args) {
Date d = new Date();
java.util.Date d2 = new Date();
}
}
//不报错
不报错,不过此时的Date
是哪个包中的?util
还是sql
?
其实是util
包里的,util
的Date
类实例化时为无参构造方法,而sql
的Date
类实例化时没有无参构造方法。所以,在上面的import
情况下,我们可以直接使用Date
实例化一个对象,使用无参构造方法,如果报错,证明:不指明路径使用Date
类,默认是sql
包中的,如果不报错,证明默认是util
包中的。
以下证明:
import java.util.Date;
import java.sql.*;
public class Package {
public static void main(String[] args) {
Date d = new Date();
}
}
并没有报错,证明:
如果存在重名类,优先使用的是没有使用通配符的包里的类,而要使用另一个重名的类,则需要使用第一种导入包的方式,在使用时加上路径,以区分重名类
【静态导入】
还有一个知识点:静态导入。
如果一个类中的方法全部是使用 static 声明的静态方法,则在导入时就可以直接使用 import static
的方式导入:
结果将会导入包中的静态方法和字段,静态导入的意义在于,它能够简化代码,使得我们更加方便。
静态导入的格式:
import static 包.类.*;
例如:
import static java.lang.Math.*;//静态导入
public class Test {
public static void main(String[] args) {
double x = 30;
double y = 40;
//不静态导入,常规写法
double ret1 = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
//静态导入,可简写
double ret2 = sqrt(pow(x, 2) + pow(y, 2));
System.out.println(ret1);
System.out.println(ret2);
}
}
【注意】
Java的import
与 C++的#include
差别很大。C++必须用#include
来引入其他文件内容,而Java的import
只是为了写代码的方便。
自定义包
自定义包要用到package
关键字,指定代码所在包。
注意:
package
语句声明了当前类所在的包- 包名尽量指定成唯一的名字,通常会用公司域名的颠倒形式(如:
com.baidu.www
) - 包名要和代码路径相匹配,例如创建
com.xiaokuer.www
的包,那么会存在一个对应的路径com/xiaokuer/www
来存储代码 - 如果一个类没有使用
package
语句,呢么该类会被放到一个默认包中
我们接下来在IDEA中演示一下:
-
先按照下图打开包的命名界面
-
按照上面建议的命名方式命名,例如
com.xiaokuer.www
-
起好名字后按下回车
-
上面提到了,创建例如
com.xiaokuer.www
为包名的包,会生成com/xiaokuer/www
的路径,我们可以查看一下:
-
我们可以调整一下IDEA对于自定义包的显示:(将对号取消)
调整之后,就更加直观了。
-
我们在
www
路径下创建一个类: -
命名好后,我们发现,在新建的
.java
文件的上方自动生成了package
语句,声明了其所在包:
包的访问权限控制
即通过访问限定符限定成员变量或成员方法,这部分内容在本文 封装 部分已经介绍了,这里不再赘述。
常用的包
java.lang
:系统常用基础类(String、Object),此包从JDK1.1后自动导入java.lang.reflect
:java反射编程包java.net
:进行网络编程开发包java.sql
:进行数据库开发的支持包java.util
:是Java提供的工具程序包。java.io
:I/O编程开发包
static成员
static修饰成员变量
static
修饰的成员变量,称为静态成员变量,也可以称为类成员。静态成员变量:不属于某个具体的对象,是所有对象所共享的。(这一点相当重要)
【静态成员变量的特性】
-
不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中
-
访问方法:1. 通过类名访问 2. 通过对象访问
我们推荐通过类名访问
-
类成员存储在方法区中
-
类变量的生命周期伴随类的一生,随着类的加载而创建,随类的卸载而销毁
为了介绍上面的特性,我们仍然使用类Dog
,添加一个成员变量owner
,代表狗狗的铲屎官。
我们假设此类描述的都是同一个人家的狗狗,那么,我们实例化的每个对象的owner
成员变量一致,都是同一个人。
此时,如果每个实例化的狗狗都要带上这个属性且要初始化太麻烦了,我们就可以采用static
修饰成员变量的方式解决这个问题:
package com.xiaokuer.www;
public class Dog {
public String name;
public int age;
public String color;
public String kind;
public static String owner;
public void eat() {
System.out.println(this.name + " 吃饭");
}
public Dog(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
public Dog() {
}
public void setInfo(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
public void printDog() {
System.out.println("名字:" + this.name);
System.out.println("年龄:" + this.age);
System.out.println("颜色:" + this.color);
System.out.println("品种:" + this.kind);
System.out.println("铲屎官:" + this.owner);
}
}
我们将owner
成员变量用static
修饰,此时owner
依赖于对象,也不在某个对象的空间内,而是在在方法区。
【验证】静态成员变量不在某个对象的空间,而是在方法区:
package com.xiaokuer.www;
public class DemoOne {
public static void main(String[] args) {
Dog dog1 = new Dog("大哈", 3, "黄白", "哈士奇");
Dog dog2 = new Dog("叶子", 4, "黑白", "边牧");
dog1.owner = "猪爸";
dog1.printDog();
System.out.println("========");
dog2.printDog();
}
}
如代码显示,我们通过dog1
对象访问并设置静态成员变量的值,然后打印两个对象的信息:
发现:两个对象打印出来的owner
信息都是通过dog1
对象访问设置好的信息。
【验证】静态成员变量不依赖于对象
public static void main(String[] args) {
Dog.owner = "猪爸";
}
我们直接设置静态成员变量owner
的值即可,不需要实例化对象。
【静态成员访问的方法】
- 通过类名访问
- 通过对象的引用访问
通过类名访问不再赘述,其反映了静态成员变量的不依赖对象性。
关于通过对象的引用访问静态成员变量,实际上对象的引用为null
也不影响:
public static void main(String[] args) {
Dog dog = null;
dog.owner = "猪爸";
System.out.println(dog.owner);
}
静态成员变量不依赖于对象,所以没有抛出"空指针异常"。
static修饰成员方法
static
修饰的成员方法,称为静态成员方法,也叫类方法,是类的方法,不是某个对象所特有
【静态成员方法的特性】
- 静态成员方法不依赖于对象,不属于某个具体的对象
- 访问方法:1. 类名访问 2. 对象调用
- 不能在静态方法中直接访问任何非静态成员变量
- 不能在静态方法中直接调用任何非静态方法。前面提到静态方法中有隐藏的
this
参数,this
与对象强相关,在静态方法中调用时无法传递this
引用 - 静态方法无法重写,不能用来实现多态
我们看一段代码:
public class Dog {
public String name;
public int age;
public String color;
public String kind;
public static String owner;
public void eat() {
System.out.println(this.name + " 吃饭");
}
public Dog(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
public Dog() {
}
public void setInfo(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
public void printDog() {
System.out.println("名字:" + this.name);
System.out.println("年龄:" + this.age);
System.out.println("颜色:" + this.color);
System.out.println("品种:" + this.kind);
System.out.println("铲屎官:" + this.owner);
}
//静态方法中直接访问非静态成员变量
public static void test() {
age += 1;
this.name = "大哈";
}
}
要想在静态方法中访问非静态的成员变量,必须实例化对象,通过对象的引用访问非静态成员变量:
public static void test() {
Dog dog = new Dog();
dog.age += 1;
dog.name = "大哈";
dog.printDog();
}
我们验证:静态方法不依赖于对象,可以直接通过类名调用
public class DemoOne {
public static void main(String[] args) {
Dog.test();
}
}
在非静态方法中可以调用静态方法,反之不可:
public class Dog {
public String name;
public int age;
public String color;
public String kind;
public static String owner;
public void eat() {
System.out.println(this.name + " 吃饭");
}
public Dog(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
public Dog() {
}
public void setInfo(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
public void printDog() {
System.out.println("名字:" + this.name);
System.out.println("年龄:" + this.age);
System.out.println("颜色:" + this.color);
System.out.println("品种:" + this.kind);
System.out.println("铲屎官:" + this.owner);
}
public static void test() {
Dog dog = new Dog();
dog.age += 1;
dog.name = "大哈";
dog.printDog();
}
//非静态方法调用静态方法,不报错!
public void useStatic() {
//写法一
test();
//写法二
Dog.test();
}
//静态方法直接调用非静态方法,报错!
public static void useNonStatic() {
printDog();
}
}
要想在静态方法调用非静态方法,必须实例化对象,用对象的引用调用非静态方法。
static成员变量初始化
三种方法:
- 就地初始化
- 静态代码块初始化
- 构造方法初始化
【就地初始化】
即,定义时直接初始化:
public class Dog {
public String name;
public int age;
public String color;
public String kind;
//就地初始化
public static String owner = "猪爸";
public void eat() {
System.out.println(this.name + " 吃饭");
}
public Dog(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
public Dog() {
}
public void setInfo(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
public void printDog() {
System.out.println("名字:" + this.name);
System.out.println("年龄:" + this.age);
System.out.println("颜色:" + this.color);
System.out.println("品种:" + this.kind);
System.out.println("铲屎官:" + this.owner);
}
}
【静态代码块初始化】
代码块是一个新知识点,我们在这里给出代码,具体知识点在本文的后面介绍:
public class Dog {
public String name;
public int age;
public String color;
public String kind;
public static String owner;
//静态代码块初始化
static {
//写法一
owner = "猪爸";
//写法二
Dog.owner = "猪爸";
}
public void eat() {
System.out.println(this.name + " 吃饭");
}
public Dog(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
public Dog() {
}
public void setInfo(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
public void printDog() {
System.out.println("名字:" + this.name);
System.out.println("年龄:" + this.age);
System.out.println("颜色:" + this.color);
System.out.println("品种:" + this.kind);
System.out.println("铲屎官:" + this.owner);
}
}
【构造方法初始化】
这一方法不常用,给出代码:
//构造方法
public Dog(String owner) {
Dog.owner = owner;
}
public class DemoOne {
public static void main(String[] args) {
//构造方法初始化
Dog dog = new Dog("猪爸");
System.out.println(Dog.owner);
}
}
代码块
代码块的概念
使用{}
定义的一段代码称为代码块。
根据代码块的定义位置以及关键字,可分为以下四种:
- 普通代码块
- 构造/实例代码块
- 静态代码块
- 同步代码块(多线程部分内容,暂时不讨论)
普通代码块
普通代码块,即定义在方法中的代码块。
是方法定义的一部分。
public class DemoOne {
public static void main(String[] args) {
//普通代码块
int a = 10;
int b = 20;
System.out.println(a + b);
}
}
构造代码块
构造代码块,又称实例代码块,即定义在类中的不加修饰符的代码块。
一般用来初始化实例成员变量。
格式:
{
//语句
}
关于构造代码块,我们要讨论的事情较多:
- 构造代码块的执行时机
- 构造代码块的执行次数
- 不同构造代码块初始化的结果(多个构造代码块的执行顺序)
我们先写一段代码:
public class Dog {
private String name;
private int age;
private String color;
private String kind;
private static String owner;
//构造代码块
{
this.name = "大哈";
this.age = 3;
//用于检验构造代码块的执行时机和次数
System.out.println("构造代码块执行了");
}
public void eat() {
System.out.println(this.name + " 吃饭");
}
public void setInfo(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
public void printDog() {
System.out.println("名字:" + this.name);
System.out.println("年龄:" + this.age);
System.out.println("颜色:" + this.color);
System.out.println("品种:" + this.kind);
System.out.println("铲屎官:" + this.owner);
}
}
了解了构造代码块的书写后,请问:构造代码块何时执行?执行几次?
构造代码块在实例化对象时执行。每次实例化对象时都会执行一次。
验证:
public static void main(String[] args) {
System.out.println("不实例化对象,观察是否打印构造代码块中的语句:");
}
结果是:不执行。
public static void main(String[] args) {
System.out.println("实例化对象,观察是否打印构造代码块中的语句:");
Dog dog = new Dog();
}
执行了!
public static void main(String[] args) {
System.out.println("实例化多个对象,观察是否多次打印构造代码块中的语句:");
Dog dog1 = new Dog();
Dog dog2 = new Dog();
}
验证构造代码块的初始化效果:
public static void main(String[] args) {
Dog dog = new Dog();
dog.printDog();
}
到此,我们验证了构造代码块的执行时机和执行次数,接下来我们看一段代码,说出main
方法执行后打印结果是什么:
public class Dog {
private String name;
//就地初始化age为2
private int age = 2;
private String color;
private String kind;
private static String owner;
//构造代码块一,初始化age为3
{
this.age = 3;
}
//构造代码块二,初始化age为4
{
this.age = 4;
}
public void eat() {
System.out.println(this.name + " 吃饭");
}
public void setInfo(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
public void printDog() {
System.out.println("名字:" + this.name);
System.out.println("年龄:" + this.age);
System.out.println("颜色:" + this.color);
System.out.println("品种:" + this.kind);
System.out.println("铲屎官:" + this.owner);
}
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog.age);
}
}
结果是 4。给出结论:当代码块的属性一样(都是构造代码块)时,初始化的最后结果与定义顺序有关,最终值是最后初始化的值(如果就地初始化在所有构造代码块后面,则最终的值为就地初始化的值)
我们调整顺序验证:
public class Dog {
private String name;
{
this.age = 3;
}
{
this.age = 4;
}
private int age = 2;
private String color;
private String kind;
private static String owner;
public void eat() {
System.out.println(this.name + " 吃饭");
}
public void setInfo(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
public void printDog() {
System.out.println("名字:" + this.name);
System.out.println("年龄:" + this.age);
System.out.println("颜色:" + this.color);
System.out.println("品种:" + this.kind);
System.out.println("铲屎官:" + this.owner);
}
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog.age);
}
}
验证成功!
【总结】
-
构造代码块在实例化对象(创建对象时)执行,执行次数与实例化的对象的个数保持一致
-
当定义的多个代码块的属性一样(都是构造代码块)时,初始化的最后结果与定义顺序有关,最终值是最后初始化的值
或者说:多块构造代码块的执行顺序与定义顺序有关
静态代码块
静态代码块,即使用static
定义的代码块。
一般用于初始化静态成员变量
格式:
static{
//语句
}
我们依旧讨论:
- 静态代码块的执行时机
- 静态代码块的执行次数
- 多个静态代码块初始化的结果(多个静态代码块的执行顺序)
我们给出结论:静态代码块在类被加载时执行,且只会执行一次
验证:
public class Dog {
private String name;
private int age;
private String color;
private String kind;
private static String owner;
//静态代码块
static {
owner = "小裤儿";
//检验静态代码块的执行时机和执行次数
System.out.println("静态代码块被执行了");
}
public void eat() {
System.out.println(this.name + " 吃饭");
}
public void setInfo(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
public void printDog() {
System.out.println("名字:" + this.name);
System.out.println("年龄:" + this.age);
System.out.println("颜色:" + this.color);
System.out.println("品种:" + this.kind);
System.out.println("铲屎官:" + this.owner);
}
public static void main(String[] args) {
System.out.println("不实例化对象,观察是否打印静态代码块中的语句:");
}
}
执行了!且执行时机很早(加载类时)。
我们尝试实例化多个对象,观察:
public static void main(String[] args) {
System.out.println("实例化多个对象,观察:");
Dog dog1 = new Dog();
Dog dog2 = new Dog();
}
静态代码块只会在加载类时执行一次!
多个静态代码块的执行顺序,说出main
方法执行的打印结果:
public class Dog {
private String name;
private int age;
private String color;
private String kind;
private static String owner = "猪爸";
static {
owner = "小裤儿";
}
static {
owner = "四角儿";
}
public void eat() {
System.out.println(this.name + " 吃饭");
}
public void setInfo(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
public void printDog() {
System.out.println("名字:" + this.name);
System.out.println("年龄:" + this.age);
System.out.println("颜色:" + this.color);
System.out.println("品种:" + this.kind);
System.out.println("铲屎官:" + this.owner);
}
public static void main(String[] args) {
System.out.println(Dog.owner);
}
}
与多个构造代码块一样,与执行顺序有关。
当代码块的属性一样(都是静态代码块)时,初始化的最后结果与定义顺序有关,最终值是最后初始化的值(如果就地初始化在所有静态代码块后面,则最终的值为就地初始化的值)
交换顺序验证:
public class Dog {
private String name;
private int age;
private String color;
private String kind;
static {
owner = "小裤儿";
}
static {
owner = "四角儿";
}
private static String owner = "猪爸";
public void eat() {
System.out.println(this.name + " 吃饭");
}
public void setInfo(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
public void printDog() {
System.out.println("名字:" + this.name);
System.out.println("年龄:" + this.age);
System.out.println("颜色:" + this.color);
System.out.println("品种:" + this.kind);
System.out.println("铲屎官:" + this.owner);
}
public static void main(String[] args) {
System.out.println(Dog.owner);
}
}
验证成功!
【总结】
-
静态代码块在类加载时执行,同一个类只会加载一次,所以静态代码块只会执行一次
-
当代码块的属性一样(都是静态代码块)时,初始化的最后结果与定义顺序有关,最终值是最后初始化的值(如果就地初始化在所有静态代码块后面,则最终的值为就地初始化的值)
多块静态代码块的执行顺序与定义顺序有关
不同代码块、构造方法的执行顺序
类中总会出现构造代码块、静态代码块都存在的情况,这种情况下,执行顺序是什么?加上构造方法呢?
我们在代码块中加入打印语句,当打印出对应语句时,证明代码块执行:
public class Dog {
private String name;
private int age;
private String color;
private String kind;
private static String owner;
//构造函数
public Dog(String name, int age, String color, String kind) {
System.out.println("构造方法被执行了");
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
//构造代码块
{
System.out.println("构造代码块被执行了");
}
//静态代码块
static {
System.out.println("静态代码块被执行了");
}
public void eat() {
System.out.println(this.name + " 吃饭");
}
public void setInfo(String name, int age, String color, String kind) {
this.name = name;
this.age = age;
this.color = color;
this.kind = kind;
}
public void printDog() {
System.out.println("名字:" + this.name);
System.out.println("年龄:" + this.age);
System.out.println("颜色:" + this.color);
System.out.println("品种:" + this.kind);
System.out.println("铲屎官:" + this.owner);
}
public static void main(String[] args) {
Dog dog = new Dog("大哈", 3, "黄白", "哈士奇");
}
}
由打印结果可知:执行顺序:静态代码块 > 构造代码块 > 构造方法
它们的执行顺序与定义顺序无关,是固定的。
对象的打印
对象的打印,我们可以通过自定义一个成员方法即可打印,不过,如果类的成员变量过多,我们实现打印函数就会十分麻烦。
这里介绍一个简单的方法:IDEA生成
-
在类的区域右击,出现以下选项卡,红框区域
-
选择下图红框的
toString
-
选择想打印的成员方法
-
生成
-
调用
public static void main(String[] args) { Dog dog = new Dog(); //传入对象的引用 System.out.println(dog); }
为什么?按照惯例,我们打印的应该是一个"地址"。
其实这里是:toString
的重写,需要继承和多态的知识,我们先看现象即可。
上面就是本文的全部内容了,希望对大家有所帮助!
预告:继承和多态