面向对象
类和对象
- 类是模子,确定对象将会拥有的特征(属性)和行为(方法),是对象的类型
- 对象是类的实例表现,是特定类型的数据,声明对象在 栈 中,实例化对象在 堆 中
- 一个 Java 文件中可以存在多个类,多个接口,但是只能存在一个
public
修饰的类或接口,且此时文件名需要与public
修饰的类或者接口同名
包
命名规范
- 由英文小写字母组成
- 创建包:域名倒序 + 模块 + 功能
- 一个包中不能存在同名类
package + 包名
:指明该文件中定义的类所在的包,一个 java 文件中只能有一条package
语句- java 文件中书写顺序:
package
-import
-class
- 导入包:分别以
*
和 指定类名 进行导入,以类名导入优先级最高
// 加载指定包下的所有直接允许访问的类,无法导入其子包中的类
import com.imooc.animal.*
// 加载指定包下的指定类(优先级最高)
import com.imooc.animal.Cat
// 在程序中直接加载类
com.imooc.animal.CatTest test = new com.imooc.animal.CatTest();
常用系统包
包名 | 描述 |
---|---|
java.lang | 包含 java 语言基础的类,该包在系统加载时默认导入 |
java.util | 包含 java 语言种常用工具,如:Scanner、Random |
java.io | 包含输入输出相关功能的类,如:File、InputStream |
构造方法
用于生成并初始化对象,针对不同的初始化状态,设置不同参数的构造方法
格式:访问修饰符 类名(参数列表){ *** }
- 方法名与类名相同,包括大小写
- 没有返回值,也不能用
void
修饰 - 只能在对象实例化(
new
)时调用,不能通过对象名、类名等其它形式调用 - 在非构造方法中,智能结合对象实例化操作被调用,不能通过方法名被访问
- 当没有指定构造方法时,编译系统会自动添加无参无反的构造方法
- 当有指定构造方法,无论是有参、无参的构造方法,编译器都不会自动添加无参的构造方法
- 一个类中可以包含多个构造方法,只要满足参数类型、顺序、个数不同即可,也称为 构造方法重载
- 多个构造方法之间可以使用
this()
进行调用- 带参时需按顺序传入设定的参数
- 调用动作必须在构造函数的第一行
- 一个构造方法中只能调用一个构造方法
- 不能在类中普通成员方法内通过
this()
调用构造方法 - 不能与
super()
共存
public class Monkey{
// 构造方法
public Monkey(){}
}
成员属性默认值
数据类型 | 说明 | 默认值 |
---|---|---|
byte | 字节型 | 0 |
short | 短整型 | 0 |
int | 整型 | 0 |
long | 长整型 | 0 |
float | 单精度浮点型 | 0.0 |
double | 双精度浮点型 | 0.0 |
char | 字符型 | 空字符 |
String | 字符串型 | null |
boolean | 布尔型 | false |
成员方法
根据类型所需任意功能,进行方法设置
格式:访问修饰符 返回值类型 方法名(参数列表){ *** }
- 方法内定义的局部变量只能在方法里使用
- 方法内不能定义静态成员
- 不能使用
public
、protected
、private
修饰
this 关键字
- 代表当前对象本身,可以理解为:指向当前对象的一个引用
- 可用于调用成员属性、成员方法、构造方法,也可以当做参数进行方法传参以及方法传值
- 大部分时候,类的方法中访问成员属性时无需使用
this
,但如果方法里存在局部变量和成员属性同名,但程序又需要在该方法里访问成员属性时,则必须使用this
进行调用 - 类的成员方法访问同类中其他成员方法时,
this
关键字可加可不加,效果相同 - 静态方法中不可使用
this
static 关键字
静态成员
被
static
修饰的成员变量,通常被称为静态成员、静态属性、类成员、全局属性等,方便在没有创建对象的情况下进行某些操作
属于整个类,由类进行维护,仅在类初次加载时会被初始化,在类销毁时回收
通过该类实例化的所有对象都共享类中的静态资源,任一对象中静态资源的修改都将影响所有对象
由于静态成员在类加载期间就已经完成初始化,存储在 Java Heap(JDK7.0 之前存储在方法区)中静态存储区,因此优先于对象而存在,可以通过类名和对象名两种方式访问
类.成员
(推荐)对象.成员
可以将频繁调用的公共信息、期望加快运行效率的成员设置为静态。但需注意,由于其生命周期长,即资源占用周期长,要慎用。
静态方法
被
static
修饰的成员方法,通常被称为静态方法、类方法、全局方法等
- 与静态成员类似,静态方法属于整个类,由类进行维护,优先对象而存在,因此可以通过类名和对象名两种方式访问
- 静态方法中不能直接访问同一个类中的非静态成员,只能直接调用一个类中的静态成员
- 不允许在方法内部定义静态局部变量
- 静态方法中不能使用
this
- 非静态方法可以通过
类名.成员法
或成员
或this.成员
的方式访问类内静态成员/静态方法 - 应用
this.静态成员/静态方法
时会出现警告,但不影响程序运行
代码块
执行优先级:
静态代码块 > 构造代码块 > 构造方法
普通代码块
定义在方法内部,用
{}
括起来的代码段
- 可以在方法中出现多次,按顺序在方法调用时执行,作用域独立
- 适用于在方法内进行代码功能拆分
构造代码块
定义在类内部,没有被
static
修饰的,用{}
括起来的代码段
- 可以在类中出现多次,按顺序在每个对象实例化时执行
- 运行顺序(由早到晚):
静态代码块
构造代码块
构造方法
- 可以在构造代码块中直接操作静态和非静态成员
- 多适用于类中每个对象产生时都需要执行的功能封装。与构造方法的区别在于,构造方法是在
new
执行时有选择性的调用带参或者无参构造,而构造代码块则是在每个对象实例化时都一定会执行
静态代码块
被
static
修饰的,定义在类内部,用{}
括起来的代码段
- 只能出现在类内,不能出现在方法内
- 可以出现多次,按顺序在类加载时执行
- 无论该类实例化多少对象,只执行一次
- 不能在静态代码块中声明静态成员,可以声明非静态成员,静态代码块中声明的成员,在外部无法访问
- 不能在静态代码块中直接对非静态成员赋值
- 基于性能优化的考量,多适用于需要在项目启动时执行一次的场景,比如项目整体资源加载等
// 普通代码块
{}
// 构造代码块
public class Book{
{}
}
// 静态代码块
static {}
抽象类
意义
- 为子类提供一个公共类型,封装子类中的重复内容(成员变量和方法)
- 封装子类当中重复的内容(成员变量和方法)
- 可借由父子关系限制子类设计的随意性,在一定程度上避免了无意义父类的实例化
描述
- 抽象类不能实例化,只能通过引用指向子类实例(向上转型)
- 含有抽象方法的类一定是抽象类,但是抽象类中可以没有抽象方法
- 一个类继承抽象类之后,必须实现其所有的抽象方法,否则也需要设置为抽象类,不同子类对父类的抽象方法可以有不同的实现
- 子类只能通过
extends
继承一个抽象父类 - 抽象类中的静态成员和方法可以被子类继承应用
- 应用场景:某个父类只知道其子类应该包含怎样的方法,但无法知道子类如何实现这些方法
public abstract class Animal{}
abstract public class Animal{}
抽象方法
抽象方法必须定义在抽象类中
抽象方法必须加
abstract
关键字,并且不能有方法体子类必须重写父类抽象方法(如果不想重写父类方法,则需要将子类也设置为抽象类)
支持
public
、protected
和默认访问权限不能使用
static
、final
、private
修饰
public abstract void eat();
内部类
在 Java 中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。与之对应,包含内部类的类被称为外部类
内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类,更好的实现了信息隐藏
成员内部类
- 内部类中最常见的就是成员内部类,定义在类内部,可以看成是外部类的一个成员,也称为普通内部类
- 在成员内部类中 无法声明静态成员
- 内部类在外部使用时,无法直接实例化,需要借由外部类信息才能完成实例化
- 内部类的访问修饰符可以任意,但是访问范围会受到影响
- 内部类可以直接访问外部类的成员,如果出现同名属性,优先访问内部类中定义的
- 可以在内部类中使用
外部类名.this.成员
的方式,访问外部类中的同名信息 - 外部类访问内部类信息,需要通过内部类实例,无法直接访问
- 内部类编译后
.class
文件命名:外部类名$内部类名.class
- 应用场景:当两个类(A,B)彼此之间需要相互访问,且对其中某一类的应用限制较高(譬如B只在A的某种特定应用时才需要),可以将B设置为A的成员内部类
// Person.java
// 外部类
public class Person {
int age;
public Heart getHeart() {
return new Heart();
}
// 内部类
class Heart{
public String beat() {
return "心脏在跳动";
}
}
}
// PersonTest.java
public class PersonTest {
public static void main(String[] args) {
Person zhangsan = new Person();
// 方式1:new 外部类.new 内部类
Person.Heart heart1 = new Person().new Heart();
// 方式2:外部类对象.new 内部类
Person.Heart heart2 = zhangsan.new Heart();
// 方式3:外部类对象.获取方法
Person.Heart heart3 = zhangsan.getHeart();
System.out.println(heart1.beat());
System.out.println(heart2.beat());
System.out.println(heart3.beat());
}
}
静态内部类
- 内部类加上
static
修饰符,也称为嵌套内部类,它无需依赖外部类的实例 - 静态内部类中,只能直接访问外部类的静态成员,如果需要调用外部非静态成员,可以通过外部对象实例
- 可以通过
外部类.内部类.静态成员
的方式,访问内部类中的静态成员 - 当内部类属性与外部类属性同名时,默认直接调用内部类中的成员
- 如果需要访问外部类中的静态属性,可以通过
外部类.属性
的方式 - 如果需要访问外部类中的非静态属性,可以通过
new 外部类().属性
的方式 - 使用场景:当类 A 需要使用类 B,而 B 并不需要直接访问外部类 A 的成员变量和方法时,可以将 B 作为 A 的静态内部类
public class Person{
public static class Heart(){
public void say(){
System.out.print('Hello world!')
}
}
}
// 获取静态内部类对象实例
Person.Heart heart1 = new Person.Heart();
// 测试类中调用静态内部类的静态方法
Person.Heart.say();
方法内部类
- 定义在外部类方法中的类,作用范围在方法内,也称局部内部类
- 方法内部类中 不能定义静态成员
- 和方法内部成员使用规则一样,不能使用
public
、protected
、private
修饰 - 方法内部类中可以包含
final
、abstract
修饰的成员,但不推荐 - 类中普通方法如需访问外部方法中的局部变量,则该变量也需定义为final(jdk1.8之后底层默认会给该变量加上final)
- 内部类编译后
.class
文件命名:外部类+$数字+内部类.class
- 应用场景:由于类对象只能在当前方法或者代码块中创建、使用,相当于一次性产品,使用场景比较少
// Person.java
public class Person {
int age = 20;
public Object getHeart() {
class Heart {
public String beat() {
return Person.this.age + "岁的心脏在跳动";
}
}
// 这里需要直接返回方法运行结果,因为离开方法之后就访问不到任何方法内部类中的元素
return new Heart().beat();
}
}
// PersonTest.java
public class PersonTest {
public static void main(String[] args) {
Person person = new Person();
System.out.println(person.getHeart());
}
}
匿名内部类
将类的定义与类的创建放到一起完成
new Person(){ ... }
没有类名,没有
class
关键字,也无法产生具体类实例对象名必须继承或实现一个接口,指定给 new 的类型为匿名类的父类或父接口,但不能有显示的
extends
或implements
子句编译后的文件命名:
外部类$数字.class
无法在类型名称前添加修饰符
public protected private abstract static
不能出现构造方法、静态成员、抽象方法,可添加构造代码块
匿名内部类可以实现接口也可以继承父类,但是不可兼得
适用场景:多可用于作为方法参数或者返回值,然后根据不同类型,无需产生具体类和对象名,返回不同的实现即可。
// Person.java
public abstract class Person {
private String name;
public Person() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract void read();
}
// PersonTest.java
public class PersonTest {
public void getRead(Person person){
person.read();
}
public static void main(String[] args) {
PersonTest test = new PersonTest();
test.getRead(new Person(){
@Override
public void read() {
System.out.println("德玛西亚");
}
});
test.getRead(new Person(){
@Override
public void read() {
System.out.println("万众一心");
}
});
}
}
问答
问:方法内部类中为什么不能有静态成员?
答:由于 static 类型的属性和方法,在类加载的时候就会存在于内存中。因此要想使用某个类的static属性和方法,那么这个类必须要先加载到虚拟机中。但是,普通的方法内部类并不会随外部类一起加载,只有在实例化外部类之后才会加载。因此,如果在外部类并没有实例化,内部类还没有加载的时候,直接调用内部类的静态成员或方法,这明显是矛盾的。所以普通的方法内部类不能有静态成员变量或静态方法。
问:为什么方法中返回值是 Object,返回的是方法调用还不报错?
public Object getHeart(){
class Heart{
public String beat(){
new Person().eat();
return Person.age + "岁的心脏在跳动";
}
}
return new Heart().beat();
}
答:这里并不是返回方法调用,而是返回内部类Heart对象的beat方法的返回结果,整个方法最后一句的执行顺序是:1、通过 new 实例化内部类对象。 2、调用其 beat 方法。3、将 beat 方法返回值通过 return 带回,也就是此时的 Object 返回值,此处,也因为 Object 是 Java 中的根类,基于向上转型的原则,可以返回具体的子类String 类型的对象
什么是方法签名?
答:在 Java 中,方法签名着重指代:方法名和参数列表(包括:参数的类型、个数以及顺序)
接口和抽象类不能直接实例化,为什么下面代码可以这么写?
// Person.java
public abstract class Person{
public abstract void read()
}
// 测试
test.getRead(new Person(){
@Override
public void read(){
System.out.println("正在读书");
}
})
答:此处并不是实例化 Person,可以理解为是创建了一个没有名字的 Person 类型的子类对象,如下面代码与上面的功能是一致的
// TempClass.java
class TempClass extends Person{
@Override
public void read(){
System.out.println("正在读书");
}
}
// PersonTest.java
class PersonTest{
public void getRead(Person person){
person.read();
}
public static void main(String[] args){
PersonTest test = new PersonTest();
test.getRead(new TempClass());
}
}
基本原则(SOLID)
单一职责原则(SRP)
一个类应该只有一个发生变化的原因
一个类(大到模块、小到方法)承担的职责越多,被复用的可能性就越小,而且当一个类承担的职责过多,就相当于把这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作,因此要将这些职责进行分离,将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,如果多个职责总是同时发生改变,则可将它们封装在同一类中。