➢当在Java中调用一个方法时, 必须严格的按照方法定义的变量进行参数传递,但是在开发中有可能会出现这样-种情况: 不确定要传递的参数个数。例如:现在要求设计一个方法, 此方法可以实现若千个整型变量的相加操作。最早的时候为了解决这个问题,往往需要将多个参数封装为数组才可以满足要求。
最初的解决方案
public class Main {
public static void main(String args[]) throws Exception {
System.out.println(add(new int[]{
1,2,3}));
}
public static int add(int [] data){
int sum = 0;
for(int i = 0; i < data.length; i++){
sum += data[i];
}
return sum;
}
}
结果:6
使用可变参数
public class Main {
public static void main(String args[]) throws Exception {
System.out.println(add(1,2,3));
}
//“...”代表接收任意个int类型的参数
public static int add(int ... data){
int sum = 0;
for(int i = 0; i < data.length; i++){
sum += data[i];
}
return sum;
}
}
结果:6
➢foreach是一种加强型的for循环操作, 主要是可以简化数组或集合数据的输出操作。以下是使用格式:
public class Main {
public static void main(String args[]) throws Exception {
int[] ints = {
1, 2, 3};
for(int i : ints){
System.out.print(i+"、");
}
}
}
结果:1、2、3、
➢在面向对象的开发中,利用对象的多态性可以解决方法参数的统一-问题, 但是随之而来也会带来有一个新的问题: “向 下转型会存在类转换异常(ClassCastException) ",所以向下转型的操作并不是安全的,那么为了解决这样的问题,从JDK 1.5开始提供有泛型技术,而本节将为读者分析泛型技术的产生原因以及相关定义。
➢现在假设要开发一个GIS系统(地理信息系统、Geographic Information System),则肯定需要一个可以描述坐标的类 (Point) ,同时在这个类里面要求保存有以下几种类型的坐标:
保存数字:x=10、y = 20;
保存小数: x= 10.2、y = 20.3;
保存字符串: x=东经20度、y =北纬15度。
➢现在这个Point类设计的关键就在于x与y这两个变量的数据类型选择上。必须有一种数据类型可以保存这三 类数据,那么首先想到的一定是Object类型,因为此时会存在有如下的转换关系:
➢int数据类型: int自动装箱为Integer, Integer向上转型为Object;
double数据类型: double自动装箱为Double,Double向 上转型为Object;
➢String数据类型: 直接向上转型为Object。
package com.company;
class Point{
private Object x; //设置坐标
private Object y; //设置坐标
public Object getX() {
return x;
}
public void setX(Object x) {
this.x = x;
}
public Object getY() {
return y;
}
public void setY(Object y) {
this.y = y;
}
}
public class Main {
public static void main(String args[]) {
//设置坐标
Point p = new Point();
p.setX(10);
p.setY(20);
//获取坐标
int x =(Integer)p.getX();
int y = (Integer)p.getY();
System.out.println("x坐标:"+x+"y坐标:"+y);
}
}
结果:x坐标:10y坐标:20
上述程序在调用setter方法设置坐标时,所有数据类型都发生了向上转型,而在取得数据时都发生了向下转型。表面看起来时没有什么问题,但是会带来一个严重后果:一旦设置的内容出现错误,在程序编译时是无法检查出来的。
错误的程序
public class Main {
public static void main(String args[]) {
//设置坐标
Point p = new Point();
p.setX("北纬10");
p.setY(20);
//获取坐标
String x =(String)p.getX();
String y = (String) p.getY();
System.out.println("x坐标:"+x+"y坐标:"+y);
}
}
结果:Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at com.company.Main.main(Main.java:26)
原本打算设置的坐标数据是字符串数据,但是在设置数据时出现了错误,将Y坐标数据设置成了20而不是字符串。由于int型可以通过自动装箱使用Integer再向上转型Object接收,所以在程序编译时不会有任何语法错误。而在程序执行过程中需要将Y坐标数据取出,并且以String的形式进行强制向下转型,这时运行中程序就会报异常了,这样就会为开发带来很大的困扰,如果可以在编译时就能够排查出来就比较合理了。所以就新增了泛型技术,其意义在于:类属性或方法的参数在定义数据类型时,可以直接使用一个标记进行占位,在具体使用时才设置其对应的实际数据类型,这样当设置的数据类型出现错误后,就可以在程序编译时监测出来。
使用泛型修改
package com.company;
class Point<T>{
private T x; //设置坐标
private T y; //设置坐标
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
public class Main {
public static void main(String args[]) {
//设置坐标
Point<String> p = new Point<>(); //设置泛型类型为String
p.setX("北纬10");
p.setY(20); //此行代码会出现警告,此处需要字符串
//获取坐标
String x =p.getX(); //取出数据,不需要强制转换
String y = p.getY(); //取出数据,不需要强制转换
System.out.println("x坐标:"+x+"y坐标:"+y);
}
}
使用泛型后,所有类中的属性的类型都是动态设置的,且所有使用泛型标记的方法参数类型也都发生改变,这样就相当于避免了向下转型的问题,从而解决了类对象转换的安全隐患。需要特别说明的时,要想使用泛型,那么它能够采用的类型只能够是类,即不能是基本类型,只能是引用类型。
定义多个泛型
class Point<P,R>{
public R fun(P p){
return null;
}
}
如果不设置泛型会怎样?
答:为了保证设计的合理性,如果不设置泛型会使用Object类型。
➢利用泛型技术虽然解决了向下转型所带来的安全隐患问题,但同时又会产生一个新的问题: 即便是同一个类,但是由于设置泛型类型的不同,那么其对象表示的含义也是不同,是不能够直接进行引用操作的,例如:现在有如下一个类。
class Message<T>{
private T msg;
public T getMsg() {
return msg;
}
public void setMsg(T msg) {
this.msg = msg;
}
}
如果定义“Message”和“Message” 虽然都是Message类的对象,但是这两个对象之间是不能够进行直接的引用传递操作,那么这样就会在方法的参数传递上造成新的问题,此时就可以利用通配符“ ?”来进行描述。
上面代码参照上面的
public class Main {
public static void main(String args[]) {
Message<String> m = new Message<>();
m.setMsg("aaa");
fun(m);
}
public static void fun(Message<String> temp){
System.out.println(temp.getMsg());
}
}
这个程序的主要功能是实现一个Message类对象的引用传递,而在定义fun()方法时也设置了接收的参数“Message temp”,这样就表示只要泛型类型为String的Message对象都可以接收。但是一旦这样定义,fun()方法就不能再接收泛型类型非String的了。
一个错误的示例
public class Main {
public static void main(String args[]) {
Message<Integer> m = new Message<>();
m.setMsg(30);
fun(m); //程序错误,因为泛型类型为String
}
public static void fun(Message<String> temp){
System.out.println(temp.getMsg());
}
}
使用通配符“ ?”来解决参数转递问题
public class Main {
public static void main(String args[]) {
Message<Integer> m = new Message<>();
Message<String> n = new Message<>();
m.setMsg(10);
n.setMsg("aaa");
fun(m);
fun(n);
}
public static void fun(Message<?> temp){
System.out.println(temp.getMsg());
}
}
结果:10
aaa
提问:能否使用Object作为泛型类型,或者不设置类型?
回答:使用通配符“ ?”的意义在于可以接收类对象,但是不能修改对象属性。
首先需要解释一个核心问题:在明确设置一个类为泛型类型时没有继承的概念范畴,也就是说虽然Object类与String类在类定义关系中属于父类与子类的关系,但换到泛型中“Message”与“Message”就属于两个完全独立的概念。
如果在定义fun()方法时不设置泛型类型,也就可以实现任意泛型类型对象的接收,但此时就会出现一个问题:如果不指派具体的泛型类型,则默认Object类型,也就是说方法里面可以随意修改属性内容。观察如下代码
public class Main {
public static void main(String args[]) {
Message<Integer> m = new Message<>();
Message<String> n = new Message<>();
m.setMsg(10);
n.setMsg("aaa");
fun(m);
fun(n);
}
public static void fun(Message temp){
//随意修改属性内容
temp.setMsg("修改属性内容");
System.out.println(temp.getMsg());
}
}
结果:修改属性内容
修改属性内容
上述程序随意修改对象内容是不严谨的,必须使用通配符“ ?”来制约这种任意修改数据问题的操作。所以“ ?”设置的泛型类型只表示可以取出,但是不能设置,一旦设置了内容,程序编译就会出现错误提示。
➢
“? extends类”:设置泛型上限,可以在声明上和方法参数上使用;
➢? extends Number:意味着可以设置Number或者是Number的子类(Integer、 Double、 … ;
➢”? super类”:设置泛型下限,方法参数.上使用;
➢? super String:意味着只能够设置String或者是它的父类0bject。
设置泛型的上限
package com.company;
class Message<T extends Number>{
//设置泛型的上限,只能是Number或Number子类
private T msg;
public T getMsg() {
return msg;
}
public void setMsg(T msg) {
this.msg = msg;
}
}
public class Main {
public static void main(String args[]) {
Message<Integer> m = new Message<>(); //Integer是Number子类
m.setMsg(10);
fun(m); //引用传递
}
public static void fun(Message<? extends Number> temp){
//设置泛型的上限,只能是Number或Number子类
System.out.println(temp.getMsg());
}
}
结果:10
设置泛型的下限
package com.company;
class Message<T>{
private T msg;
public T getMsg() {
return msg;
}
public void setMsg(T msg) {
this.msg = msg;
}
}
public class Main {
public static void main(String args[]) {
Message<String> m = new Message<>();
m.setMsg("hello");
fun(m);
}
public static void fun(Message<? super String> temp){
//设置了泛型的下限,只能是String或者是它的父类0bject
System.out.println(temp.getMsg());
}
}
结果:hello
定义泛型接口
/**
* 定义泛型接口,由于类与接口命名标准相同,为了区分出类与接口,在接口前面加上字母“I”,例如:IMessage
* 如果定义抽象类,则可以在前面加上Abstract,例如:AbstractMessage
*/
interface IMessage<T>{
//定义泛型接口
public void print(T t);
}
在子类继续设置泛型标记
package com.company;
interface IMessage<T>{
public void print(T t);
}
class MessageImpl<T> implements IMessage<T>{
@Override
public void print(T t) {
System.out.println(t);
}
}
public class Main {
public static void main(String args[]) {
IMessage<String> m = new MessageImpl<>();
m.print("hello");
}
}
结果:hello
本程序在定义IMeaasge接口子类时继续设置了泛型,在实例化接口子类对象时所设置的泛型类型也就是IMessage接口使用的泛型类型。
在子类不设置泛型,而为父接口明确定义一个泛型类型
package com.company;
interface IMessage<T>{
public void print(T t);
}
class MessageImpl implements IMessage<String>{
@Override
public void print(St
ring t) {
System.out.println(t);
}
}
public class Main {
public static void main(String args[]) {
IMessage<String> m = new MessageImpl();
m.print("hello");
}
}
结果:hello
对于泛型除了可以定义在类上外,还可以在方法上进行定义,而在方法上定义泛型时,这个方法不一定非要在泛型类中定义。
泛型方法定义
public class Main {
public static void main(String args[]) {
System.out.println(("hello"));
}
public static <T> T fun(T t){
return t;
}
}
结果:hello
➢枚举是JDK 1.5之后增加的一个主要新功能,利用枚举可以简化多例设计模式(一 个类只能够产生固定几个实例化对象)的定义,同时在Java中的枚举也可以像普通类那样定义属性、构造方法、实现接口等
枚举主要用于定义一组可以使用的类对象,这样在使用时只能通过固定的几个对象来进行类的操作。
从JDK1.5开始,专门提供了一个新的关键字:enum,利用该关键字就可以定义枚举类型。
定义枚举类
enum Color{
RED,GREEN,BLUE;
}
public class Main {
public static void main(String args[]) {
System.out.println(Color.RED); //默认内容就是对象的名称
}
}
结果:RED
枚举属于简化的多例设计模式
class Color{
private String title;
private static final Color RED = new Color("红色");
private static final Color GREEN = new Color("绿色");
private static final Color BLUE = new Color("蓝色");
public Color(String title) {
this.title = title;
}
public static Color getInstance(int chr){
switch (chr){
case 1:
return RED;
case 2:
return GREEN;
case 3:
return BLUE;
default:
return null;
}
}
@Override
public String toString() {
return "Color{" +
"title='" + title + '\'' +
'}';
}
}
public class Main {
public static void main(String args[]) {
Color red = Color.getInstance(1);
System.out.println(red);
}
}
结果:Color{
title='红色'}
Enum类
➢枚举只需要使用enum关键字就可以定义,但是严格来讲,枚举只是类结构的加强而已。因为在Java中使用enum定义的枚举类就相当于默认继承了java.lang.Enum类,此类定义如下:
public abstract class Enum
extends Object
implements Comparable, Serializable
Enum类定义的方法:
NO. | 方法 | 类型 | 描述 |
---|---|---|---|
1 | protected Enum(String name , int ordinal) | 构造 | 传递枚举对象的名称和序号 |
2 | pubilc final int ordinal() | 普通 | 取得当前枚举对象的序号 |
3 | public final String name () | 普通 | 取得当前枚举对象的名称 |
enum Color{
RED,GREEN,BLUE;
}
public class Main {
public static void main(String args[]) {
Color red = Color.RED;
System.out.println("当前枚举对象序号:"+red.ordinal());
System.out.println("当前枚举对象名称:"+red.name());
}
}
结果:当前枚举对象序号:0
当前枚举对象名称:RED
枚举类除了可以继承Enum抽象类提供的方法外,还定义了一个values()方法,这个方法会将枚举类中的全部对象以对象数组的形式返回。
返回枚举中的全部内容
enum Color{
RED,GREEN,BLUE;
}
public class Main {
public static void main(String args[]) {
for(Color c : Color.values()){
System.out.println(c.ordinal()+"--"+c.name());
}
}
}
结果:
0--RED
1--GREEN
2--BLUE
➢按照之前所理解,枚举就属于多例设计模式,那么既然是多例设计模式,对于类之中就肯定有多种组成,包括属性、方法、构造方法,在枚举之中也同样可以定义以上的内容,但是此处需要注意两点问题:
➢枚举之中定义的构造方法不能够使用public声明,如果没有无参构造,请手工调用构造传递参数;
➢枚举对象必须要放在首行,随后才可以定义属性、构造、普通方法等结构。
扩充枚举功能
enum Color{
RED("红色"),GREEN("绿色"),BLUE("蓝色");
private String title;
private Color(String title){
this.title = title;
}
@Override
public String toString() {
return this.title;
}
}
public class Main {
public static void main(String args[]) {
for(Color c : Color.values()){
System.out.print(c+"、");
}
}
}
结果:红色、绿色、蓝色、
枚举实现接口
interface IMessage{
String getTitle();
}
enum Color implements IMessage{
RED("红色"),GREEN("绿色"),BLUE("蓝色");
private String title;
private Color(String title){
this.title = title;
}
@Override
public String toString() {
return this.title;
}
@Override
public String getTitle() {
//其实没啥特别的,跟以前类一样实现方法就行
return this.title;
}
}
public class Main {
public static void main(String args[]) {
IMessage red = Color.RED;
System.out.println(red.getTitle());
}
}
结果:红色
枚举最大的作用就是限定一个类的对象的产生格式,并且其要比多例设计模式更加简单
在Switch中使用枚举
enum Color{
RED("红色"),GREEN("绿色"),BLUE("蓝色");
private String title;
private Color(String title){
this.title = title;
}
@Override
public String toString() {
return this.title;
}
}
public class Main {
public static void main(String args[]) {
Color red = Color.RED;
switch (red){
case RED:
System.out.println("红色");
break;
case GREEN:
System.out.println("绿色");
break;
case BLUE:
System.out.println("蓝色");
break;
}
}
}
结果:红色
在类设计结构中使用枚举
enum Sex{
MAN("男人"),WOMAN("女人");
private String title;
private Sex(String title){
this.title = title;
}
@Override
public String toString() {
return this.title;
}
}
class Member{
private String name;
private int age;
private Sex sex;
public Member(String name,int age,Sex sex){
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "Member{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
}
public class Main {
public static void main(String args[]) {
System.out.println(new Member("小红",15,Sex.WOMAN));
}
}
结果:Member{
name='小红', age=15, sex=女人}
➢当进行方法覆写的时候,为了保证子类所覆写的方法的确是父类中定义过的方法,就可以加上"@Override" 注解,这样一-旦用户覆写方法时出现了 错误,可以在编译时直接检查出来。
class Member{
@Deprecated //声明此方法是过期方法,尽量不适用
public void fun(){
}
}
public class Main {
public static void main(String args[]) {
Member member = new Member();
member.fun(); //有横线
}
}
暂无
定义普通方法
➢如果现在要在接口中定义普通方法,那么该方法上必须使用default来进行定义。
interface IMessage{
void print();
default void fun(){
System.out.println("这是一个普通方法");
};
}
class Message implements IMessage{
@Override
public void print() {
System.out.println("这是一个实现方法");
}
}
public class Main {
public static void main(String args[]) {
Message m = new Message();
m.print();
m.fun();
}
}
结果:这是一个实现方法
这是一个普通方法
定义static方法
➢使用default定义普通方法,是需要利用实例化对象明确调用的。如果用户有需要还可以使用static定义方法,这样该方法就可以由接口名称直接调用。
interface IMessage{
void print();
static void fun(){
System.out.println("这是static普通方法");
};
}
public class Main {
public static void main(String args[]) {
IMessage.fun(); //直接通过接口来调用,不需要实例化对象
}
}
结果:这是一个static方法
➢Lambda表达式是JDK 1.8中引入的重要技术特征。所谓的Lamda表达式指的是应用在“SAM”(Single Abstract Method,含有一个抽象方法的接口) 环境下的一-种简化定义形式,可以于解决匿名内部类的定义复杂问题。
关于@FunctionalInterface注解的使用:
在Lambda表达式中已经明确要求是在接口上进行的一种操作,并且接口中只运行定义一个抽象方法。但是在一个项目开发中往往会定义大量的接口,而为了分辨出Lambda表达式使用的接口,可以在接口上使用“@FunctionalInterface”注解声明,这样就表示此为函数式接口,里面只允许定义一个抽象方法
Lambda表达式入门操作
interface IMessage{
void print();
}
public class Main {
public static void main(String args[]) {
fun(() -> System.out.println("Lambda表达式"));
}
public static void fun(IMessage msg){
msg.print();
}
}
结果:Lambda表达式
下面是传统的写法,观察下区别
interface IMessage{
void print();
}
public class Main {
public static void main(String args[]) {
fun(new IMessage(){
@Override
public void print() {
System.out.println("Lambda表达式");
}
});
}
public static void fun(IMessage msg){
msg.print();
}
}
结果:Lambda表达式
Lambda表达式使用形式
➢方法主体为1个表达式: (params) -> expression;
➢方法主体为1行执行代码: (params) -> statement;
➢方法主体需要多行代码: (params) -> { statements}.
编写多行语句
@FunctionalInterface //用来检测该接口是否只有一个抽象方法
interface IMessage{
void print();
}
public class Main {
public static void main(String args[]) {
fun(() -> {
System.out.println("多行语句");
System.out.println("多行语句");
});
}
public static void fun(IMessage msg){
msg.print();
}
}
结果:多行语句
多行语句
定义有参数有返回值的方法
@FunctionalInterface
interface IMessage{
int add(int x,int y);
}
public class Main {
public static void main(String args[]) {
fun((a,b)->{
return a+b;
});
}
public static void fun(IMessage msg){
msg.add(5,6);
}
}
结果:11
传递可变参数
@FunctionalInterface
interface IMessage{
int add(int ...arg);
static int sum(int ...arg){
int sum = 0;
for(int temp : arg){
sum += temp;
}
return sum;
};
}
public class Main {
public static void main(String args[]) {
fun((int ...parms) -> IMessage.sum(parms));
}
public static void fun(IMessage msg){
msg.add(5,6);
}
}
结果:11
➢在Java中利用对象的引用传递可以实现不同的对象名称操作同一块堆内存空间的操作,而从DK 1.8开始,对于方法上也支持了引用操作,这样就相当于为方法定义了别名,对于方法引用,在Java8中一共定义有如下四种操作形式:
➢引用静态方法:类名称:: static方法名称;
➢引用某个对象的方法:实例化对象::普通方法;
➢引用特定类型的方法:特定类::普通方法;
➢引用构造方法:类名称:: new。
@FunctionalInterface
interface IMessage<P,R>{
R zhuanhuan(P p);
}
public class Main {
public static void main(String args[]) {
//其实就是将String.valueOf()方法变为了IMessage接口中的zhuanhuan方法,换了个名称而已
IMessage<Integer,String> msg = String::valueOf;
String s = msg.zhuanhuan(1000);
System.out.println(s);
}
}
结果:1000
@FunctionalInterface
interface IMessage<R>{
R zhuanhuan();
}
public class Main {
public static void main(String args[]) {
//其实就是将toUpperCase()方法变为了IMessage接口中的zhuanhuan方法
IMessage<String> msg = "aaa"::toUpperCase;
String s = msg.zhuanhuan();
System.out.println(s);
}
}
结果:AAA
@FunctionalInterface
interface IMessage<R>{
int zhuanhuan(R r1,R r2);
}
public class Main {
public static void main(String args[]) {
IMessage<String> msg = String::compareTo;
//传递调用的参数,形式为:"a".compareTo("b")
int s = msg.zhuanhuan("a","b");
System.out.println(s);
}
}
结果:-1
@FunctionalInterface
interface IMessage<R>{
R create(String title);
}
class Book{
private String title;
public Book(String title){
this.title = title;
}
@Override
public String toString() {
return "Book{" +
"title='" + title + '\'' +
'}';
}
}
public class Main {
public static void main(String args[]) {
IMessage<Book> msg = Book::new;
Book book = msg.create("aa");
System.out.println(book);
}
}
结果:Book{
title='aa'}
➢在方法引用的操作过程之中,读者可以发现,不管如何进行操作,对于可能出现的函数式接口的方法也最多只会有四类:有参数有返回值、有参数无返回值、无参数有返回值、判断真假。所以为了简化开发者的定义以及操作的统一, 从JDK 1.8开始提供有一个新的开发包: java.util.function, 并且在这个包中提供有以下四个核心的函数式接口: Function、 Consumer. Supplier、 Predicate。
public class Main {
public static void main(String args[]) {
Function<String,Boolean> fun = "##aa"::startsWith;
System.out.println(fun.apply("#"));
}
}
结果:true
public class Main {
public static void main(String args[]) {
Consumer<String> fun = System.out::println;
fun.accept("##");
}
}
结果:##
public class Main {
public static void main(String args[]) {
Supplier<String> fun = "aaa"::toUpperCase;
System.out.println(fun.get());
}
}
结果:AAA
public class Main {
public static void main(String args[]) {
Predicate<String> fun = "abc"::equalsIgnoreCase;
System.out.println(fun.test("ABC"));
}
}
结果:true