概念
面向对象(Object-Oriented Programming Concepts)是一种编程思想,强调的是结果。把相关的数据和方法组织为一个整体来看待,从更高的层次来进行系统建模,更贴近事物的自然运行模式。
Oracle文档
面向对象与面向过程
面向对象和面向过程是两种软件开发方法,或者说是两种不同的开发范式。
什么是面向过程?
“面向过程”(Procedure Oriented)是一种以过程为中心的编程思想,是一种自顶而下的编程模式。
最典型的面向过程的编程语言就是C语言。概述
把问题分解成一个一个步骤,每个步骤用函数实现,依次调用即可。
就是说,在进行面向过程编程的时候,不需要考虑那么多,上来先定义一个函数,然后使用各种诸如if-else、for-each等方式进行代码执行。
最典型的用法就是实现一个简单的算法,比如实现冒泡排序。什么是面向对象?
面向对象程序设计的雏形,早在出现在1960年的Simula语言中,当时的程序设计领域正面临着一种危机:在软硬件环境逐渐复杂的情况下,软件如何得到良好的维护?
面向对象程序设计在某种程度上通过强调可重复性解决了这一问题。
目前较为流行的面向对象语言主要有Java、C#、C++、Python、Ruby、PHP等
面向对象是一种将事务高度抽象化的编程模式概述:
将问题分解成一个一个步骤,对每个步骤进行相应的抽象,形成对象,通过不同对象之间的调用,组合解决问题。
就是说,在进行面向对象进行编程的时候,要把属性、行为等封装成对象,然后基于这些对象及对象的能力进行业务逻辑的实现。
比如:想要造一辆车,上来要先把车的各种属性定义出来,然后抽象成一个Car类。
特征
封装
封装属性思路
- 用private 修饰资源
- 提供公共的get方法,用来获取值
- 提供公共的set方法,用来设置值
封装方法思路
- 用pirvate 修饰方法
- 用本类的公共方法调用这个封装方法的功能
权限修饰符
匿名对象
当被调用的对象只调用一次时(多次会创建多个对象浪费空间)
new Scanner(System.in).nextInt();
成员方法
- 如果方法的返回值类型不是void,就需要使用return 关键字返回对应类型的返回值。
继承
多态
类和对象
类(class、type类型)
定义
对象
定义
- 具体的实例,根据类具体创造出来的具体的实例
构造方法
定义
- 特殊的方法,方法名与类名一致,没有返回值类型 官方文档
- 可以使用四种权限修饰符
- 每次new对象时,都会自动触发对应类中的构造方法
- 无参构造每一个类中都会默认存在一个没有参数的构造方法,但是如果提供了其他的构造函数,则默认的无参构造会被取消
- 含参构造含有参数的构造函数,可用于使用简单属快速创建对象,仅提供有限个数的属性值 ,其他的属性可以提供默认值。对参数也没有任何要求
- 全参构造
此构造函数与本类的属性一致,可用于初始化本类所有的属性
You don’t have to provide any constructors for your class, but you must be careful when doing this. The compiler automatically provides a no-argument, default constructor for any class without constructors. This default constructor will call the no-argument constructor of the superclass. In this situation, the compiler will complain if the superclass doesn’t have a no-argument constructor so you must verify that it does.
格式
与本类类名同名,且没有返回值类型的方法
构造代码块
格式
{构造代码块}
使用大括号包裹
位置
类里方法外,与其他成员并列, 主要用于抽取构造函数中共性的部分
执行时机
每次创建对象时都会执行构造代码块,且优先于构造函数执行
作用
用于提取所有构造方法的共性功能
局部代码块
格式
{局部代码块}
使用大括号包裹
位置
方法里面
执行时机
调用本局部代码块所处的方法时,才会执行
作用
限定局部变量的作用域,局部代码块中的局部变量只能在代码块里面使用
总结
执行顺序:构造代码块->构造方法->普通方法->局部代码块,分析:
- 当创建对象时,会触发构造函数
- 创建对象时也会触发构造代码块,且会优先于构造方法执行、
- 创建好对象后才能通过对象调用普通方法
- 如果普通方法里面有局部代码块,才会触发对应的局部代码块
this 关键字
- 用于访问成员变量:当成员变量与局部变量同名时,可以使用this关键字来指定使用的是本类的成员变量。否则将根据就近原则来取值。
- 用于访问构造函数:
this(参数列表)
必须在构造函数的第一行,普通方法不能使用此语句。构造函数形成死循环时会报Recursive constructor invocation
编译错误
this();用于调用本类无参构造
this(参数);用于调用本类含参构造
继承
概念
继承是面向对象最显著的一个特征
继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并扩展新的能力。
特点
- 使用extends关键字来表示继承关系,是is-a的关系,格式:
son extends father
- 相当于子类把父类的功能复制了一份
- Java只支持单继承,一个子类只能有一个父类,但是一个父类可以有多个子类。
- 父类的私有成员也会被继承,但由于是私有的不可见,所以子类不能使用父类的私有资源。这一点可以在idea中使用debug功能跟踪看到子类确实继承了父类的私有资源,但是没有访问权限,会报错
'b' has private access in 'cn.tedu.oop2.Cat'
。 - 继承多用于功能的修改,子类可以在拥有父类功能的同时,进行功能拓展
- 继承具有传递性,爷爷的功能会传递给父类,父类的功能会传递给子类
- 子类可以拥有自己的独有的方法,实现了功能的拓展,青出于蓝而胜于蓝
- 构造方法不可以被继承!因为语法的原因,构造方法的名称要与类名一致,不能在子类中出现一个父类名称的构造方法
- 如果一个类没有明确指定父类,则其默认继承自顶级父类
Object
,其构造函数中默认的super()
调用的就是Object
的无参构造
If your class has no explicit superclass, then it has an implicit superclass of Object, which does have a no-argument constructor.
super
- 当父类的成员变量与子类的变量同名时,使用super指定父类的成员变量
- 可以把super“看作”是父类对象的引用,但实际并不完全是一个父类对象
- 子类在创建对象时,默认会先调用父类的构造方法,原因是子类构造函数中的第一行默认存在super();表示调用父类的无参构造
- 子类必须调用一个父类 的构造函数,不论是无参还是含参,任选一个即可
重写
遵循的原则:两同两小一大
- 两同:子类的 方法名和参数列表与父类一致
- 两小:子类返回值类型必须是父类方法返回值类型或其子类,void没有子类,所以重写时没得改。
- 一大:子类方法的权限修饰符要大于或等于父类的
注解 @Override
- 用来加在方法上,表示这是一个重写的方法。如果重写不成功,会报错
静态static
概念
- java中的一个关键字
- 用于修饰成员(变量和方法)
特点
- 可以修饰成员变量与成员方法
- 随着类的加载而加载到内存中,优先于对象加载,可以被类名直接调用,即使尚未创建对象。
- 只加载一次,就会一直存在,不再开辟新空间,直到类消失才会消失
- 静态资源,也叫做类资源,在内存中只有一份,全局唯一,被全局所有对象所共享。所以当通过任意一种方式修改了静态变量的值以后,不管用何种方式查看,静态变量的值都是刚刚修改过的值
- 被类名直接调用静态方法
- 普通资源可以调用普通资源和静态资源,静态资源只能调用静态资源。
- 静态区域是不允许使用
this
关键字的 - 静态方法不存在重写
- 如果子类中的与父类同名的静态方法
output
方法没有static
修饰,则会报不能重写的编译错,所以static可以这样来解释:加上static是隐藏了父类中的方法,而不是重写。同样的,如果父类中的方法不是静态的,那么子类中的同名方法也不可以是静态的,也就是说静态的方法不能被覆盖,静态的方法也不能覆盖非静态的,总之一句话:静态的都不能覆盖,要么全静态,要么全非静态。参考连接
静态代码块
格式
static{
code;
}
位置
与构造代码块并列,也是类里方法外
执行时机
静态代码块也属于静态资源,随着类的加载而加载,优先于对象加载,并且只加载一次
作用
用于加载那些需要第一时间加载,并且只加载一次的资源,常用来初始化。
final关键字
- final可以用来修饰类,被修饰的类是最终类,不可以被继承
- 可以把被final修饰的类看成是叶子节点
- final可以用来修饰方法,被修饰的方法是该方法的最终实现,不可以被重写
- 被final修饰的是常量,常量的值不可以被修改。注意:不论是成员位置还是局部位置,常量定义的时候必须赋值。名称一般全部大写,单词与单词之间使用
_
分隔。编译后所有的常量都被替换成了字面值。
- 【强制】常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
正例:MAX_STOCK_COUNT
/CACHE_EXPIRED_TIME
反例:MAX_COUNT
/EXPIRED_TIME
- 修饰引用类型的变量时,该变量不能再指向其他的对象,但是所指向对象的自己的成员属性等可以更改
多态
概念
父类的引用指向子类的对象
所谓多态就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。
https://hollischuang.gitee.io/tobetopjavaer/#/basics/object-oriented/characteristics
只有普通方法或是抽象方法由于可以被重写,所以存在多态现象,其他的成员调用时是根据引用类型变量的类型来绑定的。
解释:如果是父类的引用传入的是子类的对象,当使用这个父类的引用调用父类里面定义的静态方法时,执行的就是父类里面的方法,即使子类也写了一个一模一样的方法也是如此。如果直接定义的就是子类对象,调用的是父类的静态方法而子类自己没有定义时,执行的就是从父类那里继承来的方法。如果子类也写了一个一模一样的方法,则执行的就是子类自己的方法了。
对于如下子父类关系
class A{
public static void make(){}
}
class B extends A{
}
有如下的绑定
造型 | 子类无make() | 子类声明了同样的make() |
---|---|---|
A a = new B();a.make(); | 执行A.make(); | 执行A.make(); |
B b = new B();b.make(); | 执行A.make(); | 执行B.make(); |
前提
继承、方法的重写
父类对象不可以使用子类的特有功能
口决
- 父类引用指向子类对象。【解释:创建出来的子类对象的地址值,交给父类类型的引用类型变量来保存】
Father f = new Son();// 这里的f就是父类类型的引用变量,但实际new的是子类的对象
- 编译(保存)看左边,运行看右边。方法定义看父类,方法体如果有重写的话使用子类的。
- 【解释:必须要在父类定义这个方法,才能通过编译,把多态对象看作父类类型,必须要在子类重写这个方法才能满足多态:实际干活的是子类】
- 此处的
编译看左边
是指在ide里面的时候,并不是指编译后,实际编译后前面的引用会被修改为后面new的类型的引用 Father father = new Son();
编译后会被优化成Son son = new Son();
Father father = new Son();// 定义引用变量f为父类类型
father.eat();// 假设父类里面有定义eat()方法,则此处没问题,否则报“Cannot resolve method 'eat' in 'Father' ”错误
- 成员变量看引用【当使用
father.sum
这样的形式来访问成员变量时,取值是看引用的,引用是哪个类型,就用哪个类型声明或是其最近父类的声明的变量】 - 静态方法不存在重写情况,调用时看引用。引用是哪个类型的,就调用哪个类里面定义的静态方法
异常
异常是一些用来封装错误信息的对象
它由异常的类型、提示信息、报错的行号提示三部分组成
异常的处理
- 自己捕获
try{
可能会抛出异常的代码
} catch(异常的类型 异常的名字) {
捕获到异常后,进行处理的解决方案
}
- 嵌套捕获,从上到下,异常的范围从小到大,或是最后再写范围大的异常,如本下面例子中,范围较精确的
InputMismatchException
和ArithmeticException
优先捕获,最后再捕获范围最大的Exception
异常 finally{}
是try-catch结构的第3个部分,这分不论是否捕获到异常,都一定会执行,所以常用来关流操作。
try {
System.out.println("请输入除数");
Scanner sc = new Scanner(System.in);
int a = sc.nextInt();
System.out.println("请输入被除数");
int b = sc.nextInt();
System.out.println("两数相除的结果:" + a / b);
} catch (InputMismatchException e) {
System.out.println("请输入整数");
} catch (ArithmeticException e){
System.out.println("除数不能为0");
} catch (Exception e) {
System.out.println("出错了");
} finally {
//这里的操作一定会执行,如果在上面就有了返回语句,那么返回的结果以及返回操作将被挂起,直到这里的代码执行完成后再继承挂起的操作
}
- 向上抛出
- 将异常向上抛出,交给调用者来解决。在方法的小括号与大括号之间写
throws 异常类型
,如果有多个异常,使用逗号分隔即可。
public static void method() throws ArithmeticException, InputMismatchException {}
public static void method1() throws Exception {}
- 如果一个方法向上抛出了异常,则谁调用此方法就由谁来处理这个异常,所以这里的处理也有两种方案,捕获解决或是继续向上抛出。但注意,一般会在main()方法调用之前将异常解决掉,而不是抛给main()方法,因为这会导致main()方法直接中止。
public static void main(String[] args) {
handleExceptionMethod();//在main方法中调用已经把异常解决掉了的方法
}
// 此方法做为中间方法,先行把异常处理掉
public static void handleExceptionMethod() {
try{
method();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void method() throws Exception {}
抽象类
- 被abstract修饰的方法是抽象方法,没有方法体。也不能有方法体,否则报错
Abstract methods cannot have a body
public abstract void play();
- 被abstract修饰的类是抽象类,如果一个类中包含了抽象方法,则这个类必须声明为抽象类
// 声明为抽象类
abstract class Animal{
public void eat(){System.out.println("animal eat");}
// 抽象方法
public abstract void play();
}
- 当子类继承了抽象父类后,有两种解决方案,要么也声明成抽象类(不实现 或 只实现部分抽象方法)、要么实现从父类继承的所有的抽象方法,“父债子偿”
class Pig extends Animal{
// 实现从父类那里继承过来的抽象方法
@Override
public void play() {}
}
- 抽象类不可以被实例化,不能创建对象。但是可以指向其普通子类
Animal a = new Animal();// 会报错“'Animal' is abstract; cannot be instantiated”
Animal a = new Pig(); // 可以指向已经实现了所有抽象的普通子类
- 抽象类也有构造方法,为了子类创建对象时使用,即super();
abstract class Animal2{
// 为了被子类的构造函数使用super();
public Animal2(){
System.out.println("animal2's constructor");
}
public void play(){
System.out.println("animal2.play()");
}
}
class Pig2 extends Animal2{
public Pig2(){
super();
System.out.println("pig's constructor");
}
}
- 抽象类对类内部的成员不做限制,可以全是普通成员,也可以全部是抽象方法。因为抽象类不能被实例化,所以如果一个普通类不想被实例化,可以使用abstract来修饰。
// 定义成抽象类,表示不可以创建此类的对象
abstract class Car{
String name = "car";// 可以定义成员变量
public static final int CAR_WHEELS = 4;// 可以定义常量
// 可以定义普通方法
public void start(){
System.out.println("car is start");
}
// 可以定义静态方法
public static void paint(){
System.out.println("car's color changed");
}
}
// 子类继承了十分钟任何抽象方法的抽象类后也无需实现任何方法
class BYD extends Car{
}
// 全部都是抽象方法的抽象类也是允许的
abstract class Phone{
abstract void call();
abstract void message();
}
接口
- 定义:与之前学习过的抽象类一样,接口在Java中也是一种抽象类型,接口中的内容是抽象形成的需要实现的功能。更像是一种规则和一种标准,大家都要遵守的
- 特点:
- 可以把接口理解成一个特殊的抽象类(但接口不是类!!!)
- 使用
interface
关键字来声明, 一个interface文件中只能有一个public interface且接口名字与文件名字一致。
public interface Inter{}
- 方法可以简写,默认都是被
public abstract
修饰的,可写可不写,一直都在。方法不可以被protected
和private
修饰
public interface Inter{
void eat();
public abstract void play();// Modifier 'abstract' and 'public' is redundant(冗余) for interface methods
private void teach();// 编译错误“Modifier 'private' not allowed here”
protected void ready();// 编译错误“Modifier 'protected' not allowed here”
}
- 实现类使用
implements
关键字来定义所实现的接口。定义好要实现的接口,要么实现接口中所定义的所有方法,要么自己变成抽象类只实现部分或不实现接口中的方法。
public abstract class InterImpl implements Inter{
@Override
public void eat(){
System.out.println("interimpl.eat()");
}
}
- 接口与抽象类一样,不可以被实例化
Inter inter = new Inter();// 会报错“'Inter' is abstract; cannot be instantiated”
- 创建接口(父接口) -> 接口实现类(子实现类) -> 测试类
public class TestInter {
public static void main(String[] args) {
// ③测试接口实现类,多态形式
Inter inter = new InterImpl();
inter.get();// 输出:InterImpl.get()
}
}
// ①先定义接口
interface Inter{
void get();
}
// ②创建实现类
class InterImpl implements Inter{
@Override
public void get() {
System.out.println("InterImpl.get()");
}
}
- 接口里面没有构造方法
interface UserInter{
public User();// 编译错误:`Not allowed in interface`
}
- 接口中定义的是静态常量,默认被
public static final
修饰,可写可不写。
interface UserInter{
int age = 10; // 等同于 public static final int age = 10;此处必须赋值
}
class UserInterTest{
public static void main(String[] args){
int age = UserInter.age;// 测试类中可以通过`接口名.成员`名访问此成员的值,说明是static 修饰的
UserInter.age = 10; //编译错误:“Cannot assign a value to final variable 'name'”,说明是final修饰的
}
}
- 接口可以继承接口,而且还可以多继承,也就是一个子接口继承多件父接口;一个类也可以实现多个接口;多个接口之间使用号分隔。
interface Inter1{
void save();
void delete();
}
interface Inter2 {
void find();
void update();
}
// 接口可以继承多个接口
interface Inter3 extends Inter1,Inter2{
void showTable();
}
// 一个类可以多实现
class Inter3Impl implelements Inter1,Inter2{
@Override
public void save() {}
@Override
public void delete() {}
@Override
public void find() {}
@Override
public void update() {}
}
- JDK8中接口可以被
default
修饰的方法,并可以有方法体,该方法一般称作默认方法。与普通方法类似,可以重写,但重写时就不需要加defalt
关键字
interface Info{
default void play(){
System.out.println("Info.play()");
}
default void eat(){
System.out.println("Info.eat()");
show();
}
void show();
}
- 可以有静态方法,
public
可以省略,但是static是不可以省略的。调用时只能通过接口名调用,向上造型、子类类名、子类对象均无法调用 。
public interface Inter {
public static void eat() {
System.out.println("Inter.eat()");
}
}
- 接口与抽象类的区别
抽象类 | 接口 |
---|---|
使用abstract class 关键字 | 使用interface 关键字 |
可以定义成员变量 | 只有常量,默认public static final |
对成员方法没有限制 | 只有抽象方法public abstract |
不可实例化 | 不可实例化 |
有构造方法 | 没有构造方法 |
只能单继承 | 可以多继承 |
抽象是后天重构的结果 | 接口是先天设计的结果 |
内部类
class A {// outter class
// create B object,visit !private field of B
class B{ // innner class
// just for class A
// visit all field of A
}
}
分类
- 成员内部类:类里方法外
- 局部内部类:方法里面
- 匿名内部类:
new Outter().find();
只创建对象,但是不使用引用变量对其进行引用
特点
- 内部类的实例化方式
Outer.Inner inner = outer.new Inner();
inner.delete(); // 得到内部类的对象后,使用就与普通类一样了
System.out.println("sum:"+inner.sum); //获得内部类的非私有属性也是与普通类一样
2.内部类可以使用外部类的所有资源,包括私有资源
class Outer{
String name;
private int age;
public void find(){
System.out.println("outer's find();");
}
class Inner{
int sum = 10;
public void delete(){
System.out.println("Inner ... delete();");
System.out.println("outter's name:"+name); // 访问外部类的成员属性
System.out.println("outter's age:"+age); // 访问外部类的私有资源
find(); // 访问外部类的成员方法
}
}
}
- 外部类如果想要使用内部类的资源,必须先创建内部类的对象,然后通过内部类对象来调用 内部类的资源。外部类不能直接调用内部类的普通成员。
- 普通内部类不可以有静态成员
- 匿名内部类
new Outer().new Inner().delete();
- 静态内部类:很少用。内部类可以直接被外部类的名字调用,创建静态内部类时,就不需要先创建外部类的对象了
new Outer3.Inner3()
。只访问静态内部类的成员时,外部类不会被加载
public class TestInner3 {
public static void main(String[] args) {
Outer3.Inner3 in = new Outer3.Inner3();// 可以直接new了
in.eat(); // 调用对应的内部类的普通方法
Outer3.Inner3.show(); // 静态内部类的静态方法可以通过静态方式调用
}
}
class Outer3{
static class Inner3{
public void eat(){
System.out.println("outer3.inner3.eat()");
}
public static void show(){
System.out.println("outer3.inner3.show()");
}
}
}
- 局部内部类:
- 位置:方法里
- 使用 :直接调用内部类所在方法是无法触发内部类里面的功能的,需要所在方法里面、类声明的后面创建此内部类的对象,然后调用对应的功能。
public class TestInner4 {
public static void main(String[] args) {
new Outer4().show();
}
}
class Outer4{
public void show(){
System.out.println("outer4.show();");
class Inner4 {
String name;
int age;
public void eat(){
System.out.println("inner4.eat()");
}
}
// 只有在内部类的声明后面才可以创建内部类的对象
Inner4 in = new Inner4();
in.eat();
}
}
- 匿名内部类没有名字 ,通常与匿名对象结合在一起使用,匿名对象只双脚使用一次,一次只能调用 一个功能,匿名内部类充当了实现类的角色,去实现接口/抽象类中的抽象方法,只是没有名字而已。
new Outer(){此处相当于是一个继承了前面对象的子类的类定义区域}.method();
这种写法适用于接口、抽象类和普通类,大括号里面相当于是继承了前面的类。如果里面需要临时使用外面的局部变量,需要保证此变量为实际意义上的不可变,才可以通过编译,否则会报Variable 'id' is accessed from within inner class, needs to be final or effectively fina
int id = 10;// id赋值后不可以再次改变其值,否则会报编译错误“Variable 'id' is accessed from within inner class, needs to be final or effectively final”
new Inter2(){
@Override
public void set() {
System.out.println("id:"+id);
}
}.set();