第八章 面向对象(2)
8.1 java内存详解
8.1.1 java内存
- JDK1.8以前(不含1.8)JVM内存分为5块:程序计数器-堆-虚拟机栈-本地方法栈-方法区
1.程序计数器(program counter register):PC寄存器,
2.堆:由所有线程共享,运行时动态申请的内存都在堆里面分配,包括new的对象和数组(动态申请的内存使用完毕,Java有自动垃圾回收机制);JDK1.8之后,静态变量,常量池等也在堆中。
3.栈:每个线程拥有独立的栈;存放局部变量,对象引用,操作数栈,方法出口等;先进后出,被调方法结束后,对应栈区变量等立即销毁。
4.方法区:JDK8之前,由永久代实现,主要存放类的信息,常量池,方法数据,方法代码等
5.本地方法栈:主要与虚拟机用到native方法有关 ,多语言调用的时候使用,C,C++…执行底层代码
8.1.2 图示
8.2 构造方法
8.2.1 构造方法概念
1.一个对象被创建的时候,构造方法用来初始化该对象,给对象的成员变量赋初值。
2.构造方法执行的时机:创建对象的时候执行。
3.作用:给对象的成员变量赋初始值。
4.分为有参构造和无参构造。
5. 格式:
修饰符 类名() {
方法体(给成员变量赋初始值);
}
6.特点:
1) 方法名必须和类名一致 如:Person类 构造方法名: Person() {}。
2)没有返回值,不需要返回值类型.连void都不要写
3) 默认被jvm调用使用,不能手动调用 new 构造方法();
4)构造方法必须跟在new关键字的后面
7注意事项:
1)创建类不提供空构造方法,创建对象的时候,主类会生成空构造,如果有,就不生成。
2)如果一个类提供了有参构造就不会再默认创造无参构造方法。一但创建了有参构造,优先创建无参构造,不然new对象的时候会报错。
3)构造方法是可以重载(方法名相同,参数列表不同)的,既可以定义参数,也可以不定义参数。
案例:Student类
public class Student {
private String name;
//空构造 - 如果我们不提供空构造,那么程序会默认生成一个,如果提供了,程序不再生成。
public Student(){
System.out.println("====================");
}
// 如果一个类提供了有参构造,那么就不会再默认创建无参数构造,
// 使用时特别注意:一旦创建了有参构造, 优先创建无参数构造。
//方法重载 - 有参数的构造方法, 创建对象的时候就可以直接使用有参数的构造实例化
public Student(String name){
this.name = name;
System.out.println("有参数====================:"+this.name);
}
//2参数 3参数.....
public Student(String name,int age){
this.name = name;
System.out.println("有参数====================:"+this.name+"年龄:"+age);
}
}
😁构造方法赋值和setter赋值的区别【面试题】
- 构造方法一般是new对象的时候使用,创建对象的同时就知道要为每个成员变量赋什么值,就用构造方法.代码更加简洁(一次)
- 创建完对象还要给私有成员赋值,修改私有成员变量的值,只能时候用setter方法. setter比构造方法更加灵活.(多次)
- 构造方法赋值一定比setter赋值优先。
8.2.2 成员变量初始化的过程
1.成员变量初始化的方式
- 默认初始化 private String name; // null
- 显示初始化 private String name = “rose”; // rose
- 构造方法初始化 private String name; Person(“jack”) // jack
2.三种初始化的顺序:
- 默认初始化优先执行 //不提供默认值
- 显示初始化 //提供默认值
- 构造方法初始化
3.构造方法初始化步骤:
- 类加载-> new - >初始化成员变量(不给值null / 给值直接赋值) -> 对象创建完成之后 - > setter赋值
- 后赋值会把先赋值给覆盖掉
8.2.3 标准JavaBean
- JavaBean是Java语言编写类的一种标准规范。符合JavaBean的类,称之为JavaBean(标准java类) 。
- 格式:
public slass 类名 {
//1.成员变量(必须封装)
//2.空构造方法(必须有)
//3.有参构造方法(建议)
//4.成员变量的getter 和 setter方法(必须有)
//5.toString()
}
- 注意事项:
- 要求类必须是具体的和公共的- public class 类名
- 一定具有无参数的构造方法- public 类名() {}
- 成员变量都私有化,提供成员变量的set 和get 方法
- 案例:
package com.its.constructor;
import java.util.Arrays;
public class Flower<toString> {
private String name;
private char color;
private int price;
//构造方法
//无惨构造
public Flower () {
}
//有参构造
public Flower(String name,char color,int price) {
this.name = name;
this.color = color;
this.price = price;
}
//成员变量的setter 和 getter方法
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setColor(char color) {
this.color = color;
}
public char getColor() {
return this.color;
}
public void setPrice(int price) {
this.price = price;
}
public int getPrice() {
return this.price;
}
//toString
public String toString() {
return "name:" + this.name + "color:" + this.color + "price:" + this.price ;
}
}
8.3 静态–static关键字
8.3.1 静态基础
- 静态:就是static,主要用来修饰java的变量(静态变量)和方法(静态方法)的关键字。一个变量或方法一旦被static修饰,它就是静态的内容。
- 基础案例:
创建一个Star类
属性: 姓名(name) 国家(country)
创建三个Star对象
对象1: 周杰伦 中国
对象2: 张国荣 中国
对象3: 王祖贤 中国
- Star类:
public class Star {
public String name;
public static String country;
@Override11
public String toString() {
return "Star{" +"name='" + name + '\'' + country='" + country + "\" + '}';
}
}
- Star测试
public class StarExample {
public static void main(String[] args) {
Star star1 = new Star();
Star star2 = new Star();
Star star3 = new Star();
Star2 star22222 = new Star2();
star1.name = "周杰伦";
star1.country = "中国";
star2.name = "张国荣";
star3.name = "王祖贤";
System.out.println(star1);
System.out.println(star2);
System.out.println(star3);
System.out.println(star22222);
}
}
- 内存分析
- 有静态和无静态的区别:
- 无静态:如果某个类型的所有对象,都具有一个相同的属性值,那么这个属性值就没有必要在所有对象中,都存储一 份。还有坏处:浪费堆内存空间;维护难度大,一旦需要修改,就得修改所有的对象。
- 有静态:如果某个类型的所有对象,都具有一个相同的属性值,那么就在这个属性的定义上,加一个static静态关键 字。让该变量存储在方法区字节码的静态区中,避免了所有对象都存储相同数据的问题,节省了内存空间,将来维护容易。
- 静态变量的特殊性
- 静态变量比较特殊,有一个共享的类级别区域(静态区)专门存储当前的静态变量,和对象无关。无论初始化多少个对象,只要有一个对象给静态变量赋值,其他对象都是用这个值。
- 特殊:其它类中即使有相同的名字的静态变量,也不可以调用它。
8.3.2 静态变量的特点
- 静态变量属于类(直接可以通过类名访问静态变量),不会随着对象的变化而变化(内存位置)
格式:修饰符 static 变量类型 变量名;
- 调用方式:(最合适的调用方式) : 类名.静态变量
3… 加载时机: 随着类(字节码文件加载方法区/元空间)的加载而加载。 静态变量随着类的加载进方法区。- 静态变量优先于对象而存在。
- 静态变量被所有该类对象所共享。
- 可以通过对象名调用,但是会有警告 格式: 对象名.静态变量
8.3.3 静态方法
- 静态方法:在方法声明上,加static,就是静态方法—
public static返回值类型 方法名(参数列表){ }
- 调用方式:(最合适的调用方式) : 类名.静态方法名
- 静态方法不能访问非静态的变量(成员变量)以及非静态方法。 静态方法早于对象存在, 而非静态的变量(成员变量)以及非静态方法属于对象, 如果静态方法能访问非静态方法以及非静态的变量(成员变量),就相当于使用了一个 不存在的对象,有错误。
- 静态方法中不能存在this关键字 原因: this关键字表示本类当前对象。静态方法可以在对象创建之前调用。8 如果静态方法可以访问this关键 字,相当于在创建对象之前,就使用了对象本身—矛盾.
案例:
package com.mianObject;
public class Pig {
public static String name;
public static int weight;
public static void eat() {
System.out.println("爱吃粑粑");
}
}
-----------------------
package com.mianObject;
public class PigExample {
public static void main(String[] args) {
Pig.name = "滔滔";
System.out.println(Pig.name);
Pig.eat();
}
}
8.4 继承
8.4.1 概念
- 继承(单继承(C语言有多继承)–只有一个父类)是面向对象三大特征之一,继承就是让两个类之间产生关系,这种关系就是继承。
继承的实现:修饰符 class 子类名 extends 父类 名{ }
- 继承会产生父子类,使得子类具有父类的属性(公开的)成员变量和方法。还可以在子类新定义属性方法。
- 向下关系,可以有“爷孙”关系,继承不代表所有父类的东西都能继承,private(私人的无法继承)
- JavaBean不可以做父类,因为属性都是私有的
- 一个类只能有一个父类,但是可以有多个子类。继承关系要合理。
案例:
package com.its.constructor;
public class Person {
public String name;
public String id;
public int age;
public Person(String name,String id,int age) {
this.name = name;
this.id = id;
this.age = age;
}
public Person() {
}
public void sleep () {
System.out.println("人会睡觉");
}
public void eat () {
System.out.println("人会吃饭");
}
public void drank () {
System.out.println("人会喝水");
}
}
-------------------------
package com.its.constructor;
public class Teacher extends Person {
public int workYear;
public String jobSchool;
public void teach() {
System.out.println("老师传授知识");
}
@Override
public void sleep() {
System.out.println("老师睡得晚");
}
}
------------------------------
package com.its.constructor;
public class Student extends Person {
public String studentID;
public String school;
public void learn() {
System.out.println("学生学习知识");
}
}
---------------------------
package com.its.constructor;
public class Example {
public static void main(String[] args) {
Teacher t = new Teacher();
t.name = "张老师";
t.jobSchool = "第二中学";
t.workYear = 10;
t.teach();
System.out.println(t.name + "在" + t.jobSchool + "教书");
Student s = new Student();
s.name = "小明 ";
s.school = "第二中学";
s.studentID = "197701020072";
s.learn();
System.out.println(s.name + "在" + s.school + "上学");
}
}
8.4.2 继承的好处
- 优势
- 提高代码的复用性(多个相关类有相同的成员变量或者成员方法,可以提取出来放到父类中,父类中一般定义的内容都是这类事物的共性)
- 提高了代码的维护性(如果方法的代码需要修改,修改一个地方就可以了)
- 弊端:
提高了类与类的耦合性:继承让类与类之间产生了关系,类与类之间的耦合性增强(两个类之间的关系性增强)了,当父类发生变化时子 类实现也不得不跟着变化
- 应用场景
使用继承,需要考虑类与类之间是否存在is…a的关系,不能盲目使用继承 is…a 的关系:谁是谁的一种.
- 练习:
程序员类Coder:姓名name、年龄age、工号number、工资salary、 工作work
项目经理Manager:姓名、年龄、工号、工资、奖金bounds、 工作 、开会
解决:将程序员和项目经理向上抽取一个“员工类” Employee
代码:
package com.block;
public abstract class Employee {
public String name;
public int age;
public String number;
public Double salary;
public Employee(){
}
public Employee(String name,String number,Double salary){
System.out.println("Employee抽象方法中输出this:" + this);
this.name = name;
this.number = number;
this.salary = salary;
}
public void work() {
}
}
8.4.3 继承关系成员变量的访问
1. 不同名变量的访问
- 父子类中成员变量没有同名的情况时,不冲突(直接使用)
- 子类对象可以访问父类中所有非私有成员变量
子类对象调用范围:
- 子类局部范围找: 找有没有局部变量
- 子类成员范围找: 找有没有成员变量
- 父类成员范围找: 找父类有没有定义此成员变量
- 如果都没有就报错(不考虑父亲的父亲…) 子类能使用父类的内容
- 父类对象:只能访问父类拥有的变量(父类不能访问子类信息)
2. 同名变量的访问
- 默认访问子类自己的变量
- 父子类中有成员变量同名的情况下,要访问父类成员变量,使用supper.变量名
super:是一个关键字,建立在继承关系中,用来使用父类资源。代表当前调用对象的父类那部分空间的引用,不是一个对象,不能打印 当我们创建一个子类对象时候,其实底层会优先创建父类的内存地址。
this:代表当前调用对象引用,可以打印,this即可以访问子类自己的成员变量,也可以访问父类的成员变量
案例:
public void showName() {
System.out.println(name);
System.out.println(super.name);
}
3. 继承下的内存分析
- 子类会默认调用父类的空构造器,给父类初始化
- 父类要么没有构造器,要么有空构造器+构造器
8.4.4 继承中成员方法的访问
- 子类对象:可以访问父类中所有成员方法
- 子类成员范围找 优先在自己类中找这个方法。
- 子类成员范围找不到就去父类中寻找,找到就用。
- 如果都没有就报错(不考虑父亲的父亲…)
- 父类对象:只能访问父类中拥有的方法
- 父类对象:只能访问父类中拥有的方法
- 如果都没有就报错
8.4.5 方法重写
- 概念
- 子类对父类的同名方法的重新实现(子类中提供了一个和父类中一模一样方法声明),称之为方法的重写。方法的重写需要使用注解 @Override 标注
- 注解@Override:用来检测当前的方法,是否是重写的方法,起到【校验】的作用
- 重写的目的
为了增强父类方法的实现,父类方法实现不能满足子类的需求了,. 对父类方法的功能进行了拓展和维护。
- 重写的使用场景
- 当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法.
- 即沿袭了父类的 功能,又定义了子类特有的内容 , 保留父类的实现方式:super.父类方法(实参)
- 注意事项
- 父类私有方法不能被重写 (子类看不到这个私有的方法)
- 子类方法访问权限不能更低(public > 默认 > private)
8.4.6 继承关系成员的访问
- 父类的构造方法子类不能继承,只能调用
- 子类会继承父类中的数据(成员变量),可能还会使用父类的数据。
- 所有子类初始化对象之前一定要先完成父类 数据的初始化.每一个子类构造方法的第一条语句默认都是: super() 表示访问父类的空参构造,父类继承过来成员都 赋的事默认值 每创建一个子类对象, 5都会调用父类的构造方法,目的就是为了初始化父类那部分成员变量.
- 构造器先创建对象,再做其他的操作,构造器之间的调用一定放在第一行,而且唯一只能调用一个,不管是父类的,还是自己的。
- 无论使用子类哪一个构造方法,都会优先调用父类的空参构造。
注意事项:
- 子类中所有的构造方法默认都会访问父类中无参的构造方法.
- 子类的构造方法中手动调用父类的其他构造,就不会默认访问父类的无参构造.
- 如果在子类构造方法中,手动调用父类构造方法,必须写在子类构造方法的第一条语句. 否则不能第一时间创建父类
4.子类调用父类构造方法的格式: super([实参]);
this 和supper调用构造方法的区别
- this只能访问本类的构造方法. 格式: this([实参])
- super只能访问父类的构造方法. 格式: super([实参])
- this语句和super语句不能同时出现.this语句和super语句都必须出现构造方法的第一行
- 一个构造方法无论手动显示调用this语句还是super语句,系统都不在默认添加:super()
- 不要写出this语句的循环调用(不能在本类的空参构造中调用this())
8.4.7 this&super的使用方法
- 成员变量
this.成员变量 - 既可以访问本类成员变量,也可以访问父类的
super.成员变量 - 只能访问父类成员变量- 成员方法:
this.成员方法 - 既可以访问本类成员方法,也可以访问父类的
super.成员方法 - 访问父类成员方法- 构造方法:
this(…) - 访问本类构造方法 this()
super(…) - 访问父类构造方法 super()
8.4.8 继承的注意事项总结
- 私有的成员不能被继承(不能直接被访问),只能通过setter和getter方法操作.
- 父类中的构造方法,不能继承,但是可以【调用】父类的构造方法。super(实参)语句.
原因:父类的构造方法需要和父类的类名一致、子类的构造方法需要和子类类名一致,父类和子类的类名不 一样。因此无法继承,名称有冲突。 Zi z= new Fu(); 编译报错 父类的构造方法用于给父类的成员变量赋值,子类的构造方法用于给子类的成员变量赋值,9 子类的成员变量 较多,使用父类的构造方法无法将子类中所有的成员变量都进行赋值,因此不继承父类的构造方法.
2.父类的构造方法,不能继承
8.4.9 Java语言中类继承的特点(面试题)
- java支持单继承,不支持多继承,java支持多层继承
- 单继承:一个子类只能继承一个父类(一个孩子只能有生物学上的亲爹)
- 不能多继承:一个子类不能同时继承多个父类4
- 可以多层继承:A类可以继承B类,B类可以继承C类,A类中拥有B、C类中的所有属性和方法。
8.5 代码块
8.5.1 概念
- 代码块:使用单独的大括号包起来的一段代码段。
- 定义在不同的位置,有不同的名称,有不同的作用,有不同的执行时机。有一些代码块有修饰符,根据修饰符不同也有不同功能的代码块。
- 格式:
[修饰符] { }
- 分类: 局部代码块 --构造代码块–静态代码块–同步代码块(多线程的安全问题)
8.5.2 局部代码块(了解)
- 概念:定义在方法的方法体中的代码块
修饰符 返回值类型 方法名 (形参){ {局部代码块} }
- 作用:缩短局部变量的生命周期,及时释放内存空间,提升代码的运行效率
- 执行时机:所在方法被调用的时候执行,执行到局部代码块右花括号被回收
- 注意:- 代码块可以使用上面的所有资源 - 代码块内部的局部变量【资源】外部不能使用
案例:
package com.block;
public class Block {
public static void main(String[] args) {
int c = 30;
//局部代码块
{
int a = 10;
System.out.println(a);
System.out.println(c); //代码块可以使用它上面的资源
//System.out.println(b); //代码块不可以使用它下面的资源
}
int b = 20;
//一个方法中可以定义多个局部代码块
{
System.out.println("局部代码块");
}
}
}
8.5.3 构造代码块(了解)
- 概念:定义在类中方法外(直接定义在类体中)【成员变量位置】的代码块
修饰符 class 类名 { { 构造代码块 } 类的其他内容 }
- 作用:创建对象的时候给对象的属性值显性赋值(对成员变量赋值)
显性赋值: 定义类的属性的时候人为给属性赋值
比如:private String name = “金莲”; { name = “大朗”; }- 执行时机:每次创建对象,就会执行构造代码块,执行时机比构造方法早.
- 执行特点:构造代码块,每次创建对象都会执行而且优先于构造方法执行
- 成员变量的初始化时机:默认初始化—>显性初始化 —>[3.构造代码块初始化] —>构造方法初始化
案例:
package com.block;
public class Block {
public String name;
public int age;
//构造代码快,和成员变量同等级
{
name = "张三";
age = 30;
System.out.println("构造代码块……");
}
//空构造方法
public Block() {
}
//构造方法
public Block(String name,int age) {
this.name = name;
this.age = age;
System.out.println("Block有参构造");
}
}
8.5.4 静态代码块(重点)
- 概念:被 static 修饰的构造代码块叫静态代码块
修饰符 class 类名 { static{ 构造代码块 }类的其他内容 }
- 位置:定义在类中方法外(类体中)
- 作用:给类的静态变量显性赋值 --用来加载只需要加载一次的资源 【如加载数据库驱动】
- 执行特点:只执行一次【随着类的加载而加载】 字节码文件只加载一次,所以静态代码块也只执行一次
package com.block;
public class Block {
public static String name;
public int age;
//构造代码快,和成员变量同等级
static{
name = "张三";
System.out.println("静态代码块……");
}
//空构造方法
public Block() {
}
}
8.6 final关键字
- final是一个关键字,表示最终,主要用来修饰Java资源. 被final修饰的元素变成了最终元素,不能修改。 作用范围:可以修饰 变量 方法 类
- 变量: 最终变量 ------ 变量不能改变 【变相的成为了常量】 这就是所谓的常量符号
- 特点:最终变量只能赋值一次 值不可改变 7
- 常量符号命名规范: 全部大写,多个单词之间使用下划线连接
- 方法:最终方法 ----- 方法不能被改变【功能就定死】
- 场景:父子之间,如果父类的方法不想被子类重写,就可以使用final修饰父类的方法
- 特点:最终方法不可以被重写,可以被继承调用
- 类:最终类 不能改变
- 场景:如果一个类你不想让他拥有子类,在定义这个类的时候使用final修饰即可
- 特点: 不能被继承
8.7 权限(访问)修饰符
- 用来限定资源的适用范围的修饰性词汇,叫权限修饰符,不同符号有不同的限定范围。
- public:公共的–没有范围
- protected:受保护的–本类和本包中使用
- private :私有的 限定范围是:本类中使用 默认的 : 啥也不写 限定范围: 本类和本包中使用
- 默认:只能在本包使用