Java基础笔记 对象基本要素 Java类型 Java控制结构 类 Java包 访问控制 类的复用 Upcasting和多态 接口与抽象类 内部类 容器 异常处理 I/O RTTI 泛型

状态

数据成员

行为

  • 方法成员
  • 访问控制(封装):public,private,protected

类型

  • 对象是某个类(型)的
  • 通过之前的类型定义新的类型
    组合 : has-a
    继承 : is
  • 不同类型间的关系
  • 基本类型之间的转换关系
  • Autoboxing, unboxing
  • Upcasting, downcasting

Java类型

基本类型

int double ...

数组

  • 静态初始化
    int []a = {1,2};
    直接赋值
  • 动态初始化
    int []a = new int[5];
    元素初始化为0
  • 不规则数组
    int [][]a = {{1,2},{3,4,5},{7,8,9,10}};
    ——>a.lenth为3,且a[0][2]为空(未被初始化
  • 未知错误 int [] a = new int[3]{1,2,3};
  • 使用
    输出数组时,int []a = {1,2};
    System.out.println(a);出现乱码
    System.out.println(a[0]);输出1

引用

  • 同一对象不同名字。不管使用哪个名字对 对象 进行修改,其他名字访问到的对象都是修改后的那个
  • 引用首先要初始化 new
  • 类class中的数据成员的引用:默认初始化为null

不可变类型

  • 一旦创建不能改变
  • 不可变类型 String,Integer,Float等
    对比:可变类型:MyType,数组

final 关键字

不可变对象

final 数据

  • 编译时为常数;一旦被赋值就不能被修改
  • static final仅有一个不可变的存储空间
  • final 成员在定义时可以不给初值
    必须在构造函数中初始化

final 参数

函数不能修改参数的引用

final method

不能被重写

final class

不能被继承

Java控制结构

Java操作符

移位

  • 带符号 >>
  • 不带符号 >>>

等于

“==” 与 equals()

字符串连接操作 +=

Java控制语句

循环 for each

	int []a = {1, 2, 3, 4, 5};
	for (int i : a)
		System.out.println(i);

静态成员

静态方法

static method() {}

  • 不用创建对象就可以调用
  • 不能使用需要 实例化 后才分配空间的变量/函数
  • 比如:main函数
	class hi {
		double d;
		static void display() {
			System.out.println(d);
		}

		public static void main(String []args) {
			display();
			hi.display();
			hi s = new hi();
			s.display();
		}
	}

静态数据

所有对象共享数据

  • static int i = 1;
    可以对i重新赋值,函数中使用到i的地方i值会全部改变,但是i的引用a,(如int a = i;) 值不变(为原来的i)
  • static int i; 初始化为0,可以重新赋值,等同于int i;
  • 不管何时引用,在什么对象后 .引用,都获得不变的值

类的数据成员

this关键字

  • 含义:在类的非静态方法中 , 返回调用该方法的对象的引用
  • 作用:
    区分参数名称与数据成员名称
    返回当前对象return this;
    作为其他方法的参数return OneClass.OneMethod(this)
  • 在构造函数中调用构造函数
    形式:this(...)
    出现在构造函数第一行
    只能调用一个构造函数
  • 静态方法无法使用this
    解决办法:传入this类型的参数
public static void set(MyType t, double x) { t.d = x; }
//对比
void set(double x) { this.d = x; }

super关键字

  • 每个子类对象都包含一个隐藏的父类对象
  • 在子类中 , super 用来指代父类对象的引用

数据成员的初始化

  • 初始化的值
  • 初始化的顺序
  • 静态成员 / 非静态成员
  • 子类成员 / 父类成员
  • 构造函数
  • 所有数据成员初始化在构造函数调用前完成
  • 按照成员定义的顺序初始化

类的方法

构造函数

  • 名称与类名称相同
  • 无返回值
  • 为类的静态方法
  • 在子类构造函数调用前 , 首先调用父类构造函数
  • 带参数的构造函数,调用前依然先调用父类构造函数
  • 构造函数中不能重写
public class MySubType extends MyType {
	 public MySubType () {
		super(1.0);//注意
		System.out.println("In sub class");
	}
	public static void main(String [ ]args) {
		MySubType ms = new MySubType();
	}
}

重载 (overload)

  • 方法名相同 , 参数类型 / 数量不同
  • 区分:函数名 + 参数列表
  • 函数重载与基本类型的转换
    当转换不损失精度 (up-casting)
    调用参数类型 '' 最近 '' 的函数
    例如 : charint, byteshort, shortint, intlong, long
    float, floatdouble
  • 当转换损失精度 (down-casting)
    需要强制转换
  • 函数重载与 autoboxing/unboxing

int and Integer 装箱/拆箱

重写 (override)

子类重新实现父类的方法 ( 同一个函数 )
相同函数名 , 不同参数列表

类的销毁

  • 对象占有的资源
  • 系统自动分配:
    new 操作时系统分配的内存
  • 程序显式分配:
    r.open();
  • 销毁对象占有的资源
    • 系统自动回收 ( 垃圾回收 ):
      new 操作时系统分配的内存
  • 程序显式回收
    r.colse();
  • 垃圾回收
    System.gc()
    通知JVM可以进行回收

Java包

  • 创建包
  • package mypackage;在.java 文件首行
  • javac mypackage/MyType.java
    java mypackage.MyType
  • 使用包
    import mypackage.MyType;
  • 包内 名字空间 共享
  • jar包
  • 打包 包(packed package)
  • 将包 ( 目录 ) 变成文件c: create f: output to file
    (如:jar cf restaurant.jar restaurant

访问控制

package access

  • 同一个包中的类可以访问
    其他包中的类不能访问
  • 如果没有 package 语句 , java 默认当前目录中的
    java 文件属于同一个包

public

  • 每个 .java 文件包含一个 public class, 且该 class 的名字等于 .java 文件名
  • 每个 .java 文件中除去 public class 外 , 其他的 class
    为 package access

private

  • private 构造函数 : 统计该类有多少对象
private MyType(int i1, double d1, char c1) {
	i = i1; d = d1; c = c1;
}
public static int count= 0;
public static MyType makeMyType(int i1, double d1, char c1) {
	count++;
	return new MyType(i1, d1, c1);
}
  • private 构造函数 : 该类只有一个对象
private static MyType mytype = new MyType(1, 1.0, 'a');
public static MyType access() { return mytype; }

protected

  • 可以被子类 / 同一包中的类访问 , 不能被其他类访问
  • 弱化的 private
  • 同时赋予 package access
    protected void set(double x) { d = x;}

封装

  • 将易变的与稳定的部分区分开
  • 在满足需求的情况下 , 接口尽量简单

类的复用

组合 has-a

将已有类作为新类的数据成员

class Person {
     Name name;
     Age age;
     ...
}
class Name{...}
class Age{...}

继承 is-a

新类 包含已有类 的所有数据与方法 , 并能增添修改(重写)

extends关键字

  • 子类可以使用父类所有方法和数据成员
  • 子类可以定义新的方法和数据
  • 子类可以重写父类的方法
class A {//父类 基类
...
}
public class B extends A {//子类
...
}

protected关键字

super关键字

Object class

所有类都是它的子类

Upcasting和多态

Upcasting

  • 类型转换 : 父类的引用可以指向子类对象
  • 同一基类的不同子类可以被视为同一类型 ( 基类 )
class A{ …}
class B extends A{ …}
A a = new A();
B b = new B();
A a = new B(); // upcasting
//对比
class A{ …}
class B{ …}
A a = new A();
B b = new B();
// A a = new B();compile error
class Instrument {
	public void play() {}
	static void tune(Instrument i) {
		i.play();
	}
}
public class Wind extends Instrument {
	public static void main(String[] args) {
		Wind flute = new Wind();
		Instrument.tune(flute);
	}
}

多态

  • 参数可以代表不同的子类 , 并能
    正确调用它们的方法 ( 即 , 有多种表现形态 )
  • 子类重写了父类方法f()
  • 当使用父类引用访问子类对象时 , 调用 f() 将绑定到子类的方法

动态绑定

  • 函数的调用在运行时才能确定
  • Java 中的所有方法都采用动态绑定 , 除了final static
  • 数据成员不动态绑定

接口与抽象类

抽象

抽象方法

  • 仅提供方法的名称 , 参数和返回值
  • 没有具体实现
  • 需要子类重写后才有意义
  • 使用 abstract 关键字
abstract class Instrument {
	public abstract void play(int note) ;
}
public class Wind extends Instrument {
	public void play (int note) {
		System.out.println ("Wind.play()"+ n);
	}
}
public class Stringed extends Instrument {
	public void play(int note) {
		System.out.println("Stringed.play()"+ n);
	}
}

抽象类

  • 包含抽象方法的类
  • 不能创建对象(实例化),必须被继承
  • 是不完整的类

接口

相当于一个功能列表

特点

  • 所有方法都是抽象方法,且默认为public(没有方法的实现)
  • 所有数据默认为final static
  • 没有代码重用 , 仅仅保留 upcasting 和多态
  • 所有实现该接口的类都具有接口提供的方法
  • 任何使用该接口类型的方法 , 都可以使用他的任何一种实现
  • 某种协议 (protocol)

实现

关键字interfaceimplements

interface Instrument {
	void play(int note) ;
	String what();
}
class Stringed implements Instrument {
	public void play(int note) {
		System.out.println("Stringed.play()"+ n);
	}
	public String what() {return "Stringed";}
}

一个类实现多个接口

class Seaplane implements Plane, Boat {...}
对比
error:class C extends A, B {...} //继承: 只能有一个父类

扩展接口

interface A {…}
interface B extends A{…}
interface D {…}
interface D extends A,C{…}

接口适配器

没有深入接触

内部类

普通内部类

关系

  • 定义在一个类的内部
  • 返回内部类的引用OutClassName.InnerClassName
  • 在内部类中访问外部类对象的引用
    OuterClassName.this
public class Outer{
	void f() { System.out.println(“Outer.f()”);}
	class Inner{
		public Outer g() {return Ourter.this;}
	}
	public Inner inner() { return new Inner(); }
	public static void main(String []args){
		Outer o = new Outer();
		Outer.Inner i = o.inner();
		i.g().f();
	}
}

创建内部类的对象

  • 在外部类的方法中 : 直接创建
  • 其他地方 : OuterClassObject.new
  • 内部类的对象隐含了一个引用 , 指向包含它的外部类对象
  • 创建内部类对象前 , 需要有包含它的外部类对象
  • 内部类对象能够访问该外部对象的所有成员 / 方法
public class Outer{
	class Inner{}
	public static void main(String []args){
		Outer o = new Outer();
		Outer.Inner i = o.new Inner();
	}
}

匿名内部类

  • 没有名字
  • 没有构造函数
  • 同时定义和创建
  • 必须继承另一个类或者实现一个接口(不出现implements等关键词)
  • return new oneClassName() {...}(在{}中定义)
public class Parcel{
	public Contents contents(){
		return new Contents() {
			private int i = 11;
			public int value() {return i;}
		};//注意分号
	}
}
interface Contents{
	int value();
}
  • 使用外部变量对匿名类数据成员初始化
    外部变量需要 final
public class Parcel{
	public Contents contents(final int v){
		return new Contents() {
			private int i = v;
			public int value() {return i;}
		}
	}
}

嵌套类(不考)

  • 静态的内部类(无法访问外部类的非静态成员)
  • 不需要外部的对象就可创建(不包含指向外部类对象的引用)
  • 可以放入接口中

内部类的作用

  • 多继承
    父类只能是一个普通类 / 抽象类
    可以通过多个内部类继承多个类 / 抽象类 / 接口
  • 闭包 (closure)
    带有*变量的函数 + 被绑定的*变量
  • 回调函数 (callbacks)
    函数指针

容器

  • 动态添加/删除
  • import java.util.*;

类型安全的

  • 泛型 (generic)
  • 定义容器为只能存放某种类型的对象,编译时才确定类型
  • 容器可以存放的类型为Object,这样任何类型的对象都能放入容器
  • 在确定了容器类型后 , Upcasting适用
  • 不能指定基本类型
  • 使用基本类型的wrapper(包装)
  • Autoboxing and unboxing
    compile error: ArrayList<int> a = new ArrayList<int>();
    ArrayList<Integer> a = new ArrayList<Integer>();

输出容器

容器重写了 toString() 方法 , 可以帮助可视化容器的内容

Collection

Collection is an interface
用于存放一组对象

List

  • List is an interface
  • 按照插入顺序排列(数组、链表)

ArrayList

  • 可扩展数组
  • 适用于随机访问,插入删除较慢,浪费空间
  • 每次扩张或缩减数组长度时 , 保证新的数组有一半的可用空间
  • 构造函数
    ArrayList<E>();
    ArrayList<E>(int initialCapacity);
    ArrayList<E>(Collection<E> c);

LinkedList

  • 双向链表
  • 适用于顺序访问,插入删除较快,无空间浪费
  • 实现List接口
  • 实现Queue接口
  • add(), remove(), element()
  • offer(), poll(), peek()
  • 构造函数
    LinkedList<E>();
    LinkedList<E>(Collection<E> c);
  • 方法
  • 返回链表首元素,若链表为空则抛出异常a.getFirst(); a.element();
  • 返回链表首元素,若链表为空则返回 nulla.peek();
  • 删除并返回链表首元素,若链表为空则抛出异常String s = a.remove(); String s = a.removeFirst();
  • 删除并返回链表首元素,若链表为空则返回 nullString s = a.poll();
  • 在链表头添加对象a.addFirst(“tiger”);
  • 在链表尾添加对象a.add(“cow”); a.addLast(“cow”); a.offer("cow")
  • 应用:Stack
  • 后进先出 (Last In First Out, LIFO)
  • push: 将一个对象入栈
  • pop: 从栈中取出一个元素

接口

  • add()添加元素
  • remove()删除元素
  • get()返回第 i 个位置的元素
  • size()返回元素数量
  • contains()查询
  • indexOf()序号
  • subList()子表
  • isEmpty()是否为空
  • iterator()返回迭代器
  • listIterator()返回List迭代器
  • toArray()转为数组

迭代器

Iterable 接口
  • 提供 iterator() 返回迭代器
  • Collection 扩展了 Iterable 接口
  • foreach 语句
  • 对所有实现Iterable接口的类
  • 数组
ListIterator
  • List 接口提供
  • 扩展了 Iterator
  • 双向遍历
  • hasNext(), hasPrevious()
  • next(), previous()

Set

  • 集合(没有重复元素)
  • 没有对Collection接口扩展
  • 方法
  • put()
  • add(Object o), addAll(Collection<E> c)
  • remove(Object o), removeAll(Collection<E> c)
  • contains(Object o)
  • iterator()
  • size()
  • toArray()

HashSet

实现为 hash 表,查询较快(无序)

TreeSet

实现为查询树,较慢,按顺序排列(自动字典排序

LinkedHashSet

速度快,按照插入顺序排列

Queue

  • 队列->先进先出
  • enqueue进队 ,dequeue出队
  • 应用:任务调度

LinkedList

PriorityQueue - 优先级队列

  • 每次出队时 , 选择优先级最高的对象
  • 队列中的对象可以比较优先级
  • 普通队列也可看成优先级队列 : 优先级为加入队列的时间
  • 自定义优先级
  • 构造函数
    PriorityQueue<E>(int initialCapacity, Comparator<E> comparator)
  • Comparator接口——定义两个元素的优先级关系
    包含方法compare(E e1,E e2)
    返回负数: 当 e1 优先级低于 e2
    返回正数: 当 e1 优先级高于 e2
    返回 0: 当 e1 优先级等于 e2
  • 方法
  • offer(Object o),add(Object o)将对象加入队列尾部
  • poll(),remove()弹出位于队首的对象
  • peek(),element()返回位于队首的对象 , 并不删除

Map

dictionary, associative array

  • HashMap a = new HashMap<T,T>();
  • Key-value对应
  • Key不重复,value可以重复
  • Map is an interface
  • 方法
  • 存入键值对put(K key, V value)
  • 返回键对应的值get(K key)
  • 是否包含键keycontainsKey(Object key)
  • 是否包含值 valuecontainsValue(Object value)
  • 返回键组成的 SetkeySet()
  • 返回值组成的 Collectionvalues()
  • 应用:单词出现次数

HashMap

实现为 hash 表 , 查询较块

TreeMap

实现为查询树 , 按顺序排列

LinkedHashMap

按照插入顺序排列

异常处理

错误处理场景

  1. 某方法中发现错误
  2. 中断当前执行路径
  3. 创建 / 捕捉 Exception 类对象
  4. 跳转到相应的异常处理代码段
  5. 在代码段中处理该异常

语法

抛出异常 : throw

If (t == null) throw new NullPointerException();

  • 检查错误条件
  • 含义
  • 发生了一个异常 , 请找到合适的异常处理模块处理
  • 该异常的具体信息存储在一个 Exception 对象中 .

处理异常 : try, catch

try{ // 可能会抛出异常的代码 } catch(Type1Exception e){ // 处理类型为 "Type1Exception" 的异常 } catch(Type2Exception e){ // 处理类型为 "Type2Exception" 的异常 }

  • 处理对应异常
  • 一旦发生异常立即跳转 , 不是
    等到所有的异常都发生
  • On error goto

异常对象 : Exception 类的子类

  • 方法:
    toString()
    printStackTrace()
  • 直接写 catch(Exception e)简单
  • 可以只抛出异常(只有throw),将异常交给该方法调用者(可能是另一个方法)处理

类方法的异常说明

throws关键字

 bar() throws Type1Exception, Type2Exception{
…
throw (Type1Exceptione);
…
throw (Type2Exceptione);
}
  • 标识该方法可能会抛出何种类型的异常
  • compile error if no throws
  • 包含throws关键字 , 但函数本身并不抛出相应
    异常
  • 用于 interface, abstract method
  • 保证重写的方法必须考虑所列出的异常
  • 不包含 throws 关键字,默认会抛出RuntimeException 类型的异常
  • 重新抛出异常
    调用函数可以将 catch 到的异常重新抛出,交给调用者的调用者来处理(可以一直抛到嵌套函数最外层)
  • 举例(现成异常)
    • 当数组越界时 , 自动抛出 ArrayOutOfBoundsException
  • 当访问 null 的成员时 , 自动抛出 NullPointerException
  • 当除以 0 时 , 自动抛出ArithmaticException

finally关键字

  • 无论try 语句中是否有异常抛出 , 都会执行
  • 作用:帮助保证一致性,简化代码
  • return;->统一执行finally

继承 , 接口与异常

  • 父类 / 接口的方法有异常说明 (throws关键字 )
  • 子类 / 实现重写该方法时,应满足
  • 有同样的异常说明
  • 有 " 更少 " 的异常说明
  • 抛出(相对于父类异常的)子类异常
  • 原因 upcasting

I/O

介绍

  • I/O流
  • 字节 (01 串 ): ByteArrayStream
  • 文件 : FileStream
  • 字符串 : StringStream
  • 对象 : ObjectStream
  • 最重要的操作
  • InputStream: read()
  • OutputStream: write()
  • close()
  • 便利操作
  • 从文件读取一行 : readLine()
  • 读取一个基本类型 : readInt(), readDouble()
  • 读取对象

Path接口

  • java.nio.file
  • 提供对文件路径字符串的操作
  • 创建 Path 类型的对象
    Path p1 = Paths.get("C:/Document/tmp/Hello.java");
    Path p2 = FileSystems.getDefault().getPath("C:/Document/tmp/Hello.java");
System.out.format("toString: %s%n", path.toString()); // C:homejoefoo
System.out.format("getFileName: %s%n", path.getFileName()); // foo
System.out.format("getName(0): %s%n", path.getName(0)); // home
System.out.format("getNameCount: %d%n", path.getNameCount()); // 3
System.out.format("subpath(0,2): %s%n", path.subpath(0,2)); // homejoe
System.out.format("getParent: %s%n", path.getParent());
// homejoe
System.out.format("getRoot: %s%n", path.getRoot()); // C:

Files类

包含文件操作的静态方法

Path p;

  • 判断文件是否存在 Files.exists(p)
  • 判断文件是否可读 / 可写 / 可执行Files.isReadable(p));
    Files.isWritable(p);
    Files.isExecutable(p);
  • 删除文件
try {
Files.delete(path);
} catch (NoSuchFileException x) {
System.out.format("%s: no such" + " file or directory%n", path);
} catch (DirectoryNotEmptyException x) {
System.out.format("%s not empty%n", path);
} catch (IOException x) {
// File permission problems are caught here.
System.out.println(x);
}

获得文件相关的信息

  • size(Path)
  • isDirectory(Path, LinkOption)
  • isRegularFile(Path, LinkOption...)
  • isSymbolicLink(Path)
  • getLastModifiedTime(Path, LinkOption...)
  • setLastModifiedTime(Path, FileTime)
  • getOwner(Path, LinkOption...)
  • setOwner(Path, UserPrincipal)

流Stream

每次读入/写出一个字节

InputStream类

  • read()close()
  • 不同源头对应不同子类ByteArrayInputStreamStringBufferInputStreamFileInputStream

装饰器FilterInputStream

  • InputStream的子类
  • 具有InputStream所有的类的子类接口
BufferedInputStream

缓冲 装饰器

DataInputStream

读取基本类型 装饰器
readInt(), readDouble()

OutputStream类

  • write()close()
  • 不同目的地对应不同子类(改为“Output”即可
  • String objects没有对应的StringBufferOutputStream

装饰器FilterOutputStream

  • 为 OutputStream 的子类
  • 具有 OutputStream 所有的类的子类接口
BufferedOutputStream

缓冲 装饰器

DataOutputStream

读取基本类型 装饰器
readInt(), readDouble()

PrintStream

格式化输出(到文件中) 装饰器
println(), print()

实现带缓冲的输入/输出流

read()方法增加参数

  • read(byte[ ], int off, int len)
  • 缺点
    buffer长度需要用户指定
    用户需要知道更多实现细节

继承

  • BufferedByteArrayInputStream, BufferedByteArrayOutputStream
  • BufferedStringBufferInputStream, BufferedStringBufferOutputStream
  • BufferedFileInputStream, BufferedFileOutputStream
  • 缺点
    类过多
    无法动态加载

组合

  • 定义BufferedInputStream
  • 包含一个InputStream对象作为成员
  • 调用该InputStream对象的 read(byte[], int off, int len)
    现缓冲
  • 该调用在不同系统上有不同的优化 , 并且被封装

Reader/Writer

每次读入/写出一个字符

  • Utf-16
    每次读入 / 写出 16bit, 或者 32bit
读写字节 读写字符
InputStream Reader
OutputStream Writer
FileInputStream FileReader
FileOutputStream FileWriter
StringBufferInputStream StringReader
(no corresponding class) StringWriter
ByteArrayInputStream CharArrayReader
ByteArrayOutputStream CharArrayWriter
FilterInputStream FilterReader
FilterOutputStream FilterWriter
BufferedInputStream BufferedReader
BufferedOutputStream BufferedWriter
**DataInputStream ** DataInputStream
DataOutputStream DataOutputStream
PrintStream PrintWriter
PrintWriter可以用 OutputStream
作为参数

Standard I/O

System.out

PrintStream

System.err

PrintStream

System.in

需要一些预处理

public class Echo {
	public static void main(String[] args) throws IOException {
		BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
	String s;
	while((s = stdin.readLine()) != null && s.length()!= 0)
		System.out.println(s);
	}
}

RTTI

运行时类型信息(RunTime Type Information)

Class类

每一个类都包含一个 Class 类的对象
该对象包含该类的信息

获得对象

  • 类的静态成员
    此时所有对象共享一个 Class 对象
  • 每个对象调用 getClass() 获得 Class 类的对象
    String s = “hello”; Class c = s.getClass();
  • 每个通过 .class 获得 Class 类的对象
    Class c = String.class;

拥有方法

  • getName()
  • getInterfaces()
  • getSuperclass()
  • newInstance()

RTTI 的用途

给定 Object 引用 , 判断它的类型

String s = new String(“hello”);
Object o = s;
Class c = o.getClass().getName();
String t = (String)s;

泛型

  • 参数化类型
    在定义类的成员和方法时 , 类型为可变参数

泛型类

public class TwoTuple<A, B>{ //... }

泛型接口

带有类型参数的接口
public interface Generator<T>{ T next(); }

泛型方法

  • 带有类型参数的方法的参数以及返回值
  • 语法public <T> void f(T x) { //…} 说明该方法带有类型参数T,且<T>需要放在返回值说明之前

类型擦除 (Type Erasure)

  • T仅作为占位符 , 不包含任何具体类型的信息
  • 所有T类型的对象引用最终实现为Object对象引用
  • 类型变量仅仅对编译器的静态检查有用
  • 当通过静态检查 , 所有类型参数被擦除

被限定的类型参数

限定类型参数的范围

  • 语法:class A <T extends B> { //…}表示类型参数 T 只能是 B 类型或者 B 的子类型
  • 作用 : 静态检查时 , 可以合法引用 T 的方法 ( 但最终仍然是Object)
  • 多个限定类型class A <T extends B & C> { //…}如果有类和接口 , 类应该出现在第一个位置

通配符*(不考)

<? extends B>

类型参数为某个 B 的子类型

  • 具体是哪一个无法确定
  • List<? extends Apple>
  • 无法add任何对象
  • 可以get对象

<? super B>

类型参数为某个 B 的父类型

  • 具体是哪一个无法确定
  • List<? super Apple>
  • 无法get任何对象
  • 无法确定是哪个父类
  • 可以addB的子类对象