oop:面向对象;pop:面向过程;fp:面向函数
编程的目的是什么?
为了把现实世界中的事物用语言表示出来。
如何去描述现实世界中的事物?
属性:对该事物的描述信息,事物身上的名词;
行为:事物能做什么,事物身上的动词。
Java中最基本的单位是类,Java中用类class描述事物:
成员变量->事物的属性;成员方法->事物的行为。
类是什么?对象是什么?
类是一组相关的属性和行为的集合。对象是该类事物的具体体现。
成员变量和局部变量的区别?
- 在类中的位置不同:
成员变量在类中,方法外;局部变量在方法定义中或在方法声明上。 - 在内存中的位置不同:
成员变量属于对象,保存在堆空间中;局部变量属于类,在栈内存中。 - 生命周期不同:
成员变量随着对象的创建而存在,随着对象的消失而消失;
局部变量随着方法的调用而存在,随着方法调用完毕而消失。 - 初始化值不同:
成员变量有初始值;局部变量没有初始值,必须声明、赋值后才能使用。 - 局部变量名称可以和成员变量名称一样,在方法中使用的时候,采用的是就近原则。
面向对象的基本特征:封装、继承、多态。
-
封装:在类中定义属性的时候,一般需要把属性隐藏起来。如果外界需要访问这个属性,那么就提供公共方法(set、get方法)对其访问。
(1)封装的本质:把对象的属性隐藏在对象内部,让外部无法直接操作和修改。
(2)封装的优点:
1)提高代码的安全性,重要信息可以私有化,不对外暴露;
2)提高代码的复用性,常用的代码或者功能封装到方法中,可以在其他地方反复调用;
3)封装代码的实现细节,便于修改内部代码,提高可维护性;
4)简化外部的调用,便于调用者使用。
private是一个权限修饰符,可以修饰成员变量和成员方法;被其修饰的成员只能在本类中被访问。private只是一种封装的体现,不能说封装就是私有。 -
继承:类和类之间的关系有很多中,继承就是其中一种关系,除此之外还有依赖、组合、聚合等。
(1)继承的本质:继承描述的是事物之间的所属关系,这种关系是: is-a 的关系。java中的类与类之间是单继承,但Java运行多重继承。
(2)继承的作用:子类继承父类,子类就可以继承父类中定义的属性和方法。父类中的私有属性和方法,子类中不能访问。父类中的构造器,子类是不能继承的。父类更通用,子类更具体。
(3)继承的优点:
1)提高代码的复用性。
2)类与类之间产生了关系(is-a),这是使用多态特性的前提。
(4)继承的关键字是extends:
public class A{}
class B extends class A{}
(5)java中,如果没有给类指定父类的话,那么这个类会默认继承父类Object。Object类中没有属性。java中,每个类都是直接或者间接了继承了Object,可以说Object是所有类的父类类型。
(6)子类继承父类,创建子类对象的时候,会先默认调用父类的构造器对父类中的属性进行初始化,初始化完成后再给子类使用。
- 多态:
多态的前提是继承:子类继承父类;子类重写父类中的方法;父类的引用指向子类对象。编译时多态:重载。运行时多态✨
成员变量:编译看左边(父类),运行看左边(父类)。
成员方法:编译看左边(父类),运行看右边(子类)。
静态方法:编译看左边(父类),运行看左边(父类)。
类实现接口,这是一种特殊形式的继承,多态也可以体现在类和接口的关系中。接口中的引用调用方法,调用到的是实现类中重写的方法。
引用类型的转换:向上转换、向下转换。
(1)向上转型(子类转父类):多态本身就是将子类的对象赋值给父类的引用,这就是一个自动向上转型的过程。(类型自动转换)
(2)向下转型(父类转子类):父类类型向子类类型转换,是向下转换的过程。(类型强制转换)
为什么要向下转型?父类引用是无法调用到子类中独有的方法的。
instanceof可以用于判断当前父类的引用到底是执行哪一个子类对象。
多态的应用:如果把父类作为形参,那就可以接收任意类型的子类对象。
类是组织代码的基本单元。类是一组相关属性和行为的集合,是对某种具体事物的抽象描述。
类是对一类事物的描述,是抽象的;
对象是一类事物的实例,是具体的;
类是对象的模板,对象是类的实体。
引用:引用类型的变量简称引用,引用可以指向对象。
对象没取名字也可以使用:(new Student()).方法名,但这样只能使用一次。
根据一个类可以创建多个对象,一个对象可以被多个引用指向,通过引用可以对象进行操作,例如访问对象的属性和调用方法。
创建和初始化对象的过程
Student s = new Student();
(1)对Student类进行类加载,同时初始化类中静态的属性赋默认值,给静态方法分配内存空间;
(2)执行类中的静态代码块;
(3)堆区中分配对象的内存空间,同时初始化对象中的非静态的属性赋默认值;
(4)调用Student的父类构造器;
(5)对Student中的属性进行显示赋值(比如:public int id=1;)。
(6)执行匿名代码块;
(7)执行构造器代码;
(8)=号赋值操作,把对象的内存地址赋给变量s。也就是引用指向对象,s获取到堆内存中Student对象的地址值。
注意:
(1)子类重写父类的方法,在创建子类的对象的过程中,默认调用的一定是子类中重写的方法。
(2)非静态属性的显式赋值,是在父类构造器执行结束之后和子类中的匿名代码块执行之前的时候。
方法:方法定义在类中,属于类的成员,所以也可以叫做成员方法,成员属性,可以叫做成员变量。
//定义方法的格式:
修饰符 返回类型 方法名(参数列表)抛出异常 {
//...
}
修饰符
static
被static修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static声明的成员变量属于静态成员变量,静态变量存放在Java内存区域的方法区。
方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与Java堆区分开来。
HotSpot虚拟机(热点虚拟机)中方法区也常被称为"永久代",本质上两者并不等价。仅仅是因为HotSpot虚拟机设计团队用永久代来实现方法区而已,这样HotSpot虚拟机的垃圾收集器就可以像管理Java堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。
- 静态成员变量:static修饰的属性,就是静态属性
(1)静态属性是属于类的,可以直接使用类名来访问,也可以使用对象访问,但推荐使用类名访问,调用格式:类名.静态变量名。并且这个类是所有对象共享的。
(2)静态属性的存储位置:类中的静态属性,跟随着类,一起保存在内存中的方法区。
(3)静态属性的初始化:类加载到内存中(方法区)的时候,系统就会给类中的静态属性做初始化赋默认值,就算还没有创建对象,只要这个类加载到了内存,就可以直接使用类名来访问静态属性,因为这个时候静态属性已经完成了初始化赋默认值的操作。
(4)在Java类中,可以用static修饰属性、方法、代码块、内部类。static修饰的方法不能被重写。
非静态成员变量:
(1)非静态属性,是属于对象的,一定要使用对象来访问。
(2)当创建对象的时候,对象中只会保存类中定义的非静态属性的信息,而静态属性是不会进入到对象中的。
(3)非静态属性的初始化时间:非静态属性创建对象后,系统会自动给对象中的非静态属性做初始化赋默认值,也正是因为这个原因,非静态属性只有在创建对象后,使用对象才能访问。
(静态)类变量和(非静态)成员变量的区别?
(1)所属不同:静态变量属于类;非静态变量属于对象,也叫实例变量。
(2)内存中的位置不同:静态变量存储在方法的静态区;成员变量存储在堆内存。
(3)开辟内存的时间不同:静态变量随着类加载而加载;成员变量随着对象的创建而存在。
(4)调用方式不同:静态变量可以通过类名调用,也可以通过对象调用;成员变量只能通过对象名调用。
-
静态方法和非静态方法:静态只能访问静态。
(1)静态方法属于类本身,而非静态方法属于该类生成的对象。
1)类加载完成之后,就可以直接使用类名访问静态属性和静态方法。
2)创建对象之后,才可以使用对象访问非静态属性和调用非静态方法。
(2)在类外部调用静态方法时,可以使用类名来调用,也可以使用对象来调用,但推荐使用类名。而实例方法中,调用格式:类名.静态方法名。调用静态方法无需创建对象。
(3)因为在类加载完成的时候,往往在内存中,还没有创建这个类的对象,没有对象(也就没有this),所以静态方法中不能访问this。所以静态方法中不能调用类中的非静态方法或非静态属性。
1)在类加载的时候,JVM会优先给类中的静态属性做初始化,给类中的静态方法分配内存空间。而类中非静态属性的初始化,非静态方法的分配空间,是要等到创建对象之后才会进行的。
2)当非静态方法可以调用时,说明肯定已经创建对象了,同时也说明了这个类早就完成了类加载,那么类中的静态属性和静态方法,现在肯定是可以访问和调用类中的静态属性和静态方法的。
(4)类中的构造器,可以给非静态属性做初始化,但是不能给静态属性做初始化。因为我们可以绕过创建对象的步骤,直接使用类名访问这个静态属性,而这样访问到的只会是这个属性的默认值。 -
静态代码块和非静态代码块
(1)静态代码块:也叫做静态初始化代码块,它的作用就是给类中的静态属性做初始化的。
static{
//...
}
1)静态代码块的初始化时间:由于静态代码块没有名字,我们并不能主动调用,它会在类加载的时候,自动执行。所以静态代码块,可以更早的给类中的静态属性,进行初始化赋值操作。并且,静态代码块只会自动被执行一次,因为JVM在一次运行中,对一个类只会加载一次。
(2)非静态代码块:也叫做匿名代码块,匿名代码块作用是给非静态属性做初始化操作。
{
//...
}
1)匿名代码块执行的时刻:
由于匿名代码块没有名字,我们并不能主动调用,它会在创建对象的时候,构造器执行之前,自动执行。并且每次创建对象之前,匿名代码块都会被自动执行。
2)类中的构造器,既能给非静态属性进行初始化,又能配合new关键字进行对象的创建,所以匿名代码块使用的场景较少,它能完成的工作,使用构造器也一样可以完成。
静态导入:在自己的类中,要使用另一个类中的静态属性和静态方法,那么可以进行静态导入,导入完成后,可以直接使用这个类中的静态属性和静态方法,而不用在前面加上类名。静态导入是指在import时,把别的类的静态属性和方法导入。(jdk1.5之后出现)import static 类名;。
在Java中,直接使用{}括起来的代码段,就是代码块。根据其位置和声明的不同,分为:局部代码块,构造代码块,静态代码块,同步代码块。
(1)局部代码块:
在方法中出现,用于限定变量的生命周期,及早释放,提高内存利用率;
(2)构造代码块(匿名代码块,初始化代码块):
在类中方法外,多个构造方法中相同的代码存放到一起,每次调用构造器前都会执行。
(3)静态代码块:
在类中方法外出现,构造代码块基础上加static,用于给类进行初始化,在加载的时候执行,只执行一次。
final
final修饰符,可以用来修饰类、变量、方法:
1)用final修饰的类不能被继承,也就是说这个类是没有子类的。
2)用final修饰的方法可以被子类继承,但是不能被子类的重写。
3)用final修饰的变量就变成了常量,并且它只能被赋一次值,第二次赋值就会报错。
final修饰不同类型的变量时:
(1)final修饰非静态成员变量:JVM不会为其默认赋值,我们需要手动在声明的同时;匿名代码块中;构造器中赋值,并且类中出现的所有构造器都要赋值。
(2)final修饰静态成员变量:JVM不会为其默认赋值,需要手动在声明的同时;静态代码块中赋值。
(3)final修饰引用类型变量:此时的final指的是,引用s的指向的对象不能改变,但是可以使用s来操作当前指向的对象属性和方法。
使用final方法的原因是什么?
(1)把方法锁定,以防任何继承类修改它的含义;
(2)效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。
abstract
abstract修饰符,可以修饰类、方法:
(1)如果abstract修饰方法,那么该方法就是抽象方法。抽象方法的特点就是只有方法的声明;没有方法的实现。
(2)如果abstract修饰类,那么该类就是抽象类。抽象类中可以写抽象方法,不能进行实例化创建对象。抽象类中可以没有抽象方法,但抽象方法所在的类一定要声明为抽象类。抽象类就是用来被子类继承的,子类继承抽象类,就要实现抽象类中的抽象方法,如果不实现所有的抽象方法,那么这个子类就要把自己声明成抽象类,再等其自身的子类实现抽象方法。抽象类中也有构造器,子类创建对象时要先调用父类的构造器。
(3)抽象类的成员变量可以是变量,也可以是常量。但abstract再不能修饰成员变量。抽象有构造器。abstract不能和staic、private、final一起使用。
interface
1.接口是除了类和数组之外,另外一种引用数据类型。
2.接口和类不同,类的内部封装了成员变量、构造方法和成员方法,而接口的内部主要就是封装了方法和静态常量。
(1)接口中的属性都是公共的静态常量,常量的名字一般需要全大写。默认是public static final修饰的。
(2)jdk8允许在接口中编写静态方法和默认方法;jdk9允许接口中编写私有方法。
(3)接口中的抽象方法不需要写abstract修饰符,因为接口中的方法默认是抽象方法,默认的修饰符是public abstract。
3.接口的定义和类很类似,但是接口需要使用interface关键字来定义,而类是用class关键字。
4.接口最终也会被编译成.class文件,但一定要明确接口并不是类,而是另外一种引用数据类型。
5.类与类之间的关系是单继承;
类与接口之间的关系是一个类可以实现一个或多个接口;
接口与接口之间可以单继承或多继承的关系。
(接口里面的接口:Map中的Entry,Stream里面有有关Builder。)
如果一个类实现了某个接口,就需要把这个接口中的所有抽象方法都实现,否则就要声明成抽象类,让其子类实现抽象方法。
6.从广义的角度来说,对外提供规则的都是接口。接口是对Java单继承的补充。
7.接口用关键字interface表示:interface 接口名 {}
类实现接口用implements表示:class 类名 implements 接口1,接口2… {}
8.接口不能实例化,要实例化就按照多态的方式来实例化。✨
抽象类和接口的区别?
1.成员区别:
抽象类的成员变量可以是变量或常量,成员方法可以抽象,也可以非抽象;
接口的成员变量只可以公有静态常量,成员方法只可以抽象。
2.关系区别:
类与类之间的关系是单继承;类与接口之间的关系是一个类可以实现一个或多个接口;接口与接口之间可以单继承或多继承的关系。
3.设计理念区别:
抽象类被继承体现的是:is a的关系。抽象类中定义的是该继承体系的共性功能。
接口被实现体现的是:like a的关系。接口中定义的是该继承体系的扩展功能。
访问修饰符
类中的属性和方法,可以使用以下四种修饰符进行访问控制:public > protected > default > private
public,公共的,在所有地方都可以访问。
protected,受保护的,当前类中、子类中,同一个包中其他类中可以访问。不同包中的非子类就不能访问。
default,默认的,也叫做缺省。当前类中、同一个包中的子类中可以访问。default指的是空修饰符,并不是default这个关键字。其他包的类都不能对其进行访问。
private,私有的,只有当前类中可以访问。
类,变量,方法…的修饰符使用规则?
(1)修饰符:
权限修饰符:private,default默认的,protected,public
状态修饰符:static,final
抽象修饰符:abstract
(2)类:(public使用最多)
权限修饰符:默认修饰符,public
状态修饰符:final
抽象修饰符:abstract
(3)成员变量:private使用最多
权限修饰符:private,默认的,protected,public
状态修饰符:static,final
(4)构造方法:public使用最多
权限修饰符:private,默认的,protected,public
(5)成员方法:public使用最多
权限修饰符:private,默认的,protected,public
状态修饰符:static,final
抽象修饰符:abstract
(6)除此以外的组合规则:
成员变量:public static final
成员方法:public static;public abstract;public final。
返回类型:
方法执行完,如果有要返回的数据,那么在方法上就一定要声明返回数据的类型是什么类型;如果没有要返回的数据,那么方法上就必须使用void进行声明。
只有一种特殊的方法没有返回类型,也不写void,就是构造方法,也就是构造器。
构造器
(1)什么是构造器?类中的构造器也称为构造方法、构造函数,是在创建对象的时候必须要调用的。
(2)构造器的特点:必须和类的名字保持一致;必须没有返回类型,也不能写void,没有返回值return。
(3)构造器的作用?
使用new关键字来创建对象的时候,后面跟的必须是类中存在的构造器。
构造器中的代码,在对象创建后会被调用,从而可以完成对象的初始化工作。构造器就是用来给对象的属性进行初始化的。
(4)构造器的重载?
除了默认的无参构造器之外,在类中还可以对构造器进行重载,让构造器可以接收一些参数,然后使用这些参数进行对象的初始化工作。构造器重载只看参数列表。
(5)构造器之间的调用?
使用this关键字,可以在构造器中,调用另一个构造器。
(6)默认构造器?
如果我们在类中定义了一个有参构造器,那么系统就不在为该类生成无参的默认构造器。有参构造器会覆盖无参构造器,所以要显式声明无参构造器。
(7)创建对象一定会调用构造器。
构造代码块与构造函数(构造器)的区别?
构造(非静态)代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化,因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块。也就是说,构造代码块中定义的是不同对象共性的初始化内容。
2)声明有返回类型的方法,要使用return关键字。
方法名:满足标识符的命名规则,推荐使用有意义的方法名。
参数列表:无参、一参、多参、可变参。
可变参数:可以把一个数组当做参数传进来,因为可变参数本质就是一个数组。
可变参数和普通参数共存的时候,可变参数必须放到最后一个参数的位置。
方法的形参是类名时如何调用?
当方法的形参是一个类类型(引用类型)时,需要的就是这个类的对象。
抛出的异常:方法上声明时的抛出异常使用的是throws关键字,因为可以抛出多个异常,而在方法内的异常抛出使用的是throw关键字。(手动抛出)
异常根类:java.lang.Throwable,两个子类型:java.lang.Error和java.lang.Exception。
Error ,表示错误情况,一般是程序中出现了比较严重的问题,并且程序自身并无法进行处理。
Exception ,表示异常情况,程序中出了这种异常,大多是可以通过特定的方式进行处理和纠正的,并且处理完了之后,程序还可以继续往下正常运行。
Exception 中并没有定义方法,它的方法都是从 Throwable 中继承过来的,其中常用的方式有:
printStackTrace();//打印输出当前发送异常的详细信息(重要);
getMessage();//返回异常对象被抛出的时候,所携带的信息,一般是异常的发生原因(重要);
printStackTrace(PrintWriter s);//方法重载,可以指定字符输出流,对异常信息进行输出;
printStackTrace(PrintStream s);//方法重载,可以指定字节输出流,对异常信息进行输出。
Exception类的类型:
1.编译时异常:继承自Exception类,也称为受检查的异常(checked exception),在编译期间,编译器会主动检查这类异常,发现后会报错提示我们去处理这种异常。
2.运行时异常:继承自RuntimeException类,不受检查,不要求程序员必须去处理。
常见的错误Error有:内存(堆栈)溢出;jvm错误;字节码版本错误。
常见的编译时异常有:IOException;ClassNotFoundException;SQLExceotion;NoSuchMethodException;InterruptedException…
常见的运行时异常有:ArrayIndexOutOfBoundsException;ArithmeticException;ClassCastException;NumberFormatException…
断言:assert,用于确定系统中的一些关键性条件必须成立。也就是assert后面的布尔表达式必须为true。
默认情况下,JVM是没有开启断言功能的,需要通过给JVM传参打开此项功能需要使用 -enableassertions 或者 -ea JVM参数。
java -ea com.briup.Test 或者在控制台输入-ea的参数。
参数的传递:方法中的参数分为形参和实参。
形参的作用就是接收外部传过来的实际参数的值。
实参是实际调用方法时传递的参数。传递参数有两种类型:值传递、引用传递。
(1)值传递是指方法的参数是基本数据类型。
值传递实际上是实参把自己存储的值赋值给形参,形参之后如何操作对实参没有影响。
(2)引用传递是指方法的参数类型是引用类型,因为实参和形参都是引用类型变量,都是保存的对象在堆区中的内存地址,所以称其为引用传递。
引用传递是指实参将自己存储的对象地址,赋值给了形参,这个时候实参和形参指向了同一个对象,那么不论形参还是实参操作对象,比如用形参给属性赋值,那么实参这时候就可以看到整个对象中属性的变量。
类中的非静态方法一定要使用类的对象来进行调用,没有其他的方式。
方法重载和方法重写的区别?
1.类中有多个方法,具有相同的方法名,但是方法的参数各不相同,叫做方法的重载(overload)。
(1)方法的重载,必须是在同一个类中。
(2)重载的要求:
1)方法的名字必须相同;
2)方法的参数列表必须不同:参数的类型不同/个数不同/顺序不同。
3)方法的修饰符、返回类型、抛出异常这些地方没有限制(可以相同,也可以不同,但一般都是相同的)
2.如果子类和父类中出现相同的方法,叫做方法重写(Override)。
(1)方法重写发生在子父类之间。
(2)重写的要求:
1)方法名必须相同;
2)参数列表必须相同;
3)访问控制修饰符可以被扩大,但是不能被缩小:public > protected > default > private;
4)方法抛出异常类型的范围可以被缩小,但是不能被扩大
5)返回类型可以相同,也可以不同:
如果父类的返回类型是引用类型变量,子类重写后的方法返回类型可以和父类方法的返回类型保持一致,也可以是父类方法返回类型的子类型。
如果父类的返回类型是基本数据类型,那么子类重写后的返回类型必须和父类的保持一致。
6)父类中的静态方法、私有方法不能被子类重写。
子类继承父类,在调用方法的时候,如果子类中没用重写,那么调用的是从父类继承的方法,如果子类重写了这个方法,那么将会调用到子类中重写后的方法。
this和super的区别?
(1)this关键字的使用场景:
1)区别成员变量和局部变量;
2)调用类中的其他方法;
3)调用类中的其他构造器,但调用构造器只能在类中的一个构造器中调用另一个构造器,不能在普通方法中调用类的构造器。并且this调用构造器的代码必须是当前构造器的第一句代码。
this所代表的是所在类的当前对象的地址值(引用),也就是对象自己的引用。
如果一个类中创建两个对象,this是代表什么?
每一个对象中都有自己的this,和其他对象互不影响。方法被哪个对象调用,方法中的this就代表那个对象。即谁在调用方法,this就代表谁。
(2)在子类中,使用super关键字:
1)访问父类中的属性
2)调用父类中的方法
3)调用父类中的构造器:
super();表示的是显式调用父类的无参构造器。
this和super不能同时出现。
如果父类没有无参构造器,子类可以通过this和super解决。