1.所有类继承Object

Java 中的所有类都隐式继承该类Object

我们将在 Java Core 任务中分析继承是什么以及它在 Java 中是如何工作的。现在,我们将考虑由此得出的一个简单事实:

任何类的对象都可以分配给Object变量。例子:

代码 笔记
Object o = new Scanner(System.in);
变量存储对对象o的引用Scanner
Object o = new String();
变量存储对对象o的引用String
Object o = new Integer(15);
变量存储对对象o的引用Integer
Object o = "Hello";
变量存储对对象o的引用String

这是好消息结束的地方。编译器不会跟踪保存在变量中的对象的原始类型Object,因此除了类的方法之外,您将无法 调用已保存对象Object的方法。

如果你需要调用与对象的原始类型相关联的方法,那么你需要先将对它的引用保存在正确类型的变量中,然后调用该变量的方法:

代码 笔记
Object o = new Scanner(System.in);
int x = o.nextInt();
该程序将无法编译。该类Object没有nextInt()方法。
Object o = new Scanner(System.in);

Scanner console = (Scanner) o;

int x = console.nextInt();
这会起作用。这里我们使用类型转换运算符

将对对象 的引用保存Scanner在变量中。 Scanner

您不能直接将Object变量分配给 Scanner 变量,即使该Object变量存储了对对象的引用Scanner但是,如果您使用您已经知道的类型转换运算符,就可以做到这一点。这是它的一般外观:

Type name1 = (Type) name2;

其中name1是变量名Type,是存储对象引用name2的变量名。ObjectType

类型转换

如果变量的类型和对象的类型不匹配,则会ClassCastException抛出 a 。例子:

代码 笔记
Object o = new Integer(5);
String s = (String) o;
运行时会报错:这里会抛出
aClassCastException

在 Java 中有一种方法可以避免这种错误:我们通过检查存储在变量中的对象的类型来做到这一点:

name instanceof Type

运算instanceof符检查name变量是否为Type对象。

例如,让我们在不同对象的数组中查找一个字符串:

代码 笔记
Object[] objects = {10, "Hello", 3.14};

for (int i = 0; i < objects.length; i++)
{
   if (objects[i] instanceof String)
   {
      String s = (String) objects[i];
      System.out.println(s);
   }
}
自动装箱会将这些值分别转换为IntegerStringDouble

遍历对象数组

如果对象是 aString

将其保存到String变量
在屏幕上显示变量。


2. 为什么会出现泛型——集合

让我们回到集合。

Java 开发人员一创建该类ArrayList,就希望它具有通用性,以便它可以存储任何类型的对象。所以他们使用 s 的数组Object来存储元素。

这种方法的优势在于您可以将任何类型的对象添加到集合中。

当然,也有几个弱点。

缺点 1。

从集合中检索元素时,总是需要编写类型转换运算符:

代码 笔记
ArrayList numbers = new ArrayList();


for (int i = 0; i < 10; i++)
   numbers.add(i * 10);


int sum = 0;
for (int i = 0; i < 10; i++)
{
   sum = sum + (Integer) numbers.get(i);
}
创建一个集合来存储对Object对象的引用

用数字填充集合10, 20, ... 100;



对集合的元素求和


Typecasting 是必要的

缺点2。

不能保证集合包含特定类型的元素

代码 笔记
ArrayList numbers = new ArrayList();


for (int i = 0; i < 10; i++)
   numbers.add(i * 2.5);


int sum = 0;
for (int i = 0; i < 10; i++)
{
   sum = sum + (Integer) numbers.get(i);
}
创建一个集合来存储对Object对象的引用

我们用表示为对象的数字填充集合Double
0.0, 2.5, 5.0, ...


对集合的元素求和


会出现错误:a Doublecannot be cast to anInteger

数据可以放在任何地方的集合中:

  • 用另一种方法
  • 在另一个程序中
  • 从一个文件
  • 通过网络

缺点3。

集合中的数据可能会被意外更改。

您可以将充满数据的集合传递给某种方法。该方法由不同的程序员编写,将其数据添加到您的集合中。

集合的名称并没有明确指出其中可以存储哪些类型的数据。即使你给你的变量一个明确的名字,对它的引用也可以传递给一打方法,而这些方法肯定不会知道变量的原始名称。


3.泛型

Java 中的泛型

在 Java 中,所有这些问题都被这个叫做泛型的很酷的东西消除了。

在 Java 中,泛型意味着向类型添加类型参数的能力。结果是一个复杂的复合类型。这种复合类型的一般观点是这样的:

ClassName<TypeParameter>

这是一个通用类。并且它可以在您通常使用类的任何地方使用。

代码 描述
ArrayList<Integer> list;
创建变量
list = new ArrayList<Integer> ();
创建对象
ArrayList<Integer>[] array;
创建数组

这样的集合中只能Integer存储变量:

代码 描述
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(new Integer(1));
list.add(2);
list.add("Hello");
ArrayListInteger带元素的集合
这是允许的
这也可以
自动装箱

但这是不允许的:编译错误

在 Java Collections 探索中,您将学习如何使用类型参数创建您自己的类。现在,我们将看看如何使用它们以及它们是如何工作的。


4. 泛型如何工作

实际上,泛型非常原始。

编译器只是简单地将泛型类型替换为普通类型。但是当使用泛型类型的方法时,编译器会添加一个类型转换运算符来将参数转换为类型参数:

代码 编译器做什么
ArrayList<Integer> list = new ArrayList<Integer>();
ArrayList list = new ArrayList();
list.add(1);
list.add( (Integer) 1 );
int x = list.get(0);
int x = (Integer) list.get(0);
list.set(0, 10);
list.set(0, (Integer) 10);

假设我们有一个对整数集合中的数字求和的方法:

代码 编译器做什么
public int sum(ArrayList<Integer> numbers)
{
   int result = 0;

   for (int i = 0; i < numbers.size(); i++)
      result = result + numbers.get(i);

   return result;
}
public int sum(ArrayList numbers)
{
   int result = 0;

   for (int i = 0; i < numbers.size(); i++)
      result = result + (Integer) numbers.get(i);

   return result;
}

换句话说,泛型是一种语法糖,就像自动装箱一样,但更多一点。通过自动装箱,编译器添加了将 an 转换int为 an 的方法Integer,反之亦然,对于泛型,它添加了类型转换运算符。

在编译器编译带有类型参数的泛型类之后,它们只是简单地转换为普通类和类型转换运算符。有关传递给泛型类型变量的类型参数的信息将丢失。这种效果也称为类型擦除

有时编写泛型类(带有类型参数的类)的程序员确实需要有关作为参数传递的类型的信息。在 Java Collections 任务中,您将学习如何处理这个问题以及它需要做什么。



5. 关于泛型的一些事实

这里有一些关于泛型的更有趣的事实。

类可以有多个类型参数。它看起来像这样:

ClassName<TypeParameter1, TypeParameter2, TypeParameter3>

其实,这并不奇怪。编译器可以在任何地方添加一个运算符以转换为一种类型,它可以添加多个类型转换运算符。

例子:

代码 笔记
HashMap<Integer, String> map = new HashMap<Integer, String>();
map.put(7, "Hello");
map.put(-15, "Hello");
put方法的第一个参数是Integer,第二个是String

泛型类型也可以用作参数。它看起来像这样:

ClassName<TypeParameter<TypeParameterParameter>>

假设我们要创建一个列表来存储字符串列表。在这种情况下,我们会得到这样的东西:

// List of greetings
ArrayList<String> listHello = new ArrayList<String>();
listHello.add ("Hello");
listHello.add ("Hi");

// List of goodbyes
ArrayList<String> listBye = new ArrayList<String>();
listBye.add("Bye");
listBye.add ("Goodbye");

// List of lists
ArrayList<ArrayList<String>> lists = new ArrayList<ArrayList<String>>();
lists.add(listHello);
lists.add(listBye);

泛型类型(带有类型参数的类型)也可以用作数组类型。它看起来像这样:

ClassName<TypeParameter>[] array = new ClassName<TypeParameter>[size];

这里没有什么神奇的事情发生:尖括号只是表示类型名称:

代码 非通用对应物
ArrayList<String>[] list = new ArrayList<String>[10];
StringArrayList[] list = new StringArrayList[10];
ArrayList<Integer>[] list = new ArrayList<Integer>[10];
IntegerArrayList[] list = new IntegerArrayList[10];
ArrayList<Scanner>[] list = new ArrayList<Scanner>[10];
ScannerArrayList[] list = new ScannerArrayList[10];