JAVA面向对象基础

文章目录

类和对象

1. 引用

如果一个变量的类型是类型,而非基本类型,那么该变量又叫做引用
引用-HOW2J

2. 成员变量和局部变量

成员变量

成员变量定义在类中,在整个类中都可以被访问

成员变量随着对象的建立而建立,存在于对象所在的堆内存中。

成员变量有默认初始化值

局部变量

  • 局部变量只定义在局部范围内,如:函数(例如for循环)内,语句内等
  • 局部变量存在于栈内存中。作用的范围结束,变量空间会自动释放。
  • 局部变量没有默认初始化值(必须手动初始化)。 当没有手动初始化局部变量时,编译器会报错 (Variable might not have been initialized error)
 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特点

  • 随着类的加载而加载。也就是说:静态会随着类的消失而消失。说明它的生命周期最长。
  • 优先于的对象存在 静态是先存在。对象是后存在的。
  • 所有对象所共享
  • 可以直接被类名所调用。

实例变量和类变量(静态变量)的区别

  1. 存放位置。
    类变量随着类的加载而存在于方法区中。
    实例变量随着对象的建立而存在于堆内存中。
  2. 生命周期:
    类变量生命周期最长,随着类的消失而消失
    实例变量生命周期随着对象的消失而消失

静态使用注意事项

  1. 静态方法只能访问静态成员(因为非静态成员属于对象),非静态方法既可以访问静态也可以访问非静态成员
  2. 静态方法中不可以定义this,super关键字。因为静态优先于对象存在。所以静态方法中不可以出现this
  3. 主函数是静态的

使用静态的利弊

利处
  • 对对象的共享数据进行单独空间的存储,节省内存(没有必要每一个对象中都存储一份),还可以直接被类名调用
弊端
  1. 生命周期过长
  2. 访问出现局限性。(静态只能访问静态)

什么时候使用静态?

什么时候定义静态变量(类变量)
  1. 当对象中出现共享数据时,该数据被静态所修饰。
  2. 对象中的特有数据要定义成非静态存在于堆内存中。
什么时候定义静态函数?
  • 当功能内部没有访问到非静态数据(对象的特有数据),那么该功能可以定义成静态的。

静态的应用:工具类

  • 每一个应用程序中都有共性的功能,可以将这些功能进行抽取和独立封装。以便复用

  • 可以通过建立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);
该句话都做了什么事情?

  1. 因为new用到了Person.class.所以会先找到Person.class文件并加载到内存中。
  2. 执行该类中的static代码块,如果有的话,给Person.class类进行初始化。
  3. 堆内存中开辟空间,分配内存地址
  4. 在堆内存中建立对象的特有属性。并进行默认初始化
  5. 对属性进行显示初始化
  6. 对对象进行构造代码块初始化。
  7. 对对象进行对应的构造函数初始化。
  8. 内存地址付给栈内存中的p变量。

单例设计模式

  • 设计模式:解决某一类问题最行之有效的方法。
  • java中有23种设计模式:

单例设计模式:解决一个类在内存只存在一个对象

需要将该事物的对象保证在内存中唯一时,就采用单例设计模式

保证对象唯一性
  1. 为了避免其他程序过多建立该类对象。先禁止其他程序建立该类对象(方法:将构造函数私有化)
  2. 还为了让其他程序可以访问到该类对象,只好在本类中,自定义一个对象(方法:在类中创建一个本类对象)
  3. 为了方便其他程序对自定义对象的访问,可以*对外提供一些访问方式(方法:提供一个方法来获取到该对象)。

饿汉式和懒汉式

饿汉式

先初始化对象,类被加载到内存中时就已经建立好了对象


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

继承的优点

  1. 提高了代码的复用性
  2. 类与类之间产生了关系。有了这个关系,才有了多态的特性。
  • 注意:千万不要为了获取其他类的功能,简化代码而继承。必须是类与类之间有所属关系才可以继承。
  • 判断所属关系的方式: 继承一下,然后看看父类中所有的内容是不是子类都应该具备
  • 在java中,一个类只能有一个直接父类(当多个父类中定义了多个同名内容时就会产生歧义)

如何使用一个继承体系中的功能

  1. 先查阅体系父类的描述,因为父类中定义的是该体系中共性功能
  2. 通过了解共性功能,就可以知道该体系的基本功能。
  3. 在具体调用时,要创建最子类的对象,为什么呢?
  • 有可能父类不能创建对象(如静态)
  • 创建子类对象可以使用更多的功能,包括基本的也包括特有的。

总结:查阅父类功能,创建子类对象使用功能。

子父类出现后,类成员的特点

  • 在堆内存中使用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修饰符

  1. 可以修饰类,函数,变量
  2. 被final修饰的类不可以被继承。为了避免被继承,被子类复写功能。
  3. 被final修饰的方法不可以被override
  4. 被final修饰的变量是一个常量,只能赋值一次,既可以修饰成员变量,有可以修饰局部变量。当在描述事物时,一些数据的出现值是固定的,那么这时为了增强阅读性,都给这些值起个名字。方便于阅读。而这个值不需要改变,所以加上final修饰。作为常量:常量的书写规范所有字母都大写,如果由多个单词组成。单词间通过_连接。
  5. 常和public static连用,组成全局常量。
  6. 内部类定义在类中的局部位置上时,只能访问该局部被final修饰的局部变量。

13. 抽象类

当多个类中出现相同功能,但是功能主体不同,
这是可以进行向上抽取。这时,只抽取功能定义,而不抽取功能主体
这种方法没有方法体,要用abstract关键字修饰,写法为

abstract class Student
{
	abstract void study();// 没有方法体
	
}
  1. 抽象方法一定在抽象类
  2. 抽象方法和抽象类都必须被abstract关键字修饰
  3. 抽象类不可以用new创建对象。因为调用抽象方法没意义(没有方法体)。
  4. 如果要使用抽象类中的抽象方法,必须由子类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方法,也就是说接口中可以有方法的具体实现

如果想在接口类中定义了方法并给出具体的方法体,可以通过

  1. 将该方法定义为静态方法。
  2. 为该方法加上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的关键字声明了该功能有可能会出现问题, 让使用者来处理,否则编译失败
  • 子类抛出的异常只能是父类抛出的异常或者它的子异常

对多异常的处理

  1. 声明异常时,建议声明更为具体的异常。这样处理的可以更具体。
  2. 声明几个异常,就对应有几个catch块。不要定义多余的catch块
  3. 如果多个catch块中的异常出现继承关系,父类异常catch块放在最下面
  4. 建立在进行catch处理时,catch中要定义具体处理方式
    不要简单定义一句 e.printStackTrace(),也不要简单的就书写一条输出语句。

自定义异常

因为项目中会出现特有的问题,而这些问题并未被java所描述并封装对象。
所以对于这些特有的问题可以按照java的对问题封装的思想将特有的问题。进行自定义的异常封装。

自定义异常的特点
  • java所描述并封装的异常可以自动抛出,也可以手动抛出
  • 自定义异常不能被java所识别,必须封装成对象并用throw手动抛出
  • 当在函数内部出现了throw所抛出的异常对象,那么就必须要采取相应的处理。
  1. 要么在内部try catch处理。
  2. 要么在函数上声明让调用者处理(也是通过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以及其子类,不必标识,会引发程序停止,例如空指针异常)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值