为了学习,我用Java编写了这个基本库。也许有人可以看到我可以做得更好的事情,因为我刚开始使用Java并且渴望了解更多。

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;
    };
}


评论

您对“库”一词的使用不是开发人员所说的库。这意味着您已经编写了一组可供其他程序员使用的有用的类。您实际编写的是一个代表真实库的Java类。

看起来还不错。除其他答案外,还有一个附加内容:体积可能会引起误解,因为书籍可以按“体积”编号。更好的名称是currentAmountOfBooks。当前的前缀imo非常重要,因为它可以说明很多有关该变量的信息。

您的两个评论都可以称为答案!不要犹豫,发布并获得代表:)!

#1 楼

命名

Java具有您目前不遵循的命名约定。

int Capacity = 10;
int Volume = 0;


您应该具有:Capacity-> capacityVolume-> 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 类而不是某些自定义SearchResult?
\ $ \ endgroup \ $
–曼上尉
17年5月11日在20:34

\ $ \ begingroup \ $
可选不允许添加任何其他信息。它仅允许以标准方式延迟/重定位“空检查”,并明确表明“空”是可能的结果。但是如果以后您想知道搜索失败的原因(调试,记录失败的搜索等),则可选项不会增加太多。
\ $ \ endgroup \ $
–洛伦佐
17年5月11日在21:01

\ $ \ begingroup \ $
没错,但是简单的事情不需要做任何额外的工作。如果全班有书本清单或某些已签出的东西,那么我会同意,因为您可以说它已被签出或未被携带。
\ $ \ endgroup \ $
–曼上尉
17年5月12日在0:17

\ $ \ begingroup \ $
如果要用List 替换数组,最好用Map 从标题到书替换它,否则搜索方法将不必要O(n)搜索。
\ $ \ 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是一段时间后您将要熟悉的东西,尽管它是相当技术性的,所以现在就不用担心了。
您将要重命名为小写capacityvolume
使用已声明值
如果要使用capacity中保留的数字,请改用字段引用。然后,您只需要在一个位置上更改数字,就可以更清楚地知道代码中数字的来源(魔术数字)。
您在数组初始化(执行两次)和在数组初始化中使用它打印语句。
常量与变量
确定所声明的值中的哪些是常量,哪些可以更改。不能更改的应该声明为final。如果要为每个实例允许使用不同的值,请考虑在构造函数中设置这些常量。
capacity是否对每个Library实例而言都是常量?每个titleBook是否恒定?如果是这样,请将其声明为final。也许像在capacity中一样在构造函数中请求title值。
封装
使用可见性修饰符控制对对象的字段和方法的访问。这允许每个对象控制其行为并公开其服务。像这里一样,在线搜索封装的好处。
看起来所有字段都应该是private。如果您希望班级以外的人能够读写它们,请使用getter和/或setter。
重写Object的方法
这个非常重要!实际上,Brian Goetz(Java架构师)最近谈到了这个问题,他说开发人员如何不这样做,为什么以及为什么要这么做。
您很好地重写了BooktoString。您将真的要覆盖其equalshashCode。您的IDE具有为您生成这些方法的工具。您还可以查看有助于减少样板代码的Lombok项目。
强烈建议在覆盖方法时使用@Override注释。
this的用法

通常,您不要不想尽可能使用this,但是要尽可能少地使用它。关于这一点的意见不一(无双关语)。
return this.title;可以是return title;,它更具可读性。与Library用法相同。
流控制
在使用ifelse时,请检查逻辑,以了解指定流量的最佳方法。
先检查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
BookBook带有该代码。

代码时间!
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