面向对象基础
创建对象分为如下四步:
- 分配对象空间, 将对象成员初始化为0或null或False
- 执行属性值显式初始化
- 执行构造方法
- 返回对象的地址给的相关变量
1. 对象和类的关系, 属性, 成员变量, 方法
package com.learning;
public class Sxtstu {
//属性fields
int age;
String name;
int id;
Computer comp;
//方法
void study(){
System.out.println("I'm studying now" + comp.brand);
}
void play(){
System.out.println("I'm playing now");
}
//构造方法,用于创建这个类的对象,无参的构造方法可以由系统自动创建,方法必须和类名保持一致
Sxtstu(){
}
//程序执行入口,必须要有
public static void main(String[] args) {
Sxtstu stu = new Sxtstu(); //去掉了构造方法
stu.id =10001;
stu.sname = "Wang";
stu.age =18;
//建好一个对象
Computer c1 = new Computer();
c1.brand = "Lenovo";
//把刚刚建好的对象赋给stu的comp属性
stu.comp = c1;
stu.play();
stu.study();
}
}
class Computer{
String brand;
}
2. 一个典型的类的定义和UML图
可以有多个类, 但只能有一个public修饰的类,
图先鸽了
3. 内存分析
(1)栈Stack
- 栈描述的是方法执行的内存模型,每个方法被调用的都会创建一个栈帧(存储局部变量, 操作数,方法出口等)
- JVM为每一个线程创建一个栈,用于存放该线程执行方法的信息(实际参数[与形参呼应], 局部变量等)
- 栈 的信息为线程私有, 线程间不可共享
- 栈的存储特性是先进后出, 后进先出
- 栈由系统自动分配, 速度快! 栈是一个连续的存储空间
(2)堆Heap
- 堆用于存储创建好的对象和数组(数组也是对象)
- JVM只有一个堆, 被所有线程共享
- 堆是一个不连续的存储空间, 分配灵活, 速度慢!
(3)方法区
- JVM只有一个方法区, 被所有线程共享
- 方法区也是堆, 只是用于存储类&常量相关的信息
- 用来存放程序中永远不变或唯一的内容(类信息[class对象], 静态变量, 字符串常量等)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PsD87Hhd-1585902761483)(C:\Users\Yeseung\Videos\Captures\064_内存分析详解_栈_堆_方法区_栈帧_程序执行的内存变化过程.mp4 - PotPlayer 2020_3_22 20_26_34 (2)].png)
程序执行的步骤和说明
-
javac 编译文件Sxtstu.java → \rarr → java Sxtstu 虚拟机执行这个类, 调java时启动虚拟机, 就启动空间了:栈/堆/方法区 → \rarr → 执行Sxtstu这个类,代码加载到空间中
-
执行Sxtstu这个类, 代码加载到空间中, 方法区有类的相关信息了
代码 静态变量 静态方法 字符串常量 (引号引起来的那些都算) 有了这些信息以后, java Sxtstu这个语句就算执行完了
-
开始找main方法,执行:
-
在栈中开辟一个栈帧(🐕), 定义了一个(局部)变量stu–一个引用类型的变量,且引用的是Sxtstu类,目前为空(⭐)
-
等号右边是new Sxtstu(), 建一个对象,调用类中的构造器(也是一个方法) → \rarr → 开辟一个新的栈帧(🐕🐕) → \rarr → 执行这个栈帧,执行的过程中就是通过这个方法来创建Sxtstu类的一个对象 → \rarr → 执行完毕之后,堆中就有一个刚刚new出的对象(有地址⭐⭐)了, 删除栈帧(🐕🐕), 有很多属性和方法
属性/方法 类型 默认情况 id 数值 0 sname 引用类型 null age 数值 0 comp 引用类型 null study() 引用类型 null play() 引用类型 null -
把地址 ⭐⭐ 赋值给栈帧中的stu(以后任何时候写到stu都知道是⭐⭐ )
-
开始给⭐⭐ 这个地址的对象中的属性赋值, 数值就给数值, 引用型就给方法区的字符串
-
新建局部变量c1也在main方法这个栈帧(🐕)里面,目前是null, new一个Computer → \rarr → 调用构造器, 开辟一个新的栈帧(🐕🐕🐕),会在堆里面创建新的对象, 也有一个地址(⭐⭐ ⭐), 里面只有一个属性brand(默认是null),然后把这个地址给到main方法中的局部变量c1,最后删掉栈帧(🐕🐕🐕)
-
给c1.brand赋值’联想’(从方法区来)
-
stu.comp = c1, 把c1的值赋给comp属性,相当于是把地址(⭐⭐ ⭐)传给了comp
-
执行play()方法
-
执行study()方法,要找到stu.comp → \rarr → 找到了(⭐⭐⭐),执行完毕
-
-
删掉栈帧(🐕)
-
虚拟机停掉, 内存没啦, 彻底执行完啦
4. 构造器/构造方法(Constructor)
特点
- 通过new关键字调用
- 构造器虽然有返回值, 但不能定义返回值类型(返回值的类型肯定是本类), 不能在构造器中return返回某个值
- 如果我们没有定义构造器, 则编译器会自动定义一个无参的构造函数, 如果已定义编译器不会自动添加!
- 构造器的方法名必须和类名一致
class Point{
//声明成员变量
double x,y;
// 这就是一个构造方法
public Point (double _x, double _y){
x = _x;
y = _y;
//这里可以加return;没有参数跟着的话,仅表示结束方法的运行
}
public double getDistance(Point p){
return Math.sqrt((x - p.x)(x - p.x) + (y - p.y)(y - p.y));
}
}
public class TestConstructor{
public static void main (String args){
//调的时候, 把 3.0, 4.0传给了_x, _y
//把建好的Point的对象的地址赋给p
Point p = new Point(3.0, 4.0);
//调的时候, 把 0.0, 0.0传给了_x, _y, 通过构造方法把_x, _y传给了成员变量(属性)x, y
Point origin = new POint(0,0, 0.0);
System.out.println(p.getDisatnce(origin));
}
}
构造方法的重载
方法名称相同, 形参列表不同
public class User{
int id;
String name;
String pwd;
public User(){}
//如果什么都不写只写一个id, 指的是局部变量(而不是成员变量),如果还要表示成员变量的话, 使用this
public User(int id, String name){
super(); //构造方法的第一步永远是super(),即使不写的话, 也会自动执行这个的
this.id = id; // this表示创建好的对象,等号左边的意思的是创建好的对象的成员变量
this.name = name;
}
public User(int id, String name, String pwd){
this.id = id;
this.name = name;
this.pwd = pwd;
}
public static void main (String args){
User u1 = new User();
User u2 = new User(101,'Wong');
User u3 = new User(101,'Wong',123456)
}
5. 垃圾回收器
垃圾回收器
- 引用计数: 堆中的每一个对象都有一个引用计数, 被引用一次, 计数+1, 被引用变量值变为null则引用计数-1, 直至计数为0, 则表示变成无用对象. 容易被循环引用(互相引用无法识别)
- 引用可达: 程序把所有的引用关系看作一张图, 从节点gcroot开始, 寻找节点的引用节点然后递归地继续, 直到找到所有的引用节点,所有的引用节点寻找完毕后, 没有被引用的节点即无用的节点
通用的分代垃圾回收机制
对象有三种状态: 年轻代, 年老代, 持久代(Perm)(在方法区里面)
JVM将堆内存划分为Eden, Survivor, 以及Tenured/Old空间;
垃圾回收过程:
- 新创建的对象, 绝大多数都会存储到Eden中
- 当Eden满了(到达一定的比例)不能创建新的对象, 则触发垃圾回收,将无用对象清理掉(使用引用可达法或引用计数法), 然后剩余对象复制到某一个Survivor中例如S1, 同时清空Eden
- 当Eden区再次满了, 会将S1中的不能清空的对象存在另一个Survivor中此时是S2,同时将Eden中不能清空的对象也复制到S2中,保障Eden和S1均被清空
- 重复多次(默认是15次)Survivor中没有被清理的对象,则会复制到老年代Old(Tenured)中
- 当Old也满了,则会触发一个Full GC; 之前新生代的垃圾回收称作minorGC
System.gc();
## 只是向系统发出请求启动gc()
6. This的本质
public class testThis{
int a,b,c;
// 重载的构造器
testThis(int a, int b){
this.a = a;
this.b = b;
}
testThis(int a, int b), int c{
this(a,b); //调用构造器,必须位于方法里面的第一句
this.c = c;
}
void sing(){}
void eat(){
this.sing();
System.out.println("回家吃饭")
}
public static void main(String args[]){
testThis hi = new testThis(2,3);
hi.eat();
}
}
静态方法中不能使用this, 因为this指的是当前对象
静态变量都在方法区的类信息里面(模具), 里面的放的是类信息而不是对象(对象在堆里面), 更进一步的静态方法中不能调普通的属性和方法
7. static关键字
在类中, 用static声明的变量为静态成员变量, 也成为类变量. 类变量生命周期和类相同, 在整个应用程序执行期间都有效.
强调: static修饰的成员变量和方法从属于类; 普通变量和方法从属于对象.
类似于, 造汽车根据具体的汽车的零件去找图纸上的局部很容易, 但根据一张图纸想要找一辆具体的车上的零件就很难
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XDroOlqV-1585902761486)(C:\Users\Yeseung\Videos\Captures\067_通用分代垃圾回收详解.mp4 - PotPlayer 2020_3_23 20_32_16.png)]
class User{
int id;
String name;
String pwd;
static String company = "my company";
public User(int id, String name){
this.id = id;
this.name = name;
}
public void login(){
printCompany();
System.out.println("Login:" + name)
}
public static void printCompany(){
//login(); 非静态方法,调用会报错
System.out.println(company);
}
public static void main(String args[]){
User u = new User(101, "高小七");
User.printCompany();
User.company = "阿狸";
User.printCompany();//这两个都可以print,静态变量支持修改吗
}
8. 静态初始化块以及继承树的追溯
先加载类, 再加载构造器
9. Java的参数传值机制
相当于复印一份地址, 传的是复印的那一份, 但指向的是同一个对象,因此也会发生改变;
/*测试参数传递
*/
public class User4{
int id;
String name;
String pwd;
public User4(int id,String name){
this.name = name;
this.id = id;
}
public void testParaTransfer01(User4 u){
u.name = 'Gao'
}
public void testParaTransfer02(User4 u){
u = new User4(200,'Real')
}
public static void main(String[] args){
User4 u1 = new User4(100,'Zhao')
//把u1的地址传给了Transfer里面的形参u,此时u和u1指向的都是对象u1的地址,testParaTransfer01方法操作了u指向的对象的name属性;
u1.testParaTransfer01(u1)
System.out.println(u1.name)
把u1的地址传给了Transfer里面的形参u,此时u和u1指向的都是对象u1的地址,但此时testParaTransfer02方法操作u重新构造了一个属性为200,'Real'的对象,地址也就变成了新开辟的那个地址,与原来的u1没啥子关系了
u1.testParaTransfer02(u1)
System.out.println(u1.name)
}
}
10.包及包的导入
静态导入
//导入静态属性
import static java.lang.Math.*;
面向对象进阶
继承及instanceOf的应用
package com.wong.oo;
public class Testextends {
public static void main(String[] args) {
Student stu = new Student();
stu.name = "Wong";
stu.height = 167;
stu.rest();
Student stu2 = new Student("sunny",6,"挖掘机");
System.out.println(stu2 instanceof Person);
System.out.println(stu2 instanceof Student);
System.out.println(new Person() instanceof Student);
}
}
class Person{
String name;
int height;
public void rest(){
System.out.println("休息一会儿");
}
}
class Student extends Person{
String major;
public void study(){
System.out.println("学习两小时");
}
//构造器
public Student(String name, int height, String major){
this.name = name;
this.height = height;
this.major = major;
}
//我猜测这个是构造器重载
public Student(){}
}
类是单继承,只有一个直接的父类(接口可以多继承);
子类继承父类, 可以得到父类的全部属性和方法(构造方法除外), 但不见得可以得到父类的私有属性和方法
如果定义一个类没有调用extends的话父类默认是Object
stu2 instanceof Student 该对象是否是Student类
方法的重写
子类通过重写, 用自身的行为替换父类的行为
package com.wong.oo;
public class Testoverride {
public static void main(String[] args) {
Horse h = new Horse(); //前面写Vehicle也行
h.run();
}
}
class Vehicle{
public void run(){
System.out.println("Runing");
}
public void stop(){
System.out.println("Stop");
}
public Person whoIsPsg(){
//返回一个乘客
return new Person();
}
}
class Horse extends Vehicle{
public void run(){
System.out.println("随便跑跑");
}
/*不能重写Object, 因为子类在重写父类方法的时候必须要小于原对象类型
public Object whoIsPsg(){
return new Student();
}*/
public Person whoIsPsg(){//返回值类型要小于等于父类的类型
return new Student();
}
}
重写的三个要点:
'" == " 方法名和形参列表要相同
" ≤ \leq ≤ " 返回值类型和声明异常值类型, 子类小于等于父类
" ≥ \geq ≥ "子类的访问权限大于父类
Object类的用法, toString方法的重写
package com.wong.oo;
public class TestObject {
public static void main(String[] args) {
Object obj;
TestObject to = new TestObject();
System.out.println(to.toString());
Person2 p2 = new Person2("Wong",24);
System.out.println(p2.toString());
}
public String toString(){
return "Test OOOOObject";
}
}
class Person2{
String name;
int age;
@Override //注解表示这是一个重写
public String toString(){
return name + ", age:" + age;
}
public Person2(String name,int age){
this.name = name;
this.age = age;
}
}
equals方法的重写
"=="代表双方是否相同, 如果是基本类型则表示值相等, 如果是引用类型则表示地址相等即是同一个对象, 默认的情况是看hash code是否相同
Object类中定义有: public boolean equals(Object obj)方法, 提供定义’对象内容相等’的逻辑
package com.wong.oo;
//Object中默认的equals方法是比较this和参数中的object的hashcode是否相同
import java.net.UnknownServiceException;
import java.util.Objects;
public class TestEquals {
public static void main(String[] args) {
Object obj;
String Str;
User u1 = new User(1000,"Wong","123456");
User u2 = new User(1000,"Yeseung","7557");
System.out.println(u1 == u2);
System.out.println(u1.equals(u2));
String str1 = new String("WONG");
String str2 = new String("WONG");
System.out.println(str1 == str2);
System.out.println(str1.equals(str2)); //重写
}
}
class User{
int id;
String name;
String pwd;
public User(int id, String name, String pwd) {
super();
this.id = id;
this.name = name;
this.pwd = pwd;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o; // 强制转型
return id == user.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
String类型也有equals的重写源码