这可能听起来很基本。但我是Java的新手。到目前为止,我已经投入了很少的初始学习时间,我一直对新对象声明的语法冗余感到困惑:
TypeName a = new TypeName();
特别是
String s = new String("abc");
Character c = new Character("A");
为什么世界上有人想要输入关键字TypeName
(例如。String
,Character
等...)两次?我知道有简短的说法:
String s = "abc";
char c = "A";
但这些是例外而不是规则。那么有人可以开导我吗? THX。
答案 0 :(得分:19)
因为有时你想做类似的事情:
// Explicitly force my object to be null
String s = null;
或
// Cast a child class to its parent
MyParentClass mpc = new IneritedClassFromParent();
或
// Store and access a concrete implementation using its interface
ISomeInterface isi = new ConcreteInterfaceImplementation();
换句话说,仅仅因为您声明要存储的类型并不总是意味着您希望它使用该类的新实例进行初始化。
在使用继承或使用接口时的接口实现时,您可能希望使用子类的新实例。
或者有时您可能希望最初明确强制某些内容为null并稍后填充它。
答案 1 :(得分:5)
使用此语法,您可以轻松创建X
类型的对象,并将其分配给Y
类型的变量:
List<String> myList = new ArrayList<String>();
答案 2 :(得分:5)
为什么世界会有人想要 输入关键字TypeName(例如String, 人物等......两次?
因为你做了两件事:
这两种类型不一定相同,例如。
Map m = new HashMap();
您可能习惯于使用“动态类型化”的语言,例如PHP,其中变量没有类型。使用Java的静态类型声明所获得的优势是编译器捕获了许多编程错误(即使在现代IDE中,您在键入时也是如此)。例如,如果你犯了一个简单的错误:
m.siez();
编译器会立即提醒您程序出现问题这一事实 - 它只能因为它知道声明的类型Map
没有方法siez()
而这样做。
一些现代静态类型语言(如C#和Scala)使用type inference为您提供“两全其美”,您可以省略类型声明,编译器将假设它与对象的类型相同你分配给它。但是,这样的语言总是允许显式类型声明,因为类型推断并不总是可行的或者是desirables(例如在上面的例子中,变量应该使用接口而不是具体类)。
答案 3 :(得分:4)
这根本不是多余的。使用变量有两个步骤:
声明:此步骤告诉VM该变量的静态足迹。例如:Object a;
只会在类Object
中声明的足迹可见,而Integer b;
将在Integer
类和所有继承的父类中声明所有足迹,向上到Object
可见。这是针对静态部分的。
instanciation :此步骤告诉VM该变量的动态足迹。例如:List<String> c = new LinkedList<String>();
,然后c.put("foo");
将使用LinkedList
的{{1}}方法实现,即使可见的是put()
。有时,您需要这种声明/实例,但需要覆盖以访问静态足迹不可见的非常具体的方法。例如,让我们考虑一个声明为List::put()
的方法,并且知道 public void method1(Object obj)
实例实际上是obj
,因此您将特别通过将对象强制转换为动态足迹:Integer
现在,int value = ((Integer) obj).intValue();
部分。为简单起见,Java已经简化了“原始”类的写法。更具体地说,从Java 1.5开始,您可以这样做:
String a = "A";
一切正常。但有什么区别?考虑一下这段代码:
Integer n1 = 1;
Integer n2 = new Integer(1); // same thing
int n3 = n2;
将输出
String a = new String("A");
String b = new String("A");
String c = "A";
String d = "A";
System.out.println("a:" + a.hashCode() + " = b:" + b.hashCode() + " == " + (a == b));
System.out.println("b:" + b.hashCode() + " = c:" + c.hashCode() + " == " + (b == c));
System.out.println("c:" + c.hashCode() + " = d:" + d.hashCode() + " == " + (c == d));
为什么呢?因为JVM正在尝试尽可能多地重用内存,并且由于a:65 = b:65 == false
b:65 = c:65 == false
c:65 = d:65 == true
和a
正在创建b
的新实例,因此它们不共享相同的内存空间。但是,String
和c
正在使用常量字符串值(这是编译器优化),因此指向完全相同的d
对象。
答案 4 :(得分:1)
这里有许多好的答案,说明为什么有必要。你是对的,因为它似乎多余。 java经常被(并非不公平地)批评为一点,呃......详细。有一些快捷方式。例如对于字符串String s="Abc"
(实际上不是快捷方式,它有点不同,而且更好,因为你没有明确地创建一个新对象)。对于泛型,java 7中的声明重复也会有所减少。
答案 5 :(得分:0)
那是因为没有隐式方式来分配复杂对象的值。
当您执行int a = 3;
或double b = 2.5;
时,您可以在右侧隐式声明类型。
在OOP中,你必须使用构造函数,这就是你必须做new TypeName()
的原因。这也使您能够传入参数来设置对象。
另一个原因是您使用接口时。所以你可以这样做:
MyInterface blah = new InterfaceImplementation();
MyInterface bar = new AnotherInterfaceImplementation();
甚至:
ParentClass foo = new DerivedClass();
这是因为在使用接口时,通常不希望将变量类型设置为接口实现,而是将接口本身设置为接口实现。否则,将无法指定要使用的实现。
另一个有用的东西是泛型:
List<SomeType> myList = new ArrayList<SomeType>();
Java 7将此简化为
List<SomeType> myList = new ArrayList<>();
这样您就不必两次输入<SomeType>
(这在地图中尤其令人痛苦)。
答案 6 :(得分:0)
当你进入班级泛型(扩展名)时:
class a extends b
你会发现你可以这样做:
b=new a();
答案 7 :(得分:0)
嗯,变量需要有一个类型。当您创建对象的实例时,您需要告诉它应该是哪种类型。当然,这些不必相同。例如,您可以将String设置为Object变量。当然,您可以使用类似的东西来简化操作:
var s = new TypeName();
这就是在C#中完成的。但我想在Java中他们没有看到这种需要 我同意Java在现代标准中非常冗长,但它也很容易阅读,并且没有太多的语法糖来混淆你。
答案 8 :(得分:0)
有强类型语言支持“类型推断”(例如Scala)。 Java根本就不是其中之一(尽管在Java 7中会有一些泛型参数的类型推断)。在这些语言中,虽然未声明变量类型,但编译器可以明确地推断它,并仍然检测类型错误。例如(非Java):
val str = "This is not a number!";
val x = str.intValue(); // Compiler error, because str is implicitly a String.
在Java中,在很多情况下,您会在右侧将特定的具体类型分配给左侧的更一般类型。例如:
Set students = new TreeSet();
这是一种很好的风格,因为您的代码不依赖于特定的实现。如果您需要切换实现(例如,您需要从基于散列的Set
更快的查找),只有初始化程序的右侧更改。
因此,使用正确的抽象而不是具体类型来声明公共API尤为重要。