5. 面向对象
Idea查询类的方法快捷键:在类上放置光标,按住Ctrl点击
Idea查询类的继承关系快捷键:Ctrl+H
面向对象的三大特征【封装、继承、多态】
在开发过程中,考虑怎样传参以及怎样设计远比具体的实现操作重要得多。
设计类切忌完整繁杂,而应该尽可能的分化成方法和接口。
5.1 面向对象的简介
面向过程
是一种看待问题,解决问题的思维方式。着眼点在于问题是怎样一步步解决的,然后亲历亲为解决问题。
面向对象
是一种看待问题,解决问题的思维方式。着眼点在于找到一个可以解决问题的实体,委托其帮助解决问题。
**NullPointerException 产生原因:**使用了一个null进行地址访问。这里的null表示该对象中没有存储任何的地址。
5.2 类的设计与对象的实例化
对象:可以帮助解决问题的实体。
类:由若干具有相同特征、行为的对象组成的集合。
类是一种自定义的引用数据类型
类和对象的关系:类是对象的集合,对象是类的个体。
备注:在程序设计中一定是先有类后有对象
5.2.1类的设计
语法
[访问权限修饰符] class 类名{
// 类体
// 使⽤属性,来描述所有的对象共有的特征
// 属性,其实就是⼀个变量
// 使⽤⽅法,来描述所有的对象共有的⾏为
}
语法说明:
1.类名:是一个标识符,遵循大驼峰命名法。
2.特征:又叫属性,在类中体现为全局变量(决定该类在建立对象时在堆上开辟的内存空间大小)。
3.行为:在类中的体现是方法(不会在堆上开辟内存空间,存在于方法区)。
实例化一个Dog类:
public class Dog{
//类中可以写属性和行为,统称为这个类的成员
String name; //狗对象的名字
String color; //狗对象的颜色
int age; //狗对象的年龄
static int count;
//以下三个方法都是描述一个狗对象的功能
void eat(){
System.out.println("狗会吃");
}
void sleep(){
System.out.println("狗会睡觉");
}
void beat(){
System.out.println("狗会打豆豆");
}
static void show(){
System.out.println("狗会秀");
}
}
特殊说明:
1、关于属性:属性,其实就是⼀个变量,最好不要使⽤⼀个类型,多个标识符的⽅式定义。
2、关于属性:类中的属性,不是局部变量,是有默认值的。
整型:0
浮点型:0.0
布尔型:false
字符型:'\0' -> 空格
引⽤数据类型:null
3、关于⽅法:类中,绝⼤部分的⽅法,都不⽤static修饰。
5.2.2对象的实例化
本质:在堆上开辟内存空间并分配给各属性,并将地址赋给对象。
用到关键字new,也就是说类是一种自定义的引用数据类型。
实例化一个Dog对象:
Dog xiaoming = new Dog();
注:在使用类成员的时候,要确保该类以及该类的内部类都已经实例化。
5.3 构造方法
快捷键:Alt+Insert
作为生命周期中的第一个方法,一般会在构造方法中,对对象的某些属性进行初始化赋值操作。
5.3.1 与普通方法的区别:
- 构造方法除了访问权限修饰符无其他修饰符。
- 构造方法无返回值,不要写返回值 void 这部分。
- 构造方法名必须与类名相同。
- 构造方法不能被显式调用,在实例化一个对象时被自动调用。
例:
//构造方法的声明
public class Person {
/**
* ⽆参构造
*/
Person() {
System.out.println("⽆参的构造⽅法执⾏了");
}
/**
* 有参构造
*/
Person(String name) {
System.out.println("有参的构造⽅法执⾏了");
}
}
//构造方法的调用
// 通过⽆参的构造⽅法,实例化了⼀个Person对象
Person xiaoming = new Person();
// 通过有参的构造⽅法,实例化了⼀个Person对象
Person xiaobai = new Person("xiaobai");
5.3.2 构造方法默认的提供援助
- 若一个类中未写任何构造方法,此时该类中会包含一个系统提供的public权限的无参构造方法。
- 若一个类中写了构造方法,此时系统不再提供任何构造方法。
构造方法可以重载,通过参数区分。
5.3.3 构造方法中调用其他的构造方法:
在构造⽅法中,是希望给某些属性进⾏初始化的赋值操作。⼀个类中可能写了多个构造⽅法,每⼀个
构造⽅法,都为指定的属性进⾏了初始化的赋值。但是在不同的构造⽅法中,可能会存在重复的赋值部分。因此构造方法间可以相互调用以减少冗余,此时使用this()可以调用其他构造方法以减少冗余(通过参数区分调用不同构造方法)。
// 调⽤当前类中其他的构造⽅法:
// 使⽤关键字 this() 来调⽤当前类中的构造⽅法。
Person(String name, int age, char gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
Person(String name, int age, char gender, int height, int weight) {
// 调⽤当前类的构造⽅法
this(name, age, gender);
this.height = height;
this.weight = weight;
}
注意事项:
1、在调⽤构造⽅法的时候,this()语句必须在第⼀⾏,前⾯不能加任何的语句。
2、在进⾏构造⽅法的调⽤的时候,不要出现循环调⽤。
5.3.4【拓展】析构方法 finalize
java中存在GC:garbage Collector(垃圾收集器),可以定时检测是否有废弃的空间并清理掉。
析构⽅法,是对象的⽣命周期中最后的⼀个⽅法。执⾏时机是当这个对象被销毁之前。执行了这个⽅法
之后,空间就会被销毁,这个对象也就不存在了。
@Override
protected void finalize() throws Throwable {
super.finalize();
}
每⼀个类的析构⽅法,都是finalize。在析构方法中,常常做的是对这个类所占⽤的资源进⾏释放。例
如:IO流的关闭、数据库连接的断开。。。
⾯试题:简述 final、finally、finalize 的区别
final: 修饰变量,表示常量;修饰类,表示最终类,⽆法被继承。修饰⽅法,表示最终⽅法,⽆法
被重写。
finally: 是异常捕获中的最后⼀个环节,⽆论try中的代码是否有异常,finally中的代码始终会执
⾏。⼀般在finally中也是进⾏的资源的释放操作。
finalize: 是⼀个类中的析构⽅法,表示对象即将被销毁。
5.3.5【拓展】构造代码段
直接写在⼀个类中的代码段,并且,这个代码段没有使⽤static修饰,就是⼀个构造代码段。构造代码
段,其实才是⼀个对象⽣命周期的开始,执⾏是在构造⽅法之前的。
public class Dog {
String name;
int age;
{
// 构造代码段,可以写多个
// 多个代码段的执⾏时机,跟书写的先后顺序是有关的
System.out.println("构造代码段2执⾏了");
}
{
// 这⾥就是⼀个代码段,因为这个代码段直接写在了类中,⽽且没有加static修饰
// 这样的代码段就是⼀个构造代码段
// 每次实例化对象的时候执⾏,且早于构造⽅法
System.out.println("构造代码段1执⾏了");
}
public Dog() {
System.out.println("Dog类的⽆参构造⽅法执⾏了");
}
public Dog(String name, int age) {
System.out.println("Dog类的有参构造⽅法执⾏了");
this.name = name;
this.age = age;
}
}
5.3.6 属性的赋值规范
在类的属性赋初始值的时候,如果不需要在构造⽅法中赋值,⾮静态的属性,应该在⽆参构造⽅法中赋
初始值;静态的属性应该在静态代码段中赋初始值。
// 规范
class Person {
String name;
int age;
static int count;
Person() {
this.name = "xiaoming";
this.age = 10;
}
static {
count = 10;
}
}
5.4 成员访问
访问类中的成员(属性、方法),使用点语法来访问。
类名.属性;
类名.方法;
5.4.1成员分类
类中的成员大致可以分两种:静态成员、非静态成员
5.4.2非静态成员
没有使用关键字 static 的成员均为非静态成员,也叫做实例成员。
非静态的成员是属于对象的,在访问时需用对象来使用,由此非静态成员也叫做实例成员。
5.4.3静态成员
使用关键字 static 修饰的成员,属于类,在访问时需用类来使用,也称 类的成员。
其实静态成员也可以用对象访问,但不推荐
5.4.4静态成员与非静态成员的内存分析
1.非静态属性:空间的开辟发生在实例对象时。
非静态属性的空间,在堆空间中,随着对象的实例而开辟,随着对象的销毁而销毁。
2.静态属性:空间的开辟,是发生在第一次被加载到Java内存(当程序第一次使用到该类时)。
静态属性的空间,是在静态空间中,随类的加载被开辟,程序执行过程中,不会销毁,常驻内存。
注意:在非静态方法中,可以调用非静态和静态成员(规则上不允许对象调用静态成员)。而在静态方法中只能直接访问静态成员。
在绝大多数情况下,属性、方法、类需要被定义为非静态类型,除非某属性需要被所有对象共享,可以设置静态属性。
5.4.5【拓展】静态代码段
其实也是⼀个代码段,只不过这个代码段⽤static修饰了。重要程度⾼于构造代码段。
1、执⾏时机:
静态代码段,属于类的。当类第⼀次被加载到内存中的时候执⾏且仅执行一次,执行顺序在构造代码段之前。即第⼀次在程序中⽤到这个类的时
候。例如:实例化对象、调⽤静态的⽅法、访问静态的属性、Class.forName(反射)
静态代码段加载到内存的时机:
- 实例化一个对象。
- 使用类访问静态成员(包括静态属性、静态方法)。
- Class.forname。
2、访问
因为代码段是静态的,只能直接访问当前类中的静态成员,不能直接访问⾮静态成员。参考静态⽅法。
public class Dog {
String name;
int age;
static int count;
static {
// 静态代码段
System.out.println("静态代码段执⾏了");
}
{
// 构造代码段,可以写多个
// 多个代码段的执⾏时机,跟书写的先后顺序是有关的
System.out.println("构造代码段2执⾏了");
}
{
// 这⾥就是⼀个代码段,因为这个代码段直接写在了类中,⽽且没有加static修饰
// 这样的代码段就是⼀个构造代码段
// 每次实例化对象的时候执⾏,且早于构造⽅法
System.out.println("构造代码段1执⾏了");
}
public Dog() {
System.out.println("Dog类的⽆参构造⽅法执⾏了");
}
}
5.5 包 Package
起到组织代码、组织文件的作用,类似于文件夹。
Package:声明当前文件属于哪一个包,为了给编译后的.class文件找路径。
5.5.1 包的基本结构
包与包之间,以点进⾏分隔。包名在命名的时候,是⼀个标识符,遵循⼩驼峰命名的规范。
5.5.2 不同包中的类如何使⽤
注:在Java中存在许多同名的类,一定要注意所使用的类来自于哪个包!
一个类中若需使用其他包的类,有两种方式:
-
使用类的全限定名:从这个包的最外层开始,例:
com.epackage.Person xiaoming = new com.epackage.Person();
-
导包(主要方式):使用关键字 import添加指定的包(类)。
例1:
import com.epackage.Person; //导入指定类
例2(尽量不要用.*):
import com.epackage.*; //导入指定包下的所有类,注意不能导入其子包的类
注:如果导⼊的多个包中有同名的类,或者导⼊的包和当前包中有同名的类,此时如果想区分这些类,
只能通过全限定名区分。
-
如果导入的保重含有存在歧义的类,必须使用全限定名。
5.6 this关键字
this可以用在非静态方法、非静态代码段和构造方法中。
在一个类的方法中,允许参数名字与属性的名字重复。因为属性的空间开辟在堆上,而参数作为局部变量,空间开辟在栈中。该情况下在方法中直接写变量的名字,使用的是 参数而非属性。
this表示对当前对象的引用。
1.若用在一个非静态方法中,代表调用该方法的实例对象。
2.在构造方法中,this表示刚刚被实例化的对象。
3.不产生歧义的情况下可以适当省略this。
在一个类中,在访问当前属性和方法中,在某些情况下(省略this不会产生歧义) this 可省略。
例:
public class Person {
String name;
int age;
char gender;
int height;
int weight;
void setInfo(String name, int age, char gender, int height, int weight) {
this.name = name;
this.age = age;
this.gender = gender;
this.height = height;
this.weight = weight;
}
}
this()调用当前类中的其他构造方法
在构造方法中,可以使用this()调用当前类中的其他的构造方法,具体调用哪一个构造方法,由小括号中的实参来区分。
以此法调用其他构造方法时注意事项:
1.这一句话必须写在构造方法中的第一行。
2.不要循环调用。