Java法式员必看:技术大牛都在用这四个小技能

2020-2-14 19:15| 发布者: 陌上人如玉__| 查看: 68| 评论: 0

摘要: 欢迎关注头条号:Java小野猫 在本文中,我们将探讨4种技巧以帮助提高开发简便性和可读性。并非所有这些技巧都适用于每种情况,或者大多数情况。例如,可能只有少数集中方法适用于协变返回类型或者只有几个通用类适合 ...

Java法式员必看:技术大牛都在用这四个小技能

接待关注头条号:Java小野猫

在本文中,我们将探讨4种技能以帮助进步开辟简洁性和可读性。并非一切这些技能都适用于每种情况,大概大大都情况。例如,能够只要少数集合方式适用于协变返回范例大概只要几个通用类合适利用穿插通用范例的形式,而其他方式能够可进步大大都代码库意图的可读性和清楚度。不管在何种情况下,重要的是不但方法会这些技能,而且要晓得何时利用它们。

协变返回范例


即使是最令人沉迷的Java操纵手册也城市包括有关继续、接口、笼统类和方式覆盖的先容,但却很少探讨覆盖方式时更复杂的能够性。例如,下面的代码片断对于即使是Java开辟新手也不会惊奇:

public interface Animal {

public String makeNoise();

}

public class Dog implements Animal {

@Override

public String makeNoise() {

return "Woof";

}

}

public class Cat implements Animal {

@Override

public String makeNoise() {

return "Meow";

}

}

这是多态的根基概念:工具的方式可按照其接口(Animal::makeNoise)挪用但该方式挪用的现实行为取决于实现范例(Dog::makeNoise)。例如,下面方式的输出将会改变,取决于Dog工具或Cat工具能否传递到该方式:

public class Talker {

public static void talk(Animal animal) {

System.out.println(animal.makeNoise());

}

}

Talker.talk(new Dog()); // Output: Woof

Talker.talk(new Cat()); // Output: Meow

虽然这是很多Java利用中常用的技能,但在覆盖方式时能够会采用另一种操纵:变动返回范例。虽然这能够是无穷制的覆盖方式的方式,但对覆盖方式的返回范例有着严酷限制。按照Java 8 SE说话标准(第248页):

假如一种方式声明d 1 (包括返回范例R 1)覆盖或隐藏另一种方式d 2 (包括返回范例R 2)的声明,那末d 1 必须是的返回范例替换,否则将发生编译时毛病。

其中return-type-substitutable (同上,第240页)被界说为:

1. 假如R1 无效,则R2无效

2. 假如R1 是原始范例,则R2 与R1不异

3. 假如R1 是援用范例,则合适以下条件之一:

a. R1 适用于d2 范例参数,它是R2.的子范例

b. R1 可经过未经检查的转换被转换为R2 的子范例

c. d1 不具有与d2 不异的签名,且R1 = |R2|

可以说,最风趣的例子是Rules 3.a.和3.b.:当覆盖方式时,返回范例的子范例可被声明作为覆盖返回范例,例如:

1. public interface CustomCloneable {

2. public Object customClone();

3. }

4. public class Vehicle implements CustomCloneable {

5. private final String model;

6. public Vehicle(String model) {

7. this.model = model;

8. }

9. @Override

10. public Vehicle customClone() {

11. return new Vehicle(this.model);

12. }

13. public String getModel() {

14. return this.model;

15. }

16. }

17. Vehicle originalVehicle = new Vehicle("Corvette");

18. Vehicle clonedVehicle = originalVehicle.customClone();

19. System.out.println(clonedVehicle.getModel());

虽然clone()的原始返回范例是Object,但我们可在clonedVehicle挪用getModel() (没有显式转换),由于我们已经将Vehicle :: clone的返回范例重写为Vehicle。这消除了对乱码的需要,我们晓得我们寻觅的返回范例是Vehicle,即使它被声明为Object(相当于基于先验信息的平安投递,但严酷来说不服安):
  1. Vehicle clonedVehicle = (Vehicle) originalVehicle.customClone();

请留意我们仍然可将Vehicle的范例声明为Object,返回范例将规复到Object的原始范例:

1. Object clonedVehicle = originalVehicle.customClone();

2. System.out.println(clonedVehicle.getModel()); // ERROR: getModel not a method of Object

请留意,对于通用参数,返回范例不能重载,但对于通用类,则可重载。例如,假如基类或接口方式返回 List,子类的返回范例能够覆盖到 ArrayList,但能够不会覆盖到List

穿插通用范例


建立通用类是很好的方式来建立一组类与组合工具停止交互。例如,List 仅存储和检索范例T的工具,而不领会其包括的元素的性质,在某些情况下,我们想要限制我们的通用范例参数(T)具有特定特征。例如,以下接口:

1. public interface Writer {

2. public void write();

3. }

我们能够想要建立特定Writers组合,鄙人面Composite

Patterns:

1. public class WriterComposite implements Writer {

2. private final List writers;

3. public WriterComposite(List writers) {

4. this.writers = writer;

5. }

6. @Override

7. public void write() {

8. for (Writer writer: this.writers) {

9. writer.write();

10. }

11. }

12. }

我们现在可遍历Writers树,不晓得我们碰到的具体Writer是standalongWriter还是Writer组合。假如我们也希望我们的组合作为reader和writer的组合怎样办?例如,假如我们有以下接口:

1. public interface Reader {

2. public void read();

3. }

我们若何将我们的WriterComposite点窜成ReaderWriterComposite?有种技能可建立新的接口ReaderWriter,来融合Reader和Writer接口:
  1. public interface ReaderWriter implements Reader, Writer {}

然后,我们可点窜现有的WriterComposite为以下内容:

1. public class ReaderWriterComposite implements ReaderWriter {

2. private final List readerWriters;

3. public WriterComposite(List readerWriters) {

4. this.readerWriters = readerWriters;

5. }

6. @Override

7. public void write() {

8. for (Writer writer: this.readerWriters) {

9. writer.write();

10. }

11. }

12. @Override

13. public void read() {

14. for (Reader reader: this.readerWriters) {

15. reader.read();

16. }

17. }

18. }

虽然这样做完成了我们的方针,但我们在代码中制造了收缩:我们建立了一个接口,其唯一目标是将两个现有接口合并在一路。随着越来越多接口出现,我们会看到收缩的组合爆炸。例如,假如我们建立新的Modifier接口,我们现在会需要createReaderModifier、WriterModifier和ReaderWriter接口。请留意,这些接口并不增加任何功用:它们只是合并现有接口。

为了消除这个收缩,我们需要可以指定我们的 ReaderWriterComposite 接管通用范例参数,仅当它们都是Reader和Writer时。穿插通用范例答应我们这样做,为了指定通用范例参数,必须同时摆设reader和writer接口,我们在通用范例约束之间利用&运算符:

1. public class ReaderWriterComposite implements Reader, Writer {

2. private final List readerWriters;

3. public WriterComposite(List readerWriters) {

4. this.readerWriters = readerWriters;

5. }

6. @Override

7. public void write() {

8. for (Writer writer: this.readerWriters) {

9. writer.write();

10. }

11. }

12. @Override

13. public void read() {

14. for (Reader reader: this.readerWriters) {

15. reader.read();

16. }

17. }

18. }

假如没有收缩继续树,我们现在可约束通用范例参数来摆设多个接口。请留意,不异限制还可制定其中一个接口是笼统类大概具体类。例如,假如我们将writer接口变动成笼统类,类似以下:

1. public abstract class Writer {

2. public abstract void write();

3. }

我们仍然可限制我们的通用范例参数为Reader和Writer,但Writer(由于它是笼统类而不是接口)必须先被指定(还要留意,我们的ReaderWriterComposite现在扩大Writer笼统类并摆设Reader接口,而不是实现两者)

1. public class ReaderWriterComposite extends Writer implements Reader {

2. // Same class body as before

3. }

一样重要的是,这类穿插通用范例可用于两个以上接口(大概一个笼统类和多个接口),例如,假如我们想要我们的组合还包括Modifierinterface,我们可按以下编写我们的类界说:

1. public class ReaderWriterComposite implements Reader, Writer, Modifier {

2. private final List things;

3. public ReaderWriterComposite(List things) {

4. this.things = things;

5. }

6. @Override

7. public void write() {

8. for (Writer writer: this.things) {

9. writer.write();

10. }

11. }

12. @Override

13. public void read() {

14. for (Reader reader: this.things) {

15. reader.read();

16. }

17. }

18. @Override

19. public void modify() {

20. for (Modifier modifier: this.things) {

21. modifier.modify();

22. }

23. }

24. }

虽然可履行上述,但这能够是代码嗅觉的迹象(Reader、Writer和Modifier工具能够是更具体的工具,例如File)

有关穿插通用范例的更多信息,请参阅Java 8说话标准。

自动封闭类


建立资本类是一种常见做法,但连结该资本的完整性具有应战性,出格是当触及异常处置时。例如,假定我们建立一个资本类,Resource,并希望对该资本履行操纵,这能够会激发异常(实例化进程也能够会激发异常):

1. public class Resource {

2. public Resource() throws Exception {

3. System.out.println("Created resource");

4. }

5. public void someAction() throws Exception {

6. System.out.println("Performed some action");

7. }

8. public void close() {

9. System.out.println("Closed resource");

10. }

11. }

在任一情况下(激发异常大概没有激发),我们要封闭我们的资本以确保没有资本泄露。一般的进程是在finally块中封锁我们的 close() 方式,确保不管发生什么情况,我们的资本在封锁履行范围完成前封闭:

1. Resource resource = null;

2. try {

3. resource = new Resource();

4. resource.someAction();

5. }

6. catch (Exception e) {

7. System.out.println("Exception caught");

8. }

9. finally {

10. resource.close();

11. }

经过简单的检查,我们发现很多榜样代码从Resource工具someAction()的履行可读性减损。为了填补这类情况,Java 7引入try-with-resources声明,resource可在try声明中建立,并在分开try履行范围前自动封闭。为了让类可利用try-with-resources,必须摆设自动封闭接口:

1. public class Resource implements AutoCloseable {

2. public Resource() throws Exception {

3. System.out.println("Created resource");

4. }

5. public void someAction() throws Exception {

6. System.out.println("Performed some action");

7. }

8. @Override

9. public void close() {

10. System.out.println("Closed resource");

11. }

12. }

我们的Resource类现在采用自动可封闭接口,我们可清算代码以确保资本在分开try履行范围之前封闭。

1. try (Resource resource = new Resource()) {

2. resource.someAction();

3. }

4. catch (Exception e) {

5. System.out.println("Exception caught");

6. }

与非try-with-resource技术相比,这个进程没有那末紊乱,并连结不异平安性(在try履行范围完成前resource始终封闭)。假如履行上述try-with-resource,我们获得以下输出:

1. Created resource

2. Performed some action

3. Closed resource

为了展现这类try-with-resource技术的平安性,我们可改变someAction()为抛出Exception:

1. public class Resource implements AutoCloseable {

2. public Resource() throws Exception {

3. System.out.println("Created resource");

4. }

5. public void someAction() throws Exception {

6. System.out.println("Performed some action");

7. throw new Exception();

8. }

9. @Override

10. public void close() {

11. System.out.println("Closed resource");

12. }

13. }

假如我们重新运转try -with-resources声明,我们可获得以下输出:

1. Created resource

2. Performed some action

3. Closed resource

4. Exception caught

请留意,即使在履行someAction()方式时抛出Exception,我们的资本仍然封闭,则Exception被捕捉。这可确保在分开try履行范围前,我们的资本保证封闭。一样重要的是要留意,resource可摆设Closeable接口,仍然可利用try-with-resources声明。摆设自动封闭接口和可封闭接口之间的区分在于从 close() 方式签名抛出的Exception范例:Exceptionand IOException。在我们的例子中,我们简单地改变了 close() 方式的签名,不会激发异常。

终极类和方式


在几近一切情况下,我们建立的类可由另一位开辟职员停止扩大,并按照其需求停止自界说(我们可扩大自己的类),即使我们不希望扩大我们的类。虽然这在大大都情况下是充足的,但偶然辰我们不希望方式被覆盖,大概让我们的类被扩大。例如,我们建立File类来封装文件系统中文件的读取和写入,我们能够不希望任何子类覆盖我们的 read(int bytes) 和write(String data)方式(假如这些方式的裸机被改变,能够会致使文件系统损坏)。在这类情况下,我们标志不成扩大方式作为final:

1. public class File {

2. public final String read(int bytes) {

3. // Execute the read on the file system

4. return "Some read data";

5. }

6. public final void write(String data) {

7. // Execute the write to the file system

8. }

9. }

现在,假如另一个类希望覆盖读取或写入方式,则会激发编译毛病:没法从File覆盖终极方式。我们不但记录我们的方式不应当被覆盖,编译器也确保这个意图在编译时不会履行。

在将这个想法扩大到全部类时,能够偶然辰我们不希望我们的类被扩大。这不但会使类的每个方式不成履行,还会让没法建立类的子范例。例如,假如我们在建立平安框架来利用密钥天生器,我们能够不会想要任何内部开辟职员扩大我们的密钥天生器以及覆盖天生算法(自界说功用能够会影响系统):

1. public final class KeyGenerator {

2. private final String seed;

3. public KeyGenerator(String seed) {

4. this.seed = seed;

5. }

6. public CryptographicKey generate() {

7. // ...Do some cryptographic work to generate the key...

8. }

9. }

经过将我们的KeyGenerator类作为终极类,编译器可确保没有类可扩大我们的类以及将其传递到我们的框架作为有用的加密密钥天生器。虽然简单地标志thegenerate() 方式为终极似乎已经充足,但这并不会阻止开辟职员建立自界说密钥天生器并将其作为有用天生器。由于我们的系统为平安导向,所以应当尽能够不要信赖内部天下(聪明的开辟职员可经过改变KeyGenerator类中其他方式的功用来改变天生算法)。

虽然这似乎是对开放/封锁原则的公然否认,但这样做有很好的来由。从我们上面平安示例中可以看出,很多时辰,我们没法让内部开辟职员对我们的利用法式做想做的工作,我们必须对集成很是仔细地做决议。这个概念无处不在,例如C#说话默许一个类作为final(不能被扩大),而且,它必须被开辟职员指定为开放。此外,我们应当很是稳重地肯定哪些类可以被扩大,哪些方式可被覆盖。

结论


虽然我们仅利用Java小部分功用来编写大大都代码,但这足以处理我们碰到的大部分题目。偶然辰,我们需要深入挖掘那些被忘记大概未知的说话部分来处理特定题目。协变返回范例和穿插通用范例等技术可用于一次性的情况,而自动封闭资本和终极方式及类的方式例可用于发生更可读和更正确的代码。你可将这些技能与平常编程理论连系起来,这可帮助你更好地编写Java代码

接待做Java的朋友们私信我【材料】免费获得免费的Java架构进修材料(里面有高可用、高并发、高性能及散布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个常识点的架构材料)

其中覆盖了互联网的各个方面,时代碰到各类产物各类场景下的各类题目,很值得大师鉴戒和进修,扩大自己的技术广度和常识面。


路过

雷人

握手

鲜花

鸡蛋

相关分类

返回顶部