JAVA基础&继承(下)
抽象类:大白话,就看不懂的类就叫抽象类 (哈哈)。
狼:
吼叫 嗷嗷!
狗:
吼叫 汪汪!
将两种事物的共性向上抽取为犬科:吼叫,但是这两种事物吼叫的方式不一样,所以我们可以将其函数体不写,从而让其变成抽象函数,当类中有了抽象函数后,这个类也变成了抽象类。
abstract class 犬科
{
abstract void 吼叫();
}
class 狼 extends 犬科
{
void 吼叫()
{
System.out.println("嗷嗷");
}
}
class 狗 extends 犬科
{
void 吼叫()
{
System.out.println("汪汪");
}
}
抽象类的特点:
1、没有方法体的方法,称为抽象方法。必须存放在抽象类中。抽象方法和抽象类必须用 abstract 关键字修饰。
2、抽象类不可以被实例化。因为调用抽象方法没有意义。
3、抽象类必须由其子类将抽象类中的抽象方法都覆盖后,其子类才可以实例化。否则,该子类还是抽象类。
抽象类涉及的问题?
1、抽象类中是否可以定义非抽象方法?
可以定义非抽象方法。注意:抽象类也是用于描述事物的,只不过描述事物的过程中有些信息不具体。
抽象类和一般类异同点:
相同:抽象类和一般类都用于描述事物,都可以定义成员。
不同点:抽象类中可以定义抽象成员函数,一般类不可以定义抽象函数。抽象类不可以实例化,一般类可以被实例化。
2、抽象类中是否有构造函数?
有够造函数。而且抽象类虽然自身不可以实例化,但是其子类覆盖了所有的抽象方法后是可以实例化的。所以抽象类的构造函数是用于给其子类对象进行初始化的。
3、抽象类通常都是一个父类?
是的,因为需要子类去覆盖父类中的抽象方法。
4、抽象类中的可不可以不定义抽象方法?
可以。看上去没什么意义,其实有点意义,就是不让这个类创建对象。
5、抽象关键字不可以和那些关键字共存?
final :修饰的是最终类,不可以被继承。而abstract修饰的类必须是父类,必须被继承。
static :抽象方法被static修饰,就具备了可以被类名直接调用的特点,但是抽象方法被调用没有意义
private :因为抽象方法被私有,无法被覆盖。
· 雇员示例:
· 需求:公司中程序员有姓名,工号,薪水,工作内容。
· 项目经理除了有姓名,工号,薪水,还有奖金,工作内容。
· 对给出需求进行数据建模。
需求分析:要跟具体问题领域来分析。就是所面对事物的范围领域。比如此题,就是程序员和项目经理。
问题领域的分析,就是找出问题领域中有哪些对象(常用的方法就是名词提炼法)。
分析:这个问题领域中,有两个对象:
程序员:
属性:姓名,工号,薪水
行为:工作内容
项目经理:
属性:姓名,工号,薪水,奖金
行为:工作内容
发现类中是不是共性内容。针对此题,有共性内容,可以抽取
就有了员工,
属性:姓名,工号,薪水
行为:工作内容
abstract class Employee
{
private String name;
private String id;
private double pay;
Employee(String name,String id,double pay)
{
this.name = name;
this.id = id;
this.pay = pay;
}
public abstract void work();
}
class Programmer extends Employee
{
Programmer(String name,String id,double pay)
{
super(name,id,pay);
}
public void work()
{
System.out.println("code");
}
}
class Manager extends Employee
{
private double bonus;
Manager(String name,String id,double pay,double bonus)
{
super(name,id,pay);
this.bonus = bouns;
}
public void work()
{
System.out.println("manage");
}
}
class EmployeeDemo
{
public static void main(String[] args)
{
}
}
接口
接口:接口其实在表象上好像是一个特殊的抽象类。当抽象类中的所有方法都是抽象时,可以使用接口来表示。接口定义的方式通过关键字 interface
接口的特点:
1、接口中的方法全部是抽象的。
2、接口中的成员都是固定的修饰符。最常见的成员:全局常量,抽象方法。
全局常量 public static final
抽象方法 public abstract
3、接口中的成员都是public的。
4、接口是不可以建立对象的。必须由接口的子类覆盖了所有的抽象方法后,该子类才可以实例化
5、类与类之间是继承关系。类与接口之间是实现的关系 implements。接口在实现的时候一定要注意,接口中的方法全部都是公有的,public是不能省略的。
6、接口的出现可以多实现,避免了单继承的局限性。
7、一个类在继承一个类的同时,还可以实现多个接口。
8、接口与接口之间是继承关系,而且可以多继承(原因是接口中的方法都没有方法体,就算继承过来也同样是没有方法体,这样在子类覆盖的时候不会造成不确定性)。抽象只支持单继承,而接口支持多继承。
java 对多继承机制进行改良,以多现实接口的方式体现。
抽象类中没有抽象方法存在有何用,目的就是不让建立此类对象。
抽象类和接口的区别:
相同点:
他们都是不断向上抽取出来的一个抽象的内容。
区别:
1、抽象类只能被单继承,接口可以被多实现,避免了单继承的局限性
2、抽象类中可以定义抽象方法和非抽象方法,它可以用于定义体系的共性内容。接口中只能定义抽象方法,它主要用于对象的功能扩张。
3、抽象类是继承关系,是is a 的关系,接口是实现关系,是like a 的关系。
4、抽象类中的成员修饰符不固定,接口中的成员修饰符都是固定的。
多态:
父类的引用变量指向了其子类的对象。
多态在代码中体现:父类或者接口的引用指向了自己的子类对象。
多态的好处:提高了代码的扩展性,有了多态,前期设计好的内容可以扩展后期出现的子类内容。对应指挥对象做事情的这件事,如果对象很多,指挥起来很麻烦。抽取对象的共性类型,对该类型的事物进行指挥就会变得很简单。
使用多态的前提:
1、必须要有关系:继承或者实现 2、必须有覆盖的操作。
多态的弊端:前期的程序虽然可以使用后期出现的子类内容。但是之所以能够使用,子类覆盖父类中的内容。不能使用子类中的特有内容。
在多态转型中,至始至终只有子类对象在做着类型的变化。
向上转型:当只想使用体系基本功能时,可以向上转型操作。当是一旦向上转型后,就无法使用子类特有的方法了。
向下转型:当使用子类对象中特有内容时,才做向下转型的操作。向下转型的时候必须进行判断。
判断的方法:对象 instanceof 类型 判断具体对象是否是指定的类型。instanceof 是一个关键字用于判断对象的类型。当进行向下转型时,先判断该对象是否符合被转成的类型。
Animal a = new Cat() 向上转型 Cat c = (Cat)a 向下转型
动物 y = new 猫();当一种实体具备多种类型的时候这个时候体现出了一个实体有多种表现形态。接口提供了功能的扩展性,多态提供了程序的扩展性。
多态出现后,在成员(变量、函数)调用上的一些特点
1、成员变量:子类可以覆盖父类方法,但当子类与父类中有同名变量的时候,变量是不会被覆盖的,子类中会存在父类的变量空间。
当子父类中出现了相同的非私有成员变量时,在多态调用中的特点:
在编译时期,参考引用型变量所属的类中是否有调用的成员变量,如果有调用成功,如果没有调用失败。
在运行时期,参考的还是引用型变量所属的类中的成员变量。
简单说:对于成员变量,无论编译还是运行,都参考引用型变量的所属类中的成员变量。
更简单说:成员变量看等号左边。
2、成员函数:
当子父类中出现一模一样的函数时,在多态调用中。
在编译时候:参考的引用型变量的所属类中是否有调用的成员函数
运行时期:参考的是对象所属的类中的是否有调用的成员函数。
原因:编译时期应该参考的父类,而运行时,参考子类,因为在成员函数上有一个特点:覆盖
简单:对于成员函数 编译看左边,运行看右边
3、静态函数:在多态调用中的特点:
在编译时期,参考引用型变量所属的类中是否有调用的静态成员函数,如果有调用成功,如果没有调用失败。
在运行时期,参考的还是引用型变量所属的类的静态成员函数。
简单说:对于静态函数,无论编译还是运行都看左边
对于多态调用成员而言,无论编译还是运行都是看左边。只有成员函数是非静态看右边,其他全部看左边。
举例:
需求:主板运行,当我们希望可以听音乐。
主板具有基本的运行功能,但是为了日后有一些新的扩展功能。我们可以预先定义好一些规则。
主板可以使用这些规则,至于规则的具体实现,有其他的板卡去完成。
1,定义规则。
对外提供一个接口,可以对日后的设备进行开启和关闭的。
interface PCI
{
void open();
void close();
}
class MainBoard
{
void run()
{
System.out.println("mainboard run");
}
void usePCI(PCI p)//接口类型的变量。接收自己的子类对象
{
if(p!=null)
{
p.open();
p.close();
}
}
}
//具体的板卡符合了规则。
class SoundCard implements PCI
{
public void open()
{
System.out.println("sound open");
}
public void close()
{
System.out.println("sound close");
}
}
class MainBoardDemo
{
public static void main(String[] args)
{
MainBoard m = new MainBoard();
m.run();
m.usePCI(null);
m.usePCI(new SoundCard());
}
}
object() 是所有类的父类。
object类中的常用方法 equals 方法,是用来判断其他某一个对象是否与此对象相等。
数值比较用比较运算符,equals 比较的是对象的地址值,当我们想使用equals比较对象的内容,涉及对象内容比较都需要覆写equals,然后进行比较
getClass() 获取任何对象在运行时的字节码文件对象。在java中字节码文件也属于对象。用Class 来描述字节码文件。
toString() 直接将一个对象变成字符串。输出语句在输出对象时,其实就在自动调用对象的toString方法,将对象变成字符串打印。所以在输出语句中我们不调用toString也会自动调用。
hashcode() 获取对象的哈希值
通常我们在开发的时候描述一个类,那么这个类中都会具有这些方法,但是类本身拥有自己的属性,我们通常会根据类自身的特点,来判断其是否相同,来定义其存储的位置,和它对象的字符串表现形式。所以我通常写一个会将hashcode toString getName 覆盖掉。
内部类:将类定义到另一个类中。当一个类要直接访问另一个类中的成员时,可以将这个类定义到另一个类的里面。称为另一个的内部类(内置类、嵌套类)。
内部类的访问规则:
1、内部类可以直接访问外部类中的成员。
2、外部类需要创建内部类对象才可以访问内部类中的成员。
class Outer
{
int num = 4;
class Inner
{
void show()
{
System.out.println("show run..."+num); 内部类可以直接访问外部类成员
}
}
public void method()
{
Inner in = new Inner(); 外部类要访问内部类的时候必须创建内部类对象
in.show();
}
}
class InnerClassDemo
{
public static void main(String[] args)
{
Outer out = new Outer();
out.method();
Outer.Inner in = new Outer().new Inner(); 外部直接访问内部类
in.show();
}
}
在描述事物时,事物中还有具体的事物,而且这个内部事物在访问着外部事物的内容,这时对这内部事物的描述,就可以用内部类来完成。
内部类处于外部类的成员位置上是可以被成员修饰符所修饰的。public static private 成员修饰
当内部类权限足够大,可以被外界访问的时候,怎么创建内部类的对象呢,这个时候一定要明确创建的内部属于哪个外部类。即就是先有外部在有内部类 例如 :Outer.Inner in = new Outer().new Inner();
static
内部类静态化,要调用非静态的方法show Outer.Inner in = new Outer.Inner(); in.show();
内部类静态化,要调用静态的方法show Outer.Inner.show();
当内部类中定义了静态的成员时,该内部类必须静态的,否则编译失败。
class Outer
{
static int num = 4;
static class Inner//内部类。
{
static void show()
{
System.out.println("show run..."+num);
}
}
public void method()
{
Inner in = new Inner();
in.show();
}
}
class InnerClassDemo
{
public static void main(String[] args)
{
//内部类静态化。要调用非静态的方法show。
Outer.Inner in = new Outer.Inner();
in.show();
//内部类静态化。要调用静态的方法show。
Outer.Inner.show();
}
}
private
当内部类中的成员变量和外部类中的成员变量重名时,直接访问的内部的成员变量。
为什么内部类可以直接访问外部类中的成员呢?那是因为内部类中持有一个外部类的对象引用:外部类名.this
class Outer
{
private int num = 5;
class Inner
{ int num = 9 ;
void show()
{ int num =10 ;
System.out.println("num="+num); 这时候访问的是show方法自己的num
System.out.println("Inner.this..num="+ Inner.this .num); 这个时候访问的是内部的成员变量num
System.out.println("Outer.this..num="+ Outer.this .num);这个时候访问的外部类的成员num
}
}
}
}
内部类如果定义在成员函数的局部位置上,是不能直接访问成员函数的局部变量,因为这个局部变量会随着成员函数的运行结束,而从栈内存中消失,那么内部类是无法访问到这个变量的,如果一定要访问这个变量,只能访问局部中被final修饰的局部变量。否则内部类不能访问局部变量。
class Outer
{
private int num = 5;
public void method(final int a) 这个参数也必须被final修饰
{
int x = 9;//内部类如果定义在局部位置上,只能访问局部中被final修饰的局部变量。
class Inner
{
void show()
{
System.out.println("num="+num);
System.out.println(x);
}
}
Inner in = new Inner(); 内部放在外部类成员函数内部的时候,需要先创建内部对象,然后才能访问内部类的成员。
in.show();
}
}
class InnerClassDemo2
{
public static void main(String[] args)
{
Outer out = new Outer();
out.method(8);
out.method(15);
}
}
匿名内部类:是内部类的简写格式。
前提:内部类必须继承了一个外部类,或者实现了外部的接口。
格式:
new 父类名()或者接口名()
{
覆写父类方法;
}.方法名();
其实匿名内部类就是一个子类匿名对象,只不过这个对象有点胖。
class Test
{
void show()
{
System.out.println("test run");
}
}
class Outer
{
int num = 3;
public void function()
{Test t = new Test()
{
void method()
{
System.out.println("method runrun");
}
};
t.show();
//t.method();//不行,test的子类对象已经提升了test类型。不可以调用method方法的。
}
public void function2()
{
new Object()
{
void haha()
{
System.out.println("haha");
}
}.haha(); 编译通过,运行没有问题
Object obj =new Object()
{
void haha()
{
System.out.println("haha");
}
};
obj.haha();//编译失败。 因为子类对象已经向上转型成Object。
}
}
class InnerClassDemo4
{class Inner
{
}
public static void main(String[] args)
{show(new Inter()
{
public void method()
{
System.out.println("method run....");
}
}); 注意这里的括号
}
public static void show(Inter in)
{
in.method();
}
}
interface Inter
{
void method();
}
异常:是在运行时期发生的不正常情况。
java 发现这些不正常的情况都包含一些常见信息。并将这些信息进行了对象的封装。
异常这种机制,其实就是java按照面向对象思想,将出现的问题封装成了对象。在进行问题分析时,发现问题有很多种,但是不断向上抽取最终问题可以归类两种:
1、 一种是可以针对处理的
2、 一种是通常不进行处理的
在java中的体现一个 Exception 一个是 Error 。无论是异常还是错误,他们的名称,信息等共性的内容。可以继续抽取。形成了一个父类。
这个父类就是 Throwable
|--Error
|--Exception
异常和错误都有个特点,其子类的名称的后缀名都是父类名。该体系具备一个特殊的特性:可抛性。该体系中的类可以被关键字 throws 抛出,该体系中的类产生对象可以被throws 抛出。简单说,该体系可以被 throw 和 throws 这两个关键字的操作。功能的定义者在定义功能时,发现该功能容易因为未知内容的不确定性出现问题,为了让调用者明确,有可能有问题,需要在函数上对问题进行声明。需要一个关键字 throws 异常名
对于异常的针对性处理方式:
try
{
需要被检测的代码
}
catch( 异常类 变量 )
{
异常处理代码
}
finally
{
一定会被执行的代码。
}
throw :定义在函数内,用于抛出异常对象,引起程序的跳转,后面跟的是异常对象
throws :定义在函数上,用于声明函数可能出现的问题。后面跟的是异常类,可以跟多个,用逗号隔开即可
java中把常见的问题都封装成了对象。对于自定义项目中出现的问题,java并未给出对应的描述。这时我们就需要按照面向对象的思想自己完成对问题的描述和封装。
函数内抛出异常,必须在函数上一定要用 throws 标识
函数内抛的是什么,函数上就标识什么。
异常分两种:
1、编译时被检测的异常 Exception 。这种异常通常都需要针对性的处理。
2、运行时发生的异常 RuntimeException ,对于运行时异常,一般不编写针对性处理方法,如果该异常发生,就让程序停止,对程序进行修正,因为这种异常的出现往往已经让程序无法继续运行了。
閱讀更多 竇你玩小陶 的文章