Java基础系列文章
Java基础(十):关键字static、代码块、关键字final
Java基础(十八):java比较器、系统相关类、数学相关类
一、抽象类
由来
- 随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用
- 类的设计应该保证父类和子类能够共享特征
- 有时将一个父类设计得非常抽象,以至于它
没有具体的实例
,这样的类叫做抽象类
1、语法格式
- 抽象类:
被abstract修饰的类
- 抽象方法:
被abstract修饰没有方法体的方法
抽象类的语法格式
[权限修饰符] abstract class 类名{
}
[权限修饰符] abstract class 类名 extends 父类{
}
抽象方法的语法格式
[其他修饰符] abstract 返回值类型 方法名([形参列表]);
举例:
public abstract class Animal {
public abstract void eat();
}
public class Cat extends Animal {
public void eat (){
System.out.println("小猫吃鱼和猫粮");
}
}
public class CatTest {
public static void main(String[] args) {
// 创建子类对象
Cat c = new Cat();
// 调用eat方法
c.eat();
}
}
2、abstract修饰类
- 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象
- 理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义
- 抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体
- 若没有重写全部的抽象方法,仍为抽象类
- 抽象类中,也有构造方法,是供子类创建对象时,初始化父类成员变量使用的
- 理解:子类的构造方法中,有默认的super()或手动的super(实参列表),需要访问父类构造方法
- 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类
- 理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计
- 抽象类的子类,必须重写抽象父类中
所有
的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类- 理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义
3、abstract修饰方法
- 抽象方法只有方法的声明,没有方法体
- 抽象方法其功能是确定的(通过方法的声明即可确定),只是不知道如何具体实现(体现为没有方法体)
- 子类必须重写父类中的所有的抽象方法之后,方可实例化。否则,此子类仍然是一个抽象类
注意事项
- 不能用abstract修饰变量、代码块、构造器
- 不能用abstract修饰私有方法、静态方法、final的方法、final的类
- 私有方法不能重写
- 避免使用类调用静态方法(没实现没意义)
- final方法不能被重写
- final类不能有子类
二、接口
1、定义格式
- 接口的定义,它与定义类方式相似,但是使用
interface
关键字 - 它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型
- 引用数据类型:数组,类,枚举,接口,注解
[修饰符] interface 接口名{
//接口的成员列表:
// 公共的静态常量
// 公共的抽象方法
// 公共的默认方法(JDK1.8以上)
// 公共的静态方法(JDK1.8以上)
// 私有方法(JDK1.9以上)
}
举例:
public interface USB3{
//静态常量
long MAX_SPEED = 500*1024*1024;//500MB/s
//抽象方法
void in();
void out();
//默认方法
default void start(){
System.out.println("开始");
}
default void stop(){
System.out.println("结束");
}
//静态方法
static void show(){
System.out.println("USB 3.0可以同步全速地进行读写操作");
}
}
2、接口的说明
- 在JDK8.0 之前,接口中只允许出现:
- 公共的静态的常量:其中
public static final
可以省略 - 公共的抽象的方法:其中
public abstract
可以省略 - 理解:接口是从多个相似类中抽象出来的规范,不需要提供具体实现
- 公共的静态的常量:其中
- 在JDK8.0 时,接口中允许声明
默认方法
和静态方法
:- 公共的默认的方法:其中public 可以省略,建议保留,但是default不能省略
- 公共的静态的方法:其中public 可以省略,建议保留,但是static不能省略
- 在JDK9.0 时,接口又增加了:
- 私有方法
除此之外,接口中没有构造器,没有初始化块,因为接口中没有成员变量需要动态初始化
3、接口的使用规则
3.1、类实现接口
- 接口不能创建对象,但是可以被类实现(
implements
,类似于被继承) - 类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类
- 实现的动作类似继承,格式相仿,只是关键字不同,实现使用
implements
关键字
【修饰符】 class 实现类 implements 接口{
// 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
【修饰符】 class 实现类 extends 父类 implements 接口{
// 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
注意
- 如果接口的实现类是非抽象类,那么必须
重写接口中所有抽象方法
- 默认方法可以选择保留,也可以重写
- 重写时,default单词就不要再写了,它只用于在接口中表示默认方法,到类中就没有默认方法的概念了
- 接口中的静态方法不能被继承也不能被重写
3.2、接口的多实现
- 之前学过,在继承体系中,一个类只能继承一个父类
- 而对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现
- 并且,一个类能继承一个父类,同时实现多个接口
【修饰符】 class 实现类 implements 接口1,接口2,接口3。。。{
// 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
【修饰符】 class 实现类 extends 父类 implements 接口1,接口2,接口3。。。{
// 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
- 接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次
3.3、接口的多继承
- 一个接口能继承另一个或者多个接口
- 接口的继承也使用
extends
关键字,子接口继承父接口的方法
定义父接口:
public interface Chargeable {
void charge();
void in();
void out();
}
定义子接口:
public interface UsbC extends Chargeable,USB3 {
void reverse();
}
定义子接口的实现类:
public class TypeCConverter implements UsbC {
@Override
public void reverse() {
System.out.println("正反面都支持");
}
@Override
public void charge() {
System.out.println("可充电");
}
@Override
public void in() {
System.out.println("接收数据");
}
@Override
public void out() {
System.out.println("输出数据");
}
}
- 所有父接口的抽象方法都有重写
- 方法签名相同的抽象方法只需要实现一次
3.4、接口与实现类对象构成多态引用
- 实现类实现接口,类似于子类继承父类
- 因此,接口类型的变量与实现类的对象之间,也可以构成多态引用
- 通过接口类型的变量调用方法,最终执行的是你new的实现类对象实现的方法体
接口的不同实现类:
public class Mouse implements USB3 {
@Override
public void out() {
System.out.println("发送脉冲信号");
}
@Override
public void in() {
System.out.println("不接收信号");
}
}
public class KeyBoard implements USB3{
@Override
public void in() {
System.out.println("不接收信号");
}
@Override
public void out() {
System.out.println("发送按键信号");
}
}
测试类:
public class TestComputer {
public static void main(String[] args) {
Computer computer = new Computer();
USB3 usb = new Mouse();
computer.setUsb(usb);
usb.start();
usb.out();
usb.in();
usb.stop();
System.out.println("--------------------------");
usb = new KeyBoard();
computer.setUsb(usb);
usb.start();
usb.out();
usb.in();
usb.stop();
}
}
3.5、使用接口的静态成员
- 接口不能直接创建对象,但是可以通过接口名直接调用接口的
静态方法
和静态常量
public class TestUSB3 {
public static void main(String[] args) {
//通过“接口名.”调用接口的静态方法 (JDK8.0才能开始使用)
USB3.show();
//通过“接口名.”直接使用接口的静态常量
System.out.println(USB3.MAX_SPEED);
}
}
3.5、使用接口的静态方法
- 对于接口的静态方法,直接使用“
接口名.
”进行调用即可- 也只能使用“接口名.”进行调用,不能通过实现类的对象进行调用
- 对于接口的抽象方法、默认方法,只能通过实现类对象才可以调用
- 接口不能直接创建对象,只能创建实现类的对象
public class TestMobileHDD {
public static void main(String[] args) {
//创建实现类对象
MobileHDD b = new MobileHDD();
//通过实现类对象调用重写的抽象方法,以及接口的默认方法,如果实现类重写了就执行重写的默认方法,如果没有重写,就执行接口中的默认方法
b.start();
b.in();
b.stop();
//通过接口名调用接口的静态方法
// MobileHDD.show();
// b.show();
Usb3.show();
}
}
4、JDK8中相关冲突问题
4.1、默认方法冲突问题
类优先原则
- 当一个类,既继承一个父类,又实现若干个接口时
- 父类中的成员方法与接口中的抽象方法重名
- 子类就近选择执行父类的成员方法
定义接口:
public interface Friend {
default void date(){//约会
System.out.println("吃喝玩乐");
}
}
定义父类:
public class Father {
public void date(){//约会
System.out.println("爸爸约吃饭");
}
}
定义子类:
public class Son extends Father implements Friend {
@Override
public void date() {
//(1)不重写默认保留父类的
//(2)调用父类被重写的
// super.date();
//(3)保留父接口的
// Friend.super.date();
//(4)完全重写
System.out.println("跟康师傅学Java");
}
}
定义测试类:
public class TestSon {
public static void main(String[] args) {
Son s = new Son();
s.date();
}
}
- 调用父类方法:
super.方法名()
- 调用父接口默认方法:
类名.super.方法名()
接口冲突(左右为难)
- 当一个类同时实现了多个父接口,而多个父接口中包含方法签名相同的默认方法时,怎么办呢?
声明接口:
public interface BoyFriend {
default void date(){//约会
System.out.println("神秘约会");
}
}
选择保留其中一个,通过“接口名.super.方法名
“的方法选择保留哪个接口的默认方法
public class Girl implements Friend,BoyFriend{
@Override
public void date() {
//(1)保留其中一个父接口的
// Friend.super.date();
// BoyFriend.super.date();
//(2)完全重写
System.out.println("跟康师傅学Java");
}
}
- 当一个子接口同时继承了多个接口,而多个父接口中包含方法签名相同的默认方法时,怎么办呢?
另一个父接口:
public interface USB2 {
//静态常量
long MAX_SPEED = 60*1024*1024;//60MB/s
//抽象方法
void in();
void out();
//默认方法
public default void start(){
System.out.println("开始");
}
public default void stop(){
System.out.println("结束");
}
//静态方法
public static void show(){
System.out.println("USB 2.0可以高速地进行读写操作");
}
}
子接口:
public interface USB extends USB2,USB3 {
@Override
default void start() {
System.out.println("Usb.start");
}
@Override
default void stop() {
System.out.println("Usb.stop");
}
}
- 子接口重写默认方法时,default关键字可以保留
- 子类重写默认方法时,default关键字不可以保留
4.2、常量冲突问题
- 当子类继承父类又实现父接口,而父类中存在与父接口常量同名的成员变量,并且该成员变量名在子类中仍然可见
- 当子类同时实现多个接口,而多个接口存在相同同名常量
此时在子类中想要引用父类或父接口的同名的常量或成员变量时,就会有冲突问题
父类和父接口:
public class SuperClass {
int x = 1;
}
public interface SuperInterface {
int x = 2;
int y = 2;
}
public interface MotherInterface {
int x = 3;
}
子类:
public class SubClass extends SuperClass implements SuperInterface,MotherInterface {
public void method(){
// System.out.println("x = " + x);//模糊不清
System.out.println("super.x = " + super.x);
System.out.println("SuperInterface.x = " + SuperInterface.x);
System.out.println("MotherInterface.x = " + MotherInterface.x);
System.out.println("y = " + y);//没有重名问题,可以直接访问
}
}
5、接口与抽象类之间的对比
6、面试题
1、为什么接口中只能声明公共的静态的常量?
- 因为接口是标准规范,那么在规范中需要声明一些底线边界值
- 当实现者在实现这些规范时,不能去随意修改和触碰这些底线,否则就有“危险”
- 例如
- USB1.0规范中规定最大传输速率是1.5Mbps,最大输出电流是5V/500mA
- USB3.0规范中规定最大传输速率是5Gbps(500MB/s),最大输出电流是5V/900mA
2、为什么JDK8.0 之后允许接口定义静态方法和默认方法呢?
静态方法
- 因为之前的标准类库设计中,有很多Collection/Colletions或者Path/Paths这样成对的接口和类
- 后面的类中都是静态方法,而这些静态方法都是为前面的接口服务的
- 那么这样设计一对API,不如把静态方法直接定义到接口中使用和维护更方便
默认方法
- 我们要在已有的老版接口中提供新方法时
- 如果添加抽象方法,就会涉及到原来使用这些接口的类就会有问题
- 那么为了保持与旧版本代码的兼容性,只能允许在接口中定义默认方法实现
- 比如:Java8中对Collection、List、Comparator等接口提供了丰富的默认方法
3、为什么JDK1.9要允许接口定义私有方法呢?
- 因为有了默认方法和静态方法这样具有具体实现的方法
- 那么就可能出现多个方法由共同的代码可以抽取
- 而这些共同的代码抽取出来的方法又只希望在接口内部使用,所以就增加了私有方法
三、内部类
1、概述
什么是内部类
- 将一个类A定义在另一个类B里面
- 里面的那个类A就称为
内部类(InnerClass)
- 类B则称为
外部类(OuterClass)
- 里面的那个类A就称为
为什么要声明内部类呢?
- 具体来说,当一个事物A的内部,还有一个部分需要一个完整的结构B进行描述
- 而这个内部的完整的结构B又只为外部事物A提供服务,不在其他地方单独使用
- 那么整个内部的完整结构B最好使用内部类
内部类的分类
2、成员内部类
概述
- 如果成员内部类中不使用外部类的非静态成员,那么通常将内部类声明为
静态内部类
,否则声明为非静态内部类
语法格式:
[修饰符] class 外部类{
[其他修饰符] [static] class 内部类{
}
}
成员内部类的使用特征,概括来讲有如下两种角色:
- 成员内部类作为
类的成员的角色
:- 和外部类不同,Inner class还可以声明为private或protected(四种都可以)
- 可以调用外部类的结构。(注意:在静态内部类中不能使用外部类的非静态成员)
- Inner class 可以声明为static的,但此时就不能再使用外层类的非static的成员变量
- 成员内部类作为
类的角色
:- 可以在内部定义属性、方法、构造器等结构
- 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
- 可以声明为abstract类 ,因此可以被其它的内部类继承
- 可以声明为final的,表示不能被继承
- 编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)
注意点:
- 外部类访问成员内部类的成员,需要“内部类.成员”或“内部类对象.成员”的方式
- 成员内部类可以直接使用外部类的所有成员,包括私有的数据
- 当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的
创建成员内部类对象
- 实例化静态内部类
外部类名.静态内部类名 变量 = 外部类名.静态内部类名();
变量.非静态方法();
- 实例化非静态内部类
外部类名 变量1 = new 外部类();
外部类名.非静态内部类名 变量2 = 变量1.new 非静态内部类名();
变量2.非静态方法();
举例:
public class TestMemberInnerClass {
public static void main(String[] args) {
//创建静态内部类实例,并调用方法
Outer.StaticInner inner = new Outer.StaticInner();
inner.inFun();
//调用静态内部类静态方法
Outer.StaticInner.inMethod();
System.out.println("*****************************");
//创建非静态内部类实例(方式1),并调用方法
Outer outer = new Outer();
Outer.NoStaticInner inner1 = outer.new NoStaticInner();
inner1.inFun();
//创建非静态内部类实例(方式2)
Outer.NoStaticInner inner2 = outer.getNoStaticInner();
inner1.inFun();
}
}
class Outer{
private static String a = "外部类的静态a";
private static String b = "外部类的静态b";
private String c = "外部类对象的非静态c";
private String d = "外部类对象的非静态d";
static class StaticInner{
private static String a ="静态内部类的静态a";
private String c = "静态内部类对象的非静态c";
public static void inMethod(){
System.out.println("Inner.a = " + a);
System.out.println("Outer.a = " + Outer.a);
System.out.println("b = " + b);
}
public void inFun(){
System.out.println("Inner.inFun");
System.out.println("Outer.a = " + Outer.a);
System.out.println("Inner.a = " + a);
System.out.println("b = " + b);
System.out.println("c = " + c);
// System.out.println("d = " + d);//不能访问外部类的非静态成员
}
}
class NoStaticInner{
private String a = "非静态内部类对象的非静态a";
private String c = "非静态内部类对象的非静态c";
public void inFun(){
System.out.println("NoStaticInner.inFun");
System.out.println("Outer.a = " + Outer.a);
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("Outer.c = " + Outer.this.c);
System.out.println("c = " + c);
System.out.println("d = " + d);
}
}
public NoStaticInner getNoStaticInner(){
return new NoStaticInner();
}
}
3、局部内部类
3.1、非匿名局部内部类
语法格式:
[修饰符] class 外部类{
[修饰符] 返回值类型 方法名(形参列表){
[final/abstract] class 内部类{
}
}
}
- 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编号
- 这里有编号是因为同一个外部类中,不同的方法中存在相同名称的局部内部类
- 和成员内部类不同的是,它前面不能有权限修饰符等
- 局部内部类如同局部变量一样,有作用域
- 局部内部类中是否能访问外部类的非静态的成员,取决于所在的方法
举例:
public class TestLocalInner {
public static void main(String[] args) {
Outer.outMethod();
System.out.println("-------------------");
Outer out = new Outer();
out.outTest();
System.out.println("-------------------");
Runner runner = Outer.getRunner();
runner.run();
}
}
class Outer{
public static void outMethod(){
System.out.println("Outer.outMethod");
final String c = "局部变量c";
class Inner{
public void inMethod(){
System.out.println("Inner.inMethod");
System.out.println(c);
}
}
Inner in = new Inner();
in.inMethod();
}
public void outTest(){
class Inner{
public void inMethod1(){
System.out.println("Inner.inMethod1");
}
}
Inner in = new Inner();
in.inMethod1();
}
public static Runner getRunner(){
class LocalRunner implements Runner{
@Override
public void run() {
System.out.println("LocalRunner.run");
}
}
return new LocalRunner();
}
}
interface Runner{
void run();
}
3.2、匿名内部类
- 因为考虑到这个子类或实现类是
一次性
的,那么我们“费尽心机”的给它取名字,就显得多余 - 那么我们完全可以使用匿名内部类的方式来实现,避免给类命名的问题
语法格式:
new 父类([实参列表]){
重写方法...
}
new 父接口(){
重写方法...
}
举例1:使用匿名内部类的对象直接调用方法:
interface A{
void a();
}
public class Test{
public static void main(String[] args){
new A(){
@Override
public void a() {
System.out.println("aaaa");
}
}.a();
}
}
举例2:通过父类或父接口的变量多态引用匿名内部类的对象
interface A{
void a();
}
public class Test{
public static void main(String[] args){
A obj = new A(){
@Override
public void a() {
System.out.println("aaaa");
}
};
obj.a();
}
}
举例3:匿名内部类的对象作为实参
interface A{
void method();
}
public class Test{
public static void test(A a){
a.method();
}
public static void main(String[] args){
test(new A(){
@Override
public void method() {
System.out.println("aaaa");
}
});
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/148563.html