文章目录
类和对象
1. 引用
如果一个变量的类型是类类型,而非基本类型,那么该变量又叫做引用。
引用-HOW2J
2. 成员变量和局部变量
成员变量
成员变量定义在类中,在整个类中都可以被访问。
成员变量随着对象的建立而建立,存在于对象所在的堆内存中。
成员变量有默认初始化值
局部变量
- 局部变量只定义在局部范围内,如:函数(例如for循环)内,语句内等
- 局部变量存在于栈内存中。作用的范围结束,变量空间会自动释放。
- 局部变量没有默认初始化值(必须手动初始化)。 当没有手动初始化局部变量时,编译器会报错 (Variable might not have been initialized error)
- 原因:初始化时机不同,类的成员变量在开辟堆内存时就会被JVM统一初始化,很方便,但是局部变量可能在方法被调用时还没有定义,每次都让JVM定义很消耗资源。具体为什么 Java 中全局变量不一定初始化,局部变量必须初始化?
for (int i ; i<= test2.length ; i++){// 编译会报错
}
3. 匿名对象
匿名对象是对象的简化形式
匿名对象两种使用情况
• 当对对象方法仅进行一次调用的时
• 匿名对象可以作为实际参数进行传递
4. 封装(Encapsulation)
指隐藏对象的属性和实现细节,仅对外提供公共访问方式
实现封装的方法: private(私有)关键字
private关键字:
- 是一个权限修饰符。
- 用于修饰成员(成员变量和成员函数)
- 被私有化的成员只在本类中有效。
常用方法:
将成员变量私有化,对外提供对应的set ,get 方法对其进行访问。提高对数据访问的安全性。
5. 构造函数
- 作用:对对象进行初始化,设定基本特性/行为
- 对象一建立,就会调用与之对应的构造函数
构造函数的特点
- 函数名与类名相同
- 不用定义返回值类型
- 不可以写return语句
- 当一个类中没有构造函数时,会自动建立无参构造函数
- 但是自定义构造函数够,就不自动建立了。
- 构造函数也可以重载(初始化时的参数列表可能不一样,所以我们要分别建立构造函数,对参数不同进行不同的初始化)
- 构造函数只在对象建立时执行一次
- 构造函数也可以私有化(private),但是这样就不能通过这个构造方法建立对象(如果所有的构造函数都被私有化,那么这个类的对象不可能被创建)
- 构造函数间互相调用不能写函数名,要用this关键字(但要注意避免死循环)
6. 构造代码块(开发不用,面试会考)
- 对象一建立就运行(优先于构造函数)
和构造函数的区别:
- 构造代码块是给所有对象进行统一初始化
- 而构造函数是给对应的对象进行初始化。
- 构造代码块一定会执行,所以写所有对象的共性初始化内容
7. This关键字
- this代表本类对象的引用(在哪个对象的方法里,就代表哪个对象)
- 但凡本类功能内部使用了了本类对象,都用this表示
用法1:用于区分局部变量和成员变量同名的情况
class PersonDemo3
{
public static void main(String[] args)
{
Person p = new Person();
}
}
class Person
{
private int age;
Person(int age)
{
this.age = age;// 这里是Person类的对象p,所以this.age 就是 p.age
}
}
用法2: 构造函数间的相互调用
- 在构造函数互相调用时,this语句必须写在该构造方法的第一行。因为初始化动作要先执行,如果初始化里面还有更细节的初始化,应当先执行这个更细节的初始化
8. 静态(Static)关键字
- 是一个修饰符,用于修饰成员(成员变量,成员函数).
- 当成员被静态修饰后,除了可以被对象调用外,还可以直接被类名调用。用法:类名.静态成员
- 特有内容随着对象存储,共享内容随着类存储(存放在方法区)
static特点
- 随着类的加载而加载。也就是说:静态会随着类的消失而消失。说明它的生命周期最长。
- 优先于的对象存在 静态是先存在。对象是后存在的。
- 被所有对象所共享
- 可以直接被类名所调用。
实例变量和类变量(静态变量)的区别
- 存放位置。
类变量随着类的加载而存在于方法区中。
实例变量随着对象的建立而存在于堆内存中。 - 生命周期:
类变量生命周期最长,随着类的消失而消失。
实例变量生命周期随着对象的消失而消失。
静态使用注意事项
- 静态方法只能访问静态成员(因为非静态成员属于对象),非静态方法既可以访问静态也可以访问非静态成员
- 静态方法中不可以定义this,super关键字。因为静态优先于对象存在。所以静态方法中不可以出现this
- 主函数是静态的
使用静态的利弊
利处
- 对对象的共享数据进行单独空间的存储,节省内存(没有必要每一个对象中都存储一份),还可以直接被类名调用。
弊端
- 生命周期过长
- 访问出现局限性。(静态只能访问静态)
什么时候使用静态?
什么时候定义静态变量(类变量)
- 当对象中出现共享数据时,该数据被静态所修饰。
- 对象中的特有数据要定义成非静态存在于堆内存中。
什么时候定义静态函数?
- 当功能内部没有访问到非静态数据(对象的特有数据),那么该功能可以定义成静态的。
静态的应用:工具类
-
每一个应用程序中都有共性的功能,可以将这些功能进行抽取和独立封装。以便复用。
-
可以通过建立ArrayTool的对象使用这些工具方法,对数据进行操作。
使用工具类的问题
- 对象是用于封装数据的,可是ArrayTool对象并未封装特有数据。
- 操作数组的每一个方法都没有用到ArrayTool对象中的特有数据。
所以其实是不需要对象的。可以将ArrayTool中的方法都定义成static的。直接通过类名调用即可。
但是该类还是可以被其他程序建立对象的。为了更为严谨,强制让该类不能建立对象。
可以通过将构造函数私有化完成。
9. main函数
- 主函数是一个特殊的函数。作为程序的入口,可以被jvm调用
主函数的定义
- public:代表着该函数访问权限是最大的。
- static:代表主函数随着类的加载就已经存在了。
- void:主函数没有具体的返回值。
- main:不是关键字,但是是一个特殊的单词,可以被jvm识别。
- (String[] arr):函数的参数,参数类型是一个字符串类型的数组。
- 主函数是固定格式的:jvm识别。jvm只能识别String[] args这种参数列表的main函数(重载也没用)
- jvm在调用主函数时,传入的是new String[0];
10. 对象初始化过程
person p = new Person(“zhangsan”,20);
该句话都做了什么事情?
- 因为new用到了Person.class.所以会先找到Person.class文件并加载到内存中。
- 执行该类中的static代码块,如果有的话,给Person.class类进行初始化。
- 在堆内存中开辟空间,分配内存地址。
- 在堆内存中建立对象的特有属性。并进行默认初始化。
- 对属性进行显示初始化。
- 对对象进行构造代码块初始化。
- 对对象进行对应的构造函数初始化。
- 将内存地址付给栈内存中的p变量。
单例设计模式
- 设计模式:解决某一类问题最行之有效的方法。
- java中有23种设计模式:
单例设计模式:解决一个类在内存只存在一个对象。
当需要将该事物的对象保证在内存中唯一时,就采用单例设计模式
保证对象唯一性
- 为了避免其他程序过多建立该类对象。先禁止其他程序建立该类对象(方法:将构造函数私有化)
- 还为了让其他程序可以访问到该类对象,只好在本类中,自定义一个对象(方法:在类中创建一个本类对象)
- 为了方便其他程序对自定义对象的访问,可以*对外提供一些访问方式(方法:提供一个方法来获取到该对象)。
饿汉式和懒汉式
饿汉式
先初始化对象,类被加载到内存中时就已经建立好了对象
class Single
{
private static Single s = new Single();
private Single(){}
public static Single getInstance()
{
return s;
}
}
懒汉式
对象是方法被调用时,才初始化,也叫做对象的延时加载。
class Single
{
private static Single s = null;
private Single(){}
public static Single getInstance()
{
if(s==null)
{
synchronized(Single.class)
{
if(s==null)
s = new Single();
}
}
return s;
}
}
开发时一般用饿汉式
11. 继承
语法: class A extends B
继承的优点
- 提高了代码的复用性。
- 让类与类之间产生了关系。有了这个关系,才有了多态的特性。
- 注意:千万不要为了获取其他类的功能,简化代码而继承。必须是类与类之间有所属关系才可以继承。
- 判断所属关系的方式: 继承一下,然后看看父类中所有的内容是不是子类都应该具备
- 在java中,一个类只能有一个直接父类(当多个父类中定义了多个同名内容时就会产生歧义)
如何使用一个继承体系中的功能
- 先查阅体系父类的描述,因为父类中定义的是该体系中共性功能。
- 通过了解共性功能,就可以知道该体系的基本功能。
- 在具体调用时,要创建最子类的对象,为什么呢?
- 有可能父类不能创建对象(如静态)
- 创建子类对象可以使用更多的功能,包括基本的也包括特有的。
总结:查阅父类功能,创建子类对象使用功能。
子父类出现后,类成员的特点
- 在堆内存中使用new语句创建对象时,先加载父类.class文件到内存,再加载子类.class文件到内存。
变量
如果子类中出现非私有的同名成员变量时,
- 子类要访问本类中的变量,用this
- 子类要访问父类中的同名变量,用super。
- super的使用和this的使用几乎一致。this代表的是本类对象的引用。super代表的是父类对象的引用。
方法
函数的另一个特性:override重写
- 当子类出现和父类一模一样的函数时,当子类对象调用该函数,会运行子类函数的内容。如同父类的函数被覆盖一样。
- 当子类继承父类,沿袭了父类的功能到子类中,但是子类虽具备该功能,但是功能的内容却和父类不一致,这时,没有必要定义新功能,而是使用重写,保留父类的功能定义,并重写功能内容。
注意事项
- 子类重写父类,必须保证子类权限大于等于父类权限,才可以重写,否则编译失败。
- 静态只能重写静态。(先有静态才有对象)
构造函数
- 构造函数不可以重写(因为构造函数就不是函数,没有返回值类型,自然没有函数可以被重写的特点)
- 在对子类对象进行初始化时,父类的构造函数也会运行
- 因为子类的所有构造函数默认第一行有一条隐式的语句 super()(访问父类中空参数的构造函数)
- 当父类中没有空参数的构造函数时,就要手动指定访问父类中哪一个构造函数
- super和this关键字一样,都只能放在构造函数的第一行
- 所以若要调用本类中的其他构造函数this(),隐式的super()就会被省略
原因
- 因为父类中的数据子类可以直接获取。所以子类对象在建立时,需要先查看父类是如何对这些数据进行初始化的。
class Father
{
int num ; // 这里先进行了隐式初始化
Father()
{
num = 60; //这里后进行显示初始化
System.out.println("father run");
}
}
class Son extends Fu
{
Son()
{
//super();
System.out.println("son run");
}
}
class ExtendsDemo
{
public static void main(String[] args)
{
Son z = new Son(0);
System.out.println(z.num);
}
}
在这个例子中,因为父类的构造函数对num进行了赋值,访问子类对象的同名参数值时就需要先访问父类的构造函数
- 如果要访问父类中指定的构造函数,可以通过手动定义super语句的方式来指定。
- 所以子类中 至少会有一个构造函数会访问父类中的构造函数。
- Object类是所有类的父类
12. final关键字
继承的存在一定程度上打破了封装性。所以我们会用final修饰符
- 可以修饰类,函数,变量。
- 被final修饰的类不可以被继承。为了避免被继承,被子类复写功能。
- 被final修饰的方法不可以被override。
- 被final修饰的变量是一个常量,只能赋值一次,既可以修饰成员变量,有可以修饰局部变量。当在描述事物时,一些数据的出现值是固定的,那么这时为了增强阅读性,都给这些值起个名字。方便于阅读。而这个值不需要改变,所以加上final修饰。作为常量:常量的书写规范所有字母都大写,如果由多个单词组成。单词间通过_连接。
- 常和public static连用,组成全局常量。
- 内部类定义在类中的局部位置上时,只能访问该局部被final修饰的局部变量。
13. 抽象类
当多个类中出现相同功能,但是功能主体不同,
这是可以进行向上抽取。这时,只抽取功能定义,而不抽取功能主体。
这种方法没有方法体,要用abstract关键字修饰,写法为
abstract class Student
{
abstract void study();// 没有方法体
}
- 抽象方法一定在抽象类中
- 抽象方法和抽象类都必须被abstract关键字修饰。
- 抽象类不可以用new创建对象。因为调用抽象方法没意义(没有方法体)。
- 如果要使用抽象类中的抽象方法,必须由子类override所有的抽象方法后,建立子类对象调用。如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类。
为什么要使用抽象类
- 强迫子类override抽象方法
- 抽象类中可以不定义抽象方法,这样做仅仅是不让该类建立对象。
14. 接口
接口可以认为是一个特殊的抽象类。
- 当抽象类中的方法都是抽象的,那么该类可以通过接口的形式来表示。
-
- class用于定义类
-
- interface 用于定义接口。
接口的定义:
- 接口中常见定义:常量,抽象方法。
- 接口中的成员都有固定修饰符。(即使不写也会自动补上)
- 常量:public static final
- 方法:public abstract
接口的特征
- 接口是不可以创建对象的(因为有抽象方法)
- 接口的所有方法都需要被子类实现(override),子类才可以实例化,否则子类仍然是抽象的。
- 一个类可以实现多个接口(实现多继承的形式)(因为接口没有方法体,即使两个接口里的同名抽象类被子类实现了也不会混淆)
- 一个类可以同时继承一个类和实现多个接口
- 接口之间也可以有继承关系,甚至一个接口可以继承多个接口
接口型引用
接口型引用只能指向自己的子类对象
interface User{ //一个接口
public abstract void someMethod();
}
public class Main {
public static void main(String[] args) {
User thisUser = new UserImpl();// 指向thisUser引用,所有User接口的实现类及其子类都可以放到thisUser这个容器里
}
}
class UserImpl implements User{
@Override
public void someMethod() {
}
}
JDK1.8之后的新增特性
lambda(λ)表达式
函数型接口
default 修饰符
- 1.8之前:接口类只能定义方法名,返回类型和参数列表
- 1.8之后:接口类可以有静态static方法,默认default方法,也就是说接口中可以有方法的具体实现。
如果想在接口类中定义了方法并给出具体的方法体,可以通过
- 将该方法定义为静态方法。
- 为该方法加上default关键字。
interface User{
public abstract void someMethod();//定义方法
public static final int someVariation = 0;//定义变量
public static void someMethod2() {//JDK1.8之后新增:接口中可以定义static方法
System.out.println("do some thing...");
}
public default void someMethod3(){//JDK1.8之后新增:接口中可以定义default方法(相当于类的成员方法)
System.out.println("do some thing...");
}
}
15. 多态(Polymorphism)
多态可以理解为事物存在的多种体现形态
多态的体现
- 父类的引用指向了自己的子类对象。
- 父类的引用也可以接收自己的子类对象。
多态的前提
- 必须是类与类之间有关系。要么继承,要么实现。
- 通常还有一个前提:存在override。
多态的好处
- 多态的出现大大的提高程序的扩展性。
多态的弊端
- 提高了扩展性,但是只能使用父类的引用访问父类中的成员。
多态的应用
转型
向上转型upcasting
Animal a = new Cat();//类型提升。 向上转型。
向下转型downcasting
强制将父类的引用。转成子类类型。向下转型
//如果想要调用猫的特有方法时,如何操作?
Cat c = (Cat)a;
c.catchMouse();
- 注意,不要出现将父类对象转成子类类型这样的操作
- 多态自始至终都是 子类对象在做着变化
instance of
instanceof : 用于判断对象的类型。 使用方法:对象 intanceof 类型(类类型 接口类型)
16.内部类
当描述事物时,事物的内部还有事物,该事物用内部类来描述。
因为内部事务在使用外部事物的内容。
class Body
{
private class XinZang //心脏在人体里,可以直接访问人体里的东西
{
}
public void show()
{
new XinZang().
}
}
所以类中除了可以定义变量和方法外,还可以定义其他类——内部类
- 一个类里可以定义多个内部类
- 内部类可以直接访问外部类中的成员(包括private的)(因为内部类里持有了一个内部类的引用)
- 外部类要访问内部类,必须要建立内部类对象
- 在建立内部类的对象时,要使用全名称实例化对象(Outer.Inner in = new Outer().new Inner()😉
- 局部内部类不可以用static修饰(因为是局部变量,static 只能修饰成员)
匿名内部类
- 匿名内部类其实就是内部类的简写格式。
- 定义匿名内部类的前提:
-
- 内部类必须继承一个类或者实现接口。
- 匿名内部类的格式: new 父类或者接口( ){ 定义子类的内容 }
- 内部类本质上是一个匿名子类对象。而且这个对象有点胖(可以理解为带内容的对象)
- 匿名对象只能调用一次方法
- 在建立引用时建立的是父类的引用
AbsDemo d = new AbsDemo()//父类引用指向子类对象
{
int num = 9;
void show()
{
System.out.println("num==="+num);
}
void abc()
{
System.out.println("haha");
}
};
- 匿名内部类中定义的方法最好不要超过2个(因为使用匿名内部类继承的接口或抽象类中所有的方法都要实现)
17. 异常机制
异常就是程序在运行时出现不正常情况。
异常的由来
程序遇到的问题也是现实生活中一个具体的事物,也可以通过java的类的形式进行描述。并封装成对象。
其实就是java对不正常情况进行描述后的对象体现。
对于问题的划分
一种是不可解决的问题,一种是可解决的问题。
-
对于不可解决的,java通过Error类进行描述。
对于Error一般不编写针对性的代码对其进行处理。 -
对于可解决的,java通过Exception类进行描述。
对于Exception可以使用针对性的处理方式进行处理。
无论Error或者Exception都具有一些共性内容。
比如:不正常情况的信息,引发原因等。
异常的处理
如果不对异常做任何处理,虚拟机就会调用系统自动的异常处理机制,导致程序停止。
java 提供了特有的语句进行处理。
try
{
需要被检测的代码;
}
catch (异常类 变量)
{
处理异常的代码;(处理方式)
}
finally
{
一定会执行的语句;
}
异常对象的常见方法操作
- e.getMessage():获取异常信息
- e.toString(): 异常名称 :异常信息
- e.printStackTrace(): 异常名称,异常信息,异常出现的位置。
- printStackTrace()异常名称,异常信息,异常出现的位置。其实JVM默认的异常处理机制,就是在调用printStackTrace方法。打印异常的堆栈的跟踪信息。
throws 语句
- 开发者在功能上通过throws的关键字声明了该功能有可能会出现问题, 让使用者来处理,否则编译失败
- 子类抛出的异常只能是父类抛出的异常或者它的子异常
对多异常的处理
- 声明异常时,建议声明更为具体的异常。这样处理的可以更具体。
- 声明几个异常,就对应有几个catch块。不要定义多余的catch块
。 - 如果多个catch块中的异常出现继承关系,父类异常catch块放在最下面。
- 建立在进行catch处理时,catch中要定义具体处理方式。
不要简单定义一句 e.printStackTrace(),也不要简单的就书写一条输出语句。
自定义异常
因为项目中会出现特有的问题,而这些问题并未被java所描述并封装对象。
所以对于这些特有的问题可以按照java的对问题封装的思想将特有的问题。进行自定义的异常封装。
自定义异常的特点
- java所描述并封装的异常可以自动抛出,也可以手动抛出
- 自定义异常不能被java所识别,必须封装成对象并用throw手动抛出
- 当在函数内部出现了throw所抛出的异常对象,那么就必须要采取相应的处理。
- 要么在内部try catch处理。
- 要么在函数上声明让调用者处理(也是通过try catch)。
- 一般情况在,函数内出现异常,函数上需要声明
throws和throw的区别
- throws使用在函数上。
- throw使用在函数内。
- throws后面跟的异常类。可以跟多个。用逗号隔开。
- throw后跟的是异常对象。
RuntimeException子类
- Exceptoin中有一个特殊的子类异常RuntimeException 运行时异常。
- 如果在函数内容抛出该异常,函数上可以不用声明,编译一样通过。
- 如果在函数上声明了该异常。调用者可以不用进行处理。编译一样通过
之所以不用在函数声明,是因为不需要让调用者处理。
当该异常发生,希望程序停止。因为在运行时,出现了无法继续运算的情况,希望停止程序后,
对代码进行修正。
class Person
{
public void checkName(String name)
{
//if(name.equals("lisi"))//会发生NullPointerException
if("lisi".equals(name))//if(name!=null && name.equals("lisi"))
System.out.println("YES");
else
System.out.println("no");
}
}
main()
{
Person p = new Person();
p.checkName(null/ 字符串类型可以理解为一个类,所以可以接收"null"这个值
}
在上面这个例子中,传入空后程序员希望程序停止(因为名字为空是有问题的,必须让程序停掉,修正代码)如果捕获这个异常相当于把这个问题隐藏
自定义异常时:如果该异常的发生使得程序无法继续运行,
就让自定义异常继承RuntimeException。
异常的分类
1,编译时被检测的异常。(是可处理的,必须要用throws标识,让调用者处理(try 或继续抛出),例如计时器超时)
2,编译时不被检测的异常(运行时异常。RuntimeException以及其子类,不必标识,会引发程序停止,例如空指针异常)