目录
- 3 java基本的程序设计结构
- 3.1 命名规范
- 3.2 数据类型
- 3.3 运算符
- 3.4 枚举类型:
- 3.5 字符串
- 3.6 大数值 BigInteger和BigDecimal
- 3.7 数组
- 4 对象与类
- 4.1 识别类
- 4.2 类之间的关系
- 4.3 自定义类
- 4.4 类的方法
- 4.5 高级文档注释
- 5 继承
- 5.1 超类和子类
- 5.2 多态
- 5.3 抽象类
- 5.4 反射
- 6 接口与内部类
- 6.1 Comparable接口
- 6.2 Cloneable接口
- 6.3 内部类
- 6.4 代理
- 10 部署应用程序和applet
-
- 12 泛型程序设计
- 12.1 简单泛型类
- 12.2 简单泛型方法
- 12.3 类型变量的限定
- 12.4 类型擦除
- 12.5 翻译泛型表达式
- 12.6 桥方法
- 12.7 使用泛型时的约束和局限性总结
- 12.8 泛型之间的继承问题
- 12.9 通配符类型编程(注意它和泛型编程的区别)
3 java基本的程序设计结构
3.1 命名规范
可参考 《阿里云java开发规范》
- 以字母开头
- 驼峰命名法
- 类名首字母大写
- 私有方法以 m 字母开头
- Exception,Test类或者方法,以该单词结尾
- 不出现魔法变量,常量全大写,尽量表达清楚语义,不要嫌命名长
- 不出现中英文夹杂
3.2 数据类型
共有 8 种基本类型:
- 4种整型:int(4字节),short(2字节),long(8字节),byte(1字节)
- 2种浮点型:float(4字节,有效位6位),double(8字节,有效位15位)
- 1种字符类型:char
- 1种布尔型:boolean
strictfp关键字:处理a * b / c,jvm 默认先截断 a * b,再除以 c,而加了这个关键字后,丢给底层 intel去处理中间的截断,这样就更加精确。
3.3 运算符
- 三元运算符(a>b?a:b) 比 if-else 高效:涉及cpu流水线作业和分支预测,例如遇到if-else分支的时候,会先预测一个分支,成功了那么效率得到提高,但是预测失败就会付出代价;而三元运算符直接计算两个分支,最后比较结果,虽然指令增加了,但是cpu擅长处理指令。
- 位运算:&,|,^,>>, <<,~。
- x&(x-1):用于右边取消 x 的最右边一位 1。应用:判断是否是2^n;判断整数A转化为整数B变了多少位
- x & 1 :可以判断奇偶数
- a^ b^b=a:异或可以将偶数重复的数消除。应用:一堆偶数重复的数中找一个或者两个独立数
- 异或还可以应用于不借助temp交换两个数,思想就是异或可以拿到公共的 1: a^ =b;b^ =a;a^=b
- >>1:等同于除以2;<<1:等同于乘2
- 正负数交换:~x+1,即大计基中的取反加1;这里也涉及到绝对值求法
3.4 枚举类型:
enum 关键字:
- 使用方法:它类似class关键字,但是内部只有常量,可以定义在class类内部或者外部
- 原理:继承自java.lang.Enum,会编译成一个 .class文件,也就是说它会是一个实实在在的类。在底层,编译器会自动生成很多代码,比如将每个常量赋值为static final类等,实际上让程序员偷懒了。
参考博客
public class hello3 {
enum Size {SMALL, MEDIUM, LARGE};
public static void main(String args[]) {
Size s = Size.SMALL;
System.out.println(s.toString()); // output: SMALL
System.out.println(s.ordinal()); // 0
}
}
3.5 字符串
String 关键字
- java 中字符串不是字符数组,是一个不可变的整体
- String 中直接赋值和new赋值不同,前者放在常量池中,如果常量池中存在了,那么不会创建;后者放在堆中,会一直新建对象。
- String 已经重写了equals(),hashcode(),可以直接作为hash key,但是如果自定义类作为hash key也应该重写这两个方法。下面比较一下,equals,hashcode(),==:
- equals(): 默认equals和==都是比较对象的地址,object类定义的,所以每个对象都有,一般用于比较引用数据类型,此时应该重写equals()方法,改为比较其内部成员的值
- hashcode(): 默认hashcode()是object提供的,但是String重写了,是根据内容来计算hashcode。String重写hashcode()的源码里会包含 31 这个魔法数字,原因如下:①31会被jvm优化为按位操作,31 * i = (i<<5)-1;②31是一个5位的不大不小的质数,选择质数作为哈希乘子,是因为,反过来取模的时候,可以让余数均匀分布。(注意:哈希操作是正向乘,逆向mod)
- ==:如果是基本类型,那么是值比较;如果是引用类型,那么比较地址。
- 比较equals()和hashcode():hashcode()相等,equals不一定相等,equals相等,hashcode一定相等。
String Buffer 和 String Builder
-
StringBuffer 和 StringBuilder 长度可变
-
多线程环境下,StringBuffer 线程安全, StringBuilder 线程不安全,StringBuffer通过对 append 方法加锁来达到线程安全的,builder没有加锁。
-
单线程使用时,StringBuilder 速度快
3.6 大数值 BigInteger和BigDecimal
3.7 数组
Arrays 好用的 API
- 打印数组:Arrays.toString(array);Arrays.deepToString(arrays),打印高维数组
- 匿名数组:a = new int[] {1, 2, 3, 4}; // 匿名数组写法,好处是不创建新变量重新初始化数组
- 拷贝数组:普通拷贝是引用拷贝使用统一数据源,调用 Arrays.copyOf() 是拷贝一个新数组,使用不同数据源
- 数组排序:Arrays.sort()采用了优化的快排
- 二分查找:Arrays.binarySearch()
- 不规则数组
4 对象与类
4.1 识别类
在一个系统中,类应该是一个名词,方法应该是一个动词
4.2 类之间的关系
其实有时候更多是结合业务背景的语义来判断二者是什么关系,直接通过代码不好判断
- 依赖关系(user-a),类A依赖类B,表现为类B作为类A的方法中的形式参数,或者类A调用类B的静态方法,或者类B作为类A的方法中的局部变量。
class Car {
public static void run() {
System.out.println("汽车在跑");
}
}
class Driver {
public void driver(Car car) {
car.run();
}
public void driver() {
Car car = new Car();
car.run();
}
public void driver1() {
Car.run();
}
}
- 聚合关系(has-a),类A聚合类B,表现为类B作为类A的成员变量
- 继承关系(is-a)
4.3 自定义类
-
在 java 中对象的拷贝必须使用clone()方法才能获取完整的拷贝
-
this 关键字可以理解为方法的隐式参数,可以区别方法中的局部变量和类的成员变量;也可以理解为是类对象的实例;this关键字还可以作为该类的另一个构造器
-
假设类中的成员变量含有引用型变量 a,那么 a 的 getter 应该返回 a.clone()。这里涉及到了对象的深拷贝和浅拷贝:
-
深拷贝:一个对象中引用型成员变量内的属性也拷贝,换句话说深层次的属性成了两份
-
浅拷贝:仅拷贝引用,换句话说对象中引用型成员变量的属性只有一份
class A {
private int attrA;
public Object clone() {
A a = new A(this.attrA);
return a;
}
}
class B {
private A a;
private int attrB;
public A getA() {
return (A) a.clone();
}
public Object clone() {
B b = new B(this.a, this.attrB);
return b;
}
}
public class hello3 {
public static void main(String args[]) {
A testA = new A(1);
B testB = new B(testA, 2);
A myA = testB.getA();
myA.setAttrA(11);
System.out.println(testB.getA().getAttrA());
System.out.println(myA.getAttrA());
}
}
- final 变量也可以更改,例子就是 System 类的 out final 变量有 setOut 方法来改变 out,因为 out 的实现是 native 方法,由其他语言实现的,所以绕过了 java 的存取控制机制。
- java中的方法参数是值调用。这里要注意区分按引用调用,按值调用,引用型参数类型,值类型参数类型。
- 从 c语言的角度来分析java对象的传递的情况,其实相当于c语言的指针传递,在函数内部中可以修改实参的指向,而c语言中引用传递是指函数的参数是一个别名。
- 基于此,java如果要实现swap,那么应该通过一个引用的指向去swap。(内置的IntHolder类就是这么做的)
int main(void){
int a = 1;
test(a);
printf("out: %d\n", a); // output: 3
}
void test(int &p){ // p 相当于 a 的一个别名,对形参的更改会引起实参本身的变化
p = p+2;
printf("in: %d\n", p); // output: 3
}
void testPointer(int *p){ // 指针传递,对形参本身的更改不会引起实参的变化,但是对指向内容的更改会引起变化
}
4.4 类的方法
- 构造方法:
- 重载:不仅仅构造方法可以重载,java所有方法都支持重载,注意重载的方法返回类型jdk5之前必须相同,但是现在可以是子类型。
- 默认构造器:注意,当显示提供了自定义构造器时,不自动生成默认构造器也就不能使用。只有在没有提供任何自定义构造器时候,才会自动生成。
- this关键字调用另一个构造器
class A {
int a, b;
public A(int a) {
this(a, 2);
}
public A(int a, int b) {
this.a = a;
this.b = b;
}
}
- 初始化代码块:赋值初始化<初始化代码块<构造器初始化。通常可用于静态变量复杂的初始化。
- package 的命名为什么以 com.how2j.** 形式命名,这是因为 Sun 公司提出建议以域名逆序形式命名,因为域名是唯一的,这样就保证 package 的绝对唯一性。
4.5 高级文档注释
除了基本的//和/**/之外应该掌握文档注释
javadoc对注释生成文档,可以使用标记:@author,@version,@return,@params,@see等进行注释
public class hello3 {
/**
* @author 陈
* @version 1.0
*
*/
/**
* hello 方法的简述
* <p> hello 方的详细说明 </p>
* @param str 问候语
* @param name 名字
* @return return 返回格式化字符串
*/
public String hello(String str, String name) {
return str+"," + name +"!";
}
}
5 继承
5.1 超类和子类
- 置换规则:程序中出现超类的地方,都可以用子类替换;向上转型就是这个意思。
5.2 多态
5.3 抽象类
- 抽象方法必须在抽象类中
- 抽象类中可以有其他方法
- 抽象类不能有实例,但是可以有抽象变量,该抽象变量只能指向子类的实例。比如:
// Person 是一个抽象类,Student 是继承 Person 的非抽象类
Person p = new Student(); // p 是一个抽象变量,指向子类对象
5.4 反射
总的来说就是动态获取类信息,例如在 android 项目开发中,比如有三个Acitivity类,A1,A2,A3,此时运行中的 Activity 可能是 A1 或者 A2,如果 A3 中有两个方法,method1 和 method2,如果是A1->A3,那么执行 method1;如果是 A2->A3,那么执行 method2。一个简单的策略是可以通过Intent进行通信,但是如果有很多 Activity,不同 Activity 要分发不同的 Intent 到 A3 中执行不同的 method,此时通过 Intent 消息通信就比较麻烦。这时就可以利用反射机制,动态的获取当前运行态的 Activity 类的信息分发到 method 中,无疑比 Intent 消息传递来的更加简便。
- 反射可以拿到 Class 的信息,但是通过 Class 的newInstance()方法无法真正的实例化一个对象,因为无法拿到对象的实例域,方法等。而通过 Field,Method,Constructor可以拿到对象的实例域,方法,构造器。还可以通过 Modifiers 类拿到 public 这样的修饰符。
- 可以通过 getClass(),Class.forName() 两个方法拿到对象的Class
- 可以通过 newInstance() 实例化一个 Class 的对象,但是无法通过此对象调用它的方法,实例域等
- 要想调用方法必须接着 Method 类,首先调用 getMethod()获得Method类获得方法指针,然后调用invoke来使用方法
...
try {
Class obj = Class.forName(personName);
Method method = obj.getMethod(methodName, null);
method.invoke(obj.newInstance(), null);
} catch (Exception e) {
e.printStackTrace();
}
...
6 接口与内部类
标记接口是指仅有一个名字,没有内容的接口
6.1 Comparable接口
略
6.2 Cloneable接口
Class Employee implements Cloneable{
public Employee clone() throws CloneNotSupportedException{
return (Employee) supper.clone();
}
}
6.3 内部类
内部类主要分为普通内部类**(成员内部类)、局部内部类(方法内部类)、匿名内部类、嵌套内部类(静态内部类)**。
非静态内部类中不能定义静态成员,静态内部类不能访问外部类普通成员变量(非静态成员)。
- 成员内部类:
- 内部类可以访问外部类的所有成员
- 内部类中的 this 指的是内部类的实例对象本身,如果要用外部类的实例对象: 类名 .this。
- 方法内部类:只能访问方法中的变量,且变量必须式final方法,也可以是final数组
- 匿名内部类:多用于回调响应,还有lambda表达式。
/*第一段代码:普通的用法*/
interface A{
int add(int a, int b);
}
public class hello3 {
public static void main(String args[]) {
A a = (param1, param2)->{return param1+param2;};
System.out.println(a.add(1, 1)); // output: 2
}
}
/*第二段测试代码:实现runable接口,从下面的代码可以看出runable接口实际上是一个接口变量*/
...
new Thread(()->{System.out.println("do something");}).start();
...
/*第三段代码:函数参数是接口变量*/
interface A{
int add(int a, int b);
}
public class hello3 {
public static void main(String args[]) {
int param1 = 1;
int param2 = 1;
test((int i, int j)->{ // 怎么感觉这么鸡肋
return i+j;
}, param1, param2);
}
// a 是一个接口变量
public static void test(A a, int param1, int param2) {
System.out.println(a.add(param1, param2));
}
}
/*第四段代码: 不用 lambda 版本,用匿名内部类*/
...
test(new A() {
@Override
public int add(int a, int b) {
return a+b;
}
}, param1, param2);
...
- 静态内部类:只能访问外部类静态的东西,自己应该声明为public
6.4 代理
- 代理的概念:就像通过经纪人去联系明星,经纪人就是代理类。
- 静态代理:静态创建一个代理类。
- jdk动态代理:交给jdk代理工厂创建一个新类。 这个新类的特点是:①运行时创建的;②实现了一组指定的接口。
- 子类动态代理(cglib代理):java默认的代理是目标对象必须实现接口才能被代理,但是有时候要代理没有实现接口的对象,这时候就可以采用cglib代理,cglib和ASM字节码框架有关。
- javassist动态代理:dubbo使用的这个代理机制,因为它可以动态生成类,所以动态代理就不再话下
①静态代理和目标实现同样的接口,当目标改动的时候,静态代理也要改变。当有多个静态代理的时候,修改起来比较麻烦,所以出现了动态代理。
②感觉动态代理就是要在运行时创建类对象,因为创建了类对象,只能知道它的信息,只能通过Method类获取方法,在类中通过method.invoke()去调用被代理的对象的方法,然后自己在method前后添加逻辑即可。
- 字节码框架:
- javassist:javassist是基于反射的,感觉就是通过javassist的api去写一个类和类中的方法
- ASM:直接操作字节码文件结构的,比较难搞懂
/*javassist框架实现的代码*/
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
public class MyGenerator {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
// 创建 Programmer 类
CtClass cc= pool.makeClass("com.samples.Programmer");
// 定义方法
CtMethod method = CtNewMethod.make("public void code(){}", cc);
// 插入方法代码
method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");
cc.addMethod(method);
// 保存生成的字节码
cc.writeFile("d://temp");
}
}
- 代理的实现:
- 静态代理的实现:就是和目标实现统一接口api,访问静态代理达到访问目标的目的
- 动态代理的实现:依赖于jdk的 newProxyInstance()函数,通过method.invoke()调用父类方法,在invoke()中,可以在被调用的method前后添加逻辑。
- cglib代理:依赖于ASM字节码框架,生成子类,在子类中通过方法拦截器methodIntercept()调用父类方法,可以重写intercept在被抵用的method前后添加逻辑
/*jdk代理核心api*/
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
10 部署应用程序和applet
10.1 JAR包
- JAR:java归档,传统的是使用zip格式对图片,文本等压缩打包在一起,javaSE5之后使用pack200压缩技术打包,新的技术压缩率接近百分之90
- jar命令打包:jar options retFileName targetFile1 tartgetFile2
/* client input: jar options retFileName targetFile1 tartgetFile2 */
>> jar cvf first.jar picture.png
- 可运行的jar包:这个不太会,暂时略过。
- jar密封:其他类不被package命令加载进来,也暂时略过。
12 泛型程序设计
要记住几个事实:类型消除、限定类型、自动强制转换、桥方法
-
虚拟机中没有泛型,只有普通的类和方法;(类型消除)
-
所有的类型参数(T,U等)都用它们的限定类型替换;(限定类型)
-
为保持类型安全性,会加入自动强制转换(自动强制转换)
-
桥方法是因为子类方法未覆盖住父类方法,多态灾难;(桥方法)
12.1 简单泛型类
...
class<T, B, C, D> test{
...
}
12.2 简单泛型方法
// 注意类的T和泛型方法的T没关系
class Test<T>{
...
public static <T extends Father> T genericMethod(T a){
return a;
}
...
}
12.3 类型变量的限定
- 对类型变量 T 进行限定,比如限定这个 T 类型必须实现了 MyInterface 接口,
- 只能用 extends 关键字,
- 多个限定类用 & 连接
interface MyInterface1{
...
}
class MyClass implements MyInterface1, MyInterface2{
...
}
class FirstGeneric<T extends MyInterface1 & MyInterface2>{
...
}
12.4 类型擦除
- 类型擦除,原始类型,限定类型:
- 泛型会发生类型擦除,然后变为原始类型。
- 默认T 的原始类型是Object,如果T有限定,那么原始类型就是限定类型。
- 在泛型中能不能调用原始类型的method,取决于原始类型有没有这个方法。比如原始类型如果是默认类型,就会调用失败:【T.method() <=> Object.method(),因为类型擦除为 Object,而Object中没有method(),所以失败了】
- 要想成功调用泛型方法,要满足两个条件:①具体类是限定类子类;②限定类实现了method()。
实际上类型擦除后,程序员是否正确使用泛型类,在编译前,IDE 就会自动检查,真方便
class Test<T>{ // 类型擦除为原始类型
T attr;
}
/* <=> */
class Test{
Object attr; // 默认为 Object
}
class Test<T extends Father & Mother>{ // 类型擦除为离得近的限定类型 Father,而mother不管她
T attr;
}
/* <=> */
class Test{
Father attr; // 此时是Father
}
class A{ // 单独的具体类
public void noMethod(){
System.out.println("run noMethod");
}
}
class Father{ // 泛型限定父类
public void method(){
System.out.println("run FatherMethod");
}
}
class Child extends Father{ // 继承了限定类的具体类
public void childMethod(){
System.out.println("run ChildMethod");
}
}
class Test<T extends Father>{ // 泛型类
public void testMethod(T t){
t.method(); // 调用父类已经实现的方法,并且具体类是父类的子类才会成功
}
public void testNoMethod(T t){
t.noMethod(); // 因为父类没有实现 noMethod(),所以即使传进来的 T 类已经实现了 noMethod() , 依然会报错
}
public void testChildMethod(T t){
t.childMethod(); // 父类没有实现 childMethod
}
}
public class hello{ // 主类进行测试工作
public static void main(String args[]){
Test<A> t = new Test<>();
A a = new A(); // 单独的具体类
t.testMethod(a); // 失败,限定类实现,但是具体类A非子类
t.testNoMethod(a); // 失败,限定类未实现,且具体类A非子类
Test<Child> childT = new Test<>();
Child child = new Child(); // 限定类子类
childT.testMethod(child); // 成功,满足两个条件
childT.testChildMethod(child); // 失败,限定类未实现
}
}
12.5 翻译泛型表达式
- 编译器自动强制转换:就是调用getter的时候,会先转换为原始类型,然后再自动强制转换。
class Test<T>{
private T attr;
public Test(T attr){ // 构造器
this.attr = attr;
}
public T getAttr(){ // getter 方法返回一个 T 类型
return this.attr;
}
}
public class hello{
public static void main(String args[]){
Test<Integer> test = new Test<>(123);
Integer myInt = test.getAttr(); // <=> test.getAttr()先返回Object,然后Integer强制转换: Integer myInt = (Integer) test.getAttr();
}
}
- 编译器的构造方法:应该构造方法就显示指明泛型类,否则会出现下述 par1 的现象,即编译器发生类型擦除后默认是Object类,然后jvm自动强制转换将Object转换为String,所以 getSecond() 应该返回 String,实际上却是Integer。
class Pair<T>{
private T first=null;
private T second=null;
public Pair(T fir,T sec){
this.first=fir;
this.second=sec;
}
public T getFirst(){
return this.first;
}
public T getSecond(){
return this.second;
}
public void setFirst(T fir){
this.first=fir;
}
}
...
Pair<String> pair1=new Pair("string",1); // 构造出来没问题,但是在调用 getSecond()出错
Pair<String> pair2=new Pair<String>("string",1) // 编译器直接出错
...
12.6 桥方法
- 由于继承泛型的时候,子类没有覆盖住父类的方法,导致多态出现了问题:下面代码中,程序员的本意是想要用子类setter去覆盖父类setter,结果由于类型擦除,父类的setter成了 setFirst(Object fir),因此子类中出现了两个 setter,编译器的解决办法就是桥方法。
- 桥方法:继承的父类setter中调用子类的setter。
- 桥方法在 getter 的时候会很神奇:虽然jvm不允许程序员编写方法签名一样的方法,但是它自己可以生成方法签名一样的方法。因为jvm是根据返回值和方法签名唯一确定一个方法的。
class Pair<T>{
public void setFirst(T fir){...}
public T getFirst(){...};
}
// 讨论 setter
class SonPair extends Pair<String>{
public void setFirst(String fir){...} // 此时由于类型擦除现象会有两个 setter,无法覆盖父类 setter
public void setFirst(Object fir){ // jvm编译期间自动生成桥方法,程序员不可见
setFirst((String) fir) // 子类的setter,且强制转换参数
}
}
// 讨论 getter
class DaughterPair extends Pair<String>{
public String getFirst(){...}; // 仍然有两个 getter
public Object getFirst(){...}// 会出现神奇的事情,桥方法会出现签名一样的方法,虽然程序员不能编写签名一样的方法,但是jvm是通过返回值和方法签名唯一确定一个方法,所以它自己可以生成这种签名一样的方法
}
12.7 使用泛型时的约束和局限性总结
- 泛型类中,不能在静态域,静态方法中引用泛型变量(如果是泛型方法,允许使用)
class Pair<T>{
...
private static T a ; // 禁止
private static T get(){...}; // 禁止
private static set(T t){...}; // 禁止
...
}
...
Pair<String>[] stringPairs=new Pair<String>[10]; // 禁止
...
- 禁止使用 new T(…) 这样的表达式,可以通过Class类本身也是泛型Class t,然后利用反射t.newInstance()方法来实现功能。
...
public static <T> Pair<T> makePair(Class<T> cl){
try{
return new Pair<T>(cl.newInstance(), cl.newInstance());
}catch(Exception e){
return null;
}
}
/*
等价于下面这种写法,但是下面这种写法不支持
<=>
public Pair(){
fir = new T();
sec = new T()
};
*/
...
...
/* 调用方法 */
Pair<String> p = Pair.makePair(String.class);
...
12.8 泛型之间的继承问题
如下问题 Test 不是 Test 的子类,这就引出了通配符问题。
class Father{
...
}
class Son extends Father{
...
}
class Test<T>{
...
}
...
Test<Son> 不是 Test<Father> 的子类
...
12.9 通配符类型编程(注意它和泛型编程的区别)
感觉书上的写的看着好累,但是确实经典,但是解释不全面
参考博客1,总结得很到位
参考博客2,讲解得很仔细
参考博客3,讲到了协变,逆变,不变的概念
参考博客4,解释的很完美啊,和自己的理解也特别符合,和我的用例也相当符合
- 协变,逆变,不变:
- 协变:当A≤B时有f(A)≤f(B)成立
- 逆变:当A≤B时有f(B)≤f(A)成立
- 不变:当A≤B时上述两个式子均不成立,即f(A)与f(B)相互之间没有继承关系
- 泛型方法与类型通配符的区别:
- 通配符方法比泛型方法更加通用
- 泛型是不变的,即使它的类型参数存在继承关系,但是整个泛型之间没有继承关系 : ArrayList list = new ArrayList(); -> Error
- 在java泛型中,引入了 ?(通配符)符号来支持协变和逆变,解决了泛型的继承问题
/*
* 下面代码中Child1和Child2是Father的儿子,A是泛型类
* Test类中有两个method:method1测试泛型方法,method2测试通配符方法
* 可以看见method1中,只能是Child1,而method2中还可以其他孩子
* 因此通配符方法比泛型方法更加通用
*/
class Father {
}
class Child1 extends Father{
}
class Child2 extends Father{
}
class A<T>{ // 泛型类
}
class Test{ // 测试类
public void method1(A<Child1> a) { // 泛型方法,只能是Child1
System.out.println("run method1");
}
public void method2(A<? extends Father> a) { // 通配符方法,可以是Father的所有孩子
System.out.println("run method2");
}
}
public class hello2 { // 主类进行测试工作
public static void main(String args[]) {
A<Child1> aChild1 = new A<Child1>();
A<Child2> aChild2 = new A<Child2>();
Test test = new Test();
test.method1(aChild1);
//test.method1(aChild2): // 其他孩子就报错了
test.method2(aChild1);
test.method2(aChild2);
}
}
/* 无法使用setter和getter的解释 */
class Father {
}
class Child1 extends Father{
}
class Child2 extends Father{
}
class GrandChild extends Child1{ // Father的孙子,Child1的儿子
}
class GrandGrandChild extends GrandChild{ // 曾孙子
}
class A<T>{ // 泛型类
private T t;
public A(T t) {
this.t = t;
}
public T get() {
return t;
}
public void set(T t) {
this.t = t;
}
}
class Test{ // 测试类
}
public class hello2 { // 主类进行测试工作
public static void main(String args[]) {
Father father = new Father();
Child1 child1 = new Child1();
Child2 child2 = new Child2();
GrandChild grandChild = new GrandChild();
A<Father> aFather = new A<Father>(father);
A<Child1> aChild1 = new A<Child1>(child1);
A<Child2> aChild2 = new A<Child2>(child2);
A<GrandChild> aGrandChild = new A<GrandChild>(grandChild);
A<? extends Father> aChild = aChild1; // ? 是子孙,它的 getter 没问题,但是 setter 报错
/*
* getter:? extends Father get(){...}
* setter: void set(? extends Father){...}
*/
Father fRet = aChild.get(); // getter 返回一个不确定的子类对象,编译器用capture#1占位符来代替这个不确定的子类,具体是什么不知道,只能用父类引用去接它
// aChild.set(child2); // 报错,因为必须是 capture#1 类型才允许插入,但是Father的子类无法匹配这个类型,所以就直接插不进去
A<? super GrandChild> bGrand = aGrandChild; // ? 是祖先,它的 getter 报错,setter 没问题
/*
* getter: ? super GrandChild get(){...}
* setter: void set(? super GrandChild){...}
*
*/
// Object obj = bGrand.get(); // 返回的是一个 capture#1 占位符父类对象,没有东西可以接它
bGrand.set(grandChild); // 参数是grandChild的所有父类引用占位符capture#1,所以设置子类没问题
bGrand.set(new GrandGrandChild()); // 设置了一个子孙对象
}
}
-
无限定通配符<?>:<? extends Object>,getter只能返回给Object,而setter不能使用
-
PECS 原则:producer extends consumer supper。注意:<? extends Father>,jvm底层理解为占位符capture#2,getter时候返回匹配的所有下界对象,只能用上面的父类去引用这个对象;而<? super Child>,也是理解为占位符,setter的时候匹配所有上界引用,只能用子类对象给它。总之,记住一句话,父类可以引用子类对象,子类不能引用父类对象。
-
通配符的捕获:因为通配符表示一个类型的范围,当通配符在运行的时候是一个确定的类型的时候,就可以用helper类捕获。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)