public class Library {
int Capacity = 10;
int Volume = 0;
Book[] storage = new Book[10];
public Library() {
System.out.println("Hello, I am a library, which can store up to 10 books!");
this.storage = new Book[10];
}
public void add(Book book) {
if (this.Volume < this.Capacity) {
this.storage[this.Volume] = book;
System.out.println("I added the book " + book + "!");
this.Volume++;
} else if (this.Volume >= this.Capacity) System.out.println("The library is full!");
}
public Book search(String title) {
for (int i = 0; i < this.Volume; i++) {
if (title.equals(this.storage[i].toString())) {
System.out.println("The book with the title " + title + " exists in the library!");
return this.storage[i];
}
}
System.out.println("The book with the title " + title + " does not exist in the library!");
return null;
}
}
public class Book {
String title;
public Book(String title){
this.title = title;
System.out.println("Book " + title + " created.");}
public String toString(){
return this.title;
};
}
#1 楼
命名Java具有您目前不遵循的命名约定。
int Capacity = 10;
int Volume = 0;
您应该具有:
Capacity
-> capacity
和Volume
-> volume
。我真的建议您在学习语言时阅读约定。它可以帮助读者快速扫描代码。可见性
当前,您的类中的字段是程序包私有的,这意味着包中的每个类都可以访问您的字段。建议您在班级中的字段中使用
private
,直到您确定需要其他内容为止。 您真的想为变量设置最小范围。
初始化化
您要进行两次初始化。首先,当您声明
Book[] storage = new Book[10];
时,将用它创建10本书的数组。接下来在构造函数中,执行:this.storage = new Book[10];
。这与以前的操作相同,因此您应该删除其中之一。魔术值
魔术值是常量,没有命名,但可以直接在代码中使用。在您的情况下,您的库具有默认大小,但是它是硬编码的。您可能会有:
private static final int DEFAULT_SIZE = 10
。这样便可以清楚说明其用途。括号和代码块
我更喜欢在代码块中始终使用括号,我发现它更易于阅读,不太容易更改代码时会出现bug,而实际上更改代码时更改却很少(想像Git这样的源代码控制)。 >
} else if (this.Volume >= this.Capacity) System.out.println("The library is full!");
对于每个循环
这是您确实要经常使用的东西。它们以多种语言存在,当您遍历一个集合并希望访问每个对象时,它们将使阅读起来更加容易。当您不需要知道集合在哪里(索引)时,最好使用for-each。
因此,您将拥有:
有人在评论中指出我的原始
for-each
循环存在错误。现在,我对其进行了修复,它确实不那么优雅了。将有重复,并将新的特殊情况添加到该方法。这些东西使方法变得更加复杂。部分原因是因为您有一个array[]
而不是和ArrayList
之类的东西(如果使用List
,则始终使用ArrayList
接口)。这已经在其他答案中得到了解决,因此,如果更改为更具动态性的结构,请使用for-each
,否则请继续使用for
循环,但请记住for-each
存在。评论
\ $ \ begingroup \ $
感谢@Timothy Truckle提供的链接。我也可以使用google,这也很好。我是故意含糊的,但是链接也很好。
\ $ \ endgroup \ $
–马克·安德烈(Marc-Andre)
17年5月11日在17:03
\ $ \ begingroup \ $
For每个零件都不正确。只有第一个Volume元素为非null,因此如果找不到书名,您将获得NPE
\ $ \ endgroup \ $
– Clashsoft
17年5月12日在5:08
\ $ \ begingroup \ $
我现在没有时间解决问题,今天一定会解决。
\ $ \ endgroup \ $
–马克·安德烈(Marc-Andre)
17年5月12日在13:11
\ $ \ begingroup \ $
最后一个代码块仍然存在一个错误:如果库已满,则不会打印任何内容。我个人更希望从0到音量进行迭代,因为这是我们要迭代的实际范围。否则,请检查是否为空并中断。
\ $ \ endgroup \ $
–内森·美林(Nathan Merrill)
17年5月13日在1:32
\ $ \ begingroup \ $
如果库已满,它将使用该方法的其余部分。我只是在谈论循环而不是方法。否则我不明白你说什么。
\ $ \ endgroup \ $
–马克·安德烈(Marc-Andre)
17年5月13日在1:34
#2 楼
toString
在Java中具有特殊含义:通常,toString方法返回一个“以文本形式表示”该对象的字符串。结果应该是简洁但内容丰富的表示形式,便于人们阅读。
我不希望书名“代表”它-毕竟,可能是其他同名书籍。相反,常见的模式是对字段使用名称
getTitle
(“ getter”)。库的容量和书本的长度没有链接。您不应在两个地方存储此限制。如果有人更改了其中一个数字,则您的代码会立即出现错误-它会尝试存储过多的书,或者报告没有错误时会留下空格。而是将数组的长度绑定到实际容量(
new Book[Capacity]
)或使用类似ArrayList
的类型,而不必使用长度声明。评论
\ $ \ begingroup \ $
我认为OP的意思是“图书馆”是书籍的图书馆,而不是标准意义上的图书馆。
\ $ \ endgroup \ $
–塔莫格纳·乔杜里(Tamoghna Chowdhury)
17年5月13日在6:19
\ $ \ begingroup \ $
@TamoghnaChowdhury谢谢您,修复了驾驶建议。
\ $ \ endgroup \ $
–l0b0
17年5月13日在7:36
#3 楼
java.util.Optional<T>
简短建议,因为尚未有人提出。未找到要搜索的书时,不要返回
null
或抛出异常,而应使用Optional
。在Java中,方法是否会返回null
从来不是100%清楚的,也许有人会认为这不会,因此他们将无法避免null
的空条件。如果返回if (foundBook != null) { ... }
,则非常清楚地表明结果的内容可能存在或可能不存在。他们仍然可能会处理错误,但是至少您可以肯定他们知道发生了什么。我反对当书不存在时抛出一些异常的原因是:
如果抛出一个检查的异常,则此方法的每个调用都需要用
Optional
包装。已检查的异常很难处理。如果抛出未检查的异常,那么您也会遇到
try { Book b = search("blah") } catch (SomeException e) { ... }
的问题,也许人们没有意识到这可能发生。因此,他们不会将其包装在try块中。评论
\ $ \ begingroup \ $
我不知道Java有可选的东西,但是我在Swift中看到了类似的东西。我喜欢! :-D如果您愿意,可以提供一个如何对此进行测试的示例吗?
\ $ \ endgroup \ $
– Holroy
17年5月11日在22:34
\ $ \ begingroup \ $
当然可以,但我不知道你的意思。喜欢检查是否存在?
\ $ \ endgroup \ $
–曼上尉
17年5月12日在0:18
#4 楼
List您可以用java.util.List替换Volume和storage。
List storage = new ArrayList();
List将跟踪其容量并随着需要。
您仍然可以通过这种方式检查最大图书数量:
if (storage.size() < Capacity)
如果不是
可以将“ else if”替换为更简单的“ else”。我也不喜欢缩进的变化,更希望看到行被一分为二。
this
在Java中,通常仅在以下情况下使用此关键字:存在某种歧义。在大多数情况下,不需要使用它,我认为这会使代码更难阅读。
toString用于调试
toString()方法用于用于调试输出,您不应在代码中赋予特殊含义。如果要比较标题,请仅参考标题,如果要在书上进行常规比较,请使用equals / hashcode方法对(请参见此处)。
System.out
对于生产代码,应避免使用System.out并改用日志记录库。
像您想的那样编写
这是一个很细微的地方:在这行中
if (title.equals(this.storage[i].title)) {
我将切换角色:
if (this.storage[i].title.equals(title)) {
这没什么区别对于编译器,但对人类来说却是很大的不同:在这里,我正在检查书籍,而不是书名。我正在寻找一本书,其标题等于给定的标题,并且这种“推理”应该反映在代码中。这看起来更好:
book = storage[i]
if (book.title.equals(title)) {
文件
通常最好将每个类放在自己的文件中(并且对于公共类是强制性的) ,我想这是从两个文件中剪切并粘贴的。)
混合内容
这三行交织在一起:您开始处理数据存储,然后暂停并移动为用户记录一些内容,然后再次返回以完成数据更新:
this.storage[this.Volume] = book;
System.out.println("I added the book " + book + "!");
this.Volume++;
这种混合更容易出错,代码更难阅读,重构也更难(例如,在单独的方法中移动一些相关行)。
隐藏的行为
add方法不好:调用者无法(合理)知道这本书是否被添加。在这些情况下,您应该返回一个布尔值以指示其是否成功。
null vs结果
返回null没什么错误,但是可以改进。这里的例外情况是不合适的,因为我认为寻找丢失的书是正常的,不是例外。您可以在此处使用SearchResult类。
class SearchResult {
boolean found;
String reason;
}
最大的优点是,您可以更好地跟踪搜索失败的原因,并将原因设置为提供的标题。在此示例中,显然会过度设计,但是在许多情况下,这是一个不错的选择。
代码格式
代码格式不一致:请勿手动格式化,请选择优秀的编辑器(IntellJ,Eclipse等),并使其在每次保存时都为您重新格式化代码。
尽快登录
在Book构造函数中:
public Book(String title){
this.title = title;
System.out.println("Book " + title + " created.");}
我会将println放在开头:通常,最好在开始处理传递的数据之前先记录传递的数据,这样,如果出现故障,日志将对您有所帮助。在这种情况下,处理最少,但不是一个好习惯。在这种情况下,您将编写一些与“正在创建书...”类似的内容。
评论
\ $ \ begingroup \ $
为什么不使用内置的java.util.Optional
\ $ \ endgroup \ $
–曼上尉
17年5月11日在20:34
\ $ \ begingroup \ $
可选不允许添加任何其他信息。它仅允许以标准方式延迟/重定位“空检查”,并明确表明“空”是可能的结果。但是如果以后您想知道搜索失败的原因(调试,记录失败的搜索等),则可选项不会增加太多。
\ $ \ endgroup \ $
–洛伦佐
17年5月11日在21:01
\ $ \ begingroup \ $
没错,但是简单的事情不需要做任何额外的工作。如果全班有书本清单或某些已签出的东西,那么我会同意,因为您可以说它已被签出或未被携带。
\ $ \ endgroup \ $
–曼上尉
17年5月12日在0:17
\ $ \ begingroup \ $
如果要用List
\ $ \ endgroup \ $
–大卫·康拉德(David Conrad)
17年5月12日在3:56
\ $ \ begingroup \ $
@CaptainMan当然,这是一个简单的练习,所以一切都过头了。但是仅输入字符串就足够保留了。假设您正在使用两个正则表达式按标题和作者查找一本书,并且它们是从GUI动态提供的。我发现它对数据库查询很有用:“没有带有搜索参数的实体Xy的结果”。也许输入字符串在某处“损坏”,等等。
\ $ \ endgroup \ $
–洛伦佐
17年5月12日在7:26
#5 楼
在此处进行了一些更改:从构造函数中删除了重新初始化,因为您已经
进行了初始化。
删除了
if (this.Volume >= this.Capacity)
,这已经由else属性持有。将您的迭代周期更改为foreach周期,它更具可读性和易用性。
在
;
之后删除了}
public class Library {
int Capacity = 10;
int Volume = 0;
Book[] storage = new Book[10];
public Library() {
System.out.println("Hello, I am a library, which can store up to 10 books!");
}
public void add(Book book) {
if (this.Volume < this.Capacity) {
this.storage[this.Volume] = book;
System.out.println("I added the book " + book + "!");
this.Volume++;
} else System.out.println("The library is full!");
}
public Book search(String title) {
for (Book book: storage) {
if (title.equals(book.toString())) {
System.out.println("The book with the title " + title + " exists in the library!");
return book;
}
}
System.out.println("The book with the title " + title + " does not exist in the library!");
return null;
}
}
class Book {
String title;
public Book(String title) {
this.title = title;
System.out.println("Book " + title + " created.");
}
public String toString() {
return this.title;
}
}
评论
\ $ \ begingroup \ $
不错的编辑!感谢您改善答案!
\ $ \ endgroup \ $
–马克·安德烈(Marc-Andre)
17年5月11日在15:08
#6 楼
正如已经指出的那样,使用动态数据结构会更好,但是无论如何这都是一个练习,因此让我们撇开考虑因素,接受具有预定义大小的静态解决方案。int Capacity = 10;
Book[] storage = new Book[10];
除了命名之外,在两个地方使用10也不好。您可能要更改一天中的第一次出现,而忘记更改另一天。
final static int capacity = 10;
Book[] storage = new Book[capacity];
评论
\ $ \ begingroup \ $
作为常数,容量应为大写。
\ $ \ endgroup \ $
–user131519
17年5月29日晚上8:56
#7 楼
我将重复其他人所说的内容,并添加一些非常重要的知识,并且没有人提及(出于已知原因)。命名约定
您应该使用定义的命名约定Java语言规范JLS 6.1中的内容。声明(从小写的“命名约定”开始)。 JLS是一段时间后您将要熟悉的东西,尽管它是相当技术性的,所以现在就不用担心了。
您将要重命名为小写
capacity
和volume
。使用已声明值
如果要使用
capacity
中保留的数字,请改用字段引用。然后,您只需要在一个位置上更改数字,就可以更清楚地知道代码中数字的来源(魔术数字)。您在数组初始化(执行两次)和在数组初始化中使用它打印语句。
常量与变量
确定所声明的值中的哪些是常量,哪些可以更改。不能更改的应该声明为
final
。如果要为每个实例允许使用不同的值,请考虑在构造函数中设置这些常量。capacity
是否对每个Library
实例而言都是常量?每个title
的Book
是否恒定?如果是这样,请将其声明为final
。也许像在capacity
中一样在构造函数中请求title
值。封装
使用可见性修饰符控制对对象的字段和方法的访问。这允许每个对象控制其行为并公开其服务。像这里一样,在线搜索封装的好处。
看起来所有字段都应该是
private
。如果您希望班级以外的人能够读写它们,请使用getter和/或setter。重写
Object
的方法这个非常重要!实际上,Brian Goetz(Java架构师)最近谈到了这个问题,他说开发人员如何不这样做,为什么以及为什么要这么做。
您很好地重写了
Book
的toString
。您将真的要覆盖其equals
和hashCode
。您的IDE具有为您生成这些方法的工具。您还可以查看有助于减少样板代码的Lombok项目。强烈建议在覆盖方法时使用
@Override
注释。this
的用法通常,您不要不想尽可能使用
this
,但是要尽可能少地使用它。关于这一点的意见不一(无双关语)。return this.title;
可以是return title;
,它更具可读性。与Library
用法相同。流控制
在使用
if
和else
时,请检查逻辑,以了解指定流量的最佳方法。先检查
if (volume < capacity)
,然后再检查else if (volume >= capacity)
,但是如果第一次检查失败,然后第二次必须成功,因此不需要-简单的else
即可。“ foreach”循环
当遍历索引不重要的元素时,应使用“ foreach”(或“ enhanced for”)循环。
for (int i = 0; i < volume; i++)
可能是for (Book book : storage)
。当心
equals
NPE陷阱我的IDE生成的
equals
方法会检查null
,但请熟悉Objects
类及其方法(在本例中为static equals
方法)为您检查null
。请注意,如果使用o1.equals(o2)
,则o1 == null
将抛出NPE,但是如果使用o2 == null
,则不会抛出NPE,这正是上述方法很方便的地方。如果有人将
null
字符串传递给您的search
方法,您将获得一个NPE。考虑适当的返回类型
哪种返回类型最适合操作?
您的
add
方法可能会返回boolean
以指定是否添加了这本书。 Java集合也执行此操作(请参见下文)。考虑使用
Optional<T>
...,但不要过度使用它。这是Java架构师的又一次演讲的话题,这次是Stuart Marks。我不会全部介绍,但是在返回
null
时要当心。您目前所拥有的完全可以接受。您的
search
可以返回,而Optional<Book>
可以返回,但我不会打扰它。记录您的方法,并说如果找不到匹配项,它将返回null
。考虑使用集合
Java的集合比数组有很多好处。再次,这个巨大的话题,在线搜索。
如果可以使用
List<Book>
而不是数组,则完全不需要volume
字段即可受益-而是拥有list.size()
。这样可以简化您的add
方法。如果您要将List
方法用作返回值,则boolean
也会为其add
方法返回一个for
。考虑使用流
Java 8的流允许轻松执行批量操作,并且它们更具可读性。在线搜索流相对于循环的优势。
您的
;
/“ foreach”循环可以替换为可以一次性搜索并找到结果的流。请参见下面的代码。Nitpick
方法声明的末尾不需要
toString
。Book
的Book
带有该代码。代码时间!
Library
:public class Book {
private final String title;
public Book(String title) {
this.title = title;
System.out.println("Book " + title + " created.");
}
public String getTitle() {
return title;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((title == null) ? 0 : title.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Book other = (Book) obj;
if (title == null) {
if (other.title != null)
return false;
} else if (!title.equals(other.title))
return false;
return true;
}
@Override
public String toString() {
return this.title;
}
}
List
带有数组(您可以自己玩q4312079q):public class Library {
private final int capacity;
private int volume = 0;
private Book[] storage;
public Library(int capacity) {
this.capacity = capacity;
storage = new Book[capacity];
System.out.println("Hello, I am a library, which can store up to " + capacity + " books!");
}
public void add(Book book) {
if (volume < capacity) {
storage[volume] = book;
System.out.println("I added the book " + book + "!");
volume++;
} else
System.out.println("The library is full!");
}
public Book search(String title) {
for (Book book : storage) {
if (Objects.equals(title, book.getTitle())) {
System.out.println("The book with the title " + title + " exists in the library!");
return book;
}
}
System.out.println("The book with the title " + title + " does not exist in the library!");
return null;
//or with streams: returns an Optional<Book>
return Arrays.stream(storage)
.filter(book -> Objects.equals(title, book.getTitle()))
.findAny();
}
}
#8 楼
感谢您分享代码。以下是我的一些附加想法:
面向对象编程
Java是一种面向对象的编程语言。
但是使用Java并不意味着您正在做OOP。
OOP意味着您遵循某些原则(以及其他原则):
信息隐藏/封装
单一责任/关注点分离
/>相同级别的抽象
依赖注入/控制反转
吻(保持简单(和愚蠢)。)
(不要重复自己。)
针对接口(不是实现)
“告诉!不要问。”
Demeter的法则(“不要与陌生人聊天!”)
在分支上支持多态性
其中大部分原则不适用于您的小型程序。
但是我认为您应该尽快熟悉这些术语。
另一方面,您的代码中有一些违反该原则的行为。 @ Marc-Andre已经指出了一些问题。
这里还有更多
单一责任/关注点分离
您的每种方法班级应该完全负责一项明确的职责。
例如构造函数的责任始终是初始化(
final
)对象变量。 (在对象的生存期内不更改其内容的变量应使用final
关键字。)通常通过将其参数分配给那些对象变量来实现此目的(就像在
Book
类中所做的那样) 。因此构造函数不应执行任何输出。
有时在查找错误时很有用,但应尽快从构造函数中删除输出行。
相同抽象级别
方法可以对变量(赋值,访问,计算)或调用方法(在同一类中或在其他类(也称为依赖项)的对象)上进行原始操作。后一种方法称为组合方法。
您的方法应同时使用其中一种。
例如:您的方法
search
应该更改为以下内容:public Book search(String title) {
for (int i = 0; i < this.Volume; i++) {
Book currentBook = getBookAt(i);
if (title.equals(currentBook.toString())) {
reportFound(title);
return currentBook;
}
}
reportNotFound(title);
return null;
}
private void reportFound(String title) {
System.out.println("The book with the title " + title + " exists in the library!");
}
private void reportNotFound(String title) {
System.out.println("The book with the title " + title + " does not exist in the library!");
}
private Book getBookAt(int index){
return this.storage[index];
}
不要害怕许多小的方法。当您当前的班级变大时,它们使您可以将其移至其他(新)班级。十亿美元的错误
在少数情况下,
null
是结果集中的有效元素,但我看到的大多数情况下,null
都被用作(错误)作为错误信号。而不是返回
null
,您应该引发异常。为此,您可以创建一个自定义的接收器,例如
null
:public class BookNotInLibraryException extends Exception{
public BookNotInLibraryException(String bookTitle){
super("There is no book with title >" + bookTitle + "< in the Library!");
}
}
public Book search(String title) thows BookNotInLibraryException {
for (int i = 0; i < this.Volume; i++) {
Book currentBook = getBookAt(i);
if (title.equals(currentBook.toString())) {
reportFound(title);
return currentBook;
}
}
throw new BookNotInLibraryException (title);
}
也可以更改调用此方法的位置:
// somewhere
Library lib = new Library();
try{
Book bookFromLibrary = lib.search("Some Book Title");
// do something with the book, it is never null here
} catch (BookNotInLibraryException e){
System.err.println("error processing boook: "+e);
}
评论
\ $ \ begingroup \ $
Java 8引入了Optional类型,它是null的更干净的替代品。这样做的好处是,它会在没有结果的情况下强制呼叫者处理案件。除例外外,这也是强制执行的,但是try / catch对控制流非常有害。应该为例外情况(即错误)保留例外,而不是代表结果集的一部分。
\ $ \ endgroup \ $
– MauganRa
17年5月11日在19:42
\ $ \ begingroup \ $
“例外情况应保留例外”库中缺少的一本书是例外情况恕我直言...
\ $ \ endgroup \ $
–提莫西·卡特勒(Timothy Truckle)
17年5月11日19:52
\ $ \ begingroup \ $
@TimothyTruckle我想大多数人会不同意您对此的看法
\ $ \ endgroup \ $
–曼上尉
17年5月11日在20:36
\ $ \ begingroup \ $
为失败的搜索抛出NullPointerException是一个可怕的主意,它违反了最小惊讶原则和NPE的约定,其内容为:“当应用程序在需要对象的情况下尝试使用null时抛出。”您可以抛出IllegalArgumentException或NoSuchElementException,但这只会使您的API难以使用。
\ $ \ endgroup \ $
–大卫·康拉德(David Conrad)
17年5月12日下午4:00
\ $ \ begingroup \ $
那么您是否会更改示例代码以反映这一点?
\ $ \ endgroup \ $
–塔莫格纳·乔杜里(Tamoghna Chowdhury)
17年5月13日在6:23
评论
您对“库”一词的使用不是开发人员所说的库。这意味着您已经编写了一组可供其他程序员使用的有用的类。您实际编写的是一个代表真实库的Java类。看起来还不错。除其他答案外,还有一个附加内容:体积可能会引起误解,因为书籍可以按“体积”编号。更好的名称是currentAmountOfBooks。当前的前缀imo非常重要,因为它可以说明很多有关该变量的信息。
您的两个评论都可以称为答案!不要犹豫,发布并获得代表:)!