【问题标题】:Unable to reference object attributes无法引用对象属性
【发布时间】:2021-12-15 00:37:08
【问题描述】:

关于堆栈溢出的第一个问题! 我觉得这个必须是 Java 初学者的常见问题。但我已经尝试了几个小时,但一直无法找到解决方案。我认为对象属性可以通过这种方式访问​​。

一开始我以为weapon[0]实际上是一个对象数组,所以当我创建对象数组Inventory[] inventory时,我在构造函数中使用了一个对象数组。我立即解决了这个问题,但这个问题仍然存在。

这更令人讨厌,因为在调试模式下,我可以从字面上看到weapon[0] 内部的Inventory[] inventory 及其属性。 Look at Eclipse mock me.

我目前的理论是,将对象weapon[0](类Weapons 的实例)放在对象数组Inventory[] inventory 中可能是问题所在,并且由于该位置,无法以某种方式访问​​对象的属性。任何帮助将不胜感激,谢谢!这是我第一次搞乱数组,所以我绝对是个新手。关于我的格式等的任何提示也将非常有帮助!

package arraytest;

import java.util.Scanner;
import java.util.InputMismatchException;
import java.lang.NumberFormatException;

public class ArrayTest {
    
    static Scanner kb = new Scanner(System.in);
    
    static int i = 0;
    static int choice = 0;
    
    public static void main(String[] args) {
        
        Weapons[] weapon = new Weapons[3];
        weapon[0] = new Weapons(0,"Wooden Sword",1,2);
        weapon[1] = new Weapons(1,"Bronze Sword",2.5,7.5);
        weapon[2] = new Weapons(2,"Iron Sword",5,10);
        
        Armor[] armor = new Armor[3];
        armor[0] = new Armor(3,"Wooden Armor",2,5);
        armor[1] = new Armor(4,"Bronze Armor",3,10);
        armor[2] = new Armor(5,"Iron Armor",5,15);
        
        Enemy[] enemy = new Enemy[3];
        enemy[0] = new Enemy(0,"Skeleton",3,0,10);
        enemy[1] = new Enemy(1,"Goblin",2,1,5);
        enemy[2] = new Enemy(2,"Zombie",4,1,8);
        
        Inventory[] inventory = new Inventory[256];
        
        String chooseweapon = String.format(
                "Choose your weapon:\n"
                + "1. %s\n"
                + "2. %s\n"
                + "3. %s\n"
                ,
                weapon[0].name,
                weapon[1].name,
                weapon[2].name
                );
        System.out.print(chooseweapon);
        
        while (i==0) {
            i++; //1
            
            try {
                choice = Integer.parseInt(kb.nextLine());
            } catch (NumberFormatException e) {
                System.out.println("Error. Try again.");
                i--; //0
                continue;
            }
            if (choice < 1 || choice > 3) {
                System.out.println("Error. Try again.");
                i--; //0
            }
        }
        if (choice == 1) {
            inventory[0] = new Inventory(weapon[0]);
        }
        System.out.println(inventory[0].item); //this is the problem here. i can't put .item.name, error is "name cannot be resolved or is not a field"
    }
}



class Inventory {
    
    public Object item;
    Inventory(Object item) {
        this.item = item;
        
    }
}

class Armor {
    public int id;
    public String name;
    public double defnum;
    public double val;
    
    Armor(int id, String name, double defnum, double val) {
        this.id = id;
        this.name = name;
        this.defnum = defnum;
        this.val = val;
    }
}

class Weapons {
    public int id;
    public String name;
    public double attdmg;
    public double val;
    
    Weapons(int id, String name, double attdmg, double val) {
        this.id = id;
        this.name = name;
        this.attdmg = attdmg;
        this.val = val;
    }
}

class Enemy {
    public int id;
    public String name;
    public double attdmg;
    public double defnum;
    public double health;
    
    Enemy(int id, String name, double attdmg, double defnum, double health) {
        this.id = id;
        this.name = name;
        this.attdmg = attdmg;
        this.defnum = defnum;
        this.health = health;
    }
}

【问题讨论】:

  • 您的实例变量item 的类型为Object,而该类型根本没有name 字段。因此,您可以专门化类型或将表达式 inventory[0].item 转换为 Weapon

标签: java arrays object attributes


【解决方案1】:

欢迎来到 StackOverflow!这是一个措辞恰当的问题!

这与运行时类型与编译时类型之间的差异有关。您将 item 声明为 Object 类型。

Java 允许多态性,当您将Inventory 中的item 声明为Object 类型时,允许您将任何内容分配给“is-a”的item Object(这意味着您可以将StringInteger、任何对象分配给item,因为它们都继承自Object 类)。

但是,当您稍后在程序中访问item 时,Java 无法在编译时保证无论item 引用的任何内容都具有name 属性! Integer,例如,是一个 Object,但没有有一个name 属性! Java 编译器只是说,“我只知道item 是一个Object,我不会让你访问并非所有Object 都有的属性!”。

但是,当您运行程序时,itemruntime 类型是 Weapon,因此 Eclipse 能够向您显示它的属性。但是 Java 被设计为在编译时捕获尽可能多的错误,因此如果它不能在编译时保证它有名称,它将不允许您访问 name 属性。

这可能看起来很烦人或不必要的限制(你知道所有要放入Inventory的东西都会有一个名字!),所以这就是重点超类和接口!这些特性让您可以灵活地创建共享相似属性或方法的不同类型的对象,并且仍然允许 Java 提前发现所有这些潜在问题。

要解决此问题,您可以创建一个 InventoryItem 超类,它同时扩展了 ArmorWeapon,它具有 name 属性。然后您可以将item 声明为InventoryItem 类型。这样,Java 就会知道,即使 runtime-type 可能是 WeaponArmor,它保证有一个名称。

我们可以引入一个新的类,比如InventoryItem

class Inventory {

    public InventoryItem item;
    public int id;

    Inventory(InventoryItem item, int id) {
        this.item = item;
        this.id = id;
    }
}

然后Inventory 类可以使用InventoryItem(我建议库存可能包含项目数组)

class Inventory {

    public InventoryItem item;

    Inventory(InventoryItem item) {
        this.item = item;
    }

然后你的类,比如Armor,可以扩展InventoryItem,例如:

class Armor extends InventoryItem {

    public double defnum;
    public double val;

    Armor(int id, String name, double defnum, double val) {
        super(name, id);
        this.defnum = defnum;
        this.val = val;
    }
}

然后这将起作用!

System.out.println(inventory[0].item.name); //Java now knows that inventory[0] is type Inventory, its "item" property is type InventoryItem, and that is *guaranteed* to have a name and id!

【讨论】:

  • 由于子类也有共同的 id 字段,因此将它也放入超类可能是有意义的。
  • 好点@Seelenvirtuose,已编辑。
  • 感谢您的建议!它工作得相当好,尽管超类InventoryItem 需要具有扩展它的类的所有参数/属性,以便稍后调用这些属性,这很烦人。我试图避免这种乏味,因为我有一种预感,这就是解决方案,但它比我想象的要简单得多。
  • @GiavonniPapenbrock 不客气!理论上,您可以在访问元素时将其转换为 Weapon 类型,前提是您知道该对象将始终是一个武器,但这是糟糕的设计,就好像它碰巧不是武器一样,它将在运行时失败并出现 ClassCastException。您可能正在寻找的是所谓的“鸭子类型”,而 Java 根本不支持它(除了反射,我也不推荐在这里)。 JS 或 Python 等动态类型语言为您提供了这种灵活性,但不能保证在运行时不会发生某些崩溃。
  • @Giogification 我应该提到一件事:我相信,像 Scala 这样的语言可以让您通过 structural typing 来做您正在寻找的事情,在那里您可以本质上声明一个对象必须具有此名称属性,并且任何具有该属性的类型都是可接受的。例如。 devopedia.org/images/article/24/7477.1514520259.jpg