文章目录
- Java基础(2) 之面向对象
- 1.对象
- 2.类
- 类的注意事项
- 3.this关键字
- 4.构造器
- 注意
- 5.封装性
- 6.实体JavaBean
- 实体类
- 7.成员变量和局部变量的区别
- 8.static
- static修饰成员变量
- static修饰成员方法
- static的注意事项
- 工具类
- 单例设计模式
- 9.代码块
- 静态代码块
- 实例代码块
- 10.继承
- 权限修饰符
- 单继承与object
- 方法重写
- 子类访问成员的特点
- 子类访问构造器的特点
- 访问自己类的构造器
- 11.多态
- 多态的类型转换
- 12.final关键字
- 13.抽象类
- 模板方法模式
- 14.接口
- 好处:
- 接口也能继承
- 15.内部类
- 成员内部类
- 静态内部类
- 局部内部类
- 匿名内部类
- 16.枚举
- 17.泛型
- 泛型类
- 自定义泛型类
- 自定义泛型接口
- 泛型方法
- 泛型限定
- 泛型擦除
Java基础(2) 之面向对象
-
面向对象编程有什么好处?
-
面向对象的开发更符合人类的思维习惯,让编程变得更加简单、更加直观。
1.对象
-
对象实质上是一种特殊的数据结构。这种结构怎么理解呢?
你可以把对象理解成一张表格,表当中记录的数据,就是对象拥有的数据
一句话总结,对象其实就是一张数据表,表当中记录什么数据,对象就处理什么数据。
-
Student s1 = new Student();
这句话中的原理如下-
Student s1
表示的是在栈内存中,创建了一个Student类型的变量,变量名为s1 -
而
new Student()
会在堆内存中创建一个对象,而对象中包含学生的属性名和属性值同时系统会为这个Student对象分配一个地址值
0x4f3f5b24
-
接着把对象的地址赋值给栈内存中的变量
s1
,通过s1
记录的地址就可以找到这个对象 -
当执行
s1.name=“播妞”
时,其实就是通过s1
找到对象的地址,再通过对象找到对象的name属性,再给对象的name属性赋值为播妞
;
-
2.类
用什么来设计这张表呢?就是类(class),类可以理解成对象的设计图,或者对象的模板。设计图中规定有哪些数据,对象中就只能有哪些数据。
- 对象可以理解成一张数据表,而数据表中可以有哪些数据,是有类来设计的。
类的注意事项
3.this关键字
- 哪一个对象调用方法,方法中的this就是哪一个对象
通过this在方法中可以访问本类对象的成员变量
4.构造器
构造器就是用来创建对象的。可以在创建对象时给对象的属性做一些初始化操作
-
构造器其实是一种特殊的方法,但是这个方法没有返回值类型,方法名必须和类名相同。
-
在创建对象时,会调用构造器。
-
也就是说
new Student()
就是在执行构造器,当构造器执行完了,也就意味着对象创建成功。 -
new 对象就是在执行构造方法
注意
- 1.在设计一个类时,如果
不写
构造器,Java会自动生成
一个无参数构造器。 - 2.一旦
定义了有参数
构造器,Java就不再提供
空参数构造器,此时建议自己加一个无参数
构造器。
5.封装性
所谓封装,就是用类设计对象处理某一个事物的数据时,应该把要处理的数据,以及处理数据的方法,都设计到一个对象中去。
-
封装的设计规范:
- 合理隐藏、合理暴露
-
体现:
-
被
private
修饰的变量或者方法,只能在本类中被访问。 -
private double score;
就相当于把score变量封装在了Student对象的内部,且不对外暴露,你想要在其他类中访问score这个变量就,就不能直接访问了;如果你想给Student对象的score属性赋值,得调用对外暴露的方法
setScore(int score)
,在这个方法中可以对调用者传递过来的数据进行一些控制,更加安全。
-
6.实体JavaBean
实体类
- 实体类中除了有给对象存、取值的方法就没有提供其他方法了。所以实体类仅仅只是用来封装数据用的。
实体类就是一种特殊的类,它需要满足下面的要求:
- 实体类仅仅只用来
封装数据
,而对数据的处理交给其他类来完成,以实现数据和数据业务处理相分离
。
7.成员变量和局部变量的区别
面向对象的核心点就是封装,将数据和数据的处理方式,都封装到对象中; 至于对象要封装哪些数据?对数据进行怎样的处理? 需要通过类来设计。
需要注意的是,不同的人,对同一个对象进行设计,对象封装那些数据,提供哪些方法,可能会有所不同;只要能够完成需求,符合设计规范,都是合理的设计。
8.static
static读作静态,可以用来修饰成员变量,也能修饰成员方法。
static修饰成员变量
- 实际开发中,如果某个数据只需要一份,且希望能够被
共享(访问、修改)
,则该数据可以定义成类变量
来记住。
Java中的成员变量按照有无static修饰分为两种:类变量、实例变量。它们的区别如下:
静态变量是属于类的,只需要通过类名就可以调用:
类名.静态变量
实例变量是属于对象的,需要通过对象才能调用:
对象.实例变量
static修饰成员方法
成员方法根据有无static也分为两类:类方法、实例方法
有static修饰的方法,是属于类的,称为类方法;调用时直接用类名调用即可。
无static修饰的方法,是属于对象的,称为
实例方法
;调用时,需要使用对象
调用。
static的注意事项
工具类
如果一个类中的方法全都是静态的,那么这个类中的方法就全都可以被类名直接调用,由于调用起来非常方便,就像一个
工具
- 工具类里的方法全都是静态的,推荐用类名调用为了
防止使用者用对象
调用。我们可以把工具类的构造方法私有化
public class MyUtils{//私有化构造方法:这样别人就不能使用构造方法new对象了private MyUtils(){}//类方法public static String createCode(int n){...}
}
-
示例:生成验证码的工具类
-
public class MyUtils{public static String createCode(int n){//1.定义一个字符串,用来记录产生的验证码String code = "";//2.验证码是由所有的大写字母、小写字母或者数字字符组成//这里先把所有的字符写成一个字符串,一会从字符串中随机找字符String data = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKMNOPQRSTUVWXYZ";//3.循环n次,产生n个索引,再通过索引获取字符Random r = new Random();for(int i=0; i<n; i++){int index = r.nextInt(data.length());char ch = data.charAt(index);//4.把获取到的字符,拼接到code验证码字符串上。code+=ch;}//最后返回code,code的值就是验证码return code;} }
单例设计模式
让类对外只能产生一个对象;如任务管理器对象
-
把类的构造器私有。
-
定义一个类变量记住类的一个对象。
-
定义一个类方法,返回对象。
-
-
懒汉式(拿对象时,才开始创建对象。)
-
把类的构造器私有。
定义一个类变量用于存储对象。
提供一个类方法,保证返回的是同一个对象。
-
9.代码块
代码块根据有无static修饰分为两种:
静态代码块
、实例代码块
静态代码块
静态代码块不需要创建对象就能够执行
静态代码块,随着类的加载而执行,而且只执行
一次
。
实例代码块
-
实例代码块的作用和构造器的作用是一样的,用来给对象初始化值;而且每次创建对象之前都会先执行实例代码块.
10.继承
子类对象实际上是由子、父类两张设计图共同创建出来的。
其实像这种两个类中有相同代码时,没必要重复写。
我们可以把重复的代码提取出来,作为父类,然后让其他类继承父类就可以了,这样可以提高代码的复用性。
权限修饰符
权限修饰符是用来限制类的成员(成员变量、成员方法、构造器…)能够被访问的范围。
单继承与object
-
Java语言只支持单继承,不支持多继承,但是可以多层继承。
-
Java是
单继承
的:一个类只能继承一个直接父类; -
Object
类是Java中所有类的祖宗
。
方法重写
当子类觉得父类方法不好用,或者无法满足父类需求时,子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写。
-
重写后,方法的访问遵循
就近原则
-
1.重写的方法上面,可以加一个注解@Override,用于标注这个方法是复写的父类方法
-
2.子类复写父类方法时,访问权限必须大于或者等于父类方法的权限
-
public > protected > 缺省
-
-
- 重写的方法返回值类型,必须与被重写的方法返回值类型一样,或者范围更小
- 私有方法、静态方法不能被重写,如果重写会报错。
-
-
示例:
-
public class A {public void print1(){System.out.println("111");}public void print2(int a, int b){System.out.println("111111");} }
-
public class B extends A{// 方法重写@Override // 安全,可读性好public void print1(){System.out.println("666");}// 方法重写@Overridepublic void print2(int a, int b){System.out.println("666666");} }
-
尽量做到
声明不变,重新实现
-
应用场景之一就是:子类重写Object的toString()方法,以便返回对象的内容。
子类访问成员的特点
继承至少涉及到两个类
,而每一个类中都可能有各自的成员(成员变量、成员方法),就有可能出现子类和父类有相同成员的情况,那么在子类中访问其他成员有什么特点呢?
-
在子类中访问其他成员(成员变量、成员方法),是依据就近原则的
-
如果子类和父类出现同名变量或者方法,优先使用子类的;此时如果一定要在子类中使用父类的成员,可以加
this
或者super
进行区分。
子类访问构造器的特点
子类中访问构造器的语法规则
- 子类全部构造器,都会
先调用父类
构造器,再执行自己。
- 如果不想使用默认的
super()
方式调用父类构造器,还可以手动使用super(参数)
调用父类有参数构造器。
访问自己类的构造器
this(): 调用本类的空参数构造器
this(参数): 调用本类有参数的构造器
如下图:
- 注意:this和super访问构造方法,只能用到
构造方法的第一句
,否则会报错。
访问本类成员:
this.成员变量 //访问本类成员变量
this.成员方法 //调用本类成员方法
this() //调用本类空参数构造器
this(参数) //调用本类有参数构造器访问父类成员:
super.成员变量 //访问父类成员变量
super.成员方法 //调用父类成员方法
super() //调用父类空参数构造器
super(参数) //调用父类有参数构造器
11.多态
什么是多态?
多态是在继承、实现情况下的一种现象,表现为:对象多态、行为多态。
在多态形式下,右边的代码是解耦合的,更便于扩展和维护。
定义方法时,使用父类类型作为形参,可以接收一切子类对象,扩展行更强,更便利。
public class Test2 {public static void main(String[] args) {// 目标:掌握使用多态的好处Teacher t = new Teacher();go(t);Student s = new Student();go(s);}//参数People p既可以接收Student对象,也能接收Teacher对象。public static void go(People p){System.out.println("开始------------------------");p.run();System.out.println("结束------------------------");} }
多态的类型转换
多态形式下,不能调用
子类特有
的方法,比如在Teacher类中多了一个teach方法,在Student类中多了一个study方法,这两个方法在多态形式下是不能直接调用
的。
- 但是转型后是可以调用的
- 因此我们最终记住一句话:原本是什么类型,才能还原成什么类型
12.final关键字
面向对象编程中偶尔会用到的一个关键字叫
final
,也是为后面学习抽象类
和接口
做准备的
-
final修饰
类
:该类称为最终类,特点是不能被继承
-
-
final修饰
方法
:该方法称之为最终方法,特点是不能被重写
。 -
final修饰
变量
:该变量只能被赋值一次
。 -
-
static final 修饰的成员变量,称之为
常量
。 -
在程序编译后,
常量
会“宏替换”,出现常量的地方,全都会被替换为其记住的字面量
。
13.抽象类
在Java中有一个关键字叫abstract,它就是抽象的意思,它可以修饰
类
也可以修饰方法。
- 被abstract修饰的类,就是抽象类
- 被abstract修饰的方法,就是抽象方法(
不允许有方法体
)
//abstract修饰类,这个类就是抽象类
public abstract class A{//abstract修饰方法,这个方法就是抽象方法public abstract void test();
}
- 类的成员(成员变量、成员方法、构造器),类的成员都可以有。
- 抽象类是不能创建对象的,如果抽象类的对象就会报错
抽象类虽然不能创建对象,但是它可以作为父类让子类继承。而且子类继承父类必须重写父类的所有抽象方法。
子类继承父类如果不复写父类的抽象方法,要想不出错,这个子类也必须是抽象类
-
好处
-
1.用抽象类可以把父类中相同的代码,包括方法声明都抽取到父类,这样能更好的支持多态,一提高代码的灵活性。
2.反过来用,我们不知道系统未来具体的业务实现时,我们可以先定义抽象类,将来
让子类去实现
,以方便系统的扩展。
-
模板方法模式
模板方法模式解决了多个子类中有相同代码的问题
第1步:定义一个抽象类,把子类中相同的代码写成一个模板方法。
第2步:把模板方法中不能确定的代码写成抽象方法,并在模板方法中调用。
第3步:子类继承抽象类,只需要父类抽象方法就可以了。
-
模板方法模式主要解决方法中存在重复代码的问题
-
-
我们可以写一个抽象类C类,在C类中写一个doSing()的抽象方法。再写一个sing()方法
先设计模板方法:
// 模板方法设计模式 public abstract class C {// 模板方法public final void sing(){System.out.println("唱一首你喜欢的歌:");doSing();System.out.println("唱完了!");}public abstract void doSing(); }
-
写一个A类和B类继承C类,复写doSing()方法,代码如下
-
public class A extends C{@Overridepublic void doSing() {System.out.println("我是一只小小小小鸟,想要飞就能飞的高~~~");} }
public class B extends C{@Overridepublic void doSing() {System.out.println("我们一起学猫叫,喵喵喵喵喵喵喵~~");} }
-
14.接口
-
总结为一句话,就是接口实现之后我就可以用接口来接收所有实现了接口的对象,因为所有的这些对象都
重写
了接口的方法,充分利用了多态。
比抽象类抽象得更加彻底的一种特殊结构
-
Java提供了一个关键字
interface
,用这个关键字来定义接口这种特殊结构。格式如下: -
public interface 接口名{//成员变量(常量)//成员方法(抽象方法) }
定义好接口之后,是不能创建对象的
-
所以说接口有什么用?
-
- 接口是用来被类实现(implements)的,我们称之为实现类。
- 一个类是可以实现多个接口的(接口可以理解成干爹),类实现接口必须重写所有接口的全部抽象方法,否则这个类也必须是抽象类
-
比如,定义一个B接口,里面有两个方法testb1(),testb2()
-
public interface B {void testb1();void testb2(); }
-
再定义一个C接口,里面有两个方法testc1(), testc2()
-
public interface C {void testc1();void testc2(); }
-
再写一个实现类D,同时实现B接口和C接口,此时就需要复写
四个方法
-
// 实现类 public class D implements B, C{@Overridepublic void testb1() {}@Overridepublic void testb2() {}@Overridepublic void testc1() {}@Overridepublic void testc2() {} }
好处:
弥补了类单继承的不足,一个类同时可以实现多个接口。
让程序可以面向接口编程,这样程序员可以
灵活方便的切换
各种业务实现。class Student{}interface Driver{void drive(); }interface Singer{void sing(); }//A类是Student的子类,同时也实现了Dirver接口和Singer接口 class A extends Student implements Driver, Singer{@Overridepublic void drive() {}@Overridepublic void sing() {} }public class Test {public static void main(String[] args) {//想唱歌的时候,A类对象就表现为Singer类型Singer s = new A();s.sing();//想开车的时候,A类对象就表现为Driver类型Driver d = new A();d.drive();} }
接口弥补了单继承的不足,同时可以轻松实现在多种业务场景之间的
切换。
-
在JDK8版本以后接口中能够
定义
的成员也做了一些更新
-
public interface A {/*** 1、默认方法:必须使用default修饰,默认会被public修饰* 实例方法:对象的方法,必须使用实现类的对象来访问。*/default void test1(){System.out.println("===默认方法==");test2();}/*** 2、私有方法:必须使用private修饰。(JDK 9开始才支持的)* 实例方法:对象的方法。*/private void test2(){System.out.println("===私有方法==");}/*** 3、静态方法:必须使用static修饰,默认会被public修饰*/static void test3(){System.out.println("==静态方法==");}void test4();void test5();default void test6(){} }
- 我们写一个B类,实现A接口。B类作为A接口的实现类,只需要重写抽象方法就尅了,对于默认方法不需要子类重写。
public class B implements A{@Overridepublic void test4() {}@Overridepublic void test5() {} }
public class Test {public static void main(String[] args) {// 目标:掌握接口新增的三种方法形式B b = new B();b.test1(); //默认方法使用对象调用// b.test2(); //A接口中的私有方法,B类调用不了A.test3(); //静态方法,使用接口名调用} }
就是说jdk8以后接口中的方法可以被
default
,static
,private
修饰,这些方法可以在接口中实现,而什么都不加,就不能实现,得实体类来实现。感觉开始和抽象类差不多了。
-
接口也能继承
-
一个接口可以继承多个接口,接口同时也可以被类实现。
-
1.一个接口继承多个接口,如果多个接口中存在相同的方法声明,则此时不支持多继承
2.一个类实现多个接口,如果多个接口中存在相同的方法声明,则此时不支持多实现
3.一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会有限使用父类的方法
4.一个类实现类多个接口,多个接口中有同名的默认方法,则这个类必须重写该方法。
public class Test {public static void main(String[] args) {// 目标:理解接口的多继承。} }interface A{void test1(); } interface B{void test2(); } interface C{}//比如:D接口继承C、B、A interface D extends C, B, A{}//E类在实现D接口时,必须重写D接口、以及其父类中的所有抽象方法。 class E implements D{@Overridepublic void test1() {}@Overridepublic void test2() {} }
15.内部类
- 当一个类的内部,包含一个完整的事物,且这个事物没有必要单独设计时,就可以把这个事物设计成内部类
内部类是类中的五大成分之一(成员变量、方法、构造器、内部类、代码块),如果一个类定义在另一个类的内部,这个类就是内部类。
成员内部类
- 既可以访问内部类成员、也可以访问外部类成员
- 如果内部类成员和外部类成员同名,可以使用**
类名.this.成员
**区分
成员内部类就是类中的一个普通成员,类似于成员变量、成员方法。
-
public class Outer {private int age = 99;public static String a="黑马";// 成员内部类public class Inner{private String name;private int age = 88;//在内部类中既可以访问自己类的成员,也可以访问外部类的成员public void test(){System.out.println(age); //88System.out.println(a); //黑马int age = 77;System.out.println(age); //77System.out.println(this.age); //88System.out.println(Outer.this.age); //99}} }
成员内部类如何创建对象,格式如下:
//外部类.内部类 变量名 = new 外部类().new 内部类(); Outer.Inner in = new Outer().new Inner(); //调用内部类的方法 in.test();
静态内部类
在成员内部类的前面加了一个static关键字。静态内部类属于外部类自己持有。
-
public class Outer {private int age = 99;public static String schoolName="黑马";// 静态内部类public static class Inner{//静态内部类访问外部类的静态变量,是可以的;//静态内部类访问外部类的实例变量,是不行的public void test(){System.out.println(schoolName); //99//System.out.println(age); //报错}} }
静态内部类创建对象时,需要使用外部类的类名调用。
//格式:外部类.内部类 变量名 = new 外部类.内部类(); Outer.Inner in = new Outer.Inner(); in.test();
局部内部类
局部内部类是定义在方法中的类,和局部变量一样,
只能在方法中有效。
所以局部内部类的局限性很强,一般在开发中是不会使用的
public class Outer{public void test(){//局部内部类class Inner{public void show(){System.out.println("Inner...show");}}//局部内部类只能在方法中创建对象,并使用Inner in = new Inner();in.show();} }
匿名内部类
匿名内部类是一种特殊的
局部内部类
;所谓匿名,指的是程序员不需要为这个类声明名字。匿名内部类本质上是一个没有名字的子类对象、或者接口的实现类对象。
匿名内部类的作用:简化了创建子类对象、实现类对象的书写格式。
new 父类/接口(参数值){@Override重写父类/接口的方法; }
-
示例:
-
public abstract class Animal{public abstract void cry(); }
我想要在
不定义子类的情况下
创建Animal的子类对象
,就可以使用匿名内部类
public class Test{public static void main(String[] args){//这里后面new 的部分,其实就是一个Animal的子类对象//这里隐含的有多态的特性: Animal a = Animal子类对象;Animal a = new Animal(){@Overridepublic void cry(){System.out.println("猫喵喵喵的叫~~~");}}a.eat(); //直线上面重写的cry()方法} }
- 匿名内部类在编写代码时没有名字,编译后系统会为
自动为匿名内部类生产字节码
,字节码的名称会以外部类$1.class
的方法命名
- 匿名内部类在编写代码时没有名字,编译后系统会为
-
**只有在调用方法时,当方法的形参是一个接口或者抽象类,为了简化代码书写,而直接传递匿名内部类对象给方法。**这样就可以少写一个类。
-
public interface Swimming{public void swim(); }
public class Test{public static void main(String[] args){Swimming s1 = new Swimming(){public void swim(){System.out.println("狗刨飞快");}};go(s1);Swimming s1 = new Swimming(){public void swim(){System.out.println("猴子游泳也还行");}};go(s1);}//形参是Swimming接口,实参可以接收任意Swimming接口的实现类对象public static void go(Swimming s){System.out.println("开始~~~~~~~~");s.swim();System.out.println("结束~~~~~~~~");} }
-
16.枚举
枚举是一种特殊的类,它的格式是:
public enum 枚举类名{枚举项1,枚举项2,枚举项3; }
枚举项就表示枚举类的对象,只是这些对象在定义枚举类时就预先写好了,以后就只能用这几个固定的对象。
枚举项实际上是枚举类的对象
枚举类A是用class定义的,说明枚举确实是一个类,而且X,Y,Z都是A类的对象;而且每一个枚举项都是被
public static final
修饰,所以被可以类名调用,而且不能更改。
-
既然枚举是一个类的话,我们能不能在枚举类中
定义构造器、成员变量、成员方法
呢?答案是可以的-
public enum A{//定义枚举项X,Y,Z("张三"); //枚举项后面加括号,就是在执行枚举类的带参数构造方法。//定义空构造器public A(){}//成员变量private String name;//定义带参数构造器public A(String name){this.name=name;}//成员方法public String getName(){return name;}... }
虽然枚举类中可以像类一样,写一些类的其他成员,但是一般不会这么写,如果你真要这么干的话,到不如直接写普通类来的直接。
-
-
枚举一般表示几个固定的值,然后作为参数进行传输。
public enum Constant{BOY,GRIL }
public class Test{public static void main(String[] args){//调用方法,传递男生provideInfo(Constant.BOY);}public static void provideInfo(Constant c){switch(c){case BOY:System.out.println("展示一些信息给男生看");break;case GRIL:System.out.println("展示一些信息给女生看");break;}} }
17.泛型
泛型类
- 泛型类,在实际工作中一般都是源代码中写好,我们直接用的,就是
ArrayList<E>
这样的,自己定义泛型类是非常少的。
所谓泛型指的是,在定义类、接口、方法时,同时声明了一个或者多个类型变量(如:),称为泛型类、泛型接口、泛型方法、它们统称为泛型。
ArrayList
类就是一个泛型类
ArrayList
集合的设计者在定义ArrayList
集合时,就已经明确ArrayLis
t集合时给别人装数据用的,但是别人用ArrayList
集合时候,装什么类型的数据他不知道,所以就用一个<E>
表示元素的数据类型。当别人使用
ArrayList
集合创建对象时,new ArrayList<String>
就表示元素为String类型,new ArrayList<Integer>
表示元素为Integer类型。
-
-
泛型的好处:在编译阶段可以避免出现一些非法的数据。
-
泛型的本质:把具体的数据类型传递给类型变量。
-
自定义泛型类
//这里的<T,W>其实指的就是类型变量,可以是一个,也可以是多个。 public class 类名<T,W>{}
- 示例:
//定义一个泛型类,用来表示一个容器 //容器中存储的数据,它的类型用<E>先代替用着,等调用者来确认<E>的具体类型。 public class MyArrayList<E>{private Object[] array = new Object[10];//定一个索引,方便对数组进行操作private int index;//添加元素public void add(E e){array[index]=e;index++;}//获取元素public E get(int index){return (E)array[index];} }
自定义泛型接口
- 在实际工作中,一般也都是框架底层源代码把泛型接口写好,我们实现泛型接口就可以了.
泛型接口其实指的是在接口中把不确定的数据类型用
<类型变量>
表示。定义格式如下://这里的类型变量,一般是一个字母,比如<E> public interface 接口名<类型变量>{}
示例:做一个系统要处理学生和老师的数据,需要提供2个功能,保存对象数据、根据名称查询数据,要求:这两个功能处理的数据既能是
老师对象,也能是学生对象。
public class Teacher{}
public class Student{}
定义一个
Data<T>
泛型接口,T表示接口中要处理数据的类型。public interface Data<T>{public void add(T t);public ArrayList<T> getByName(String name); }
这个接口可以让不同的类实现操作不同的数据
//此时确定Data<E>中的E为Teacher类型, //接口中add和getByName方法上的T也都会变成Teacher类型 public class TeacherData implements Data<Teacher>{public void add(Teacher t){}public ArrayList<Teacher> getByName(String name){} }
//此时确定Data<E>中的E为Student类型, //接口中add和getByName方法上的T也都会变成Student类型 public class StudentData implements Data<Student>{public void add(Student t){}public ArrayList<Student> getByName(String name){} }
泛型方法
public <泛型变量,泛型变量> 返回值类型 方法名(形参列表){}
- 在返回值类型和修饰符之间有定义的才是泛型方法
public class Test{public static void main(String[] args){//调用test方法,传递字符串数据,那么test方法的泛型就是String类型String rs = test("test");//调用test方法,传递Dog对象,那么test方法的泛型就是Dog类型Dog d = test(new Dog()); }//这是一个泛型方法<T>表示一个不确定的数据类型,由调用者确定public static <T> test(T t){return t;}
}
泛型限定
泛型限定的意思是对泛型的数据类型进行范围的限制。有如下的三种格式
- <?> 表示任意类型
- <? extends 数据类型> 表示指定类型或者指定类型的子类
- <? super 数据类型> 表示指定类型或者指定类型的父类
假设有Car作为父类,BENZ,BWM两个类作为Car的子类
class Car{} class BENZ extends Car{} class BWN extends Car{}public class Test{public static void main(String[] args){//1.集合中的元素不管是什么类型,test1方法都能接收ArrayList<BWM> list1 = new ArrayList<>();ArrayList<Benz> list2 = new ArrayList<>();ArrayList<String> list3 = new ArrayList<>();test1(list1);test1(list2);test1(list3);//2.集合中的元素只能是Car或者Car的子类类型,才能被test2方法接收ArrayList<Car> list4 = new ArrayList<>();ArrayList<BWM> list5 = new ArrayList<>();test2(list4);test2(list5);//2.集合中的元素只能是Car或者Car的父类类型,才能被test3方法接收ArrayList<Car> list6 = new ArrayList<>();ArrayList<Object> list7 = new ArrayList<>();test3(list6);test3(list7);}public static void test1(ArrayList<?> list){}public static void test2(ArrayList<? extends Car> list){}public static void test3(ArrayList<? super Car> list){} }
泛型擦除
就是说泛型只能编译阶段有效,一旦编译成字节码,字节码中是不包含泛型的。而且泛型只支持引用数据类型,不支持
基本数据类型
。
反编译后:ArrayList后面没有泛型