前面我们讲过数组,当有多个数组都需要遍历时,我们可以将遍历的代码封装到方法中,需要遍历时,就调用相应的方法即可,提高代码的复用性。在对数组遍历的基础上继续增加需求,比如获取最值,数值逆序等,同样需要将这些功能封装到相应的方法中。这样继续封装会发现方法越来越多,于是就想能不能将这些方法继续进行封装呢?通过前面的讲解我们知道类是可以存放方法的,所以,我们就考虑使用类封装来这多个方法,将来再做数组的操作时,不用去找具体的方法,先找到这个类,然后使用这个类中的方法。这就是面向对象思想的编程方式。
面向对象思想概述
面向对象是基于面向过程的编程思想
面向对象思想特点
是一种更符合我们思想习惯的思想
可以将复杂的事情简单化
将我们从执行者变成了指挥者
角色发生了转换
面向对象开发
就是不断的创建对象,使用对象,指挥对象做事情。
面向对象设计
其实就是在管理和维护对象之间的关系。
面向对象特征
封装(encapsulation)
继承(inheritance)
多态(polymorphism)
我们学习编程语言,就是为了模拟现实世界的事物,实现信息化。比如:去超市买东西的计费系统,去银行办业务的系统。
我们如何表示一个现实世界事物呢:
属性 就是该事物的描述信息
行为 就是该事物能够做什么
举例:学生事物
我们学习的Java语言最基本单位是类,所以,我们就应该把事物用一个类来体现。
类:是一组相关的属性和行为的集合
对象:是该类事物的具体体现
举例:
类 学生
对象 班长就是一个对象
类:可以理解为构造对象的一个蓝图或者模版,是抽象的概念
对象:是以类为模型创建的具体实例,是对类的一种具体化。
类与对象的关系如图
1:图纸就是类
2:每一个汽车就是一个个的对象
现实世界的事物
属性 人的身高,体重等
行为 人可以学习,吃饭等
Java中用class描述事物也是如此 成员变量 就是事物的属性
成员方法 就是事物的行为
定义类其实就是定义类的成员(成员变量和成员方法)
在类中的位置不同
成员变量 类中方法外
局部变量 方法内或者方法声明上
在内存中的位置不同
成员变量 堆内存
局部变量 栈内存
生命周期不同
成员变量 随着对象的存在而存在,随着对象的消失而消失
局部变量 随着方法的调用而存在,随着方法的调用完毕而消失
初始化值不同
成员变量 有默认的初始化值
局部变量 没有默认的初始化值,必须先定义,赋值,才能使用。
基本类型作为形式参数
引用类型作为形式参数
匿名对象:就是没有名字的对象。
是对象的一种简化表示形式
匿名对象的两种使用情况
对象调用方法仅仅一次的时候
作为实际参数传递
封装概述
是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
好处:
隐藏实现细节,提供公共的访问方式
提高了代码的复用性
提高安全性。
封装原则:
将不需要对外提供的内容都隐藏起来。
把属性隐藏,提供公共方法对其访问。
private关键字:
是一个权限修饰符。
可以修饰成员(成员变量和成员方法)
被private修饰的成员只在本类中才能访问。
private最常见的应用:
把成员变量用private修饰
提供对应的getXxx()/setXxx()方法
记住:
方法被哪个对象调用,this就代表那个对象
什么时候使用this呢?
局部变量隐藏成员变量
其他用法后面和super一起讲解
构造方法作用概述
给对象的数据进行初始化
构造方法格式
方法名与类名相同
没有返回值类型,连void都没有
没有具体的返回值
构造方法注意事项
如果你不提供构造方法,系统会给出默认构造方法
如果你提供了构造方法,系统将不再提供
构造方法也是可以重载的
成员方法其实就是我们前面讲过的方法
方法具体划分:
根据返回值
有明确返回值方法
返回void类型的方法
根据形式参数
无参方法
带参方法
类
成员变量
构造方法
无参构造方法
带参构造方法
成员方法
getXxx()
setXxx()
给成员变量赋值的方式
无参构造方法+setXxx()
带参构造方法
Student s = new Student();在内存中做了哪些事情?
1.加载Student.class文件进内存
2.在栈内存为s开辟空间
3.在堆内存为学生对象开辟空间
4.对学生对象的成员变量进行默认初始化
5.对学生对象的成员变量进行显示初始化
6.通过构造方法对学生对象的成员变量赋值
7.学生对象初始化完毕,把对象地址赋值给s变量
static关键字特点
随着类的加载而加载
优先于对象存在
被类的所有对象共享(这也是我们判断是否使用静态关键字的条件)
可以通过类名调用
static关键字注意事项
在静态方法中是没有this关键字的
静态方法只能访问静态的成员变量和静态的成员方法
所属不同
静态变量属于类,所以也称为为类变量
成员变量属于对象,所以也称为实例变量(对象变量)
内存中位置不同
静态变量存储于方法区的静态区
成员变量存储于堆内存
内存出现时间不同
静态变量随着类的加载而加载,随着类的消失而消失
成员变量随着对象的创建而存在,随着对象的消失而消失
调用不同
静态变量可以通过类名调用,也可以通过对象调用
成员变量只能通过对象名调用
public static void main(String[] args) {}
public 被jvm调用,访问权限足够大。
static 被jvm调用,不用创建对象,直接类名访问
void被jvm调用,不需要给jvm返回值
main 一个通用的名称,虽然不是关键字,但是被jvm识别
String[] args 以前用于接收键盘录入的
局部代码块
在方法中出现;限定变量生命周期,及早释放,提高内存利用率
构造代码块
在类中方法外出现;多个构造方法方法中相同的代码存放到一起,每次调用构造都执行,并且在构造方法前执行
静态代码块 在类中方法外出现,加了static修饰
在类中方法外出现,并加上static修饰;用于给类进行初始化,在加载的时候就执行,并且值执行一次。
继承概述
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
通过extends关键字可以实现类与类的继承
class 子类名 extends 父类名 {}
单独的这个类称为父类,基类或者超类;这多个类可以称为子类或者派生类。
有了继承以后,我们定义一个类的时候,可以在一个已经存在的类的基础上,还可以定义自己的新成员。
提高了代码的复用性
多个类相同的成员可以放到同一个类中
提高了代码的维护性
如果功能的代码需要修改,修改一处即可
让类与类之间产生了关系,是多态的前提
其实这也是继承的一个弊端:类的耦合性很强
Java只支持单继承,不支持多继承。
一个类只能有一个父类,不可以有多个父类。
class SubDemo extends Demo{} //ok
class SubDemo extends Demo1,Demo2...//error
Java支持多层继承(继承体系)
class A{}
class B extends A{}
class C extends B{}
子类只能继承父类所有非私有的成员(成员方法和成员变量)
其实这也体现了继承的另一个弊端:打破了封装性
子类不能继承父类的构造方法,但是可以通过super关键字去访问父类构造方法。
不要为了部分功能而去继承
我们到底在什么时候使用继承呢?
继承中类之间体现的是:”is a”的关系。
1:在父类中定义一个成员变量
2:在子类中定义一个成员变量和父类中成员变量名称不同,然后再在子类中定义一个方法去访问变量,发现变量名不同,访问非常简单
3:在子类中再定义一个成员变量,和父类中的成员变量名称一致,然后继续访问。发现访问的是子类的成员变量。
4:如果我要访问父类的成员变量该怎么办呢?通过回想this来引入super关键字
结论:
在子类方法中访问一个变量
首先在子类局部范围找
然后在子类成员范围找
最后在父类成员范围找(肯定不能访问到父类局部范围)
如果还是没有就报错。(不考虑父亲的父亲…)
用法(this和super均可如下使用)
访问成员变量
this.成员变量 super.成员变量
访问构造方法(子父类的构造方法问题讲)
this(…) super(…)
访问成员方法(子父类的成员方法问题讲)
this.成员方法() super.成员方法()
1:看程序写结果
class Test
{
private static int x = 10;
public void show(int x)
{
x++;
System.out.println(x);
}
public static void main(String[] args)
{
int x = 20;
Test t = new Test();
t.show(x);
}
}
结果:21
子类中所有的构造方法默认都会访问父类中空参数的构造方法
为什么呢?
因为子类会继承父类中的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化。
每一个构造方法的第一条语句默认都是:super()
如何父类中没有构造方法,该怎么办呢?
子类通过super去显示调用父类其他的带参的构造方法
子类通过this去调用本类的其他构造方法
本类其他构造也必须首先访问了父类构造
一定要注意:
super(…)或者this(….)必须出现在第一条语句山
否则,就会有父类数据的多次初始化
1:看程序写结果
class Fu{
public int num = 10;
public Fu(){
System.out.println("fu");
}
}
class Zi extends Fu{
public int num = 20;
public Zi(){
System.out.println("zi");
}
public void show(){
int num = 30;
System.out.println(num);
System.out.println(this.num);
System.out.println(super.num);
}
}
class Test {
public static void main(String[] args) {
Zi z = new Zi();
z.show();
}
}
class Fu {
static {
System.out.println("静态代码块Fu");
}
{
System.out.println("构造代码块Fu");
}
public Fu() {
System.out.println("构造方法Fu");
}
}
class Zi extends Fu {
static {
System.out.println("静态代码块Zi");
}
{
System.out.println("构造代码块Zi");
}
public Zi() {
System.out.println("构造方法Zi");
}
}
Zi z = new Zi(); 请执行结果。
A:静态随着类的加载而加载。
B:静态代码块 -- 构造代码块 -- 构造方法的执行流程
静态代码块 -- 构造代码块 -- 构造方法
C:只要有子父关系,肯定先初始化父亲的数据,然后初始化子类的数据。
结果:
静态代码块Fu
静态代码块Zi
构造代码块Fu
构造方法Fu
构造代码块Zi
构造方法Zi
3:面试题
class X {
Y b = new Y();
X() {
System.out.print("X");
}
}
class Y {
Y() {
System.out.print("Y");
}
}
public class Z extends X {
Y y = new Y();
Z() {
System.out.print("Z");
}
public static void main(String[] args) {
new Z();
}
}
【铺垫的小知识】:
第一个:成员变量有基本类型和引用类型的。
class Demo {
//基本类型
int x = 10;
//引用类型
Student s = new Student();
}
第二个:类的初始化过程
加载class文件
堆中开辟空间
变量的默认初始化
变量的显示初始化
构造代码块初始化
构造方法初始化
第三个:遇到extends,就要知道,先初始化父类数据,然后初始化子类数据。
分层初始化。
super在这里仅仅表示要先初始化父类数据。
1:在父类中定义一个成员方法
2:在子类中定义一个成员方法和父类中成员方法名称不同,然后在测试类中通过子类对象去访问方法,发现方法名不同,访问非常简单
3:在子类中再定义一个成员方法,和父类中的成员方法名称一致,然后继续访问。发现访问的是子类的成员方法。
4:如果我要访问父类的成员方法该怎么办呢?回想刚才提过的super关键字
结论:
通过子类对象去访问一个方法
首先在子类中找
然后在父类中找
如果还是没有就报错。(不考虑父亲的父亲…)
方法重写概述
子类中出现了和父类中一模一样的方法声明,也被称为方法覆盖,方法复写。
使用特点:
如果方法名不同,就调用对应的方法
如果方法名相同,最终使用的是子类自己的
方法重写的应用:
当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容。
方法重写的注意事项
父类中私有方法不能被重写
子类重写父类方法时,访问权限不能更低
父类静态方法,子类也必须通过静态方法进行重写。
final关键字是最终的意思,可以修饰类,成员变量,成员方法。
修饰类,类不能被继承
修饰变量,变量就变成了常量,只能被赋值一次
修饰方法,方法不能被重写
final关键字面试题
final修饰局部变量
在方法内部,该变量不可以被改变
在方法声明上,分别演示基本类型和引用类型作为参数的情况
1.基本类型,是值不能被改变
2.引用类型,是地址值不能被改变
final修饰变量的初始化时机
在对象构造完毕前即可
多态概述
某一个事物,在不同时刻表现出来的不同状态。
举例:
猫可以是猫的类型。猫 m = new 猫();
同时猫也是动物的一种,也可以把猫称为动物。
动物 d = new 猫();
在举一个例子:水在不同时刻的状态
多态前提和体现
有继承关系
有方法重写
有父类引用指向子类对象
成员访问特点
成员变量
编译看左边,运行看左边
成员方法
编译看左边,运行看右边
静态方法
编译看左边,运行看左边
所以前面我说静态方法不能算方法的重写
面试题:
1:看程序写结果(先判断有没有问题,如果没有,写出结果)
class Fu
{
public void show()
{
System.out.println("fu show");
}
}
class Zi extends Fu
{
public void show()
{
System.out.println("zi show");
}
public void method()
{
System.out.println("zi method");
}
}
class Test
{
public static void main(String[] args)
{
Fu f = new Zi();
f.method();
}
}
2.看程序写结果
class Fu
{
public void show()
{
System.out.println("fu show");
}
}
class Zi extends Fu
{
public void show()
{
System.out.println("zi show");
}
public void method()
{
System.out.println("zi method");
}
}
class Test
{
public static void main(String[] args)
{
Fu f = new Zi();
f.show();
}
}
3.:看程序写结果(先判断有没有问题,如果没有,写出结果)
class A
{
public void show()
{
show2();
}
public void show2()
{
System.out.println("我");
}
}
class B extends A
{
public void show2()
{
System.out.println("爱");
}
}
class C extends B
{
public void show()
{
super.show();
}
public void show2()
{
System.out.println("你");
}
}
public class Test
{
public static void main(String[] args)
{
A a = new B();
a.show();
B b = new C();
b.show();
}
}
多态的好处
提高了程序的维护性(由继承保证)
提高了程序的扩展性(由多态保证)
多态的弊端
不能访问子类特有功能
那么我们如何才能访问子类的特有功能呢?
多态中的转型
向上转型
从子到父
父类引用指向子类对象
向下转型
从父到子
父类引用转为子类对象
抽象类概述
我说动物,你知道我说的是什么动物吗?只有看到了具体的动物,你才知道,这是什么动物。 所以说,动物本身并不是一个具体的事物,而是一个抽象的事物。只有真正的猫,狗才是具体的动物。同理,我们也可以推想,不同的动物吃的东西应该是不一样的,所以,我们不应该在动物类中给出具体体现,而是应该给出一个声明即可。
在Java中,一个没有方法体的方法应该定义为抽象方法,而类中如果有抽象方法,该类必须定义为抽象类。
格式
abstract class 类名 {}
public abstract void eat();
抽象类不一定有抽象方法,有抽象方法的类一定是抽象类
抽象类不能实例化
那么,抽象类如何实例化呢?
按照多态的方式,由具体的子类实例化。其实这也是多态的一种,抽象类多态。
抽象类的子类
要么是抽象类
要么重写抽象类中的所有抽象方法
成员变量
可以是变量
也可以是常量
构造方法
有构造方法,但是不能实例化
那么,构造方法的作用是什么呢?
用于子类访问父类数据的初始化
成员方法
可以有抽象方法 限定子类必须完成某些动作
也可以有非抽象方法 提高代码复用性
abstract不能和哪些关键字共存
private 冲突
final 冲突
static 无意义
我们想想狗一般就是看门,猫一般就是作为宠物了,对不。但是,现在有很多的驯养员或者是驯兽师,可以训练出:猫钻火圈,狗跳高,狗做计算等。而这些额外的动作,并不是所有猫或者狗一开始就具备的,这应该属于经过特殊的培训训练出来的,对不。所以,这些额外的动作定义到动物类中就不合适,也不适合直接定义到猫或者狗中,因为只有部分猫狗具备这些功能。所以,为了体现事物功能的扩展性,Java中就提供了接口来定义这些额外功能,并不给出具体实现,将来哪些猫狗需要被培训,只需要这部分猫狗把这些额外功能实现即可。
接口用关键字interface表示
格式:interface 接口名 {}
类实现接口用implements表示
格式:class 类名 implements 接口名 {}
接口不能实例化
那么,接口如何实例化呢?
按照多态的方式,由具体的子类实例化。其实这也是多态的一种,接口多态。
接口的子类
要么是抽象类
要么重写接口中的所有抽象方法
成员变量
只能是常量
默认修饰符 public static final
构造方法
没有,因为接口主要是扩展功能的,而没有具体存在
成员方法
只能是抽象方法
默认修饰符 public abstract
类与类
继承关系,只能单继承,但是可以多层继承
类与接口
实现关系,可以单实现,也可以多实现。还可以在继承一个类的同时实现多个接口
接口与接口
继承关系,可以单继承,也可以多继承
成员区别
抽象类 变量,常量;有抽象方法;抽象方法,非抽象方法
接口 常量;抽象方法
关系区别
类与类 继承,单继承
类与接口 实现,单实现,多实现
接口与接口 继承,单继承,多继承
设计理念区别
抽象类 被继承体现的是:”is a”的关系。共性功能
接口 被实现体现的是:”like a”的关系。扩展功能
形式参数
基本类型
引用类型
返回值类型
基本类型
引用类型
链式编程
1:形式参数的问题在前面匿名对象的时候已经讲解过了。
但是,今天又多了抽象类和接口类型作为形式参数。
形式参数
类:需要的是该类的对象
抽象类:需要的是该抽象类的子类对象
接口:需要的是接口的子类对象
具体类作为形式参数:
class Student {
public void show() {
System.out.println(“show”);
}
}
class StudentDemo {
//如果参数是一个类名,那么实际需要的是一个具体的对象
public void method(Student s) {
s.show();
}
}
class StudentTest {
public static void main(String[] args) {
//如何调用StudentDemo中的method方法呢?
StudentDemo sd = new StudentDemo();
Student s = new Student();
sd.method(s);
}
}
抽象类作为形式参数:
abstract class Person {
public abstract void show();
}
class PersonDemo {
public void method(Person p) {
p.show();
}
}
//自己定义类继承自Person类
class PersonTest {
public static void main(String[] args) {
//如何调用PersonDemo中的method方法呢?
PersonDemo pd = new PersonDemo ();
Person p = new Student();
pd.method(p);
}
}
接口作为形式参数,类似抽象类作为形式参数。
2:返回值的问题
基本类型 返回什么就用什么接收。
引用类型
类:其实返回的是该类的对象
抽象类:其实返回的是该类的子类对象
接口:其实返回的是该接口的子类对象
具体类作为返回值类型:
class Student {
public void show() {
System.out.println(“show”);
}
}
class StudentDemo {
public Student getStudent() {
//Student s = new Student();
//return s;
return new Student();
}
}
class StudentTest {
public static void main(String[] args) {
//如何测试呢?
//原本我可以直接通过Student创建对象,调用功能
//但是现在不让这样做,怎么办呢?
StudentDemo sd = new StudentDemo();
Student s = sd.getStudent();
s.show();
}
}
抽象类作为返回值类型:
abstract class Person {
public abstract void show();
}
class PersonDemo {
public Person getPerson() {
Person p = new ???();
return p;
return new ???();
}
}
//自己定义类继承自Person类,否则PersonDemo代码无法完成?
class PersonTest {
public static void main(String[] args) {
//如何调用PersonDemo中的method方法呢?
PersonDemo pd = new PersonDemo ();
Person p = pd.getPerson();
p.show(); //其实调用的是Student的
}
}
接口作为返回值类型,类似抽象类作为返回值类型
3:链式编程的案例演示
new PersonDemo().getPerson().show();
包的概述
其实就是文件夹
作用:对类进行分类管理
包的划分:
举例:
学生的增加,删除,修改,查询
老师的增加,删除,修改,查询
以及以后可能出现的其他的类的增加,删除,修改,查询
基本的划分:按照模块和功能分。
高级的划分:就业班做项目的时候你就能看到了。
定义包的格式
package 包名;
多级包用.分开即可
注意事项:
package语句必须是程序的第一条可执行的代码
package语句在一个java文件中只能有一个
如果没有package,默认表示无包名
手动式
a:javac编译当前类文件。
b:手动建立包对应的文件夹。
c:把a步骤的class文件放到b步骤的最终文件夹下。
d:通过java命令执行。注意了:需要带包名称的执行
java cn.itcast.HelloWorld
自动式
a:javac编译的时候带上-d即可
javac -d . HelloWorld.java
b:通过java命令执行。和手动式一样
导包概述
不同包下的类之间的访问,我们发现,每次使用不同包下的类的时候,都需要加包的全路径。比较麻烦。这个时候,java就提供了导包的功能。
导包格式
import 包名;
注意:
这种方式导入是到类的名称。
虽然可以最后写*,但是不建议。
成员变量:
四种权限修饰符均可,final,static
我们自己定义:private居多
构造方法:
四种权限修饰符均可,其他不可
我们自己定义:public 居多
成员方法:
四种权限修饰符均可,fianl,static,abstract
我们自己定义:public居多
把类定义在其他类的内部,这个类就被称为内部类。
举例:在类A中定义了一个类B,类B就是内部类。
内部类的访问特点:
内部类可以直接访问外部类的成员,包括私有。
外部类要访问内部类的成员,必须创建对象
按照内部类在类中定义的位置不同,可以分为如下两种格式:
成员位置(成员内部类)
局部位置(局部内部类)
成员内部类
外界如何创建对象
外部类名.内部类名 对象名 = 外部类对象.内部类对象;
一般内部类就是不让外界直接访问的。
举例讲解这个问题:Body和Heart,电脑和CPU。
成员内部的常见修饰符
private 为了保证数据的安全性
static 为了让数据访问更方便
1.被静态修饰的成员内部类只能访问外部类的静态成员
2.内部类被静态修饰后的方法
a.静态方法
b.非静态方法
注意:
1:非静态的成员内部类,成员只能是非静态的。
2:内部类被静态修饰后的方法有静态和非静态之分。他们的访问和不用静态是不一样的。
访问非静态方法:外部类名.内部类名 对象名 = new 外部类名.内部类名();
访问静态方法:上面创建的对象访问,或者外部类名.内部类名.方法名();
补齐程序(注意:内部类和外部类没有继承关系)
class Outer {
public int num = 10;
class Inner {
public int num = 20;
public void show() {
int num = 30;
System.out.println(?);
System.out.println(??);
System.out.println(???);
}
}
}
在控制分别输出:30,20,10
答案:
class Outer {
public int num = 10;
class Inner {
public int num = 20;
public void show() {
int num = 30;
System.out.println(num);
System.out.println(this.num);
System.out.println(Outer.this.num);
}
}
}
class OuterDemo {
public static void main(String[] args) {
Outer.Inner oi = new Outer().new Inner();
oi.show();
}
}
局部内部类访问局部变量的注意事项:
必须被final修饰?
为什么呢?
因为局部变量会随着方法的调用完毕而消失,这个时候,局部对象并没有立马从堆内存中消失,还要使用那个变量。为了让数据还能继续被使用,就用fianl修饰,这样,在堆内存里面存储的其实是一个常量值。通过反编译工具可以看一下。
class Outer {
public void method() {
final int n = 100;
class Inner {
public void show() {
System.out.println(n);
}
}
Inner i = new Inner();
i.show();
}
}
class OuterDemo {
public static void main(String[] args) {
Outer o = new Outer();
o.method();
}
}
前提:存在一个类或者接口
这里的类可以是具体类也可以是抽象类。
格式:
new 类名或者接口名() {重写方法;}
本质:
是一个继承了类或者实现了接口的子类匿名对象
首先回顾我们曾经讲过的方法的形式参数是引用类型的情况,重点是接口的情况,我们知道这里需要一个子类对象。而匿名内部类就是一个子类匿名对象,所以,可以使用匿名内部类改进以前的做法。
abstract class Person {
public abstract void show();
}
class PersonDemo {
public void method(Person p) {
s.show();
}
}
class PersonTest {
public static void main(String[] args) {
//如何调用PersonDemo中的method方法呢?
PersonDemo pd = new PersonDemo ();
pd.method(new Person() {
public void show() {
System.out.println(“show”);
}
});
}
}