Java学习笔记-单例模式等

时间:2021-10-16 12:06:06

该博文是传智播客Java基础视频的学习笔记仅为个人学习笔记,谢谢             

有些类当中,方法都是静态的,不需要创建对象,所以将构造函数私有化。

javadoc的用法:class 前边必须加上public,加上public之后,文件名和类名必须一致,文档能提取出来的只有共有的方法,也只能提取/** */注释的内容。

单例模式:私有化构造函数,通过new在本类中创建一个本类对象,定义一个公有方法,将创建的对象返回 继承:提高代码复用性,让类与类之间之间产生关系,多态产生的前提,Java中支持单继承,不直接支持多继承,但对c++中的多继承机制进行了改良。通过多实现的方式来体现。Java中支持多重继承,如:A extends B , B extends C ... ...,所以,要使用一个继承体系时,查看该体系的顶层类,了解基本功能,然后,创建最底层的子类对象,去使用功能 。 当本类的成员和局部变量同名,用this区分。当子父类中的成员变量同名时用super区分父类 子父类中成员变量的特点:
public class ExtendsDemo {
public static void main(String[] args) {
Zi z = new Zi();
z.show();
}
}
class Fu{
int num = 5;
// 如果将该处的num设为private,
// 则下边的super.num会提示The field Fu.num is not visible
// 即,子类不能直接访问父类中的私有成员
}
class Zi extends Fu{
int num = 4;
void show(){
System.out.println(this.num+"..."+super.num);// 4...5
}
}

子父类中成员函数的特点--覆盖(也称重写、覆写,override):子父类中出现一模一样的情况,会执行子类的函数。重载是overload
覆盖注意事项:子类方法覆盖父类方法时,子类权限必须要大于等于父类的权限,否则会提示子类中的 方法无法覆盖父类中的方法(Cannot reduce the visibility of the inherited method from Fu)。如果父类中的方法为private,那么就谈不上覆盖了,覆盖指的是,父类有一个方法可以用,但是子类不用,而是自己写一个出来把父类的方法覆盖掉,如果父类的某个方法为private,那么,虽然子类中定义了相同的方法不会出错,但只能叫做子类自己定义一个方法来用静态只能覆盖静态,也只能被静态覆盖。
什么时候使用覆盖操作?当对一个类进行子类扩展,子类需要保留父类的功能声明,但是要定义子类中该功能的特有内容时,就可以使用覆盖操作完成
子父类中构造函数的特点:在子类所有的构造函数中,如果不显示调用父类构造函数,第一行都有一个默认的隐式语句,super();调用父类中空参数的构造函数
public class ExtendsDemo {
public static void main(String[] args) {
Zi z1 = new Zi();// 执行结果是 fu zi
Zi z2 = new Zi(3);// 执行结果是 fu zi3
}
}
class Fu{
Fu(){
// 如果将该构造函数改为下列构造函数,则需在zi()函数第一行显示调用super(int),
// 否则会报错,因为,定义了有参构造函数,默认构造函数就没有了
// zi()函数第一行中却有默认的super()
System.out.println("fu");
}
//Fu(int x){
//System.out.println("fu"+x);
//}
}
class Zi extends Fu{
Zi(){
System.out.println("zi");
}
Zi(int x){
//该处也是默认的调用super()
System.out.println("zi"+x);
}
}

因为子类继承了父类的内容,而父类会在构造函数中进行初始化,所以子类在使用父类的内容时,应该走父类的构造函数进行相同的初始化操作,即在子类的构造函数中默认加入了super()语句。
如果父类中定义了非空参数的构造函数,则子类构造函数中必须要在第一行用super语句明确调用父类中的某个构造函数。如果子类构造函数中使用了this调用了本类的构造函数,那么super就没有了,因为this和super语句都只能放在函数中的第一行,但是子类中一定有一个构造函数调用父类的构造函数。

子类成员变量的初始化问题:子类中通过super初始化父类的内容时,子类成员变量只是默认初始化,比如int类型的成员变量值为0,并未显示初始化,等super()父类初始化完毕后,才进行子类成员变量的显示初始化。
public class InitializationDemo {
public static void main(String[] args) {
Zi z = new Zi(); // zi...show...0
z.show(); // zi...show...8
}
}
class Fu{
int num = 3;
Fu(){
show();
}
void show(){
System.out.println("fu...show...");
}
}
class Zi extends Fu{
int num = 8;
Zi(){
super(); // 隐式的super()语句
System.out.println("zi...ini..."+num);
//此处super()语句执行完,父类的初始化完成,开始子类的成员变量的显示初始化
//所以结果为zi...ini...8
}
void show(){
System.out.println("zi...show..."+num);
}
}

一个对象的实例化过程:
Person p = new Person();1、JVM会读取指定路径下的Person.class文件,并加载进内存,同时会加载Person的父类(假设Person有父类)2、在堆内存中开辟空间,分配地址3、在堆内存的对象空间中对对象的属性进行默认初始化4、调用对应的构造函数进行初始化5、在构造函数中,第一行会先调用父类的构造函数进行初始化6、父类初始化完毕后,再对本类的属性进行显示初始化7、再进行本类构造函数的特定初始化8、所有初始化完毕后,将地址赋给引用变量 p

final关键字:final可以修饰类、方法和变量final修饰的类不可以被继承final修饰的方法不可以被覆盖final修饰的变量是一个常量,只能被赋值一次内部类只能访问被final修饰的局部变量
应用场景:1、封装被打破是继承的弊端之一,可以考虑使用final修饰类避免,如:final class Fu2、如果Fu类中,只有一部分方法不能被覆盖,则只需用final修饰不能被覆盖的方法3、如果一个变量是固定值,就可以用final修饰这个变量为一个常量,名称规范:所有字母大写,单词间用下划线连接,常量只能赋值一次final int x;语句会报错,The blank final field x may not have been initialized,因为final修饰的变量需要显示初始化
 
抽象:
抽象类特点:1、方法只有声明没有具体实现,该方法就是抽象方法,需要被abstract修饰,如abstract void show();抽象方法必须定义在抽象类中,抽象类也是用abstract修饰 abstract class Demo{}2、抽象类不可以被实例化3、抽象类的子类必须覆盖所有抽象方法之后,才能创建对象,否则这个子类还是一个抽象类

抽象类中也有构造函数,用于给子类对象进行初始化抽象类中也可以不定义抽象方法,目的就是不让该类创建对象,但是这种情况很少见,AWT的适配器对象就是这种类,通常这个类中的方法有方法体,但是却没有内容abstract关键字不可以和private、static、final进行组合

接口:当一个抽象类中的方法都是抽象的时候,可以将这个类用另一种形式来体现。接口的出现将Java中的多继承通过另一种方式体现出来,即多实现格式:interface{}接口中的成员修饰符是固定的,接口中的成员都是public的成员常量:public static final成员函数:public abstract由于接口中的修饰符格式是固定的,所以,接口中成员的修饰符public、 static 、final 和abstract都可以省略

一个类在继承某个类的同时,还能实现多个接口,接口与接口之间是继承关系,而且接口之间可以多继承,原因就是没有方法体,不会出现执行内容的不确定性。


多态:在代码中的体现就是,父类或者接口的引用指向子类对象,提高代码的扩展性多态的前提:1、必须存在继承或实现关系2、需要存在覆盖
多态使用过程中,涉及 instanceof 的使用,instanceof 只能用于引用数据类型的判断

多态的特点:成员函数:编译时,要看引用变量所属的类中是否有所调用的成员,有则编译通过,没有,则编译失败。运行时,要查看对象所属的类成员变量:编译时,要看引用变量所属的类中是否有所调用的成员,有则编译通过,没有,则编译失败。运行时,只看引用变量所属的类静态函数:编译和运行都参考引用变量所属的类

内部类:
内部类可以直接访问外部类中的成员,包括私有成员外部类要访问内部类中的成员,必须要建立内部类对象内部类可以被成员修饰符所修饰内部类的使用场景:
假设Inner为Outer的内部类,则在其他类中创建Inner对象的写法为:Outer.Inner in = new Outer().new Inner();如果内部类是静态的,相当于一个外部类Outer.Inner in = new Outer.Inner();如果内部类是静态的,内部类的成员也是静态的,则直接使用:
Outer.Inner.show();如果一个内部类中有了静态成员,该内部类必须用静态修饰
public class InnerClassDemo {
public static void main(String[] args) {
new Outer().method();
}
}
class Outer{
int num = 3;
class Inner{
int num = 4;
void show() {
int num = 5;
System.out.println(num); //5
System.out.println(this.num); // 4
System.out.println(Outer.this.num); // 3
}
}
void method() {
new Inner().show();
}
}


内部类也可以放在局部位置上
public class InnerClassDemo {
public static void main(String[] args) {
new Outer().method();
}
}
class Outer{
int num = 3;
void method() {
class Inner{
int num = 4;
void show() {
System.out.println(num); //4
}
}
new Inner().show();
}
}

从内部类中访问局部变量x,需要被声明为final类型:
class Outer{
int num = 3;
void method() {
final int x = 9;//从内部类中访问局部变量x,需要被声明为final类型
class Inner{
int num = 4;
void show() {
System.out.println(x);
}
}
new Inner().show();
}
}
因为Inner虽然是在局部位置上,但它是一个类,是可以创建对象的,method方法执行完之后,非final修饰的局部变量x就消失了,但是创建出来的Inner对象可能还在被某个变量所指向,这时候就会导致inner对象需要访问局部变量x时访问不到,所以就需要用final修饰x



匿名内部类:使用的前提:内部类必须继承或实现一个外部类或者接口
public class InnerClassDemo {
public static void main(String[] args) {
new Outer().method();
}
}
abstract class Demo{
abstract void function();
}
class Outer{
/*class Inner extends Demo{
void function() {
System.out.println("function");
}
}*/
void method() {
//new Inner().function();
new Demo(){
void function() {
System.out.println("function");
}
}.function();
}
}



匿名内部类的使用场景:当函数的参数是接口类型,并且接口中的方法不超过三个时,可以用匿名内部类作为实际参数传递

匿名内部类使用注意事项:
public class InnerClassDemo1 {
public static void main(String[] args) {
new Outer().method();
}
}
class Outer{
void method(){
new Object(){
void show(){
System.out.println("show");
}
}.show();
// 以下方式编译失败,因为匿名内部类这个子类对象被向上转型为了Object类型,
// 这样就不能再使用子类的特有方法了,参考多态
/*Object obj = new Object(){
void show(){
System.out.println("show");
}
};
obj.show();*/
}
}


对象的初始化过程:一个对象的实例化过程:Person p = new Person();1、JVM会读取指定路径下的Person.class文件,并加载进内存,同时会加载Person的父类(假设Person有父类)2、在堆内存中开辟空间,分配地址3、在堆内存的对象空间中对对象的属性进行默认初始化4、调用对应的构造函数进行初始化5、在构造函数中,第一行会先调用父类的构造函数进行初始化6、父类初始化完毕后,再对本类的属性进行显示初始化7、显示初始化完成之后,是构造代码块初始化8、再进行本类构造函数的特定初始化9、所有初始化完毕后,将地址赋给引用变量 p