抽象类
为什么要定义抽象方法:如果定义某个方法时不能确定该方法的具体实现细节; 比如定义 Person 类的 eat 方法时, 不能确定其具体实现细节,因为中国人、 西方国家的人、 南亚国家的人吃饭方式不一样。 可以把该方法定义成一个抽象方法,具体 的实现细节,交给其后代(子类)来实现。
抽象方法的定义
使用 abstract 关键字修饰方法的定义,方法体必须为空(否则就不是抽象方法),抽象方法必须是非静态的(抽象方法不能被 static 修饰), 抽象方法不能被 final 修饰、 不能被 private 修饰.
[ 修饰符 ] abstract 返回值类型 methodName() ; //注意这里没有 { }
抽象类的定义
为什么要定义抽象类:定义抽象方法的类, 必须被定义成抽象类
抽象类的定义方法
[ 修饰符 ] abstract class className {
//使用 abstract 修饰类定义
}
抽象类的特征
- 抽象类不能被实例化 (有构造但仅供子类调用)
- 抽象类中可包含属性、 方法、 构造、 内部类、 枚举、 代码块等
- 其中的方法可以是抽象方法, 也可以是已实现方法
- 含有抽象方法的类, 必须被定义成抽象类
- 这个抽象方法可能是自定义、 继承来的、 或实现自接口的,否则就要全部实现其中的抽象方法
- 抽象类中, 可以没有抽象方法
DEMO
/**
* 为什么要有抽象类
* 1、含有抽象方法的类,必须被定义成抽象类;
* 但是,抽象类未必非要有抽象方法
* 2、如果期望当前类不能被实例化,
* 而是交给子类完成实例化操作,可以定义抽象类
* (抽象类有构造方法,抽象类不能被实例化(直接创建该类的对象))
* Person p = new Person(); // 错误的
*/
public abstract class Person {
// 只要有花括号,就可以执行,这样的方法已经实现过了
/**当一个方法在定义时无法确定其实现细节(具体处理什么、怎么处理)
* 如果一个方法不是native 修饰的,
* 当它没有方法体时,这个方法是不能被执行的,此时这个方法就是一个抽象的方法,
* 需要使用抽象关键字来修饰(abstract)
*/
public abstract void eat(String food);
/**
* abstract 修饰方法时,不能与 static 、 final连用
*/
}
/**
* 子类继承某个抽象类
* 1、如果子类依然不确定某个方法的实现细节(不实现继承自父类的抽象方法),
* 则可以将该类继续声明为抽象类
* 2、如果子类不希望是抽象类,必须实现继承自父类的抽象方法
*/
public class Sinaean extends Person{
public Sinaean(){
super();
}
// 这个方法不再是抽象方法了,而且可以有方法体
@Override
public void eat(String food) {
System.out.println("中国人大部分都用筷子吃"+ food);
}
}
public class Thai extends Person{
@Override
public void eat(String food) {
System.out.println("泰国人有时候用手抓着吃: "+ food);
}
}
/**
* 创建抽象类的实例:
* 1、创建其子类类型的实例(这是本质)
* 2、可以通过静态方法来获得其实例(本质还是创建子类类型对象 )
*/
public class Main {
public static void main(String[] args) {
// 不能实例化Person 类型:抽象类不能被实例化
// Person p = new Person();//Cannot instantiate the type Person
// 声明一个Person 类型的变量p(p的编译时类型是 Person)
// 创建抽象类的子类类型的对象,并将其堆内存中首地址赋值给栈空间中的p变量
Person p =new Sinaean();
p.eat("火锅");
System.out.println("运行时类型: " + p.getClass());System.out.println("内存中的首地址是: "+ System.identityHashCode(p));
// 创建抽象类的子类类型的对象,并将其堆内存中首地址赋值给栈空间中的p变量
// p 变量中原来存储的地址将被覆盖
p = new Thai();
p.eat("米饭");
System.out.println("运行时类型: " + p.getClass());
System.out.println("内存中的首地址是: "+ System.identityHashCode(p));
Calendar c = Calendar.getInstance(); // 通过静态方法来获得一个实例
Class<?> clazz = c.getClass();// 获得c 所引用的对象的真实类型(运行时类型)
System.out.println(clazz);
Class<?> superClass = clazz.getSuperclass();// 获得clazz的父类
System.out.println(superClass);
}
}
总结
抽象类的特点
1、抽象类【有构造】,但是不能被实例化
抽象类的构造方法专供子类调用(构造方法也是可以执行的)
2、抽象类中可以有抽象方法,也可以没有
含有抽象方法的类必须是抽象类(参看Person中的第一点)
抽象类中可以没有抽象方法(参看Person中的第二点)
3、怎么创建抽象类的实例:创建其子类类型的实例(这是本质)
待创建对象的子类类型必须是非抽象类
不一定非要是直接子类,间接子类也可以
可以通过静态方法来获得其实例(Calendar.getInstance() )
Calendar c = Calendar.getInstance();
4、应该选择哪种方式来创建抽象类的实例:
a>、如果当前抽象类中有静态方法,则优先使用静态方法
b>、如果子类中有静态方法返回相应实例,用子类的静态方法
c>、寻找非抽象的子类,创建子类类型的对象即可
d>、自己继承这个类并实现其中的抽象方法,然后创建实例
注意:有时为了实现我们的需求,可能会不调用静态方法来获得实例,而是选择创建子类对象
接口
接口是一种比抽象类更抽象的类型;接口是从多个相似的类中抽象出来的规范: 它定 义了某一批类(接口的实现类或实现类的子类)所要必须遵循的规范。 接口只定义常量 或方法, 而不关注方法的实现细节,接口体现了规范和实现相分离的设计哲学。
定义接口
[ 修饰符 ] interface InterfaceName {
定义在接口中的常量 ( 0 到 n 个)
定义在接口中的抽象方法 ( 0 到 n 个)
static 修饰的方法 ( 0 到 n 个)
default 修饰的方法( 0 到 n 个)
}
接口中的成员
- 可以包含属性(只能是常量)
- 系统会对没有显式使用 public static final 修饰的变量追加这些修饰
- 可以包含方法(必须是抽象的实例方法)
- 接口中不允许有非抽象的方法
- 接口中可以存在静态方法( 用 static 修饰的静态方法)
- 接口中可以存在默认方法( 用 default 修饰的方法 )
- 可以包含内部类或内部接口
- 可以包含枚举类
- 不能包含构造方法
- 不能包含代码块
接口的继承和实现
接口的继承使用 extends 关键字实现:
public interface Usb1 extends Usb {};
Java 语言中的接口可以继承多个接口: 多个接口之间使用 , 隔开 ( 英文状态的逗 号 );子接口可以继承父接口中的: 抽象方法、常量属性、内部类、枚举类
类可以实现接口:使用 implements 关键字来实现
接口的特征
接口中的属性默认都是 public 、 static 、 final 类型:这些成员必须被显式初始化;接口中的方法默认都是 public 、 abstract 类型的。
接口中根本就没有构造方法, 也就可能通过构造来实例化,但允许定义接口类型的引用变量,该引用变量引用实现了这个接口的类的实例。
接口不能实现另一个接口, 但可以继承多个接口。
接口必须通过实现类来实现它的抽象方法,当某个类实现了某个接口时, 必须实现其中所有的抽象方法,或者是不实现其中的抽象方法, 而把该类定义成抽象类。
类只能继承一个类, 但可以实现多个接口,多个接口之间用逗号分开。
接口和抽象类的异同
共同点:
接口和抽象类都不能被实例化
接口和抽象类都处于继承树的顶端
接口和抽象类都可以包含抽象方法
实现接口或继承抽象类的普通类必须实现其中的抽象方法
区别
抽象类中可以有非抽象方法, 接口中只能有抽象方法或static修饰的方法或default修饰的方法
一个类只能继承一个直接父类, 而接口可以实现多继承
抽象类可定义静态属性和普通属性, 而接口只能定义静态属性
抽象类有自己的构造, 接口完全没有
抽象类中可以有代码块, 接口中不可以有
DEMO
/**
* 声明接口,并确定接口中可以有什么
* 1、常量
* 2、抽象方法
* 3、 default 修饰的非抽象方法(JDK1.8开始)* 4、接口没有构造方法
*/
public interface Usb {
// 接口没有构造方法
// public Usb(){}
/**
* 接口中只能定义常量(没有不是不是常量的属性)
* 1、接口中所有的属性默认都是 public static final 修饰的
* 2、常量的命名:所有字母都是大写,如果有多个单词,中间用下划线隔开
* */
int POWER_UNIT = 100 ;// 充当供电单位
/**
* JDK1.8 之前 仅允许在接口中声明抽象方法
* 所有的方法都是 public abstrct 修饰的
*/
void power();
/**
* JDK1.8 开始,允许定义被default修饰的非抽象方法
* 这个方法是个public 修饰的非静态方法(子类或子接口可以重写)
*/
default void show(){
System.out.println("每次供电单位是: "+POWER_UNIT) ;
}
}
/**
* 1、类 可以实现接口,用关键字implements来完成实现
* 2、如果本来不希望是抽象类,则需要实现从接口"继承"的所有抽象方法
*/
public class MiUsb extends Object implements Usb {
/**
* MiUsb中都有什么
* 从Object中继承的所有方法
* 从Usb中继承的常量
* 从Usb中继承的default的方法(JDK1.8开始)
* 实现了所有的抽象方法
*/
@Override
public void power() {
System.out.println("小米Usb充电器,供电单位: "+POWER_UNIT);
}
@Override
public void show() {
Usb.super.show();
}
}
public class Test {
public static void main(String[] args) {
// 声明一个接口类型的引用变量Usb u = null;
// 创建实现类的实例 并将其堆内存首地址赋值给u变量
u = new MiUsb();
u.power();
}
}
一个类实现多个接口
public interface Transfer {
void transmission();
}
/**
* 1、用接口继承接口
* 2、接口可以继承父接口中的常量、抽象方法、 default方法
* 3、一个接口可以继承多个接口,中间用逗号隔开就行
*/
public interface UsbTypeC extends Usb , Transfer{}
/**
* 一个类可以实现多个接口,中间用逗号分隔开就可以
*/
public class OppoUsb implements UsbTypeC,Usb{
@Override
public void transmission() {
System.out.println("Oppo手机");
}
@Override
public void power() {
System.out.println("Oppo 手机,供电单位"+ POWER_UNIT);
}
}
public class Test2 {
public static void main(String[] args) {
// 声明一个接口类型的引用变量
OppoUsb u = null;
u = new OppoUsb();
u.power();// 实现了Usb接口中的方法
u.transmission();// 实现了Transfer接口中的方法
}
}
内部类
内部类的分类如下:
成员内部类:
实例内部类
静态内部类
局部内部类:
匿名内部类
DEMO
public class Human {/* 类体括号 */
public static void main(String[] args){ // main 方法的方法体开始
int a = 250;
System.out.println(a);
class ON{ // 局部内部类(Local Inner Class)
}
ON oo = new ON();
System.out.println(oo);
class OFF{ // 局部内部类(Local Inner Class)
}
}// main 方法的方法体结束static String earth; // 属性:静态属性( 类属性 )
String name ; // 属性 :实例属性(实例变量)
static class Country{ // 静态内部类[ static Inner Class]
}
class Head{// 实例内部类 (成员内部类) [ Member Inner Class ]
}
class Hand{// 实例内部类
}
}
获得到自己的内部类
/**
* 获得某个类内部的所有的静态内部类和所有的成员内部类
* 注意:不能获得到局部内部类
*/
public class GetInnerClass {
public static void main(String[] args) {
Class<?> c = Human.class;
// 获得 c 内部的内部类(静态内部类、成员内部类)
Class<?>[] classes = c.getDeclaredClasses();// 获得本类内声明的非 局部内部类
for (int i = 0; i < classes.length; i++) {
Class<?> cc = classes[i];
System.out.println(cc);
}
}
}
也可以通过一个内部类获取自己声明在哪个类内部
public class GetOutterClass {
public static void main(String[] args) {
Class<?> c = Human.Country.class;
// 获得某个内部类声明在那个外部类中
Class<?> oc = c.getDeclaringClass(); // 获得声明自己的 那个类
System.out.println(oc);
}
}
创建静态内部类的实例
public class GetInstance1 {
public static void main(String[] args) {
/** 静态内部类的实例 */
Human.Country c = new Human.Country();
System.out.println(c);
/** 实例内部类的实例 */
Human h = new Human();// 创建外部类的实例
Human.Hand hand = h.new Hand();// 以外部类的实例 h 为基础,创建内部类的实例
System.out.println(hand);
// 或者:
Human.Head head= new Human().new Head();
System.out.println(head);
}
}
匿名内部类
有一个局部内部类,它连名字都没有,则它就是匿名内部类,但是它有对应的.class文件。
新建一个新的 Class,叫做 TestAnonyous1。随后创建一个接口,叫做 USB,并提供一个方法(void transfer) 。具体在 TestAnonyous1 中的例子:用匿 名内部类实现接口。
DEMO
/**
* 创建匿名内部类
*/
public class TestAnonymours1 {
public static void main(String[] args) {
// 编译时类型:变量u 声明的类型是USB
// 用匿名内部类来实现接口
USB u = new USB(){
@Override
public void transfer() {
System.out.println("USB正在传输");
}
};// 把USB当成尸体,结果鬼{}上身了,就能实例化了
u.transfer();
System.out.println(System.identityHashCode(u));
// 获得创建的实例的运行时类型
Class<?> c = u.getClass();// 任何一个对象都可以通过getClass来获得其 运行时类型
System.out.println(c);
Class<?> oc = c.getDeclaringClass();// 尝试获得声明自己的那个外部类
System.out.println(oc);// null 说明 匿名内部类不是直接声明在类体内部的
Class<?>[] inters = c.getInterfaces();
for (int i = 0; i < inters.length; i++) {
System.out.println(inters[i]);
}
}
}
public abstract class AbstractUSB implements USB{
// 从实现的接口中继承了抽象方法 transfer
}
/**
* 创建匿名内部类
*/
public class TestAnonymours2 {
public static void main(String[] args) {
// 创建一个抽象类的实例(本质一定是创建其子类类型的实例)
// 用匿名内部类来继承抽象类,并实现其中的抽象方法
AbstractUSB au = new AbstractUSB() {
@Overridepublic void transfer() {
System.out.println("AbstractUSB正在传输");
}
};
au.transfer();
Class<?> c = au.getClass();// 获得au 对象的运行时类型
System.out.println("匿名内部类: "+ c.getName());
Class<?> sc = c.getSuperclass();
System.out.println("匿名内部类的父类: " + sc.getName() );
}
}
/**
* 创建匿名内部类
*/
public class TestAnonymours3 {
public static void main(String[] args) {
// 用匿名内部类继承一个普通的类
// 并重写其中的方法
Object o = new Object(){
@Override
public String toString(){
return "我是鬼。。 ";
}};
System.out.println(o);
System.out.println(o.getClass());
System.out.println(o.getClass().getSuperclass());
}
}
总结
1、内部类
嵌套在另一个类内部的类
2、内部类的分类
直接写在类体括号内的:
静态内部类、非静态内部类(实例内部类、成员内部类)
不是直接写在类体括号内,比如写在方法中、写在代码块中:局部内部类
如果某个局部内部类连名字都没有,那它就是匿名内部类
3、问题:
对于 Human.java 来说有一个与它对应的 Human.class 文件,内部类是否有对应的 .class 文件?
有.class 文件,对于静态内部类、实例内部类来说,他们对应的 .class 的名称是:
外部类类名$内部类类名.class比如 Human 类中的 Country 类对应的 字节码文件的名称是:Human$Country.class
对于局部内部类(有名称的)来说:他们对应的 .class 文件名称是:外部类类名$Number 内部类类名.class
其中的 Number 是 使用 该名称 的 内部类 在 外部类 出现的位置(第几个)
4、一个类能否获取到自己的内部类(静态内部类、成员内部类)
GetInnerClass.java
5、一个内部类能否获取自己声明在哪个类内部: GetOutterClass.java
6、创建内部类的实例
a>、局部内部类的实例,只能在当前的代码块内部使用,
比如 Human 类内部的 main 方法的 ON 类,则这个类只能在 main 方法内部使用
class ON{ // 局部内部类(Local Inner Class)
}
ON oo = new ON();// 创建局部内部类的实例
System.out.println(oo);
b>、 创建静态内部类的实例:
// 外部类.静态内部类 变量名 = new 外部类.静态内部类()Human.Country c = new Human.Country();
c>、创建实例内部类的实例:
Human h = new Human();// 创建外部类的实例
外部类.实例内部类 变量名 = 外部类实例.new 实例内部类();
Human.Hand hand = h .new Hand();
7、匿名内部类
有一个局部内部类,它连名字都没有,则它就是匿名内部类,但是它有对应的.class 文件
匿名内部类对应的.class 文件名 是外部类类名$数字.class
a>、用匿名内部类实现接口: TestAnonymous1.java
b>、用匿名内部类继承抽象类: TestAnonymous2.java
c>、用匿名内部类继承普通的类: TestAnonymous3.java