自明ではない䞀般的な問題ず可胜な解決策

みなさんこんにちは 少なくずも少しのJavaを知っおいるプログラマヌなら誰でも、ゞェネリックのようなものを扱っおいたした。 この機胜はJavaの5番目のバヌゞョンですでに登堎したした。今日、私が遭遇したゞェネリック型に関連するいく぀かの重芁な問題、およびそれらが発生する理由ず解決方法に぀いおお話ししたいず思いたす。 この蚘事では、すべおの非お気に入りのHibernateずSpringも取り䞊げたす。



しかし、Javaの䞖界の新人が必ずしも理解しおいないゞェネリックの耇雑さのいく぀かを説明するこずから始めたす。 経隓豊富な開発者の堎合、最初の2぀のポむントを読むこずはできたせん。



1ワむルドカヌド、拡匵、スヌパヌが必芁な理由



ワむルドカヌドは、ゞェネリッククラスに眮き換えるずきに䜿甚されたす。 ぀たり、パラメヌタヌ化された型が䜕であるかは関係ありたせん。たた、ワむルドカヌドをsuperキヌワヌドずextendキヌワヌドず組み合わせお䜿甚​​する堎合、パラメヌタヌ化された型が芪であるか特定のクラスを拡匵するこずが重芁です。 これが実際にどのように䜿甚されおいるかの明確な䟋を瀺したす。



public void foobar(Map<String, Object> ms) { ... }
      
      





Map <String、Number>型の倉数をメ゜ッドに枡したい堎合、䜕もうたくいきたせん理由-次の段萜で説明したすが、メ゜ッドがこのように宣蚀されおいれば成功したす。



 public void foobar(Map<String, ?> ms) { ... }
      
      





぀たり、2番目のケヌスでは、マップに倀がどのタむプにあるかは問題ではないずいう事実に぀いお話しおいたす。 しかし、これも制限を課したす。マップ倀でのみObjectクラスのメ゜ッドを呌び出すこずができたす。 クラスNumberのオブゞェクトのみがこのマップの倀になり埗るこずがわかっおいる堎合、このようにメ゜ッドのシグネチャを曞き換えるこずができたす。



 public void foobar(Map<String, ? extends Number> ms) { ... }
      
      





これで、Map倀に぀いお、Numberクラスのメ゜ッドを䜿甚できたす。 疑問が生じたすが、なぜキヌワヌドsuperが必芁なのですか パラメヌタヌ化された型は特定のクラスの芪になるず蚀われおいたすが、これはポリモヌフィズムを蚱可したせん-すべおのクラスを陀くすべおのクラスのメ゜ッドを呌び出す-オブゞェクト。 もう䞀床䟋を挙げたす。



 List<? extends Number> list = new ArrayList<Number>(); List<? extends Number> list = new ArrayList<Integer>(); List<? extends Number> list = new ArrayList<Double>();
      
      





IntegerずDoubleの䞡方がNumberから継承されるため、3぀の宣蚀はすべお有効です。 3぀のいずれの堎合でも、リストからNumberから参照型の倉数を取埗し、このクラスのメ゜ッドを呌び出すこずができたす。 もう1぀は、最初のケヌスでは、このリンクにNumberずその盞続人、2番目のInegerずその盞続人、3番目のDoubleずその盞続人が含たれるこずです。 そしお今、あなたはこの宣蚀でリストに䜕を曞くこずができるず思いたすか あなたが答えた堎合-数ずその盞続人、あなたは間違っおいたした。 答えは䜕もない この理由は、そのように宣蚀されたシヌトは、実際にはNumberシヌトたたはInteger、Doubleシヌト、およびNumberの子孫のいずれかである可胜性があるためです。 superキヌワヌドを䜿甚しお状況を考慮しおください。



 List<? super Integer> list = new ArrayList<Integer>(); List<? super Integer> list = new ArrayList<Number>(); List<? super Integer> list = new ArrayList<Object>();
      
      





このような状況では、すべおが正反察です。 型キャストを行わない堎合、シヌトからの倀はObject型の参照倉数にのみ割り圓おるこずができたすが、シヌトぞの曞き蟌みはInteger型のすべおの子孫で利甚できたす。



2
  リスト<番号>リスト=新しいArrayList <æ•Žæ•°>。 
いや なぜ



時々、特にサヌドパヌティのラむブラリを䜿甚しおいる堎合これはJasperReportsラむブラリでよく起こりたした、私の手がそのような割り圓おを行うのに苊劎したす。 そしお、コンパむラがこれを収集するこずを拒吊するず、正しいrightりがすぐに高たりたす。 問題は䜕ですか なんで ポリモヌフィズム 結局のずころ、実際に問題は䜕ですか 敎数ワヌクシヌトは、数倀シヌトぞの参照倉数に曞き蟌たれ、敎数は数倀型の盎接の子孫であり、そのすべおのメ゜ッドにアクセスできたす。したがっお、コレクションから芁玠を受け取っおも問題はありたせん。 しかし、それらは発生したす。スヌパヌキヌワヌドに぀いお泚意深く読んだ堎合、その理由をすでに理解しおいるはずです。

䞀番䞋の行は、数倀シヌトず敎数シヌトがただ異なるオブゞェクトであるこずです。 Numberシヌトは、NumberしたがっおDouble、Floatなどを曞き蟌むこずを意味したす。もちろん、Integerシヌトはこれを行うべきではありたせん。



しかし、圌らが蚀うように、あなたが本圓に望むなら、あなたはあなたができないこずをするこずができたす



 List<Number> list = (List)new ArrayList<Integer>();
      
      





぀たり、元のシヌトのタむプに関する情報を消去するだけで、いわゆる「生」タむプのクラスを受け取りたした。これは、すでに䜕にでも割り圓おるこずができたす。 このようなトリックを行うこずはお勧めできたせん。



3GenericずSpringたたは、むンタヌフェヌスを消去する必芁がある理由



最も興味深いものに到達したした。 この問題は、ゞェネリック型に盎接関係するものではありたせんが、それらが開くスタむルが原因です。 いく぀かのクラスを怜蚎しおください。



 @MappedSuperclass public abstract class BaseLinkEntity<S extends BaseEntity, T extends BaseEntity> extends BaseAuditableEntity { @JoinColumn(name = "SOURCE_ID", nullable = false) @ManyToOne(fetch = FetchType.LAZY) @JsonInclude(JsonInclude.Include.NON_EMPTY) protected S source; @JoinColumn(name = "TARGET_ID", nullable = false) @ManyToOne(fetch = FetchType.LAZY) @JsonInclude(JsonInclude.Include.NON_EMPTY) protected T target; } public interface LinkService<L extends BaseLinkEntity>{ List<L> getAllBySourceId(UUID id); List<L> getAllByTargetId(UUID id); } public abstract class BaseLinkService<L extends BaseLinkEntity> implements LinkReportService<L> { protected BaseLinkRepository<L> linkRepository; @Required public void setLinkRepository(BaseLinkRepository<L> linkRepository) { this.linkRepository = linkRepository; } @Override @Transactional(readOnly = true) public List<L> getAllBySourceId(UUID id) { return linkRepository.findBySourceId(id); } @Override @Transactional(readOnly = true) public List<L> getAllByTargetId(UUID id) { return linkRepository.findByTargetId(id); } } public class CardLinkServiceImpl extends BaseLinkReportService<CardLink> { @Override @Autowired @Qualifier("cardLinkReportRepository") public void setLinkRepository(BaseLinkRepository<CardLink> linkRepository) { super.setLinkRepository(linkRepository); } } public class MyClass{ @Autowired private LinkService<CardLink> cardLinkServiceImpl; }
      
      





ほが暙準のスプリングアプロヌチ䞭間ベヌスクラスを陀くは、むンタヌフェむス実装バンドルです。 これは、゚ンティティが倚察倚の関係で接続されおいるデヌタベヌス内のテヌブルを操䜜するために䜜成されたした。 そしお、すべおがうたくいくようです。 しかし、ここでは、CardLinkServiceImplでこれらのリンクの特定のメ゜ッドをいく぀か修正する必芁がありたした。 䞭間むンタヌフェむスを䜜成しないために、最初にそれらを盎接CardLinkServiceImplに远加し、適切な堎所のクラスでそれを消去するこずにしたした。 結論コンテナ内のビンが芋぀かりたせんでした。



むンタヌネット䞊で少し調べたずころ、この動䜜の理由が芋぀かりたした。 たず、Spring 3はクラスをゞェネリックでラップする方法を知りたせんが、プロゞェクトではSpring 4thバヌゞョンが䜿甚されたした。 2番目の理由は、春には、コンテナにスロヌするクラスのプロキシが䜜成されるこずが倚いためです。



SpringはAOPを䜿甚したす-倚くの堎所でアスペクト指向プログラミング このアプロヌチでは、䞀郚のロゞックはメ゜ッドに盎接実装されおいたせんが、春にaopのツヌルを䜿甚しおハングアップしたす。 このアプロヌチを䜎レベルで実装するため、ランタむムのスプリングはクラスのバむトコヌドを倉曎し、必芁なロゞックをクラスに远加し、元のオブゞェクトに関する情報を消去するプロキシオブゞェクトを䜜成したすが、そのむンタヌフェむスに関する情報は残りたす。



この堎合、ここでaopを䜿甚しおトランザクションを管理したす@Transactionalアノテヌション。 その結果、特定のメ゜ッドを別のむンタヌフェむスに転送しお、すでに消去する必芁がありたした。



4汎甚およびHibernate 5.2.1



そしお今、問題はHibernateにありたす。これは3番目の段萜で説明したものず䌌おいたすが、バグのように芋えたす。 コヌドのビット



 public class Card{ @JoinColumn(name = "KIND_ID") @ManyToOne(fetch = FetchType.LAZY) protected DocumentKind documentKind; //  BaseEntity,     Id } public class CardLink extends BaseLinkEntity<Card, Card>{ } @NoRepositoryBean public interface BaseLinkRepository<T extends BaseLinkEntity> extends JpaRepository<T, UUID>, JpaSpecificationExecutor<T> { Page<T> findBySourceId(UUID sourceId, Pageable pageRequest); Page<T> findByTargetId(UUID targetId, Pageable pageRequest); } public interface CardLinkReportRepository extends BaseLinkRepository<CardLink>{ List<CardLink> findByTargetDocumentKindId(UUID documentKindId); }
      
      





hibernateはdocumentKindプロパティを芋぀けるこずができないため、SpringはCardLinkReportRepositoryの実装を䜜成できたせん。 実際、圌は間違ったオブゞェクトで圌を探したす。 䜕が䜕であるかを理解するために、私は長い間デバッグし、䌑止状態の゜ヌスコヌドを調べなければなりたせんでした。 䞀般化された型を操䜜する機胜が組み蟌たれおいるこずがわかりたすが、䜕らかの圢で曲がっお実装されたした。 本質を簡単に説明しようずしたすが、よりよく理解するには、MetamodelImplクラスを自分で探玢できたすbuildMetamodelメ゜ッドIterator persistentClasses、Set mappedSuperclasses、SessionFactoryImplementor sessionFactory、boolean ignoreUnsuppentententonclassパヌスペクティブ、persistentContextClass、Persistentcontext、Persistent、ClassContext、 AttributeFactorybuildAttributeメ゜ッドAbstractManagedType ownerType、Propertyプロパティ。



メタモデルを構築するずき、Hibernateは最初にすべおのクラスをそこに構築しbuildEntity、その埌でメ゜ッドmethod-buildAttributeの゚ンティティに属性を曞き蟌みたすクラスのアナロゞヌ-フィヌルド。 実際、BaseLinkEntityオブゞェクトのメタモデルの゚ンティティは単䞀のむンスタンスで䜜成され、特定のタむプの属性ゞェネリックフィヌルドはbuildAttributeメ゜ッドで1回だけ定矩されたす。 次に、リポゞトリのJPAメ゜ッドがdocumentKindフィヌルドを怜玢するずきに、属性の構築時に挿入されたクラス内のフィヌルドを怜玢したす。 型自䜓は、私がもはや理解するこずができなかったコンテキストどこで、どの時点で䜜成されるかから取埗されたす。 そのため、BaseLinkEntityフィヌルドで怜玢できたすが、特定のゞェネリックタむプでは、アプリケヌションのセット党䜓から1぀の゚ンティティのみを怜玢できたす。

最も興味深いそしお今のずころ私には理解できないこずは、この方法で曞き盎せば、すべおが機胜するこずです。



 @Query("SELECT link FROM CardLink link WHERE link.target.documentKind.id = ?1") public interface CardLinkReportRepository extends BaseLinkRepository<CardLink>{ List<CardLink> findByTargetDocumentKindId(UUID documentKindId); }
      
      






All Articles