this
关键字this
调用构造器:
// 编译正常
Integer[] a = {1, 2, 3,};
Integer[] b = new Integer[]{1, 2, 3,};
Integer[] c = new Integer[]{1,};
// 静态代码块
static {
cup1 = new Cup("A");
cup2 = new Cup("B");
}
// 实例化初始化
{
cup1 = new Cup("A");
cup2 = new Cup("B");
}
// 编译顺序
public class Window {
static {
// 1、静态代码块最先编译(类加载即执行)
}
// 静态成员变量仅在必要时初始化(对象创建或第一次访问静态数据时才会初始化;且只会初始化一次)
static Window sta = new Window();
// 非静态成员变量
Window w = new Window();
{
// 2、非静态代码块与非静态成员变量顺序执行,都在构造函数之前执行
}
Window(){
// 3、构造函数在静态代码块及非静态代码块后执行
}
}
// 方法重载
char ch = 1;
·· | 同一个类 | 同一个包 | 不同包的子类 | 不同包的非子类 |
---|---|---|---|---|
public | 是 | 是 | 是 | 是 |
protected | 是 | 是 | 是 | 否 |
default | 是 | 是 | 否 | 否 |
private | 是 | 否 | 否 | 否 |
@Override
注解是 JavaSE5
加入的finalize()
方法main
方法访问:
main()
方法会被调用Clazz
类不是一个public
类,如果命令行是java Clazz
,那么Clazz.main()
仍然会被调用public main()
仍然是可以访问的is-a
,组合:has-a
【大多数情况下,子类会有自己的特有域,新的可以被称为:is-like-a
像一个】final
关键字:数据、方法、类
static
又是final
的域只占据一段不能被改变的存储空间 ==> 常量final
使数字恒定不变;对象引用:final
使引用不变(一旦引用被初始化指向一个对象,就无法再把它改为指向另一个对象;然而,对象其自身却是可以改变的,Java并未提供使任何对象恒定不变的路径)final
:声明为final
但又未给定初始值(编译器能确保空白final
在使用前必须被初始化) ==> Spring
依赖中使用构造函数进行依赖组装final
参数:无法在方法中更改参数引用所指向的对象(主要用来向匿名内部类传递数据)final
方法:只有在想要明确禁止覆盖时,才将方法设置为final
的;所有private
方法都隐式的指定为final
;final
方法对程序的执行效率没有明确的提升final
类:禁止被继承java
构造方法有两个构造方法,分别是实例化构造方法(instance初始化方法)和Class对象构造方法;
实例化构造方法:负责完成实例字段的初始化
Class对象构造方法:可以理解为Class(静态)构造方法,负责静态字段的初始化(Java编译器自动生成)
每个对象都会对应有且仅有一个Class对象,即Class对象是单例的。所以在第一次创建对象时,需要先创建Class对象(类加载过程完成,调用的就是Class对象构造方法);后续继续创建该Class的实例对象时,就不需要再创建Class对象了。
初次使用(类加载)之处也是static初始化发生之处。所有的static对象和static代码段都会在类加载时依定义顺序执行;static域只会被初始化一次(类仅会被加载一次)
static
和private
方法【private
方法同属于final
方法】之外,其他所有方法都是动态|后期|运行时绑定 ==> 自动进行
)private
方法才可以被覆盖(private
方法默认是final
的);导出类中,对于基类中的private
方法最好采用不同的名字final
方法【含private
方法】)Java
语言中,所有类型转换都会得到检查;即使是一次普通的加括弧类型转换,进入运行期时仍然会对其进行检查;如果转换失败,会返回ClassCaseException
异常。在运行期对类型进行检查的行为被称为运行时类型识别(RTTI)interface
关键字产生了一个完全抽象的类,不提供任何具体实现协议
static
且final
的private
的public
private
接口不能在定义它的类之外被实现在JDK11+
中,从所在的外部类
静态方法创建某个非静态内部类的对象,可以不用按照OuterClassName.InnerClassName
书写;在其他独立的类中必须遵从此规则
生成一个非静态内部类对象
public class Outer{
public class Inner{
public Outer outer(){
// 通过 OuterClassName.this 可以在内部类中返回当前的外部类对象
return Outer.this;
}
}
public static void main(String[] args) {
Outer ou = new Outer();
// 非静态内部类依赖于外部类对象使用 .new 生成内部类对象
// 在当前外部类或其他独立类中均生效
Inner inner = ou.new Inner();
}
}
.this
:在非静态内部类中正确返回当前所属外部类对象,可利用OuterClassName.this
实现
.new
:通过外部类对象创建非静态内部类对象,可利用OuterClassInstance.new InnerClassName()
实现
// 为匿名内部类实现构造器行为
abstract class Base{
public Base(int i){
System.out.println("Base constructor, i= " + i);
}
public abstract void f();
}
public class Outer{
public static Base getBase(int i){
return new Base(i){
{
// Java的类加载顺序保证了优先调用父类构造,此处子类构造被替换为实例初始化
// 效果上等同于当前匿名内部类拥有了自己的构造
System.out.println("Inside instance initializer");
}
@Override
public void f() {
System.out.println("In anonymous f()");
}
};
}
}
分类(按照书写位置分类)
- 非静态内部类(局部|匿名)对象隐式的保存了一个指向创建它的外围类对象引用,在此作用域内,内部类有权操作所有的成员,包括private成员
- 静态内部类(嵌套类)
- 要创建嵌套类对象,并不需要其外围类的对象
- 不能从嵌套类的对象中访问非静态的外围类对象
- 嵌套类可以包含static的数据和字段
- 嵌套类可以作为接口的一部分
- 如果想要创建某些公共代码,使得它们可以被该接口的所有实现公用,推荐使用接口内的嵌套类实现
- 一个内部类(非静态)被嵌套多少层并不重要 -- 它能透明的访问所有它所嵌入的外围类的所有成员
为什么需要内部类?每个内部类都能独立的继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响
内部类继承
// 内部类继承
class WithInner{
class Inner{
// 内部类
}
}
// 直接继承内部类
public class InheritClass extends WithInner.Inner{
// 无法使用默认的构造器实现,必须提供外围类的引用
InheritClass(WithInner wi) {
// 提供外围类对象的引用
wi.super();
}
}
// 非静态内部类任意继承不影响
内部类可以被覆盖吗?
// 1、当继承了某个外围类的时候,内部类并没有什么变化
// 2、不同的内部类是完全独立的,各自在自己的命令空间内
// 3、可以明确的继承某个内部类
class Egg{
protected class Yolk{
// 这是一个内部类
}
}
// 4、当前外围类必须同时继承被继承内部类的外围类
class BigEgg extends Egg{
public class Yolk extends Egg.Yolk{
// 继承自另一个内部类
}
}
局部内部类与匿名内部类
如果内部类是匿名的,编译器会简单的产生一个数字作为类名标识符,结果:OuterClassName$Num.class
@SuppressWarnings
注解及其参数表示只抑制有关“不受检查的异常”的警告信息Object
Java
容器类库的用途是“保存对象”,划分为两个不同的概念
Collection
:元素序列,List
按照插入顺序保存元素、Set
不能有重复元素、Queue
按照排队规则确定对象产生顺序Map
:一组成对的“键值对”对象,允许使用键来查找值;也可以叫做映射表、字典Set
中只有元素不存在的情况下才会添加:TreeSet
默认按照字典序
升序保存对象,LinkedHashSet
保留了添加顺序(TreeMap
、LinkedHashMap
同)java.util.Arrays
、java.util.Collections
Arrays.asList
说明:
ArrayList
是一个内部类,不是标准的 java.util.ArrayList
Arrays.<T>asList(T... t)
Collections.addAll
:执行效率很高,The behavior of this convenience method is identical to that of c.addAll(Arrays.asList(elements)), but this method is likely to run significantly faster under most implementationsList
ArrayList
:长于元素随机访问,但是在List
中间插入和移除元素较慢 -- 数组LinkedList
:在List
中插入和移除元素代价较低,提供了优化的顺序访问;随机访问速度较慢 -- 链表;可以作为栈
或队列
使用Iterator
只能向前移动;ListIterator
只能用于各种List
的访问,可以实现双向移动Stack
,"栈"通常指后进先出 LIFO
的容器,有时也称为叠加栈
Queue
,队列
是一个典型的先进先出 FIFO
容器;先进先出
是最典型的队列规则;PriorityQueue
优先级队列,通过默认的自然顺序或自定义Comparator
实现插入元素时同步维护元素优先级Iterable
的类,都可以用于foreach
语法中(数组
除外)Queue
及栈
的行为,由LinkedList
提供支持public static void f() throws Exception {
System.out.println("Originating the exception in f()");
throw new Exception("Throw from f()");
}
public static void g() throws Exception {
try {
f();
} catch (Exception e) {
System.out.println("Inside g().e, e.printStackTrace()");
e.printStackTrace(System.out);
// 重新抛出异常
throw e;
}
}
public static void h() throws Exception {
try {
f();
} catch (Exception e) {
System.out.println("Inside.h(), e.printStackTrace()");
e.printStackTrace(System.out);
// 把当前调用栈信息填入原来的异常对象(有关原来的异常发生点信息会丢失)
// todo 调用 fillInStackTrace 的这一行就成了异常的新发生地点了
throw (Exception) e.fillInStackTrace();
}
}
// 异常丢失
public static void i(){
try{
System.out.println("异常丢失");
try{
// 这个方法抛出异常1
a.occurException1();
}finally{
// 这个方法抛出异常2
a.occurException2();
}
}catch(Exception e){
// 这里最后只能捕捉到 finally 中产生的异常2,异常1被丢掉了
System.out.println(e);
}
}
// 构造器中发生异常时 finally 的使用规则
// 在创建需要清理的对象之后,立即进入一个 try-finally 语句块
public static void main(String[] args) {
try {
InputFile in = new InputFile("Cleanup.java");
try {
String s;
int i = 1;
while ((s = in.getLine()) != null) {
System.out.println("对文件的一些操作");
}
} catch (Exception e) {
System.out.println("Caught exception in main()");
e.printStackTrace(System.err);
} finally {
// 构造阶段可能抛出异常,且要求清理资源,强烈建议使用嵌套try-catch-finally子句
in.dispose();
}
} catch (Exception e) {
System.out.println("InputFile construction failed!");
}
}
能够抛出任意类型的Throwable
对象,分为两种类型:Error
表示编译时和系统错误,一般不用关心;Exception
是可以被抛出的基本类型
通常,异常对象中仅有的信息就是异常类型,除此之外不包含任何有意义的内容(最重要的部分就是异常类型)
异常处理理论上有两种基本类型:
分类:虚拟机能处理的 -- 不受检查异常;虚拟机不能处理的 -- 被检查异常???
extends Exception
实现;必须要在方法上主动throws
指明RuntimeExcpetion
及其子类,会自动被Java
虚拟机抛出,不必在异常说明将它们列出来;这种异常属于编程错误,通常不用在代码中捕获,但是可以抛可以声明方法将抛出的异常,实际上却不抛出,为异常先占个位置;定义抽象基类和接口时可以进行异常预留
栈轨迹:printStackTrace()
方法所提供的信息可以通过getStackTrace()
方法访问,将返回一个由栈轨迹中的元素所构成的数组,其中每一个元素都表示栈中的一帧。元素0是栈顶元素,并且是调用序列中的最后一个方法调用;数组中的最后一个元素和栈底是调用序列中的第一个方法调用
重新抛出异常:
fillInStackTrace()
的代码行会成为新的异常发生处fillInStackTrace()
,且有关原来的异常发生点的信息会丢失在Throwable的子类中,只有三种基本的异常类提供了带cause的构造器:Error、Exception、RuntimeException。如果要把其他类型的异常链接起来,应该使用initCause方法而不是构造器
Exception
对象都是使用new
在堆上创建的对象,垃圾回收器会自动执行清理
无论try
块中的异常是否抛出,finally
中的语句总能被执行(可用于实现重试机制、释放资源)
当涉及break
和continue
语句的时候,finally
子句也会得到执行(如果 -- 不建议在finally
子句中存在return
语句,那么此返回会覆盖所有其他的return
finally
中使用return
)
异常丢失
当覆盖方法时,只能抛出在基类方法的异常说明里列出的异常(也可以不抛出异常或抛出更小的异常)
一个方法同时存在于抽象基类和接口中(两种方法定义抛出不同类型的异常),子类继承并实现时,接口方法抛出的异常不能改变抽象基类中的异常(两者的异常必须相同或子类不抛出任何异常)
对于在构造阶段可能会抛出异常,并且要求清理的类,最安全的使用方式是使用嵌套的try-catch-finally
子句
异常匹配:抛出异常时,异常处理系统会按照代码编写顺序找出“最近”的处理程序(匹配成功即不再继续查找)【派生类的对象也可以匹配其基类的异常处理程序】
被检查异常处理方式;
RuntimeException(e)
)extends RuntimeException
异常public class TestClazz{
private Formatter formatter;
private String name;
public void testFormat(int x, int y){
// 内容输出格式化
formatter.format("The turtle %s is moving to (%d, %d)", x, y);
// 更复杂的格式化
formatter.format("%-15s %5s %10s\n", "Item", "Qty", "Price");
}
public String toString(){
// 无意识的递归:编译器尝试将this转换为String进行拼接(转换方式为 this.toString())
//return " TestClazz address: " + this + "\n";
// 输出当前对象的内存地址,正确的方式是调用 Object.toString()
return " TestClazz address: " + super.toString() + "\n";
}
public static void main(String[] args){
// Formatter构造时接受一个输出内容目的地的参数;存在多种重载
Formatter f = new Formatter(System.out);
}
}
不可变的String
:String
对象是不可变的,String
类中每一个看起来会修改String
值的方法,实际上都会创建一个全新的String
对象,以包含修改后的字符串内容,只读特性
每当把String
对象作为方法的参数时,都会复制一份引用,而盖引用所指的对象其实一直都在单一的物理位置上,从未改变
用于String
的+
和+=
是Java
中仅有的两个重载过的操作符,而Java
并不允许程序员重载任何操作符
+
与+=
会被重载为使用StringBuilder
实现- 如果可以预知最终的字符串有多长,预先指定
StringBuiler
的大小可以避免多次重新分配缓冲
StringBuffer
:线程安全,开销更大
如果要在toString()
方法中使用循环,最好自己创建一个StringBuilder
对象
如果希望toString()
方法打印对象内存地址,可以考虑使用this
关键字
无意识的递归
格式化输出:
System.out.format()
System.out.printf()
Formatter
:在Java
中所有新的格式化功能都由java.util.Formatter
类处理,接受一个参数表明内容的输出目的地,常用的包括PrintStream
、OutputStream
、File
格式化符说明:%[argument_index$][flags][width][.precision]conversion
-
改变对齐方向width
应用于各种数据类型的数据转换,且行为方式都相同precision
不是所有类型数据都支持;对于不同的数据类型,其意义也不相同;对整数应用precision
会触发异常正则表达式
匹配一个反斜杠需要四个反斜杠
\\\\
,第一个:转义符,第二个:斜杠本身,第三个:转义符,第四个:斜杠本身
\
在Java
和正则表达式中都是转义字符,所以表示斜杠时都需要书写为:\\
理解:正则中的
\
,使用字符串表示即\\
,而每一个斜杠都需要一个转义符,结果即\\\\
表示一个斜杠
组:用括号划分的表达式,组号0
表示整个表达式,组号1
表示被第一队括号括起的组,以此类推
A(B(C))D
:有三个组,组0是ABCD;组1是BC;组2是C
java.util.Matcher
提供了方法获取组相关信息,groupCount(),不含组0
、group()
。。。
几个特殊符号
字符 | 作用 |
---|---|
\n | 换行 |
\r | 回车 |
\t | 制表(相当于tab) |
\f | 换页 |
Scanner
定界符:默认情况下,Scanner
使用空白字符对输入进行分词,方法useDelimiter()
可以自定义定界符
// 初始化的时机
public class Initialization{
// static final 的编译期常量读取时不会触发初始化
static final int staticFinal = 47;
// 非编译器常量,读取时会触发初始化
static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
// static非final域,读取时需要执行
static int staticNonFinal = 147;
// JDK11+会产生告警
Class<Integer> intClass1 int.class;
Class<Integer> intClass2= int.class;
// Integer extends Number,但Number Class域Integer Class之间没关系
// Class<Number> numberClass = int.class;
// 使用通配符?,好处是实现选择非具体的类引用(不会产生任何的告警)
Class<?> numberClass = int.class;
// 创建类型子类型,? extends T 创建一个范围(规定上限)
Class<? extends Number> bounded = int.class;
// ? super T 也是创建一个范围(规定下限)
// FancyToy extends Toy
Class<FancyToy> fancyToyClass = FancyToy.class;
// 可以直接获取到具体类型
FancyToy fancyToy = fancyToyClass.getDeclaredConstructor().newInstance();
Class<? super FancyToy> superClass = fancyToyClass.getSuperclass();
// todo 只允许将超类声明为“某个类,是FancyToy的超类(非接口)”
// Class<Toy> superclass2 = fancyToyClass.getSuperclass();
// 这里就只能是 Object 了
Object object = superClass.getDeclaredConstructor().newInstance();
}
运行时类型识别(RTTI,Run-Time Type Identification):在运行时,识别一个对象的类型。(在Java
中,所有的类型转换都是在运行时进行正确性检查的)
Java
中每个对象都有相应的Class
对象,因此,可通过Class
对象知道某个对象‘真正’所属的类;无论我们对引用进行怎样的类型转换,对象本身所对应的Class
对象都是同一个。当我们通过某个引用调用方法时,Java
总能找到正确的Class
类中所定义的方法,并执行该Class
中的代码。由于Class
对象的存在,Java
不会因为类型的向上转型而迷失,这也是多态的原理
Java
使用Class
对象来执行其RTTI
,即使正在执行的是类似转型的操作
对于基本数据类型的包装器类,有一个标准字段TYPE
,是一个引用,指向对应的基本数据类型的Class
对象
基本数据类型 | 包装器类 |
---|---|
boolean.class | Boolean.TYPE |
char.class | Character.TYPE |
...... | ...... |
以上两种表示方式是等效的。(建议使用.class
的形式,保持与普通类的一致性)
加载类的准备工作包含三个步骤:
类加载器
执行,查找字节码
并从字节码创建一个Class
对象验证类中的字节码,为静态域分配存储空间
,必须的话,将解析这个类创建的对其他类的所有引用执行静态初始化器和静态初始化块
初始化触发
.class
来创建Class
对象的引用时,不会自动的初始化该Class
对象Class.forName
会立即就触发初始化static final
的值(必须是编译期常量
),不会对所在类进行初始化static
域为非final
,读取之前需要进行链接(分配存储空间)和初始化(初始化该存储空间)将泛型
语法应用于Class
对象时(参考上面的代码说明)
? extends T
,newInstance()
将返回该对象的真实类型? super T
,newInstance()
仍旧只能返回Object
新的类型转换语法:java.lang.Class#cast
RTTI
表现形式
Class
对象。通过查询Class
对象获取运行时所需的信息instanceof
instanceof
与isInstance
结果完全等效
在动态代理上所作的所有调用都会被重定向到单一的调用处理器(InvocationHandler)
上,它的工作是揭示调用的类型并确定响应的策略
将interface
实现为私有内部类、匿名类都无法阻止通过反射
在运行过程中的修改操作
// 泛型类定义
public class GenericClazz<T>{
public static <K, V> Map<K, V> map() {
return new HashMap<>();
}
// 泛型数组
// 使用类型标记 Class<T> 执行数组的创建以便从擦除中进行类型恢复
// 泛型数组的运行时类型只能是 Object[]
private T[] array;
public GenericClazz(Class<T> type, int sz) {
array = (T[])Array.newInstance(type, sz);
}
public static void main(String[] args) {
// 自动类型推断
Map<String, List<? extends Pet>> map = New.map();
}
}
// 泛型方法
public <T> void f(T t){
// 泛型方法定义:需要将泛型参数列表置于返回值之前
}
// 泛型多边界定义
class MultiBounded<T extends ConcreteOrAbstractClazzOrInterface & Interface1 & Interface2>{
// 泛型边界支持多边界
// 1、必须具体类在前,接口在后
// 2、具体类可以是普通类或抽象类或接口
// 3、具体类只能存在一个,接口可以多个
}
class Fruit{}
class Apple extends Fruit{}
class Plate<T>{
private T item;
public Plate(T item) {
this.item = item;
}
public T get() {
return this.item;
}
public void set(T item) {
this.item = item;
}
public static void main(String[] args) {
// p是协变的
// JVM会使用一个占位符 CAP#1 来表示p接受一个Fruit或子类,运行时通过 CAP#1 把类型擦除了
Plate<? extends Fruit> p = new Plate<>(new Apple());
p.set(null);
// 无论如何都不能赋值一个 CAP#1 类型 ==> Java类不存在一个公共的子类??
// p.set(new Fruit());
// p.set(new Apple());
// CAP#1 表示的是 Fruit及其子类
Fruit fruit = p.get();
Object object = p.get();
// Apple apple = p.get();
}
}
// 1、Payable<Employee>与Payable<Hourly>泛型移除后简化为相同的类Payable,下面的代码编译报错
// 2、如果移除Payable<T>的泛型,编译正常(有趣。。。)
interface Payable<T>{}
class Employee implements Payable<Employee>{}
//class Hourly extends Employee implements Payable<Hourly>{}
// CRG
class BasicHolder<T> {}
class Subtype extends BasicHolder<Subtype> {
// 基类用导出类替代其参数(泛型基类变成了一种其所有导出类的公共功能的模板)
}
// 自限定
class SelfBounded<T extends SelfBounded<T>>{
// 强制泛型当作其自己的边界参数来使用;只能强制作用于继承关系
// 自限定希望的用法:class A extends SelfBounded<A>{}
}
// 参数协变
interface SelfBoundSetter<T extends SelfBoundSetter<T>>{
void set(T arg);
}
interface Setter extends SelfBoundSetter<Setter>{}
void test(Setter s1,Setter s2,SelfBoundSetter sbs){
s1.set(s2);
// 使用了自限定类型,编译器不能识别将基类型当作参数传递目标方法的尝试。
// 不使用自限定类型,普通的继承机制会介入,能够实现重载
// s1.set(sbs);
}
元组(tuple)
:与List
同,都可用于数据存储,包含多个数据。不同的是:列表只能存储相同的数据类型,元组可以存储不同的数据类型,如同时存储int
,string
,list
等,并且可以根据需求无限扩展
泛型可以使用在类或方法上
应该尽量使用泛型方法
static
的方法无法访问泛型类的类型参数;如果static
方法需要使用泛型能力,就必须使其称为泛型方法
类型参数推断:使用泛型类时,必须在创建对象时指定类型参数的值;使用泛型方法时,编译器会自动推导出具体类型
类型推断只对赋值操作有效,其他时候并不起作用。如果将一个泛型方法调用结果作为参数传递给另一个方法,此时编译器只会认为接受的是一个Object
类型的变量
在泛型代码内部,无法获得任何有骨干泛型参数类型的信息。Java泛型是使用擦除
来实现的,任何具体的类型信息在运行时都被擦除了,都会称为原生
类型;泛型类型参数将擦除到它的第一个边界
泛型并不是Java
语言的一种特性,它是Java
泛型实现中的一种折中;泛型类型只有在静态类型检查期间才出现
泛型兼容性:泛型化的代码必须能够与非泛型化的代码共存。使用擦除
是目前唯一可行的解决方案(从非泛化代码到泛化代码的转变过程;在不破坏现有类库的情况下将泛型融入Java语言)
在泛型中创建数组,推荐使用Array.newInstance()
即使擦除在方法或类内部移除了有关实际类型的信息,编译器
仍然可以保证在方法或类中使用的类型的内部一致性
不能创建泛型数组。一般的解决方案为在任何想要使用数组的地方都使用ArrayList
成功创建泛型数组的唯一方式就是创建一个被擦除类型的新数组,然后对其进行转型(转型后生成的数组并不能做任何操作;不管怎么转换,实际的运行时类型始终是Object[]
。数组在运行时类型只能是Object[]
)
泛型数组在运行时只能是Ojbect[]
;直接获取其数组同时会触发编译器警告及运行时ClassCastException
,原因是运行时无法获取其真实类型。使用类型标记Class<T>
创建数组不能改变运行时类型,但可以从类型擦除中恢复
泛型边界:重用了extends
关键字;可以定义多边界;通配符(?)被限定为单一边界
通配符?与类型参数T区别:
<? extends T>
不能往里存,只能能往外取(被称作协变)
往里存:不能调用<? extends T>
泛型类的以T
为形参的方法
往外取:可以调用<? extends T>
泛型类的以T
为返回值的方法
不能往里存的根本原因是因为,Java没有一个共同的子类,却有一个共同的父类
Object
。所以永远可以向上转型取数据,却不能向下转型存数据
<? super T>
不影响往里存,但是往外取却只能放在Object
(被称为逆变)
容器中存放的是T
的任意基类,但是不确定是哪一个基类
往里存:往容器中放T
的子类一定成立(这些类一定是<? super T>
的子类)
往外取:编译器不知道具体存放的类,所以取出来的是所有类的共同父类Object
PECS(Producer Extends Consumer Super)
extends
super
List<?>
与List
List
实际上表示“持有任何Object
类型的原生List
”List<?>
表示“具有某种特定类型
的非原生List
,只是不知道那种类型是什么”任何基本类型都不能作为类型参数<T>
;自动包装机制不能应用于数组
古怪的循环泛型(CRG):基类用导出类替代其参数(泛型基类变成了一种其所有导出类的公共功能的模板)
自限定:强制泛型当作其自己的边界参数来使用;只能强制作用于继承关系
参数协变:使用了自限定类型,编译器不能识别将基类型当作参数传递目标方法的尝试(此方法接收基类作为参数)
类型参数可能会在一个方法的throws
子句中用到
混型:混合多个类的能力,以产生一个可以表示混型中所有类型的类。价值之一是它们可以将特性和行为一致的应用于多个类之上。基于继承实现。
原文:https://www.cnblogs.com/yangcxx/p/15108782.html