on java8学习笔记2022.2.19-2022.2.20
2022.2.19
第十章 接口
如果一个类并不需要包含抽象方法,但同时还想阻止对它的任何实例化,这时将其定义为抽象类就很有用了。
接口和抽象类之间最显著的区别可能是两者的惯用方式。接口通常暗示“类的类型”或作为形容词来使用,例如
Runnable
或Serializable
,而抽象类通常是类层次结构的一部分,并且是“事物的类型”,例如String
或Instrument
。接口也可以包含字段,但这些字段是隐式的
static
和final
。接口(该接口的修饰符是默认)如果只有包访问权限的话,那么包外的类是无法实现这个接口的,即便这个接口里是public方法
你可以选择将接口中的方法显式声明为
public
,但即使不显式声明,它们也是public
的。所以当实现一个接口时,来自接口的方法必须被定义为public
。否则,它们将默认为包访问权限,导致在继承期间降低了方法的可访问性,而这是Java编译器不允许的。其实这里我是没太理解作者的意思的,不过就我目前测试的结果,其实接口直接默认确实是最好的选择,因为接口没有private这种东西,也没有protected这种东西,实际上就是默认和public,而当接口是public时,它的方法本身就全部默认是public的,而默认的时候就全部内置默认,非常和谐,而接口本身就是为了描述类所共有的特征的,所有类的方法的这么个东西,所以访问权限一致我倒是觉得挺合理的
package Test;import example.a; import example.one;public class test implements a {public static void main(String[] args){new test().function();}public void function(){System.out.println("hello");} }
package example;public interface a {void function(); }
添加默认方法的一个令人信服的原因是,它允许向现有接口中添加方法,而不会破坏已经在使用该接口的所有代码。默认方法有时也称为防御方法(defender method)或虚拟扩展方法(virtual extension method)。
在JDK 9中,接口里的
default
和static
方法都可以是private
的。讲道理,第二句话就使得接口有了类的特征了,而且很诡异,这么搞有什么意义,如果设为了private,接口外的都无法访问这个方法,那我设置这个方法是要干嘛?注意,我的意思是这个接口对实现类是直接隐藏的,因为不是有默认final这个东西,真的让人头大,比如下面这个
package example;public interface a {private void b(){System.out.println("hello");}}
package example; public class one implements a{String s1 = "hello";public one (String s1){this.s1 = s1;}public static void main(String[] args){System.out.println(new two("second"));a b = new one();b.}@Overrideprivate void b(){} }
关注下@Override,会发现,它提示你未从超类重写方法,而下面的代码显示了你在包外无法调用接口a的私有静态方法,这导致我看不懂这个私有静态方法有什么用,当然,同时编译器也会告诉你接口中的方法多余.
package example; public class one implements a{String s1 = "hello";public one (String s1){this.s1 = s1;}public static void main(String[] args){a.c();} }
package example;public interface a {private static void b(){System.out.println("hello");}public static void c(){System.out.println("hi");} }
// interfaces/MultipleInheritance.java import java.util.*;interface One {default void first() { System.out.println("first"); } }interface Two {default void second() {System.out.println("second"); } }interface Three {default void third() { System.out.println("third"); } }class MI implements One, Two, Three {}public class MultipleInheritance {public static void main(String[] args) {MI mi = new MI(); mi.first(); mi.second(); mi.third(); } } /* 输出: first second third */
// interfaces/MICollision.java import java.util.*;interface Bob1 {default void bob() {System.out.println("Bob1::bob"); } }interface Bob2 {default void bob() {System.out.println("Bob2::bob"); } }// class Bob implements Bob1, Bob2 {} /* 产生: error: class Bob inherits unrelated defaults for bob() from types Bob1 and Bob2 class Bob implements Bob1, Bob2 {} ^ 1 error */interface Sam1 {default void sam() {System.out.println("Sam1::sam"); } }interface Sam2 {default void sam(int i) {System.out.println(i * 2); } }// 这里能正常工作是因为参数列表不同 class Sam implements Sam1, Sam2 {}interface Max1 {default void max() {System.out.println("Max1::max"); } }interface Max2 {default int max() { return 47; } }// class Max implements Max1, Max2 {} /* 产生: error: types Max2 and Max1 are incompatible; both define max(), but with unrelated return types class Max implements Max1, Max2 {} ^ 1 error */
这里要解决的话就只能重写
返回类型不是方法签名的一部分
// interfaces/Jim.java import java.util.*;interface Jim1 {default void jim() {System.out.println("Jim1::jim");} }interface Jim2 {default void jim() {System.out.println("Jim2::jim");} }public class Jim implements Jim1, Jim2 {@Override public void jim() {Jim2.super.jim();}public static void main(String[] args) {new Jim().jim();} } /* 输出: Jim2::jim */
不是很清楚为什么这里一定要加个super,如果不加的话,会报错,当然,也可以这么写
package example; // interfaces/Jim.java import java.util.*;interface Jim1 {default void jim() {System.out.println("Jim1::jim");} }interface Jim2 {default void jim() {System.out.println("Jim2::jim");} }public class Jim implements Jim1, Jim2 {@Override public void jim() {System.out.println("hello");}public static void main(String[] args) {new Jim().jim();} }
有可能那个交给它基类写去了?谁知道呢
// interfaces/MetalWork.java import onjava.Operation;class Heat implements Operation {@Override public void execute() {Operation.show("Heat");} }public class MetalWork {public static void main(String[] args) {// 必须在静态上下文中定义才能使用方法引用Operation twist = new Operation() {public void execute() {Operation.show("Twist");}};Operation.runOps(new Heat(), // [1]new Operation() { // [2]public void execute() {Operation.show("Hammer");}},twist::execute, // [3]() -> Operation.show("Anneal") // [4]);} } /* 输出: Heat Hammer Twist Anneal */
// onjava/Operation.java package onjava;public interface Operation {void execute();static void runOps(Operation... ops) {for(Operation op : ops)op.execute();}static void show(String msg) {System.out.println(msg);} }
这个看了挺久的,除了最后一个lambda没学过以外,其他大概看懂了,第一个是上面已经定义好的类,第二个是以接口定义的类,第三个是方法引用.
这里让我比较新奇的就是匿名的接口类的定义方式了
怎么看这个匿名接口类的定义方法呢?首先将new先隔开,那么后面Operation就是定义接口中需要我们定义的方法,而那个new则是创建这个对象
Operation() {public void execute() {Operation.show("Twist");}}
这里没有构造器,应该调用的默认构造器
{public void execute() {Operation.show("Twist");}}
> ```java
> // interfaces/interfaceprocessor/Processor.java
> package interfaces.interfaceprocessor;
>
> public interface Processor {
> default String name() {
> return getClass().getSimpleName();
> }
> Object process(Object input);
> }
> // interfaces/interfaceprocessor/Applicator.java
> package interfaces.interfaceprocessor;
>
> public class Applicator {
> public static void apply(Processor p, Object s) {
> System.out.println("Using Processor " + p.name());
> System.out.println(p.process(s));
> }
> }
> ```
>
> 复用代码的第一种方法是,调用者可以编写符合这个接口的类,如下所示:
>
> ```java
> // interfaces/interfaceprocessor/StringProcessor.java
> // {java interfaces.interfaceprocessor.StringProcessor}
> package interfaces.interfaceprocessor;
> import java.util.*;
>
> interface StringProcessor extends Processor {
> @Override
> String process(Object input); // [1]
> String S = // [2]
> "If she weighs the same as a duck, " +
> "she's made of wood";
> static void main(String[] args) { // [3]
> Applicator.apply(new Upcase(), S);
> Applicator.apply(new Downcase(), S);
> Applicator.apply(new Splitter(), S);
> }
> }
>
> class Upcase implements StringProcessor {
> @Override // 协变返回
> public String process(Object input) {
> return ((String)input).toUpperCase();
> }
> }
>
> class Downcase implements StringProcessor {
> @Override
> public String process(Object input) {
> return ((String)input).toLowerCase();
> }
> }
>
> class Splitter implements StringProcessor {
> @Override
> public String process(Object input) {
> return Arrays.toString(((String)input).split(" "));
> }
> }
> /* 输出:
> Using Processor Upcase
> IF SHE WEIGHS THE SAME AS A DUCK, SHE'S MADE OF WOOD
> Using Processor Downcase
> if she weighs the same as a duck, she's made of wood
> Using Processor Splitter
> [If, she, weighs, the, same, as, a, duck,, she's, made,
> of, wood]
> */
> ```
>
> [1] 这个声明是不必要的,如果删除它,编译器也不会提示错误。但它能指出方法的返回值从`Object`协变为`String`。
> [2] 字段`s`自动是`static`和`final`的,因为它是在接口内定义的。
> [3] 你甚至可以在接口中定义一个`main()`方法。接口继承接口,我只能说好家伙,还有一个默认的main()方法,真是把我秀到了```java
package example;public interface a {private static void b(){System.out.println("hello");}static void c(){System.out.println("hi");}static void main(String[] args) {b();}
}
``````java
package example;
public class one implements a{String s1 = "hello";public one (String s1){this.s1 = s1;}public static void main(String[] args){a.c();a.main(args);}
}
```忽然间我就懂怎么用这个诡异的private的方法了,把他嵌套再这个接口的其他public方法里
// interfaces/interfaceprocessor/FilterProcessor.java // {java interfaces.interfaceprocessor.FilterProcessor} package interfaces.interfaceprocessor; import interfaces.filters.*;class FilterAdapter implements Processor {Filter filter; FilterAdapter(Filter filter) {this.filter = filter; } @Override public String name() { return filter.name(); } @Override public Waveform process(Object input) {return filter.process((Waveform)input); } }public class FilterProcessor {public static void main(String[] args) {Waveform w = new Waveform();Applicator.apply(new FilterAdapter(new LowPass(1.0)), w);Applicator.apply(new FilterAdapter(new HighPass(2.0)), w);Applicator.apply(new FilterAdapter(new BandPass(3.0, 4.0)), w); } } /* 输出: Using Processor LowPass Waveform 0 Using Processor HighPass Waveform 0 Using Processor BandPass Waveform 0 */
在使用适配器的实现方式里,
FilterAdapter
构造器通过你拥有的Filter
接口,来生成一个你需要的Processor
接口的对象。你可能还会注意到FilterAdapter
类中使用了委托。协变允许我们从
process()
里产生一个Waveform
,而不仅仅是一个Object
。接口与实现的解耦允许我们将一个接口应用于多个不同的实现,因此代码更具可复用性。
挺骚的,我现在看委托越来越感觉像是在玩代理
// interfaces/Adventure.java // Multiple interfacesinterface CanFight {void fight(); }interface CanSwim {void swim(); }interface CanFly {void fly(); }class ActionCharacter {public void fight() {} }class Hero extends ActionCharacterimplements CanFight, CanSwim, CanFly {@Override public void swim() {}@Override public void fly() {} }public class Adventure {public static void t(CanFight x) { x.fight(); }public static void u(CanSwim x) { x.swim(); }public static void v(CanFly x) { x.fly(); }public static void w(ActionCharacter x) { x.fight(); }public static void main(String[] args) {Hero h = new Hero();t(h); // 当作一个CanFight类型u(h); // 当作一个CanSwim类型v(h); // 当作一个CanFly类型w(h); // 当作一个ActionCharacter类型} }
发现我搞混了一个东西,如果是下面这种形式
public void fun(){}
上面这种表示的是一个默认方法,也就是他不是abstract的,下面这种才是抽象方法的写法
public void fun();
这就带来了一个问题:应该使用接口还是抽象类?如果可以在没有任何方法定义或成员变量的情况下创建基类,那么就使用接口而非抽象类。事实上,如果你认为某个类可以作为基类的话,也就可以考虑把它设计成接口(在10.11节中会重新讨论这个主题)。\
// interfaces/HorrorShow.java // 通过继承来扩展接口interface Monster {void menace(); }interface DangerousMonster extends Monster {void destroy(); }interface Lethal {void kill(); }class DragonZilla implements DangerousMonster {@Override public void menace() {}@Override public void destroy() {} }interface Vampire extends DangerousMonster, Lethal {void drinkBlood(); }class VeryBadVampire implements Vampire {@Override public void menace() {}@Override public void destroy() {}@Override public void kill() {}@Override public void drinkBlood() {} }public class HorrorShow {static void u(Monster b) { b.menace(); }static void v(DangerousMonster d) {d.menace();d.destroy();}static void w(Lethal l) { l.kill(); }public static void main(String[] args) {DangerousMonster barney = new DragonZilla();u(barney);v(barney);Vampire vlad = new VeryBadVampire();u(vlad);v(vlad);w(vlad);} }
注意下,这里Vampire一次性继承了大量的接口,但是,这是接口特有的属性,类不能这样
// interfaces/InterfaceCollision.javainterface I1 { void f(); } interface I2 { int f(int i); } interface I3 { int f(); } class C { public int f() { return 1; } }class C2 implements I1, I2 {@Override public void f() {} @Override public int f(int i) { return 1; } // 重载 }class C3 extends C implements I2 {@Override public int f(int i) { return 1; } // 重载 }class C4 extends C implements I3 {// 完全相同,没有问题: @Override public int f() { return 1; } }// 方法只有返回类型不同 //- class C5 extends C implements I1 {} //- interface I4 extends I1, I3 {}
之所以出现问题,是因为重写、实现和重载令人不快地混合在一起。此外,重载方法不能只有返回类型不同。当最后两行取消注释时,错误消息说明了一切:
error: C5 is not abstract and does not override abstract method f() in I1 class C5 extends C implements I1 {}error: types I3 and I1 are incompatible; both define f(), but with unrelated return types interface I4 extends I1, I3 {}
这里我还是有点意外的,原来重载方法只有返回类型不同真会报错,我把这个改成了下面这个就不会报错了
// interfaces/InterfaceCollision.javainterface I1 { void f(); } interface I2 { int f(int i); } interface I3 { int f(); } class C { public int f() { return 1; } }class C2 implements I1, I2 {@Overridepublic void f() {}@Overridepublic int f(int i) { return 1; } // 重载 }class C3 extends C implements I2 {@Overridepublic int f(int i) { return 1; } // 重载 }class C4 extends C implements I3 {// 完全相同,没有问题:@Override public int f() { return 1; } }// 方法只有返回类型不同 //class C5 extends C implements I1 {} interface I4 extends I1, I2 {}
然后我这里思考了一下,参数全部一样,返回类型居然不一样确实很离谱,这就不就变成抽奖了,而且你这样实现也会很为难,让人无法理解
下面代码展示了final修饰的变量必须被初始化,否则报错
package example; public final class one implements a{public final int i ;public String s1 = "hello";public one (String s1){this.s1 = s1;}public static void main(String[] args){one a = new one("fhdjahj");a.s1="hello world";System.out.println(a.i);} }
只要将final去掉,那么输出结果就是0,否则你编译都过不去
2022.2.20
第十章 接口
接口中的任何字段都自动是
static
和final
的,因此接口是创建一组常量值的便捷工具。package Test;import example.a; import example.one; import example.*; public class test {public static void main(String[] args){//new test().function();System.out.println(Months.JANUARY);}public void function(){System.out.println("hello");} }
package example;public interface Months {intJANUARY = 1, FEBRUARY = 2, MARCH = 3,APRIL = 4, MAY = 5, JUNE = 6, JULY = 7,AUGUST = 8, SEPTEMBER = 9, OCTOBER = 10,NOVEMBER = 11, DECEMBER = 12; }
首先要想这么用得先导包,我提前用idea,怎么导Months都不对,所以把他放进了我自己创建的包里面去了,然后我实验了一下,确实已经被默认修改成了final
package Test;import example.a; import example.one; import example.*; public class test {public static void main(String[] args){//new test().function();Months.JANUARY=2;}public void function(){System.out.println("hello");} }
这些字段不是接口的一部分。这些值存储在该接口的静态存储区中。
接口可以嵌套在类和其他接口中。这呈现了几个有趣的特性:
// interfaces/nesting/NestingInterfaces.java // {java interfaces.nesting.NestingInterfaces} package interfaces.nesting;class A {interface B {void f(); } public class BImp implements B {@Override public void f() {} } private class BImp2 implements B {@Override public void f() {} } public interface C {void f(); } class CImp implements C {@Override public void f() {} } private class CImp2 implements C {@Override public void f() {} } private interface D {void f(); } private class DImp implements D {@Override public void f() {} } public class DImp2 implements D {@Override public void f() {} } public D getD() { return new DImp2(); } private D dRef; public void receiveD(D d) {dRef = d;dRef.f(); } }interface E {interface G {void f(); } // 多余的public: public interface H {void f(); } void g(); // 接口内不能用private //- private interface I {} }public class NestingInterfaces {public class BImp implements A.B {@Override public void f() {} } class CImp implements A.C {@Override public void f() {} } // private的接口只能在定义的类里实现: //- class DImp implements A.D {//- public void f() {} //- } class EImp implements E {@Override public void g() {} } class EGImp implements E.G {@Override public void f() {} } class EImp2 implements E {@Override public void g() {}class EG implements E.G {@Override public void f() {}} } public static void main(String[] args) {A a = new A();// 无法访问A.D://- A.D ad = a.getD();// 只能返回A.D://- A.DImp2 di2 = a.getD();// 无法访问该接口的方法//- a.getD().f();// 另一个A才能处理getD():A a2 = new A();a2.receiveD(a.getD()); } }
在类中嵌套接口的语法相当明显。就像非嵌套接口一样,它们可以具有
public
或包访问权限的可见性。另外这里有个不易理解之处:接口也可以是
private
的,如A.D
中所示(嵌套接口和嵌套类使用相同的限定语法)。private
的嵌套接口有什么好处?你可能会猜测,它只能作为DImp
中的私有内部类来实现,但是A.DImp2
表明它也可以作为public
类来实现。不过A.DImp2
在使用时只能被视为自身的类型,你不能提及它实现了private
的接口D
。所以实现了private
接口的话,可以在不添加任何类型信息的情况下,限定该接口中的方法定义(也就是说,不允许任何向上转型)。方法
getD()
让我们进一步陷入困境,这与private
接口有关:它是一个public
方法,但返回了一个private
接口的引用。可以对这个方法的返回值做些什么?在main()
里我们多次尝试使用该返回值,但都失败了。该返回值必须传递给一个有权使用它的对象——这里是另一个A
,它可以通过receiveD()
方法使用这个返回值。接口
E
表明,接口之间也可以嵌套。然而,关于接口的规则——特别是所有接口元素必须是public
的——在这里是严格执行的,所以嵌套在另一个接口中的接口自动为public
的,不能设为private
。NestingInterfaces
展示了实现嵌套接口的各种方式。特别要注意的是,当实现一个接口时,并不需要实现嵌套在其中的接口。此外,private
接口不能在它们的定义类之外实现。初看起来,这些特性好像仅仅是为了语法一致性而添加的,但我通常发现,一旦了解了一个特性,你就会经常发现它的有用之处。
看了很久,D那里应该说的是因为接口D是private的,也就是只在类A中可见,那么你就不能写成这个形式了
A.D a = new Dimp();
很有意思的一点,一个默认类里面居然定义了一个公共类,不过我试了下,这个公共类实际上还是出不了包
package Test;import example.a; import example.one; import example.*; public class test {public static void main(String[] args){//new test().function();System.out.println(new three.four());}public void function(){System.out.println("hello");} }
package example; public final class one implements a{public int i ;public String s1 = "hello";public one (String s1){this.s1 = s1;}public static void main(String[] args){one a = new one("fhdjahj");a.s1="hello world";System.out.println(a.i);} } class three {public class four {String s1 = "hello world";static{System.out.println("hello");}public String toString(){return s1;}} }
package example; public final class one implements a{public int i ;public String s1 = "hello";public one (String s1){this.s1 = s1;}public static void main(String[] args){one a = new one("fhdjahj");a.s1="hello world";System.out.println(a.i);} } class three {public class four {String s1 = "hello world";static{System.out.println("hello");}public String toString(){return s1;}} }
看了这么多接口的内容,现在就感觉接口就像游戏中的法师,如果要调用火元素来进行攻击,就必须动用火元素魔法的咒语,而这个咒语就是接口,而再打比方,就是你想要用特效为暴击的魔法攻击,而不同元素实现暴击的方式可以不一样,但表现出来的结果就是暴击,而这个特效为暴击的魔法攻击实际上也就是接口.
接口是通向多个实现的网关,如果想生成适合某个接口的对象,一种典型的方式是工厂方法(Factory Method)设计模式。你不是直接调用构造器,而是在工厂对象上调用创建方法,它可以产生接口实现。这样,理论上,你的代码与接口实现完全隔离,从而可以透明地将一种实现替换为另一种实现。下面是一个示例,展示了工厂方法的结构:
// interfaces/Factories.javainterface Service {void method1(); void method2(); }interface ServiceFactory {Service getService(); }class Service1 implements Service {Service1() {} // 包访问权限 @Override public void method1() {System.out.println("Service1 method1"); } @Override public void method2() {System.out.println("Service1 method2"); } }class Service1Factory implements ServiceFactory {@Override public Service getService() {return new Service1(); } }class Service2 implements Service {Service2() {} // 包访问权限 @Override public void method1() {System.out.println("Service2 method1"); } @Override public void method2() {System.out.println("Service2 method2"); } }class Service2Factory implements ServiceFactory {@Override public Service getService() {return new Service2(); } }public class Factories {public static void serviceConsumer(ServiceFactory fact) {Service s = fact.getService();s.method1();s.method2(); } public static void main(String[] args) {serviceConsumer(new Service1Factory());// 服务是完全可以互换的:serviceConsumer(new Service2Factory()); } } /* 输出: Service1 method1 Service1 method2 Service2 method1 Service2 method2 */
如果没有工厂方法,你的代码必须在某处指定创建
Service
的确切类型,以调用相应的构造器。为什么要添加这种额外的间接层?一个常见的原因是创建框架。假设你正在创建一个系统来玩游戏,例如想要在同一个棋盘上玩国际象棋和跳棋:
// interfaces/Games.java // 一个使用了工厂方法的游戏框架interface Game { boolean move(); } interface GameFactory { Game getGame(); }class Checkers implements Game {private int moves = 0; private static final int MOVES = 3; @Override public boolean move() {System.out.println("Checkers move " + moves);return ++moves != MOVES; } }class CheckersFactory implements GameFactory {@Override public Game getGame() { return new Checkers(); } }class Chess implements Game {private int moves = 0; private static final int MOVES = 4; @Override public boolean move() {System.out.println("Chess move " + moves);return ++moves != MOVES; } }class ChessFactory implements GameFactory {@Override public Game getGame() { return new Chess(); } }public class Games {public static void playGame(GameFactory factory) {Game s = factory.getGame();while(s.move()); } public static void main(String[] args) {playGame(new CheckersFactory());playGame(new ChessFactory()); } } /* 输出: Checkers move 0 Checkers move 1 Checkers move 2 Chess move 0 Chess move 1 Chess move 2 Chess move 3 */
如果
Games
类代表了一段复杂的代码,这种方式意味着你可以在不同类型的游戏中复用该代码。可以想象,更加复杂的游戏可以从这种模式中受益。在下一章中,你将看到实现工厂方法的更优雅的方式,那就是使用匿名内部类。
我看不出来为什么工厂会有这种作用,境界还是太低了
我自己只能理解到工厂将接口和实现代码完全的分离了,这里的分离指的仅仅只是Game接口和实现Game接口的类,因为这里的Game接口简单,但我用来实现的那个类不一定简单,他可能远比这个复杂得多,而工厂化给出接口类,我自己更便偏向于易读性
sealed
类的子类只能通过下面的某个修饰符来定义。final
:不允许有进一步的子类。sealed
:允许有一组密封子类。non-sealed
:一个新关键字,允许未知的子类来继承它。
// interfaces/SealedSubclasses.java // {NewFeature}从JDK 17开始sealed class Bottom permits Level1 {} sealed class Level1 extends Bottom permits Level2 {} sealed class Level2 extends Level1 permits Level3 {} final class Level3 extends Level2 {}
注意,一个
sealed
类必须至少有一个子类。这个结构还挺有意思,虽然感觉没啥软用
一个
sealed
的基类无法阻止non-sealed
的子类的使用,因此可以随时放开限制:// interfaces/NonSealed.java // {NewFeature} 从JDK 17开始sealed class Super permits Sub1, Sub2 {} final class Sub1 extends Super {} non-sealed class Sub2 extends Super {} class Any1 extends Sub2 {} class Any2 extends Sub2 {}
Sub2
允许任意数量的子类,因此它似乎放开了对可以创建的类型的控制。但是,我们还是严格限制了sealed
类Super
的直接子类。也就是说,Super
仍然只能有直接子类Sub1
和Sub2
。忽然感觉这玩意是专门为了针对向上转型吗?
package example; public class one implements a{String s1 = "hello";public one (String s1){this.s1 = s1;}@Overridepublic void cd() {;}public static void main(String[] args){a.c();a.main(args);Super first = new Any1();} } sealed class Super permits Sub1, Sub2 {} final class Sub1 extends Super {} non-sealed class Sub2 extends Super {} class Any1 extends Sub2 {} class Any2 extends Sub2 {}
不过我试了下,编译是能过的,看不懂为什么要有这种新特性
重看了一遍,这个应该是为了画图的时候好画,因为它的修饰符就那么几个,这样就可以使类的层次结构更加鲜明,不过不可否认的是,这样的可拓展性我更感觉还是不太行
// interfaces/PermittedSubclasses.java // {NewFeature}从JDK 17开始sealed class Color permits Red, Green, Blue {} final class Red extends Color {} final class Green extends Color {} final class Blue extends Color {}public class PermittedSubclasses {public static void main(String[] args) {for(var p: Color.class.getPermittedSubclasses())System.out.println(p.getSimpleName()); } } /* 输出: Red Green Blue */
我们可能很容易就会觉得接口是好的,因此总是选择接口而不是具体的类。几乎任何要创建一个类的场景,都可以创建一个接口和一个工厂来代替。
许多人受到了这种诱惑,只要有可能就创建接口和工厂。这里的逻辑似乎是,你可能会用到不同的实现,因此应该始终添加这层抽象。这是一种过早的设计优化。
任何抽象都应该由真正的需求来驱动。接口应该是在必要时用来重构的东西,而不是在任何地方都多加一个间接层级,进而带来额外的复杂性。这种额外的复杂性影响很大,如果你让某人在克服这种复杂性上花费时间,而他最终却发现你添加接口只不过是为了“以防万一”,而非出于什么令人信服的其他理由——那好吧,如果我看到这样的设计,就会开始质疑这个人做过的其他所有设计。
一个比较恰当的指导方针是“优先使用类而不是接口”。从类开始设计,如果很明显接口是必要的,那么就重构。接口是一个很好的工具,但它很容易被滥用。
第十一章 内部类
更普遍的情况是,外部类有一个方法,该方法返回一个指向内部类的引用,正如在
to()
和contents()
方法中看到的那样。// innerclasses/Parcel2.java // 返回一个指向内部类的引用public class Parcel2 {class Contents {private int i = 11;public int value() { return i; } } class Destination {private String label;Destination(String whereTo) {label = whereTo;}String readLabel() { return label; } } public Destination to(String s) {return new Destination(s); } public Contents contents() {return new Contents(); } public void ship(String dest) {Contents c = contents();Destination d = to(dest);System.out.println(d.readLabel()); } public static void main(String[] args) {Parcel2 p = new Parcel2();p.ship("Tasmania");Parcel2 q = new Parcel2();// 定义指向内部类的引用:Parcel2.Contents c = q.contents();Parcel2.Destination d = q.to("Borneo"); } } /* 输出: Tasmania */
要在外部类的非静态方法之外的任何地方创建内部类的对象,必须像在
main()
中看到的那样,将对象的类型指定为OuterClassName.InnerClassName
。这里如果不在包外的话,可以直接这么写
public class Parcel2 {class Contents {private int i = 11;public int value() { return i; }}class Destination {private String label;Destination(String whereTo) {label = whereTo;}String readLabel() { return label; }}public Destination to(String s) {return new Destination(s);}public Contents contents() {return new Contents();}public void ship(String dest) {Contents c = contents();Destination d = to(dest);System.out.println(d.readLabel());}public static void main(String[] args) {Parcel2 p = new Parcel2();p.ship("Tasmania");Parcel2 q = new Parcel2();// 定义指向内部类的引用:Contents c = q.contents();Destination d = q.to("Borneo");} }
// innerclasses/Sequence.java // 保存一个对象序列interface Selector {boolean end(); Object current(); void next(); }public class Sequence {private Object[] items; private int next = 0; public Sequence(int size) {items = new Object[size]; } public void add(Object x) {if(next < items.length)items[next++] = x; } private class SequenceSelector implements Selector {private int i = 0;@Overridepublic boolean end() { return i == items.length; }@Overridepublic Object current() { return items[i]; }@Overridepublic void next() { if(i < items.length) i++; } } public Selector selector() {return new SequenceSelector(); } public static void main(String[] args) {Sequence sequence = new Sequence(10);for(int i = 0; i < 10; i++)sequence.add(Integer.toString(i));Selector selector = sequence.selector();while(!selector.end()) {System.out.print(selector.current() + " ");selector.next();} } } /* 输出: 0 1 2 3 4 5 6 7 8 9 */
Sequence
是以类的形式包装起来的定长Object
数组。可以调用add()
向序列末尾增加一个新的Object
(如果还有空间)。要取得Sequence
中的每一个对象,可以使用名为Selector
的接口。这是迭代器(Iterator)设计模式的一个例子,我们会在第12章进一步学习。通过Selector
,可以检查是否到了Sequence
的末尾(end()
),访问当前Object
(current()
),以及移动到下一个Object
(next()
)。因为Selector
是一个接口,所以其他类可以用自己的方式实现该接口,而且其他方法可以以该接口为参数,来创建更通用的代码。人看麻了,有点工厂化方法的味道
内部类的对象在构造时,需要一个指向外围类对象的引用,如果编译器无法访问这个引用,它就会报错。不过这种情况大多不需要程序员干预。
有一点味道了,因为内部类的对象是从属于他的外部对象的
// innerclasses/DotThis.java // 访问外部类对象public class DotThis {void f() { System.out.println("DotThis.f()"); } public class Inner {public DotThis outer() {return DotThis.this;// 如果直接写“this”,引用的会是Inner的“this”} }public Inner inner() { return new Inner(); } public static void main(String[] args) {DotThis dt = new DotThis(); DotThis.Inner dti = dt.inner(); dti.outer().f();} } /* 输出: DotThis.f() */
学习如何通过内部类返回外部类
// innerclasses/DotNew.java // 使用.new语法直接创建一个内部类的对象public class DotNew {public class Inner {} public static void main(String[] args) {DotNew dn = new DotNew(); DotNew.Inner dni = dn.new Inner(); } }
要直接创建内部类的对象,你可能会以为,要遵循和前面同样的形式,使用外部类的名字
DotNew
,然而事实并非如此。我们要使用外部类的对象来创建内部类的对象,正如我们在示例代码中所看到的那样。这也解决了内部类的名字作用域问题,所以我们不用dn.new DotNew.Inner()
(确实也不能用)。看了半天,才发现我看错了,作者下面解释的那个是用了内部类创建了外部类,再用外部类对应的方法去生成内部类,而这种创建方法本身逻辑就有点不自洽
其他
- bit等于一个二进制位
- java的switch的使用https://www.w3schools.com/java/java_switch.asp
on java8学习笔记2022.2.19-2022.2.20相关推荐
- Java8学习笔记(1) -- 从函数式接口说起
转载自 Java8学习笔记(1) -- 从函数式接口说起 希望本文能够成为Java8 Lambda表达式的快速入门指南. 函数式接口 理解Functional Interface(函数式接口,以下 ...
- 《神经科学:探索脑》学习笔记(第19章 脑的节律)
<神经科学:探索脑>第二版 中文版 学习笔记(合集) https://altli.blog.csdn.net/article/details/106306300
- Java8学习笔记(七)--Collectors
本系列文章翻译自@shekhargulati的java8-the-missing-tutorial 你已经学习了Stream API能够让你以声明式的方式帮助你处理集合.我们看到collect是一个将 ...
- Java8学习笔记:计算两个时间的间隔及相隔天数、秒数等
环境 Java11 IDEA:2020.1 前言 如果大家用过Java7的时间,应该都知道,要计算startTime和endTime时间间隔是要写很多代码的: Java8开始对时间类进行过重构优化,并 ...
- java变量默为public_《Java8学习笔记》读书笔记(六)
第5章 对象封装 5.1 何谓封装 定义类并不等于做好了面向对象中封装的概念,那么到底什么才有封装的含义? 5.1.1 封装对象初始流程 假设要写个可以管理储值卡的应用程序,首先得定义储值卡会记录哪些 ...
- 《Java8学习笔记》读书笔记(7)
第7章 接口与多态 学习目标 使用接口定义 了解接口的多态操作 利用接口枚举常数 利用enum枚举常数 7.1 何谓接口 接口是在面向对象思想的发展之中逐渐发展出来的.正如前面所说过的继承的隐患,当类 ...
- 《JAVA编程思想》学习笔记:第19章(枚举)
目录 Java编程思想(一)第1~4章:概述 Java编程思想(二)第5章:初始化和清理 Java编程思想(三)第6章:访问权限 Java编程思想(四)第7章:复用类 Java编程思想(五)第8章:多 ...
- Java8学习笔记(三)--方法引入
POJO public class Emp {private int empno;private String ename;//其余方法略 } 方法的引入分为以下四种: 引入静态方法 @Functio ...
- python学习笔记:第19天 类的约束、异常、MD5和logging
目录 一.类的约束 二.异常处理: 三.MD5加密 四.日志(logging模块) 一.类的约束 真正写写项目的代码时都是多人协作的,所以有些地方需要约束程序的结构.也就是说,在分配任务之前就应该把功 ...
最新文章
- linux共享磁盘给指定ip,Linux 磁盘挂载和mount共享
- POJ NOI MATH-7828 最大公约数与最小公倍数
- mysql默认join是什么类型_MySQL:join语句类型
- python时间减法_干!一张图整理了 Python 所有内置异常
- Java JSON对象怎么遍历_Java遍历JsonObject对象
- linux 运行jupyter,在 Linux 上安装并运行 Jupyter
- C#网络编程----(二)启动、停止和观察进程
- web前端优化--图片优化
- c语言家庭财务管理算法,c语言家庭财务管理报告.doc
- HTML页面跳转及表单详解
- 一文带你入坑全链路监控,skywalking
- HTML+CSS+JS 实现炫酷效果,你知道几种呢?
- Angularjs兼容低版本IE
- ctf ordinary keyboard的加密问题
- Python -- 反编译pyc文件
- 【网络编程】Trojan源码 文件传输+远程cmd+键盘记录
- Python入门机器学习
- 今晚直播|两把吃鸡的时间,华为大牛带你玩转 OpenCV 黑魔法!
- 评测i5 1240p和i5 11320h 选哪个
- 亿级流量电商详情页系统设计与实战-虚拟机centos环境搭建
热门文章
- Java黑皮书课后题第7章:7.10(找出最小元素的下标)使用下面的方法头编写一个方法,求出一个整数数组中的最小元素下标。编写测试程序,提示用户输入10个数字,调用这个方法返回最小值的下标(多个则最小
- vat可以退税吗_【涨知识】企业对外投资可以申请出口退税吗?
- Shell基础知识(三)
- 【LeetCode】105#从前序与中序遍历序列构造二叉树
- Python第二天学习
- 图片懒加载解决方案 lazyload.js
- 验证多个Filter过滤一个资源时执行顺序
- HTML标签语义化——使用b标签,还是strong标签
- HighCharts报表 API
- RxSwift之NotificationCenter的使用和自定义