Java基础学习12之JDK三大主要特性

583 阅读13分钟

Java基础学习12之JDK三大主要特性

「这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战」。

关于作者

  • 作者介绍

🍓 博客主页:作者主页
🍓 简介:JAVA领域优质创作者🥇、一名在校大三学生🎓、在校期间参加各种省赛、国赛,斩获一系列荣誉🏆。
🍓 关注我:关注我学习资料、文档下载统统都有,每日定时更新文章,励志做一名JAVA资深程序猿👨‍💻。


JDK三大主要特性——泛型

泛型可以帮助我们解决参数转换的问题

泛型的引出

下面首先通过一个简单分析来研究一下泛型出现的主要目的是什么?例如现在要求定义一个表示坐标的操作类(Point)这个类可以表示三种类型的坐标:

​ 整数坐标:x=10、y=20;

​ 小数坐标:x=10.1、y=20.3;

​ 字符串数据:x=“东经10度”、y=“西经20度”。

类之中如果想要表示以上的数据,一定需要定义x和y两个属性,而且每一个属性可以接收三种数据类型,那么只能使用Object类来定义会比较合适,这样会发生如下的几种转换关系:

整数:int→自动装箱为Integer→向上转型为Object;

小数:double→自动装箱为Double→向上转型为Obejct;

字符串:字符串→向上转型为Obejct;

设置整形

package com.demo;
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 PointDemo {
	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);
	}
}

设置小数

package com.demo;
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 PointDemo {
	public static void main(String[] args) {
		//第一步:设置数据
		Point p = new Point();
		p.setX(10.1);
		p.setY(20.2);
		//第二步:取出数据
		double x = (Double)p.getX();
		double y = (Double)p.getY();
		System.out.println("x = " + x + ",y = " + y);
	}
}

设置字符串

package com.demo;
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 PointDemo {
	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);
	}
}

看起来所有功能都实现了,并根据之前所学的内容,也只能做到这些了,但是本程序还有一系列问题。

本程序解决问题的关键就在Object类,所有的类型都可以想Obejct转换,但是成也是它败也是它。

public class PointDemo {
	public static void main(String[] args) {
		Point point = new Point();
		//设置参数
		point.setX(10);
		point.setY("北纬");
		//取出参数
		String x = (String) point.getX();
		String y = (String) point.getY();
		System.out.println("x的坐标是:"+x+"y的坐标是:"+y);
	}
}

这个时候程序并没有任何的语法错误,因为数字10 被包装成了Integer,可以使用Obejct接收,从技术上而言,本操作没有问题,但是从实际来讲,因为没有统一,多以在取得数据并且执行向下转型的过程中就会出现如下的错误提示信息:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
	at cn.mldn.demo.TestDemo.main(PointDemo.java:30)

所以,就可以得出一个结论,以上的程序存在安全隐患,但是并没有在程序的编译过程中检查出来,而现在就可以利用泛型来解决这种问题。

泛型实现

泛型:类之中操作的属性或方法的参数类型不在定义的时候声明,而是在使用的时候动态设置。

package com.demo;
//在定义Point不知道是什么类型,由使用者来进行定义使用
class Point<T> {//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 PointDemo {
	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);
	}
}

测试没有了向下转型的操作关系,那么程序就避免了安全性的问题,而且如果设置的类型不统一,在程序编译的过程之中也是可以很好的解决了,直接会报出语法错误。

而且当用户在使用Point类声明对象的时候没有设置泛型,程序在编译的过程之中,会提示警告信息,而且为了保证程序不出现错误,所有的类型都将使用Obejct进行处理。使用泛型可以很好的解决数据类型的统一问题。

但是在此处需要提醒的是,JDK1.5和JDK1.7在定义泛型的时候是稍微有些区别的。

JDK1.5的时候声明泛型的操作

Point<String> p= new Point<String>();

以上是JDK1.5的语法,在声明对象和实例化对象的时候都必须设置好泛型类型。

JDK1.7的时候简化了

Point<String> p= new Point< >();

​ 这个时候实例化对象时泛型的泛型类型就通过声明时泛型类型来定义了。

通配符

泛型的而出现的确是可以解决了数据的统一问题以及避免了向下转型操作,但同事也会带来新的问题,下面通过一段程序,来观察一下会产生什么问题?

为了简化定义一个简单的泛型

package com.demo;

class Message<T>{
	private T info;

	public T getInfo() {
		return info;
	}

	public void setInfo(T info) {
		this.info = info;
	}
}

以上的类对象进行引用传递

public class MessageDemo {
	public static void main(String[] args) {
		Message<String> msg = new Message<>();
		msg.setInfo("hello,world!");
		print(msg);//以上的类对象进行引用传递
	}
	public static void print(Message<String> s){
			System.out.println(s.getInfo());
	}
}

但是如果现在定义的泛型类型不是String呢?例如:换成了int(不能是基本数据类型,只能是包装类)

public class MessageDemo {
	public static void main(String[] args) {
		Message<Integer> msg = new Message<>();
		msg.setInfo(100);
		print(msg);//无法进行引用传递
	}
	public static void print(Message<String> s){
			System.out.println(s.getInfo());
	}
}

发现这个时候的print()方法无法再接收Message对象的引用,因为这个方法只能够接收Message对象的引用,那么可以将print()方法重载换成Message

public class MessageDemo {
	public static void main(String[] args) {
		// TODO 自动生成的方法存根
		Message<Integer> msg = new Message<>();
		msg.setInfo(100);
		print(msg);
	}
	public static void print(Message<String> msg){
		System.out.println(msg.getInfo());
	}
	public static void print(Message<Integer> msg){
		System.out.println(msg.getInfo());
	}
}

这个时候发现按照之前的方式根本就无法进行方法的重载,方法的重载没有说为一个类而定义的,因为方法重载的时候观察的不是泛型类型,而是类的名称,或者说是数据类型的,所以现在就可以发现,这个给出了泛型类之后,就相当于将一个类又划分成了几个小类。

image-20210815130238917

那么现在的问题:方法接收的参数问题又严重了,而且比之前使用对象多态性解决问题时出现的麻烦更大了,至少那个时候可以利用重载来接收一个类的所有子类对象,而现在连重载都使用不了。

​ 这个时候,有人提出了,干脆在定义方法的时候就别写泛型类型了。

定义方法的时候不定义泛型类型

public class MessageDemo {
	public static void main(String[] args) {
		// TODO 自动生成的方法存根
		Message<Integer> msg = new Message<>();
		msg.setInfo(100);
		print(msg);
	}
	public static void print(Message msg){
		System.out.println(msg.getInfo());
	}
}

虽然现在print()方法的参数上出现了警告,但是现在的程序可算是正常了,但是新的问题又来了。问题就在于方法操作中,没有类型限制了。

public static void print(Message msg){
		msg.setInfo(100);
		System.out.println(msg.getInfo());
}

发现此时在print()方法之中操作的时候,由于没有设置泛型类型,那么所有类型都统一变为了Object,也就可以修改了。而通过本程序也就发现了,必须找到一种方法,:此方法可以接收任意的泛型类型的设置,并且不能修改,只能输出,为了解决这样的问题,可以使用通配符“?”表示。

public static void print(Message<?> msg){
		System.out.println(msg.getInfo());
}

由于“?”出现的情况较多,尤其在学习一些类库的时候,所以对于“?”就记住一点,表示任意类型,如果有参数返回的时候也是这个“?”,当成Object进行理解。

既然现在谈到了Obejct,那么现在实际上又有了另外一个问题:对于所有的子类,都是Object子类,那么如果对于之前的程序都使用Object能不能接收?

Message<String> msg = new Message<>();
Message<Object> s = msg;

因为Object的范围比String的范围大。

而在通配符“?”上有衍生出了两个子符号:

  1. 设置泛型的上限:?extends 类;

​ 例如:?extends Number,表示只能是Number或者是Number的子类Integer等;

​ 2.设置泛型的下限:?super类;

​ 例如:?super String,表示只能是String或者是String的父类(Object)

设置泛型上限

package com.demo;

class Message<T extends Number>{
	private T info;

	public T getInfo() {
		return info;
	}

	public void setInfo(T info) {
		this.info = info;
	}
}
public class MessageDemo {
	public static void main(String[] args) {
		Message<Integer> msg = new Message<>();
		msg.setInfo(100);
        //100
		print(msg);
	}
	public static void print(Message<? extends Number> s){
			System.out.println(s.getInfo());
	}
}

设置泛型下限

package com.demo;

class Message<T>{
	private T info;

	public T getInfo() {
		return info;
	}

	public void setInfo(T info) {
		this.info = info;
	}
}
public class MessageDemo {
	public static void main(String[] args) {
		Message<String> msg = new Message<>();
		msg.setInfo("Hello,world!100");
        //Hello,world!100
		print(msg);
	}
	//此时使用通配符“?”描述的是它可以接收任何任意数据类型,但是由于不确定类型,无法修改
	public static void print(Message<? super String> s){
			System.out.println(s.getInfo());
	}
}

泛型接口

在之前的所有定义的泛型之中,都是在类上定义的,而对于接口也是可以进行泛型定义的,而使用泛型定义的接口可以称为泛型接口。

interface Message<T>{
	public String echo(T msg);
}

而对于泛型接口的实现,在Java中有两种方式:

方式一:在子类上继续定义泛型,同时此泛型继续在接口上使用

package com.test;
interface IMessage<T>{
	public void print(T t);
}
class MessageImpl<T> implements IMessage<T>{

	@Override
	public void print(T t) {
		// TODO Auto-generated method stub
		System.out.println(t);
	}

	
}
public class MessageTest {
	public static void main(String[] args) {
		IMessage<String> msgimpl = new MessageImpl();
		msgimpl.print("Hello,world!!");
	}
}

方式二:在子类上设置具体类型

package com.test;
interface IMessage<T>{
	public void print(T t);
}
class MessageImpl implements IMessage<String>{

	@Override
	public void print(String t) {
		// TODO Auto-generated method stub
		System.out.println(t);
	}
	
}
public class MessageTest {
	public static void main(String[] args) {
		IMessage<String> msgimpl = new MessageImpl();
		msgimpl.print("Hello,world!!");
	}
}
3.5 泛型方法

对于泛型除了可以在类上定义,也可以在方法上定义,而在方法上进行泛型的时候这个方法不一定非在泛型类中定义。

public class TestDemo {
	public static void main(String[] args) {
		// TODO 自动生成的方法存根
		Integer result[] = get(1,2,3);
		for(int temp : result){
			System.out.println(temp);
		}
	}
	public static <T> T[] get(T... date){
		return date;
	}
}

JDK三大主要特性——枚举

在讲解枚举之前回顾一个概念:多例设计模式,构造方法私有化(非public),之后在类的内部存在若干个指定的对象,通过一个方法返回指定对象。

多例与枚举

定义一个描述颜色基色的多例设计类

package com.demo;
class Color {
	private static final Color RED = new Color("红色");
	private static final Color GREEN = new Color("绿色");
	private static final Color BLUE = new Color("蓝色");
	private String title;
	private Color(String title){
		this.title=title;
	}
	public String toString(){
		return this.title;
	}
	public static Color getColor(int num){
		switch(num){
		case 0:
		return RED;
		case 1:
		return GREEN;
		case 2:
		return BLUE;
		default:
		return null;
		}
	}
}
public class ColorDemo {
	public static void main(String[] args) {
		Color c = Color.getColor(0);
		System.out.println(c);
	}
}

基于枚举开发

package com.demo;
enum Color {
	RED,BULE,PINK;
}
public class ColorDemo {
	public static void main(String[] args) {
		System.out.println(Color.RED);
	}
}

Enum类

很明显,现在可以发现,利用枚举实现多例设计会更加的简单直白一些,但是在Java之中,枚举并不是一个新的类型,严格来讲,每一个使用enum定义的类实际上都属于一个类继承了Enum父类而已,而java.lang.Enum类定义如下:

public abstract class Enum<E extends Enum<E>>
extends Object
implements Comparable<E>, Serializable

而在Enum类种子红定义了两个方法:

  1. 取得枚举的序号:public final int ordinal();
  2. 取得枚举的名称:public final String name()。
package com.demo;
enum Color {
	RED,BULE,PINK;
}
public class ColorDemo {
	public static void main(String[] args) {
		//0===RED
		System.out.println(Color.RED.ordinal() + "===" + Color.RED.name());
	}
}

取得所有颜色数据

package com.demo;
enum Color {
	RED,BULE,PINK;
}
public class ColorDemo {
	public static void main(String[] args) {
				/*0===RED
				1===BULE
				2===PINK*/
		for (Color c : Color.values()) {
			System.out.println(c.ordinal() + "===" + c.name());
		}
		
	}
}

面试题:请解释enum和Enum的区别?

enum是一个关键字,使用enum定义的枚举类本质上相当于一个类继承了Enum类而已。

枚举中定义其它结构

按照之前所理解,枚举就属于多例设计模式,那么既然是多例设计模式,对于类之中就肯定有多种组成,包括属性,方法,构造方法,在枚举之中也同样可以定义以上的内容,不过需要注意的是,枚举类之中定义的构造方法绝对不能是public,必须私有化。

除了这些要求之外枚举之中每一个定义的对象必须定义在第一行。

package com.demo;
enum Color {
	RED("红色"),BULE("蓝色"),PINK("粉色");
	private String c;
	private Color(String c){
		this.c = c;
	}
	public String toString(){
		return this.c;
	}
}
public class ColorDemo {
	public static void main(String[] args) {
				/*红色*/
		System.out.println(Color.RED);
	}
}

枚举接口

package com.demo;
interface IColor{
	public String getColor();
}
enum Color implements IColor{
	RED("红色"),BULE("蓝色"),PINK("粉色");
	private String c;
	private Color(String c){
		this.c = c;
	}
	public String toString(){
		return this.c;
	}
	@Override
	public String getColor() {
		// TODO Auto-generated method stub
		return this.c;
	}
}
public class ColorDemo {
	public static void main(String[] args) {
				/*红色*/
		System.out.println(Color.RED);
	}
}

枚举应用

只有指定的几个对象

性别

package com.demo;
class Person{
	private String name;
	private int age;
	private Sex sex;
	public Person(String name, int age, Sex sex) {
		super();
		this.name = name;
		this.age = age;
		this.sex = sex;
	}
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + ", sex=" + sex + "]";
	}
}
enum Sex{
	MALE("男"),FEMALE("女");
	private	String sex;

	private Sex(String sex) {
		this.sex = sex;
	}
	public String toString(){
		return this.sex;
	}
}
public class MaleDemo {
	public static void main(String[] args) {
		Person person = new Person("jack", 10, Sex.MALE);
		System.out.println(person.toString());
	}
}

枚举还可以进行switch语句进行编写和判断。

JDK三大主要特性——Annotation

在JDK1.5之后,程序允许通过注解(Annotation)的方式来进行程序的定义,而在JavaSE之中攒在了三种Annotation:@Override、@Deprecated、@SuppressWarnings。

准确的覆写:@Override

方法的覆写:发生继承关系之中,子类定义了与父类的方法名称相同、参数类型以及个数相同的覆写,被覆写的方法不能够拥有比父类更为严格的访问控制权限。

package com.annotation;
class Person{
	//现在是希望进行toString()覆写,但遗憾的由于你自己的输入错误,导致方法的覆写错误
	//@Override
	public String tostring(){//现在希望可以进行toString()方法的覆写
		return "一个人";
	}
}
public class Demo1 {
	public static void main(String[] args) {
		System.out.println(new Person().tostring());
	}
}

这个时候不叫覆写,属于自己定义一个扩展的方法,最为重要的是,这个问题在程序编译的根本就无法显示出来。但是现在为了保证我们的覆写方法的严格,可以使用一个注解(@Override)来检测:如果该方法确定成的覆写了

,则不会有我们的语法错误,如果进行成功的覆写,认为语法的错误。

package com.annotation;
class Person{
	//现在是希望进行toString()覆写,但遗憾的由于你自己的输入错误,导致方法的覆写错误
	public String toString(){//现在希望可以进行toString()方法的覆写
		return "一个人";
	}
}
public class Demo1 {
	public static void main(String[] args) {
		System.out.println(new Person());
	}
}

声明过期操作:@Deprecated

对于程序开发而言,往往一些使用的类要进行修改或者是维护,如果说现在一个类之中的某个方法,可能一开始推出的时候正常使用,但是在后面的版本就存在了一些问题,在修改之后不希望人再去使用这些方法,那么肯定不能直接删除,因为如果直接删除了,那么之前的程序就会出现问题了,所以最好的做法是告诉用户:这个方法存在了问题,不建议再使用了,这个时候就使用“@Deprecated”声明。

package com.annotation;
class Person1{
	@Deprecated//表示该方法不建议使用,即使使用仍然不会报错
	public Person1(){}
	public Person1(String name){}
	@Deprecated
	public void print(){}
}
public class Demo2 {
	public static void main(String[] args) {
		Person1 person = new Person1();//明确标记过期
		person.print();
	}
}

压制警告:@SuppressWarning

程序在编译的时候如果提示警告但是不会报错只是存在了某些安全隐患,肯定会提示用户,所以不想让其显示的话,就增加压制警告信息。

package com.annotation;
class Person1<T>{
	@Deprecated//表示该方法不建议使用,即使使用仍然不会报错
	public Person1(){}
	public Person1(String name){}
	@Deprecated
	public void print(){}
}
public class Demo2 {
	@SuppressWarnings("rawtypes")
	public static void main(String[] args) {
		Person1 person = new Person1();//明确标记过期
		person.print();
	}
}

image-20210815220449698

关于软件的开发模式;

​ 第一阶段:会将所有的操作都写在程序之中,例如:网络程序,连接程序,连接的服务器地址,用户验证等;

​ 第二阶段:程序+配置文件,配置文件和程序相分离,配置文件过多,后期修改非常复杂;

​ 第三阶段:将配置文件写回到程序之中,但是和程序进行有效的分离。