一、反射
1.1、反射的引入
案例:美团外卖–>付款–>要么用微信支付,要么使用支付宝支付
1、新建java项目
下一步,下一步,起个项目名
我们可以把src删了,把Reflect当作项目的父项目。
新建TestReflect模块:包结构:com.msj.reflect
规则指定方:接口(美团外卖)Mtwm.java
package com.msj.reflect;
public interface Mtwm {
void payOnline();
}
微信要使用美团的在线支付功能,就要实现美团的接口(规则)
package com.msj.reflect;
public class WeChat implements Mtwm {
@Override
public void payOnline() {
System.out.println("我已经点了美团的外卖,正在使用微信支付");
}
}
支付宝也实现了美团的支付功能:
package com.msj.reflect;
public class AliPay implements Mtwm {
@Override
public void payOnline() {
System.out.println("我购买了美团的外面,正在使用支付宝进行支付");
}
}
测试:
package com.msj.reflect;
public class Test {
public static void main(String[] args) {
new WeChat().payOnline();
}
}
使用支付宝支付同理:
package com.msj.reflect;
public class Test {
public static void main(String[] args) {
String str = "支付宝";
if("微信".equals(str)) {
new WeChat().payOnline();
}else if("支付宝".equals(str)) {
new AliPay().payOnline();
}else {
System.out.println("暂不支持 " + str + "支付");
}
}
}
把支付方法提取为一个方法:
package com.msj.reflect;
public class Test {
public static void main(String[] args) {
String str = "支付宝";
if("微信".equals(str)) {
wPay(new WeChat());
}else if("支付宝".equals(str)) {
aliPay(new AliPay());
}else {
System.out.println("暂不支持 " + str + "支付");
}
}
public static void wPay(WeChat wc) {
wc.payOnline();
}
public static void aliPay(AliPay ap) {
ap.payOnline();
}
}
上面代码的缺陷:如果在出现其他支付方式,如招商银行,建设银行等在线方式,则需要添加if逻辑判断语句和对应的方法,特别的麻烦:
为了提高代码的扩展性,面向对象提供了多态,上面的代码使用多态实现:
package com.msj.reflect;
public class Test {
public static void main(String[] args) {
String str = "支付宝";
if("微信".equals(str)) {
pay(new WeChat());
}else if("支付宝".equals(str)) {
pay(new AliPay());
}else {
System.out.println("暂不支持 " + str + "支付");
}
}
public static void pay(Mtwm wc) {
wc.payOnline();
}
}
多态的一种形式:方法的形参是接口,具体传入的是接口的实现类的对象—>这就是多态的一种形式。
多态确实可以提高代码的扩展性,但是,扩展性没有达到最好。比如上面的代码中还是有if逻辑判断,如果有其他的支付方式加入,我们还是得加上相应得逻辑判断。
怎么没有达到最好:比如上面得分支,还是需要手动的删除或者添加。
以上问题的解决办法:反射机制
我们现在这里使用反射实现上述功能:(虽然还没学)
package com.msj.reflect;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class TestReflect {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
String str = "com.msj.reflect.WeChat";
Class cls = Class.forName(str);
Object o = cls.newInstance();
Method method = cls.getMethod("payOnline");
method.invoke(o);
}
}
结果:
使用阿里的实现类的时候,就是使用支付宝支付。
1.2、Java反射的概念
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象, 都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
在编译后产生字节码文件的时候,类加载器子系统通过二进制字节流,负责从文件系统加载class文件。
在执行程序(java.exe)时候,将字节码文件读入JVM中—>这个过程叫做类的加载。然后在内存中对应创建一个java.lang.Class对象–>这个对象会被放入字节码信息中,这个Class对象,就对应加载那个字节码信息,这个对象将被作为程序访问方法区中的这个类的各种数据的外部接口。
所以:我们可以通过这个对象看到类的结构,这个对象就好像是一面镜子,透过镜子看到类的各种信息,我们形象的称之为反射
这种“看透”class的能力(the ability of the program to examine itself)被称为introspection(内省、内观、反省)。Reflection和introspection是常被并提的两个术语。
说明:在运行期间,如果我们要产生某个类的对象,Java虚拟机(JVM)会检查该类型的Class对象是否已被加载。
如果没有被加载,JVM会根据类的名称找到.class文件并加载它。一旦某个类型的Class对象已被加载到内存,就可以用它来产生该类型的所有对象。
补充:
动态语言vs静态语言
1、动态语言
是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以
被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运
行时代码可以根据某些条件改变自身结构。
主要动态语言: Object-C、 C#、JavaScript、 PHP、 Python、 Erlang 。
2、静态语言
与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、
C++。
所以Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动
态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。
Java的动态性让编程的时候更加灵活!
1.3、Class类
Class类中的方法
创建一个新的包解构:com.msj.test01
该包下新建一个Person类:
package com.msj.test01;
public class Person {
private int age;
public String name;
private void test() {
System.out.println("Person--> age");
}
public void sleep() {
System.out.println("Person--> sleep");
}
}
Student.java
package com.msj.test01;
public class Student extends Person {
private int sno;
double height;
protected double weight;
public double score;
public String showInfo() {
return "我是一名三好学生";
}
private void work() {
System.out.println("我已以后会找工作,成为一名程序员");
}
public Student() {
System.out.println("空参构造器");
}
private Student(int sno) {
this.sno = sno;
}
Student(int sno, double weight) {
this.sno = sno;
this.weight = weight;
}
}
1.4、获取字节码信息
获取字节码信息的四种方式:在test01中新建测试类
package com.msj.test01;
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Person p = new Person();
Class c1 = p.getClass();
System.out.println("方式一: " + c1);
Class c2 = Person.class;
System.out.println("方式二: " + c2);
Class c3 = Class.forName("com.msj.test01.Person");
System.out.println("方式三: " + c3);
ClassLoader classLoader = Test.class.getClassLoader();
Class c4 = classLoader.loadClass("com.msj.test01.Person");
System.out.println("方式四: " + c4);
}
}
以上四种方式获得的字节码信息都是一样的:可以验证
package com.msj.test01;
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Person p = new Person();
Class c1 = p.getClass();
System.out.println("方式一: " + c1);
Class c2 = Person.class;
System.out.println("方式二: " + c2);
Class c3 = Class.forName("com.msj.test01.Person");
System.out.println("方式三: " + c3);
ClassLoader classLoader = Test.class.getClassLoader();
Class c4 = classLoader.loadClass("com.msj.test01.Person");
System.out.println("方式四: " + c4);
System.out.println(c1 == c2);
System.out.println(c2 == c3);
System.out.println(c3 == c4);
}
}
上面为什么通过Test的类加载器可以获得Person的字节码信息?因为无论是Test,还是Person类,都是通过系统类加载器加载的,而我们自定义的类都是通过系统类加载器加载的,所以我们通过Test获得系统类加载器,然后通过系统类加载器获得了Person的字节码信息。
回头看之前的代码:
Class cls = Class.forName(str);
可以作为Class类的实例的种类:
1、类:无论是外部类,内部类都可以
2、接口
3、注解
4、数组
5、基本数据类型
6、void
验证:
package com.msj.test01;
public class Test01 {
public static void main(String[] args) {
Class<Person> personClass = Person.class;
Class<Comparable> comparableClass = Comparable.class;
Class<Override> overrideClass = Override.class;
int[] arr1 = {1, 2, 3};
Class aClass = arr1.getClass();
int[] arr2 = {5, 6, 7};
Class aClass1 = arr2.getClass();
System.out.println(aClass == aClass1);
Class<Integer> integerClass = int.class;
Class<Void> voidClass = void.class;
System.out.println(voidClass);
}
}
1.5、运行时获取类的结构
补充完善上面提供的类:Person和Student
比如让Person实现一个接口:比如实现Serializable
package com.msj.test01;
import java.io.Serializable;
public class Person implements Serializable {
private int age;
public String name;
private void test() {
System.out.println("Person--> age");
}
public void sleep() {
System.out.println("Person--> sleep");
}
}
让Student也实现一个接口:这个接口自己写:MyInterface
package com.msj.test01;
public interface MyInterface {
void myMethod();
}
自定义注解:
package com.msj.test01;
import static java.lang.annotation.ElementType.*;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
完善Student方法,增加注解等,让所有修饰符都尽可能的用到:
package com.msj.test01;
@MyAnnotation(value = "hello")
public class Student extends Person implements MyInterface {
private int sno;
double height;
protected double weight;
public double score;
@MyAnnotation(value = "himethod")
public String showInfo() {
return "我是一名三好学生";
}
public String showInfo(int a, int b) {
return "这是showInfo的一个重载方法";
}
private void work() {
System.out.println("我已以后会找工作,成为一名程序员");
}
void happy() {
System.out.println("做人最重要的开开心心每一天");
}
protected int getSno() {
return this.sno;
}
public Student() {
System.out.println("空参构造器");
}
public Student(double weight, double height) {
this.weight = weight;
this.height = height;
System.out.println("两个参数的构造器");
}
private Student(int sno) {
this.sno = sno;
}
Student(int sno, double weight) {
this.sno = sno;
this.weight = weight;
}
protected Student(int sno, double height, double weight) {
this.sno = sno;
}
@Override
@MyAnnotation(value = "helloMyMethod")
public void myMethod() {
System.out.println("我重写了myMethod这个方法");
}
@Override
public String toString() {
return "Student{" +
"sno=" + sno +
", height=" + height +
", weight=" + weight +
", score=" + score +
'}';
}
}
一、获取构造器和创建对象
1、新建Test02类
package com.msj.test01;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Test02 {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class cls = Student.class;
Constructor[] c1 = cls.getConstructors();
for (Constructor c : c1) {
System.out.println(c);
}
System.out.println("---------------------------------");
Constructor[] c2 = cls.getDeclaredConstructors();
for (Constructor c : c2) {
System.out.println(c);
}
System.out.println("----------------------------------");
Constructor con1 = cls.getConstructor();
System.out.println(con1);
System.out.println("-----------------------------------");
Constructor con2 = cls.getConstructor(double.class, double.class);
System.out.println(con2);
System.out.println("-----------------------------------");
Constructor protectCon = cls.getDeclaredConstructor(int.class, double.class, double.class);
Constructor privateCon = cls.getDeclaredConstructor(int.class);
Constructor noCon = cls.getDeclaredConstructor(int.class, double.class);
System.out.println(privateCon);
System.out.println(protectCon);
System.out.println(noCon);
System.out.println("----------------------------------");
Object o1 = con1.newInstance();
System.out.println(o1);
System.out.println("-----------------------------------");
Object o2 = con2.newInstance(180.5, 170.6);
System.out.println(o2);
System.out.println("-----------------------------------");
privateCon.setAccessible(true);
Object o3 = privateCon.newInstance(12);
System.out.println(o3);
System.out.println("------------------------------------");
Object o4 = protectCon.newInstance(12, 13.5, 15.2);
System.out.println(o4);
System.out.println("-------------------------------------");
Object o5 = noCon.newInstance(12, 180.2);
System.out.println(o5);
}
}
效果:
注意:对于非private修饰的构造器设置可见性(setAccessible)是不起作用的。
二、通过反射获取属性和对属性赋值
新建Test03.java
Modifier.java中的修饰符的定义:如public是ox01,static是0x08,则public static的返回值是0x09,对应十进制也是9
Test03.java
package com.msj.test01;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class Test03 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
Class cls = Student.class;
Field[] fields = cls.getFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println("-----------------------------------");
Field[] fields1 = cls.getDeclaredFields();
for (Field field : fields1) {
System.out.println(field);
}
System.out.println("-----------------------------------");
Field score = cls.getField("score");
System.out.println(score);
System.out.println("----------------------------------");
Field sno = cls.getDeclaredField("sno");
System.out.println(sno);
System.out.println("----------------------------------");
String name = sno.getName();
System.out.println(name);
Class type = sno.getType();
System.out.println(type);
System.out.println(type.getName());
int modifiers = sno.getModifiers();
System.out.println(modifiers);
System.out.println("修饰符: " + Modifier.toString(modifiers));
System.out.println(Modifier.toString(sno.getModifiers()));
System.out.println("----------------------------------");
Object obj = cls.newInstance();
score.set(obj, 98);
System.out.println(obj);
}
}
三、获取方法和调用方法
获取异常:我们给myMethod加一个运行时异常
// 实现myMethod方法
@Override
@MyAnnotation(value = "helloMyMethod")
public void myMethod() throws RuntimeException {
System.out.println("我重写了myMethod这个方法");
}
新建类:Test04.java
package com.msj.test01;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class Test04 {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
Class cls = Student.class;
Method[] methods = cls.getMethods();
for(Method method: methods) {
System.out.println(method);
}
System.out.println("---------------------");
Method[] declaredMethods = cls.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println(method);
}
System.out.println("---------------------------------");
Method showInfo = cls.getMethod("showInfo");
System.out.println(showInfo);
Method showInfo1 = cls.getMethod("showInfo", int.class, int.class);
System.out.println(showInfo1);
System.out.println("----------------------");
Method work = cls.getDeclaredMethod("work");
System.out.println(work);
System.out.println("-----------------");
System.out.println("方法名:" + work.getName());
System.out.println("修饰符:" + Modifier.toString(work.getModifiers()));
System.out.println("返回值:" + work.getReturnType());
System.out.println("参数列表:");
Class[] parameterTypes = showInfo1.getParameterTypes();
for (Class parameterType : parameterTypes) {
System.out.println(parameterType);
}
System.out.println("------------------------");
Method myMethod = cls.getMethod("myMethod");
Annotation[] annotations = myMethod.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
System.out.println("-------------------------");
Class[] exceptionTypes = myMethod.getExceptionTypes();
for (Class type : exceptionTypes) {
System.out.println(type);
}
System.out.println("--------------------------");
Object o = cls.newInstance();
myMethod.invoke(o);
System.out.println("------------------------");
System.out.println(showInfo1.invoke(o, 12, 45));
}
}
四、获取接口
获取当前类实现的接口,所在包,注解等
新建Test05.java
package com.msj.test01;
import java.lang.annotation.Annotation;
public class Test05 {
public static void main(String[] args) {
Class cls = Student.class;
Class[] interfaces = cls.getInterfaces();
for (Class anInterface : interfaces) {
System.out.println(anInterface);
}
System.out.println("-------------------");
Class superclass = cls.getSuperclass();
Class[] interfaces1 = superclass.getInterfaces();
for (Class aClass : interfaces1) {
System.out.println("父类的接口:" + aClass);
}
System.out.println("---------------------");
Package aPackage = cls.getPackage();
System.out.println(aPackage);
System.out.println("----------------------");
Annotation[] annotations = cls.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("类上的注解:" + annotation);
}
}
}
1.6、关于反射的面试题
一、问题1:创建Person的对象,以后用new Person()创建,还是使用反射创建?
思路:如果有Person类,我们就不使用反射创建对象,使用类创建实例更好—>只有在运行时才知道使用的是那个类,这时才需要使用反射创建对象。
二、问题2:反射是否破坏了面向对象的封装性?
思路:如果我们未在运行时直接使用反射创建实例,确实会破坏类的封装性,但在面试时绝对不可以这样答。我们使用反射是为了使语言有更好的动态性,可以在运行做更多的东西,虽然反射会破坏类的封装性,但官方却不建议我们这样的,即我们在使用反射的时候尽量去访问public修饰的东西,对于非public修饰的东西,我们尽量不去访问,保证类的封装性。
二、Java注解
2.1、junit单元测试引入
1、软件测试的目的:软件测试的目的使在规定的条件下对程序进行操作,已发现程序错误,衡量软件质量,并对其是否满足设计要求进行评估的过程。
2、测试分类
Junit属于白盒测试
没有Junit的情况下如何测试:
新建一个module:TestJavaSE
包结构:com.msj.calculator
1、新建Calculator测试类
package com.msj.calculator;
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public int sub(int a, int b) {
return a - b;
}
}
一般测试:在该包下新建一个测试类:
Test.java
package com.msj.calculator;
public class Test {
public static void main(String[] args) {
Calculator cal = new Calculator();
System.out.println(cal.add(12, 13));
System.out.println(cal.sub(30, 20));
}
}
没有使用Junit的时候:缺点
1、测试一定走main方法,并且main方法的格式还不能写错。
2、要是在同一个main中测试的话,那么不需要的东西必须注释掉,如果要测试不同的方法,并且不一起测试,那么就要注释无用的或是创建新的测试类,这样的话,要建的测试类就会特别的多。
3、测试逻辑分开的话,需要定义多个测试类,非常的麻烦。
4、业务逻辑和测试混用,代码十分凌乱。
基于以上缺点,我们可以选择Junit测试工具。
我们新建一个测试的包:即在com.msj下新建一个test包
在test包下新建测试类
1、一般测试和业务做一个分离,分离为不同的包
2、测试类见名知意:公司域名到这些+test,以后测试类就放在这个包下。
3、测试类的名字一般叫xxxTest
4、测试方法的运行—>这个方法可以独立运行,不依托于main方法:如测试加法:testAdd(),测试减法:testSub(),见名之意,并且方法一般为无参方法,方法的返回值类型为void。
5、测试方法定义完成后 ,不能直接运行,必须在方法前加入一个注解:@Test
6、要使用@Test注解,需要导入Junit的环境。
添加Junit的方法,选择Junit4加入到classPath即可。
导入Junit后,我们在外部依赖就可以看到Junit的依赖了。
如:CalculatorTest.java
package com.msj.test;
import com.msj.calculator.Calculator;
import org.junit.Test;
public class CalculatorTest {
@Test
public void testAdd() {
Calculator cal = new Calculator();
System.out.println(cal.add(100, 54));
}
@Test
public void testSub() {
Calculator cal = new Calculator();
System.out.println(cal.sub(342, 89));
}
}
这里的每个方法都可以独立运行,且互不干扰。
7、结果判定:
绿色:编译通过,可以正常运行
红色:出现异常
8、即使出现绿色,也不意味着你的测试就通过了,因为代码中可能存在逻辑错误,这种请款需要加入断言。
如,我们把加法的逻辑写为了减法:
Calculator.java
package com.msj.calculator;
public class Calculator {
public int add(int a, int b) {
return a - b;
}
public int sub(int a, int b) {
return a - b;
}
}
CalculatorTest.java
加入断言:Assert,该类属于Junit下
package com.msj.test;
import com.msj.calculator.Calculator;
import org.junit.Assert;
import org.junit.Test;
public class CalculatorTest {
@Test
public void testAdd() {
Calculator cal = new Calculator();
int result = cal.add(10, 30);
Assert.assertEquals(40, result);
}
@Test
public void testSub() {
Calculator cal = new Calculator();
System.out.println(cal.sub(342, 89));
}
}
报红:从而知道程序中逻辑出现了问题
Junit中另外两个注解:首先把Calculator的逻辑改对。我们想在程序中知道测试什么时候开始,什么时候结束,我们可以使用如下方式:
package com.msj.test;
import com.msj.calculator.Calculator;
import org.junit.Assert;
import org.junit.Test;
public class CalculatorTest {
@Test
public void testAdd() {
System.out.println("-----------------方法测试开始了-----------------");
Calculator cal = new Calculator();
int result = cal.add(10, 30);
Assert.assertEquals(40, result);
System.out.println("-----------------方法测试结束了-----------------");
}
@Test
public void testSub() {
System.out.println("-----------------方法测试开始了-----------------");
Calculator cal = new Calculator();
System.out.println(cal.sub(342, 89));
System.out.println("-----------------方法测试结束了-----------------");
}
}
在上面的代码中我们两个方法中都使用了同样的语句告诉我们测试的开始和结束。我们尝试把上面的代码做一个提取:然后配置@After和@Before注解,就可以实现以上的功能:
@Before:某一个方法中加入了@Before以后,那么这个方法中的功能会在测试方法执行前执行。
@After:某一个方法加入了@After注解后,那么这个方法中的功能会在测试方法执行前现在执行。
一般会在@Before修饰的那个方法中加入:加入一些申请资源的代码,如申请数据库资源,申请IO资源,申请网络资源等
一般会在@After修饰的那个方法中加入:加入一些释放资源的代码,如释放数据资源,释放IO资源,释放网络资源等。
2.2、注解的引入
历史:JIDK5.0新增,注解(Annotation),也叫元数据
什么是注解:注解其实就是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理,通过使用注解,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息,代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。
使用注解要在前面增加@符号,并把该注解当成一个修饰符使用,用于修饰它支持的程序元素。
注解的重要性:Annotation可以像修饰符一样被使用,可以用于修饰包,类,构造器,成员变量,参数,局部变量的声明,这些信息被保存在Annotation的name=value的键值对中,在JavaSE中,注解的使用比较简单,比如标记过时的功能,忽略警告等,在JavaEE/Android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的冗余代码和XML配置等,未来的开发模式都是基于注解的,JPA(Java的持久化API)是基于注解的,Spring2.5x以上都是基于注解的,Hibernate3.x以后也是基于注解的,现在的Struts2有一部分也是基于注解的,注解是一种趋势,一定程度上可以说:框架=注解+反射+设计模式
Junit注解:@Test,@After,@Before,注意这些注解能够被识别,就需要导入Junit,因为Junit在底层对这些注解做了支持。
2.3、文档注解
说明注释允许你在程序中嵌入关于程序的信息,你可以使用javadoc工具软件来生成信息,并输出到HTML文件中。
说明注释,使你更加方便地记录你的程序信息。
文档注解我们一般使用在文档注释中,配合javadoc工具
javadoc工具软件识别一下标签:
1、新建一个TestAnnotation模块:
包结构:com.msj.annotation
Student类:
package com.msj.anntation;
public class Student {
}
新建Person类:
package com.msj.anntation;
public class Person {
public void eat(int num1, int num2) {
}
public int sleep(int age) {
new Student();
if(age > 100) {
throw new RuntimeException();
}
if(age < 0) {
throw new IndexOutOfBoundsException();
}
return 10;
}
}
生成注释:
在控制台使用命令行输入:javadoc命令
在Idea中也可以使用工具生成:Idea中Javadoc的使用
选择只给我当前的模块生成API
也可以直接对整个工程生成:、
选择输出路径:
防止乱码:在Other command line arguments栏输入:-encoding utf-8 -charset utf-8
生成成功后:就是一个API文档
2.4、JDK内置的三个注解
@Override:限定重写父类方法,该注解只能用于方法
@Deprecated:用于表示所修饰的元素(类,方法,构造器,属性等)已过时,通常是因为所修饰的结构危险或存在更好的选择。
@SuppressWarnings:抑制编译器警告
1、@Override:
在com.msj下先创建一个anno02的包
重写父类方法成功:不加@Override也可以,但有时我们会把eat写为eta,我们以为也是重写了父类的方法,但由于子类的方法名和父类的方法名不一致从而导致重写不成功,于是我们可以加上@Override注解,它会帮我们检查重写是否成功,如果不成功就会报红。
Person.java
package com.msj.anno02;
public class Person {
public void eat() {
System.out.println("父类的eat方法");
}
}
Person.java
package com.msj.anno02;
public class Student extends Person {
@Override
public void eat() {
System.out.println("子类eat方法");
}
}
@Override:限定重写方法,只要重写的方法有问题,就会有错误提示。
2、@Deprecated
我们使用Java中已经过时的方法:
点进去看源码:
上面就会有一个@Deprecated的注解
Student.java
package com.msj.anno02;
public class Student extends Person {
@Override
public void eat() {
System.out.println("子类eat方法");
}
@Deprecated
public void study() {
System.out.println("学生在学习....");
}
}
使用:这时study也会变成一个废弃的方法
package com.msj.anno02;
import java.util.Date;
public class Test {
public static void main(String[] args) {
Date date = new Date();
System.out.println(date.getMonth());
Student studnent = new Student();
studnent.study();
}
}
在方法前加入@Deprecated这个注解,这个方法就会变为过期的方法,当让这个注解也可以用在类上。
3、@SuppressWarnings
package com.msj.anno02;
public class Test {
public static void main(String[] args) {
@SuppressWarnings("unused")
int age = 10;
}
}
如上面定义了一个变量age,如果没有加上@SuppressWarnings就会说灰色的,鼠标放上去会提示改变了未使用过,如果我们加上@SuppressWarnings注解,经过就会消失,@SuppressWarnings需要传入一个参数,如这里的经过时unused,我们就传入unused.
@SuppressWarnings(“unused”, “rwatypes”)
rwatypes:压制泛型的警告。
注解使用实列:替代配置文件的注解
Servlet3.0之前的配置:
Servlet3.0之后的配置:
@WebServlet("/hello")
这个注解可以代替很复杂的功能。
2.5、自定义注解
1、自定义注解使用很少,一般情况下都是使用现成的注解。
2、自定义注解
新建包:com.msj.anno03
新建注解:MyAnnotation
发现:定义注解声明使用的关键字:@interface,跟接口没有一点关系。
3、注解的内部:
以@SuppressWarnings为例:内部:
String[] value();
这个value()是属性还是方法?
答案:看上去是无参方法(实际上也叫无参方法),但实际上我们应该理解为成员变量,一个属性。
无参数方法名字–> 成员变量的名字。
无参方法的返回值—>成员变量的类型。
如上:属性名是value(成员变量名),属性的类型是String数组(成员变量的返回值)
无参数方法的类型可以是八种基本数据类型,还可以是String 、枚举、注解类型,还可以是以上类型对应的数组。
该属性叫配置参数,使用注解的话,如果你定义了配置参数,就必须该参数赋值。
PS:如果一个成员变更了的话,名字尽量叫value(只是预定俗称的用法)
自定义注解如下:
package com.msj.anno03;
public @interface MyAnnotation {
String[] value();
}
4、使用注解
【1】、如果你定义了配置参数,就必须该参数赋值。
【2】、如果只有一个参数,并且该参数的名字是value,则value=就可省略不写,直接写要配置的参数即可。
package com.msj.anno03;
@MyAnnotation({"abc", "def", "hig"})
public class Person {
}
【3】、如果你给配置设置默认的值,使用的时候可以不用传值
在定义一个注解:
package com.msj.anno03;
public @interface MyAnnotation2 {
String[] value() default "abc";
}
使用
package com.msj.anno03;
@MyAnnotation({"abc", "def", "hig"})
@MyAnnotation2
public class Person {
}
【4】、一个注解的内部是可以不配内置参数的
package com.msj.anno03;
public @interface MyAnnotation3 {
}
内部没有定义配置参数的注解,可以叫做标记。
内部定义内置参数,叫做元数据。
【5】、注解的使用:现在只学习注解的技能点,具体怎么应用,后面慢慢学。
2.6、元注解
元注解是用于修饰其他注解的注解。
JDK5.0提供了四种元注解:Retention,Target,Documented,Inherited
1、元注解:Retention:保留,保持
定义:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
在使用该注解的时候要传入一个参数:value,该参数的类型是:RetentionPolicy,RetentionPolicy是一个枚举类型,定义如下:
package java.lang.annotation;
public enum RetentionPolicy {
SOURCE,
CLASS,
RUNTIME
}
@Retention:用于修饰注解,用于指定修饰的那个注解的生命周期,@Retention包含一个RetentionPolicy枚举类型的成员变量,使用时必须为该value成员变量指定值。
RetentionPolicy.SOUREC:在源文件中有效(即源文件保留),编译器直接丢弃这种策略注解,在.class文件中不会保留注解信息。
RententionPolicy.CLASS:在calss文件中有效(即class保留),保留在.class文件中,但是运行Java程序时,他就不会继续加载了,不会保留在内存中,JVM不会保留注解,如果注解没有加Retention元注解,那么相当于默认的注解就是这种状态。
RententionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行Java程序时,JVM会保留该注解,并且加载到内存中,那么程序可以通过反射获取该注解。
MyAnnotation.java
package com.msj.anno03;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
String[] value();
}
重新构建一下项目:
构建后刷新一下生成的out项目(就会把新生成的class文件加入)
Person.java
package com.msj.anno03;
@MyAnnotation({"abc", "def", "hig"})
@MyAnnotation2
public class Person {
}
由于MyAnnotation注解我们设置它的生命周期只在源文件有效,所以Person的class文件中不会保留该注解,兵器MyAnnotation2没有加上@Retention注解,所以它的默认生命周期是保留到class文件中(即相当于RetentionPolicy.CLASS),所以我们可以看到class文件中有MyAnnotation2注解:
2、元注解Target
@Target:用于修饰注解的注解,用于指定被修饰的注解能用于修饰那些程序元素,@Target包含一个名为value的成员变量。
package com.msj.anno03;
public @interface MyAnnotation4 {
}
没有@Target的注解默认什么都可以修饰:既可以修饰类,方法和属性
package com.msj.anno03;
@MyAnnotation4
public class Student {
@MyAnnotation4
int age;
@MyAnnotation4
public Student() {}
@MyAnnotation4
public void eat() {
}
}
限定注解可以修饰的东西:需要使用@Target注解,需要传入要修饰的类型:
value是一个ElementType类型,其定义如下:
@Target的定义
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
ElementType的定义:
public enum ElementType {
TYPE,
FIELD,
METHOD,
PARAMETER,
CONSTRUCTOR,
LOCAL_VARIABLE,
ANNOTATION_TYPE,
PACKAGE,
TYPE_PARAMETER,
TYPE_USE
}
TYPE:能用于类上
Method:只能用于方法上
Field:用于属性上
Parameter:用于形参上
Constructor:用于构造方法上
自定义注解想加载什么上就传入对应的类型即可。
package com.msj.anno03;
import static java.lang.annotation.ElementType.*;
import java.lang.annotation.Target;
@Target({CONSTRUCTOR, METHOD, FIELD, TYPE})
public @interface MyAnnotation4 {
}
3、@Documented
被@Documented修饰的注解,被Javadoc提取的时候,该注解会被提取到Java 的API文档中。如@Deprecated被@Documented修饰,则注解@Deprecated在被javadoc提取的时候也会被提取到API文档中:如刚才我们使用的setDate方法就是用了@Deprecated,我们可以看一下它的API文档:
可见@Deprecated也在API文档中。
如果:Documented修饰了注解,那么Javadoc提取的时候,该注解不被提取到API文档中。
4、@Inherited
被它修饰的注解将具有继承性,如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解。
很少被使用
新建包:com.msj.anno04
package com.msj.anno04;
import java.lang.annotation.Inherited;
@Inherited
public @interface MyAnnotation {
}
父类:Person.java
package com.msj.anno04;
@MyAnnotation
public class Person {
}
子类:Student.java
package com.msj.anno04;
public class Student extends Person {
}
注解:如果MyAnnotation注解使用了@Inherited之后,就具备了继承性,那么相当于子类Student也使用了MyAnnotation这个注解。
三、枚举
3.1、枚举的引入
【1】数学:枚举法
1<x<4
2<y<5
求:x+y=6
枚举法:一个一个的试
使用枚举法的前提:有限,确定
【2】Java中,类的对象是有限个、确定的,这个类我们可以定义为枚举类。
举例:一个星期类:星期一二四五六日
性别:男女
季节:春夏秋冬等
【3】自定义枚举类:
1、新建模块:TestEnum
包:com.msj.enum
package com.msj.enum01;
public class Season {
private final String seasonName = "春天";
private final String seasonDesc = "春暖花开";
}
但是上面的属性一旦写完,就固定了,不能再修改,所以我们不能这样写,可以利用构造器赋值。
package com.msj.enum01;
public class Season {
private final String seasonName;
private final String seasonDesc;
public Season(String seasonName, String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
}
注意:使用final修饰后,对应的属性只能使用构造器或者直接赋值,其他赋值方式(如set)等都是不可以的。
使用上述的方式后,就意味着外界可以随便调用我的构造方法进行赋值,但是我不想让外界调用该构造方法,可以使用private修饰
package com.msj.enum01;
public class Season {
private final String seasonName;
private final String seasonDesc;
private Season(String seasonName, String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
public static final Season SPRING = new Season("春天", "春暖花开");
public static final Season SUMMER = new Season("夏天", "烈日炎炎");
public static final Season AUTUMN = new Season("秋天", "硕果累累");
public static final Season WINTER = new Season("冬天", "白雪皑皑");
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}
使用我们定义的枚举类:
package com.msj.enum01;
public class Test {
public static void main(String[] args) {
Season spring = Season.SPRING;
System.out.println(spring);
System.out.println(spring.getSeasonName());
System.out.println(spring.getSeasonDesc());
}
}
3.2、枚举类
JDK1.5后使用eumn关键字来定义枚举类
新建包:com.msj.enum02
package com.msj.enum02;
public enum Season {
SPRING("春天", "春暖花开"),
SUMMER("夏天", "烈日炎炎"),
AUTUMN("秋天", "硕果累累"),
WINTER("冬天", "白雪皑皑");
private final String seasonName;
private final String seasonDesc;
private Season(String seasonName, String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}
修改:
枚举类的实测:
package com.msj.enum02;
public class TestSeason {
public static void main(String[] args) {
Season season = Season.SUMMER;
System.out.println(season);
System.out.println(season.getSeasonDesc());
}
}
效果:
我们现在把枚举类中的toString方法删除:
package com.msj.enum02;
public enum Season {
SPRING("春天", "春暖花开"),
SUMMER("夏天", "烈日炎炎"),
AUTUMN("秋天", "硕果累累"),
WINTER("冬天", "白雪皑皑");
private final String seasonName;
private final String seasonDesc;
private Season(String seasonName, String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
}
我们看看打印的是什么:
可以看出enum类不是继承自Object类,因为如果集成自Object类,没有实现toString类,则打印的是地址,带@符号的,如(我使用com.msj.enum01中的方法注释掉toString()方法测试的):
所以enum的直接父类不是Object(跟父类也属于Object)。我们通过代码看一下它的父类是谁:
package com.msj.enum02;
public class TestSeason {
public static void main(String[] args) {
Season season = Season.SUMMER;
System.out.println(season);
System.out.println(season.getSeasonDesc());
System.out.println(Season.class.getSuperclass().getName());
}
}
结果:
enum的直接父类是java.lang下的Enum类。但是我们自定的枚举类(即不使用enum关键字定义的枚举类)的上层父类是Object。
enum枚举类的补充:
我们看到别人的枚举类经常是不带括号的枚举类,我们刚才创建的枚举类实际上是调用了构造器进行初始化。
新建包:com.msj.enum03
package com.msj.enum03;
public enum Season {
SPRING,
SUMMER,
AUTUMN,
WINTER
}
相当于这个枚举类没有属性,里面的常量(对象)都是调用空参构造器进行初始化。如SPRING相当于:
public static final Season SPEING = new Season();
当调用空参时,圆括号可以省略。
为什么这么简单:因为枚举类底层没有属性,属性构造器,toString,get方法都删掉不写了。
源码案例:如Thread源码中就有一个内置的枚举类:State
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
看大的形态就剩常量名(常量对项名了)
3.3、Enum类的常用方法
在enum包下新建一个TestSeason类:
package com.msj.enum03;
public class TestSeason {
public static void main(String[] args) {
Season autumn = Season.AUTUMN;
System.out.println(autumn);
System.out.println("-------------------");
Season[] values = Season.values();
for (Season value : values) {
System.out.println(value);
}
System.out.println("------------------");
Season autumn1 = Season.valueOf("AUTUMN");
System.out.println(autumn1);
}
}
3.4 、枚举类实现接口
创建一个新的包:com.msj.enum04
枚举类本质也是一个类,他也可以实现接口:
定义一个TestInterface接口:
package com.msj.enum04;
public interface TestInterface {
void show();
}
Season枚举类:实现了TestInterface接口
package com.msj.enum04;
public enum Season implements TestInterface {
SPRING,
SUMMER,
AUTUMN,
WINTER;
@Override
public void show() {
System.out.println("这是Season枚举类......");
}
}
Test.java
package com.msj.enum04;
public class Test {
public static void main(String[] args) {
Season autumn = Season.AUTUMN;
autumn.show();
Season summer = Season.SUMMER;
summer.show();
}
}
发现枚举的实列调用的show方法都是同一个。
即所有的枚举对象,调用show方法,结果都是一样的。
但是现在我们想做的是:不同的对象,调用的show方法也不一样,枚举也提供了相应的方法,如下:
package com.msj.enum04;
public enum Season implements TestInterface {
SPRING{
@Override
public void show() {
System.out.println("这是春天...");
}
},
SUMMER {
@Override
public void show() {
System.out.println("这是夏天...");
}
},
AUTUMN {
@Override
public void show() {
System.out.println("这是秋天...");
}
},
WINTER {
@Override
public void show() {
System.out.println("这是冬天...");
}
}
}
刚才的测试类:
package com.msj.enum04;
public class Test {
public static void main(String[] args) {
Season autumn = Season.AUTUMN;
autumn.show();
Season summer = Season.SUMMER;
summer.show();
}
}
实现了不同的对象调用不同的方法:
3.5、枚举的应用
新创建一个包:com.msj.enum05
package com.msj.enum05;
public class Person {
private int age;
private String name;
private String sex;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
Test.java
package com.msj.enum05;
public class Test {
public static void main(String[] args) {
Person person = new Person();
person.setAge(19);
person.setName("小美");
person.setSex("猴王");
System.out.println(person);
}
}
上面的代码:我们知道性别要么是男,要么是女,但是我输入了男、女之外的其他值,仍然能赋值成功,当然我们也可以使用if逻辑判断控制用户的输入,当输入非法是抛出异常或是设置默认值,我们这里使用枚举实现输入字符的非法检查:
package com.msj.enum05;
public class Person {
private int age;
private String name;
private Gender sex;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Gender getSex() {
return sex;
}
public void setSex(Gender sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
Gender.java
package com.msj.enum05;
public enum Gender {
MAN,
WOMAN
}
Test.java
package com.msj.enum05;
public class Test {
public static void main(String[] args) {
Person person = new Person();
person.setAge(19);
person.setName("小美");
person.setSex(Gender.MAN);
System.out.println(person);
}
}
测试:
使用枚举是在方法的入口处对参数进行限制,以前的方式是在方法的内部进行限制。
还可以使用枚举结合switch进项使用:
package com.msj.enum05;
public class Test02 {
public static void main(String[] args) {
Gender man = Gender.MAN;
switch(man) {
case MAN:
System.out.println("是个女孩");
break;
case WOMAN:
System.out.println("是个男孩");
}
}
}
四、泛型
什么是泛型?
Java推出泛型之前,程序员可以构建一个元素类型为Object的集合,该集合能够存储任意的数据类型对象,而在使用集合的过程中,需要程序员明确知道存储每个元素的数据类型,否则很容易引发ClassCastException异常。
新建模块:JavaGenericTest
包结构:com.msj.demo01
package com.msj.deno01;
import java.util.ArrayList;
public class MainClass {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("java");
list.add(100);
list.add(true);
for (Object o : list) {
System.out.println(o);
}
System.out.println("---------------------");
for (int i = 0; i < list.size(); i++) {
Object o = list.get(i);
System.out.println(o);
}
System.out.println("---------------------");
for (int i = 0; i < list.size(); i++) {
String o = (String)list.get(i);
System.out.println(o);
}
}
}
可以发现,我们的程序再强转数据类型时会报错(即不使用Object类时),而且这个错误是发生在运行期,在编译的时候并不报错,这就很可怕。
泛型的概念:Java泛型(generics)是JDK中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许我们在编译时检测到非法的数据类型结构。
泛型的本质就是参数化类型(类型参数化),也就是所操作的数据类型被指定为一个参数。
使用泛型后,Java会限定我们传入的数据类型,指定了传入的数据类型后,只能传入对应的数据类型,传入的其他的数据类型直接提示报错。
package com.msj.deno01;
import java.util.ArrayList;
public class MainClass {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList();
list.add("java");
list.add("vue");
list.add("html");
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
}
}
并且我们在使用集合中的元素的时候,直接使用String(创建list时指定的类型)接收对象中的数据,并且不需要强制类型转换。
泛型的好处:
4.1、泛型类
1、泛型类的定义语法
class 类名称 <泛型标识,泛型标识, ......> {
private 泛型标识 变量名;
......
}
常用的泛型标识:T、E、K、V
K、V几乎都是集合中使用:键值对K-V
package com.msj.deno01;
public class Generic<T> {
private T key;
public Generic() {}
public Generic(T key) {
this.key = key;
}
public T getKey() {
return key;
}
public void setKey(T key) {
this.key = key;
}
@Override
public String toString() {
return "Generic{" +
"key=" + key +
'}';
}
}
2、泛型类的使用
使用语法:
类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>();
jdk1.7以后,后面的<>中的具体的数据类型可以省略不写。
类名<具体的数据类型> 对象名 = new 类名<>();
package com.msj.deno01;
public class MainClass {
public static void main(String[] args) {
Generic<String> str = new Generic<>("java");
String key = str.getKey();
System.out.println("key1=" + key);
Generic<Integer> integerGeneric = new Generic<>(125);
int key2 = integerGeneric.getKey();
System.out.println("key2=" + key2);
Generic generic = new Generic("ABC");
Object key3 = generic.getKey();
System.out.println("key3="+ key3);
System.out.println(integerGeneric.getClass());
System.out.println(str.getClass());
System.out.println(integerGeneric.getClass() == str.getClass());
}
}
注意事项:
- 泛型类,如果没有指定数据类型,此时,操作类型是Object。
- 泛型的类型参数只能是类类型,不能是基本数据类型。
- 泛型类型在逻辑上可以看成是多个不同的类型,但实际上都是相同的类型。
4.2、泛型类小案例
抽奖小案例:抽奖器(ProductGetter.java)
package com.msj.deno01;
import java.util.ArrayList;
import java.util.Random;
public class ProductGetter<T> {
private T product;
ArrayList<T> list = new ArrayList<>();
Random random = new Random();
public void addProduct(T t) {
list.add(t);
}
public T getProduct() {
product = list.get(random.nextInt(list.size()));
return product;
}
}
测试
package com.msj.deno01;
public class MainClass {
public static void main(String[] args) {
ProductGetter<String> stringProductGetter = new ProductGetter<>();
String[] strProducts = {"苹果手机", "小米手机", "扫地机器人"};
for (int i = 0; i < strProducts.length; i++) {
stringProductGetter.addProduct(strProducts[i]);
}
String product = stringProductGetter.getProduct();
System.out.println("恭喜你抽中了->" + product);
System.out.println("--------------------------");
ProductGetter<Integer> productGetter = new ProductGetter();
int[] cash = {1500, 5000, 2500, 3000, 900, 300};
for (int i = 0; i < cash.length; i++) {
productGetter.addProduct(cash[i]);
}
int money = productGetter.getProduct();
System.out.println("恭喜你抽中 " + money + "元现金");
}
}
4.3、泛型类派生子类
从泛型类派生子类:
class ChildGeneric<T> extends Generic<T>
class ChildGenerric extends Generic<String>
父类:
package com.msj.deno01;
public class Parent<E> {
private E value;
public void setValue(E value) {
this.value = value;
}
public E getValue() {
return value;
}
}
1、子类也是泛型类
继承:在创建子类的之前会先创建父类
如果不指定父类的类型,重写父类的方法的时候默认使用Object类型
package com.msj.deno01;
public class ChildFirst<T> extends Parent{
@Override
public Object getValue() {
return super.getValue();
}
}
如果指定父类的类型,则父类的泛型类型和子类的泛型类型要一致
package com.msj.deno01;
public class ChildFirst<T> extends Parent<T>{
@Override
public T getValue() {
return super.getValue();
}
}
测试:
package com.msj.deno01;
public class Test01 {
public static void main(String[] args) {
ChildFirst<String> stringChildFirst = new ChildFirst<>();
stringChildFirst.setValue("abc");
String value = stringChildFirst.getValue();
System.out.println(value);
}
}
我们也可以对子类做泛型扩展,但至少要有一个子类泛型类型与父类泛型类型一致:如
package com.msj.deno01;
public class ChildFirst<T, E, K> extends Parent<T>{
@Override
public T getValue() {
return super.getValue();
}
}
2、子类不是泛型类
如果子类不是泛型类,并且不指定父类的泛型类型,父类仍是以Object来操作的。
package com.msj.deno01;
public class ChildSecond extends Parent {
@Override
public Object getValue() {
return super.getValue();
}
}
如果要指定父类的泛型类型,就要明确父类的泛型类型(即父类的泛型类型不能再写T、E、K等)
package com.msj.deno01;
public class ChildSecond extends Parent<String> {
@Override
public String getValue() {
return super.getValue();
}
@Override
public void setValue(String value) {
super.setValue(value);
}
}
这时重写父类方法时就有明确的类型了。
package com.msj.deno01;
public class Test01 {
public static void main(String[] args) {
ChildFirst<String> stringChildFirst = new ChildFirst<>();
stringChildFirst.setValue("abc");
String value = stringChildFirst.getValue();
System.out.println(value);
System.out.println("--------------------");
ChildSecond childSecond = new ChildSecond();
childSecond.setValue("马玉梅");
System.out.println(childSecond.getValue());
}
}
4.4、泛型接口
泛型接口的定义语法:
interface 接口名称 <泛型标识,泛型标识, ......> {
泛型标识 方法名();
......
}
泛型接口的使用:
- 实现类不是泛型类,接口要明确数据类型(不明确则以Object类型操作)
- 实现类也是泛型类,实现类和接口的泛型类型要一致(不写泛型类型则以Object类型操作)
新建包:com.msj.demo02
1、不指定接口的泛型类型,则以Object类型来操作
package com.msj.demo02;
public class Apple implements Generator{
@Override
public Object getKey() {
return null;
}
}
2、子类不是泛型类:实现泛型接口的类不是泛型类,需要明确实现泛型接口的数据类型(即使不指定接口的数据类型,他也会自动指定泛型接口的数据类型为Object)
package com.msj.demo02;
public class Apple implements Generator<String>{
@Override
public String getKey() {
return "hello java";
}
}
package com.msj.demo02;
public class Test01 {
public static void main(String[] args) {
Apple apple = new Apple();
String key = apple.getKey();
System.out.println(key);
}
}
3、实现类是泛型类
不指定接口的泛型类型时,同理
package com.msj.demo02;
public class Pair<T> implements Generator{
@Override
public Object getKey() {
return null;
}
}
指定接口的泛型类型:只能与实现类的泛型类型一致,我们这里对泛型实现类做了泛型扩充
package com.msj.demo02;
public class Pair<T, E> implements Generator<T>{
private T key;
private E value;
public Pair(T key, E value) {
this.key = key;
this.value = value;
}
@Override
public T getKey() {
return key;
}
public E getValue() {
return value;
}
}
测试:
package com.msj.demo02;
public class Test01 {
public static void main(String[] args) {
Apple apple = new Apple();
String key = apple.getKey();
System.out.println(key);
System.out.println("----------------------");
Pair<String, Integer> pair = new Pair<>("梁山好汉", 108);
String key1 = pair.getKey();
Integer value = pair.getValue();
System.out.println(key1 + " 的数量是 " + value);
}
}
4.5、泛型方法
泛型方法
- 泛型类,是在实例化类的时候指明泛型的具体类型
- 泛型方法,是在调用方法的时候指明泛型的具体类型
语法:
修饰符<T, E, ...> 返回值类型 方法名(形参列表) {
方法体......
}
- public与返回值中间非常重要,可以理解为声明此方法为泛型方法。
- 只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
- 表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
- 与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、K、V等形式的参数常用于表示泛型。
包:com.msj.demo03
package com.msj.demo03;
import java.util.ArrayList;
import java.util.Random;
public class ProductGetter<T> {
private T product;
ArrayList<T> list = new ArrayList<>();
Random random = new Random();
public void addProduct(T t) {
list.add(t);
}
public T getProduct() {
product = list.get(random.nextInt(list.size()));
return product;
}
public <E> E getProduct(ArrayList<E> list) {
return list.get(random.nextInt(list.size()));
}
}
测试
package com.msj.demo03;
import java.util.ArrayList;
public class Test01 {
public static void main(String[] args) {
ProductGetter<Integer> productGetter = new ProductGetter<>();
ArrayList<String> list = new ArrayList<>();
list.add("笔记本电脑");
list.add("台式电脑");
String product = productGetter.getProduct(list);
System.out.println(product + "\t" + product.getClass().getSimpleName());
}
}
我们可以发现,我们实例化类对象的时候使用的泛型类型是Integer,而调用方法使用的String,所以泛型方法的使用是独立的(即泛型方法与泛型类互补干扰)
我们定义泛型方法的使用,使用的泛型标识可以和泛型类的一样,因为他们互不干扰。
如下:
package com.msj.demo03;
import java.util.ArrayList;
import java.util.Random;
public class ProductGetter<T> {
private T product;
ArrayList<T> list = new ArrayList<>();
Random random = new Random();
public void addProduct(T t) {
list.add(t);
}
public T getProduct() {
product = list.get(random.nextInt(list.size()));
return product;
}
public <T> T getProduct(ArrayList<T> list) {
return list.get(random.nextInt(list.size()));
}
}
代码不变,结果相同
使用不同的数据类型调用泛型方法:
package com.msj.demo03;
import java.util.ArrayList;
public class Test01 {
public static void main(String[] args) {
ProductGetter<Integer> productGetter = new ProductGetter<>();
ArrayList<String> list = new ArrayList<>();
list.add("笔记本电脑");
list.add("台式电脑");
String product = productGetter.getProduct(list);
System.out.println(product + "\t" + product.getClass().getSimpleName());
System.out.println("----------------------------");
ArrayList<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(2);
list1.add(4);
list1.add(150);
list1.add(6025);
list1.add(456);
Integer product1 = productGetter.getProduct(list1);
System.out.println(product1 + "\t" + product1.getClass().getSimpleName());
}
}
结果:
注意:之前我们使用的成员方法它的返回值类型遵循类的泛型约束,在定义的使用修饰符后不需要写标识,并且成员方法不能使用static修饰,而泛型方法就可以使用static修饰,即使用类泛型约束的成员方法不能是静态的。
静态的泛型方法:
package com.msj.demo03;
import java.util.ArrayList;
import java.util.Random;
public class ProductGetter<T> {
private T product;
ArrayList<T> list = new ArrayList<>();
Random random = new Random();
public void addProduct(T t) {
list.add(t);
}
public T getProduct() {
product = list.get(random.nextInt(list.size()));
return product;
}
public <T> T getProduct(ArrayList<T> list) {
return list.get(random.nextInt(list.size()));
}
public static <T, E, K> void printType(T t, E e, K k) {
System.out.println(t + "\t" + t.getClass().getSimpleName());
System.out.println(e + "\t" + e.getClass().getSimpleName());
System.out.println(k + "\t" + k.getClass().getSimpleName());
}
}
测试:
package com.msj.demo03;
import java.util.ArrayList;
public class Test01 {
public static void main(String[] args) {
ProductGetter<Integer> productGetter = new ProductGetter<>();
ArrayList<String> list = new ArrayList<>();
list.add("笔记本电脑");
list.add("台式电脑");
String product = productGetter.getProduct(list);
System.out.println(product + "\t" + product.getClass().getSimpleName());
System.out.println("----------------------------");
ArrayList<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(2);
list1.add(4);
list1.add(150);
list1.add(6025);
list1.add(456);
Integer product1 = productGetter.getProduct(list1);
System.out.println(product1 + "\t" + product1.getClass().getSimpleName());
System.out.println("-------------------------------");
ProductGetter.printType(100, "java", true);
}
}
从这里就可以看出,泛型方法是我们在使用的时候在确定它的类型,即我们传入什么,他就是什么类型,非常的方便。
泛型方法与可变参数:
public <E> void print(E...e) {
for(E el : e) {
System.out.println(el);
}
}
案例:
package com.msj.demo03;
import java.util.ArrayList;
import java.util.Random;
public class ProductGetter<T> {
private T product;
ArrayList<T> list = new ArrayList<>();
Random random = new Random();
public void addProduct(T t) {
list.add(t);
}
public T getProduct() {
product = list.get(random.nextInt(list.size()));
return product;
}
public <T> T getProduct(ArrayList<T> list) {
return list.get(random.nextInt(list.size()));
}
public static <T, E, K> void printType(T t, E e, K k) {
System.out.println(t + "\t" + t.getClass().getSimpleName());
System.out.println(e + "\t" + e.getClass().getSimpleName());
System.out.println(k + "\t" + k.getClass().getSimpleName());
}
public static <E> void print(E...e) {
for (E e1 : e) {
System.out.println(e1);
}
}
}
测试:可变参数泛型方法的定义
package com.msj.demo03;
import java.util.ArrayList;
public class Test01 {
public static void main(String[] args) {
ProductGetter<Integer> productGetter = new ProductGetter<>();
ArrayList<String> list = new ArrayList<>();
list.add("笔记本电脑");
list.add("台式电脑");
String product = productGetter.getProduct(list);
System.out.println(product + "\t" + product.getClass().getSimpleName());
System.out.println("----------------------------");
ArrayList<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(2);
list1.add(4);
list1.add(150);
list1.add(6025);
list1.add(456);
Integer product1 = productGetter.getProduct(list1);
System.out.println(product1 + "\t" + product1.getClass().getSimpleName());
System.out.println("-------------------------------");
ProductGetter.printType(100, "java", true);
System.out.println("------------------------------");
ProductGetter.print(1, 2, 3, 45, 6);
}
}
泛型方法总结:
- 泛型方法能使方法独立于类而产生变化
- 如果static方法要使用泛型能力,就必须使其成为泛型方法。
4.6、类型通配符
类型通配符:
- 类型通配符一般使用"?"代替具体类型实参
- 所以,类型通配符是类型实参,而不是类型形参
包结构:com.msj.demo04
package com.msj.demo04;
public class Box<E> {
private E first;
public E getFirst() {
return first;
}
public void setFirst(E first) {
this.first = first;
}
}
Test.java
package com.msj.demo04;
public class Test {
public static void main(String[] args) {
Box<Number> box1 = new Box<>();
box1.setFirst(150);
showBox(box1);
}
public static void showBox(Box<Number> box) {
Number first = box.getFirst();
System.out.println(first);
}
}
一个方法的时候没有问题,但是我们使用Box的时候想给showBox传入一个Integer的类,就会发现报错:
package com.msj.demo04;
public class Test {
public static void main(String[] args) {
Box<Number> box1 = new Box<>();
box1.setFirst(150);
showBox(box1);
System.out.println("--------------");
Box<Integer> box2 = new Box<>();
box2.setFirst(250);
showBox(box2);
}
public static void showBox(Box<Number> box) {
Number first = box.getFirst();
System.out.println(first);
}
}
于是我们想到使用重载:
package com.msj.demo04;
public class Test {
public static void main(String[] args) {
Box<Number> box1 = new Box<>();
box1.setFirst(150);
showBox(box1);
System.out.println("--------------");
Box<Integer> box2 = new Box<>();
box2.setFirst(250);
showBox(box2);
}
public static void showBox(Box<Number> box) {
Number first = box.getFirst();
System.out.println(first);
}
public static void showBox(Box<Integer> box) {
Number first = box.getFirst();
System.out.println(first);
}
}
还是报错,因为我们刚才说过
Box<Integer>,Box<Number>本质都是Box类,他们属于同一类型,不能重载。
于是有的人想到把showBox()传入的参数类型改为Object
package com.msj.demo04;
public class Test {
public static void main(String[] args) {
Box<Number> box1 = new Box<>();
box1.setFirst(150);
showBox(box1);
System.out.println("--------------");
Box<Integer> box2 = new Box<>();
box2.setFirst(250);
showBox(box2);
}
public static void showBox(Box<Object> box) {
Number first = box.getFirst();
System.out.println(first);
}
}
还是报错,因为泛型不能使用继承的思路解决。
解决办法,可以使用类型通配符:?
?:代表具体的数据类型,可以代表任意的(如果我们使用Object接收的话)
package com.msj.demo04;
public class Test {
public static void main(String[] args) {
Box<Number> box1 = new Box<>();
box1.setFirst(150);
showBox(box1);
System.out.println("--------------");
Box<Integer> box2 = new Box<>();
box2.setFirst(250);
showBox(box2);
}
public static void showBox(Box<?> box) {
Object first = box.getFirst();
System.out.println(first);
}
}
成功运行:
一、类型通配符的上限
语法:
类/接口<? extends 实参类型>
要求泛型的类型,只能是实参类型,或实参类型的子类类型。
package com.msj.demo04;
public class Test {
public static void main(String[] args) {
Box<Number> box1 = new Box<>();
box1.setFirst(150);
showBox(box1);
System.out.println("--------------");
Box<Integer> box2 = new Box<>();
box2.setFirst(250);
showBox(box2);
}
public static void showBox(Box<? extends Number> box) {
Number first = box.getFirst();
System.out.println(first);
}
}
注意:Integer的父类是Number,因为Integer继承了Number
案例:动物类
package com.msj.demo04;
public class Animal {
}
Cat类:
package com.msj.demo04;
public class Cat extends Animal {
}
MiniCat类:
package com.msj.demo04;
public class MiniCat extends Cat{
}
Test类
package com.msj.demo04;
import java.util.ArrayList;
public class Test02 {
public static void main(String[] args) {
ArrayList<Animal> animals = new ArrayList<>();
ArrayList<Cat> cats = new ArrayList<>();
ArrayList<MiniCat> miniCats = new ArrayList<>();
showAnimal(animals);
showAnimal(cats);
showAnimal(miniCats);
}
public static void showAnimal(ArrayList<? extends Cat> list) {
for (int i = 0; i < list.size(); i++) {
Cat cat = list.get(i);
System.out.println(cat);
}
}
}
发现只有传入animals是报错了,因为showAnimal限定的类型上限是Cat,所以只有Cat及Cat的子类(MiniCat)没有报错。
注意:使用上限通配符的时候,我们不能在list中add数据,因为这里使用的上限通配符,不能确定传入的是Cat还是Cat的子类,从而不能add值;
package com.msj.demo04;
import java.util.ArrayList;
public class Test02 {
public static void main(String[] args) {
ArrayList<Animal> animals = new ArrayList<>();
ArrayList<Cat> cats = new ArrayList<>();
ArrayList<MiniCat> miniCats = new ArrayList<>();
showAnimal(animals);
showAnimal(cats);
showAnimal(miniCats);
}
public static void showAnimal(ArrayList<? extends Cat> list) {
list.add(new Animal());
list.add(new Cat());
list.add(new MiniCat());
for (int i = 0; i < list.size(); i++) {
Cat cat = list.get(i);
System.out.println(cat);
}
}
}
在源码中:addAll()也是使用类类型通配符的上限,要求传入的是集合类型,其上限就是我们的类型,所以下面的代码:
cats.addAll(animals);
cats.addAll(cats);
cats.addAll(miniCats);
cats.addAll()表示addAll()中的类型上限就是cats的泛型Cat,所以调用addAll()的时候Cat或Cat的子类就可以,addAll(animals)就不可以。
package com.msj.demo04;
import java.util.ArrayList;
public class Test02 {
public static void main(String[] args) {
ArrayList<Animal> animals = new ArrayList<>();
ArrayList<Cat> cats = new ArrayList<>();
ArrayList<MiniCat> miniCats = new ArrayList<>();
cats.addAll(animals);
cats.addAll(cats);
cats.addAll(miniCats);
}
public static void showAnimal(ArrayList<? extends Cat> list) {
for (int i = 0; i < list.size(); i++) {
Cat cat = list.get(i);
System.out.println(cat);
}
}
}
二、类型通配符的下限
语法
类/接口<? supper 实参类型>
要求该泛型的类型,只能是实参类型,或实参类型的父类型。如下的代码(所以
package com.msj.demo04;
import java.util.ArrayList;
import java.util.List;
public class TestDown {
public static void main(String[] args) {
ArrayList<Animal> animals = new ArrayList<>();
ArrayList<Cat> cats = new ArrayList<>();
ArrayList<MiniCat> miniCats = new ArrayList<>();
showAnimal(animals);
showAnimal(cats);
showAnimal(miniCats);
}
public static void showAnimal(List<? super Cat> list) {
for (Object o : list) {
System.out.println(o);
}
}
}
注意:类型通配符的下限可以填充元素,但是不能保证填充元素的类型约束要求。
TreeSet有两个重载方法:一个是传入比较器(使用类型通配符的上限),一个传入集合(使用类型通配符的下限)
使用TreeSet的小案例:
package com.msj.demo04;
public class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
@Override
public String toString() {
return "Animal{" +
"name='" + name + '\'' +
'}';
}
}
Cat
package com.msj.demo04;
public class Cat extends Animal {
public int age;
public Cat(String name, int age) {
super(name);
this.age = age;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
MiniCat
package com.msj.demo04;
public class MiniCat extends Cat{
public int level;
public MiniCat(String name, int age, int level) {
super(name, age);
this.level = level;
}
@Override
public String toString() {
return "MiniCat{" +
"name='" + name + '\'' +
", age=" + age +
", level=" + level +
'}';
}
}
Test:
package com.msj.demo04;
import java.util.Comparator;
import java.util.TreeSet;
public class Test08 {
public static void main(String[] args) {
TreeSet<Cat> treeSet = new TreeSet<>(new Comparator2());
treeSet.add(new Cat("jerry", 20));
treeSet.add(new Cat("jim", 28));
treeSet.add(new Cat("frank", 18));
treeSet.add(new Cat("amy", 15));
for (Cat cat : treeSet) {
System.out.println(cat);
}
}
}
class Comparator1 implements Comparator<Animal> {
@Override
public int compare(Animal o1, Animal o2) {
return o1.name.compareTo(o2.name);
}
}
class Comparator2 implements Comparator<Cat> {
@Override
public int compare(Cat o1, Cat o2) {
return o1.age - o2.age;
}
}
class Comparator3 implements java.util.Comparator<MiniCat> {
@Override
public int compare(MiniCat o1, MiniCat o2) {
return o1.level - o2.level;
}
}
可以发现按照年龄排序:
我们发现传入Cat的父类Animal的比较器也是可以的,因为比较器使用的类型的上限通配符。
package com.msj.demo04;
import java.util.Comparator;
import java.util.TreeSet;
public class Test08 {
public static void main(String[] args) {
TreeSet<Cat> treeSet = new TreeSet<>(new Comparator1());
treeSet.add(new Cat("henry", 30));
treeSet.add(new Cat("jim", 28));
treeSet.add(new Cat("frank", 18));
treeSet.add(new Cat("amy", 15));
for (Cat cat : treeSet) {
System.out.println(cat);
}
}
}
class Comparator1 implements Comparator<Animal> {
@Override
public int compare(Animal o1, Animal o2) {
return o1.name.compareTo(o2.name);
}
}
class Comparator2 implements Comparator<Cat> {
@Override
public int compare(Cat o1, Cat o2) {
return o1.age - o2.age;
}
}
class Comparator3 implements java.util.Comparator<MiniCat> {
@Override
public int compare(MiniCat o1, MiniCat o2) {
return o1.level - o2.level;
}
}
可以看到是按name排序的
如果我们传入比较器3则报错。因为父类指定的上限通配符,由于父类的创建先于子类,所以我们可以使用父类的属性进行比较。如果传入比较器3的话,Cat类创建了,其子类MiniCat还没由创建,所以不能使用子类的属性进行比较,因而报错。
总结:
- 通配符的上限:实参类型和实参类型的子类
- 通配符的下限:实参类型和实参类型的父类
4.7、类型擦除
概念:泛型是Java 1.5版本才引入的概念,在这次之前是没有泛型的,但是,泛型代码能够很好地和之前的版本的代码兼容,那是因为,泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,我们称之为–类型擦除。
package com.msj.demo05;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList<Integer> intList = new ArrayList<>();
ArrayList<String> strings = new ArrayList<>();
System.out.println(intList.getClass().getSimpleName());
System.out.println(strings.getClass().getSimpleName());
}
}
可以看到,当文件变为字节码的时候,不管是使用Integer还是使用String约束的ArrayList,最终都是ArrayList类型。即他们的约束被擦除了。
类型擦除:
1、无限制类型擦除
package com.msj.demo05;
public class Erasure<T> {
private T key;
public T getKey() {
return key;
}
public void setKey() {
this.key = key;
}
}
我们通过反射看看类型是不是被擦除了(如果被擦除,最终的泛型类型T都将变为Obje。
package com.msj.demo05;
import java.lang.reflect.Field;
public class Test02 {
public static void main(String[] args) {
Erasure<Integer> erasure = new Erasure<>();
Class<? extends Erasure> aClass = erasure.getClass();
Field[] fields = aClass.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName() + "\t" + field.getType().getSimpleName());
}
}
}
确实类型擦除了:
2、有限制类型擦除
package com.msj.demo05;
public class Erasure<T extends Number> {
private T key;
public T getKey() {
return key;
}
public void setKey() {
this.key = key;
}
}
现在类型被擦除为类型通配符的上限Number了:
package com.msj.demo05;
import java.lang.reflect.Field;
public class Test02 {
public static void main(String[] args) {
Erasure<Integer> erasure = new Erasure<>();
Class<? extends Erasure> aClass = erasure.getClass();
Field[] fields = aClass.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName() + "\t" + field.getType().getSimpleName());
}
}
}
3、擦除方法中类型定义的参数
package com.msj.demo05;
import java.util.List;
public class Erasure<T extends Number> {
private T key;
public T getKey() {
return key;
}
public void setKey() {
this.key = key;
}
public <T extends List> T show(T t) {
return t;
}
}
测试:
package com.msj.demo05;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test02 {
public static void main(String[] args) {
Erasure<Integer> erasure = new Erasure<>();
Class<? extends Erasure> aClass = erasure.getClass();
Field[] fields = aClass.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName() + "\t" + field.getType().getSimpleName());
}
System.out.println("---------------------------");
Method[] methods = aClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName() + "\t" + method.getReturnType().getSimpleName());
}
}
}
4、类型通配符的下限跟无限制擦除一样,擦除为Object。
5、桥接方法
桥接方法是保证实现类型擦除后的接口中的方法,即保持接口和类的实现关系。
package com.msj.demo05;
public interface Info<T> {
T info(T t);
}
实现类:
package com.msj.demo05;
public class InfoImpl implements Info<Integer> {
@Override
public Integer info(Integer value) {
return value;
}
}
通过反射看看实现类中到底有几个方法,看看是不是有一个桥接方法:
package com.msj.demo05;
import java.lang.reflect.Method;
public class Test03 {
public static void main(String[] args) {
Class<InfoImpl> infoClass = InfoImpl.class;
Method[] declaredMethods = infoClass.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println(method.getName() + "\t" + method.getReturnType().getSimpleName());
}
}
}
确实多了一个Object的桥接方法
4.8、泛型与数组
泛型数组的创建
- 可以声明带泛型的数组引用,但是不能直接创建带泛型的数组对象。
- 可以通过java.reflect.Array的newInstance(Class,int)创建T[]数组
包结构:com.msj.demo06
package com.msj.demo06;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList<String>[] lists;
ArrayList<String>[] lists = new ArrayList()[5];
}
}
解决办法一:
我们创建一个原生ArrayList数组对象,然后把这个数值对象赋值给带泛型数组的引用。
package com.msj.demo06;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList[] list = new ArrayList[5];
ArrayList<String>[] listArr = list;
ArrayList<Integer> intList = new ArrayList<>();
intList.add(100);
list[0] = intList;
String s = listArr[0].get(0);
}
}
发生ClassCastException异常(我们使用泛型的目的就是防止ClassCastException,而这里恰恰发生了),所以这种方式使用泛型数组是错误的。
如果非要用上面的写法,我们可以修改如下:不要把原生数组对象的数据类型给暴露出来
package com.msj.demo06;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList<String>[] listArr = new ArrayList[5];
ArrayList<Integer> intList = new ArrayList<>();
listArr[0] = intList;
String s = listArr[0].get(0);
}
}
这时我们的泛型约束要正确才可以:
package com.msj.demo06;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList<String>[] listArr = new ArrayList[5];
ArrayList<String> strList = new ArrayList<>();
strList.add("abc");
listArr[0] = strList;
String s = listArr[0].get(0);
System.out.println(s);
}
}
可以正常打印:
不能创建带泛型的数组对象的原因:Java在编译时会做类型擦除,而数组从创建将一直保存原有的数据类型,不能被擦除,两者的设计原理不同,所以不能直接创建。
2、使用反射类中的newInstance创建泛型数组。
package com.msj.demo06;
import java.lang.reflect.Array;
public class Fruit<T> {
private T[] array;
public Fruit(Class<T> cls, int len) {
array = (T[])Array.newInstance(cls, len);
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) {
return array[index];
}
public T[] getArray() {
return array;
}
}
测试:
package com.msj.demo06;
import java.util.ArrayList;
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
ArrayList<String>[] listArr = new ArrayList[5];
ArrayList<String> strList = new ArrayList<>();
strList.add("abc");
listArr[0] = strList;
String s = listArr[0].get(0);
System.out.println(s);
System.out.println("--------------------------------");
Fruit<String> fruit = new Fruit<>(String.class, 3);
fruit.put(0, "苹果");
fruit.put(1, "香蕉");
fruit.put(2, "梨");
System.out.println(Arrays.toString(fruit.getArray()));
String s1 = fruit.get(2);
System.out.println(s1);
}
}
4.9、泛型和反射
反射常用的泛型类
Class<T>
Constructor<T>
包结构:
com.msj.demo07
package com.msj.demo07;
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试
package com.msj.demo07;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Test {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<Person> personClass = Person.class;
Constructor<Person> constructor = personClass.getConstructor();
Person person = constructor.newInstance();
Class personClass1 = Person.class;
Constructor constructor1 = personClass1.getConstructor();
Object o = constructor1.newInstance();
}
}
];
ArrayList<String>[] listArr = list;
ArrayList<Integer> intList = new ArrayList<>();
intList.add(100);
list[0] = intList;
String s = listArr[0].get(0);
}
}
发生ClassCastException异常(我们使用泛型的目的就是防止ClassCastException,而这里恰恰发生了),所以这种方式使用泛型数组是错误的。
[外链图片转存中…(img-0OK8McDt-1628867401031)]
如果非要用上面的写法,我们可以修改如下:不要把原生数组对象的数据类型给暴露出来
package com.msj.demo06;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList<String>[] listArr = new ArrayList[5];
ArrayList<Integer> intList = new ArrayList<>();
listArr[0] = intList;
String s = listArr[0].get(0);
}
}
[外链图片转存中…(img-bxagBJhX-1628867401031)]
这时我们的泛型约束要正确才可以:
package com.msj.demo06;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList<String>[] listArr = new ArrayList[5];
ArrayList<String> strList = new ArrayList<>();
strList.add("abc");
listArr[0] = strList;
String s = listArr[0].get(0);
System.out.println(s);
}
}
可以正常打印:
[外链图片转存中…(img-YGCpV0fX-1628867401032)]
不能创建带泛型的数组对象的原因:Java在编译时会做类型擦除,而数组从创建将一直保存原有的数据类型,不能被擦除,两者的设计原理不同,所以不能直接创建。
2、使用反射类中的newInstance创建泛型数组。
package com.msj.demo06;
import java.lang.reflect.Array;
public class Fruit<T> {
private T[] array;
public Fruit(Class<T> cls, int len) {
array = (T[])Array.newInstance(cls, len);
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) {
return array[index];
}
public T[] getArray() {
return array;
}
}
测试:
package com.msj.demo06;
import java.util.ArrayList;
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
ArrayList<String>[] listArr = new ArrayList[5];
ArrayList<String> strList = new ArrayList<>();
strList.add("abc");
listArr[0] = strList;
String s = listArr[0].get(0);
System.out.println(s);
System.out.println("--------------------------------");
Fruit<String> fruit = new Fruit<>(String.class, 3);
fruit.put(0, "苹果");
fruit.put(1, "香蕉");
fruit.put(2, "梨");
System.out.println(Arrays.toString(fruit.getArray()));
String s1 = fruit.get(2);
System.out.println(s1);
}
}
4.9、泛型和反射
反射常用的泛型类
Class<T>
Constructor<T>
包结构:
com.msj.demo07
package com.msj.demo07;
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试
package com.msj.demo07;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Test {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<Person> personClass = Person.class;
Constructor<Person> constructor = personClass.getConstructor();
Person person = constructor.newInstance();
Class personClass1 = Person.class;
Constructor constructor1 = personClass1.getConstructor();
Object o = constructor1.newInstance();
}
}
|