面向对象基础:
-
对象的进化史
-
基本数据类型阶段 数据少 无数据管理时代
-
数组 数据多了,将同类型的数据放到一起 弱管理
-
结构体 数据多了,数据复杂了。将不同类型的数据放到一起 强管理
-
对象 数据多了、类型复杂了、行为复杂了。将不同类型的数据放到一起 超强管理
-
前三个:面向过程 数据和行为是分开的 行为去组织数据
最后一个:面向对象 通过对象组织数据
对象和类:
对象是具体的,类是抽象的(类是对对象的共性的总结)。类也是一个模版,通过类这个模版我们也可以new对象。
对象分为两部分:
静态部分:属性→成员变量
动态部分:行为→方法
类是封装了对象的属性和行为的载体
定义类:
public class 类名 {
//属性
private(建议) 数据类型 属性名; //建议增加相应的getter、setter方法
//方法
(往往是对上面数据的操作,建议static修饰,方法很类似于面向过程中的函数。面向过程中,函数是最基本单位,整个程序由一个个函数调用组成。面向对象中,整个程序的基本单位是类,方法是从属于类和对象的
方法的调用方式:
对象名.方法名(实参列表)
return 语句终止方法的运行并指定要返回的数据
Java中进行方法调用中传递参数时,遵循值传递的原则(传递的都是数据的副本):
基本类型传递的是该数据值的copy值。
引用类型传递的是该对象引用的copy值,但指向的是同一个对象)
//构造方法
}
构造方法(作用:new对象和初始化属性):
-
方法名必须跟类名保持一致
-
无返回类型(返回本类的对象)
-
通过new来调用(实例化对象)
-
无参构造器问题:
-
如果我们没有手动定义构造器,系统会自动为我们添加一个无参的构造器
-
如果我们自己定义了构造器,系统就不会为我们添加无参构造器
-
-
构造方法的第一句总是:super(写不写都是这句),即调用直接父类的构造方法。
(若是构造方法的第一行代码没有显式的调用super(…)或者this(…))
- 有继承关系的构造方法调用的顺序
Dog—继承—>Animal—继承—>Object
流程就是:先向上追溯到Object,然后再依次向下执行(new)类的初始化块(构造方法用于对象的初始化!静态初始化块,用于类的初始化操作!)和构造方法,直到当前子类为止。
注:静态初始化块调用顺序,与构造方法调用顺序一样,上溯到Object类,先执行Object的静态初始化块,再向下执行子类的静态初始化块,直到我们的类的静态初始化块为止。
- 有参构造方法不能被自动调用,只能依赖super显式地调用。
方法的重载(Overload):
两同(同一个类、同一个方法名)三不同(参数列表不同:;类型、个数、顺序)
返回值不同,构成重载吗? No
形参名称不同,构成重在吗? No
内存分析:Java虚拟机的内存可以分为三个区域:栈stack、堆heap、方法区method area。
栈的特点如下:
1.
栈描述的是方法执行的内存模型。每个方法被调用都会创建一个栈帧(存储局部变量、操作数、方法出口等)
2.
JVM为每个线程创建一个栈(多个线程多个栈),用于存放该线程执行方法的信息(实际参数、局部变量等)
3. 栈属于线程私有,不能实现线程间的共享!
4. 栈的存储特性是“先进后出,后进先出”
5. 栈是由系统自动分配,速度快!栈是一个连续的内存空间!
堆的特点如下:
1. 堆用于存储创建好的对象(new执行完)和数组(数组也是对象)
2. JVM只有一个堆,被所有线程共享
3. 堆是一个不连续的内存空间,分配灵活,速度慢!
方法区(又叫静态区)特点如下:
1. JVM只有一个方法区,被所有线程共享!
2. 方法区实际也是堆,只是用于存储类、常量相关的信息!
用来存放程序中永远是不变或唯一的内容。(类信息【Class对象】(代码一加载)、静态变量、字符串常量等)
class Computer {
String brand; //品牌
}
public class SxtStu {
//属性fields
int id;
String sname;
int age;
Computer comp;//计算机
//方法
void study() {
System.out.println("我正在学习!使用我们的电脑,"+comp.brand);
}
void play() {
System.out.println("我在玩游戏!王王者农药!");
}
//构造方法。用于创建这个类的对象。无参的构造方法可以由系统自动创建。
}
//程序执行的入口,必须要有
Javac Sxtstu.java ,java Sxtstu
public static void main(String[] args) {
SxtStu stu1 = new SxtStu();//创建一个对象
stu1.sname = "张三";
Computer comp1 = new Computer();
comp1.brand = "联想";
stu1.comp = comp1;
stu1.study();
}
}
注意:
-
先加载代码到方法区
-
main方法也是静态方法
-
变量是声明(赋值),对象是引用(new存放的是对象的内存地址)
-
new对象时调用构造方法,也是方法,要开辟新的栈帧(图中没画是因为对象创建完毕)
-
类是模板,调用构造方法来根据模板创建对象
-
stu和c1都是main方法中的局部变量
-
对象中的变量起初都是默认值(类的成员变量)
几个关键字:
this(本质:创建好的对象的地址
创建一个对象分为如下四步:
1. 分配对象空间,并将对象成员变量初始化为0或空
2. 执行属性值的显式初始化(方法区中的量赋值??)
3. 执行构造方法(this 可用于构造方法)
4. 返回对象的地址给相关的变量
):
1.普通方法中,调用本方法的对象。
void sing() {
}
void eat() {
this.sing(); // 调用本类中的sing();
}
2.构造方法中,正要初始化的对象。如下
3.调用其他的构造方法
TestThis() {
System.out.println("正要初始化一个Hello对象");
}
TestThis(int a, int b) {
// TestThis(); //这样是无法调用构造方法的!
this(); // 调用无参的构造方法,并且必须位于第一行!
this.a = a;
this.b = b;
}
TestThis(int a, int b, int c) {
this(a, b); // 调用带参的构造方法,并且必须位于第一行!
this.c = c;
}
4.this不能用于static方法中(this-对象 static-类信息)
super:
1.super是直接父类对象的引用。可以通过super来访问父类中被子类覆盖的方法或属性。(只能是public和protected)
- 使用super调用普通方法,语句没有位置限制,可以在子类中随便调用。(与构造方法中super位置做对比)
static:
用它修饰的变量和方法,就变成了静态变量和静态方法。从属于类。通过类名即可调用。实际存储于方法区中。
- 为该类的公用变量,属于类,被该类的所有实例共享,在类被载入时被显式初始化。
2.
对于该类的所有对象来说,static成员变量(局部变量出错)只有一份。被该类的所有对象共享!!
3.
一般用“类名.类属性/方法”来调用。(也可以通过对象引用(不建议)对象.静态成员(是否静态易混淆)或类名(不需要实例化)访问静态成员。)
- 在static方法中不可直接访问非static的成员。
堆中对象(汽车)可以得到方法区中类(图纸)
垃圾回收机制(速学堂4.7-4.7.4)
两种无用对象(是没有任何变量引用该对象)情况:对象引用超过其作用范围
{
Example e=new Example();
}
- 将对象赋值为null
{
Example e=new Example();
e=null;
}
垃圾回收相关算法
- 引用计数法
堆中每个对象都有一个引用计数。被引用一次,计数加1.
被引用变量值变为null,则计数减1,直到计数为0,则表示变成无用对象。优点是算法简单,缺点是“循环引用的无用对象”无法别识别。
循环引用
public class Student {
String name;
Student friend;
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student();
s1.friend = s2;
s2.friend = s1;
s1 = null;
s2 = null;
}
}
s1和s2互相引用对方,导致他们引用计数不为0,但是实际已经无用,但无法被识别。
2. 引用可达法(根搜索算法)
程序把所有的引用关系看作一张图,从一个节点GC
ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。
内存泄漏:
1.创建大量无用对象
比如,我们在需要大量拼接字符串时,使用了String而不是StringBuilder。
String str = “”;
for (int i = 0; i < 10000; i++) {
str += i; //相当于产生了10000个String对象
}
- 静态集合类的使用
像HashMap、Vector、List等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,所有的对象Object也不能被释放。
3.各种连接对象(IO流对象、数据库连接对象、网络连接对象)未关闭
IO流对象、数据库连接对象、网络连接对象等连接对象属于物理连接,和硬盘或者网络连接,不使用的时候一定要关闭。
4.监听器的使用
释放对象时,没有删除相应的监听器。
补充:
1.我们无权调用垃圾回收器。
-
System.gc()强制启动垃圾回收器,该方法只是通知JVM,并不是运行垃圾回收器。尽量少用,会申请启动Full
GC,成本高,影响系统性能。 -
垃圾回收机制只能回收new创建的对象,否则用Object的protected方法finalize()。
package:
package必须位于非注释性第一句。
包名:域名倒着写
import:
引入外部的类
Import static 静态成员 :导入类的静态成员(主要是属性)
final:
修饰变量:常量(命名规范:全部大写,多个单词之间通过下划线隔开)
修饰方法:不能被重写 同private修饰 private final似乎可以被覆盖
修饰类: 不能被继承 所有方法都是final形式 成员变量可final/非final
面向对象的三大特征:
封装
通过private、default 、protected、public关键字实现属性或方法的封装。
注:其他包中的子类
“高内聚,低耦合”
1.一般使用private访问权限。
2.
提供相应的get/set方法来访问相关属性,这些方法通常是public修饰的,以提供对属性的赋值与读取操作(注意:boolean变量的get方法是is开头!)。
继承
通过extends 。两个好处:
-
代码重用
-
通过继承实现对现实世界更加准确的建模
类只有单继承,没有像C++那样的多继承
Java中类没有多继承,接口有多继承。
子类继承父类,可以得到父类的全部属性和方法 (除了父类的构造方法???),
方法重写(Override)的要点(实现多态的必要条件):
1.“==”: 方法名、形参列表相同。
2.“≤
≤”:返回值类型和声明异常类型,子类小于等于父类(超类、基类、派生类等)。
3.“≥”: 访问权限,子类大于等于父类
Object类:
-
是我们所有类的根基类
-
toString (用来打印对象)
public String toString() {
return getClass().getName() + "\@" + Integer.toHexString(hashCode());
}
getClass()返回对象执行时的Class实例 final 不能被重写
getname()返回类的名称
默认会返回“类名+@+16进制的hashcode”。在打印输出或者用字符串连接对象时,会自动调用该对象的toString()方法
-
==、equals、hashcode
-
“==”比较的是两个对象的引用是否相同(基本类型则表示值相等,引用类型则表示地址相等即是同一个对象)在String
s1 = "chance"时
在new String(“chance”)时
- equals()比较的是两个对象的实际内容 ❌ 只是在String类中是
默认就是比较两个对象的hashcode
非String类(即Object中的equals)==和equals一样 都是比较引用地址,即对象
public boolean equals(Object obj) {
return (this == obj);
}
String类中被重写 这才是比较两个对象的实际内容
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String) anObject;
…方法将两个String对象拆分成字符数组,然后通过遍历字符数组中的每一个字符是否都相等,若相等,则返回true
否则返回false…
}
1.如果两个对象equals()相同,那么这两个对象的HashCode()一定也相同
equal()对比是绝对可靠的 效率低
2.如果两个对象的HashCode()相同,不代表两个对象就相同,只能说明这两个对象在散列存储结构中,存放于同一个位置
所以解决方式是,每当需要对比的时候,首先用hashCode()去对比,如果hashCode()不一样,则表示这两个对象肯定不相等(也就是不必再用equal()去再对比了),如果hashCode()相同,此时再对比他们的equal(),如果equal()也相同,则表示这两个对象是真的相同了,这样既能大大提高了效率也保证了对比的绝对正确性!
- wait、notify、notifyAll final 不能被重写
多态(polymorphism):同一个方法调用,由于对象不同可能会有不同的行为
符合开闭原则(对扩展开放,对修改关闭)
- 多态是方法的多态,不是属性的多态(多态与属性无关)。
2.三个必要条件:继承、方法的重写、父类引用指向子类对象(向上转型,属于自动类型转换)
3.父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了。
class Animal {
**public** **void** shout() {
System.out.println("叫了一声!");
}
}
class Dog extends Animal {
**public** **void** shout() {
System.out.println("旺旺旺!");
}
**public** **void** seeDoor() {
System.out.println("看门中....");
}
}
class Cat extends Animal {
**public** **void** shout() {
System.out.println("喵喵喵喵!");
}
}
public class TestPolym {
**public** **static** **void** main(String[] args) {
Animal a1 = **new** Cat(); // 向上可以自动转型
//传的具体是哪一个类就调用哪一个类的方法。大大提高了程序的可扩展性。
animalCry(a1);//a1为编译类型,Cat对象才是运行时类型。
Animal a2 = **new** Dog();
animalCry(a2);//a2为编译类型,Dog对象才是运行时类型。
//编写程序时,如果想调用运行时类型的方法,只能进行强制类型转换。
// 否则通不过编译器的检查。
弊端,就是无法调用子类特有的功能,我不能使用父类的引用变量调用Dog类特有的seeDoor()方法
Dog dog = (Dog)a2;//向下需要强制类型转换
dog.seeDoor();
}
// 有了多态,只需要让增加的这个类继承Animal类就可以了。
**static** **void** animalCry(Animal a) {
a.shout();
}
父类引用做方法的形参,实参可以是任意的子类对象,可以通过不同的子类对象实现不同的行为方式。
/\* 如果没有多态,我们这里需要写很多重载的方法。
\* 每增加一种动物,就需要重载一种动物的喊叫方法。
static void animalCry(Dog d) {
d.shout();
}
static void animalCry(Cat c) {
c.shout();
}\*/
}
向下转型时用到 myobject(父类对象引用) instanceof (父类对象是否为子类的实例)
ExampleClass(子类)
动态绑定机制(java慢,现找,下一步做什么不知道,做的时候再说)、静态绑定:
抽象类(abstract):
-
包含抽象方法的类,一定是抽象类。
-
抽象类不能被new(实例化)。
-
抽象类可以包含:普通方法、成员变量、构造方法(不能用,不能new,只能用来被子类调用。)。
类不能用final修饰
方法只能用public修饰
如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类
接口:
-
interface
-
类实现接口:implements 可以实现多个接口
-
接口可以多继承
interface Inter implements Inter1,Inter2{ //Inter、Inter1、Inter2都为接口
}
- 接口定义的一组规范!实现现实世界中这样的逻辑::如果你是…则必须能…
定义接口的详细说明:
1.
访问修饰符:只能是public(仅限于接口在与其同名的文件中被定义)或默认。
2. 接口名:和类名采用相同命名机制。
3. extends:接口可以多继承。
4.
全局常量(一般是不会在接口中使用):接口中的属性只能是常量,总是:public
static final 修饰。不写也是。
5. 公共的抽象方法:接口中的方法只能是:public abstract。
省略的话,也是public abstract。
要点
1. 子类通过implements来实现接口中的规范。
2. 接口不能创建实例,但是可用于声明引用变量类型。
3.
一个类实现了接口,必须实现接口中所有的方法,并且这些方法只能是public的。
4.
JDK1.7之前,接口中只能包含静态常量、抽象方法,不能有普通属性、构造方法、普通方法。
5. JDK1.8后,接口中包含普通的静态方法。
区别:
- 普通类:具体实现
2. 抽象类:具体实现,规范(抽象方法) 有main 有构造器???
3. 接口:规范!(全部抽像)接口是抽象类的特殊化 比抽象类更加“抽象”
无main 无构造器???
对于遵循某个接口的抽象类,可以不实现该接口中的抽象方法
内存机制:
栈
-
存放局部变量
-
不可以被多个线程共享
-
空间连续,速度快
堆
-
存放对象
-
可以被多个线程共享(所以有资源冲突问题 每个对象都有锁)
-
空间不连续,速度慢。但是灵活
方法区
-
存放类的信息:代码、静态变量、字符串常量等
-
可以被多个线程共享
-
空间不连续,速度慢。但是灵活
垃圾回收器(GC Garbage Collection):
-
程序员不能调用垃圾回收器。但是可以通过System.gc()建议回收。
-
Finallize(释放对象池资源):一般也不用调
递归算法:
- 递归头:什么时候不调用自己
通属性、构造方法、普通方法。
5. JDK1.8后,接口中包含普通的静态方法。
区别:
- 普通类:具体实现
2. 抽象类:具体实现,规范(抽象方法) 有main 有构造器???
3. 接口:规范!(全部抽像)接口是抽象类的特殊化 比抽象类更加“抽象”
无main 无构造器???
对于遵循某个接口的抽象类,可以不实现该接口中的抽象方法
内存机制:
栈
-
存放局部变量
-
不可以被多个线程共享
-
空间连续,速度快
堆
-
存放对象
-
可以被多个线程共享(所以有资源冲突问题 每个对象都有锁)
-
空间不连续,速度慢。但是灵活
方法区
-
存放类的信息:代码、静态变量、字符串常量等
-
可以被多个线程共享
-
空间不连续,速度慢。但是灵活
垃圾回收器(GC Garbage Collection):
-
程序员不能调用垃圾回收器。但是可以通过System.gc()建议回收。
-
Finallize(释放对象池资源):一般也不用调
递归算法:
-
递归头:什么时候不调用自己
-
递归体:什么时候调用自己
来源:CSDN
作者:XXX10tacion
链接:https://blog.csdn.net/weixin_42642241/article/details/95106887