目录
一、面向对象——基础
=========================================================================
1.1、面向对象思想
面向对象(Object Oriented)是软件开发方法。面向对象的概念和应用已超越了程序设计和软件开发,是一种对现 实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。
面向对象是相对于面向过程来讲的,指的是把相关的数据和方法组织为一个整体 来看待,从更高的层次来进行系 统建模,更贴近事物的自然运行模式。
面向过程到面向对象思想层面的转变: 面向过程关注的是执行的过程,面向对象关注的是具备功能的对象。
面向过程到面向对象,是程序员思想上 从执行者到指挥者的转变。
例子:
1.2、三大思想 && 三大特性
面向对象思想从概念上讲分为以下三种:
- OOA:面向对象分析(Object Oriented Analysis)
- OOD:面向对象设计(Object Oriented Design)
- OOP:面向对象程序(Object Oriented Programming)
面向对象的三大特性:
- 封装性:所有的内容对外部不可见
- 继承性:将其他的功能继承下来继续发展
- 多态性:方法的重载本身就是一个多态性的体现
1.3、类 && 对象的创建
1.3.1、 类与对象的关系
- 类:是一个 具有 共性 的东西。 例如: 制造汽车的图纸,规定了必须有四个 车轱辘。
- 对象:是一个具有 个性 的东西。 例如:根据 上面的图纸,我加入自己的个性,制造出方形、三角形的车轱辘。
1.3.2、 类的创建格式
首先类由 属性和方法组成。
class 类名称 {
①成员属性 例如 上面提到的 四个车轱辘 ,就是属性
②成员方法 例如 根据上面图纸制造的车,可以水陆两用 、刹车、加速(车的行为)
③ 其他(这里先不介绍,后面进阶、高阶在介绍;先重点掌握上面两点)。
}
=========================================================================
①成员属性 在类里面的定义格式:
数据类型 属性名 int age ; 数据类型 属性名 = 初始化值 int age =18
②成员方法 在类里面的定义格式:
权限修饰符 返回值类型 方法名(形式参数列表) {
//方法体
//return 返回值;
}
1.3.3、 类创建涉及的问题
- 类必须编写在。java文件中
- 一个.java文件中,可以存在N个类,但是只能存在一个public修饰的类。
- java文件的文件名称必须与public修饰的类名完全一致;
1.3.4、对象的创建 && 对象的使用
一个类要想真正的进行操作,则必须依靠对象,对象的定义格式如下:
类名 对象名 = new 类名() ; 要创建多个 对象时,切记名字 不能重复!
如果要想访问类中的属性或方法,即对象使用:
访问类中的属性: 对象.属性 ;
- 可以获取该属性的值(创建类时,定义好初始值)。
- 也可以通过赋值,重新定义 该对象专属的属性值 。
调用类中的方法: 对象.方法(实际参数列表) ;
- 这里 的参数列表,根据 类中方法 是否定义有;如果没有,则不用传参
1.4、栈 && 堆
1.4.1 栈
Java栈的区域很小 , 大概2m左右 , 特点是存取的速度特别快 栈存储的特点是, 先进后出 。 存储的是:
- 基本数据类型的数据
- 引用数据类型的引用!
例如: int a =10; Person p = new Person();
10存储在栈内存中 , 创建的对象的引用(p)存在栈内存中
1.4.2、堆
堆存放的是类的对象 . Java是一个纯面向对象语言, 限制了对象的创建方式:
所有类的对象都是通过new关键字创建
new关键字, 是指告诉JVM , 需要明确的去创建一个新的对象 , 去开辟一块新的堆内存空间。
堆内存与栈内存不同, 优点在于我们创建对象时 :
- 不必关注堆内存中需要开辟多少存储空间 。
- 也不需要关注内存占用 时长 。
- 堆内存中内存的释放是由GC(垃圾回收器)完成的 。
- GC 回收堆内存的规则: 当栈内存中不存在此对象的引用时,则视其为垃圾 , 等待垃圾回收器回收
1.4.3、 栈与堆的实例演示
通过上述,栈与堆的概念理解,可能还不足以让大家理解清楚。下面举一个实例来讲解。 先上代码
public class Demo3 { public static void main(String[] args) { Book b1 = new Book(); b1.name = "金苹果"; b1.info = "讲述了果农辛勤种植金苹果的过程。"; Book b2 = new Book(); b2.name = "银苹果"; b2.info = "讲述了果农辛勤种植银苹果的过程。"; b2 = b1; b2.name = "铜苹果"; b1.say(); } } class Book{ String name; String info; void say() { System.out.println("书名:"+name+" , 简介:"+info); } }
输出结果为:
书名:铜苹果, 简介:讲述了果农辛勤种植金苹果的过程。
以下面的图来解析:
- 流程按 ① ② ③ ④顺序走。
- 若有多个 ①,则表示 同一时间进行的操作。
由上图可知,在程序执行结束之后:
- 堆中0x14这个位置 的Book()对象,在栈中没有人引用它。
- GC发现时(随机性,不确定什么时候发现噢),就会把这个0x14位置的Book()对象清理掉。
1.5、 构造方法 && 方法、构造方法重载
1.5.1、构造方法介绍
定义的格式:
- 与普通方法基本相同, 区别在于: 方法名必须与类名相同, 没有返回值类型的声明 !
作用: 用于对象初始化。
执行时机: 在创建对象时,自动调用
特点:
- 所有的Java类中都会至少存在一个构造方法,如果一个类中没有明确的编写构造方法, 则编译器会自动生成一个无参的构造方法, 构造方法中没有任何的代码!
- 如果自行编写了任意一个构造器, 则编译器就不会再自动生成无参的构造方法。
public class Demo3 { public static void main(String[] args) { Person p = new Person(); // 这里就的Person() 其实就相当于 调用了构造方法 p = new Person(); p = new Person(); p = new Person(); } } /** *无参构造方法 这是自己定义的 一个无参构造方法 *如果 没自己定义,系统就会生一个 这种无参构造方法,但没有下面的 输出语句。 */ class Person { public Person() { System.out.println("对象创建时,此方法调用"); } }
可以看到上面的例子中,我们自己定义了一个无参构造方法,然后在创建对象时 "new Person()",就相当于在调用 构造方法;如果你只定义了一个 有参数的构造方法,那么在创建对象时,也就是"new Person()"里的括号里面要写入 参数实际值。不然会报错。且这个参数,可以直接赋值给类中的属性。
所以通常我们会自己同时定义一个无参构造方法和一个全参的构造方法,方便我们创建对象时,好操作!(构造方法,不是要与类名相同吗!!!那这建造了两个构造方法,不就会重名了吗? 能想到这个问题,说明大家理解了构造方法的定义,但是可以明确的说,这样操作,不会出错,这涉及到 构造方法的重载,后面会提到的)。
- 建议自定义无参构造方法,不要对编译器形成依赖,避免错误发生。
- 当类中有非常量成员变量时,建议提供两个版本的构造方法,一个是无参构造方法,一个是全属性做参数的构造方法。
- 当类中所有成员变量都是常量或者没有成员变量时,建议不提供任何版本的构造。
1.5.2、方法重载
方法重载:就是一个类中,有两个或两个以上的相同方法名的 方法。
方法重载有什么用:
- 可以让我们在不同的需求下, 通过传递不同的参数调用方法来完成具体的功能。
方法重载的条件:
- 方法名称相同,
- 参数类型 或者 参数长度 或者 参数顺序 不同,
- (ps:与返回值类型 无关 )
package com.kaikeba.demo2; public class Demo5 { public static void main(String[] args) { Math m = new Math(); int num = m.sum(100, 500); //传入整型参数时,ta就会自动匹配到 下面的 ①方法 System.out.println(num); double num2 = m.sum(10.5, 20.6); //传入整型参数时,ta就会自动匹配到 下面的 ②方法 System.out.println(num2); } } /** * 利用构造方法,可以根据我们自身需要,去传入不同类型的参数,完成计算。 */ class Math{ int sum(int x,int y) { //① 方法。 int z = x+y; return z; } double sum(double x,double y) { // ② 方法。 double z = x+y; return z; } }
1.5.3、构造方法重载
构造方法重载,其实跟 前面提到的 方法重载是一样的。
一个类, 可以存在多个构造方法 : 参数列表的长度或类型不同即可完成构造方法的重载
构造方法的重载 ,可以让我们在不同的创建对象的需求下, 调用不同的方法来完成对象的初始化!
package com.kaikeba.demo2; public class Demo6 { public static void main(String[] args) { Person3 p = new Person3("张三",18); p.say(); Person3 p2 = new Person3("李四"); //可以看到,这里传入了 一个参数,所以ta会自动匹配 ②方法 p2.say(); //这里没有 给age赋值,且age为引用类型数据,默认值为0,不报错 } } class Person3{ Person3(String name2,int age2){ //构造方法 ① name = name2; age = age2; } Person3(String name2){ //构造发布方法 ② name = name2; } String name; int age; void say() { System.out.println("自我介绍: 姓名:"+name+", 年龄:"+age); } }
1.6、 匿名对象
匿名对象:就是 没有对象名 的 对象 。
在前面的 栈和堆里面,我们知道,b1是对象Book()的 引用名,也就是对象名,ta建立在栈中,记录了该对象Book()在堆里面的地址。
- 匿名对象,没有引用名;也就不会在栈里面记录下该对象在堆里的地址;
- 匿名对象,直接在堆里面,创建空间;
- 没有栈记录堆的地址(没有对象名),匿名对象因此只能使用一次,之后就会被GC视为垃圾,等待回收。
Book b1 = new Book(); //非匿名 b1.name = "金苹果"; new Book().name="金苹果"; //匿名对象
匿名对象常见错误,看下面代码:(下面调用了.say()方法,最终结果会输出什么??)
/** * 这是前面 栈&&堆 的例子 * Book b1 = new Book(); * b1.name = "金苹果"; * b1.info = "讲述了果农辛勤种植金苹果的过程。"; */ new Book().name= "金苹果"; new Book().info="讲述了果农辛勤种植金苹果的过程。"; new Book().say()
结果就是 "书名:null , 简介:null"
原因很好理解,其实就是因为:匿名对象只能使用一次!!!上面三行代码一点关联都没有。相当于 各自创建了 自己的匿名对象(在堆里,各自开辟了 各自的空间)
- 第一个匿名对象,给自己的 .name属性赋值为"金苹果"
- 第二个匿名对象,给自己的.info属性赋值为"讲述了果农辛勤种植金苹果的过程。"
- 第三个匿名对象调用了.say()方法
其余,没提到的属性,就是没有赋值。所以.say()方法,打印出来的结果就是 "书名:null , 简介:null"。
而且,上述的三个匿名对象,在调用结束后,就会被GC视为垃圾,等待回收。(就是释放他们,在堆里占据的空间)
二、面向对象——进阶
=========================================================================
2.1、封装
什么是封装?封装有什么用?怎么去封装呢? 封装后怎么去使用这个类?下面来了解一下:
1. 什么是封装?
封装,简单来说,就是 不让外界去 直接访问或修改 类里面的属性(这个外界就是,这个类以外的地方) (说的是属性,类的方法还是可以直接使用的) 通过下面的代码来讲解。
public class Demo3 { public static void main(String[] args) { Book b1 = new Book(); /** * 第二步,这里在Book类外面, 创建了一个对象;然后去给 name、info两个属性赋值。 * 这时候 赋值 就会出错,因为Book()类已经封装了,其内部属性,不容外界修改或赋值!! */ b1.name = "金苹果"; b1.info = "讲述了果农辛勤种植金苹果的过程。"; } } /** * 第一步:假设Book这个类,已经 封装好了(实际还没封装!!); * 这个类里面 有name、info两个属性 */ class Book{ String name; String info; void say() { System.out.println("书名:"+name+" , 简介:"+info); } }
2.封装有什么用?
封装的意义在于:
- 保护或者防止代码(数据〉被我们无意中破坏。
- 保护成员属性,不让类以外的程序直接访问和修改;
举个例子:
在上面代码中,Book()类里面,再添加一个出版年份 “year”的属性(int型);然后,在创建对象之后,通过下面语句,对该属性进行赋值:
" b1.year = -1000 "
大家,说这行代码有错误吗?
其实,从代码上看,是没有逻辑错误的;但是,从实际来看,有哪一本书的出版年份是 “ -1000 ” 的??? 所以,这句赋值代码,纯纯是在乱搞,完全违背了Book()类的定义该属性的意义。
为了防止这种情况,Java就引进了 " 封装 "。不让外界,直接访问或赋值类中定义的属性。(就是不让 别人 随意赋值、调用)
3. 怎么去封装?
在了解 什么是封装,封装有什么用之后,下面开始讲解怎么去封装:
以上面的代码为列,给Book()类的两个属性(name、info),前面添加了一个"private",这英文顾名思义 "私有的"。
class Book{ private String name; private String info; void say() { System.out.println("书名:"+name+" , 简介:"+info); } }
"private",就表示两个属性是Book类独有的,没经过Book类的同意,别人不能随便使用!!! 所以,添加了这个“private”之后,你会发现
b1.name = "金苹果";
b1.info = "讲述了果农辛勤种植金苹果的过程。这两行代码,就会报错。
那封装之后,这个类怎么去使用呢?我们下面来介绍一下:
4.封装之后如何使用?
在完成封装之后,类里面的属性,我们不能直接调用,也不能赋值。但是我们还可以直接去调用类里面的方法啊!!!
就利用这个特性,在每封装一个属性之后,我们就会在这个类里面写两个关于这个属性的的方法。以下面代码为列:
package tasktest; public class Demo3 { public static void main(String[] args) { Book b1 = new Book(); b1.setName("Java养成记"); //给 b1这个对象 的name属性 赋值 System.out.println(b1.getName()); //调用b1 的name属性 } } class Book{ private String name; private String info; void setName(String myName) { //设置name 属性 name = myName; // myName,由上面调用 该方法时,传入的值; 然后再赋值给 “name” } String getName() { //获取 name 属性 return name; } void say() { System.out.println("书名:"+name+" , 简介:"+info); } }
可以看到,针对name属性,我们添加了一个 setName(String myName) 和 getName()两个方法。然后,在创建对象时,通过这两个方法去给这个对象的属性进行赋值和调用!!!
细心的小伙伴,可能已经发现,如果 “出版年份--year” ,通过setYear()方法赋值"-1000",那最终结果,不和之前一样吗?
诶,您厉害,道理是这样!!!,所以可以在setYear()方法里面,添加一些if条件语句,对接收到的参数进行判断,合理的年份,就正常赋值;不合理的年份,就打印提示信息、赋予默认值等等操作(根据自身需求,设计!!!)
封装原则:
隐藏对象的属性和实现细节,仅对外公开访问方法,并且控制访问级别
2.2、this关键字
在Java基础中,this关键字是一个最重要的概念。使用this关键字可以完成以下的操作:
- · 调用类中的属性
- · 调用类中的方法或构造方法 (在一个构造方法中,利用this()方法调用另一个构造方法时,this()语句必须写在第一行)
- · 表示当前对象
解析:
调用类中的属性
以2.1章节中的Book()类代码为列,setName()方法中" name = myName " 的本意,是把 对象调用该方法时传入的值 赋值给Book()类中的name属性。但是,其实这样写,还是有一点点的小问题。准确来说,应该使用" this.name = myName"。再后面我们为了追求,setName()方法中形参名,可以见名知意,可以直接如下面代码所示:
void setName(String name) { this.name = name ; }
调用类中的方法或构造方法
Book() { this("默认书名","默认简介"); System.out.println("利用this()调用下面的全参构造方法时,this()必须放在第一行" ); } Book(String name,String info) { this.name = name; this.info = info; }
表示当前对象
Book b1 = new Book(); Book b2 = new Book(); b1.say(); b2.say(); /** *这两句,都是调用了各自的say()方法。 *就相当于 say()方法中,("书名:"+b1.name+" , 简介:"+b1.info); * 就相当于告知 系统 ,调用的b1的name、b1的info * 就相当于告知 系统 ,调用的b2的name、b2的info */ //Book()类中的 say()方法 void say() { System.out.println("书名:"+this.name+" , 简介:"+this.info); }
2.3、static关键字
static表示“静态”的意思,可以用来修饰成员变量和成员方法(后续还会学习 静态代码块 和 静态内部类)。
static的主要作用:创建独立于具体对象的域变量或者方法 :
- 被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以 通过类名去进行访问。(也可以通过对象名,去访问、赋值、修改值,不过一般用类名)
- 并且不会因为对象的多次创建 而在内存中建立多份数据
解析: static修饰 类里面的属性(即成员变量):
相当于,这个类 的多个对象,该属性相同,例如前面Book()类里,如果添加了一个year(出版年份)的属性,
解析: static修饰 类里面的方法:
使用static修饰的 方法,可以直接通过 类名.方法名 去使用;( 也就意味着,static修饰的方法,被调用时,对象可能没有被创建 )。
总结:
- 1. 静态成员 在类加载时加载并初始化。
- 2. 无论一个类存在多少个对象 , 静态的属性, 永远在内存中只有一份( 可以理解为所有对象公用 )
- 3. 在访问时: 静态不能访问非静态 , 非静态可以访问静态 !
static关键字 与 封装private关键字,无冲突!!!可以一起使用,修饰类中属性!!!
2.4、权限修饰符
修饰符 类 包 子类 其他包 public √ √ √ √ protected √ √ √ × default √ √ × × private √ × × ×
2.5、代码块
普通代码块
在执行的流程中出现的代码块,我们称其为普通代码块。
构造代码块
在类中的成员代码块,我们称其为构造代码块,在每次对象创建时执行,执行在构造方法之前。
静态代码块
在类中使用static修饰的成员代码块,我们称其为静态代码块,在类加载时执行。每次程序启动到关闭,只会执行—次的代码块。
同步代码块
在后续多线程技术中学习。
代码块的执行顺序:
静态代码块-->构造代码块-->构造方法
三、面向对象——高阶
=======================================================================
3.1、继承
继承:
- 继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。
- 继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方 法,或子类从父类继承方法,使得子类具有父类相同的行为。
格式:
lass 父类{}
class子类extends父类{}
继承的限制:
Java中只有单继承,多重继承﹐没有多继承。
子类实例化的内存演示:
在堆中,先创建 该类 的父类;再创建该类,然后该类里 包含一个 “super”属性,这个属性的值 就是 父类在堆的地址。
调用方法时,先查找该类里面有没有该方法,如果没有就去父类里面找。
如果有就执行该类里面的方法(还是方法重写)
父类里面 私有的属性、方法;子类不能调用(private)
3.2、super()
super:
- 通过super ,可以访问父类构造方法 (默认无参构造方法)
调用super构造方法的代码,必须写在子类构造方法的第一行。
- 通过super ,可以访问父类的属性
- 通过super ,可以访问父类的方法
class student extends Person{ public student( ) { super("无名称",1); //调用父类构造方法,默认无参,但这里调用了全参构造方法 super.sex ="男"; //调用父类属性 super. setName("哈哈哈"); //调用父类方法 } }
3.3、重写 && 重写和重载的区别
重写(override)规则:
- 参数列表必须完全与被重写方法的相同;
- 返回类型必须完全与被重写方法的返回类型相同;
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected.
- 父类的成员方法只能被它的子类重写。
- 声明为static和privete的方法不能被重写,但是能够被再次声明。
ava中重写(override)与重载(Overload)的区别:
1、发生的位置
- 重载:一个类中
- 重写:子父类中
2、参数列表限制
- 重载:必须不同的
- 重写:必须相同的
3、返回值类型
- 重载:与返回值类型无关
- 重写:返回值类型必须一致
4、访问权限:
- 重载:与访问权限无关
- 重写:子的方法权限必须不能小于必父的方法权限
5、异常处理:
- 重载:于异常无关
- 重写:异常范围可以更小,但是不能抛出新的异常。
3.4、final关键字
final关键字:
- final用于修饰属性、变量。
变量成为了常量,无法对其再次进行赋值。
final修饰的局部变量,只能赋值一次(可以先声明后赋值)
final修饰的是成员属性(成员变量),必须在声明时赋值。
全局常量(public static final )
- final用于修饰类
final修饰的类,不可以被继承,(没有子类)
- final用于修饰方法
final修饰的方法,不能被子类重写。
.
3.5、抽象类
抽象类必须使用abstract class声明
一个抽象类中可以没有抽象方法。
抽象方法必须写在抽象类或者接口中。
格式:
abstract class 类名 { // 抽象类
}
public String tostring( ) { return "Person [name=" + name + ", age=" + age + "]"; } public boolean equals(object o) { if(this == o) { return true; } if(o == nul1) { return false; } if(o instanceof Person) { Person p2 = (Person) o; if(this.name.equals(p2.name) && this.age == p2.age) //相同 return true; }else { return false; }else { return false; } }
成员内部类
局部内部类
匿名内部类 (属于局部内部类的一种)
静态内部类(与普通 成员内部类 很像,前面多了一个关键字static。)
包装类: