【java基础】——java面向对象(下)—多态、内部类、异常、包

时间:2023-02-24 22:44:17

一、面向对象——多态

1、多态概述

①什么是多态?某一事物在不同时刻表现出来的不同状态。

如:猫可以是猫的类型。猫 m = new();同时猫也是动物的一种,也可以把猫称为动物。

②多态的好处:提高了代码的可扩展性,前期定义的代码可以使用后期的内容。

③多态的弊端:前期定义的内容不能使用后期子类的特有功能。

④多态的前提:

  • 必须要有关系:继承,实现。
  • 有覆盖。
  • 有父类引用指向子类对象。

2、多态时成员的特点

①成员变量:编译时,参考引用型变量所属的类中是否有调用的成员变量,有则编译通过,没有则编译失败。运行时,参考引用型变量所属的类是否有调用的成员变量,并运行该所属类中的成员变量。简单的说,编译和运行都参考等号的左边。
②成员函数(非静态):编译时,参考引用型变量所属的类中是否有调用的函数,有则编译通过,没有则编译失败。运行时,参考的是对象所属的类是否有调用的函数,简单的说,编译看左边,运行看右边。
③静态函数:简单说,编译和运行都看做左边。其实对于静态方法,是不需要对象的。直接用类名调用即可。

3、多态中的转型问题

①向上转型:从子到父,父类引用指向子类对象。
②向下转型:从父到子,父类引用转为子类对象。
在多台向下转型中容易出现的问题,代码演示如下:

/*
ClassCastException:类型转换异常
一般在多态的向下转型中容易出现
*/
class Animal {
public void eat(){}
}

class Dog extends Animal {
public void eat() {}

public void lookDoor() {

}
}

class Cat extends Animal {
public void eat() {

}

public void playGame() {

}
}

class DuoTaiDemo5 {
public static void main(String[] args) {
//内存中的是狗
Animal a = new Dog();
Dog d = (Dog)a;

//内存中是猫
a = new Cat();
Cat c = (Cat)a;

//内存中是猫
Dog dd = (Dog)a; //ClassCastException
}
}


二、面向对象——内部类

1、内部类(inner class)概述

①概念:内部类是指定义在另一个类中的类。当描述事物时,事物的内部还有事物,该事物就用内部类来描述。因为内部事物在使用外部事物的内容。

②内部类访问的特点:

  • 内部类可以直接访问外部类中的成员。
  • 外部类需要访问内部类,必须建立内部类的对象
  • 如果内部类中有静态成员,那么外部类也必须是静态的。

2、内部类的分类

按照内部类在类中定义的位置的不同,可以分为如下两种格式:
  • 成员位置——成员内部类
  • 局部位置——局部内部类
①成员内部类
a、外界如何创建对象?外部类名.内部类名  对象名 = 外部类对象.内部类对象;
b、成员内部类常见的修饰符
  • private 为了保证数据的安全性
  • static 为了让数据访问更方便。被静态修饰的成员内部类只能访问外部的静态成员。
内部类被静态修饰后的方法有静态和非静态之分,它们的访问和不用静态不是一样的。
访问非静态方法:外部类名.内部类名对象名 = new 外部类名.内部类名();

访问静态方法:上面创建的对象访问,或者外部类名.内部类名.方法名();

成员内部类面试题

class Outer {
public int num = 10;

class Inner {
public int num = 20;

public void show() {
int num = 30;
System.out.println(num);//30
System.out.println(this.num);//20
System.out.println(Outer.this.num);//10
}
}
}

class OuterDemo {
public static void main(String[] args) {
Outer.Inner oi = new Outer().new Inner();
oi.show();
}
}
②局部内部类

a、局部内部类可以直接访问外部类的成员。

b、可以创建内部类对象,通过对象调用内部类方法,来使用局部内部类功能。

c、局部内部类访问局部变量的注意事项:

  • 必须被final修饰。为什么?因为局部变量会随着方法的调用完毕而消失,这个时候,局部对象并没有立马从堆内存中消失,还要使用那个变量。为了让数据还能继续被使用,就用fianl修饰,这样,在堆内存里面存储的其实是一个常量值。
如下面的代码所示:
class Outer
{
int x = 3;
void method(final int a)
{
final int y = 4;
//局部内部类
class Inner
{
void function()
{
System.out.println(y);
}
}
new Inner().function();//使用局部内部类中的方法。
}
}
class InnerClassDemo
{
public static void main(String[] args)
{
Outer out = new Outer();
out.method(7);//打印7
out.method(8);//打印8
}
}
③匿名内部类
a、什么是匿名内部类?匿名内部类其实就是内部类的简写格式。
b、匿名内部类的前提:存在一个类或接口。
c、匿名内部类的格式:new 类名或者接口名(){重写方法}。
d、匿名内部类的本质:是一个继承了类或者实现了接口的子类匿名对象。
e、匿名内部类的通常使用场景之一:当函数参数是接口类型时,而且接口中的方法不超过三个,可以使用匿名内部类作为实际参数进行传递。
匿名内部类在开发中的使用代码实例:

/*
匿名内部类在开发中的使用
*/
interface Person {
public abstract void study();
}

class PersonDemo {
//接口名作为形式参数
//其实这里需要的不是接口,而是该接口的实现类的对象
public void method(Person p) {
p.study();
}
}

//实现类
class Student implements Person {
public void study() {
System.out.println("好好学习,天天向上");
}
}

class InnerClassTest2 {
public static void main(String[] args) {
//测试
PersonDemo pd = new PersonDemo();
Person p = new Student();
pd.method(p);
System.out.println("--------------------");

//匿名内部类在开发中的使用
//匿名内部类的本质是继承类或者实现了接口的子类匿名对象
pd.method(new Person(){
public void study() {
System.out.println("好好学习,天天向上");
}
});
}
}
匿名内部类面试题:按照要求补齐代码
interface Inter { void show(); }
class Outer { //补齐代码 }
class OuterDemo {
public static void main(String[] args) {
Outer.method().show();
}
}
要求在控制台输出”HelloWorld”
题目分析:Outer.method.show();相当于Inter.in = Outer.method();  in.show();
                  Outer.method():Outer类中有个静态的方法method。
                  .show():method这个方法运算后的结果是一个对象。而且是一个Inter类型的对象。因为只有Inter类型的对象,才能调用show方法。
所以,补齐后的代码为:
/*
匿名内部类面试题:
按照要求,补齐代码
interface Inter { void show(); }
class Outer { //补齐代码 }
class OuterDemo {
public static void main(String[] args) {
Outer.method().show();
}
}
要求在控制台输出”HelloWorld”
*/
interface Inter {
void show();
//public abstract
}

class Outer {
//补齐代码
public static Inter method() {
//子类对象 -- 子类匿名对象
return new Inter() {
public void show() {
System.out.println("HelloWorld");
}
};
}
}

class OuterDemo {
public static void main(String[] args) {
Outer.method().show();
/*
1:Outer.method()可以看出method()应该是Outer中的一个静态方法。
2:Outer.method().show()可以看出method()方法的返回值是一个对象。
又由于接口Inter中有一个show()方法,所以我认为method()方法的返回值类型是一个接口。
*/
}
}

三、面向对象——异常

1、异常的概述

①什么是异常?

a、异常是指在运行时期发生的不正常情况。在java中用类的形式对不正常情况进行了描述和封装,描述不正常情况的类,就是异常类。

b、在以往的正常情况,流程代码和问题处理代码相结合。现在将正常流程代码和问题处理代码相分离,提高阅读性。

c、其实异常就是java通过面向对象的思想将问题封装成了对象,用异常类对其进行描述,不同的问题用不同的类进行具体描述。

②异常的由来

在理想状况下,用户输入的数据永远是正确的,打开的文件一定存在,并且永远不会出现bug。但是,在现实中我们往往会遇到许多的问题。如果一个用户在运行程序期间,由于程序的错误或一些外部环境的影响造成数据的丢失,用户就有可能不再使用这个程序了,为了避免这类事情的发生,至少应该做到以下几点:

  • 向用户通报错误。
  • 保存所有的操作结果。
  • 允许用户以适当的形式退出程序。

2、异常体系

对于程序出现的问题,可能会有很多种,这就意味着描述的类也很多,将其共性进行向上抽取,就形成了异常体系。

异常体系(Throwable):无论是error还是异常,都应该抛出,让调用者知道并处理。该体系的优点就在于Throwable及其所有的子类都具有可抛性。可抛性是通过两个关键字来体现的,throw和throws,凡是可以被这两个关键字操作的类和对象都具有可抛性。

最终就将问题分成了两大类:

①error:一般不可处理。java运行时系统的内部错误和资源耗尽错误。是由jvn抛出的严重性问题,这种问题一般不针对性处理,直接修改程序。

②Exception:可处理的。Exception体系。

a、该体系的特点:子类的后缀都是用其父类名作为后缀,阅读性很强。

b、在java中没有定义的异常就按照java异常的创建思想,面向对象,进行自定义描述,并封装成对象。

注意:如果一个类称为异常类,则必须要继承异常体系,因为只要继承异常体系的子类才具有可抛性。才可以被两个关键字操作。

异常体系如下图所示:

【java基础】——java面向对象(下)—多态、内部类、异常、包

3、异常的分类

①异常通常分为两种,一种是编译时被检测异常,一种是编译时不被检测异常(运行时异常)。

  • 编译时被检测异常:只要是Exception及其子类都是,除了RuntimeException体系。这种问题一旦出现,希望在编译时就进行检测,让这种问题有对应的处理。
  • 编译时不检测异常(运行时异常):就是Exception中的RuntimeException及其子类。这种问题的发生,无法让功能继续,运算无法进行,更多的是因为调用者的原因导致的,或者是引发了内部状态的改变而导致的,那么这种问题一般不处理,直接编译通过,在运行时,让调用者调用时的程序强行停止,让调用者修改程序代码。“如果出现RuntimeException,那么就一定是你的问题”。

4、异常的处理

①异常处理机制

在java应用程序中,异常处理机制为:抛出异常,捕获异常。

  • 抛出异常:当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。
  • 捕获异常:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适 的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适 的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。
②throws和throw的区别
  • throws使用在函数上,throw使用在函数内。
  • throws抛出的是异常类,可以抛出多个,用逗号隔开;throw抛出的是异常对象。

③在java中,提供了特有的处理异常的方式,格式如下:

try
{
    需要被检测的代码;
}
catch(异常类 变量)
{
    处理异常的代码;(处理方式)
}
finally
{
    一定会执行的语句;
}

除此之外还有两种格式,一种是没有finally代码块,一种是没有catch代码块。

注意:在finally中定义的通常是关闭资源代码,因为资源必须释放。finally只有一种情况不会执行:当系统执行到System.exit的时候。

④自定义异常

定义类继承Exception或者RuntimeException。目的有两个:一是为了让该自定义类具有可抛性,二是为了让该类具备操作异常的共性方法。

当要定义自定义异常信息时,可以使用父类已经定义好的功能。异常信息传递给父类的构造函数。代码实例如下:

class MyException extends Exception{

MyException(String message){

super(message);
}
}
自定义异常:按照java面向对象的思想,将程序中出现的特殊问题进行封装。

⑤异常处理原则:

  • 函数内容如果要抛出需要检测的异常,那么函数上必须要有声明,否则必须在函数内用try  catch进行捕捉,否则编译失败。
  • 如果调用到了声明异常的函数,要么try catch,要么throws,否则编译失败。
  • 什么时候try,什么时候throws呢?功能内容可以解决就用try…catch;解决不了,用throws告诉调用者,由调用者解决。
  • 一个功能如果抛出了多个异常,那么调用时,必须有对应的多个catch进行针对性的处理,内部有几个需要检测的异常,就抛几个,抛出几个,就catch几个。
⑥异常处理注意事项

a、子类在覆盖父类方法时,父类如果抛出了异常,那么子类的方法只能抛出父类的异常或者该异常的子类。

b、如果父类抛出了多个异常,呢吗子类只能抛出父类异常的子集。简单的说,子类覆盖父类的异常或者子类或子集。

注意:如果父类的方法没有抛出异常,那么子类的方法绝对不能抛出异常。只能进行捕获处理。

⑦异常实际应用中的应用和总结

a、在处理运行时异常时,采用逻辑去合理规避同时辅助try—catch处理。

b、在多重catch块后面。可以加上一个catch(Exception)来处理可能被一楼的异常。

c、对于不确定的代码,也可以加上try—catch处理潜在的异常。

d、尽量去处理异常,切忌只是简单的调用printstack.Trance()去打印输出。

e、具体如何处理异常,要根据不同的业务需求和异常类型来决定。

f、尽量添加finally代码块去释放占用的资源。

⑧异常综合练习代码演示:

/*
毕老师用电脑上课。

开始思考上课中出现的问题。

比如问题是
电脑蓝屏。
电脑冒烟。
要对问题进行描述,封装成对象。

可是当冒烟发生后,出现讲课进度无法继续。

出现了讲师的问题:课时计划无法完成。
*/
//自定义电脑蓝屏异常
class LanPingException extends Exception{
LanPingException(String message){
super(message);
}
}
//自定义电脑冒烟异常
class MaoYanException extends Exception{
MaoYanException(String message){
super(message);
}
}

//出现了问题,无法上课,课时计划无法完成异常
class NoPlanException extends Exception{
NoPlanException(String msg){
super(msg);
}
}

class Computer{
private int state = 3;
//电脑启动
public void run()throws LanPingException,MaoYanException{
if(state==2)
throw new LanPingException("蓝屏了");
if(state==3)
throw new MaoYanException("冒烟了");

System.out.println("电脑运行");
}
//电脑重启
public void reset(){
state = 1;
System.out.println("电脑重启");

}
}

class Teacher{
private String name;
private Computer cmpt;
//对老师进行初始化
Teacher(String name){
this.name = name;
cmpt = new Computer();
}
//老师讲课
public void prelect()throws NoPlanException{
try{
cmpt.run();
}
catch (LanPingException e){
cmpt.reset();
}
catch (MaoYanException e){
test();
throw new NoPlanException("课时无法继续"+e.getMessage());
}
System.out.println("讲课");
}
public void test(){
System.out.println("练习");
}

}

class ExceptionTest {
public static void main(String[] args) {
Teacher t = new Teacher("毕老师");
try{
t.prelect();
}
catch (NoPlanException e){

System.out.println(e.toString());
System.out.println("换老师或者放假");
}

}
}

四、面向对象——包(package)

1、什么是包?

包其实就是文件夹。在包中,我们通常存放的是类文件,因为我们在编写程序时,难免会出现类名相同的情况。为了方便于对类进行管理,java中就有了包的出现,在不同的包中可以有相同的类名,调用的时候连同包名一起调用即可。包也是一种封装形式。
报名的书写规则:
  • 包名必须写在程序的第一行。
  • 类文件的全称:包名.类名。

2、包的作用

①对子类文件进行分类管理。将一些相关的类放在同一个包中。
②给类提供多层命名(名称)空间。
③避免多个类重命名,对于相同的类名,可通过包名将两者区分。
④包的出现可以将java 的类文件与源文件相分离。

3、包与包之间的访问

①要访问其它包中的类,需要定义类的全称。包名.类名。

②包如果不在当前路径,需要使用classpath设定环境变量,为JVM指明路径。

③被访问的包中的类权限必须是public的。被访问的包中的类的方法也必须是public的。

4、四种权限修饰词

  public protected default private
同一类中 ok ok ok ok
同一包中 ok ok ok  
子类中 ok ok    
不同包中 ok      

5、import(导包)

①导包的原则:用到哪个类,就导哪个类,不要多导。(尽管多导也能运行)

②导包的作用:

  • 可以简化书写。
  • 一个程序文件中只有一个package,但可以有多个import。
③导包的注意事项:
  • 在导包时,用到哪个写哪个,尽量不要import pack.*;一般是import pack.Demo;可以节省内存空间,也能提高点效率。
  • 定义包名不要重复。一般我们定义包名是用url的倒写形式。如:package  cn.itheima.Demo
  • 定义包名时,包名都是小写字母。