帮我看一下这个Java数组微信小程序数组遍历,我不理解int[] foo = arr[0] 和 foo = arr[1]为什么指向不同

在学 php,理解不了为什么 $foo-&$arr[1] 和 $foo-&{$arr}[1] 的结果不一致...
19:42:16 +08:00 · 2258 次点击
class foo {
var $bar = 'I am bar.';
var $arr = array('I am A.', 'I am B.', 'I am C.');
= 'I am r.';
$foo = new foo();
$bar = 'bar';
$baz = array('foo', 'bar', 'baz', 'quux');
echo $foo-&$bar . "\n";
//$bar 的值是 bar ,$foo-&bar 的值是 I am bar
echo $foo-&$baz[1] . "\n"; //I am bar.
$start = 'b';
echo $foo-&{$start . $end} . "\n";//$start 的值是'b' ,$end 的值是'ar' ,{$start . $end}的值是 'bar',$foo-&{$start . $end} 的值是'I am bar.'
$arr = 'arr';
echo $foo-&$arr[1] . "\n";// $arr 的值是 'arr' ,$arr[1] 的值是 r $foo-&$arr[1]的值是 'I am r.'。
echo $foo-&{$arr}[1] . "\n";// 我理解:$arr 的值是 'arr' ,{$arr}[1] 的值是 r $foo-&$arr[1]的值是 'I am r.'。但实际输出是 I am B.不太理解这里的运算优先级是什么情况。查手册运算符优先级里面没有 -& ...
最后一行实在理解不了为什么输出 I am B ...
第 1 条附言 &·&
10:02:15 +08:00
在 php7 两者的结果都变成了&I am B&,这种和语言版本相关的特征已经放弃理解了...
21 回复 &| &直到
20:24:47 +08:00
& & 19:57:52 +08:00
php 中的大括号 有一个用法是界定变量的界限,也可以界定表达式{$arr} = 'arr',网上都有总结
& & 19:59:00 +08:00
还有你这个 类里面用的 var php5*差不多都淘汰了,建议你换高的版本
& & 20:00:12 +08:00
在 php 中使用 var 声明类成员属性是 php4 时代的写法,
另外$arr 与{$arr}结果一致没有什么不妥。
& & 20:17:04 +08:00
PHP7 做了统一:
& & 20:19:32 +08:00
$$foo['bar']['baz'] // interpreted as ($$foo)['bar']['baz']
$foo-&$bar['baz']
// interpreted as ($foo-&$bar)['baz']
$foo-&$bar['baz']() // interpreted as ($foo-&$bar)['baz']()
Foo::$bar['baz']()
// interpreted as (Foo::$bar)['baz']()
理解就好,而且一般也不这么写,看起来好费劲。
& & 20:20:46 +08:00
大括号改变了解释的顺序。
& & 20:52:33 +08:00
在 $b 等于 c ... $b[$d] 等于 e 的情况下 ...
在旧版本的 php 中 $a-&$b[$d] 的含义是从 a 对象中取得 $c [$d] 的值 ... 即 $e 的值 ...
使用变量定界符的话 $a-&{$b}[$d] 的含义是从 a 对象中优先取得 $c 的值然后取 $d 位 ...
如楼上所说这种差异在 php7 里面已经不存在了 ... php7 中你这两种情况都视为有定界符 ...
假如你要获得原始的结果需要使用 $a-&{$b[$d]} ... 等同于 $a-&e ... 这也是最规范的写法 ...
两点建议第一是从最新版的 php 用起 ... 现在还从 5.2 年代的教程看起的话未免太过误人子弟 ...
第二是 php 里面这种小坑其实还挺多的 ... 这类知识点除了面试时候有用实际开发中很难遇到 ...
所以学的时候觉得头疼也不用太过担心 ... 这不是个常见情况 ... 能记则记不能记也没关系的 ...
& & 20:54:30 +08:00
这个是直接复制的
的例子,就没动 var 声明。
问题是结果不一致...
@ 搜索了 Php 中&{}&大括号的用法总结,但是还是不理解这里的运算优先级。
$foo-&$arr[1]
是[]运算符优先,但是 $foo-&{$arr}[1] 就变成了 -& 运算符优先。怎么理解 -& [] 的优先级呢?
查 php 文档运算符优先表里面没有-&,好头疼...
的确在 php7 下结果变了,这样会让一些程序掉坑里吧?
echo $foo-&$arr[1]
echo $foo-&{$arr}[1]
都输出 &I am B.&
& & 21:06:46 +08:00
的确,实际应用中基本用不到,这种复杂的情况一般都会手动指定优先级。
的中文文档,刚刚又看了一下英文版本,发现删掉了这一部分,并说明 php7 有变更,详情查看 php7 迁移指南。
不过英文版本也很多地方没更新,这里还是使用 var 声明的类变量。
& & 21:30:50 +08:00
输出 B 是因为 Array 的 index 从 0 开始, 1 当然是输出第二个。
& & 22:50:50 +08:00
@ 手册更新也是逐步的。。。能否告知下你看的是中文手册的那一部分,我去更新下_(:з」∠)_
& & 07:59:57 +08:00 via iPhone
233 你理解错楼主意思了
& & 08:34:58 +08:00
非常感谢, Example #1 Variable property example 的地址是:
& & 08:54:44 +08:00
为什么要简单的问题,搞这么复杂! 我觉得编程就应避繁从简,回避这种容易产生歧义用法
& & 09:56:35 +08:00
@ 好像是的,他的问题不在 index ,在 variable 名称变换
@ 话说你这个例子看看就好,实际开发都是不规范的写法,不必太深究这种变换
& & 10:30:08 +08:00 via Android
php 里面不是传统的解析 ast 的那种思路, 233 。它的语法设计思路在某种意义上跟 Ruby 的语意设计思路有点像
& & 11:27:45 +08:00
理解这个完全是浪费时间,我写了五六年 php ,也理解不了。。。
& & 13:14:13 +08:00
这个是链式结构,我问过这个问题,楼主看一下这个-&https://segmentfault.com/q/6442
& & 14:52:11 +08:00
@ PHP7 开始也引入 AST 了
& & 18:02:35 +08:00
怎么感觉是要去考试一样?计算机语言用的时候都是挑一种简单好理解就行了,考试才会找出各种对的和错误的。。。
& & 20:24:47 +08:00
打算认真学习下 php ,看
文档时碰到了这个问题,实在太违反常识了...
& · & 1662 人在线 & 最高记录 3541 & · &
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.0 · 52ms · UTC 02:35 · PVG 10:35 · LAX 18:35 · JFK 21:35? Do have faith in what you're doing.&h2&&b&java后端程序员1年工作经验总结&/b&&/h2&&p&&b&java后端1年经验和技术总结(1)&/b&&/p&&h2&&b&1.引言&/b&&/h2&&p&  毕业已经一年有余,这一年里特别感谢技术管理人员的器重,以及同事的帮忙,学到了不少东西。这一年里走过一些弯路,也碰到一些难题,也受到过做为一名开发却经常为系统维护和发布当救火队员的苦恼。遂决定梳理一下自己所学的东西,为大家分享一下。&/p&&p&  经过一年意识到以前也有很多认识误区,比如:&/p&&p&  偏爱收集,经常收集各种资料视频塞满一个个硬盘,然后心满意足的看着容量不行动。&/p&&p&  不重基础,总觉得很多基础东西不需要再看了,其实不懂的地方很多,计算机程序方面任何一个结果都必有原因,不要只会用不知道原理,那是加工厂出来的。现在ide查看代码那么方便,ctrl+点击就进入了JDK查看实现细节。&/p&&p&  好高骛远,在计算机基础不牢固的情况下,总想着要做架构,弄分布式,搞大数据之类。&/p&&p&  不重视性能,只求能实现功能,sql查询是不是可以优化,是否有算法妙用,大对象是否要清除。&/p&&p&  不重视扩展性,模块之间紧密耦合,常用方法不提取成工具类,调用关系混乱等问题。&/p&&p&  ……&/p&&p&  本文重点不在这些,故只列举了一小部分,下面进入正题。&/p&&h2&&b&2.语法基础&/b&&/h2&&p&&b& 2.1 Java类初始化顺序&/b&&/p&&p&  这是所有情况的类初始化顺序,如果实际类中没有定义则跳过:父类静态变量——父类静态代码块——子类静态代码块——父类非静态变量——父类非静态代码块——父类构造函数——子类非静态变量——子类非静态代码块——子类构造函数&/p&&p&&b& 2.2 值传递和引用传递&/b&&/p&&p&  可能很多人对此不屑一顾,心想老子都工作一年了,对这些还不熟悉吗?但实际情况并非这样,JDK中东西全部熟悉了吗?以一个最简单的例子开始,你觉得下图中代码执行完之后fatherList中的元素是什么?&/p&&figure&&img src=&https://pic2.zhimg.com/v2-2c124a5cfaad73d04c5eeb42d83a1375_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&932& data-rawheight=&266& class=&origin_image zh-lightbox-thumb& width=&932& data-original=&https://pic2.zhimg.com/v2-2c124a5cfaad73d04c5eeb42d83a1375_r.jpg&&&/figure&&p&  这是一个最基础的值传递和引用传递的例子,你觉得好简单,已经想跃跃欲试的挑战了,那么请看下面的,StringBuffer很好理解,但是当你执行一遍之后发现是不是和预想中的输出不一样呢?String不是引用类型吗,怎么会这样呢?如果你无法理解,那么请看下String的实现源码,了解下其在内存中分配的实现原理。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-dda807c5bc10f37fbd5245_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1082& data-rawheight=&896& class=&origin_image zh-lightbox-thumb& width=&1082& data-original=&https://pic2.zhimg.com/v2-dda807c5bc10f37fbd5245_r.jpg&&&/figure&&p&&b& 2.3 集合的使用&/b&&/p&&p&  这部分几乎每个人都会用到,而且大家还都不陌生。下图来源于互联网,供大家复习一下。但是利用集合的特性进行巧妙的组合运用能解决优化很多复杂问题。Set不可重复性,List的顺序性,Map的键值对,SortSet/SortMap的有序性,我在工作中有很多复杂的业务都巧妙的使用了这些,涉及到公司保密信息,我就不贴出代码了。工作越久越发现这些和越巧妙。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-d68c73a0ea3cd59d77ef99_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&530& data-rawheight=&499& class=&origin_image zh-lightbox-thumb& width=&530& data-original=&https://pic2.zhimg.com/v2-d68c73a0ea3cd59d77ef99_r.jpg&&&/figure&&p&&b& 2.3 异常处理&/b&&/p&&p&  1.看着try、catch、finally非常容易,如果和事务传播结合在一起,就会变得极其复杂。&/p&&p&  2.finally不一定必须执行,return在catch/finally中处理情况(建议亲自操刀试一下)。&/p&&p&  3.catch中可以继续抛自定义异常(并把异常一步步传递到控制层,利用切面抓取封装异常,返回给调用者)。&/p&&p&&b& 2.4 面向对象思想&/b&&/p&&p&  一提起面向对象,大家都知道抽象、封装、继承、和多态。但是实际工作经验中又知道多少呢,对于项目中如何巧用估计更不要提了。&/p&&p&  共性的机会每个都需要用的建立基类,如每个控制层方法可能要通过security获取一个登录用户id,用于根据不同的用户操作不同的数据,可以抽象出一个应用层基类,实现获取id的protect方法。同理DAO层可以利用泛型提取出一个包含增删改查的基类。&/p&&p&  多态的Override:基类的引用变量不仅可以指向基类的实例对象,也可以指向其子类的实例对象,如果指向子类的实例对象,其调用的方法应该是正在运行的那个对象的方法。在策略模式中使用很普遍。&/p&&p&  提到面向对象,就不可避免的要说设计模式,在工作中,一个技术大牛写的一个类似策略模式(更复杂一点),十分巧妙的解决了各种业务同一个方法,并且实现了订单、工单、业务的解耦,看得我是非常佩服。我想很多面试中都会问道单例模式吧,还没有理解的建议去看一看。&/p&&h2&&b&3.多线程&/b&&/h2&&p&&b& 3.1 线程安全&/b&&/p&&p&  这个是老生常谈的问题了,但是确实是问题和bug高发区。线程同步问题不需要单独写了,想必大家都清楚,不太熟悉的建议百度一下。&/p&&p&&b& 3.1.1 线程安全问题&/b&&/p&&p&  1.代码中如果有同步操作,共享变量要特别注意(这个一般都能意识到)&/p&&p&  2多个操作能修改数据表中同一条数据的。(这个容易被忽略,业务A可能操作表a,业务B也可以操作表a,业务A、B即使在不同的模块和方法中,也会引起线程安全问题。例如如果一个人访问业务A接口,另一个人访问业务B接口,在web中每个业务请求都是会有单独的一个线程进行处理的,就会出现线程安全问题)。&/p&&p&  3.不安全的类型使用,例如StringBuffer、StringBuild,HashTable、HashMap等。在工作中我就遇到过有人在for循环进行list的remove,虽然编译器不报错,程序可以运行,但是结果却可想而知。&/p&&p&  4.Spring的bean默认是单例的,如果有类变量就要特别小心了(一般情况下是没人在控制层、业务层、DAO层等用类变量的,用的话建议是final类型,例如日志log,gson等)。&/p&&p&  5.多个系统共享数据库情况,这个其实和分布式系统类似&/p&&p&  用户重复提交问题(即使代码中从数据库读取是否存在进行限制不能解决问题)&/p&&p&&b& 3.1.2 线程安全解决&/b&&/p&&p&  在需要同步的地方采用安全的类型。&/p&&p&  JDK锁机制,lock、tryLock,synchronized,wait、notify、notifyAll等&/p&&p&  Concurrent并发工具包,在处理一些问题上,谁用谁知道。强烈建议查看源码!&/p&&p&  数据表加锁。(除非某个表的访问频率极低,否则不建议使用)&/p&&p&  涉及分布式的,采用中间件技术例如zookeeper等解决。&/p&&p&&b& 3.2 异步&/b&&/p&&p&  异步使用场景不影响主线程,且响应较慢的业务。例如IO操作,第三方服务(短信验证码、app推送、云存储上传等)。&/p&&p&  如果异步任务很多,就需要使用任务队列了,任务队列可以在代码级别实现,也可以利用redis(优势太明显了)。&/p&&p&&b& 3.3 多线程通信&/b&&/p&&p&  这方面文章非常多,这里不在详述。&/p&&p&  1.共享变量方式(共享文件、全局变量,信号量机制等)&/p&&p&  2.消息队列方式&/p&&p&  3. 忙等,锁机制&/p&&p&&b& 3.4多线程实现&/b&&/p&&p&  1.集成Thread类,重写(这里的重写指的是override)run方法,调用start方法执行。&/p&&p&  2.实现Runable接口,实现run方法,以Runable实例创建thread对象。&/p&&p&  3.实现Callable接口,实现call方法,FutureTask包装callable接口,FutureTask对象创建thread对象,常用语异步操作,建议使用匿名内部类,方便阅读和使用。&/p&&p&  额外需要说明的是:&/p&&p&  1.理解thread的join方法;&/p&&p&  2.不要认为volitate是线程安全的(不明白原因的建议去看jvm运行时刻内存分配策略);&/p&&p&  3.sleep时间片结束后并不保证立马获取cpu。&/p&&p&  4.ThreadLocal能够为每一个线程维护变量副本,常用于在多线程中用空间换时间。&/p&&h2&&b&4. 开源框架&/b&&/h2&&p&&b&4.1 Hibernate、Mybatis&/b&&/p&&p&  相信每一个java程序员对这些都不陌生,这里不再详述。&/p&&p&  需要说明的主要以下几点:&/p&&p&  1.hibernate一级缓存(内置session缓存),二级缓存(可装配sessionFactory缓存),二级缓存会引起并发问题。&/p&&p&  2.hibernate延迟加载原理理解。&/p&&p&  3.hibernate 的get、load方法,sava、persist、savaOrUpdate方法区别&/p&&p&  4.session重建了关联关系却并没有同数据库进行同步和更新  &/p&&p&  5.hibernate session关联关系:detached对象、persistent对象&/p&&p&  6.Spring data集成,注解方式配置属性和实体。&/p&&p&  7.mybatis 插件。&/p&&p&  8.分页查询(数据库)。&/p&&p&  9.连接池技术&/p&&p&&b&4.2 Spring IOC&/b&&/p&&p&&b&  4.1.1 Spring bean&/b&&/p&&p&    1.bean注入 注解方式方便易读,引用第三方(数据库连接,数据库连接池,JedisPool等)采用配置文件方式。&/p&&p&    2. bean作用域:Singleton,prototype,request,session,global session&/p&&p&    3.bean生命周期:如下图所示(图片来源于互联网):&/p&&figure&&img src=&https://pic4.zhimg.com/v2-0feaaba1ee3f66e125727_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&565& data-rawheight=&541& class=&origin_image zh-lightbox-thumb& width=&565& data-original=&https://pic4.zhimg.com/v2-0feaaba1ee3f66e125727_r.jpg&&&/figure&&p&&b&4.3 Spring AOP&/b&&/p&&p&  基本概念:关注点、切面Aspect、切入点pointcut、连接点joinpoint、通知advice、织入weave、引入introduction。&/p&&p&  Spring AOP支持5中类型通知,分别是MethodBeforeAdvice、AfterReturningAdvice、ThrowsAdvice、MethodInterceptor、IntroductionInterceptor(吐槽一下名字太长)&/p&&p&  实现方式如下:&/p&&p&  1.基于代理的AOP&/p&&p&  2.基于@Aspect注解驱动的切面。(强烈推荐:可读性好,易维护,易扩展,开发快)&/p&&p&  3.纯POJO切面。&/p&&p&  4.注入式Aspect切面。&/p&&p&&b&4.4 Srping事务&/b&&/p&&p&&b& 4.4.1 事务传播&/b&&/p&&p&  概念:某些操作需要保证原子性,如果中间出错,需要事务回滚。如果某个事务回滚,那么调用该事务的方法中的事务的作出如何的动作,就是事务传播。&/p&&p&  短时间内写不清楚,建议访问 &u&&a href=&//link.zhihu.com/?target=http%3A//www.cnblogs.com/yangy608/archive//1907065.html& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&cnblogs.com/yangy608/ar&/span&&span class=&invisible&&chive//1907065.html&/span&&span class=&ellipsis&&&/span&&/a&&/u& 查看。&/p&&p&  事务传播属性:&/p&&p&  1. PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。&/p&&p&  2. PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。&/p&&p&  3. PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。&/p&&p&  4. PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。&/p&&p&  5. PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。&/p&&p&  6. PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。&/p&&p&  事务隔离级别:&/p&&p&  1. ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.另外四个与JDBC的隔离级别相对应&/p&&p&  2. ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,充许令外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。&/p&&p&  3. ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据&/p&&p&  4. ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。&/p&&p&  5. ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。&/p&&p&&b&4.5 其他Spring 技术栈&/b&&/p&&p&  spring boot 轻量级启动框架&/p&&p&  spring security 用户权限管理,根据角色和用户,实现UserDetailsService,进行自定义权限管理。&/p&&p&  spring task 代码级定时任务,注解方式,使用起来非常方便。需要注意的是,如果某次定时任务出了异常而没有进行处理,会导致接下来定时任务失效。如果各个任务相互独立,可以简单用try,catch包围(之前就吃过这方面的亏)。&/p&&p&  spring data 注解方式定义实体,属性等&/p&&p&  spring mvc 简单明了的mvc框架。url传值、数组传值、对象传值、对象数组等传值类型,上传/下载文件类型需要注意。&/p&&p&  spring restful 注意命名,对命名要求很严格。&/p&&p&  spring shell 命令行方式执行命令,救火、导入导出数据等用起来非常方便、制作报表。  &/p&&h2&&b&5. Web基础&/b&&/h2&&p&&b& 5.1 web容器启动&/b&&/p&&p&  1.web.xml加载顺序: listener -& filter -& servlet&/p&&p&  2.webt容器启动过程,java新手很怕配置文件,理解完这些有助于熟悉配置文件 &u&&a href=&//link.zhihu.com/?target=http%3A//blog.csdn.net/u/article/details/& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&blog.csdn.net/u&/span&&span class=&invisible&&2/article/details/&/span&&span class=&ellipsis&&&/span&&/a&&/u&&/p&&p&&b& 5.2 Servlet、Interceptor、Listener、Filter&/b&&/p&&p&  Servlet 接收请求返回响应,最原始的web业务处理类。&/p&&p&  Interceptor 拦截器,可以实现HandlerInterceptor接口自定义拦截器,在日志记录、权限检查、性能监控、通用行为等场景使用,本质是AOP。&/p&&p&  Listener 监听器 常用于统计在线人数等纵向功能。&/p&&p&  Filter 过滤器 在请求接口处理业务之前改变requset,在业务处理之后响应用户之前改变response。如果某些数据不加密,很容易用抓包工具加filter作弊。&/p&&p&&b& 5.3 web项目结构&/b&&/p&&p&&b&  5.3.1 mvn结构&/b&&/p&&p&  熟练掌握几种常见的mvn项目结构,mvn可以自动生成,这里不再详述。&/p&&p&&b&  5.3.2 mvn包管理&/b&&/p&&p&  1.版本号尽量几种在一个文件中便于管理。&/p&&p&  2.spring milestone包解决spring包冲突问题。&/p&&p&  3.mvn dependency:tree命令分析所有包依赖,对于冲突的在pom文件中&exclusion& 包围起来&/p&&p&&b&  5.3.3 版本控制&/b&&/p&&p&  1.git、svn等&/p&&p&  2.代码冲突解决方案&/p&&p&  3.分支管理。&/p&&p&  对于某个稳定版本上线后,如果在此基础上开发新功能,一定要新建分支,在新分支上提交代码,最后在新版发布时合并分支。修改运营环境bug切换到主分支进行修改&/p&&p&&b&5.4 Http请求&/b&&/p&&p&&b&  5.4.1 请求方法&/b&&/p&&p&  post、get、put、head、delete、copy、move、connect、link、patch,最常用的是前4、5个。&/p&&p&&b&  5.4.2 请求头,状态码&/b&&/p&&p&  常用的请求头有Accept(下载文件会特殊使用)、Accept-Charset(设置utf-8字符集)、Content-Type(json等配置)等&/p&&p&  常用的响应头有Content-Type、Content-Type、Content-Length等,偏前端,不再详述。&/p&&h2&&b&6. 系统架构&/b&&/h2&&p&  接触的不是特别多,目前用到的只是服务器主从备份。Nginx反向代理进行配置。&/p&&p&  多个项目nginx配置&/p&&p&  Spring Mvc 用json数据进行交互,配置json转换的servlet。&/p&&p&  封装返回值&/p&&p&  自定义RunEnvironmentException(状态码,原因),覆盖原有Exception,切面ExceptionHandler抓取Exception并封装到返回值中(前后端松耦合)&/p&&p&  令人头疼的用户重复(连续快速点击)提交问题,前端限制治标不治本;后端用sessonid在切面上实现,又需要前端存储,对所有请求数据加sessionId。最后用jedis中存储,用接口名+用户名当做key,根据不同的接口对不同的key可以单独设置时间,不仅保证了重复提交问题,也避免了恶意请求问题,同时还能自定义请求间隔。(期初担心redis缓存读写时间延误导致限制失效,后来发现多虑了,对一般的小系统来说,经性能测试,发现即使请求频率再提高100被也不会导致限制失效)&/p&&p&  testNg单元测试、性能测试,覆盖测试。&/p&&p&  切面管理日期、权限。缓存等。&/p&&h2&&b&7. Nosql&/b&&/h2&&p&  1.Redis的java库Jedis。&/p&&p&  Jedispool配置。&/p&&p&  项目中用到的有任务队列、缓存。&/p&&p&  2. neo4j图数据库&/p&&p&  处理社交、推荐&/p&&h2&&b&8. 服务端&/b&&/h2&&p&  linux操作系统熟悉以centos为例:&/p&&p&  常用简单命令:ssh、vim、scp、ps、gerp、sed、awk、cat、tail,df、top,shell、chmod、sh、tar、find、wc、ln、|&/p&&p&  目录结构明细:/etc/、~/、/usr/、/dev/、/home/、/etc/init.d/&/p&&p&  服务端:jdk、tomcat、nginx、mysql、jedis、neo4j启动与配置(特别说明的是该死的防火墙,nginx启动后一直访问不了,查找一下午查不到原因,最后发现是防火墙问题)&/p&&p&  监控服务器状态(cpu,磁盘,内存),定位pid,日志查看&/p&&p&  nginx负载均衡、反向代理、配置&/p&&p&自动化部署脚本&/p&&p&  简单shell脚本书写,避免大量人力劳动。&/p&&p&  监控系统,代码抛fatal异常自动发邮件,系统指标持续偏高自动发邮件。&/p&&h2&&b&9. 数据库相关&/b&&/h2&&h2&&b&10. 第三方接口对接&/b&&/h2&&p&&b&10.1 支付接口&/b&&/p&&p&  微信支付坑比较多,用将近两周时间才把微信支付所有完成。需要在微信后台配置的地方太多。&/p&&p&  而支付宝支付模块只用了2天时间就搞定了。&/p&&p&&b&10.2 推送接口&/b&&/p&&p&  为用户定义tag、定义alias,注意当数据更新时需要同步更新tag、更新alias。如果没采用异步实现(用户体验就是好卡啊)&/p&&p&&b&10.3 云存储&/b&&/p&&p&  大量文件上传云端(七牛云),注意创建bucket&/p&&p&&b&10.4 短信验证&/b&&/p&&p&  很简单的第三方接口,引入依赖,直接调用即可。需要在第三方后台设置模板等,注意限定用户访问次数。&/p&&p&&b&10.5 邮件&/b&&/p&&p&  很简单小功能,工具类。&/p&&p&时间有限,目前先写这么多技术栈。对于代码书写和、算法技巧问题,会抽时间写在(2)中。&/p&&p&我有一个微信公众号,经常会分享一些Java技术相关的干货。如果你喜欢我的分享,可以用微信搜索“Java团长”或者“javatuanzhang”关注。&/p&&blockquote&原文链接:&a href=&//link.zhihu.com/?target=http%3A//www.cnblogs.com/sjlian/p/.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&java后端程序员1年工作经验总结 - 木公松&/a&&/blockquote&
java后端程序员1年工作经验总结java后端1年经验和技术总结(1)1.引言 毕业已经一年有余,这一年里特别感谢技术管理人员的器重,以及同事的帮忙,学到了不少东西。这一年里走过一些弯路,也碰到一些难题,也受到过做为一名开发却经常为系统维护和发布当救火…
&ul&&li&熟悉常用的数据结构,包括数组,链表,树,哈希表等。&br&&/li&&li&熟悉结构化编程和面向对象编程。&br&&/li&&li&能够阅读UML设计图,根据UML语义进行编码&br&&/li&&li&了解RDBMS和SQL的使用,包括DDL和DML,理解数据库事务(ACID),锁和死锁。&br&&/li&&li&熟悉Java语法特性,正确理解多线程,同步,临界区,线程安全,异常,泛型等。&br&&/li&&li&了解常用的Java库,比如java.lang,&a href=&//link.zhihu.com/?target=http%3A//java.io& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&java.io&/span&&span class=&invisible&&&/span&&/a&,&a href=&//link.zhihu.com/?target=http%3A//java.net& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&java.net&/span&&span class=&invisible&&&/span&&/a&,java.util,java.sql,javax.xml,&br&&/li&&li&能够看懂中英文javadoc,快速上手新的类库和框架。&br&&/li&&li&熟悉Java Web编程,如Servlet,Spring,以及ORM框架。&br&&/li&&li&熟练使用Java IDE如eclipse,尤其是能够调试错误。&br&&/li&&li&会使用构建工具如Ant,Maven构建项目。&br&&/li&&li&会使用JUnit开发单元测试用例,能够使用代码覆盖率工具如EMMA检查测试的覆盖率&br&&/li&&li&如果能够使用代码调优工具提升代码性能更好&br&&/li&&li&如果能够使用静态检查工具如PMD检查代码中的问题会更好&br&&/li&&li&如果能够了解JVM的机制,如垃圾回收,类加载会更好&br&&/li&&/ul&&br&&br&&br&怎样才是一个进阶水平的Java程序员呢?&br&&ul&&li&掌握面向对象的需求分析和设计&br&&/li&&li&理解SOLID原则,理解常用设计模式,熟练掌握重构&/li&&li&理解面向服务的架构&br&&/li&&li&理解Linux操作系统的一些概念,比如内核空间,用户空间,系统调用,命名管道,Unix Domain Socket,&/li&&li&熟悉Linux下命令行工具。&br&&/li&&li&深入理解进程,线程。&/li&&li&了解Java内存模型&br&&/li&&li&了解Java的并发包,如锁,读写锁,Barrier,Executer和Future&/li&&li&了解Java 8的Lambda表达式和Streaming API&/li&&li&理解TCP/IP协议族&br&&/li&&li&理解HTTP协议和REST API&br&&/li&&li&理解多路复用和非阻塞IO&br&&/li&&li&熟悉Socket编程,能够使用网络框架如netty开发服务端程序&br&&/li&&li&掌握RPC框架的使用,比如RMI,Thrift,Dubbo&br&&/li&&li&掌握消息队列的使用,如JMS,ActiveMQ等&br&&/li&&li&掌握分布式缓存如memcache,Redis的使用&br&&/li&&li&了解代理和负载均衡如Nginx的使用&br&&/li&&li&熟悉分布式KV存储如zookeeper,etcd等,理解选主和分布式锁&br&&/li&&li&会使用Mock框架进行单元测试&/li&&li&了解性能方面的知识,如吞吐量和时延。&/li&&li&能够进行执行性能测试,收集性能数据并进行分析。&br&&/li&&li&了解安全性相关的知识,比如私密性(加密和解密),完整性(消息认证),不可否认性(签名和验签),身份认证,授权,传输层安全(如SSL/TLS),数字证书和PKI体系&/li&&li&了解可用性相关的知识(,如可用性指标,如MTBF,MTTR,和高可用的实现,如主备。双活,负载均衡&/li&&li&了解容错性,容灾和故障转移&/li&&/ul&
熟悉常用的数据结构,包括数组,链表,树,哈希表等。 熟悉结构化编程和面向对象编程。 能够阅读UML设计图,根据UML语义进行编码 了解RDBMS和SQL的使用,包括DDL和DML,理解数据库事务(ACID),锁和死锁。 熟悉Java语法特性,正确理解多线程,同步,临界区…
&p&这篇文章应该可以解答你的疑问。&/p&&p&Java中异常提供了一种识别及响应错误情况的一致性机制,有效地异常处理能使程序更加健壮、易于调试。异常之所以是一种强大的调试手段,在于其回答了以下三个问题:&/p&&ul&&li&什么出了错?&/li&&li&在哪出的错?&/li&&li&为什么出错?&/li&&/ul&&p&在有效使用异常的情况下,异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪“抛出,异常信息回答了“为什么“会抛出,如果你的异常没有回答以上全部问题,那么可能你没有很好地使用它们。有三个原则可以帮助你在调试过程中最大限度地使用好异常,这三个原则是:&/p&&ul&&li&具体明确&/li&&li&提早抛出&/li&&li&延迟捕获&/li&&/ul&&p&为了阐述有效异常处理的这三个原则,本文通过杜撰个人财务管理器类JCheckbook进行讨论,JCheckbook用于记录及追踪诸如存取款,票据开具之类的银行账户活动。&br&&strong&具体明确&/strong&&br&Java定义了一个异常类的层次结构,其以Throwable开始,扩展出Error和Exception,而Exception又扩展出RuntimeException.如图1所示.&/p&&figure&&img src=&https://pic3.zhimg.com/acad623aaaabcf49dd856ab_b.jpg& data-rawwidth=&300& data-rawheight=&204& class=&content_image& width=&300&&&/figure&&br&&p&图1.Java异常层次结构&/p&&p&这四个类是泛化的,并不提供多少出错信息,虽然实例化这几个类是语法上合法的(如:new Throwable()),但是最好还是把它们当虚基类看,使用它们更加特化的子类。Java已经提供了大量异常子类,如需更加具体,你也可以定义自己的异常类。&/p&&p&例 如:&a href=&//link.zhihu.com/?target=http%3A//java.io& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&java.io&/span&&span class=&invisible&&&/span&&/a& package包中定义了Exception类的子类IOException,更加特化确的是 FileNotFoundException,EOFException和ObjectStreamException这些IOException的子 类。每一种都描述了一类特定的I/O错误:分别是文件丢失,异常文件结尾和错误的序列化对象流.异常越具体,我们的程序就能更好地回答”什么出了错”这个 问题。&/p&&p&捕 获异常时尽量明确也很重要。例如:JCheckbook可以通过重新询问用户文件名来处理FileNotFoundException,对于 EOFException,它可以根据异常抛出前读取的信息继续运行。如果抛出的是ObjectStreamException,则程序应该提示用户文件 已损坏,应当使用备份文件或者其他文件。&/p&&p&Java让明确捕获异常变得容易,因为我们可以对同一try块定义多个catch块,从而对每种异常分别进行恰当的处理。&/p&&br&&br&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&n&&File&/span& &span class=&n&&prefsFile&/span& &span class=&o&&=&/span& &span class=&k&&new&/span& &span class=&n&&File&/span&&span class=&o&&(&/span&&span class=&n&&prefsFilename&/span&&span class=&o&&);&/span&
&span class=&k&&try&/span&&span class=&o&&{&/span&
&span class=&n&&readPreferences&/span&&span class=&o&&(&/span&&span class=&n&&prefsFile&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&span class=&k&&catch&/span& &span class=&o&&(&/span&&span class=&n&&FileNotFoundException&/span& &span class=&n&&e&/span&&span class=&o&&){&/span&
&span class=&c1&&// alert the user that the specified file&/span&
&span class=&c1&&// does not exist&/span&
&span class=&o&&}&/span&
&span class=&k&&catch&/span& &span class=&o&&(&/span&&span class=&n&&EOFException&/span& &span class=&n&&e&/span&&span class=&o&&){&/span&
&span class=&c1&&// alert the user that the end of the file&/span&
&span class=&c1&&// was reached&/span&
&span class=&o&&}&/span&
&span class=&k&&catch&/span& &span class=&o&&(&/span&&span class=&n&&ObjectStreamException&/span& &span class=&n&&e&/span&&span class=&o&&){&/span&
&span class=&c1&&// alert the user that the file is corrupted&/span&
&span class=&o&&}&/span&
&span class=&k&&catch&/span& &span class=&o&&(&/span&&span class=&n&&IOException&/span& &span class=&n&&e&/span&&span class=&o&&){&/span&
&span class=&c1&&// alert the user that some other I/O&/span&
&span class=&c1&&// error occurred&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&br&&p&JCheckbook 通过使用多个catch块来给用户提供捕获到异常的明确信息。举例来说:如果捕获了FileNotFoundException,它可以提示用户指定另一 个文件,某些情况下多个catch块带来的额外编码工作量可能是非必要的负担,但在这个例子中,额外的代码的确帮助程序提供了对用户更友好的响应。&/p&&p&除前三个catch块处理的异常之外,最后一个catch块在IOException抛出时给用户提供了更泛化的错误信息.这样一来,程序就可以尽可能提供具体的信息,但也有能力处理未预料到的其他异常。&/p&&p&有 时开发人员会捕获范化异常,并显示异常类名称或者打印堆栈信息以求"具体"。千万别这么干!用户看到java.io.EOFException或者堆栈信息 只会头疼而不是获得帮助。应当捕获具体的异常并且用"人话"给用户提示确切的信息。不过,异常堆栈倒是可以在你的日志文件里打印。记住,异常和堆栈信息是用来帮助开发人 员而不是用户的。&/p&&p&最后,应该注意到JCheckbook并没有在readPreferences()中捕获异常,而是将捕获和处理异常留到用户界面层来做,这样就能用对话框或其他方式来通知用户。这被称为"延迟捕获",下文就会谈到。&/p&&p&&strong&提早抛出&/strong&&br&异常堆栈信息提供了导致异常出现的方法调用链的精确顺序,包括每个方法调用的类名,方法名,代码文件名甚至行数,以此来精确定位异常出现的现场。&/p&&br&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&n&&java&/span&&span class=&o&&.&/span&&span class=&na&&lang&/span&&span class=&o&&.&/span&&span class=&na&&NullPointerException&/span&
&span class=&n&&at&/span& &span class=&n&&java&/span&&span class=&o&&.&/span&&span class=&na&&io&/span&&span class=&o&&.&/span&&span class=&na&&FileInputStream&/span&&span class=&o&&.&/span&&span class=&na&&open&/span&&span class=&o&&(&/span&&span class=&n&&Native&/span& &span class=&n&&Method&/span&&span class=&o&&)&/span&
&span class=&n&&at&/span& &span class=&n&&java&/span&&span class=&o&&.&/span&&span class=&na&&io&/span&&span class=&o&&.&/span&&span class=&na&&FileInputStream&/span&&span class=&o&&.&&/span&&span class=&n&&init&/span&&span class=&o&&&(&/span&&span class=&n&&FileInputStream&/span&&span class=&o&&.&/span&&span class=&na&&java&/span&&span class=&o&&:&/span&&span class=&mi&&103&/span&&span class=&o&&)&/span&
&span class=&n&&at&/span& &span class=&n&&jcheckbook&/span&&span class=&o&&.&/span&&span class=&na&&JCheckbook&/span&&span class=&o&&.&/span&&span class=&na&&readPreferences&/span&&span class=&o&&(&/span&&span class=&n&&JCheckbook&/span&&span class=&o&&.&/span&&span class=&na&&java&/span&&span class=&o&&:&/span&&span class=&mi&&225&/span&&span class=&o&&)&/span&
&span class=&n&&at&/span& &span class=&n&&jcheckbook&/span&&span class=&o&&.&/span&&span class=&na&&JCheckbook&/span&&span class=&o&&.&/span&&span class=&na&&startup&/span&&span class=&o&&(&/span&&span class=&n&&JCheckbook&/span&&span class=&o&&.&/span&&span class=&na&&java&/span&&span class=&o&&:&/span&&span class=&mi&&116&/span&&span class=&o&&)&/span&
&span class=&n&&at&/span& &span class=&n&&jcheckbook&/span&&span class=&o&&.&/span&&span class=&na&&JCheckbook&/span&&span class=&o&&.&&/span&&span class=&n&&init&/span&&span class=&o&&&(&/span&&span class=&n&&JCheckbook&/span&&span class=&o&&.&/span&&span class=&na&&java&/span&&span class=&o&&:&/span&&span class=&mi&&27&/span&&span class=&o&&)&/span&
&span class=&n&&at&/span& &span class=&n&&jcheckbook&/span&&span class=&o&&.&/span&&span class=&na&&JCheckbook&/span&&span class=&o&&.&/span&&span class=&na&&main&/span&&span class=&o&&(&/span&&span class=&n&&JCheckbook&/span&&span class=&o&&.&/span&&span class=&na&&java&/span&&span class=&o&&:&/span&&span class=&mi&&318&/span&&span class=&o&&)&/span&
&/code&&/pre&&/div&&br&&p&以 上展示了FileInputStream类的open()方法抛出NullPointerException的情况。不过注意 FileInputStream.close()是标准Java类库的一部分,很可能导致这个异常的问题原因在于我们的代码本身而不是Java API。所以问题很可能出现在前面的其中一个方法,幸好它也在堆栈信息中打印出来了。&/p&&p&不幸的是,NullPointerException是Java中信息量最少的(却也是最常遭遇且让人崩溃的)异常。它压根不提我们最关心的事情:到底哪里是null。所以我们不得不回退几步去找哪里出了错。&/p&&p&通过逐步回退跟踪堆栈信息并检查代码,我们可以确定错误原因是向readPreferences()传入了一个空文件名参数。既然readPreferences()知道它不能处理空文件名,所以马上检查该条件:&/p&&br&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&kd&&public&/span& &span class=&kt&&void&/span& &span class=&nf&&readPreferences&/span&&span class=&o&&(&/span&&span class=&n&&String&/span& &span class=&n&&filename&/span&&span class=&o&&)&/span&
&span class=&kd&&throws&/span& &span class=&n&&IllegalArgumentException&/span&&span class=&o&&{&/span&
&span class=&k&&if&/span& &span class=&o&&(&/span&&span class=&n&&filename&/span& &span class=&o&&==&/span& &span class=&kc&&null&/span&&span class=&o&&){&/span&
&span class=&k&&throw&/span& &span class=&k&&new&/span& &span class=&n&&IllegalArgumentException&/span&&span class=&o&&(&/span&&span class=&s&&&filename is null&&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&span class=&c1&&//if&/span&
&span class=&c1&&//...perform other operations...&/span&
&span class=&n&&InputStream&/span& &span class=&n&&in&/span& &span class=&o&&=&/span& &span class=&k&&new&/span& &span class=&n&&FileInputStream&/span&&span class=&o&&(&/span&&span class=&n&&filename&/span&&span class=&o&&);&/span&
&span class=&c1&&//...read the preferences file...&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&br&&p&通过提早抛出异常(又称"迅速失败"),异常得以清晰又准确。堆栈信息立即反映出什么出了错(提供了非法参数值),为什么出错(文件名不能为空值),以及哪里出的错(readPreferences()的前部分)。这样我们的堆栈信息就能如实提供:&/p&&br&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&n&&java&/span&&span class=&o&&.&/span&&span class=&na&&lang&/span&&span class=&o&&.&/span&&span class=&na&&IllegalArgumentException&/span&&span class=&o&&:&/span& &span class=&n&&filename&/span& &span class=&n&&is&/span& &span class=&kc&&null&/span&
&span class=&n&&at&/span& &span class=&n&&jcheckbook&/span&&span class=&o&&.&/span&&span class=&na&&JCheckbook&/span&&span class=&o&&.&/span&&span class=&na&&readPreferences&/span&&span class=&o&&(&/span&&span class=&n&&JCheckbook&/span&&span class=&o&&.&/span&&span class=&na&&java&/span&&span class=&o&&:&/span&&span class=&mi&&207&/span&&span class=&o&&)&/span&
&span class=&n&&at&/span& &span class=&n&&jcheckbook&/span&&span class=&o&&.&/span&&span class=&na&&JCheckbook&/span&&span class=&o&&.&/span&&span class=&na&&startup&/span&&span class=&o&&(&/span&&span class=&n&&JCheckbook&/span&&span class=&o&&.&/span&&span class=&na&&java&/span&&span class=&o&&:&/span&&span class=&mi&&116&/span&&span class=&o&&)&/span&
&span class=&n&&at&/span& &span class=&n&&jcheckbook&/span&&span class=&o&&.&/span&&span class=&na&&JCheckbook&/span&&span class=&o&&.&&/span&&span class=&n&&init&/span&&span class=&o&&&(&/span&&span class=&n&&JCheckbook&/span&&span class=&o&&.&/span&&span class=&na&&java&/span&&span class=&o&&:&/span&&span class=&mi&&27&/span&&span class=&o&&)&/span&
&span class=&n&&at&/span& &span class=&n&&jcheckbook&/span&&span class=&o&&.&/span&&span class=&na&&JCheckbook&/span&&span class=&o&&.&/span&&span class=&na&&main&/span&&span class=&o&&(&/span&&span class=&n&&JCheckbook&/span&&span class=&o&&.&/span&&span class=&na&&java&/span&&span class=&o&&:&/span&&span class=&mi&&318&/span&&span class=&o&&)&/span&
&/code&&/pre&&/div&&p&另外,其中包含的异常信息("文件名为空")通过明确回答什么为空这一问题使得异常提供的信息更加丰富,而这一答案是我们之前代码中抛出的NullPointerException所无法提供的。&/p&&p&通过在检测到错误时立刻抛出异常来实现迅速失败,可以有效避免不必要的对象构造或资源占用,比如文件或网络连接。同样,打开这些资源所带来的清理操作也可以省却。&/p&&p&&strong&延迟捕获&/strong&&br&菜鸟和高手都可能犯的一个错是,在程序有能力处理异常之前就捕获它。Java编译器通过要求检查出的异常必须被捕获或抛出而间接助长了这种行为。自然而然的做法就是立即将代码用try块包装起来,并使用catch捕获异常,以免编译器报错。&/p&&p&问 题在于,捕获之后该拿异常怎么办?最不该做的就是什么都不做。空的catch块等于把整个异常丢进黑洞,能够说明何时何处为何出错的所有信息都会永远丢失。把异常写到日志中还稍微好点,至少还有记录可查。但我们总不能指望用户去阅读或者理解日志文件和异常信息。让readPreferences()显示错误信息对话框也不合适,因为虽然JCheckbook目前是桌面应用程序,但我们还计划将它变成基于HTML的Web应用。那样的话,显示错误对话框显然不是个选择。同时,不管HTML还是C/S版本,配置信息都是在服务器上读取的,而错误信息需要显示给Web浏览器或者客户端程序。 readPreferences()应当在设计时将这些未来需求也考虑在内。适当分离用户界面代码和程序逻辑可以提高我们代码的可重用性。&/p&&p&在有条件处理异常之前过早捕获它,通常会导致更严重的错误和其他异常。例如,如果上文的readPreferences()方法在调用FileInputStream构造方法时立即捕获和记录可能抛出的FileNotFoundException,代码会变成下面这样:&/p&&br&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&kd&&public&/span& &span class=&kt&&void&/span& &span class=&nf&&readPreferences&/span&&span class=&o&&(&/span&&span class=&n&&String&/span& &span class=&n&&filename&/span&&span class=&o&&){&/span&
&span class=&c1&&//...&/span&
&span class=&n&&InputStream&/span& &span class=&n&&in&/span& &span class=&o&&=&/span& &span class=&kc&&null&/span&&span class=&o&&;&/span&
&span class=&c1&&// DO NOT DO THIS!!!&/span&
&span class=&k&&try&/span&&span class=&o&&{&/span&
&span class=&n&&in&/span& &span class=&o&&=&/span& &span class=&k&&new&/span& &span class=&n&&FileInputStream&/span&&span class=&o&&(&/span&&span class=&n&&filename&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&span class=&k&&catch&/span& &span class=&o&&(&/span&&span class=&n&&FileNotFoundException&/span& &span class=&n&&e&/span&&span class=&o&&){&/span&
&span class=&n&&logger&/span&&span class=&o&&.&/span&&span class=&na&&log&/span&&span class=&o&&(&/span&&span class=&n&&e&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&span class=&n&&in&/span&&span class=&o&&.&/span&&span class=&na&&read&/span&&span class=&o&&(...);&/span&
&span class=&c1&&//...&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&br&&p&上 面的代码在完全没有能力从FileNotFoundException中恢复过来的情况下就捕获了它。如果文件无法找到,下面的方法显然无法读取它。如果 readPreferences()被要求读取不存在的文件时会发生什么情况?当然,FileNotFoundException会被记录下来,如果我们 当时去看日志文件的话,就会知道。然而当程序尝试从文件中读取数据时会发生什么?既然文件不存在,变量in就是空的,一个 NullPointerException就会被抛出。&/p&&p&调试程序时,本能告诉我们要看日志最后面的信息。那将会是NullPointerException,非常让人讨厌的是这个异常非常不具体。错误信息不仅误导我们什么出了错(真正的错误是FileNotFoundException而不是NullPointerException),还误导了错误的出处。真正 的问题出在抛出NullPointerException处的数行之外,这之间有可能存在好几次方法的调用和类的销毁。我们的注意力被这条小鱼从真正的错误处吸引了过来,一直到我们往回看日志才能发现问题的源头。&/p&&p&既然readPreferences() 真正应该做的事情不是捕获这些异常,那应该是什么?看起来有点有悖常理,通常最合适的做法其实是什么都不做,不要马上捕获异常。把责任交给 readPreferences()的调用者,让它来研究处理配置文件缺失的恰当方法,它有可能会提示用户指定其他文件,或者使用默认值,实在不行的话也 许警告用户并退出程序。&/p&&p&把异常处理的责任往调用链的上游传递的办法,就是在方法的throws子句声明异常。在声明可能抛出的异常时,注意越具体越好。这用于标识出调用你方法的程序需要知晓并且准备处理的异常类型。例如,“延迟捕获”版本的readPreferences()可能是这样的:&/p&&br&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&kd&&public&/span& &span class=&kt&&void&/span& &span class=&nf&&readPreferences&/span&&span class=&o&&(&/span&&span class=&n&&String&/span& &span class=&n&&filename&/span&&span class=&o&&)&/span&
&span class=&kd&&throws&/span& &span class=&n&&IllegalArgumentException&/span&&span class=&o&&,&/span&
&span class=&n&&FileNotFoundException&/span&&span class=&o&&,&/span& &span class=&n&&IOException&/span&&span class=&o&&{&/span&
&span class=&k&&if&/span& &span class=&o&&(&/span&&span class=&n&&filename&/span& &span class=&o&&==&/span& &span class=&kc&&null&/span&&span class=&o&&){&/span&
&span class=&k&&throw&/span& &span class=&k&&new&/span& &span class=&n&&IllegalArgumentException&/span&&span class=&o&&(&/span&&span class=&s&&&filename is null&&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&span class=&c1&&//if&/span&
&span class=&c1&&//...&/span&
&span class=&n&&InputStream&/span& &span class=&n&&in&/span& &span class=&o&&=&/span& &span class=&k&&new&/span& &span class=&n&&FileInputStream&/span&&span class=&o&&(&/span&&span class=&n&&filename&/span&&span class=&o&&);&/span&
&span class=&c1&&//...&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&br&&p&技 术上来说,我们唯一需要声明的异常是IOException,但我们明确声明了方法可能抛出FileNotFoundException。 IllegalArgumentException不是必须声明的,因为它是非检查性异常(即RuntimeException的子类)。然而声明它是为 了文档化我们的代码(这些异常也应该在方法的JavaDocs中标注出来)。&/p&&p&当 然,最终你的程序需要捕获异常,否则会意外终止。但这里的技巧是在合适的层面捕获异常,以便你的程序要么可以从异常中有意义地恢复并继续下去,而不导致更 深入的错误;要么能够为用户提供明确的信息,包括引导他们从错误中恢复过来。如果你的方法无法胜任,那么就不要处理异常,把它留到后面捕获和在恰当的层面处理。&br&&strong&结论&/strong&&br&经验丰富的开发人员都知道,调试程序的最大难点不在于修复缺陷,而在于从海量的代码中找出缺陷的藏身之处。只要遵循本文的三个原则,就能让你的异常协助你跟踪和消灭缺陷,使你的程序更加健壮,对用户更加友好。&/p&&p&英文原文:&a href=&//link.zhihu.com/?target=http%3A//today.java.net/pub/a/today//exceptions.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Jim Cushing&/a&,编译:&a href=&//link.zhihu.com/?target=http%3A//www.importnew.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&ImportNew&/a& - &a href=&//link.zhihu.com/?target=http%3A//www.importnew.com/author/zhengwei& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&郑玮&/a&&/p&
这篇文章应该可以解答你的疑问。Java中异常提供了一种识别及响应错误情况的一致性机制,有效地异常处理能使程序更加健壮、易于调试。异常之所以是一种强大的调试手段,在于其回答了以下三个问题:什么出了错?在哪出的错?为什么出错?在有效使用异常的情况下…
&figure&&img src=&https://pic3.zhimg.com/v2-ae36dfce0bff_b.jpg& data-rawwidth=&588& data-rawheight=&396& class=&origin_image zh-lightbox-thumb& width=&588& data-original=&https://pic3.zhimg.com/v2-ae36dfce0bff_r.jpg&&&/figure&&p&今天来实现一个简单的贪吃蛇应用,效果如下:&/p&&figure&&img src=&http://pic4.zhimg.com/v2-dcd0f22b5ffe6ce4f7c5bf_b.jpg& data-rawwidth=&354& data-rawheight=&277& class=&content_image& width=&354&&&/figure&&p&在网上能找到不少贪吃蛇的代码,但是往往写得比较乱,甚至有所有代码都包含在一个类中的情况,对于初学者而言即使能Copy后跑起来,也不一定能够真正理解代码的逻辑。&/p&&p&实际上实现贪吃蛇的代码并不复杂,如果尝试去给出优雅地实现,比如写出具有清晰的类结构,有助于真正提高大家程序设计的基本功。&/p&&p&此外,应该让代码具有良好的扩展性,将来你希望更新你的贪吃蛇应用时,比如:&/p&&ul&&li&让贪吃蛇显示出不同的样子&/li&&li&增加或者修改积分规则&/li&&/ul&&p&应该要做到修改尽量少的代码。实际的应用都是不断演化的,良好的设计能够让应用更易于维护。&/p&&p&所以贪吃蛇应用非常适合入门Java编程的同学,可以了解用面向对象的方式来编程解决问题,学习如何设计类,如何选择数据结构以及Java Swing的基础知识。详细的完整课程见&a href=&http://link.zhihu.com/?target=https%3A//course.tianmaying.com/java-snake& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Java贪吃蛇的设计与实现&/a&。&/p&&h2&开始设计&/h2&&p&Java是一门面向对象语言,一个Java程序就是一系列对象(Object)的集合,对象通过方法调用来彼此协作完成特定的功能。面向对象是一种非常符合人类思维的编程方法,因为现实世界就是由对象和对象之间的交互来构成的,所以我们其实很容易将现实世界映射到软件开发中。其实我们可以把Java语言当成是一门普通的语言,学习英语是为了与世界交流,而学习Java就是与计算机交流。我们需要把自己的思维,通过Java语言表达出来,让计算机理解。&/p&&p&那现在我们怎么用Java,用面向对象的思维,来表达出贪吃蛇这个游戏呢?&/p&&p&贪吃蛇游戏的规则无需多言,我们马上能想到两个对象,一条蛇和一个棋盘,我们可以定义两个类:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public class Snake {
&/code&&/pre&&/div&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public class Grid {
&/code&&/pre&&/div&&p&棋盘里有一条蛇,这其实就是棋盘和蛇的关系,所以可以给棋盘定义一个成员变量,类型为Snake,Grid的代码变为:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public class Grid {
&/code&&/pre&&/div&&p&Grid还有长度和宽度等属性,可以建立构造函数。面向对象的知识大家可以复习&a href=&http://link.zhihu.com/?target=https%3A//course.tianmaying.com/java-basic%2Bobject-orientation-basics& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Java面向对象基础&/a&。&/p&&p&用面向对象建模语言UML来表达这两个类的关系如下:&/p&&p&我们要创建的是一个窗体应用,整个负责与用户交互的窗体,可以设计一个类来表示:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public class SnakeApp {
&/code&&/pre&&/div&&p&这些类内部定义基本还没有,不过没关系,在练习过程中就会慢慢充实起来。&/p&&h2&表达虚拟概念的类&/h2&&p&刚接触面向对象编程的同学,从现实世界往Java世界做对象映射往往不是什么问题,因为比较直观。比如一个人和一张桌子,对应地设计一个对应的类即可。&/p&&p&其实一个系统用Java语言来表达的话,往往要设计一些表达虚拟概念的类。将来大家学习到更高级的面向对象设计知识,比如&a href=&http://link.zhihu.com/?target=https%3A//www.tianmaying.com/search%3Fkey%3D%25E8%25AE%25BE%25E8%25AE%25A1%25E6%25A8%25A1%25E5%25BC%258F& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&设计模式&/a&,就会发现其实这些表达虚拟概念的类才往往是设计一个优秀系统的关键。&/p&&p&SnakeApp作为一个窗体应用,会接收到用户的输入(比如控制贪吃蛇方向的按键操作),需要展示当前游戏的界面和状态。而Grid则需要随机生成食物,维护着贪吃蛇的状态。那么Grid就要根据SnakeApp中的用户交互来控制游戏状态,因为我们可以设计一个GameController来表示这种控制。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public class GameController {
&/code&&/pre&&/div&&p&GameController的职责在于接收窗体SnakeApp传递过来的有意义的事件(比如用户改变方向),然后传递给Grid,让Grid即时地更新状态,同时根据最新状态渲染出游戏界面让SnakeApp显示。&/p&&p&总体的设计图如下:&/p&&p&上面的设计其实是一个典型的MVC模式,&a href=&http://link.zhihu.com/?target=https%3A//zh.wikipedia.org/wiki/mvc& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&MVC模式(Model-View-Controller)&/a&是&a href=&http://link.zhihu.com/?target=https%3A//zh.wikipedia.org/wiki/%25E8%25BD%25AF%25E4%25BB%25B6%25E5%25B7%25A5%25E7%25A8%258B& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&软件工程&/a&中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller):&/p&&ul&&li&Controller——负责转发请求,对请求进行处理:对应于GameController&/li&&li&View——负责界面显示,对应于SnakeApp&/li&&li&Model——业务功能编写(例如算法实现)、数据库设计以及数据存取操作实现,对应于Grid和Snake&/li&&/ul&&p&将来大家学习Java Web开发,也会接触到&a href=&http://link.zhihu.com/?target=https%3A//course.tianmaying.com/spring-mvc& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Spring MVC&/a&。当然对于贪吃蛇游戏最终的类设计并非如此,这只是一个最初的概览,后面我们不仅仅会充实类,而且会增加一些新的类。&/p&&h2&贪吃蛇的方向&/h2&&p&接下来考虑贪吃蛇的行进方向问题。贪吃蛇行进的方向可以为上下左右。一种常见的做法是定义一个包含静态常量的类或者接口,比如:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&class Direction {
public static final UP = 0;
public static final RIGHT = 1;
public static final DOWN = 2;
public static final LEFT = 3;
&/code&&/pre&&/div&&p&这是一种典型的取值范围在一个有限的数据集中的场景,这种场景有一种更好的处理方式:枚举(即Enum)。类似的场景还有比如一周包含从星期一到星期日7个取值。&/p&&p&Enum本质上是一种特殊的类,可以有更多丰富的操作,相比使用静态常量而言功能更加强大,而且具有更好的维护性。&/p&&h2&使用枚举定义Direction&/h2&&p&通过枚举来定义方向的代码如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&/**
* 贪吃蛇前进的方向
public enum Direction {
&/code&&/pre&&/div&&p&相比前面的代码简洁了许多。&/p&&p&其实UP、RIGHT等枚举值默认就是public、[static](&a href=&http://link.zhihu.com/?target=https%3A//course.tianmaying.com/java-basic%2Bstatic-variable-and-method& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Java入门基础教程&/a&)和[final](&a href=&http://link.zhihu.com/?target=https%3A//course.tianmaying.com/java-basic%2Bdata-types%237& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Java入门基础教程&/a&)的。&/p&&h2&枚举的使用&/h2&&p&枚举最典型的使用场景就是Switch语句,比如根据贪吃蛇移动的方法来变化它的坐标位置:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&switch (direction) {
// 向上移动
case RIGHT:
// 向右移动
case DOWN:
// 向下移动
case LEFT:
// 向左移动
&/code&&/pre&&/div&&p&我们也可以遍历一个枚举的所有取值,如:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&for (Direction direction: Direction.values()) {
System.out.println(direction);
&/code&&/pre&&/div&&h2&给枚举添加成员变量、方法和构造函数&/h2&&p&方向有时需要进行运算,因此赋予一定的值操作起来会更加方便,比如判断两个方向是否相邻。&/p&&p&这里我们给Direction中的每一个取值关联一个整数值。这时需要给枚举添加成员变量、方法和构造函数了。我们说过,Enum是一种特殊的Class,所以做这些事情毫无压力。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&/**
* 贪吃蛇前进的方向
public enum Direction {
// 成员变量
private final int directionC
// 成员方法
public int directionCode() {
return directionC
// 构造函数
Direction(int directionCode) {
this.directionCode = directionC
&/code&&/pre&&/div&&p&上面的代码添加了一个私有的成员directionCode作为方向的整数代码,在后面的编码中你会看到这样的代码对于运算的话会非常方便。&/p&&p&成员方法directionCode()使得外部可以访问到方向的整数代码,比如:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&int code = Direction.UP.directionCode();
&/code&&/pre&&/div&&p&增加成员变量后,构造函数就需要传入一个代码参数进行初始化。注意枚举的构造函数不能用Public修饰,否则在外部也能创建新的枚举值不是就会乱套了。&/p&&p&这时枚举的定义就可以调用新的构造函数了,传入一个整数值来初始化directionCode,比如UP(0)就表示向上的方向的整数代码为0。&/p&&h2&如何设计一个类&/h2&&p&在总体设计中我们给出了几个类,构成了应用的整体概览。具体到每一个类,则需要我们继续去定义其内部结构。&/p&&p&设计一个类时,往往还要考虑它的接口和继承层次,这里我们暂时无需考虑。简单地理解,一个类的内部无外乎两部分:&/p&&ul&&li&成员变量:一个类操作的数据和内容应该被定义为成员变量,这些成员变量共同构成了一个对象的状态。&/li&&li&成员方法:公有方法就是这个类提供给外部世界的接口,系统中的其他类可以通过公有方法来操作这个类的数据,因此需要考虑这个类的职责和功能,从而确定公有方法。私有方法则一般为公有方法的辅助方法,供内部调用。&/li&&/ul&&p&现在我们来考虑如何编写Snake类。&/p&&h2&设计成员变量&/h2&&p&一条贪吃蛇是由一个一个的节点组成的,在传统的贪吃蛇应用中这个节点通常展示为一个黑色的小方块。所以我们需要选择一种数据结构来表示这些相互连接的节点。不过在这之前,需要先定义出节点这个东西。&/p&&p&显然,表示节点状态的就是它的X坐标和Y坐标,那么我们通过一个类来定义节点:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&package com.tianmaying.
public class Node {
public Node(int x, int y) {
public int getX() {
public int getY() {
&/code&&/pre&&/div&&p&成员变量x和y构成了一个Node的状态。注意这两个成员变量使用final修饰了,表示进行初始赋值之后就不能改变。&/p&&h2&选择数据结构&/h2&&p&为了表示相互连接在一起的节点,我们可以为Snake定义一个集合类型的成员变量,让集合来保存所有节点。&/p&&p&常用的集合类包括Map、 List和Set,这里显然List是比较适合的,它提供了一系列操作一个元素序列的方法。&/p&&p&结合我们自己的应用场景可以发现,贪吃蛇不断变长小经常做插入操作,而且我们不需要随机去访问贪吃蛇中的某一个节点。因此选择LinkedList。&/p&&p&有了这个思考过程,接下来Snake的成员变量就很清晰了:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&package com.tianmaying.
import java.util.LinkedL
public class Snake {
private LinkedList&Node& body = new LinkedList&&();
&/code&&/pre&&/div&&h2&设计Snake方法&/h2&&p&Snake应该提供什么方法来操作自己的状态呢?贪吃蛇有两种情况下下会有状态的变化,一种是吃到食物的时候, 一种就是做了一次移动的时候。&/p&&p&此外,贪吃蛇也需要定一些查询自己状态和信息的公有方法。比如获取贪吃蛇的头部,获取贪吃蛇的body,对应可以加入这些方法。&/p&&p&一开始可能定义的方法不够完整,没关系,在编码过程中你会很自然地发现需要Snake提供更多方法来完成特定功能,这个时候你再添加即可。&/p&&p&把这些方法加入进去之后,Snake的代码看起来就丰富多了:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&package com.tianmaying.
import java.util.LinkedL
public class Snake {
private LinkedList&Node& body = new LinkedList&&();
public Node eat(Node food) {
// 如果food与头部相邻,则将food这个Node加入到body中,返回food
// 否则不做任何操作,返回null
public Node move(Direction direction) {
// 根据方向更新贪吃蛇的body
// 返回移动之前的尾部Node
public Node getHead() {
return body.getFirst();
public Node addTail(Node area) {
this.body.addLast(area);
public LinkedList&Node& getBody() {
&/code&&/pre&&/div&&p&eat和move方法都给出了详细的处理流程,你自己来尝试一下吧。&/p&&p&这里简单解释一下贪吃蛇移动一格的处理。第一感觉是让body中每个Node的坐标都改变一次,这是一个很笨的o(n)的做法,其实只需要在头部增加一个Node,尾部删除一个Node即可。&/p&&h2&定义意义明确的私有方法&/h2&&p&一般情况下类中的每个方法不应该做太多的事情,体现在代码量上就是一个方法不要包含太多的代码。&/p&&p&一种最简单也是非常有用的方法就是提取出意义明确的私有方法,这样会让代码更加易懂,调试和维护都会更加方便。&/p&&p&大家可以对比一下下面两种写法:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
public Node eat(Node food) {
if (Math.abs(a.getX() - b.getX()) + Math.abs(a.getY() - b.getY()) == 1) {
// 相邻情况下的处理
&/code&&/pre&&/div&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
public Node eat(Node food) {
if (isNeighbor(body.getFirst(), food)) {
// 相邻情况下的处理
private boolean isNeighbor(Node a, Node b) {
return Math.abs(a.getX() - b.getX()) + Math.abs(a.getY() - b.getY()) == 1;
&/code&&/pre&&/div&&p&我们推崇第二种写法,将节点相邻判断的逻辑提取到一个新的方法中,阅读eat()方法的代码时,一眼就知道if语句块要处理的问题。而第一种情况下,时间长了,你可能会一时想不起来这个长长的条件语句用来干嘛的了。如果你说可以加注释的话,那么你想想让方法命名本身就成为有意义的“注释”是不是一种更好的方式呢?&/p&&h2&Grid的数据成员&/h2&&p&你现在的Grid代码应该是这个样子:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&package com.tianmaying.
import java.util.A
public class Grid {
public Grid(int width, int height) {
this.width =
this.height =
&/code&&/pre&&/div&&p&显然这样成员变量是不足以表达一个棋盘的所有状态的,还需要以下信息:&/p&&ul&&li&棋盘的方格是否被贪吃蛇覆盖&/li&&li&食物的位置在哪个方格&/li&&li&贪吃蛇目前的移动方向&/li&&/ul&&p&一个Grid创建后,它的长宽就是固定不变了,方格的覆盖可以用一个boolean类型的二维数组来表示,如果一个Node被贪吃蛇覆盖,则对应坐标的数组元素为true,否则为false。&/p&&p&为了表达信息后,Grid需要增加一些成员变量:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public class Grid {
public final boolean status[][];
// 初始方向默认设置为向左
private Direction snakeDirection = Direction.LEFT;
&/code&&/pre&&/div&&h2&Grid的构造函数&/h2&&p&创建一个棋盘时,需要做一些必要的初始化工作,比如:&/p&&ul&&li&根据width和height初始化二维数组&/li&&li&初始化一条贪吃蛇&/li&&li&初始化食物&/li&&/ul&&p&这些工作都可以在构造函数中完成,构造函数就是用来初始化一个类的地方。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public Grid(int width, int height) {
this.width =
this.height =
status = new boolean[width][height];
initSnake();
createFood();
&/code&&/pre&&/div&&p&接下来看initSnake()和createFood()如何实现。&/p&&h2&关键方法:初始化贪吃蛇&/h2&&p&我们可以根据棋盘大小来创建一只大小合适的贪吃蛇,并将其放置在棋盘的某些位置。&/p&&p&我们设定的规则如下:&/p&&ul&&li&贪吃蛇的长度为棋盘宽度的三分之一&/li&&li&贪吃蛇为水平放置,即包含的所有Node的Y坐标相同,Y坐标为棋盘垂直中间位置(即height / 2),最左边的X为棋盘水平中间位置(即width / 2)&/li&&/ul&&p&所有initSnake()的代码逻辑如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&private Snake initSnake() {
snake = new Snake();
// 设置Snake的Body
// 更新棋盘覆盖状态
&/code&&/pre&&/div&&h2&关键方法:随机创建食物&/h2&&p&随机创建食物,即随机生成食物的X坐标和Y坐标。我们可以使用Java提供的Random类来生成随机数。&/p&&p&这里需要注意两点:&/p&&ul&&li&生成的X坐标和Y坐标必须在有效的范围之内,不能超过棋盘大小&/li&&li&食物的位置不能喝贪吃蛇的位置重叠&/li&&/ul&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public Node createFood() {
// 使用Random设置x和y
food = new Node(x, y);
&/code&&/pre&&/div&&h2&关键方法:一次移动&/h2&&p&在Sanke的move方法中,我们只是让贪吃蛇进行移动,移动方向是否有效以及移动后游戏能否继续并没有判断,我们把这些逻辑都放到Grid类的实现中,由Grid类来驱动Snake的move操作,Snake只管执行命令即可。&/p&&p&每一次移动可以认为是游戏的下一步,因此我们将这个函数定义为nextRound()。&/p&&p&如何移动后能够继续,返回true,否则返回false。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public boolean nextRound() {
按当前方向移动贪吃蛇
if (头部的位置是否有效) {
if (头部原来是食物) {
把原来move操作时删除的尾部添加回来
创建一个新的食物
更新棋盘状态并返回游戏是否结束的标志
&/code&&/pre&&/div&&p&头部位置无效有两种情况:&/p&&ul&&li&碰到边界&/li&&li&碰到自己&/li&&/ul&&p&吃到食物时,食物添加到原来的头部,贪吃蛇身长+1,所以之前move操作删除的尾部添加回来就是最新的贪吃蛇状态了,而之前的实现中Snake.move()操作已经给我们返回尾部的Node了。&/p&&p&同时Grid需要提供一个外部修改贪吃蛇行进方向的方法,如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public void changeDirection(Direction newDirection) {
if (snakeDirection.compatibleWith(newDirection)) {
snakeDirection = newD
&/code&&/pre&&/div&&p&这个方法将来在处理用户的键盘输入时需要用到。我们之前实现的Direction.compatibleWith()方法在这个时候派上用场了。&/p&&h2&应用界面&/h2&&p&编写完Grid和Snake之后,我们开始考虑应用的界面展示。棋盘和贪吃蛇要在一个窗口中显示,需要使用Java Swing编程的知识。&/p&&p&Swing 是一个为Java提供的GUI(Graphics User Interface,图形化界面)编程工具包,是J2SE类库中的一部分,它包含了诸如文本框和按钮等一系列GUI组件。&/p&&p&Swing编程是一个比较大的主题,这里我们只介绍能够实现贪吃蛇效果的必要知识。此外,Java Swing编程目前来说也不能说是应用非常广泛的技术(比如相比Java Web开发),如果只是练习Java基础,了解一些基本原理和常用组件的用法即可。&/p&&p&我们提到过&a href=&http://link.zhihu.com/?target=https%3A//zh.wikipedia.org/wiki/mvc& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&MVC模式(Model-View-Controller)&/a&。下面要实现的就是View了。这部分做完之后,你应该可以看到一条贪吃蛇静静地躺在棋盘上。&/p&&h2&一个简单的Swing程序&/h2&&p&SnakeApp是我们希望用来实现界面的类,我们也将其作为整个应用初始化的地方。&/p&&p&下面是创建一个窗体的典型代码:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&// 创建JFrame
JFrame window = new JFrame(&天码营贪吃蛇游戏&);
// 设置窗口大小
window.setPreferredSize(new Dimension(200, 200));
// 往窗口中添加组件
JLabel label = new JLabel(&欢迎访问tianmaying.com&);
window.getContentPane().add(label);
// 设置窗口为大小不可变化
window.setResizable(false);
// 窗口关闭的行为
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 渲染和显示窗口
window.pack();
window.setVisible(true);
&/code&&/pre&&/div&&p&JFrame: GUI应用的窗口对象,能够最大化、最小化和关闭,它是一个容器,允许添加其他组件,并将它们组织起来呈现给用户。&/p&&p&默认情况下,关闭窗口,只隐藏界面,不释放占用的内存,window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);表示关闭窗口时直接关闭应用程序,相当于调用System.exit(0)。&/p&&p&SnakeApp的实现&/p&&p&了解了如何创建一个GUI程序之后,我们可以在SnakeApp中实现一个init()函数骨架了:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&package com.tianmaying.
import javax.swing.*;
public class SnakeApp {
public void init() {
//创建游戏窗体
JFrame window = new JFrame(&天码营贪吃蛇游戏&);
// 画出棋盘和贪吃蛇
window.pack();
window.setResizable(false);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
public static void main(String[] args) {
SnakeApp snakeApp = new SnakeApp();
snakeApp.init();
&/code&&/pre&&/div&&p&这样运行出来的窗体是空的,如何画出棋盘和贪吃蛇呢?这需要使用GraphicsAPI了。&/p&&h2&Graphics API&/h2&&p&这里有一个官方的&a href=&http://link.zhihu.com/?target=https%3A//docs.oracle.com/javase/tutorial/2d/basic2d/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Graphics API基础教程&/a&。&/p&&p&从这个教程中,你可以知道Graphics可以帮助我们画出各种图形和图像。&/p&&p&分析第一节中展示的界面,其实只包含了两种元素:圆形和矩形。食物是一个圆形,棋盘的背景是一个大矩形,蛇是由多个小矩形组成的。&/p&&p&那让我们了解一下如何画矩形和圆形吧。&/p&&ul&&li&&p&画一个实体的圆形,可以使用fillOval(int x,int y,int width,int height)方法,它用预定的颜色填充的椭圆形,当横轴和纵轴相等时,所画的椭圆形即为圆形。&/p&&/li&&li&&p&画一个实体的矩形,可以使用fillRect(int x,int y,int width,int height)方法,它用预定的颜色填充一个矩形。&/p&&/li&&/ul&&p&为了使用Graphics API画图,我们创建一个新类GameView来做这件事情:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&package com.tianmaying.
import javax.swing.*;
import java.awt.*;
public class GameView {
private final G
public GameView(Grid grid) {
this.grid =
public void draw(Graphics graphics) {
drawGridBackground(graphics);
drawSnake(graphics, grid.getSnake());
drawFood(graphics, grid.getFood());
public void drawSnake(Graphics graphics, Snake snake) {
public void drawFood(Graphics graphics, Node squareArea) {
public void drawGridBackground(Graphics graphics) {
&/code&&/pre&&/div&&p&可以看到在GameView的draw()方法中,分别去画背景、贪吃蛇和食物即可,画这些东西的时候,就需要使用fillOval和fillRect方法了。这里可以实现两个私有的辅助类:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
private void drawSquare(Graphics graphics, Node squareArea, Color color) {
graphics.setColor(color);
int size = Settings.DEFAULT_NODE_SIZE;
graphics.fillRect(squareArea.getX() * size, squareArea.getY() * size, size - 1, size - 1);
private void drawCircle(Graphics graphics, Node squareArea, Color color) {
graphics.setColor(color);
int size = Settings.DEFAULT_NODE_SIZE;
graphics.fillOval(squareArea.getX() * size, squareArea.getY() * size, size, size);
&/code&&/pre&&/div&&p&基于drawSquare()和drawCircle()就能很容易地画出界面了。&/p&&h2&在窗口中显示界面&/h2&&p&知道了如何通过Graphics画界面之后,我们还面临一个问题,如何显示在JFrame中。&/p&&p&这就是使用JPanel了,它也是一种容器类,可以加入到JFrame窗体中,而且它具有一个接口:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public void paintComponent(Graphics graphics);
&/code&&/pre&&/div&&p&在这个接口中可以拿到当前面板的Graphics实例,基于之前介绍的API就能画图了,我们按照如下方式修改GameView的代码:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&package com.tianmaying.
import javax.swing.*;
import java.awt.*;
public class GameView {
private JP
public void init() {
canvas = new JPanel() {
public void paintComponent(Graphics graphics) {
drawGridBackground(graphics);
drawSnake(graphics, grid.getSnake());
drawFood(graphics, grid.getFood());
public void draw() {
canvas.repaint();
public JPanel getCanvas() {
&/code&&/pre&&/div&&p&这部分代码需要着重解释一下,因为涉及到一种回调和匿名类几个概念。&/p&&ul&&li&GameView新增了一个JPanel类型的成员变量canvas&/li&&li&新增了一个init()方法用以初始化canvas&/li&&li&原来的draw(Graphics graphics)方法改为了draw(),此时不需要传入参数,只需调用canvas的repaint()方法即可。因为JPanel的repaint()方法可以自动刷新界面&/li&&li&原来的draw(Graphics graphics)实现代码移到public void paintComponent(Graphics graphics)方法的内部了,只要放进去即可,Swing会在合适的时机去调用这个方法,展示出合适的界面,这就是典型的回调(callback)的概念。&/li&&/ul&&p&再来分析一下下面这个代码:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&canvas = new JPanel() {
public void paintComponent(Graphics graphics) {
drawGridBackground(graphics);
drawSnake(graphics, grid.getSnake());
drawFood(graphics, grid.getFood());
&/code&&/pre&&/div&&p&这段代码其实等价于创建一个CanvasPanel(任何合法的命名都可以),然后在GameView中使用。&/p&&p&因为这个CanvasPanel仅仅在这里使用一次,我们就可以使用匿名类的方式,现场定义现场使用用完即走,就有了这种写法。对这样的代码了然于心的时候,说明你已经有不错的Java编程经验啦。&/p&&p&最后,在SankeApp中,只需要将这个JPanel添加到JFrame中就行了。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
public void init() {
// 初始化grid
JFrame window = new JFrame(&天码营贪吃蛇游戏&);
Container contentPane = window.getContentPane();
// 基于Grid初始化gamaView
gameView = new GameView(grid);
gameView.init();
// 设置gameView中JPanel的大小
gameView.getCanvas().setPreferredSize(new Dimension(Settings.DEFAULT_GRID_WIDTH, Settings.DEFAULT_GRID_HEIGHT));
// 将gameView中JPanel加入到窗口中
contentPane.add(gameView.getCanvas(), BorderLayout.CENTER);
window.pack();
&/code&&/pre&&/div&&p&好了,一条呆萌的贪吃蛇已经静静躺在漆黑一片的棋盘中了。&/p&&figure&&img src=&http://pic3.zhimg.com/v2-9e0371ecb0c13b74197bdc9f7f72dd0e_b.png& data-rawwidth=&900& data-rawheight=&644& class=&origin_image zh-lightbox-thumb& width=&900& data-original=&http://pic3.zhimg.com/v2-9e0371ecb0c13b74197bdc9f7f72dd0e_r.png&&&/figure&&br&&h2&GameController的作用&/h2&&p&你已经可以根据一个Grid画出来游戏界面了,接下来就要开始处理用户的按键输入了。&/p&&p&还记得总体设计概览图吗? 我们已经实现了大部分的类,也增加了一些新的类,现在应该是这个样子了:&/p&&p&这里要再次提高MVC模式,系统可以分为三个部分,模型(Model)、视图(View)和控制器(Controller):&/p&&ul&&li&Model:业务功能、核心数据结构与算法,对应蓝色部分&/li&&li&View:负责界面显示,对应黄色部分&/li&&li&Controller:负责转发用户操作事件,对事件进行处理,对应红色部分&/li&&/ul&&p&模型和视图已经基本完成了,我们在界面中画出了贪吃蛇以及它的食物,现在,让我们学习如何通过键盘

我要回帖

更多关于 微信小程序js遍历数组 的文章

 

随机推荐