MVVM 实战之计算器

android
DataBinding
MVVM
calculator

前些日子,一直在学习基于 RxAndroid + Retrofit + DataBinding 技术组合的 MVVM 解决方案。初识这些知识,深深被它们的巧妙构思和方便快捷所吸引,心中颇为激动。但是,“纸上得来终觉浅,绝知此事要躬行”,学习完以后心里还是没有谱,于是,决定自己动手做一个基于这些技术和框架的小应用。

既然是对新技术学习和掌握的练习,因此,摊子不宜铺的太大。经过思量,最终决定使用 DataBinding 技术构建一个小的 MVVM 应用。MVVM 就是 Model-View-ViewModel 的缩写,与 MVC 模式相比,把其中的 Control 更换为 ViewModel 了。MVVM 的特点:ModelView 之间完全没有直接的联系,但是,通过 ViewModelModel 的变化可以反映在 View 上,对 View 操作呢,又可以影响到 Model

平时在编写 Android 应用时,大家都在深受 findViewById 的折磨。DataBinding 还有个好处,就是完全不需要使用 findViewById 来获取控件(当然,需要在布局文件中给控件设置 id 属性)。有了 DataBinding 的支持,在数据变化后,也不需使用代码来改变控件的显示了。这样,我们的代码就清爽多了。

Model


MVVM 中,Model 的变化可以直接反映到 View 上,而不需要通过代码进行设置。这样,就不能用普通的 Java 类型的变量了。Android 专门为这种变量定义了新的变量类型:ObservableXXX

注意:ObservableXXX 是在 android.databinding 包下

变量定义如下:

/** 被操作数 */
public ObservableField<String> firstNum = new ObservableField<>("0");
/** 上一次结果 */
public ObservableField<String> secondNum = new ObservableField<>("");
/** 当前结果 */
public ObservableField<String> resNum = new ObservableField<>("");

  

变量的定义位置应该在 ViewModel 中,后方会有完整代码。

View

布局文件


DataBinding 的布局特点是把正常布局包裹在 layout 节点中,layout 布局中的第一个子直接子元素必须是 data 节点。因为,计算器布局的特点非常符合网格布局的特点,因此,我们选择 GridLayout 控件作为 layout 布局中的第二个直接子元素。布局内容如下:

 <?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="cal"
type="com.ch.wchhuangya.android.pandora.vm.CalculatorVM"/>
</data> <LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"> <LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="10dp"
android:layout_weight="2"
android:gravity="bottom"
android:orientation="vertical"
> <TextView
android:id="@+id/cal_top_num"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:maxLines="1"
android:paddingRight="10dp"
android:text="@{cal.secondNum}"
android:textColor="#555"
android:textSize="35sp"
tools:text="16"
/> <TextView
android:id="@+id/cal_bottom_num"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:maxLines="1"
android:paddingRight="10dp"
android:text="@{cal.firstNum}"
android:textColor="#222"
android:textSize="45sp"
tools:text="+ 3234234"
/> <TextView
android:id="@+id/cal_res"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:maxLines="1"
android:paddingRight="10dp"
android:text="@{cal.resNum}"
android:textColor="#888"
android:textSize="30sp"
tools:text="= 3234250"
/> </LinearLayout> <android.support.v7.widget.GridLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="3"
app:columnCount="4"
app:orientation="horizontal"
app:rowCount="5"
> <Button
android:id="@+id/cal_clear"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
app:layout_rowWeight="1"
android:text="clear"
android:onClick="@{() -> cal.clear()}"
/> <Button
android:id="@+id/cal_del"
android:layout_marginRight="5dp"
app:layout_rowWeight="1"
android:text="del"
android:onClick="@{() -> cal.del()}"
/> <Button
android:id="@+id/cal_divide"
android:layout_marginRight="5dp"
app:layout_rowWeight="1"
android:text="÷"
android:onClick="@{cal::operatorClick}"
/> <Button
android:id="@+id/cal_multiply"
app:layout_rowWeight="1"
android:text="×"
android:onClick="@{cal::operatorClick}"
/> <Button
android:id="@+id/cal_7"
android:layout_marginLeft="5dp"
app:layout_rowWeight="1"
android:text="7"
android:onClick="@{cal::numClick}"
/> <Button
android:id="@+id/cal_8"
app:layout_rowWeight="1"
android:text="8"
android:onClick="@{cal::numClick}"
/> <Button
android:id="@+id/cal_9"
app:layout_rowWeight="1"
android:text="9"
android:onClick="@{cal::numClick}"
/> <Button
android:id="@+id/cal_minus"
app:layout_rowWeight="1"
android:text="-"
android:onClick="@{cal::operatorClick}"
/> <Button
android:id="@+id/cal_4"
android:layout_marginLeft="5dp"
app:layout_rowWeight="1"
android:text="4"
android:onClick="@{cal::numClick}"
/> <Button
android:id="@+id/cal_5"
app:layout_rowWeight="1"
android:text="5"
android:onClick="@{cal::numClick}"
/> <Button
android:id="@+id/cal_6"
app:layout_rowWeight="1"
android:text="6"
android:onClick="@{cal::numClick}"
/> <Button
android:id="@+id/cal_add"
app:layout_rowWeight="1"
android:text="+"
android:onClick="@{cal::operatorClick}"
/> <Button
android:id="@+id/cal_1"
android:layout_marginLeft="5dp"
app:layout_rowWeight="1"
android:text="1"
android:onClick="@{cal::numClick}"
/> <Button
android:id="@+id/cal_2"
app:layout_rowWeight="1"
android:text="2"
android:onClick="@{cal::numClick}"
/> <Button
android:id="@+id/cal_3"
app:layout_rowWeight="1"
android:text="3"
android:onClick="@{cal::numClick}"
/> <Button
android:id="@+id/cal_equals"
app:layout_rowSpan="2"
app:layout_rowWeight="1"
app:layout_gravity="fill_vertical"
android:text="="
android:onClick="@{() -> cal.equalsClick()}"
/> <Button
android:id="@+id/cal_12"
android:layout_marginLeft="5dp"
app:layout_rowWeight="1"
android:text="%"
android:onClick="@{() -> cal.percentClick()}"
/> <Button
android:id="@+id/cal_zero"
app:layout_rowWeight="1"
android:text="0"
android:onClick="@{cal::numClick}"
/> <Button
android:id="@+id/cal_dot"
app:layout_rowWeight="1"
android:text="."
android:onClick="@{() -> cal.dotClick()}"
/> </android.support.v7.widget.GridLayout> </LinearLayout>
</layout>

布局文件

布局内容比较简单,下面,只说一些重点:

  1. DataBinding 的布局中,如果需要使用 tools 标签,它的声明必须放在 layout 节点上。否则,布局预览中没有效果

  2. data 节点中申明的是布局文件各元素需要使用到的对象,也可以为对象定义别名

  3. 布局文件中的控件如果要使用 data 中定义的对象,值的类似于:@{View.VISIBLE} 。控件的属性值中,不仅可以使用对象,还能使用对象的方法

Fragment


MVVM 中,ActivityFragment 的作用只是用于控件的初始化,包括控件属性(如颜色)等的设置。因此,它的代码灰常简单,具体如下:

 package com.ch.wchhuangya.android.pandora.view.activity.calculator;

 import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup; import com.ch.wchhuangya.android.pandora.R;
import com.ch.wchhuangya.android.pandora.databinding.CalculatorBinding;
import com.ch.wchhuangya.android.pandora.vm.CalculatorVM; /**
* Created by wchya on 2016-12-07 16:17
*/ public class CalculatorFragment extends Fragment { private CalculatorBinding mBinding;
private CalculatorVM mCalVM; @Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mBinding = DataBindingUtil.inflate(inflater, R.layout.calculator, container, false);
mCalVM = new CalculatorVM(getContext());
mBinding.setCal(mCalVM);
return mBinding.getRoot();
} @Override
public void onDestroy() {
super.onDestroy();
mCalVM.reset();
}
}

Fragment

该类中,只有两个方法。

onCreateView 方法用于返回视图,返回的方法与平时使用的 Fragment 略有不同。平时用 View.inflate 方法获取视图并返回,在 DataBinding 下,使用 DataBindingUtil.inflate 方法返回 ViewBinding 对象,然后给该对象对应的布局文件中的变量赋值。

onDestory() 方法中调用了两个释放资源的方法,这两个方法是在 ViewModel 中声明的。

ViewModel


MVVM 中,ViewModel 是重头,它用于处理所有非 UI 的业务逻辑。对于计算器来说,业务逻辑就是数字、符号的输入,数字运算等。具体内容如下:

 package com.ch.wchhuangya.android.pandora.vm;

 import android.content.Context;
import android.databinding.ObservableField;
import android.view.View;
import android.widget.Button; /**
* Created by wchya on 2016-12-07 16:17
*/ public class CalculatorVM extends BaseVM { /** 用于定义操作符后的空格显示 */
public static final String EMPTY_STR = " ";
/** 用于定义结果数字前的显示 */
public static final String EQUALS_EMPTY_STR = "= "; /** 被操作数 */
public ObservableField<String> firstNum = new ObservableField<>("0");
/** 上一次结果 */
public ObservableField<String> secondNum = new ObservableField<>("");
/** 当前结果 */
public ObservableField<String> resNum = new ObservableField<>(""); /** 被操作数的数值 */
double fNum;
/** 上一次结果的数值 */
double sNum;
/** 当前结果的数值 */
double rNum;
/** 标识当前是否为初始状态 */
boolean initState = true;
/** 当前运算符 */
CalOperator mCurOperator;
/** 前一运算符 */
CalOperator mPreOperator; /** 运算符枚举 */
enum CalOperator {
ADD("+"),
MINUS("-"),
MULTIPLY("×"),
DIVIDE("÷"); private String value; CalOperator(String value) {
this.value = value;
} /** 根据运算符字符串获取运算符枚举 */
public static CalOperator getOperator(String value) {
CalOperator otor = null;
for (CalOperator operator : CalOperator.values()) {
if (operator.value.equals(value))
otor = operator;
}
return otor;
}
} public CalculatorVM(Context context) {
mContext = context;
} /**
* 数字点击处理
* 当数字变化时,先变化 firstNum,然后计算结果
*/
public void numClick(View view) {
String btnVal = ((Button) view).getText().toString(); if (btnVal.equals("0")) { // 当前点击 0 按钮
if (firstNum.get().equals("0")) // 当前显示的为 0
return;
} String originalVal = firstNum.get();
boolean firstIsDigit = Character.isDigit(originalVal.charAt(0)); if (isInitState()) { // 初始状态(既刚打开页面或点击了 Clear 之后)
handleFirstNum(btnVal, Double.parseDouble(btnVal));
handleResNum(EQUALS_EMPTY_STR + btnVal, Double.parseDouble(btnVal));
} else {
if (firstIsDigit) { // 首位是数字,直接在数字后添加
String changedVal = originalVal + btnVal;
handleFirstNum(changedVal, Double.parseDouble(changedVal));
handleResNum(EQUALS_EMPTY_STR + String.valueOf(fNum), Double.parseDouble(changedVal));
} else { // 首位是运算符,计算结果后显示 if (originalVal.length() == 3 && Double.parseDouble(originalVal.substring(2)) == 0L) // 被操作数是 运算符 + 空格 + 0
handleFirstNum(mCurOperator.value + EMPTY_STR, Double.parseDouble(btnVal));
else
handleFirstNum(originalVal + btnVal, Double.parseDouble((originalVal + btnVal).substring(2))); cal();
}
}
adjustNums();
setInitState(false);
} /** 退格键事件 */
public void del() {
String first = firstNum.get();
if (secondNum.get().length() > 0) { // 正在计算 if (first.length() <= 3) { // firstNum 是运算符,把 secondNum 的值赋值给 firstNum,secondNum 清空
handleFirstNum(sNum + "", sNum);
handleResNum(EQUALS_EMPTY_STR + secondNum.get(), sNum);
handleSecondNum("", 0L);
mCurOperator = null;
} else { // 把最后一个数字删除,重新计算
String changedVal = first.substring(0, first.length() - 1);
handleFirstNum(changedVal, Double.parseDouble(changedVal.substring(2)));
cal();
}
} else { // 没有计算 if ((first.startsWith("-") && first.length() == 2) || first.length() == 1) { // 只有一位数字
setInitState(true);
handleFirstNum("0", 0L);
handleResNum("", 0L);
} else {
String changedFirst = first.substring(0, firstNum.get().length() - 1);
handleFirstNum(changedFirst, Double.parseDouble(changedFirst));
handleResNum(EQUALS_EMPTY_STR + fNum, fNum);
}
}
adjustNums();
} /** 运算符点击处理 */
public void operatorClick(View view) {
String btnVal = ((Button) view).getText().toString(); // 如果当前有运算符,并且运算符后有数字,把当前运算符赋值给前一运算符
if (mCurOperator != null && firstNum.get().length() >= 3)
mPreOperator = mCurOperator; mCurOperator = CalOperator.getOperator(btnVal); if (secondNum.get().equals("")) { // 1. 没有 secondNum,把 firstNum 赋值给 secondNum,然后把运算符赋值给 firstNum handleSecondNum(firstNum.get(), Double.parseDouble(firstNum.get()));
handleFirstNum(mCurOperator.value + EMPTY_STR, 0L);
} else { // 2. 有 secondNum
if (firstNum.get().length() == 2) { // 2.1 只有运算符时,只改变运算符显示,其它不变 firstNum.set(mCurOperator.value + EMPTY_STR);
} else { // 2.2 既有运算符,又有 firstNum 和 secondNum 时,计算结果 if (mPreOperator != null) {
mPreOperator = null; handleFirstNum(mCurOperator.value + EMPTY_STR, 0L);
handleSecondNum(rNum + "", rNum);
} else {
cal();
handleFirstNum(mCurOperator.value + EMPTY_STR, 0L);
}
}
}
setInitState(false);
adjustNums();
} /**
* 点的事件处理
* 1. 只能有一个点
* 2. 输入点后,firstNum 的值不变,只改变显示
*/
public void dotClick() {
if (firstNum.get().contains("."))
return;
else {
setInitState(false);
String val = firstNum.get(); if (!Character.isDigit(val.charAt(0)) && val.length() == 2) {
handleFirstNum(val + "0.", fNum);
} else
handleFirstNum(val + ".", fNum);
}
} /**
* 百分号的事件处理
* 1. 初始状态或刚刚经过 clear 操作时,点击无反应
* 2. 当 firstNum 为运算符时,点击无反应
* 3. 其余情况,点击后将 firstNum 乘以 0.01
*/
public void percentClick() {
String originalVal = firstNum.get();
if (isInitState())
return;
else if (originalVal.length() == 1 && !Character.isDigit(originalVal.charAt(0)))
return;
else {
fNum = fNum * 0.01;
if (mCurOperator != null) {
handleFirstNum(mCurOperator.value + " " + fNum, fNum);
cal();
} else {
handleFirstNum(String.valueOf(fNum), fNum);
handleResNum(String.valueOf(fNum), fNum);
}
}
} /**
* 等号事件处理
* 1. 只有 firstNum,不作任何处理
* 2. 有 secondNum 时,把 secondNum 和 firstNum 的值进行运算,然后把值赋值给 firstNum,清空 secondNum,
*/
public void equalsClick() {
if (!secondNum.get().equals("")) {
cal();
handleFirstNum(String.valueOf(rNum), rNum);
handleSecondNum("", 0L);
}
adjustNums();
} /** 计算结果 */
private void cal() {
switch (mCurOperator) {
case ADD:
rNum = sNum + fNum;
handleResNum(EQUALS_EMPTY_STR + rNum, rNum);
break;
case MINUS:
rNum = sNum - fNum;
handleResNum(EQUALS_EMPTY_STR + rNum, rNum);
break;
case MULTIPLY:
rNum = sNum * fNum;
handleResNum(EQUALS_EMPTY_STR + rNum, rNum);
break;
case DIVIDE:
if (fNum == 0L) {
rNum = 0L;
handleResNum("= ∞", rNum);
} else {
rNum = sNum / fNum;
handleResNum(EQUALS_EMPTY_STR + rNum, rNum);
}
break;
}
adjustNums();
} /**
* 调整结果,主要将最后无用的 .0 去掉
*/
private void adjustNums() {
String ffNum = firstNum.get();
String ssNum = secondNum.get();
String rrNum = resNum.get();
if (ffNum.endsWith(".0")) {
firstNum.set(ffNum.substring(0, ffNum.length() - 2));
}
if (ssNum.endsWith(".0")) {
secondNum.set(ssNum.substring(0, ssNum.length() - 2));
}
if (rrNum.endsWith(".0"))
resNum.set(rrNum.substring(0, rrNum.length() - 2));
} /** 将计算器恢复到初始状态 */
public void clear() {
setInitState(true); handleFirstNum("0", 0L); handleSecondNum("", 0L); handleResNum("", 0L); mCurOperator = null;
} /** 处理被操作数的显示和值 */
private void handleFirstNum(String values, double val) {
firstNum.set(values);
fNum = val;
} /** 处理上次结果的显示和值 */
private void handleSecondNum(String values, double val) {
secondNum.set(values);
sNum = val;
} /** 处理本次结果的显示和值 */
private void handleResNum(String values, double val) {
resNum.set(values);
rNum = val;
} public boolean isInitState() {
return initState;
} public void setInitState(boolean initState) {
this.initState = initState;
} @Override
public void reset() {
// 释放其它资源
mContext = null; // 取掉观察者的注册
unsubscribe();
}
}

ViewModel

要注意的是:ObservableXXX 变量值的获取方法为—— variable.get(),设置方法为:variable.set(xxx)

该类有一个父类:BaseVM, 它用于定义一些通用的变量和子类必须实现的抽象方法。内容如下:

 package com.ch.wchhuangya.android.pandora.vm;

 import android.content.Context;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity; import java.util.ArrayList;
import java.util.List; import rx.Subscription; /**
* Created by wchya on 2016-11-27 20:32
*/ public abstract class BaseVM { /** VM 模式中,View 引用的持有 */
protected AppCompatActivity mActivity;
/** VM 模式中,View 引用的持有 */
protected Fragment mFragment;
/** VM 模式中,上下文引用的持有 */
protected Context mContext;
/** 所有用到的观察者 */
protected List<Subscription> mSubscriptions = new ArrayList<>(); /** 释放持有的资源引用 */
public abstract void reset(); /** 将所有注册的观察者反注册掉 */
public void unsubscribe() {
for (Subscription subscription : mSubscriptions) {
if (subscription != null && subscription.isUnsubscribed())
subscription.unsubscribe();
}
}
}

BaseVM

最终效果如下:

计算器

结束语


本文只是借助计算器这个小应用,把所学的 DataBindingMVVM 的知识使用在实际当中。文中主要使用了 Google 官方 DataBinding 的一些特性,比如为控件设置属性值,为控件绑定事件等。如果读者对这一块内容还不了解,请在官网上查找相关文档进行学习,地址:https://developer.android.com/topic/libraries/data-binding/index.html

笔者在学习时,对官方文档进行了翻译,如果大家对英文文档比较抗拒,可以尝试看一下我的翻译。因为本人能力有限,难免出现错误,欢迎大家用评论的方式告知于我,翻译文档的地址:http://www.cnblogs.com/wchhuangya/p/6031934.html

该应用只是实现了计算器的基本功能,功能不够完善,而且,还有一些缺陷。已知的缺陷有:1. 双精度位数的处理;2. 特别大、特别小数字的显示及处理;这些缺陷只是计算器算法处理上的缺陷,与本文的主题无关,有兴趣的朋友可以将其修改、完善。记着,改好后记得告诉我哦!

路漫漫其修远兮,吾将上下而求索。此话与诸君共勉之!

MVVM 实战之计算器的更多相关文章

  1. Jetpack MVVM 实战项目,附带源码+视频,收藏!

    从读者的反馈来看,近期大部分安卓开发已跳出舒适圈,开始尝试认识和应用 Jetpack MVVM 到实际的项目开发中. 只可惜,关于 Jetpack MVVM,网上多是 东拼西凑.人云亦云.通篇贴代码  ...

  2. C# WPF MVVM 实战 – 5- 用绑定,通过 VM 设置 View 的控件焦点

    本文介绍在 MVVM 中,如何用 ViewModel 控制焦点. 这焦点设置个东西嘛,有些争论.就是到底要不要用 ViewModel 来控制视图的键盘输入焦点.这里不讨论,假设你就是要通过 VM,设置 ...

  3. C# WPF MVVM 实战 – 4 - 善用 IValueConverter

    IValueConverter,做 WPF 的都应该接触过,把值换成 Visibility .Margin 等等是最常见的例子,也有很多很好的博文解释过用法.本文只是解释一下,MVVM 中一些情景. ...

  4. Electron 实战桌面计算器应用

    前言 Electron 是一个搭建跨平台桌面应用的框架,仅仅使用 JavaScript.HTML 以及 CSS,即可快速而容易地搭建一个原生应用.这对于想要涉及其他领域的开发者来说是一个非常大的福利. ...

  5. Android 开发笔记___初级控件之实战__计算器

    功能简单,实现并不难,对于初学者可以总和了解初级控件的基本使用. 用到的知识点如下: 线性布局 LinearLayout:整体界面是从上往下的,因此需要垂直方向的linearlayout:下面每行四个 ...

  6. AvalonJS+MVVM实战部分源码

    轻量级前端MVVM框架avalon,它兼容到 IE6 (其他MVVM框架,KnockoutJS(IE6), AngularJS(IE9), EmberJS(IE8), WinJS(IE9) ),它可以 ...

  7. python小实例——tkinter实战(计算器)

    一.完美计算器实验一 import tkinter import math import tkinter.messagebox class calculator: #界面布局方法 def __init ...

  8. MVVM实战

    1.层次依赖 - (UIViewController *)createInitialViewController { self.viewModelServices = [RWTViewModelSer ...

  9. Windows Phone 十一、MVVM模式

    MVVM 模式介绍 模型-视图-视图模型 (MVVM) 是一种用来分离 UI 和非 UI 代码的应用设计模式 MVVM – 模型(Model) MVVM 中的 Model 与 MVC 中的一致,用于封 ...

随机推荐

  1. 【文件监控】之一:理解 ReadDirectoryChangesW part1

    理解 ReadDirectoryChangesW 原作者:Jim Beveridge 原文:http://qualapps.blogspot.com/2010/05/understanding-rea ...

  2. 一步一步学RenderMonkey(3)——改良Phong光照模型 【转】

    转载请注明出处: http://blog.csdn.net/tianhai110 改良后的Phong光照模型: 上一节实现的phong镜面光照模型,如果固定光源,移动视点(及matView 关联为ma ...

  3. DevExpress中的lookupedit的使用方法详解

    摘自: http://***/zh-CN/Info/catalog/17631.html 概述:本文详细介绍了DevExpress中的lookupedit的使用方法. 绑定数据源:   1 2 3 l ...

  4. Strategy Pattern(策略模式)

    Head First定义: 策略模式定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户. 策略模式的设计原则主要有三个: 找出应用中可能需要变化的部分,把它们独 ...

  5. Out of office 模板

    I am out of the office until 0X/0X/201X. I will be checking my email regularly.  Please leave a comp ...

  6. c语言指针详解 经典

    指针是C语言中广泛使用的一种数据类型. 运用指针编程是C语言最主要的风格之一.利用指针变量可以表示各种数据结构: 能很方便地使用数组和字符串: 并能象汇编语言一样处理内存地址,从而编出精练而高效的程序 ...

  7. Java 8 Stream 教程

    Java 8 Stream Tutorial 本文采用实例驱动的方式,对JAVA8的stream API进行一个深入的介绍.虽然JAVA8中的stream API与JAVA I/O中的InputStr ...

  8. springMVC 头像裁剪上传并等比压

    第一次写头像裁剪上传,原本想着直接本地预览裁剪再上传,可是时间有限,jquery.jcrop貌似并没有对 假设是ie下图片预览效果是滤镜做的  做出对应处理,也没有时间去改;仅仅好将就一下先把图片上传 ...

  9. C# 和 Java的区别积累

    1.类的继承 A.C#用 ":"符号: B.Java用关键字 extends: 2.接口的实现 A.C#用 ":"符号: B.Java用关键字 implemen ...

  10. java泛型介绍

    一.泛型初衷 Java集合不会知道我们需要用它来保存什么类型的对象,所以他们把集合设计成能保存任何类型的对象,只要就具有很好的通用性.但这样做也带来两个问题: –集合对元素类型没有任何限制,这样可能引 ...