Java基础知识:泛型程序设计

  • 类型参数的好处:
    • 在Java增加泛型类之前,泛型程序设计是用继承实现的。
    • 对Object类的强制类型转换,没有错误检查。指定了类型参数就可以让编译器进行类型检查。
    • 可读性更好,类型参数可以指定元素的类型。
  • 使用泛型机制编写的程序代码要比那些杂乱地使用 Object变量,然后再进行强制类型转换的代码具有更好的安全性可读性

 

  • 定义简单泛型类:
  • 泛型类可以有多个类型变量。
  • 类定义中的类型变量指定方法的返回类以及域和局部变量的类型。
  • 在Java库中,使用变量 E 表示 集合 的元素类型,KV 分别表示 关键字TUS)表示 “任意类型”。
  • 示例程序:(遍历一遍找出字符串数组字典序的最大值字符串与最小值字符串)
package pair1;

public class PairTest1 {
	public static void main( String[] args ) {
		String[] arr = { "Harden", "James", "Bryant", "Curry" };
		Pair<String> res = ArrayAlg.minmax(arr);
		System.out.println( "min string: " + res.getFirst() );
		System.out.println( "max string: " + res.getSecond() );
	}
}

class ArrayAlg{
	public static Pair<String> minmax( String[] a ){
		if( a == null || a.length == 0 ) return null;
		
		String min = a[0];
		String max = a[0];
		
		for( int i = 1; i < a.length; i++ ) {
			if( min.compareTo(a[i]) > 0 ) min = a[i];
			if( max.compareTo(a[i]) < 0 ) max = a[i];
		}
		
		return new Pair<String>(min,max);
	}
	
}

 

  • 泛型方法:
  • 还可以定义一个带有类型参数的方法。可以定义在普通类中,也可以定义在泛型类中。
  • 类型变量放在修饰符的后面返回类型的前面
  • 调用一个泛型方法时,在方法名前尖括号中放入具体的类型。若编译器可推导出类型,可省略尖括号内容。

 

  • 类型变量的限定
  • public static <T> T min(T[] a)
  • public static<T extends Comparable> T min(T[] a)
  • T extends Comparable & Serializable
  • <T extends BoundingType> 表示T应该是绑定类型的子类型,T 和 绑定类型 可以是,也可以是 接口。限定中至多有一个类,如果用类作为限定,它必须是限定列表的第一个
  • 示例程序:(泛型方法+类型变量限定的测试)
package pair2;

import pair1.Pair;
import java.time.*;
public class PairTest2 {
	public static void main( String[] args ) {
		LocalDate[] birthdays = 
			{
				LocalDate.of(1906, 12, 9),
				LocalDate.of(1815, 12, 10),
				LocalDate.of(1903, 12, 3),
				LocalDate.of(1910, 6, 22)
			};
		Pair<LocalDate> res = ArrayAlg.minmax(birthdays);
		System.out.println("min birthday: " + res.getFirst() );
		System.out.println("max birthday: " + res.getSecond() );
	}
}

class ArrayAlg{
	public static <T extends Comparable> Pair<T> minmax( T[] a ){
		if( a == null || a.length == 0 ) return null;
		
		T min = a[0];
		T max = a[0];
		
		for( int i = 1; i < a.length; i++ ) {
			if( min.compareTo(a[i]) > 0 ) min = a[i];
			if( max.compareTo(a[i]) < 0 ) max = a[i];
		}
		
		return new Pair<T>(min,max);
	}
}

 

  • 泛化代码与虚拟机:
  • 虚拟机没有泛型类型对象——所有对象都属于普通类。
  • 类型擦除:
    • 无论何时定义一个泛型类型,都自动提供一个相应的 原始类型raw type)。原始类型的名字就是删去了类型参数后的泛型类型名。
    • 擦除(erase)类型变量,并替换为 限定类型(第一个)(无限定类型则替换为 Object)。
    • 为了提高效率,应该将 标签接口(没有方法的接口) 放在边界列表的末尾。
  • 翻译泛型表达式:
    • 当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换
    • 存取一个泛型域时也要插入强制类型转换。
  • 翻译泛型方法:
    • 类型擦除与多态发生冲突:需要编译器生成 桥方法
    • 桥方法例子: public void setSecond( Object second ) { setSecond( (Date) second ); } 
    • 虚拟机中,用参数类型和返回类型确定一个方法→编译器能产生两个仅返回类型不同的方法字节码
    • 虚拟机中没有泛型,只有普通的类和方法
    • 所有的类型参数都能被其限定类型替换
    • 桥方法被合成保持多态。
    • 为保持类型安全性,必要时插入强制类型转换。
  • 调用遗留代码:设计Java泛型类型时,主要目标是允许泛型代码和遗留代码之间能够互操作。当然,编译器可能会产生一些警告。查看过警告后,如果没有问题,可以利用 注解 使之消失。

 

  • 约束与局限性
  • 泛型需要考虑的限制,大多限制都是类型擦除引起的。
  • 不能使用基本类型实例化类型参数。这样与Java语言基本类型的独立状态一致。
  • 运行时类型查询适用于原始类型:所有的类型查询只产生原始类型:
    • instanceof,试图查询一个对象是否属于某个泛型类时,得到一个编译器错误
    • getClass 总是返回 原始类型
    • 强制类型转换,会得到一个警告
  • 不能创建参数化类型的数组:
    • Pair<String>[] table = new Pair<String>[10];   //Error
    • 只是不允许创建,但可以声明。(不安全)
  • Varargs警告
    • 向参数可变的方法传递一个泛型类型的实例。只会得到一个警告而不是错误。
  • 不能实例化类型参数
    • 形如: new T(...) 、new T[...] 或 T.class 都是非法的。
    •  Class类本身就是泛型,String.class就是Class<String>的(唯一)实例。
//通过构造器引用调用 Pair<String> p = Pair.makePair( String::new );
public static <T> Pair<T> makePair( Supplier<T> constr )
{
    return new Pair<>( constr.get(), constr.get() );
}

//通过 Pair<String> p = Pair.makePair( String.class ) 调用。
public static <T> Pair<T> makePair( Class<T> cl )
{
    try{ return new Pair<>( cl.newInstance(), cl.newInstance() ); }
    catch( Exception ex ) { return null; }
}
  • 不能构造泛型数组:
    • 就像不能实例化一个泛型实例一样,也不能实例化数组。
    • public static <T extends Comparable> T[] minmax( T[] a ) { T[] mm = new T[2]; } // Error
    • 数组类型不能自由进行强制类型转换:ClassCastException。
    • ArrayList的toArray方法,需要生成一个T[]的数组。解决办法:
      • Object[] toArray()
      • T[] toArray(T[] result)
    • 泛型类的静态上下文中类型变量无效:禁止使用带有类型变量的静态域和方法。(由于类型擦除)
    • 不能抛出也不能捕获泛型类对象。
    • 可以消除对受查异常的检查:
      • Java异常处理的一个基本原则:必须为所有受查异常提供一个处理器。
      • 可以利用泛型消除这个限制
//以下程序的意义:
//run方法是声明为不抛出任何受查异常的,必须捕获run方法中的所有受查异常
//于是乎可以将它们“包装”成非受查异常,“瞒过”编译器
public abstract class Block
{
    public abstract void body() throws Exception;

    public Thread toThread()
    {
        return new Thread()
            {
                public void run()
                {
                    try
                    {
                        body();
                    }
                    catch( Throwable t )
                    {
                        Block.<RuntimeException>throwAs(t);
                    }
                }
            };
    }
    
    @SuppressWarnings("unchecked")
    public static <T extends Throwable> void ThrowAs( Throwable e ) throws T
    {
        throw (T) e;
    }
}

 

  • 注意擦除后的冲突:
    • 泛型规范的另一原则:要想支持擦除的转换,就需要强行限制一个类型变量不能同时成为两个接口类型的子类这两个接口是同一接口的不同参数化
  • 泛型类型的继承规则:
    • 无论 S 和 T 有何种关系,通常,Pair<S> 和 Pair<T> 都没有什么联系
    • 永远可以将参数化类型转换成一个原始类型。如:Pair<Employee> 类型 是 Pair 类型的一个子类型。与遗留代码衔接时,这个转换十分必要。
    • 泛型类可以扩展或实现其他的泛型类。

 

 

  • 通配符类型
    • 通配符概念:
      • Pair<? extends Employee> 表示任何泛型Pair类型,它的类型参数是Employee的子类
      • 类型 Pair<Manager> 是 Pair<? extends Employee> 的子类型。
    • 通配符的超类型限定:
      • 通配符限定与类型变量限定十分类似,但是还有一个附加的能力,即可以限定一个 超类型限定。如: ? super Manager 。
      • 可以为方法提供参数,但不能使用返回值。
      • 直观地讲:带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取
    • 无限定通配符:
      • Pair<?> 与 Pair 本质的不同在于:可以用任意Object对象调用原始Pair类的setObject方法。无限定通配符有时对于许多简单的操作非常有用(适用于不需要实际的类型的通用程序)。
    • 通配符捕获:
      •  通配符捕获只有在许多限制的情况下才是合法的。编译器必须能够确信通配符表达的是单个确定的类型。
    • 示例程序:(测试子类型限定通配符、超类限定通配符、无限定通配符以及通配符捕获)
package pair3;
import pair1.Pair;

public class PairTest3 {
	public static void main( String[] args ) {
		Manager ceo = new Manager("Gus Greedy", 800000, 2003, 12, 15 );
		Manager cfo = new Manager("Sid Sneaky", 600000, 2003, 12, 15 );
		
		Pair<Manager> buddies = new Pair<>( ceo, cfo );
		printBuddies(buddies);
		
		ceo.setBonus(1000000);
		cfo.setBonus(500000);
		
		Manager[] managers = { ceo, cfo };
		
		Pair<Employee> result = new Pair<>();
		minmaxBonus(managers, result);
		System.out.println("first: " + result.getFirst().getName() 
				+ ", second: " + result.getSecond().getName() );
		
		maxminBonus( managers, result );
		System.out.println("first: " + result.getFirst().getName() 
				+ ", second: " + result.getSecond().getName() );		
	}
	
	
	public static void printBuddies( Pair<? extends Employee> p ) {
		Employee first = p.getFirst();
		Employee second = p.getSecond();
		System.out.println( first.getName() + " and " + second.getName() + " are buddies." );
	}
	
	public static void minmaxBonus( Manager[] a, Pair<? super Manager> result ) {
		if( a.length == 0 ) return;
		Manager max = a[0];
		Manager min = a[0];
		
		for( int i = 1; i < a.length; i++ ) {
			if( max.getBonus() < a[i].getBonus() ) max = a[i];
			if( min.getBonus() > a[i].getBonus() ) min = a[i];
		}
		
		result.setFirst(min);
		result.setSecond(max);
	}
	
	public static void maxminBonus( Manager[] a, Pair<? super Manager> result ) {
		minmaxBonus(a, result);
		PairAlg.swapHelper(result);
	}
	
	
}

class PairAlg{
	public static boolean hasNulls( Pair<?> p ) {
		return p.getFirst() == null || p.getSecond() == null;
	}
	
	public static void swap( Pair<?> p ) {
		swapHelper(p);
	}
	
	public static <T> void swapHelper( Pair<T> p ) {
		T tmp = p.getFirst();
		p.setFirst(p.getSecond());
		p.setSecond(tmp);
	}
}
package pair1;

public class Pair<T> {
	private T first;
	private T second;
	
	public Pair() { first = null; second = null; }
	public Pair( T first, T second ) {
		this.first = first;
		this.second = second;
	}
	
	public T getFirst() {
		return first;
	}
	
	public T getSecond() {
		return second;
	}
	
	public void setFirst( T first ) { this.first = first; }
	public void setSecond( T second ) { this.second = second; }
	
}
package pair3;

import java.time.*;
public class Employee {
	private String name;
	private double salary;
	private LocalDate hireDay;
	
	public Employee() {
		name = "";
		salary = 0;
		hireDay = LocalDate.of(0, 0, 0);
	}
	
	public Employee( String name, double salary, int year, int month, int day ) {
		this.name = name;
		this.salary = salary;
		this.hireDay = LocalDate.of(year, month, day);
	}
	
	public String getName() { return name; }
	public double getSalary() { return salary; }
	public LocalDate getHireDay() { return hireDay; }
	
	public void raiseSalary( double byPercent ) {
		double raise = salary * byPercent / 100;
		salary += raise;
	}
	
}
package pair3;

public class Manager extends Employee {
	private double bonus;
	
	public Manager() {
		super();
		bonus = 0;
	}
	
	public Manager( String name, double salary, int year, int month, int day ) {
		super(name,salary,year,month,day);
		bonus = 0;
	}
	
	public void setBonus( double bonus ) {
		this.bonus = bonus;
	}
	
	public double getBonus() { return bonus; }
	
	public double getSalary() {
		double base = super.getSalary();
		return base + bonus;
	}
}

 

 

  • 反射与泛型:
    • java.lang.Class<T>:
      • T newInstance()
      • T cast( Object obj )
      • T[] getEnumConstants()
      • Class<? super T> getSuperclass()
      • Constructor<T> getConstructor( Class... parameterTypes )
      • Constructor<T> getDeclaredConstructor( Class... parameterTypes )
      • TypeVariable[] getTypeParameters() //如果这个类型被声明为泛型类型,则获得泛型类型变量,否则获得一个长度为0的数组。
      • Type getGenericSuperclass() //获得被声明这一类型的超类的泛型类型。若这个类型是Object或者不是一个类类型,则返回null。
      • Type[] getGenericInterfaces() // 获得被声明为这个类型的接口的泛型类型(以声明的次序)
    • java.lang.reflect.Constructor<T>:
      • T newInstance( Object... parameters )
    • java.lang.reflect.TypeVariable:
      • String getName()
      • Type[] getBounds() //获得类型变量的子类限定
    • java.lang.reflect.WildcardType:
      • Type[] getUpperBounds()
      • Type[] getLowerBounds()
    • ......
    • TypeVariable接口描述类型变量。
    • WildcardType接口描述通配符。
    • ParameterizedType接口描述泛型类或接口类型。
    • GenericArrayType接口描述泛型数组。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值