面向对象究竟学什么?
①学习如何获取已有的对象并使用;
②如何自己设计对象并使用;
③如何自己设计一个继承结构并使用;
④如何使用多态。
封装
将数据和对数据的操作封装起来,对外隐藏数据,只提供一些的方法。
如果一个需求中有多个事物,不知道哪些行为该归属到哪个事物中该怎么办呢?(或者说如何设计一个类呢?)
(这一部分先了解吧,因为没有代码积累,再看也没什么感悟)
对象代表什么,就得封装相应的数据,并为数据提供相应的行为。
类如果用来描述一个事物,自然要提供这个事物所能完成的各种功能。最简单的就是,比如说要设计一个手机类的话自然要考虑手机所能完成的各项功能。
(1)比如人画圆这个事,肯定有人和圆两个类,那么画圆这个行为是封装到人这个类还是圆这个类中呢?如何去体现面向对象的思想呢?
其实我们要提供对圆这个对象操作的各种方法,这个是圆的类去做的。所以人画圆这件事就只需人创建一个圆的对象然后调用圆对象的画圆方法就可以了。
(2)比如String类中对字符串的各种操作,为什么会有这些操作?因为我们关注的是字符串这个对象,然后自然要考虑各种对字符串的操作,比如截取、替换等等。
static
1、静态变量
访问方式:类名.静态变量,如Student.teacherName。
(1)说为什么会出现static关键字?
有一些属性的值对于所有对象来说都是相同的,是所有对象共享的。
(2)特点:
随着类的加载而出现,是优于对象的。
(3)内存原理:
静态区:存类中所有的静态变量;当需要某个静态变量的值时就去静态区中找。
2、静态方法
与静态成员变量一样,静态方法也是通过类名.方法名称访问。
主要是工具类的应用。
3、注意事项
都是对于一个类中的静态和非静态来说的:
(1)静态方法中只能访问类中的静态变量和静态方法;
(2)非静态可以访问所有 。
类
1、类的组成
(1)总的来说,类是描述对象的模板。
(2)类包括变量、方法(也叫属性和行为)、构造方法、代码块和内部类5部分,其中变量又分为实例变量和类变量,方法分为实例方法和类方法,如下图所示:
其中第三行的每一个也有别的名称:
实例变量 = 成员变量 = 字段
类变量 = 静态变量
实例方法 = 成员方法
类方法 = 静态方法
(3)构造方法
①调用时机:在创建对象的时候,由虚拟机调用,不能自己调用。
②类中一定要有构造方法。
如果没写,虚拟机会自动提供一个空参构造方法;
如果自己写了构造方法,创建对象的时候就只能按照自己定义的构造方法来用,否则会报错。
③构造方法的重载:即一个类中可以定义多个方法名相同但参数列表不同的构造方法。
下面这个代码是一个类的例子:
public class Phone {
//成员变量(属性),也叫实例变量
String name;
int price;
//静态变量,也叫类变量
static String teacherName;
//空参的构造方法
public Phone(){
}
//有参的构造方法
public Phone(String name, int price){
this.name = name;
this.price = price;
}
//成员方法(行为),也叫实例方法
public void call(){
System.out.println("打电话");
}
//静态方法,也叫类方法
public static void method(){
}
}
2、三种类
类分为测试类,JavaBean类和工具类。
(1)标准的JavaBean类
描述一种事物的类,如Student,Teacher,Dog,Cat等,包括以下内容:
①类名见名知意,符合标准的大驼峰命名;
②私有化成员变量;
③至少提供空参和全参两个构造方法;
④对于每一个私有化成员变量需要提供对应的setXxx和getXxx方法,如果有额外的行为也写上。
如何对成员变量赋值?
有两种方式:
①使用有参构造函数赋值;
②使用对应的setXxx和getXxx方法赋值。
(2)测试类
用来测试其他类的代码是否正确的类,通常带有main方法,是程序的入口。
(3)工具类
帮我们做一些事情的,不是用来描述事物的。
1、类名见名知意,符合标准的大驼峰命名,如操作数组的工具类ArrUtil;
2、私有化构造方法,那么外界就无法创建它的对象,因为创建对象没有什么实际意义。
3、提供各种静态方法。
Tips:alt+鼠标左键进行批量修改。
继承
为了提高代码的复用性,提出了继承。以前只学习了方法的复用,继承是类层次的复用。
封装是描述一个类的,继承使类与类之间有了关系。
父类又叫基类(Base)和超类(Super);子类又叫派生类。
1、特点
(1)Java只能单继承:一个类只能继承于一个直接父类;
(2)不支持同时继承多个直接父类,但支持多层继承,即A继承于B,B继承于C。
(3)Java中的每个类都直接或间接继承自Object类。
一个类中有构造方法、变量和方法3部分,当有两个类有继承关系时,子类可以继承父类的哪些内容?又可以直接访问父类的哪些内容呢?
(1)构造方法
无论父类的构造方法是否私有,子类都不可以继承;子类可通过super关键字直接访问父类的构造方法。
子类继承了父类中的变量就要对其初始化,所以子类的构造函数中一定会调用父类的构造方法,那么是如何调用的呢?
首先如果子类没手动调用的话,那么会隐式提供super()去调用父类的空参构造方法,这行代码也可以手动写上去。这种方式不会报错的前提是父类中要存在空参构造方法,不管这个空参是自己写的还是虚拟机提供的。所以一般类中要提供一个空参构造方法。
但是如果父类中只有自己定义的有参构造方法,那么上述方式就会报错,所以这种情况下子类一定要显示调用父类的构造方法且放在代码的第一行。
(2)变量
父类中的private成员是被继承到子类中的,即当创建一个子类的对象时,private成员在物理空间上是存在的,但是子类的对象无法直接访问,可以使用对应的setXxx和getXxx方法进行访问。
(3)方法
虚方法:非private、非static、非final修饰的方法。
把类中的虚方法抽取到一个表中,起名虚方法表。
当继承时,子类会把自己类中的虚方法添加到父类的虚方法表形成自己的虚方法表。
添加到虚方法表中的方法子类都可以直接访问。
总结来说就是子类可以直接访问父类中的非私有的属性和行为。
2、方法的重写
(1)当父类的方法不能满足子类的需求时,子类可以根据需求重写此方法。
加上@Override注解,可以检查重写的语法是否正确,同时提高代码的可读性。
(2)要求:
①子类中重写的方法名和形参列表与父类的一定要完全相同。
②只有被添加到虚方法表中的方法才能被重写。
(3)方法重写的本质:
子类重写的方法将父类提供的原来的虚方法进行覆盖了,所以子类的虚方法表中只有重写的方法。
(4)子类重写方法的时候有时候需要调用父类中的此方法,有的则不用,这是与具体的需求有关。
总体来说,继承可以实现代码的复用和功能的扩展。
继承性允许在构成软件系统的层次结构中利用已经存在的类并扩充它们,以支持新的功能。这使得编程者只需要在新类中定义已经存在的类中所没有的成分来建立新类,从而大大提高了软件的可重用性和可维护性。
就近原则和this、super关键字
1、就近原则
下面是一个例子:
总的来说,当在子类中使用一个变量时,先到局部中去找,没有则去成员变量中找,再没有则会去父类中找,总之就是一级一级去找。
问题1:在子类中当同名时怎么区分这三个变量呢?
可使用this、super进行区分。
问题2:当类与类之间有继承关系时,它们之间的成员变量和成员方法可能会存在同名的,那么通过对象访问时访问的是哪个类中的呢?
首先说明,当重名时成员变量不能进行覆盖父类的,两个会同时存在;而成员方法会覆盖父类的,只剩下子类自己重写的。
1、首先是成员变量的访问,有直接和间接两种访问方式。
(1)当通过对象.直接访问成员变量时,使用new关键字创建对象的时候等号的左边是哪个类,就使用哪个类的成员变量。
比如:
①Father f = new Child();,当System.out.println(f.value);时输出的是堆空间中父的value;
②Child c = new Child();,当System.out.println(c.value);是输出的是子中的value。
但是这种情况比较少见,因为类中属性一般是私有化的,不能直接访问。
(2)当通过方法访问变量时,方法是在哪个类中定义的,就是使用哪个类中的变量。
若子类只是继承了父类中的方法,没有重写,则该方法仍然是属于父类的,则通过对象调用方法使用的是父类中的变量,若子类重写覆盖则这个方法就变成子类的了,则使用的是子类中的变量。
2、方法的访问同(2)
2、this关键字
(1)原理:在方法的定义中会有一个隐藏的形参this,在调用此方法时由虚拟机赋值,传的是方法调用者的地址。
所以this中存的是方法调用者的地址。
(2)当成员变量和局部变量同名时:用this关键字区分成员变量和局部变量。
(3)this也可以访问本类的其他构造方法。
3、super关键字
(1)访问父类成员变量和成员方法。
(2)访问父类构造方法。
抽象
1、抽象方法
(1)为什么会出现?
①当父类中方法的方法体不知道怎么写时,可以使用abstract关键字定义为抽象方法,便可以不用写方法体了,但是仔细一想,不使用abstract关键字,其实普通的成员方法的方法体是空的一样可以满足要求,所以为什么要额外提出抽象方法呢?②是因为子类可能会忘记重写此方法,不安全,或者父类中就不写这个成员方法,由子类去写,这样不也是可以的吗?③这样无法限定子类书写的格式,不好,所以这样其实是一种规范,防止子类瞎写。
所以提出abstract关键字,用此关键字修饰的方法子类必须按照一定的格式进行重写,否则会报错。
2、抽象类
(1)当类中存在抽象方法时此类必须定义为抽象类。
(2)继承此类的话一定要重写抽象方法,若没有重写的话,则该类还是抽象类。
(3)抽象类不能实例化。
(4)抽象类中也可以没有抽象方法,当一个类不想让外界创建它的对象时可以将此类定义为抽象方法。
(5)抽象类中可以有构造方法,因为可能需要对父类中的成员变量初始化。
接口
接口是一种规则,侧重行为。
1、接口中成员的特点
在JDK7,包括JDK7之前,接口中的只有包含:抽象方法和常量。
(1)常量
默认修饰符:public static final,不写也不报错,会自动加上。
final接口中的方法代表一种规则,不希望被改变;
static方便使用类名调用;
public使得在所有的地方都可以使用。
(2)构造方法
接口不能实例化,且只有常量,不会被修改,所以干脆不提供构造方法。
(3)方法
默认修饰符:public abstract
只能是抽象方法。
一个类可以同时实现多个接口,要重写所有接口中所有的抽象方法。
如果同时实现的多个接口中有重名的抽象方法怎么办?
只要重写一次就可以,即表示重写了接口1中的,也重写了接口2中的。
接口和接口之间的关系:既可以单继承,也可以多继承。
接口还可以继承?
如果类实现了最后的子接口,那么要实现继承体系中所有的抽象方法。
在JDK8时新增了有方法体的方法,即默认方法和静态方法。
(1)默认方法
①修饰符:public default,public可以省略,defaulet不可以。
②为了解决接口升级问题:当需求增加时,只要接口里增加了新的规则,那么所有实现类必须改变,否则就会报错。
为了避免这种情况,那我在接口中写有方法体的方法实现类就不会报错了。
子类可不必进行重写,若要重写去需要去掉default关键字。
③如果实现了多个接口,多个接口中存在相同名字的默认方法,那么子类必须强制重写。
因为会不知道调用哪个接口中的默认方法,重写之后就直接调用实现类中重写的方法。
(2)静态方法
①修饰符:public static,public可以省略,static不可以。
②只能由接口.进行调用,不能由实现类调用。
③静态方法不能被重写。
在JDK9中可以定义private修饰的方法
为了解决JDK8中增加的默认方法的代码重复问题,将其抽取为一个私有方法。
分为普通私有方法(不加default关键字)和静态私有方法。
2、接口的应用
(1)多个类可能用到的方法定义为接口。
(2)接口的多态:接口的引用指向接口的实现类的对象。
3、适配器设计模式
实现所有的抽象方法,方法体为空,然后真正的实现类再继承这个类就可以了,用到哪个方法则重写哪个类。在接口和实现类中见增加一个类。中间的实现类为了不让创建对象,定义为抽象类。
多态
为了解决继承和接口出现的对象的多种形态的代码冗余问题。
什么是对象的多种形态?
即一个对象小明,既有作为人的形态,也有学生的形态。
使用多态的方式创建对象:父类引用可以指向子类的对象。
1、多态的编译和运行
属性的多态和行为的多态,侧重行为的多态。
多态时成员变量和成员方法访问特点:
调用成员变量时:编译看左边,运行看左边;
调用成员方法时:编译看左边,运行看右边。
(1)若使用成员变量,在编译时会查看父类中有没有,如果没有编译失败;运行时即会使用父类的成员变量的值。
(2)成员方法编译看左边,编译的时候看父类中是否有,没有则报错;运行时会调用子类中继承或重写的成员方法。
2、好处
右边对象可以实现解耦,便于扩展和维护。
3、多态的一个应用
定义方法的时候,使用父类的对象作为形参,可以接收所有的子类对象,体现多态的便利性和扩展性。
4、弊端
由于编译会检查父类是否存在该方法,所以不能调用子类的特有功能。
解决方法:
向下转型,转为子类的对象就可以调用子类的特有功能了。
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
转换的时候不能瞎转,同级之间不能转型,否则容易报错,即ClassCastException,类型转换异常!所以在转换之前可以判断一下类型,使用instanceof关键字进行判断:
// 向下转型
// 如果a是Cat类型
if (a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
} else if (a instanceof Dog){
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse
}
在JDK14的时候提出了新特性,把判断和强转合并成了一行,代码如下:
//新特性
//先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
//如果不是,则不强转,结果直接是false
if(a instanceof Dog d){
d.lookHome();
}else if(a instanceof Cat c){
c.catchMouse();
}else{
System.out.println("没有这个类型,无法转换");
}
内部类
将一个类的定义写在另外一个类里面。
内部类是外部类的一部分,且单独时出现没有意义。
1、成员内部类
写在成员位置,属于外部类的成员,可以用权限修饰符。
那么这两个类中的成员访问特点?
(1)内部类中可以随意使用外部类中的成员变量;
(2)外部类无法直接访问内部类的成员,可以先创建一个内部类的对象,通过对象去访问。
怎么创建内部类对象呢?
Outer.Inner oi = new Outer().new Inner();
这里要先创建一个外部类对象,然后再创建内部类对象。
如果无法直接创建呢?
在外部类中定义一个成员方法,返回一个内部类对象。比如在ArrayList类中,就有这种思想的运用。
public Iterator<E> iterator() {
return new Itr();
}
//内部类Itr
private class Itr implements Iterator<E> {
当内部类和外部类中存在同名的变量时,在内部类中如何访问外部类中的成员?
Outer.this :外部类对象的地址值。
2、静态内部类
静态内部类和成员内部类类似,只是多了一个static关键字。
静态内部类中不允许访问外部类中的非静态成员。
3、局部内部类
定义在方法中的类,类似于局部变量,不能使用权限修饰符。
4、匿名内部类
格式:
new IAnimal(){
@Override
public void speak(){
System.out.println(speak);
}};
其实是创建了实现接口或者抽象的类的对象。
六、Java内存分配
- 方法区:用于存储类的结构信息,如类的成员变量、方法代码等。
- 堆:用于存储对象实例。所有通过 new 关键字创建的对象都在堆中分配内存。堆是 Java 中最大的一块内存区域,它被所有线程共享。
- 栈:每个线程都有一个独立的栈,用于存储局部变量、方法参数、调用栈等。栈中的数据是按照先进后出的方式进行管理。
- 本地方法栈:用于存储 Java 以外的本地方法的调用和执行。
- 程序计数器:用于记录当前线程执行的位置,也就是下一条要执行的指令。
- 运行时常量池:用于存储编译期生成的各种字面量和符号引用。
垃圾回收器会自动监测并回收不再使用的内存对象,释放内存资源,当一个对象没有被任何引用所指向时,就会被判定为垃圾对象,垃圾回收器会将其回收并释放内存。
Java的内存机制使得程序员无需手动管理内存资源,大大简化了开发过程,提高了代码的可靠性和安全性,同时,合理地使用内存和优化内存使用也有助于提升程序的性能。
说到内存有两个东西需要考虑:
1、这块内存的地址;
2、这块内存里存的东西。
变量里面存的是一个值。
基本数据类型的变量存的是内存里存的东西;
引用数据类型的变量存的则是内存的地址。
其实我觉得两者可以说是结合使用的,比如引用数据类型的变量引用的就是堆里面存储的基本数据类型变量的地址。
其实为什么有这两种我也不明白。