201871010110-李华《面向对象程序设计(java)》第十一周学习总结
阅读原文时间:2023年07月14日阅读:1

项目

内容

这个作业属于哪个课程

https://www.cnblogs.com/nwnu-daizh/

这个作业的要求在哪里

https://www.cnblogs.com/nwnu-daizh/p/11815810.html

作业学习目标

第一部分:总结第八章关于泛型程序设计理论知识(25分)

一.为什么要使用泛型程序设计

1.使用泛型机制编写的程序代码要比那些杂乱的使用object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性
2.泛型程序设计意味着编写的代码可以被很多不同类型的对象所重用。
   在Java中增加泛型类之前,泛型程序设计是用继承实现的。但是有两个问题:当获取一个值时必须进行强制类型转换,,这里没有错误检查,可以向数组列表中添加任何类的对象
3.类型参数的魅力在于:使得程序具有更好的可读性和安全性

二.定义简单泛型类
      。 类型变量使用大写形式,且比较短,这是很常见的。在Java库中,使用变量E来表示集合的元素类型,K和V来表示关键字与值的类型。T表示“任意类型”

。泛型:具有类型参数(类型变量),可以用其他具体的类来替代此参数,从而扩大方法的实用范围以及提高程序的安全性。 
泛型类:具有一个或多个类型变量的类,用具体的类替换类型变量即为实例化泛型类。

public class Pair

{        …               //两个以上类型变量用,隔开

}
三. 泛型方法

泛型方法,可以定义带有类型参数的方法。

这个方法在修饰符static后面和返回类型的前面插入了一个类型变量,代表的意义是调用这个方法时,等于号左边的类型必须要与这个类型变量一致。

。参数或返回类型为泛型变量的方法,可以位于普通类或泛型类之间,< T>位于修饰符之后、返回类型之前。

。类型变量放在修饰符的后面,返回类型的前面。当调用一个泛型方法时,在方法名钱的尖括号中放入具体的类型:
       String middle = ArrayAlg.getMiddle("John", "Holk");

四.类型变量的限定

类型变量的限定是为了只有在约束限制内相关联的类型才可以使用这个泛型类。

1.< T extends BoundingType> 
      将泛型限制为实现了指定接口或类的类,限定类型可以是接口,也可以是类。能够限定多个接口,但最多只能限定一个类,且该类必须是限定列表中的第一个。多个限定类型之间用&隔开。

2.有时,类或方法需要对类型变量加以约束

一个类型变量或通配符可以有多个限定,例如:T extends Comparator & Serializable        限定类型用&分割,逗号用来分割类型变量

3.在Java的继承中,可以根据需要拥有多个接口超类型,但限定类只能有一个。如果用一个类做限定,必须把这个类写在限定列表的第一个

五. 泛型代码和虚拟机
  1.类型擦除:虚拟机没有泛型对象,在定义一个泛型类型时,会自动提供其相对应的原始类型,即擦去类型变量,如果是限定的类型变量,则用第一个限定类型的代替,如果是无限定的类型变量,则用Object代替。

原始类型用一个限定类型变量来替换,如果没有限定就替换成Object(类型擦除)
  2.关于Java泛型转换的事实:
             (1)虚拟机中没有泛型,只有普通的类或方法
             (2)所有类型参数都用它们的限定类型替换
             (3)桥方法被合成来保持多态
             (4)为保持类型安全性,必要时加入强制类型转换(在调用泛型方法时,如果擦去了返回类型,则编译器会插入强制类型转换)
六. 约束和局限性

1.不能用基本类型实例化类型参数,即不能有Pair< double>,因为类型擦去后Pair的类型为Object,不能将double赋给它,只能有Pair< Double>

2.对于类型查询(instanceof),只产生原始类型。getClass()也是返回原始类型

3.不能创建参数化类型的数组,即new Pair< String>[10]是不允许的,Pair< String>[] 不能初始化
只能使用ArrayList< Pair< String>>来搜集参数化类型

4.由于不能创建参数化类型的数组,所以在提供一系列参数化类型作为参数的方法中,调用用时会发出警告,可用@SafeVarags来消除创建泛型数组的有关限制

@SafeVarags public static void addAll(collection coll,T…st)//一系列T对象会被写为数组

5.不能实例化类型变量,即new T(..)/new T[…]/T.class等

6.不能构造泛型数组,一般需要强制类型转换,即[T[]] new Object[size];

7.不能在静态域和静态方法中使用类型变量

8.泛型中的类型变量不能是基本类型,因为基本类型与普通类的顶级父类Object没有继承关系。

使用instanceof或者getClass,都会得到擦除类型变量后的原始类型,如Pair,使用这两种方式,得到的原始类型是Pair.class。

。泛型类的约束与局限性(*)。不能用基本类型实例化类型参数。运行时类型查询只适用于原始类型。不能抛出也不能捕获泛型类实例。参数化类型的数组不合法。不能实例化类型变量。泛型类的静态上下文中类型变量无效
。注意擦除后的冲突
七.继承规则

假如Employee是Manager的父类,但是Pair不是Pair的子类,这两个类没有关联关系。这是为了类型安全考虑的。

继承规则

Java中的数组是协变的(covariant)。

例如:Integer扩展了Number,那么在要求Number[]的

地方完全可以传递或者赋予Integer[],Number[]也是

Integer[]的超类型。

Employee是Manager的超类,因此可以将一个

Manager[]数组赋给一个类型为Employee[]的变量:

Manager[] managerBuddies = {ceo, cfo};

Employee[] employeeBuddies = managerBuddies;

八.通配符类型

通配符可以分为子类型限定通配符和超类型限定通配符
子类型限定通配符:  extends Employee 不能向更改器方法传递特定的类型,但是可以让访问器方法返回特定类型
超类型限定通配符: super Manager,不能像向改器传递Manager的超类,访问器方法只能返回Object类

第二部分:实验部分

实验1:测试程序1(6分)

编辑、调试、运行教材311312页代码,结合程序运行结果理解程序;

实验代码:

package pair1;

/**
* @version 1.00 2004-05-10
* @author Cay Horstmann
*/
public class Pair //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 newValue) { first = newValue; }
public void setSecond(T newValue) { second = newValue; }
}

package pair1;

/**
* @version 1.01 2012-01-26
* @author Cay Horstmann
*/
public class PairTest1
{
public static void main(String[] args)
{
String[] words = { "Mary", "had", "a", "little", "lamb" };
Pair mm = ArrayAlg.minmax(words);
System.out.println("min = " + mm.getFirst());
System.out.println("max = " + mm.getSecond());
}
}

class ArrayAlg
{
/**
* Gets the minimum and maximum of an array of strings.
* @param a an array of strings
* @return a pair with the min and max values, or null if a is null or empty
*/
public static Pair minmax(String[] a) //静态minmax方法遍历了数组并同时计算出最小值和最大值
{
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<>(min, max);//用一个pair对象返回两个结果;
}
}

实验结果如下:

泛型的定义和使用:
1) 不仅Java的集合都定义成了泛型,用户自己也可以定义任意泛型的类、接口,只要在定义它们时用<>来指定类型参数即可;
2) 例如:public class Fruit { … },其中指定了该泛型的类型参数,这个T是一个类型参数名,用户可以任意命名(就像方法参数的形参名一样),
只有在定义该泛型的对象时将T替换成指定的具体类型从而产生一个实例化的泛型对象,
例如:Fruit fruit = new Fruit<>(…);
3) 类型形参可以在整个接口、类体内当成普通类型使用,集合所有可使用普通类型的地方都可以使用类型形参
4) 定义泛型构造器:泛型的构造器还是类名本身,不用使用菱形语法

实验1:测试程序2(6分)

编辑、调试运行教材315 PairTest2,结合程序运行结果理解程序;

实验代码:

package pair2;

/**
* @version 1.00 2004-05-10
* @author Cay Horstmann
*/
public class Pair //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 newValue) { first = newValue; }
public void setSecond(T newValue) { second = newValue; }
}

package pair2;

import java.time.*;

/**
* @version 1.02 2015-06-21
* @author Cay Horstmann
*/
public class PairTest2
{
public static void main(String[] args)
{
LocalDate[] birthdays =
{
LocalDate.of(1906, 12, 9), // G. Hopper
LocalDate.of(1815, 12, 10), // A. Lovelace
LocalDate.of(1903, 12, 3), // J. von Neumann
LocalDate.of(1910, 6, 22), // K. Zuse
};
Pair mm = ArrayAlg.minmax(birthdays);
System.out.println("min = " + mm.getFirst());
System.out.println("max = " + mm.getSecond());
}
}

class ArrayAlg
{
/**
Gets the minimum and maximum of an array of objects of type T.
@param a an array of objects of type T
@return a pair with the min and max values, or null if a is null or empty
*/
public static Pair minmax(T[] a) //将T限制为实现了Comparable借接口的类
{ //对类型变量T设置限定
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<>(min, max); // 用Pair返回最大值和最小值
}
}

  实验结果如下:

注意:泛型的类型参数和方法的参数一样,也是一种参数,只不过是一种特殊的参数,用来表示未知的类型罢了;
   因此,泛型也是在定义的时候必须使用形参(虚拟参数,用户自己随意命名),但是在使用泛型的时候(比如定义泛型引用、继承泛型)就必须使用实参,而泛型的实参就是具体的类型
   使用泛型的时候必须传入实参
   在使用泛型的时候可以不使用菱形语法指定实参,直接裸用类型名

实验1:测试程序3(6分)

用调试运行教材335 PairTest3,结合程序运行结果理解程序;

实验代码

package pair3;

/**
* @version 1.01 2012-01-26
* @author Cay Horstmann
*/
public class PairTest3
{
public static void main(String[] args)
{
var ceo = new Manager("Gus Greedy", 800000, 2003, 12, 15);
var cfo = new Manager("Sid Sneaky", 600000, 2003, 12, 15);
var buddies = new Pair(ceo, cfo);
printBuddies(buddies);

  ceo.setBonus(1000000);  
  cfo.setBonus(500000);  
  Manager\[\] managers = { ceo, cfo };

  var result = new Pair<Employee>();  
  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 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 result)
{
if (a.length == 0) return;
Manager min = a[0];
Manager max = a[0];
for (int i = 1; i < a.length; i++) { if (min.getBonus() > a[i].getBonus()) min = a[i];
if (max.getBonus() < a[i].getBonus()) max = a[i];
}
result.setFirst(min);
result.setSecond(max);
}

public static void maxminBonus(Manager[] a, Pair result)
{
minmaxBonus(a, result);
PairAlg.swapHelper(result); // OK--swapHelper captures wildcard type捕获通配符类型
}
// can't write public static . . .无法写入公共静态
}

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 void swapHelper(Pair p)
{
T t = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(t);
}
}

package pair3;

/**
* @version 1.00 2004-05-10
* @author Cay Horstmann
*/
public class Pair //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 newValue) { first = newValue; }
public void setSecond(T newValue) { second = newValue; }
}

package pair3;

public class Manager extends Employee
{
private double bonus;

/**
@param name the employee's name
@param salary the salary
@param year the hire year
@param month the hire month
@param day the hire day
*/
public Manager(String name, double salary, int year, int month, int day)
{
super(name, salary, year, month, day);
bonus = 0;
}

public double getSalary()
{
double baseSalary = super.getSalary();
return baseSalary + bonus;
}

public void setBonus(double b)
{
bonus = b;
}

public double getBonus()
{
return bonus;
}
}

package pair3;

import java.time.*;

public class Employee
{
private String name;
private double salary;
private LocalDate hireDay;

public Employee(String name, double salary, int year, int month, int day)
{
this.name = name;
this.salary = salary;
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;
}
}

实验结果如下:

类型变量(尖括号中的变量)放在修饰符的后面,返回类型的前面。

当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型。
泛型方法并不是在泛型类中定义的,也就是说,泛型方法不一定非得定义在泛型类中

实验2:结对编程练习(32分)

1 编写一个泛型接口GeneralStack,要求类方法对任何引用类型数据都适用。GeneralStack接口中方法如下:

push(item);            //如item为null,则不入栈直接返回null。

pop();                 //出栈,如为栈为空,则返回null。

peek();                //获得栈顶元素,如为空,则返回null.

public boolean empty();//如为空返回true

public int size();     //返回栈中元素数量

2定义GeneralStack的类ArrayListGeneralStack要求:

ü 类内使用ArrayList对象存储堆栈数据,名为list;

ü 方法: public String toString()//代码为return list.toString();

ü 代码中不要出现类型不安全的强制转换。

3定义Car类,类的属性有:

private int id;

private String name;

方法:Eclipse自动生成setter/getter,toString方法。

4main方法要求

ü 输入选项,有quit, Integer, Double, Car 4个选项。如果输入quit,程序直接退出。否则,输入整数m与n。m代表入栈个数,n代表出栈个数。然后声明栈变量stack。

ü 输入Integer,打印Integer Test。建立可以存放Integer类型的ArrayListGeneralStack。入栈m次,出栈n次。打印栈的toString方法。最后将栈中剩余元素出栈并累加输出。

ü 输入Double ,打印Double Test。剩下的与输入Integer一样。

ü 输入Car,打印Car Test。其他操作与Integer、Double基本一样。只不过最后将栈中元素出栈,并将其name依次输出。

特别注意:如果栈为空,继续出栈,返回null

输入样例

Integer

5

2

1 2 3 4 5

Double

5

3

1.1 2.0 4.9 5.7 7.2

Car

3

2

1 Ford

2 Cherry

3 BYD

quit

输出样例

Integer Test

push:1

push:2

push:3

push:4

push:5

pop:5

pop:4

[1, 2, 3]

sum=6

interface GeneralStack

Double Test

push:1.1

push:2.0

push:4.9

push:5.7

push:7.2

pop:7.2

pop:5.7

pop:4.9

[1.1, 2.0]

sum=3.1

interface GeneralStack

Car Test

push:Car [id=1, name=Ford]

push:Car [id=2, name=Cherry]

push:Car [id=3, name=BYD]

pop:Car [id=3, name=BYD]

pop:Car [id=2, name=Cherry]

[Car [id=1, name=Ford]]

Ford

interface GeneralStack

实验代码如下:

package kdj;
import java.util.ArrayList;
import java.util.Scanner;

interface GeneralStack{
public T push(T item);
public T pop();
public T peek();
public boolean empty();
public int size();
}
class ArrayListGeneralStack implements GeneralStack{
ArrayList list=new ArrayList();
@Override
public String toString() {
return list.toString();
}

@Override  
public Object push(Object item) {  
    if (list.add(item)){  
        return item;  
    }else {  
        return false;  
    }  
}

@Override  
public Object pop() {  
    if (list.size()==0){  
        return null;  
    }  
    return list.remove(list.size()-1);  
}

@Override  
public Object peek() {  
    return list.get(list.size()-1);  
}

@Override  
public boolean empty() {  
    if (list.size()==0){  
        return true;  
    }else {  
        return false;  
    }  
}

@Override  
public int size() {  
    return list.size();  
}  

}

package kdj;
public class Car{
private int id;
private String name;

@Override  
public String toString() {  
    return "Car \[" +  
            "id=" + id +  
            ", name=" + name  +  
            '\]';  
}

public int getId() {  
    return id;  
}

public void setId(int id) {  
    this.id = id;  
}

public String getName() {  
    return name;  
}

public void setName(String name) {  
    this.name = name;  
}

public Car(int id, String name) {  
    this.id = id;  
    this.name = name;  
}  

}

package kdj;
import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
while (true){
String s=sc.nextLine();
if (s.equals("Double")){
System.out.println("Double Test");
int count=sc.nextInt();
int pop_time=sc.nextInt();
ArrayListGeneralStack arrayListGeneralStack = new ArrayListGeneralStack();
for (int i=0;i0){
int size=arrayListGeneralStack.size();
for (int i=0;i<size;i++){
Car car=(Car) arrayListGeneralStack.pop();
System.out.println(car.getName());
}
}
System.out.println("interface GeneralStack");
}else if (s.equals("quit")){
break;
}
}

}  

}

  实验结果如下:

     

实验总结:(15分)

这一章的内容比起之前的所有内容知识点化的内容会比较少一点,首先理解了泛型的概念,了解到。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上,因此泛型类型中的静态变量是所有实例共享的。通过实例的编译和运行,掌握了泛型类的定义与使用,如果一个泛型类中有泛型方法,泛型方法的类型参数可以与泛型类的类型参数不同。若在泛型类中的静态方法要访问泛型参数,必须使它变成泛型方法。然后了解了泛型方法的声明与使用;掌握了泛型接口的定义与实现。将其与之前所学的知识点进行比较的理解和学习,理解泛型程序设计和其用途,对这一章有了概括性的认识。