怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版,每个 Java 初学者都应该收藏(10)

简介: 怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版,每个 Java 初学者都应该收藏

03、具体示例


为了进一步展示抽象类的特性,我们再来看一个具体的示例。假设现在有一个文件,里面的内容非常简单——“Hello World”,现在需要有一个读取器将内容读取出来,最好能按照大写的方式,或者小写的方式。


这时候,最好定义一个抽象类,比如说 BaseFileReader:


public abstract class BaseFileReader {
    protected Path filePath;
    protected BaseFileReader(Path filePath) {
        this.filePath = filePath;
    }
    public List<String> readFile() throws IOException {
        return Files.lines(filePath)
                .map(this::mapFileLine).collect(Collectors.toList());
    }
    protected abstract String mapFileLine(String line);
}


filePath 为文件路径,使用 protected 修饰,表明该成员变量可以在需要时被子类访问。


readFile() 方法用来读取文件,方法体里面调用了抽象方法 mapFileLine()——需要子类扩展实现大小写的方式。


你看,BaseFileReader 设计的就非常合理,并且易于扩展,子类只需要专注于具体的大小写实现方式就可以了。


小写的方式:


public class LowercaseFileReader extends BaseFileReader {
    protected LowercaseFileReader(Path filePath) {
        super(filePath);
    }
    @Override
    protected String mapFileLine(String line) {
        return line.toLowerCase();
    }
}


大写的方式:


public class UppercaseFileReader extends BaseFileReader {

   protected UppercaseFileReader(Path filePath) {

       super(filePath);

   }


   @Override

   protected String mapFileLine(String line) {

       return line.toUpperCase();

   }

}


你看,从文件里面一行一行读取内容的代码被子类复用了——抽象类 BaseFileReader 类中定义的普通方法 readFile()。与此同时,子类只需要专注于自己该做的工作,LowercaseFileReader 以小写的方式读取文件内容,UppercaseFileReader 以大写的方式读取文件内容。


接下来,我们来新建一个测试类 FileReaderTest:


public class FileReaderTest {

   public static void main(String[] args) throws URISyntaxException, IOException {

       URL location = FileReaderTest.class.getClassLoader().getResource("helloworld.txt");

       Path path = Paths.get(location.toURI());

       BaseFileReader lowercaseFileReader = new LowercaseFileReader(path);

       BaseFileReader uppercaseFileReader = new UppercaseFileReader(path);

       System.out.println(lowercaseFileReader.readFile());

       System.out.println(uppercaseFileReader.readFile());

   }

}


项目的 resource 目录下有一个文本文件,名字叫 helloworld.txt。



可以通过 ClassLoader.getResource() 的方式获取到该文件的 URI 路径,然后就可以使用 LowercaseFileReader 和 UppercaseFileReader 两种方式读取到文本内容了。


输出结果如下所示:


[hello world]

[HELLO WORLD]



十、Java 接口


对于面向对象编程来说,抽象是一个极具魅力的特征。如果一个程序员的抽象思维很差,那他在编程中就会遇到很多困难,无法把业务变成具体的代码。在 Java 中,可以通过两种形式来达到抽象的目的,一种是抽象类,另外一种就是接口。


如果你现在就想知道抽象类与接口之间的区别,我可以提前给你说一个:


一个类只能继承一个抽象类,但却可以实现多个接口。

当然了,在没有搞清楚接口到底是什么,它可以做什么之前,这个区别理解起来会有点难度。


01、接口是什么


接口是通过 interface 关键字定义的,它可以包含一些常量和方法,来看下面这个示例。


public interface Electronic {

   // 常量

   String LED = "LED";


   // 抽象方法

   int getElectricityUse();


   // 静态方法

   static boolean isEnergyEfficient(String electtronicType) {

       return electtronicType.equals(LED);

   }


   // 默认方法

   default void printDescription() {

       System.out.println("电子");

   }

}



1)接口中定义的变量会在编译的时候自动加上 public static final 修饰符,也就是说 LED 变量其实是一个常量。


Java 官方文档上有这样的声明:


Every field declaration in the body of an interface is implicitly public, static, and final.

换句话说,接口可以用来作为常量类使用,还能省略掉 public static final,看似不错的一种选择,对吧?


不过,这种选择并不可取。因为接口的本意是对方法进行抽象,而常量接口会对子类中的变量造成命名空间上的“污染”。


2)没有使用 private、default 或者 static 关键字修饰的方法是隐式抽象的,在编译的时候会自动加上 public abstract 修饰符。也就是说 getElectricityUse() 其实是一个抽象方法,没有方法体——这是定义接口的本意。


3)从 Java 8 开始,接口中允许有静态方法,比如说 isEnergyEfficient() 方法。


静态方法无法由(实现了该接口的)类的对象调用,它只能通过接口的名字来调用,比如说 Electronic.isEnergyEfficient("LED")。


接口中定义静态方法的目的是为了提供一种简单的机制,使我们不必创建对象就能调用方法,从而提高接口的竞争力。


4)接口中允许定义 default 方法也是从 Java 8 开始的,比如说 printDescription(),它始终由一个代码块组成,为实现该接口而不覆盖该方法的类提供默认实现,也就是说,无法直接使用一个“;”号来结束默认方法——编译器会报错的。


image.png


允许在接口中定义默认方法的理由是很充分的,因为一个接口可能有多个实现类,这些类就必须实现接口中定义的抽象类,否则编译器就会报错。假如我们需要在所有的实现类中追加某个具体的方法,在没有 default 方法的帮助下,我们就必须挨个对实现类进行修改。


来看一下 Electronic 接口反编译后的字节码吧,你会发现,接口中定义的所有变量或者方法,都会自动添加上 public 关键字——假如你想知道编译器在背后都默默做了哪些辅助,记住反编译字节码就对了。


public interface Electronic
{
    public abstract int getElectricityUse();
    public static boolean isEnergyEfficient(String electtronicType)
    {
        return electtronicType.equals("LED");
    }
    public void printDescription()
    {
        System.out.println("\u7535\u5B50");
    }
    public static final String LED = "LED";
}



有些读者可能会问,“二哥,为什么我反编译后的字节码和你的不一样,你用了什么反编译工具?”其实没有什么秘密,微信搜「沉默王二」回复关键字「JAD」就可以免费获取了,超级好用。


相关文章
|
6月前
|
设计模式 安全 Java
面向对象编程的精髓:Java设计模式 - 原型模式(Prototype)完全参考手册
【4月更文挑战第7天】原型模式是OOP中的创建型设计模式,用于通过复制现有实例创建新实例,尤其适用于创建成本高或依赖其他对象的情况。它包括Prototype接口、ConcretePrototype实现和Client客户端角色。优点是性能优化、避免子类化和动态增加产品族。实现包括定义原型接口、实现具体原型和客户端调用克隆方法。最佳实践涉及确保克隆正确性、选择深拷贝或浅拷贝及考虑线程安全。但需注意克隆方法管理、性能开销和循环引用等问题。在Java中,实现Cloneable接口和覆盖clone方法可实现原型模式。
77 4
|
6月前
|
网络协议 Java Maven
Java自救手册
Java自救手册
85 2
|
6月前
|
算法 Java
「译文」Java 垃圾收集参考手册(四):Serial GC
「译文」Java 垃圾收集参考手册(四):Serial GC
|
6月前
|
算法 Java PHP
「译文」Java 垃圾收集参考手册(一):垃圾收集简介
「译文」Java 垃圾收集参考手册(一):垃圾收集简介
|
5月前
|
Java 开发者
【技术成长日记】Java 线程的自我修养:从新手到大师的生命周期修炼手册!
【6月更文挑战第19天】Java线程之旅,从新手到大师的进阶之路:始于创建线程的懵懂,理解就绪与运行状态的成长,克服同步难题的进阶,至洞悉生命周期的精通。通过实例,展示线程的创建、运行与同步,展现技能的不断提升与升华。
39 2
|
5月前
|
Java 数据安全/隐私保护
Java基础手册二(类和对象 对象创建和使用 面向对象封装性 构造方法与参数传递 this关键字 static关键字 继承 多态 方法覆盖 final关键字 访问控制权限修饰符)
Java基础手册二(类和对象 对象创建和使用 面向对象封装性 构造方法与参数传递 this关键字 static关键字 继承 多态 方法覆盖 final关键字 访问控制权限修饰符)
35 0
|
5月前
|
存储 Java
Java基础手册(标识符 关键字 字面值 变量 数据类型 字符编码 运算符 控制语句 方法及方法重载和递归 面向对象与面向过程)
Java基础手册(标识符 关键字 字面值 变量 数据类型 字符编码 运算符 控制语句 方法及方法重载和递归 面向对象与面向过程)
39 0
|
5月前
|
存储 自然语言处理 Java
Java IO流完全手册:字节流和字符流的常见应用场景分析!
【6月更文挑战第26天】Java IO流涵盖字节流和字符流,字节流用于二进制文件读写及网络通信,如图片和音频处理;字符流适用于文本文件操作,支持多语言编码,确保文本正确性。在处理数据时,根据内容类型选择合适的流至关重要。
75 0
|
6月前
|
Java 开发者
效率工具RunFlow完全手册之Java开发者篇
这篇手册面向Java开发者,介绍了如何使用QLExpress执行Java代码和进行方法验证。示例包括数学计算、读取系统环境变量及格式化输出。QLExpress提供运行时执行Java代码的功能,并支持QLExpress语法的脚本文件。
45 3
效率工具RunFlow完全手册之Java开发者篇
|
6月前
|
算法 安全 Java
「译文」Java 垃圾收集参考手册(三):GC 算法基础篇
「译文」Java 垃圾收集参考手册(三):GC 算法基础篇