热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

探究RecyclerView的ViewHolder复用

在用recyclerview的时候,发现其onCreateViewHolder方法,既不是调用了一次,也不是调用了item个数的次数。因而想看看,RecyclerView的ViewHolde

在用recyclerview的时候,发现其onCreateViewHolder方法,既不是调用了一次,也不是调用了item个数的次数。因而想看看,RecyclerView的ViewHolder复用。

网上找到了一篇好的文章。


文章转自:http://www.jianshu.com/p/d7ec36aa8e4b

ViewHolder与复用

在复写RecyclerView.Adapter的时候,需要我们复写两个方法:

  • onCreateViewHolder
  • onBindViewHolder

这两个方法从字面上看就是创建ViewHolder和绑定ViewHolder的意思,来看一下源码中对我们实现的这两个方法的注释:

/** * Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent * an item. * 

* This new ViewHolder should be constructed with a new View that can represent the items * of the given type. You can either create a new View manually or inflate it from an XML * layout file. *

* The new ViewHolder will be used to display items of the adapter using * {@link #onBindViewHolder(ViewHolder, int, List)}. Since it will be re-used to display * different items in the data set, it is a good idea to cache references to sub views of * the View to avoid unnecessary {@link View#findViewById(int)} calls. * * @param parent The ViewGroup into which the new View will be added after it is bound to * an adapter position. * @param viewType The view type of the new View. * * @return A new ViewHolder that holds a View of the given view type. * @see #getItemViewType(int) * @see #onBindViewHolder(ViewHolder, int) */

当RecyclerView需要一个新的类型的item的ViewHolder的时候调用这个方法。

第二段描述是讲如何创建这个ViewHolder,跳过。

新的ViewHolder将会被用来通过adapter调用onBindViewHolder展示item。由于它将会被复用去展示在数据集中的不同items,所以缓存View的子view引用去避免不必要的对findViewById方法的调用是一个好主意。

看了上面的这段话,我产生了一个疑问,第一段话的意思仿佛是只有在需要新的类型的ViewHolder的时候才需要调用这个方法。如果是这样,的确可以从侧面说明他是以ViewHolder为单位来实现复用的。为了验证我的想法,我在onCreateViewHolder和onBindViewHolder方法中加入了计数的代码,看一下log:

log

从中可以看出并不是像我想的那样,只调用了一次,稍微想一下也很容易想明白,因为他是通过ViewHolder复用不假,我这里只有一种ViewType,上下滑动的时候需要的ViewHolder种类是只有一种,但是需要的ViewHolder对象数量并不止一个。所以在后面创建了5个ViewHolder之后,需要的数量够了,无论我怎么滑动,他都只需要复用以前创建的对象就行了。

在这里,感觉ViewHolder的类型和对象数量有点像Java中Class和对象的关系。Java中第一次将.class装载入JVM虚拟机的时候,会生成一个Class对象,以后所有这个类的对象都由Class生成。是不是有点像呢?

看到了这个log之后,我的第一反应是在这个ViewHolder对象的数量“够用”之后就停止调用onCreateViewHolder方法,看一下源码:

/** * This method calls {@link #onCreateViewHolder(ViewGroup, int)} to create a new * {@link ViewHolder} and initializes some private fields to be used by RecyclerView. * * @see #onCreateViewHolder(ViewGroup, int) */
        public final VH createViewHolder(ViewGroup parent, int viewType) {
            TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
            final VH holder = onCreateViewHolder(parent, viewType);
            holder.mItemViewType = viewType;
            TraceCompat.endSection();
            return holder;
        }

可以发现这里并没有限制,那么是不是在调用这个createViewHolder方法的时候做了限制呢?

View getViewForPosition(int position, boolean dryRun) {
            if (position <0 || position >= mState.getItemCount()) {
                throw new IndexOutOfBoundsException("Invalid item position " + position
                        + "(" + position + "). Item count:" + mState.getItemCount());
            }
            boolean fromScrap = false;
            ViewHolder holder = null;
            // 0) If there is a changed scrap, try to find from there
            if (mState.isPreLayout()) {
                holder = getChangedScrapViewForPosition(position);
                fromScrap = holder != null;
            }
            // 1) Find from scrap by position
            if (holder == null) {
                holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
                if (holder != null) {
                    if (!validateViewHolderForOffsetPosition(holder)) {
                        // recycle this scrap
                        if (!dryRun) {
                            // we would like to recycle this but need to make sure it is not used by
                            // animation logic etc.
                            holder.addFlags(ViewHolder.FLAG_INVALID);
                            if (holder.isScrap()) {
                                removeDetachedView(holder.itemView, false);
                                holder.unScrap();
                            } else if (holder.wasReturnedFromScrap()) {
                                holder.clearReturnedFromScrapFlag();
                            }
                            recycleViewHolderInternal(holder);
                        }
                        holder = null;
                    } else {
                        fromScrap = true;
                    }
                }
            }
            if (holder == null) {
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                if (offsetPosition <0 || offsetPosition >= mAdapter.getItemCount()) {
                    throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
                            + "position " + position + "(offset:" + offsetPosition + ")."
                            + "state:" + mState.getItemCount());
                }

                final int type = mAdapter.getItemViewType(offsetPosition);
                // 2) Find from scrap via stable ids, if exists
                if (mAdapter.hasStableIds()) {
                    holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
                    if (holder != null) {
                        // update position
                        holder.mPosition = offsetPosition;
                        fromScrap = true;
                    }
                }
                if (holder == null && mViewCacheExtension != null) {
                    // We are NOT sending the offsetPosition because LayoutManager does not
                    // know it.
                    final View view = mViewCacheExtension
                            .getViewForPositionAndType(this, position, type);
                    if (view != null) {
                        holder = getChildViewHolder(view);
                        if (holder == null) {
                            throw new IllegalArgumentException("getViewForPositionAndType returned"
                                    + " a view which does not have a ViewHolder");
                        } else if (holder.shouldIgnore()) {
                            throw new IllegalArgumentException("getViewForPositionAndType returned"
                                    + " a view that is ignored. You must call stopIgnoring before"
                                    + " returning this view.");
                        }
                    }
                }
                if (holder == null) { // fallback to recycler
                    // try recycler.
                    // Head to the shared pool.
                    if (DEBUG) {
                        Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "
                                + "pool");
                    }
                    holder = getRecycledViewPool().getRecycledView(type);
                    if (holder != null) {
                        holder.resetInternal();
                        if (FORCE_INVALIDATE_DISPLAY_LIST) {
                            invalidateDisplayListInt(holder);
                        }
                    }
                }
                if (holder == null) {
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                    if (DEBUG) {
                        Log.d(TAG, "getViewForPosition created new ViewHolder");
                    }
                }
            }

            // This is very ugly but the only place we can grab this information
            // before the View is rebound and returned to the LayoutManager for post layout ops.
            // We don't need this in pre-layout since the VH is not updated by the LM.
            if (fromScrap && !mState.isPreLayout() && holder
                    .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
                holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                if (mState.mRunSimpleAnimations) {
                    int changeFlags = ItemAnimator
                            .buildAdapterChangeFlagsForAnimations(holder);
                    changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
                    final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,
                            holder, changeFlags, holder.getUnmodifiedPayloads());
                    recordAnimationInfoIfBouncedHiddenView(holder, info);
                }
            }

            boolean bound = false;
            if (mState.isPreLayout() && holder.isBound()) {
                // do not update unless we absolutely have to.
                holder.mPreLayoutPosition = position;
            } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
                if (DEBUG && holder.isRemoved()) {
                    throw new IllegalStateException("Removed holder should be bound and it should"
                            + " come here only in pre-layout. Holder: " + holder);
                }
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                holder.mOwnerRecyclerView = RecyclerView.this;
                mAdapter.bindViewHolder(holder, offsetPosition);
                attachAccessibilityDelegate(holder.itemView);
                bound = true;
                if (mState.isPreLayout()) {
                    holder.mPreLayoutPosition = position;
                }
            }

            final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
            final LayoutParams rvLayoutParams;
            if (lp == null) {
                rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
                holder.itemView.setLayoutParams(rvLayoutParams);
            } else if (!checkLayoutParams(lp)) {
                rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
                holder.itemView.setLayoutParams(rvLayoutParams);
            } else {
                rvLayoutParams = (LayoutParams) lp;
            }
            rvLayoutParams.mViewHolder = holder;
            rvLayoutParams.mPendingInvalidate = fromScrap && bound;
            return holder.itemView;
        }

可以看出的确是有条件的。当然,在此不具体分析,不然可能会深入细节无法自拔。

Recycler && RecycledViewPool

说实话,上面分析完,我也有点没方向,因为毕竟整个RecyclerView一万多行代码在那,不知道看哪了,不过好在网上有篇文曾干过和我差不多的事 RecyclerView源码分析 ,前人指了条路,跟着看一下源码好了。

Recycler:

/** * A Recycler is responsible for managing scrapped or detached item views for reuse. * * 

A "scrapped" view is a view that is still attached to its parent RecyclerView but * that has been marked for removal or reuse.

* *

Typical use of a Recycler by a {@link LayoutManager} will be to obtain views for * an adapter's data set representing the data at a given position or item ID. * If the view to be reused is considered "dirty" the adapter will be asked to rebind it. * If not, the view can be quickly reused by the LayoutManager with no further work. * Clean views that have not {@link android.view.View#isLayoutRequested() requested layout} * may be repositioned by a LayoutManager without remeasurement.

*/

Recycler负责管理废弃(scrapped)或者分离(detach)的item。

scrapped指的是仍然在RecyclerView上但是已经被标记了移除或者复用。

一个对Recycler的经典的使用时LayoutManager,它通过Recycler为adapter的数据集的特定位置获取一个view。如果这个view将被复用但被认为是“dirty”的,那么这个adapter将调用方法重新绑定它。如果不是,这个view可以迅速的被LayoutManager复用而不用进一步的处理。Clean view无需调用request layout,不需要重新测量就能复用。

RecycledViewPool:

/** * RecycledViewPool lets you share Views between multiple RecyclerViews. * 

* If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool * and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}. *

* RecyclerView automatically creates a pool for itself if you don't provide one. * */ public static class RecycledViewPool { private SparseArray> mScrap = new SparseArray>(); private SparseIntArray mMaxScrap = new SparseIntArray(); private int mAttachCount = 0; private static final int DEFAULT_MAX_SCRAP = 5; public void clear() { mScrap.clear(); } public void setMaxRecycledViews(int viewType, int max) { mMaxScrap.put(viewType, max); final ArrayList scrapHeap = mScrap.get(viewType); if (scrapHeap != null) { while (scrapHeap.size() > max) { scrapHeap.remove(scrapHeap.size() - 1); } } } public ViewHolder getRecycledView(int viewType) { final ArrayList scrapHeap = mScrap.get(viewType); if (scrapHeap != null && !scrapHeap.isEmpty()) { final int index = scrapHeap.size() - 1; final ViewHolder scrap = scrapHeap.get(index); scrapHeap.remove(index); return scrap; } return null; } int size() { int count = 0; for (int i = 0; i viewHolders = mScrap.valueAt(i); if (viewHolders != null) { count += viewHolders.size(); } } return count; } public void putRecycledView(ViewHolder scrap) { final int viewType = scrap.getItemViewType(); final ArrayList scrapHeap = getScrapHeapForType(viewType); if (mMaxScrap.get(viewType) <= scrapHeap.size()) { return; } if (DEBUG && scrapHeap.contains(scrap)) { throw new IllegalArgumentException("this scrap item already exists"); } scrap.resetInternal(); scrapHeap.add(scrap); } void attach(Adapter adapter) { mAttachCount++; } void detach() { mAttachCount--; } /** * Detaches the old adapter and attaches the new one. *

* RecycledViewPool will clear its cache if it has only one adapter attached and the new * adapter uses a different ViewHolder than the oldAdapter. * * @param oldAdapter The previous adapter instance. Will be detached. * @param newAdapter The new adapter instance. Will be attached. * @param compatibleWithPrevious True if both oldAdapter and newAdapter are using the same * ViewHolder and view types. */ void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter, boolean compatibleWithPrevious) { if (oldAdapter != null) { detach(); } if (!compatibleWithPrevious && mAttachCount == 0) { clear(); } if (newAdapter != null) { attach(newAdapter); } } private ArrayList getScrapHeapForType(int viewType) { ArrayList scrap = mScrap.get(viewType); if (scrap == null) { scrap = new ArrayList<>(); mScrap.put(viewType, scrap); if (mMaxScrap.indexOfKey(viewType) <0) { mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP); } } return scrap; } }

老规矩,先看注释:RecycledViewPool让你在多个RecyclerView之间共享View。如果你想要在RecyclerView间循环利用view,创建一个RecyclerViewPool的实例然后调用setRecyclerViewPool方法。

如果你不提供一个那么RecyclerView将为他自己自动的创建一个RecycledViewPool。

接下来看下里面的代码,内部有一个SparseArray一个SparseIntArray,看到这终于感觉快看到点子上了,毕竟看起来就像是两个放东西的容器,应该是离真相不远了。先看下SparseArray是个啥,点进去看注释第一句就是 SparseArrays map integers to Objects ,这是一个integer和对象的映射,他内部也是有两个数组一个是integer作为键的int[] mKeys的int类型的数组,另外一个是Object[] mValues的对象数组。而结合他在RecycledViewPool中的定义 SparseArray> mScrap;这种定义,表明是一个integer映射ViewHolder的集合。这个该怎么理解呢?在我们的实际使用中,很可能会有非常多种类的viewType,那么这个时候同一类的ViewHolder就保存在同一个ArrayList中,而在RecyclerView内部ViewType都是通过int类型的数字来代表的,正好符合。由此可以大致可以确定这个mScrap就是保存ViewHolder的关键了。

而SparseIntArray则是Integer映射Integer,在这可以结合setMaxRecycledViews方法中的第一行代码 mMaxScrap.put(viewType,max),可以看出这是表明了一种ViewType对应的可保存对象集合的最大尺寸。

大致了解了下RecycledViewPool,然后回头去看一下之前被我跳过的getViewForPosition:

View getViewForPosition(int position, boolean dryRun) {
            if (position <0 || position >= mState.getItemCount()) {
                throw new IndexOutOfBoundsException("Invalid item position " + position
                        + "(" + position + "). Item count:" + mState.getItemCount());
            }
            boolean fromScrap = false;
            ViewHolder holder = null;
            // 0) If there is a changed scrap, try to find from there
            if (mState.isPreLayout()) {
                holder = getChangedScrapViewForPosition(position);
                fromScrap = holder != null;
            }
            // 1) Find from scrap by position
            if (holder == null) {
                holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
                if (holder != null) {
                    if (!validateViewHolderForOffsetPosition(holder)) {
                        // recycle this scrap
                        if (!dryRun) {
                            // we would like to recycle this but need to make sure it is not used by
                            // animation logic etc.
                            holder.addFlags(ViewHolder.FLAG_INVALID);
                            if (holder.isScrap()) {
                                removeDetachedView(holder.itemView, false);
                                holder.unScrap();
                            } else if (holder.wasReturnedFromScrap()) {
                                holder.clearReturnedFromScrapFlag();
                            }
                            recycleViewHolderInternal(holder);
                        }
                        holder = null;
                    } else {
                        fromScrap = true;
                    }
                }
            }
            if (holder == null) {
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                if (offsetPosition <0 || offsetPosition >= mAdapter.getItemCount()) {
                    throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
                            + "position " + position + "(offset:" + offsetPosition + ")."
                            + "state:" + mState.getItemCount());
                }

                final int type = mAdapter.getItemViewType(offsetPosition);
                // 2) Find from scrap via stable ids, if exists
                if (mAdapter.hasStableIds()) {
                    holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
                    if (holder != null) {
                        // update position
                        holder.mPosition = offsetPosition;
                        fromScrap = true;
                    }
                }
                if (holder == null && mViewCacheExtension != null) {
                    // We are NOT sending the offsetPosition because LayoutManager does not
                    // know it.
                    final View view = mViewCacheExtension
                            .getViewForPositionAndType(this, position, type);
                    if (view != null) {
                        holder = getChildViewHolder(view);
                        if (holder == null) {
                            throw new IllegalArgumentException("getViewForPositionAndType returned"
                                    + " a view which does not have a ViewHolder");
                        } else if (holder.shouldIgnore()) {
                            throw new IllegalArgumentException("getViewForPositionAndType returned"
                                    + " a view that is ignored. You must call stopIgnoring before"
                                    + " returning this view.");
                        }
                    }
                }
                if (holder == null) { // fallback to recycler
                    // try recycler.
                    // Head to the shared pool.
                    if (DEBUG) {
                        Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "
                                + "pool");
                    }
                    holder = getRecycledViewPool().getRecycledView(type);
                    if (holder != null) {
                        holder.resetInternal();
                        if (FORCE_INVALIDATE_DISPLAY_LIST) {
                            invalidateDisplayListInt(holder);
                        }
                    }
                }
                if (holder == null) {
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                    if (DEBUG) {
                        Log.d(TAG, "getViewForPosition created new ViewHolder");
                    }
                }
            }

            // This is very ugly but the only place we can grab this information
            // before the View is rebound and returned to the LayoutManager for post layout ops.
            // We don't need this in pre-layout since the VH is not updated by the LM.
            if (fromScrap && !mState.isPreLayout() && holder
                    .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
                holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                if (mState.mRunSimpleAnimations) {
                    int changeFlags = ItemAnimator
                            .buildAdapterChangeFlagsForAnimations(holder);
                    changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
                    final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,
                            holder, changeFlags, holder.getUnmodifiedPayloads());
                    recordAnimationInfoIfBouncedHiddenView(holder, info);
                }
            }

            boolean bound = false;
            if (mState.isPreLayout() && holder.isBound()) {
                // do not update unless we absolutely have to.
                holder.mPreLayoutPosition = position;
            } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
                if (DEBUG && holder.isRemoved()) {
                    throw new IllegalStateException("Removed holder should be bound and it should"
                            + " come here only in pre-layout. Holder: " + holder);
                }
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                holder.mOwnerRecyclerView = RecyclerView.this;
                mAdapter.bindViewHolder(holder, offsetPosition);
                attachAccessibilityDelegate(holder.itemView);
                bound = true;
                if (mState.isPreLayout()) {
                    holder.mPreLayoutPosition = position;
                }
            }

            final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
            final LayoutParams rvLayoutParams;
            if (lp == null) {
                rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
                holder.itemView.setLayoutParams(rvLayoutParams);
            } else if (!checkLayoutParams(lp)) {
                rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
                holder.itemView.setLayoutParams(rvLayoutParams);
            } else {
                rvLayoutParams = (LayoutParams) lp;
            }
            rvLayoutParams.mViewHolder = holder;
            rvLayoutParams.mPendingInvalidate = fromScrap && bound;
            return holder.itemView;
        }

在具体分析这个方法之前,先给出这个类内部几个参数的大致意思:

private ArrayList mAttachedScrap

private ArrayList mChangedScrap 与RecyclerView分离的ViewHolder列表。

private ArrayList mCachedViews ViewHolder缓存列表。

private ViewCacheExtension mViewCacheExtension 开发者控制的ViewHolder缓存

private RecycledViewPool mRecyclerPool 提供复用ViewHolder池。

可以看到源码中已经给了我们步骤提示:

  • If there is a changed scrap, try to find from there
    从mChangedScrap中寻找ViewHolder
  • 1) Find from scrap by position
    如果上一步未找到ViewHolder,则从mAttachedScrap中通过position找
  • 2) Find from scrap via stable ids, if exists
    如果上一步未找到且存在stable id,则通过id在mAttachedScrap中找ViewHolder
  • 如果上一步未找到且mViewCacheExtension不为空,则在mViewCacheExtension中找ViewHolder
  • 如果上一步未找到则通过RecycledCiewPool寻找ViewHolder
  • 如果上一步未找到则通过Adapter的createViewHolder创建一个新的ViewHolder

如此一来经历了以上的步骤,一个ViewHolder便会先从缓存中读取,如果都无法匹配到,则会新创建一个。如此便实现了ViewHolder的复用。

 

 




推荐阅读
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • springmvc学习笔记(十):控制器业务方法中通过注解实现封装Javabean接收表单提交的数据
    本文介绍了在springmvc学习笔记系列的第十篇中,控制器的业务方法中如何通过注解实现封装Javabean来接收表单提交的数据。同时还讨论了当有多个注册表单且字段完全相同时,如何将其交给同一个控制器处理。 ... [详细]
  • 闭包一直是Java社区中争论不断的话题,很多语言都支持闭包这个语言特性,闭包定义了一个依赖于外部环境的自由变量的函数,这个函数能够访问外部环境的变量。本文以JavaScript的一个闭包为例,介绍了闭包的定义和特性。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • 本文介绍了在Android开发中使用软引用和弱引用的应用。如果一个对象只具有软引用,那么只有在内存不够的情况下才会被回收,可以用来实现内存敏感的高速缓存;而如果一个对象只具有弱引用,不管内存是否足够,都会被垃圾回收器回收。软引用和弱引用还可以与引用队列联合使用,当被引用的对象被回收时,会将引用加入到关联的引用队列中。软引用和弱引用的根本区别在于生命周期的长短,弱引用的对象可能随时被回收,而软引用的对象只有在内存不够时才会被回收。 ... [详细]
  • 本文详细介绍了Android中的坐标系以及与View相关的方法。首先介绍了Android坐标系和视图坐标系的概念,并通过图示进行了解释。接着提到了View的大小可以超过手机屏幕,并且只有在手机屏幕内才能看到。最后,作者表示将在后续文章中继续探讨与View相关的内容。 ... [详细]
author-avatar
兔子狗530_772
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有