元空间中存储的是类的原信息,字节码等。元空间是Java8之后引入的,元空间可以看做是JVM规范中方法区中的实现
所有使用运算符new的对象,都存储在堆内存中
构造方法
构造代码块
每一次new的时候,都会先执行一次构造代码块,构造代码块是在构造方法执行之前执行的
语法格式:{}
如果所有的构造方法在最开始的时候有相同的一部分代码,不妨将公共的代码提取到构造代码块当中,这样代码可以得到复用
关于构造代码块。对象的创建和初始化过程梳理:
①new的时候在堆内存中开辟空间,给所有属性赋默认值
②执行构造代码块进行初始化
③执行构造方法体进行初始化
④构造方法执行结束,对象初始化完毕。
this关键字
①this是一个关键字。
②this出现在实例方法中,代表当前对象。语法是:this.
③this本质上是一个引用,该引用保存当前对象的内存地址。
④通过“this.”可以访问实例变量,可以调用实例方法。
⑤this存储在:栈帧的局部变量表的第0个槽位上。
⑥this. 大部分情况下可以省略,用于区分局部变量和实例变量时不能省略。
⑦this不能出现在静态方法中。
⑧“this(实参)”语法:
- 只能出现在构造方法的第一行。
- 通过当前构造方法去调用本类中其他的构造方法。
- 作用是:代码复用。
static关键字
①static是一个关键字,翻译为:静态的。
②static修饰的变量叫做静态变量。当所有对象的某个属性的值是相同的,建议将该属性定义为静态变量,来节省内存的开销。
③静态变量在类加载时初始化,存储在堆中。
④static修饰的方法叫做静态方法。
⑤所有静态变量和静态方法,统一使用“类名.”调用。虽然可以使用“引用.”来调用,但实际运行时和对象无关,所以不建议这样写,因为这样写会给其他人造成疑惑。
⑥使用“引用.”访问静态相关的,即使引用为null,也不会出现空指针异常。
⑦静态方法中不能使用this关键字。因此无法直接访问实例变量和调用实例方法。
⑧静态代码块在类加载时执行,一个类中可以编写多个静态代码块,遵循自上而下的顺序依次执行。
⑨静态代码块代表了类加载时刻,如果你有代码需要在此时刻执行,可以将该代码放到静态代码块中。
5. 什么情况下把成员变量定义为静态成员变量?
* 当一个属性是对象级别的,这个属性通常定义为实例变量。(实例变量是一个对象一份。100个对象就应该有100个空间)
* 当一个属性是类级别的(所有对象都有这个属性,并且这个属性的值是一样的),建议将其定义为静态变量,在内存空间上只有一份。节省内存开销。
* 这种类级别的属性,不需要new对象,直接通过类名访问。
* 6. 静态变量存储在哪里?静态变量在什么时候初始化?(什么时候开辟空间)
* JDK8之后:静态变量存储在堆内存当中。
* 类加载时初始化。
年轻代:刚new出来
老年代:经过几次GC回收,没死,就是老年代
java A 启动java虚拟机,启动类加载器,类加载器区硬盘上找A.class文件()classpath,进行类加载
链接:符号引用:类名,方法名,属性名,每一个符号都会关联一个地址,通过地址拿到相应的信息
执行:调主方法
单例模式
饿汉单例模式
类加载时对象就创建好了,不管这个对象用不用,提前把对象创建好
怎么实现:
- 第一步:构造方法私有化
- 第二步:对外提供一个公开的静态方法,用这个方法获取单个实例
- 第三步:定义一个静态变量,在类加载的时候,初始化静态变量。(只初始化一次)
懒汉式单例模式
用到这个对象的时候再创建对象,不在类加载的时候创建对象
- 第一步:构造方法私有化
- 第二步:对外提供一个静态方法,通过这个方法可以获取到单例对象
- 提供一个静态变量,但是这个变量值为null
public class Singleton {
private static Singleton s;
private Singleton(){}
public static Singleton get(){
if (s == null) {
s = new Singleton();
System.out.println("对象创建了");
}
return s;
}
}
静态方法在类加载的时候不会执行,而是在类加载之后,当静态方法被首次调用时才会执行。静态方法属于类本身,而不是类的实例,因此它们可以在类加载之前被声明,但是不会在类加载时执行
继承
子类继承父类后,除私有的不支持继承、构造方法不支持继承。其它的全部会继承。
方法覆盖
- 什么情况下考虑使用方法覆盖?
-
- 当从父类中继承过来的方法无法满足当前子类的业务需求时。
- 发生方法覆盖的条件?
-
- 具有继承关系的父子类之间
- 相同的返回值类型,相同的方法名,相同的形式参数列表
- 访问权限不能变低,可以变高。
- 抛出异常不能变多,可以变少。
- 返回值类型可以是父类方法返回值类型的子类。
- 方法覆盖的小细节:
-
- @Override注解标注的方法会在编译阶段检查该方法是否重写了父类的方法。
- 私有方法不能继承,所以不能覆盖。
- 构造方法不能继承,所以不能覆盖。
- 静态方法不存在方法覆盖,方法覆盖针对的是实例方法。
- 方法覆盖说的实例方法,和实例变量无关。(可以写程序测试一下)
多态
instanceof 堆中的对象
// instanceof运算符的出现,可以解决ClassCastException异常。
/*
instanceof 运算符的语法规则:
1. instanceof运算符的结果一定是:true/false
2. 语法格式:
(引用 instanceof 类型)
3. 例如:
(a instanceof Cat)
true表示什么?
a引用指向的对象是Cat类型。
false表示什么?
a引用指向的对象不是Cat类型。
软件开发七大原则?
开闭原则 (Open-Closed Principle,OCP):一个软件实体应该对扩展开放,对修改关闭。即在不修改原有代码的基础上,通过添加新的代码来扩展功能。(最基本的原则,其它原则都是为这个原则服务的。)
单一职责原则:一个类只负责单一的职责,也就是一个类只有一个引起它变化的原因。
里氏替换原则:子类对象可以替换其基类对象出现的任何地方,并且保证原有程序的正确性。
接口隔离原则:客户端不应该依赖它不需要的接口。
依赖倒置原则:高层模块不应该依赖底层模块,它们都应该依赖于抽象接口。换言之,面向接口编程。
迪米特法则:一个对象应该对其它对象保持最少的了解。即一个类应该对自己需要耦合或调用的类知道得最少。
合成复用原则:尽量使用对象组合和聚合,而不是继承来达到复用的目的。组合和聚合可以在获取外部对象的方法中被调用,是一种运行时关联,而继承则是一种编译时关联。
super
super代表的是当前对象的父类型特征
- super关键字和this关键字对比来学习。this代表的是当前对象,super代表的是当前对象中的父类型特征
- super不能使用在静态上下文中
- super大部分情况下是可以省略的,什么时候不能省略?
-
- 当父类和子类中定义了相同的属性(实例变量)或者相同方法(实例方法)时,如果需要在子类中访问弗雷德属性或方法时,super不能史略
- this可以单独输出,super不能单独输出
- super(实参);通过子类的构造方法调用父类的构造方法,目的是为了完成父类型特征的初始化
- 当一个构造方法第一行没有显示的调用“super(实参)”,也没有显示的调用“this(实参)”,系统会自动调用super()。因此一个类中的无参构造方法建议显示的定义出来
- super(实参):这个语法只能出现在构造方法第一行
- 在Java语言中只要new对象,Object的无参构造方法一定会执行
public CreditAccount(String actno, double balance, double credit){
// 通过子类的构造方法调用父类的构造方法。
// 引入这个语法的作用:1.代码复用。2.为了“模拟”现实世界中的要有儿子,得先有父亲。
3.通过子类构造方法调用父类构造方法是为了给继承过来的父类型特征初始化。
super(actno, balance);
this.credit = credit;
}
final关键字
- final修饰的类不能被继承
- final修饰的方法不能被覆盖
- final修饰的变量,一旦赋值不能重新赋值
- final修饰的实例变量必须在对象初始化时手动赋值(在构造方法中赋值也可以)
- final修饰的实例变量一般和static联合使用:称为常量
- final修饰的引用,一旦指向某个对象后,不能再指向其它对象。但指向的对象内部的数据是可以修改的。
抽象类
- 什么时候考虑将类定义为抽象类?
-
- 如果类中有些方法无法实现或者没有意义,可以将方法定义为抽象方法。类定义为抽象类。这样在抽象类中只提供公共代码,具体的实现强行交给子类去做。比如一个Person类有一个问候的方法greet(),但是不同国家的人问候的方式不同,因此greet()方法具体实现应该交给子类。再比如主人喂养宠物的例子中的宠物Pet,Pet中的eat()方法的方法体就是没有意义的。
- 抽象类如何定义?
-
- abstract class 类名{}
- 抽象类有构造方法,但无法实例化。抽象类的构造方法是给子类使用的。抽象方法如何定义?
-
- abstract 方法返回值类型 方法名(形参);
- 抽象类中不一定有抽象方法,但如果有抽象方法那么类要求必须是抽象类。
- 一个非抽象的类继承抽象类,要求必须将抽象方法进行实现/重写。
- abstract关键字不能和private,final,static关键字共存。
接口
接口的语法
- 接口(interface)在Java中表示一种规范或契约,它定义了一组抽象方法和常量,用来描述一些实现这个接口的类应该具有哪些行为和属性。接口和类一样,也是一种引用数据类型
- 接口怎么定义?[修饰符列表] interface 接口名{}
- 抽象类是半抽象的,接口是完全抽象的。接口没有构造方法,也无法实例化
- 接口中只能定义:常量+抽象方法。接口中的常量的static final可以省略。接口中的抽象方法的abstract可以省略。接口中所有的方法和变量都是public修饰的
- 接口和接口之间可以多继承
- 类和接口的关系我们叫做实现,用implements关键字进行接口的实现
- 一个非抽象的类实现接口必须将接口中所有的抽象方法全部实现
- 一个类可以实现多个接口。class 类 implements 接口A,接口B{}
- JDK8之后,接口中允许出现默认方法和静态方法
-
- 引入默认方法是为了解决接口演变问题:接口可以定义抽象方法,但是不能实现这些方法。所有实现接口的类都必须实现这些抽象方法。这会导致接口升级问题:当我们向接口添加或删除一个抽象方法时,会破坏该接口的所有实现,并且所有该接口的用户都必须修改其代码才能适应更改。这就是所谓的“接口演变”问题
- 引入的静态方法只能使用本接口名来访问,无法实现类名访问
- JDK9之后允许接口中定义私有的实例方法(为默认方法服务的)和私有的静态方法(为静态方法服务的)
- 所有的接口隐式的继承Object,因此接口也可以调用Object类的相关方法
接口的作用
- 面向接口调用的称为:接口调用者
- 面向接口实现的称为:接口实现者
- 调用者和实现者通过接口达到了解耦合,也就是说调用者不需要关心具体的实现者,实现者也不需要关心具体的调用者,双方都遵循规范,面向接口进行开发
- 面向抽象编程,面相接口编程,可以降低程序的耦合度,提高程序的扩展力
接口与抽象类如何选择
- 抽象类和接口虽然在代码角度都能达到相同的效果,但适用场景不同
-
- 抽象类主要适用于公共代码的提取,当多个类中有共同的属性和方法时,为了达到代码的复用,建议为这几个类提取出一个父类,在该弗雷中编写公共的代码,如果有一些方法无法在该类中实现,可以延迟到子类中实现,这样的类就应该使用抽象类
- 接口主要用于功能的扩展,例如有很多类,一些类需要这个方法,另外一些类不需要这个方法时,可以将该方法定义到接口中,需要这个方法的类就去实现这个接口,不需要这个方法的类就可以不实现这个接口,接口主要规定的是行为
类转换成接口的时候,类和接口之间不需要有继承关系,编译器也不会报错
类之间的关系
1. 泛化关系(is a)
2. 实现关系(is like a)
3. 关联关系(has a)
4. 聚合关系聚合关系指的是一个类包含、合成或者拥有另一个类的实例,而这个实例是可以独立存在的。聚合关系是一种弱关联关系,表示整体与部分之间的关系。例如一个教室有多个学生
5. 组合关系(Composition)组合关系是聚合关系的一种特殊情况,表示整体与部分之间的关系更加强烈。组合关系指的是一个类包含、合成或者拥有另一个类的实例,而这个实例只能同时存在于一个整体对象中。如果整体对象被销毁,那么部分对象也会被销毁。例如一个人对应四个肢体。
6. 依赖关系(Dependency)依赖关系是一种临时性的关系,当一个类使用另一个类的功能时,就会产生依赖关系。如果一个类的改变会影响到另一个类的功能,那么这两个类之间就存在依赖关系。依赖关系是一种较弱的关系,可以存在多个依赖于同一个类的对象。例如A类中使用了B类,但是B类作为A类的方法参数或者局部变量等。
访问权限
访问权限控制符不能修饰局部变量。
Object类
hashCode:返回一个对象的哈希值,通常作为在哈希表中查找该对象的键值。Object类的默认实现是根据对象的内存地址生成一个哈希码(即将对象的内存地址转换为整数作为哈希值)。hashCode()方法是为了HashMap、Hashtable、HashSet等集合类进行优化而设置的,以便更快地查找和存储对象。
finalize:当java对象被回收时,由GC自动调用被回收对象的finalize方法,通常在该方法中完成销毁前的准备。
- 浅克隆
- 深克隆
public Object clone() throws CloneNotSupportedException {
// 重写方法,让其达到深克隆的效果。
// User要克隆,User对象关联的Address对象也需要克隆一份。
Address copyAddr = (Address)this.getAddr().clone();
User copyUser = (User)super.clone();
copyUser.setAddr(copyAddr);
return copyUser;
}
内部类
静态内部类
静态内部类:可以把静态内部类当做静态变量来看。
* 结论:在静态内部类当中,无法直接访问外部类的实例相关的数据。
实例内部类
实例内部类:等同可以看做实例变量。
* 结论:实例内部类中可以直接访问外部类中实例成员和静态成员。
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
System.out.println(outerClass.i);
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
innerClass.x();
}
局部内部类
局部内部类:等同于局部变量。
*
* 结论:局部内部类能不能访问外部类的数据,取决于局部内部类所在的方法。
* 如果这个方法是静态的:只能访问外部类中静态的。
* 如果这个方法是实例的:可以都访问。
*
* 局部内部类不能使用访问权限修饰符修饰。
*
* 局部内部类在访问外部的局部变量时,这个局部变量必须是final的。只不过从JDK8开始。这个final关键字不需要提供了。系统自动提供。