Java面向对象
1、基本概念
面向对象思想
物以类聚,分类的思维模式,思考问题首先会解决问题需要哪些分类,然后对这些分类进行单独思考。最后,才对某个分类下的细节进行面向过程的思索。面向对象适合处理复杂的问题,适合处理需要多人协作的问题!
-
面向对象编程(Object-Orented Programming,OOP)
-
面向对象编程的本质就是:以类的方式组织代码,以对象的形式(封装)数据。
-
抽象
-
三大特性
- 封装
- 继承
- 多态
-
从认识论角度考虑是先有对象后有类。对象,是具体的事物。类,是抽象的,是对对象的抽象。
-
从代码运行角度考虑是先有类后有对象。类是对象的模板。
2、类与对象的创建
- 类是一种抽象的数据类型,它是对某一类十五阵地描述/定义,但是并不是代表某一具体的事物。
- 静态的属性 属性
- 动态的行为 方法
- 对象是抽象概念的具体实例。
- 必须使用new关键字创造对象,调用构造器方法实例化。 Person person = new Person();
- 对象的属性 对象名.属性名。person.name
- 对象的方法 对象名.方法名()。person.getName()
3、封装
- 该露的露,该藏的藏
- 我们程序设计要追求”高内聚,低耦合”。高内聚:类的内部数据操作细节自己完成,不允许外部干涉;低耦合:仅暴露少量的方法给外部使用。
- 封装(数据的隐藏)
- 通常, 应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,这称为信息隐藏。
- 属性私有,get/set
封装的优点
- 良好的封装能够减少耦合。
- 类内部的结构可以自由修改。
- 可以对成员变量进行更精确的控制。
- 隐藏信息,实现细节。
四种权限
public:具有最大的访问权限,可以访问任何一个在classpath下的类、接口、异常等。它往往用于对外的情况,也就是对象或类对外的一种接口的形式。
protected:主要的作用就是用来保护子类的。它的含义在于子类可以用它修饰的成员,其他的不可以,它相当于传递给子类的一种继承的东西
default:有时候也称为friendly,它是针对本包访问而设计的,任何处于本包下的类、接口、异常等,都可以相互访问,即使是父类没有用protected修饰的成员也可以。
private:访问权限仅限于类的内部,是一种封装的体现,例如,大多数成员变量都是修饰符为private的,它们不希望被其他任何外部的类访问。
ps:不在同一包的子类可以访问父类protected修饰的数据成员
包之间的访问:
- 被访问的包中的类权限必须是public的;
- 类中的成员权限:public或者protected;
- protected是为其他包中的子类提供的一种权限。
4、继承
继承可以提高代码的复用性,让我们的变成更加靠近人类思维。当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有子类不需要重新定义这些方法和属性,只需要 extends来声明继承父类即可
- 继承的本质是对某一批类的抽象, 从而实现对现实世界更好的建模。
- extands的意思是"扩展”。子类是父类的扩展。
- JAVA中类只有单继承,没有多继承!
- 继承是类和类之间的一种关系。除此之外,类和类之间的关系还有依赖、组合、聚合等。
- 继承关系的俩个类,-个为子类(派生类),-个为父类(基类)。子类继承父类,使用关键字extends来表示。
继承带来的便利
- 提高了代码复用性
- 提高了代码扩展性和可维护性
super
super.属性名
访问父类的属性,但不能访问父类的private属性。super.方法名
访问父类的方法,但不能访问父类的private方法。super.(参数列表)
访问父类构造器,只能放在构造器第一句,且只有一句。- 当子类中有和父类中的成员(属性和方法)重名时,为了访问父类成员,必须通过super。
- super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用super去访问爷爷类的成员;如果多个基类中都有同名的成员,使用super访问遵循就近原则。
继承的细节和深入讨论
- 所有类都是Object类的子类,Object是所有类的基类。
- 子类继承了父类所有属性和方法,非私有属性和方法可以在子类直接访问,但是私有属性和方法不能在子类直接访问,要通过公共方法去访问。
- 子类创建时要先完成父类的初始化,当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类构造器中用super指定使用父类的哪个构造器来完成父类的初始化工作,否则编译不通过。(父类构造器调用不限于父类,将一直追溯到顶级父类Object)
- super必须在构造器第一行;在父类有无参构造器时,子类构造器第一行默认隐式调用super()
- super()和this()有于都只能放在构造器第一行,因此两个方法不能共存同一个构造器。
方法重写
方法重写(方法覆盖)就是子类有一个方法,和父类的某个方法的名称、返回类型、参数一样,那么我们就说这个方法覆盖了父类的那个方法。
- 子类的方法的参数、方法名称、要和父类方法的参数、方法名称完全一样。
- 子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类。比如:父类方法返回类型Object,子类重写方法的返回类型String。
- 子类方法不能缩小父类方法的访问权限。
5、多态
方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础上的。
- 方法的多态:方法重写和重载
- 对象的多态:
- 一个对象编译类型和运行类型可以不一致
- 编译类型在定义对象时,就可以确定,不能改变
- 运行类型是可以改变的
- 编译类型是 = 号左边,运行类型是 = 号右边。
注意:属性没有重写之说,当子类属性与父类重名时,属性的值看编译类型。
多态的向上转型
- 多态的本质:父类的引用指向了子类对象
- 语法:
父类类型 引用名 = new 子类类型()
- 特点:编译类型看左边,运行类型看右边,可以调用父类中所有成员(需要遵守访问权限),不可以调用子类中特有成员。
多态的向下转型
- 语法:
子类类型 引用名 = (子类类型) 父类引用
- 只能强转父类的引用,不能强转父类的对象
- 要求父类的引用必须指向的是当前类型的对象
- 可以调用子类类型中所有的成员
java动态转型机制
- 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定。
- 当调用对象属性时,没有动态绑定机制, 就近原则调用属性。
instanceof
用于判断对象的运行类型是否为XX类型或者XX类型的子类型,返回true或false。
语法:对象引用名 instanceof 要对比判断的类型
多态的应用
- 多态数组-创建父类数组,用于存储不同子类的对象。
- 多态参数-在函数中,父类做形参,接受子类
6、Object类
Class Object是类Object结构的根。 每个班都有Object作为超类。 所有对象(包括数组)都实现了这个类的方法。
Equals
==和equals的区别
- ==既可以判断基本类型,又可以判断引用类型。
- ==如果判断基本类型,判断的是值是否相等。例如:int i=10和double d=10.0。
- ==如果判断引用类型,判断的是地址是否相等,即判定是不是同一个对象。
- equals是Object类中的方法,只能用于判断引用类型。
- 默认判断地址是否相同,子类中往往重写该方法,用于判断内容是否相等。可查看Integer,String中的源码了解。
HashCode
- 提高具有哈希结构的容器的效率
- 两个引用,如果指向的是同一个对象,则哈希值肯定一样
- 两个引用,如果指向的是不同对象,则哈希值肯定不一样
- 哈希值主要是根据地址号来的,但是不完全等同于地址值(由于java是运行在JVM上的,对象的内部地址是无法获取的)
toString
该toString类方法Object返回一个由其中的对象是一个实例,该符号字符的类的名称的字符串(全类名)@ ”和对象的哈希码的无符号的十六进制表示。 换句话说,这个方法返回一个等于下列值的字符串:
getClass().getName() + '@' + Integer.toHexString(hashCode())
子类一般推荐重写,当直接输出一个对象时,toString方法会默认调用。
7、类变量和类方法
静态方法,只能访问静态成员;非静态方法,可以访问静态成员和非静态成员。
类变量
类变量又叫静态变量
定义语法: 访问修饰符 static 数据类型 变量名
- 类变量(静态变量)是所有对象共享的。
- 类变量在类加载而加载
- 通过类名.变量名访问,也可以通过对象名.变量名访问(类变量访问依然遵守访问权限)
JDK1.8以前放在方法区的静态域中,JDK8以后放在堆中的class对象中
类方法
类方法又叫静态方法
定义语法: 访问修饰符 static 数据返回类型 方法名(参数列表){}
- 当我们希望不创建实例也能调用某个方法时(通常为工具),把该方法做成静态方法非常合适。
- 开发工具类时,可以将方法写出静态方法,方便调用。
- 当方法中不涉及任何对象和相关成员时,可以写成静态方法,提高效率。
细节:
- 类方法在类加载而加载,结构信息存储在方法区。
- 类方法可以通过类名调用也可以通过对象名调用。
- 类方法中不能使用和对象相关的关键字(this和super)
main方法
定义语法:public static main(String[] args)
- main方法是Java虚拟机在调用
- java虚拟机调用类的main()方法,因为不在同一个包中,所有main方法的访问权限必须是public。
- java虚拟机执行main方法时不必创建对象,所以main方法必须是static。
- main方法接收String类型的数组参数,用于保存指向java命令时传递给所运行的类参数。
- java 执行程序 参数1 参数2 参数 3
代码块
代码块有称为初始化块,属于类中的成员(即类中的一部分),类似于方法,将逻辑语句封装在方法体总,通过{}包围起来。
但和方法不同,没有方法名,没有返回值,没有参数,只有方法体,而且不用通过对象或者类显示调用,而是加载类时,或创建对象时隐式调用。
无论我们调用哪个构造器创建对象,都会先调用代码块的内容。代码块调用顺序优于构造器。
应用场景:如果多个构造器中有重复的语句,可以抽取到初始化块(代码块)中,减少代码的冗余。
基本语法: [修饰符] {代码}
注意:
- 修饰符可选,且只能写static。
- 有static叫静态代码块,没有的叫普通代码块。静态代码块只能调用静态成员,普通代码块可以调用任意成员。
- ;号可写可不写。
static代码块
static代码块又叫静态代码块,作用是对类进行初始化。它会随着类的加载而执行,并且只会执行一次。
类什么时候被加载:
- 创建对象实例的时候。
- 创建子类对象实例时,父类也会被加载。
- 使用类的静态成员时。
普通代码块
普通代码块会在对象实例创建时被隐式调用。被创建一次,就会调用一次。
如果只是使用类的静态成员时,普通代码块并不会执行。
创建一个对象时,在类中的初始化调用顺序是:静态代码块=静态属性》普通代码块=普通属性》构造器。
在构造器中,会隐式调用super()和普通代码块。
class A{
public A(){
//super();
//普通代码块;
System.out.println("A构造");
}
}
当父类和子类都有静态代码块和普通代码块时的调用顺序:
父类静态》子类静态》父类普通》父类构造》子类普通》子类构造
final关键字
final可以修饰类,属性,方法和局部变量。在某些情况下,会使用到final:
- 当不希望类被继承时,可以使用final修饰。
- 当不希望父类某个方法被重写时,可以使用final修饰。
- 当不希望类的某个属性值被修改时,可以使用final修饰。
- 当不希望某个局部变量被修改时,可以使用final修饰。
注意
- final修饰的属性一般叫常量。一般用XX_XX_XX(大写)命名。
- 可以不在初始化时赋值,可以在构造器,或者代码块赋值。abafan
- 如果final修饰的属性是静态,必须在初始化或者静态代码块中赋值,不能在构造器中赋值。
- 一般来说,final类的方法已经没必要再用final修饰。
- final不能修饰构造器。
- final往往和static搭配使用,效率更高,底层做了优化,不会导致类加载。
- 包装类(Inter,Double,Float,Boolean,String等类)都是final类。
8、抽象类和接口
抽象类
当父类的某些方法需要声明,但又不确定如何实现时,可以用abstract关键字修饰,将其声明为抽象方法,抽象方法没有方法体;用abstract修饰该类,那么这个类就是抽象类。有抽象方法的类必须声明为抽象类。
定义语法:访问修饰符 abstract 返回类型 方法名();
一般来说,抽象类一般会被子类继承,实现父类的抽象方法。
注意:
- 抽象类不能被实例化。
- 抽象类不一定有抽象方法。
- 有抽象方法的类必须声明为抽象类。
- abstract只能修饰类和方法。
- 抽象成员可以有任意成员,如构造器,属性等。
- 抽象方法没有方法体。
- 抽象类被继承一定要实现所有抽象方法。除非该方法在子类依然被抽象。
- 抽象方法不能使用private,final,static来修饰,因为这些关键字都与重写违背。
接口
接口就是将一些没有实现的方法封装到一起,到某个类要使用时,再根据具体情况把这些方法实现出来。
定义语法:
interface 接口名{
//属性
//方法
}
class 类名 implement 接口 {
自己的属性
自己的方法
必须实现接口的抽象方法
}
接口是更加抽象的抽象类。抽象类里的方法可以有方法体,在jdk7以前,接口所有方法都没有方法体,
在jdk8以后,接口类可以有静态方法,默认方法(需要使用default关键字修饰),接口类中可以有具体的方法实现了。
注意:
- 接口也不能被实例化
- 接口的方法默认public访问权限,默认使用abstract修饰,可写可不写。
- 普通类必须实现所有接口方法,抽象类可以不用。
- 一个类可以实现多个接口,必须实现多个接口的方法。
- 接口的属性,只能是final,而且是public static final修饰符,且必须初始化。
- 接口不能继承其他类,但是可以继承多个其他接口。
- 接口的修饰符,只能是public和默认,这点和类是一样的。
接口和继承的区别
- 继承的价值主要在于:解决代码的复用性和可维护性。
- 接口的价值主要在于:设计号各种方法规范,让其他类去实现。
- 接口比继承更加灵活。
- 接口在一定程度上实现解耦。
9、内部类
一个类的内部有完整嵌套了另一个类的结构,被嵌套的类被称为内部类(inter class),嵌套其他类的类又被称为外部类(outer class)。内部类的最大特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。
语法:
class Outer{//外部类
class Inner{//内部类
}
}
class Other{//外部其他类
}
内部类的分类:
- 定义在外部类局部位置上
- 局部内部类(有类名)
- 匿名内部类(无类名)
- 定义在外部类的成员位置上
- 成员内部类(无static修饰)
- 静态内部类(有static修饰)
内部类本质都还是一个类。
局部内部类
局部内部类时定义在外部类的局部位置,如方法,代码块中,并且有类名。局部内部类本质上还是一个类。
- 可以访问外部类的所有成员,包括私有成员。
- 不能使用final以外的其他访问修饰符,因为它的地位等同于局部变量,只能使用final。
- 作用域:定义它的方法体或者代码块中。
- 局部内部类访问外部类成员直接访问,外部类访问局部内部类(必须在作用域内访问)需要创建对象实例才能访问。
- 当局部内部类和外部类属性重名时,遵守就近原则,调用外部类属性时,使用
外部类类名.this.属性名
调用。
匿名内部类
匿名内部类时定义在外部类的局部位置,比如方法中,并且没有类名。本质上还是一个隐藏名字(系统会分配名字)类,同时还是一个对象。
语法:
new 类或接口名(参数列表){
类体
};
注意:
- 匿名内部类可以访问外部类所有属性,外部类不能访问匿名内部类,因为匿名内部类创建实例后就销毁了。
- 不能使用访问修饰符,因为它的地位等同于局部变量。
- 作用域:定义它的方法体或者代码块中。
- 当匿名内部类和外部类属性重名时,遵守就近原则,调用外部类属性时,使用
外部类类名.this.属性名
调用。
成员内部类
成员内部类是定义在外部类成员位置。
- 成员内部类可以访问外部类的所有成员
- 成员内部类的定位是成员,所以可以使用private,public,protect等访问修饰符修饰。
- 作用域:同其他成员,作用域为整个类
- 成员内部类访问外部类直接访问;外部类访问成员内部类需要先创建对象实例访问
- 外部其他类使用成员内部类:
- 外部类.成员内部类 变量名=成员内部类.new 成员内部类()
- 通过外部类公共方法返回实例
- 当成员内部类和外部类属性重名时,遵守就近原则,调用外部类属性时,使用
外部类类名.this.属性名
调用。
静态内部类
静态内部类是定义在外部类成员位置。并且有static修饰
- 可以直接访问外部类所有静态成员,但是不能访问非静态成员。
- 静态内部类的定位是成员,所以可以使用private,public,protect等访问修饰符修饰。
- 作用域:同其他成员,作用域为整个类
- 静态内部类访问外部类成员(静态)直接访问;外部类访问静态内部类需要先创建对象实例访问
- 外部其他类访问静态内部类:
- 直接通过类名访问,前提满足访问访问权限。
- 通过外部类公共方法返回实例
- 当成员内部类和外部类属性重名时,遵守就近原则,调用外部类属性时,使用
外部类类名.属性名
调用。
10、枚举
枚举是一组常量的组合,可以理解为一种特殊的类,里面只包含一些有限的特定的对象。比如季节只有春夏秋冬,月份只有12个月等。
自定义枚举类:
- 构造器私有化,防止new产生新对象。
- 去掉set方法,防止属性被修改,因为对象通常只读
- 在内部直接创建固定对象。
- 对枚举对象的属性通常使用final + static共同修饰,实现底层优化。属性通常使用全部大写,常量命名规范。
- 枚举对象根据需要,也可以有多个属性。
关键字实现:
- 当我们使用enum关键字实现枚举类时,会默认继承一个Enum类。
- 简化定义为:常量名(参数列表),定义多个用,间隔,最后分号结尾。且必须放在类首行
- 使用无参构造创建枚举对象时,可以将列表和小括号都省略。
注意:使用enum关键字后不能再继承其他类,但是可以实现接口。因为枚举类会隐式继承Enum类。
11、注解
注解(Annotation)也被称为元数据(Metadata),用于修饰解释包,类,方法,属性,构造器,局部变量等数据信息。
和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息。在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。
在JavaEE中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替java EE旧版中所遗留的繁冗代码和XML配置等
使用Annotation时要在其前面增加@符号,并把该Annotation当成一个修饰符使用。用于修饰它支持的程序元素
➢三个基本的Annotation:
- @Override:限定某个方法,是重写父类方法,该注解只能用于方法
- @Deprecated: 用于表示某个程序元素(类,方法等)已过时
- @SuppressWarnings: 抑制编译器警告
12、异常
基本概念:Java语言中,将程序执行中发生的不正常情况称为"异常”。(开发过程中的语法错误和逻辑错误不是异常)
●执行过程中所发生的异常事件可分为两大类
- Error(错误): Java虚拟机无法解决的严重问题。如: JVM系统内部错误、资源耗尽等严重情况。比如: StackOverflowError(栈溢出)和OOM(out of memory), Error 是严重错误,程序会崩溃。
- Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如空指针访问,试图读取不存在的文件,网络连接中断等等,Exception 分为两大类:运行时异常和编译时异常。
异常分类
- 检查性异常:在编译时不能被简单的忽略。
- 运行时异常Exception:运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
- 错误Error:错误不是异常,而是脱高程序员控制的问题。错误在代码中通常植忽确。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。
Java把异常当作对象来处理,并定义-个基类java.lang.Throwable作为所有异常的超类。
在Java API中已经定义了许多异常类,这些异常类分为两大类,错误Error和异常Exception。
异常处理机制
- 抛出异常
- 捕获异常
异常处理关键字:try catch finally throw throws
捕获异常
public static void main(String[] args) {
int a = 1;
int b = 0;
try{//监控区域
System.out.println(a/b);
}catch (ArithmeticException e) { //catch(异常类型)
System.out.println("捕获异常ArithmeticException,变量b不能为0");
}finally { //处理善后工作
System.out.println("finally");
}
//finally区可以不要
}
catch可以捕获多个异常:要求异常类型从小到大!
try{//监控区域
}catch (ArithmeticException e) { //catch(异常类型)
}catch (Exception e1){
}catch (Throwable e2){
}
抛出异常
public class Test {
public static void main(String[] args) {
new Test().test(1,0);
}
//假如方法中处理不了异常,throws方法上抛出异常
public void test(int a,int b) throws ArithmeticException{
if (b==0){
throw new ArithmeticException();
}
//System.out.println(a/b);
}
}
自定义异常
public class MyException extends Exception{
//传递的数字大于10抛出异常
private int detail;
public MyException(int a){
this.detail = a;
}
//toString:异常的打印信息
@Override
public String toString() {
return "MyException{" +
"detail=" + detail +
'}';
}
}
- 处理运行时异常时,采用逻辑去合理规避同时辅助try-catch处理
- 在多重catch块后面,可以加一个catch(Exception) 来处理可能会被遗漏的异常
- 对于不确定的代码,也可以加上try-catch,处理潜在的异常
- 尽量去处理异常,切忌只是简单地调用printStackTrace()去打印输出
- 具体如何处理异常,要根据不同的业务需求和异常类型去决定
- 尽量添加finally语句块去释放占用的资源, IO