快速入门
计算机的核心作用就是处理数据, 变量用来存储单个数据, 数组用来储存一批数据, 对象用来存储一类数据
什么是对象: 对象就是一种特殊的数据结构, 在java中万物皆对象
面相对象编程的好处: 更加符合人类思维习惯
类和实例对象
在java中必须先设计类, 才能根据类创建对象
类是创建对象的模板, 对象是类的具体实例


语法




成员变量
- 类中的变量称为成员变量, 类中的方法称为成员方法
 - 完整定义格式:
 

- 一般无需指定初始值, 存在默认值
 

注意事项
- 类名称首字母大写, 满足驼峰模式
 - 一个代码文件中, 可以写多个class类, 但只能有一个用public修饰, public修饰的类名必须是java代码的文件名
 - 实际开发中, 建议一个文件只定义一个class类
 
执行原理

执行流程
- java程序运行在jvm虚拟机中
 - jvm虚拟机把内存划分为3块, 方法区, 栈内存, 堆内存
 - jvm虚拟机执行代码时, 首先把Test启动类提取到方法区中执行
 - 然后把Test启动类的main方法放到栈内存中运行
 - main方法中使用了Cart类, 虚拟机把Cart类提取到方法区
 - new Cart时,会在堆内存中开辟空间, 存放汽车对象, 汽车象会有一个类的地址. 指向方法区的汽车类
 - 存放汽车对象的变量地址会被赋值给左侧的c1变量, 对象创建完成
 - 对象变量c1通过变量地址, 找到堆内存中的汽车对象, 在找到对象对象的name属性, 完成属性赋值
 
注意事项:
- 对象与对象之间的数据不会相互影响, 除非多个变量指向同一个对象
 - 如果某个对象没有变量引用它, 该对象无法被操作,会被视为垃圾对象, 垃圾对象会被垃圾回收机制清除
 
构造器
构造器的作用: 定义在类中, 用于创建一个类的对象, 并返回对象的地址
public class Car {
    //成员变量
    private String  name;
    //无参构造器 
    public Car() {
    }
    //有参构造器
    public Car(String n) {
         this.name = n;
    }
}
// 调用无参构造器
Car c1 = new Car()
// 调用有参构造器
Car c2 = new Car("奔驰") 
- 构造器必须与类名一致, 且没有返回值
 - 无参构造器: 创建对象, 里面的数据都是默认值
 - 有参构造器: 创建对象, 同时可以为里面的数据赋值
 - 类在定义时, java会为类自动生成无参构造器
 - 如果类中定义了有参构造器, java就不会自动生成无参构造器了, 此时建议手动补充无参构造器
 
this关键字
可以出现在构造器和成员方法中, 代表当前对象的地址
public class Car {
    // this在构造器中使用
    public Car() {
        sout("this1:" + this)
    }
    // this在成员方法中使用
    public void run() {
        sout("this2:" + this)
    }
} 
 
作用
this关键字指向当前类的调用者, 用来访问该对象的成员变量和成员方法
解决对象成员变量与方法内部变量可能的命名冲突

三大特性-封装
概念
封装是一种设计对象的原则, 它帮助我们正确的设计对象的属性和方法
好处
让变成变得简单, 有什么事情, 找对象调方法就行了

原则
对象代表什么, 就要封装对应的数据, 并提供数据对应的行为
合理隐藏: 成员变量全部隐藏, 提供get/set方法访问数据
合理暴露: 成员方法根据是否有用对外暴露

修饰符
公开成员: public修饰符, 公开成员可以通过对象直接访问
私有成员: private修饰符, 私有成员无法通过对象访问, 只能在类的内部访问
public class Student {
    // 成员变量私有
    private double score;
    // 成员方法按需暴漏
    public void setScore(double score) {
        this.score = score;
    }
    // 通过方法操作数据
    public double getScore(double score) {
        return score;
    }
} 
 
javaBean
javaBean也称为 实体类, 是一种专门保存数据的java类
要求
- 成员变量必须私有(private), 提供getXX/setXX方法
 - 必须提供无参构造器, 有参构造器可选
 
好处
- 实际开发中数据和数据的处理都会很多, 如果都混在一起, 代码的可读性和可维护性都是很差
 - 所以流行的开发方式就是数据和数据处理相分离, 所以要用实体类保存数据
 
static关键字
static是静态的意思, 也就是静态修饰符, 可以用来修饰成员变量, 成员方法
静态变量
// 定义
public class User {
    // 静态变量
    static String name;
    // 实例变量
    int age;
    
}
// 访问方式1(推荐)
// 通过类名访问: 类名.静态成员变量
User.name;
// 访问方式2(不推荐)
// 通过对象访问: 对象.静态成员变量
User u = new User();
// 访问静态变量
u.name;
// 访问实例变量
u.age; 
- static修饰的成员变量就是静态变量(类变量)
 - 静态变量属于类, 可以被类的所有对象访问
 - 推荐通过类名访问静态变量, 因为用过对象访问实例变量, 并不直观
 - 无static修饰的成员变量就是实例变量
 - 实例变量是属于每个对象的
 - 只能通过实例对象访问, 且每个对象的信息不同
 
特点
- 静态变量只会跟随类加载一次, 内存中只有一份
 - 静态成员变量: 某个数据只需要一份, 且希望能够被共享, 该数据就应该定义为类变量
 
// 系统启动后, 需要用户类记住自己创建了多少个对象
public class User {
    // 类变量
    public static int number;
    // 构造器
    public User() {
        User.number++;
    }
} 
静态方法
// 定义
public class User {
    // 实例方法
    public void run() {
        sout("好好学习.天天向上")
    }
    // 静态方法
    public static int getMax(int a, int b) {
        return a > b ? a : b
    }
    
}
// 访问方式1(推荐)
// 通过类名访问: 类名.静态成员方法
User.getMax();
// 访问方式2(不推荐)
// 通过对象访问: 对象.静态成员方法
User u = new User();
// 访问静态方法
u.getMax();
// 访问实例方法
u.run(); 
- 静态成员方法(static修饰, 属于类), 建议使用类名触发, 也可以用对象触发(不直观)
 - 实例成员方法(无static修饰, 属于对象),只能用对象触发
 - 如果表示对象自己的行为, 且方法中需要访问实例成员的, 则该方法必须声明成实例方法
 - 如果该方法是执行公用功能, 则可以声明成静态方法
 - 静态方法只能访问静态成员, 不可以直接访问实例成员
 - 实例方法可以访问静态成员, 也可以访问实例成员
 - 静态方法中不可以使用this关键字
 
main方法
main方法是类方法, 使用java命令执行Test(类)程序的时候, 虚拟机会通过Test(类)访问main方法, 然后运行
java Test -> Test.main()
工具类
工具类就是一个类中只定义类方法, 每个方法完成一个功能, 这个类就是工具类
好处
- 调用方便: 如果使用实例方法, 还要new出实例, 麻烦而且浪费内存
 - 代码复用: 一次编写, 处处可用
 - 建议: 工具类不需要创建对象, 建议把工具类的构造器进行私有
 
public class XxxUtil {
    // 方法1
    public static void xxx() {
        ...
    }
    // 方法2
    public static boolean xxx(String email) {
        ...
    }
    // 方法3
    public static String xxx(int n) {
        ...
    }
    // 构造器私有
    private XxxUtil() { }
} 
代码块
代码块是类的5大成分之一: 成员变量/构造器/方法/代码块/内部类
在java中, 使用 {} 括起来的代码被称为代码块
静态代码块
- 格式: static { }
 - 特点: 类加载时自动执行, 由于类只会加载一次, 所以静态代码块也只会执行一次
 - 使用: 在类加载的时候做一些静态数据初始化的操作, 例如: 对类变量的初始化赋值
 
实例代码块(了解)
- 格式: { }
 - 特点: 每次创建对象, 调用构造器执行时, 都会执行实例代码块, 并在构造器之前执行
 - 使用: 和构造器一样, 用来完成对象的初始化赋值
 
三大特性-继承
继承就是用extends关键字, 让一个类和另一个建立父子关系
// Student称为子类
// People称为父类
public class Student extends People {
    
} 
- 作用: 子类继承父类后, 就可以直接使用父类公共的属性和方法了
 - 好处: 继承的好处就是提高代码的复用性
 

- 规范: 子类们相同的特性(公共属性/公共方法)放在父类中定义, 子类独有的属性和方法放在子类中定义
 
原理
子类的对象是由子类和父类共同决定的

权限修饰符
权限修饰符的作用就是 限制类中的成员能够被访问的范围

- 访问权限从小到大是: private(私有)< 缺省(不写) < protected(受限制) < public(公开)
 
继承的特点
- 子类可以继承父类的属性和方法
 - 子类可以继承父类的私有成员, 只是不能直接访问
 

- 子类不能继承父类的构造器, 因为子类有自己的构造器
 - 子类可以直接使用父类的静态成员(共享), 但是共享不等于继承
 - jaba是单继承模式: 一个类只能继承一个直接父类
 

- java不支持多继承, 但是支持多层继承
 

- java中所有的类都是Object类的子类, 要么直接继承, 要么间接继承
 
继承后的变化
1.子类访问成员的原则
在子类方法中访问成员(成员变量/成员方法)遵循就进原则
- 先在子类局部范围找, 然后在子类成员范围找, 然后在父类范围找, 找不到就报错
 - 如果子父类中出现了重名的成员, 会优先使用子类的(更近)
 - 如果一定要在子类中使用父类的成员, 可以通过super关键字,指定访问父类的成员
 - 格式: super.父类成员变量/父类成员方法
 - 也可以通过this关键字, 指定访问子类的成员 (没用, 因为默认就访问子类的成员)
 - 格式: this.成员变量/成员方法
 
2.子类的方法重写
当子类觉得父类中的某个方法不好用, 子类可以重写一个名称和参数列表相同的方法, 去覆盖父类的方法
- 重写后, 方法的访问, java会遵循就近原则
 - 建议使用@Override注解, 他可以让编译器检查重写的格式, 使代码可读性更好
 
基本要求
- 重写方法的名称和形参列表必须与被重写方法保持一致
 - 私有方法, 静态方法不能被重写
 - 子类重写父类方法, 访问权限必须等于或大于父类该方法的权限
 - 重写的方法的返回值, 必须与被重写方法的返回值一致,或者范围更小
 - 以上了解即可, 实际开中遵循: 声明不变, 重新实现
 
举例
- 可以通过重写Object类的toString方法, 使其返回对象内容, 而不是地址
 - 可以通过右键->Generate->toString 快速生成
 
3.子类构造器的特点
子类中所有的构造器, 默认都会先调用父类的无参构造器, 再执行自己
原因
- 子类在初始化的时候,有可能会用到父类中的数据, 如果父类没有初始化, 子类就无法使用父类的数据
 - 所以, 子类初始化之前, 一定会调用父类的构造器, 完成父类的初始化
 - 默认情况下, 子类构造器的第一行代码都是super() , 写不写都有, 他会调用父类的无参数构造器
 - 如果父类没有无参构造器, 则会报错, 我们需要在子类调用父类的有参构造器, super(参数)
 - 在继承模式下, 处理数据的构造器被分到多个不同的类中去了,
 - 所以需要先调用父类构造器, 再调用自己的构造器, 确保数据的完整初始化
 
4.子类其他构造器
如果父类中没有无参构造器, 代码就会报错, 因为子类默认要调用父类的无参构造器, 我们可以通过 super(...) 调用父类的有参构造器, 解决问题
// 父类
public class People {
    private String name;
    // 有参构造器 
    public Prople(String name) {
        this.name = name   
    }
}
// 子类
public class Student extends People {
   private int age;
    // 有参构造器
   public Student(int age) {
     // 调用兄弟构造器 
     this(age, '张三');
   }
   // 有参构造器
   public Student(int age, String name) {
       // 调用父类有参构造器
       super(name)
       // 初始化对象
       this.age = age;
   }
}
// 初始化学生对象
Student s = new Student(18) 
注意
- 子类通过 this(...) 调用本类的其他构造器, 其他构造器可以通过 super(...) 去调用父类的构造器
 - super(...) 和 this(...) 都只能放在构造器的第一行, 所以不能同时使用
 
this和super
this代表本类对象的引用, super代表父类存储空间的标识

包
包就是分门别类的管理类的, 类似于文件夹
建包
// 语句
// package 公司域名倒写.技术名称
// 必须在第一行, 一般IEDA工具会帮助创建
package com.itheima.javabean;
public class Student {
    
} 
导包
相同包下的类可以直接访问, 不同包下的类必须导包
- 语句: import 包名.类名;
 - 使用java官方的程序, 也需要导包, lang包下的程序可以直接用
 - 一个类中使用多个不同包下的类, 如果类的名字一样, 默认只能导入一个, 另一个必须带包名访问
 
自动导包

权限修饰符
用来控制一个成员能够被访问的范围, 可以修饰成员变量, 构造器, 内部类

- 能够识别他人定义的成员的访问范围
 - 自己定义成员一般要满足如下要求:
 
- 成员变量一般私有
 - 方法一般公开
 - 希望该成员只能被本类访问, 使用private修饰
 - 希望该成员只能被本类和同一个包下的其他类及子类访问, 使用protected修饰
 - 其他情况用public就行
 
final关键字
final关键字是最终的意思, 可以修饰类/方法/变量
- 修饰类: 该类就是最终类, 特点是不能被继承, 工具类中可能会用到
 - 修饰方法: 该方法就是最终方法, 特点是不能被重写
 - 修饰变量: 该变量就只能被赋值一次,不能再次赋值
 
- 基本类型的变量: 该变量存储的数据不能被改变,
 - 引用类型的变量: 该变量存储的地址不能被改变, 但是对象里面的值可以改变
 
常量
使用public static final 修饰的成员变量就是常量, 必须要有初始值
- 命名规范: 全大写英文, 使用下划线连接
 - 作用: 通常用于记录系统的配置信息
 

- 优势:
 
- 实现软编码, 代码可读性更好,可维护性更好
 - 程序编译后,常量会被"宏替换", 即出现常量的地方全部会被替换成其记住的字面量,保证常量和字面量的性能是一样的
 
枚举
枚举是java中一种特殊的类, 专门用来做信息分类, 可读性好, 入参约束谨慎, 代码优雅
定义
public enum Season {
    SPRING, SUMMER, AUTUMN, WINTER;
} 
 
反编译后

- 枚举类都是继承了枚举类型: java.lang.Enum
 - 枚举都是最终类, 不可以被继承
 - 枚举类的构造器都是私有的, 枚举对外不能创建对象
 - 枚举类的第一行只 能罗列一些名称, 这些名称都是常量, 并且每个常量记住的都是枚举类的一个对象
 - 枚举类中, 从第二行开始, 可以定义类的其他各种成员
 - 枚举类相当于是多例模式
 - 枚举会从 java.lang.Enum 类中继承一些方法
 
- Season[] se = Season.values() // 拿到全部对象
 - Season se = Season.valueOf("SPRING"); // 拿到指定名称的枚举对象
 - se.name() // 拿到枚举对象的名字
 - se.ordinal() // 拿到枚举对象的索引
 
补充
- 可以使用枚举实现单例模式
 
public enum C {
     x;  //单例
} 
- 枚举和常量都是用来表示一组信息, 然后作为参数进行传输
 
- 枚举相对于常量, 限制性更好,
 - 常量相对于枚举, 灵活性更好,
 - 两者的使用场景相似
 
抽象类
abstract关键字是抽象的意思, 可以修饰类, 成员方法
// 抽象类
修饰符 abstract class 类名 {  
    // 抽象方法
    修饰符 abstract 返回值类型 方法名称(形参列表);
} 
- 抽象方法只有方法签名, 不能声明方法体
 - 一个类中如果定义了抽象方法,这个类必须声明为抽象类, 否则报错
 - abstract修饰的类称为抽象类, abstract修饰的方法称为抽象方法
 - 类该有的成员(成员变量/方法/构造器),抽象类都可以有
 - 抽象类可以理解为不完整的设计图, 得到了抽象方法, 但是失去了创建对象的能力
 - 所以抽象类不能创建对象, 仅作为一种特殊的类, 让子类继承并实现抽象方法
 - 一个类继承抽象类, 必须重写完抽象类的全部抽象方法, 否则这个类也要定义成抽象类
 - abstract不能修饰变量, 代码块, 构造器
 
示例
父类知道每个子类都要做某个行为, 但每个子类要做的情况不一样
- 父类就可以定义抽象方法, 交给子类去重写实现,
 - 设计这样的抽象类, 就是为了更好的支持多态
 

扩展
- final和abstract是互斥关系
 - abstract定义的抽象类作为模板让子类继承, final定义的类不能被继承
 - 抽象方法定义通用功能让子类重写, final定义的方法子类不能重写
 - 使用抽象类可以实现 [模版方法] 设计模式
 
三大特性-多态
多态就是对象可以有多种形态, 是继承/实现情况下的一种现象, 表现为:对象多态和行为多态
对象多态: 一个对象可以有角色(形态), 你既是你爸爸的儿子, 也是你媳妇的老公
行为多态: 一个对象的行为可以有不同的表现, 人都会唱歌, 但是你唱的和刘德华唱的不一样
注意: 多态是对象或行为的多态, java中的属性(成员变量)不谈多态
多态的前提
- 有继承/实现关系;
 - 存在父类引用子类对象;
 - 存在方法重写 (多态侧重行为多态)
 
常见形式
父类类型 对象名称 = new 子类构造器
public class Prople {
    public String name = "父类的名称"
    public void run() {
        sout("人可以跑")
    }
} 
public class Teacher extends Prople {
    public String name = "老师的名称"
    public void run() {
        sout("老师跑的气喘吁吁")
    }
} 
public class Student extends Prople {
    public String name = "学生的名称"
    public void run() {
        sout("学生跑的飞快")
    }
} 
public class Test {
    public static void main(String[] ages) {
        // 对象多态: 人对象可以指向学生对象,也可以指向老师对象
        People p1 = new Student();
        People p2 = new Teacher();
        
        // 行为多态: 同样调用run方法,执行结果不同
        p1.run(); // 学生跑的飞快
        p2.run(); // 老师跑的气喘吁吁
        
        // 成员变量不存在多态
        p1.name;  // 父类的名称
        p1.name;  // 父类的名称
    }
} 
运行特点
- 方法调用: 编译看左边(People), 运行看右边(Student();)
 - 变量调用: 编译看左边(People), 运行也看左边(People)
 
多态的好处
1:在多态形势下, 右边对象是解耦合的, 更便于扩展和维护
// 可以方便的切换
// prople p1 = new Teacher(); 
prople p1 = new Student();
p1.run() 
2:定义方法时, 使用父类类型的形参, 可以接受一切子类对象, 扩展性更强
Student s = new Student();
go(s);
Teacher t = new Teacher();
go(t);
publci static void go(People p) {
    
} 
多态的问题
多态下不能使用子类的独有功能
public class Prople {
    public void run() {
        sout("人可以跑")
    }
} 
public class Teacher extends Prople {
    public void run() {
        sout("老师跑的气喘吁吁")
    }
    public void sayHi() {
        sout("老师需要讲课")
    }
} 
public class Test {
    public static void main(String[] ages) {
        People p1 = new Student();
        p1.run();   // 老师跑的气喘吁吁
        p1.sayHi(); // 报错 
    }
} 
 
解决方法
强制类型转换: 把父类类型强转成子类类型, 解决多态下无法使用子类独有功能的问题
方法: 子类 变量名 = (子类) 父类变量
public class Test {
    public static void main(String[] ages) {
        People p1 = new Student();
        // 强制类型转换
        Student s1 = (Student) p1;
        
        // 正常运行: "老师需要讲课"
        p1.sayHi();
        
        // 类型谈判, 防止类型转换错误
        if(p1 instanceof Student) {
             Student s1 = (Student) p1;
             p1.sayHi();
        }
        
    }
} 
补充
- 只要存在继承/实现关系, 就可以在编译阶段进行强制类型转换, 编译阶段不会报错
 - 运行时, 如果发现强转的类型和对象的真实类型不符, 就会报类型转换异常(ClassCastException)的错误
 - 官方建议, 强制类型转换前先 使用 instanceof 关键字, 判断当前对象的真实类型
 
接口
接口是一种规范, 在java中, 使用 interface关键字定义接口, 可以理解为一种特殊的结构
定义
public interface A {
     // 成员变量(默认就是常量)
     String SCHOOL_NAME = "程序员";
     //  成员方法(默认就是抽象方法)
     void test();
} 
- JDK8之前的接口中只能有抽象方法和常量
 - 接口中的成员都是public修饰的, 写不写都是
 - 接口方法默认被public修饰的原因: 接口是需要被类实现的, 大部分接口方法都是要对外暴漏的
 - 接口不能实例化
 
使用
接口不能创建对象, 接口是用来被 类 实现的, 实现接口的类称为实现类
// 实现接口的关键字: implements
修饰符 class 实现类 implements 接口1, 接口2, ... {
    
} 
- 一个类可以实现多个接口(干爹)
 - 一个类实现接口, 必须重写完 全部接口的 全部抽象方法, 否则实现类需要定义为抽象类
 
优势
- 弥补了类单继承的不足,一个类可以同时实现多个接口
 - 面向接口编程, 可以灵活方便的切换各种业务实现
 
新增
jdk8开始, 接口新增了三种方法, 目的是增强接口的能力, 更便于项目的扩展和维护
了解即可, java源码中会看到
1.默认方法
// 默认方法必须使用default修饰
// 默认方法, 使用实现类的对象调用
default void run() {
    sout("默认方法")
} 
 
2.静态方法
// 静态方法必须使用static修饰
// 静态方法, 使用本身的接口名来调用
static void run() {
    sout("静态方法")
} 
 
3.私有方法
// 私有方法必须使用private修饰
// jdk9开始支持, 只能在本类中被其他的默认方法或私有方法访问
static void run() {
    sout("私有方法")
} 
 
接口的多继承
一个接口可以同时继承多个接口, 便于实现类实现多个接口
public interface 接口1 extends 接口1, 接口2 {
    
} 
接口使用细节
一个接口继承多个接口, 如果多个接口中存在方法签名冲突, 则此时不支持多继承
一个类实现多个接口, 如果多个接口中存在方法签名冲突, 则此时不支持多实现
一个类继承了父类, 又同时实现了接口, 父类中和接口中存在同名的默认方法, 实现类会优先使用父类的
一个类实现了多个接口, 多个接口中存在同名的默认方法, 会冲突, 解决方法就是在实现类中重写该方法
内部类
如果一个类中定义在另一个类的内部, 这个类就是内部类
内部类是类的五大成分之一(成员变量/方法/构造器/内部类/代码块)
public class People {
    // 内部类
    public class Heart {
        
    }
} 
- 场景: 当一个类的内部, 包含了一个完成的事物, 且这个事物没必要单独设计时, 就可以设计成内部类
 - 内部类通常可以方便访问外部类的成员, 包括私有成员
 - 内部类提供了更好的封装性, 可以使用private, protectecd修饰内部类
 
静态内部类
用static修饰的内部类, 属于外部类自己持有, 特点与普通类一致
// 定义
public class Car {
   // 静态成员内部类
   public static class Engine { }
}
// 创建对象
Car.Engine eg = new Car.Engine();
// 访问成员
// 可以直接访问外部类的静态成员
// 不可以直接访问外部类的实例成员 
成员内部类
无static修饰, 属于外部类的对象, JDK16之后, 成员内部类也可以定义静态成员了
// 定义
public class Car {
    // 成员内部类
   public class Engine { }
}
// 创建对象
Car.Engine  eg = new Car().new Engine();
// 访问成员
// 可以直接访问外部类的实例成员和静态成员
// 可以拿到当前外部类对象, 格式是: 外部类名.this 
匿名内部类(重点)
本质是一个没有名字的局部内部类, 作用就是方便的创建一个子类对象, 目的是简化代码
// 1.定义一个抽象类
abstract class Animal {
    public abstract void cry();
}
// 2.使用匿名内部类语法
// 把匿名内部类编译成一个子类, 然后立即创建一个子类对象
Animal a = new Animal(){
    @Override
    public void cry() {
        sout("喵喵喵")
    }
};
// 直接使用子类 
a.run(); // 喵喵喵
 
- 语法:
 

- 匿名内部类本质是一个子类, 并且会立即创建一个子类对象
 - 匿名内部类的对象类型, 相当于是当前new的那个类型子类类型
 - 使用场景: 把匿名内部类当做参数传给方法
 - 匿名内部类都是在需要的时候用, 比如一个方法需要一个对象作为参数
 - 那么单独创建对象传进去就麻烦, 此时可以使用匿名内部类简化代码
 
泛型
泛型提供了在编译阶段的类型约束, 并自动进行检查, 可以避免强制类型转换和可能出现的转换异常
// 该集合只能添加字符串类型的数据
ArrayList<String> list1 = new ArrayList<>();
list1.add("java1"); 
- 定义类,接口,方法时, 同时声明一个或多个类型变量,用于限制成员类型,
 - 使用泛型的类称为泛型类, 泛型接口或泛型方法, 统称为泛型
 - 原理: 把具体的数据类型作为参数传给类型变量, 通过类型变量限制成员的类型
 
自定义泛型类
// 模拟ArrayList集合
public class MyArrayList<E> {
    // 模拟add方法
    public boolean add(E e) {
          return true;
     }
    // 模拟get方法
    public E get(int index) {
          return null;
    }
}
// 使用泛型
MyArrayList<String> list = new MyArrayList<>();
list.add("java1")
list.get(1) 
- 如果需要, 可以进一步使用extends限制泛型的范围
 
public class MyArrayList<E extends Animal> { }
- 表示类型E必须是Animal类型或者是Animal的子类
 
自定义泛型接口
public interface Date<T> {
    void add(T t);
    ArrayList<T> getByName(String name):
} 
- 类型变量建议使用大写英文字母, E T K V 等
 
自定义泛型方法
public static <T> T test(T t) {
     return t;
} 
通配符
- 在使用泛型是可以用 ? 表示一切类型
 
泛型上下限
- 泛型上限: ? extends Cart
 - 表示能接受的类型必须是Car或其子类
 
- 泛型下限: ? super Cart
 - 表示能接受的必须是Cart或其父类
 
泛型擦除
- 泛型是在编译阶段工作的. 就是帮助程序进行类型校验的, 程序编译成class文件后, 泛型就不会存在了
 - 泛型不支持基本数据类型, 只支持引用数据对象, 如Integer Double
 



















