在构建容器时,为什么使用 Java 泛型比使用对象 Class 更好? (Java 泛型和数据结构)
When building containers why is using Java Generics better than using the Object Class? (Java Generics & DataStructures)
所以我一直在审查我的数据结构,并发现了一个关于 Java 泛型和 Object
class 的有趣想法。我已经 运行 以两种不同的方式实现了一个“通用包” (下面的注意事项:IObjectBag.java、ObjectBag.java、IGenericBag.java 和 GenericBag.java) 并同时使用了它们 (注意:低于 main.java 和输出)。我已经根据堆栈溢出规则删除了一些不必要的代码,但如果您想要完整的实现,请告诉我。
此外,除了查看 ArrayList
class here 的源代码外,我还在许多网站、书籍和课程中研究了这个主题,我明白我的 GenericBag
比我的 ObjectBag
更好,但不足以在面试中以实际的方式解释它。我很困惑,我的 GenericBag
在其实现中使用了比 ObjectBag
更多的转换操作(参见 Remove
和 PrintBag
)。
所以,除了语法糖,为什么我的 GenericBag
更好?请使用我的 classes 作为示例。
runtime/overhead/space/time 有什么我没有注意到的重要区别吗?
你会如何回答这个问题或希望在面试中得到回答?
奖金问题:如果你愿意,请在Main
和GenericBag
评论中回答奖金问题(我想我可以自己回答,只是想听听你的意见)。
IObjectBag
接口:
public interface IObjectBag {
void add(Object item);
Object remove(Object item) throws NoSuchElementException;
boolean isEmpty();
int find(Object item);
Object get(int index);
int numItems();
}
ObjectBag
class:
public class ObjectBag implements IObjectBag {
private Object [] items; // the java class attribute that will hold out "ints"
private int numItems;
public static void printBag(IObjectBag bag) {
for(int i = 0; i < bag.numItems(); i++) {
System.out.println(bag.get(i));
}
}
public ObjectBag(int size) {
this.items = new Object[size]; // fills array with null values
this.numItems = 0;
}
public void add(Object item){
// adds item to end of bag
}
public Object remove(Object item) {
int index = this.find(item);
if(index == -1) throw new NoSuchElementException("oops nothing found");
Object out = this.items[index];
this.items[index] = null;
this.numItems -= 1;
if(index + 1 != this.items.length && this.items[index + 1] != null) {
for(int i = index; i < this.items.length; i++) {
if(i + 1 != this.items.length) this.items[i] = this.items[i + 1];
}
this.items[this.items.length - 1] = null;
}
return out;
}
public int find(Object item) {
// return index given item or -1
}
public Object get(int index) {
// returns item given index
}
}
IGenericBag
class:
public interface IGenericBag <T> {
void add(T item);
T remove(T item) throws NoSuchElementException;
boolean isEmpty();
int find(T item);
T get(int index);
}
GenericBag
class:
public class GenericBag<T> implements IGenericBag<T> {
// private T[] items; can't use this b/c see comment in constructor
private Object[] items;
private int numItems;
public static void printBag(GenericBag bag) {
for(int i = 0; i < bag.numItems(); i++) {
System.out.println(bag.get(i));
}
}
public GenericBag(int size) {
// this.items = new T[size]; Bonus: throws generic array creation error (why?)
this.items = new Object[size];
this.numItems = 0;
}
public void add(T item){
this.items[this.numItems] = item;
this.numItems += 1;
}
public T remove(T item) {
int index = this.find(item);
if(index == -1) throw new NoSuchElementException("oops nothing found");
T out = (T) this.items[index];
this.items[index] = null;
this.numItems -= 1;
if(index + 1 != this.items.length && this.items[index + 1] != null) {
for(int i = index; i < this.items.length; i++) {
if(i + 1 != this.items.length) this.items[i] = this.items[i + 1];
}
this.items[this.items.length - 1] = null;
}
return out;
}
public int find(Object item) {
// given object return index or throw exception
}
public T get(int index) {
return (T) this.items[index];
}
}
Main
class:
public class Main {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
System.out.println("Hello Whosebug!");
Object int1 = new Integer(1);
Object int2 = new Integer(2);
Object int3 = new Integer(3);
/* using my object bag ************************************************/
System.out.println("using my object bag");
IObjectBag myObjectBag = new ObjectBag(3);
myObjectBag.add(int1);
myObjectBag.add(int2);
myObjectBag.add(int3);
myObjectBag.remove(int2);
ObjectBag.printBag(myObjectBag);
/* using my generic bag ***********************************************/
System.out.println("using generic bag");
// Bonus Question: using object like above causes error at add method (why?)
Integer int4 = new Integer(4);
Integer int5 = new Integer(5);
Integer int6 = new Integer(6);
GenericBag<Integer> myGenericBag = new GenericBag<Integer>(3);
//Bonus Question: using Interface decllaration like above causes error in print bag (why?)
myGenericBag.add(int4);
myGenericBag.add(int5);
myGenericBag.add(int6);
myGenericBag.remove(int4);
GenericBag.printBag(myGenericBag);
}
}
输出:
Hello Whosebug!
using my object bag
1
3
using generic bag
5
6
您的 ObjectBag 问题 'automaticaly' 已由您的 GenericBag 实施提供的 类型安全性 解决:
- 正在访问一个条目returns对象,现阶段你不知道对象是什么类型。
- 您可以在同一个列表中插入任何类型的对象(混合),例如字符串和整数,这是一种反模式,会导致代码不可读(用您的泛型包试试!)
- 因为你的编译器在你声明它之后知道你的 GenericBag 的类型,所以在你的代码的任何阶段,如果你将鼠标悬停在你的 genericBag 实例上,你就会知道它的类型,这使得你的代码更具可读性,也可以扩展到其他人
泛型也提供了更多的方式,假设你想让你的 GenericBag 只接受数字,那么你可以这样写:
public class GenericBag<T extends Number>
我的建议是阅读一些关于 Java 基础知识的文章,尤其是泛型,有一种基于实践的学习方式是一件好事,但是有很多文章可以给你一些非常好的理论知识对此事的见解。
比方说,在 ObjectBag
上使用 GenericBag<String>
的原因与在 Object
上使用 String
(或任何其他类型)基本相同:
类型安全。
您声明了某个方法 returns 一个字符串集合而不是其他任何东西,从而防止您将其他对象放在那里,或者试图将您从包中得到的东西当作其他类型。当您有 100 行代码时,这可能听起来很愚蠢,但是当您使用体面的代码库时,这可能会为您节省大量调试时间。
尽管类型安全不是灵丹妙药,它只是一种工具,有些人觉得有用,有些人觉得有用。我很确定这是任何编程论坛的热门圣战主题。
如果您在没有这种范式(Java脚本背景,对吗?)的情况下也能自如地工作,您可以考虑尝试一些动态类型的语言,例如 Python 而不是 Java。
所以我一直在审查我的数据结构,并发现了一个关于 Java 泛型和 Object
class 的有趣想法。我已经 运行 以两种不同的方式实现了一个“通用包” (下面的注意事项:IObjectBag.java、ObjectBag.java、IGenericBag.java 和 GenericBag.java) 并同时使用了它们 (注意:低于 main.java 和输出)。我已经根据堆栈溢出规则删除了一些不必要的代码,但如果您想要完整的实现,请告诉我。
此外,除了查看 ArrayList
class here 的源代码外,我还在许多网站、书籍和课程中研究了这个主题,我明白我的 GenericBag
比我的 ObjectBag
更好,但不足以在面试中以实际的方式解释它。我很困惑,我的 GenericBag
在其实现中使用了比 ObjectBag
更多的转换操作(参见 Remove
和 PrintBag
)。
所以,除了语法糖,为什么我的
GenericBag
更好?请使用我的 classes 作为示例。runtime/overhead/space/time 有什么我没有注意到的重要区别吗?
你会如何回答这个问题或希望在面试中得到回答?
奖金问题:如果你愿意,请在Main
和GenericBag
评论中回答奖金问题(我想我可以自己回答,只是想听听你的意见)。
IObjectBag
接口:
public interface IObjectBag {
void add(Object item);
Object remove(Object item) throws NoSuchElementException;
boolean isEmpty();
int find(Object item);
Object get(int index);
int numItems();
}
ObjectBag
class:
public class ObjectBag implements IObjectBag {
private Object [] items; // the java class attribute that will hold out "ints"
private int numItems;
public static void printBag(IObjectBag bag) {
for(int i = 0; i < bag.numItems(); i++) {
System.out.println(bag.get(i));
}
}
public ObjectBag(int size) {
this.items = new Object[size]; // fills array with null values
this.numItems = 0;
}
public void add(Object item){
// adds item to end of bag
}
public Object remove(Object item) {
int index = this.find(item);
if(index == -1) throw new NoSuchElementException("oops nothing found");
Object out = this.items[index];
this.items[index] = null;
this.numItems -= 1;
if(index + 1 != this.items.length && this.items[index + 1] != null) {
for(int i = index; i < this.items.length; i++) {
if(i + 1 != this.items.length) this.items[i] = this.items[i + 1];
}
this.items[this.items.length - 1] = null;
}
return out;
}
public int find(Object item) {
// return index given item or -1
}
public Object get(int index) {
// returns item given index
}
}
IGenericBag
class:
public interface IGenericBag <T> {
void add(T item);
T remove(T item) throws NoSuchElementException;
boolean isEmpty();
int find(T item);
T get(int index);
}
GenericBag
class:
public class GenericBag<T> implements IGenericBag<T> {
// private T[] items; can't use this b/c see comment in constructor
private Object[] items;
private int numItems;
public static void printBag(GenericBag bag) {
for(int i = 0; i < bag.numItems(); i++) {
System.out.println(bag.get(i));
}
}
public GenericBag(int size) {
// this.items = new T[size]; Bonus: throws generic array creation error (why?)
this.items = new Object[size];
this.numItems = 0;
}
public void add(T item){
this.items[this.numItems] = item;
this.numItems += 1;
}
public T remove(T item) {
int index = this.find(item);
if(index == -1) throw new NoSuchElementException("oops nothing found");
T out = (T) this.items[index];
this.items[index] = null;
this.numItems -= 1;
if(index + 1 != this.items.length && this.items[index + 1] != null) {
for(int i = index; i < this.items.length; i++) {
if(i + 1 != this.items.length) this.items[i] = this.items[i + 1];
}
this.items[this.items.length - 1] = null;
}
return out;
}
public int find(Object item) {
// given object return index or throw exception
}
public T get(int index) {
return (T) this.items[index];
}
}
Main
class:
public class Main {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
System.out.println("Hello Whosebug!");
Object int1 = new Integer(1);
Object int2 = new Integer(2);
Object int3 = new Integer(3);
/* using my object bag ************************************************/
System.out.println("using my object bag");
IObjectBag myObjectBag = new ObjectBag(3);
myObjectBag.add(int1);
myObjectBag.add(int2);
myObjectBag.add(int3);
myObjectBag.remove(int2);
ObjectBag.printBag(myObjectBag);
/* using my generic bag ***********************************************/
System.out.println("using generic bag");
// Bonus Question: using object like above causes error at add method (why?)
Integer int4 = new Integer(4);
Integer int5 = new Integer(5);
Integer int6 = new Integer(6);
GenericBag<Integer> myGenericBag = new GenericBag<Integer>(3);
//Bonus Question: using Interface decllaration like above causes error in print bag (why?)
myGenericBag.add(int4);
myGenericBag.add(int5);
myGenericBag.add(int6);
myGenericBag.remove(int4);
GenericBag.printBag(myGenericBag);
}
}
输出:
Hello Whosebug!
using my object bag
1
3
using generic bag
5
6
您的 ObjectBag 问题 'automaticaly' 已由您的 GenericBag 实施提供的 类型安全性 解决:
- 正在访问一个条目returns对象,现阶段你不知道对象是什么类型。
- 您可以在同一个列表中插入任何类型的对象(混合),例如字符串和整数,这是一种反模式,会导致代码不可读(用您的泛型包试试!)
- 因为你的编译器在你声明它之后知道你的 GenericBag 的类型,所以在你的代码的任何阶段,如果你将鼠标悬停在你的 genericBag 实例上,你就会知道它的类型,这使得你的代码更具可读性,也可以扩展到其他人
泛型也提供了更多的方式,假设你想让你的 GenericBag 只接受数字,那么你可以这样写:
public class GenericBag<T extends Number>
我的建议是阅读一些关于 Java 基础知识的文章,尤其是泛型,有一种基于实践的学习方式是一件好事,但是有很多文章可以给你一些非常好的理论知识对此事的见解。
比方说,在 ObjectBag
上使用 GenericBag<String>
的原因与在 Object
上使用 String
(或任何其他类型)基本相同:
类型安全。
您声明了某个方法 returns 一个字符串集合而不是其他任何东西,从而防止您将其他对象放在那里,或者试图将您从包中得到的东西当作其他类型。当您有 100 行代码时,这可能听起来很愚蠢,但是当您使用体面的代码库时,这可能会为您节省大量调试时间。
尽管类型安全不是灵丹妙药,它只是一种工具,有些人觉得有用,有些人觉得有用。我很确定这是任何编程论坛的热门圣战主题。
如果您在没有这种范式(Java脚本背景,对吗?)的情况下也能自如地工作,您可以考虑尝试一些动态类型的语言,例如 Python 而不是 Java。