Java基础
泛型了解吗?写了个题,关于泛型边界的。
通俗解释
通俗的讲,泛型就是操作类型的 占位符,即:假设占位符为T,那么此次声明的数据结构操作的数据类型为
T类型。
假定我们有这样一个需求:写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,该如何实现?答案是可以使用 Java 泛型。
使用 Java 泛型的概念,我们可以写一个泛型方法来对一个对象数组排序。然后,调用该泛型方法来对整型数组、浮点数数组、字符串数组等进行排序。
泛型方法
你可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。
下面是定义泛型方法的规则:
- 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的 <E>)。
- 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
- 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
- 泛型方法体的声明和其他方法一样。注意类型参数 只能代表引用型类型,不能是原始类型 (像 int,double,char 的等)。
public class GenericMethodTest { // 泛型方法 printArray public static < E > void printArray( E[] inputArray ) { // 输出数组元素 for ( E element : inputArray ){ System.out.printf( "%s ", element ); } System.out.println(); } public static void main( String args[] ) { // 创建不同类型数组: Integer, Double 和 Character Integer[] intArray = { 1, 2, 3, 4, 5 }; Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 }; Character[] charArray = { 'H', 'E', 'L', 'L', 'O' }; System.out.println( "整型数组元素为:" ); printArray( intArray ); // 传递一个整型数组 System.out.println( "\n双精度型数组元素为:" ); printArray( doubleArray ); // 传递一个双精度型数组 System.out.println( "\n字符型数组元素为:" ); printArray( charArray ); // 传递一个字符型数组 } }
泛型类
泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。
和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。
public class Box<T> { private T t; public void add(T t) { this.t = t; } public T get() { return t; } public static void main(String[] args) { Box<Integer> integerBox = new Box<Integer>(); Box<String> stringBox = new Box<String>(); integerBox.add(new Integer(10)); stringBox.add(new String("菜鸟教程")); System.out.printf("整型值为 :%d\n\n", integerBox.get()); System.out.printf("字符串为 :%s\n", stringBox.get()); } }
类型通配符
类型通配符一般是使用
?
代替具体的类型参数。例如List<?>
在逻辑上是List<String>
,List<Integer>
等所有 List<具体类型实参> 的父类。<? extends T>
表示类型的上界,表示参数化类型的可能是T 或是 T的子类<? super T>
表示类型下界(Java Core中叫超类型限定),表示参数化类型是此类型的超类型(父类型),
直至Object
为什么要使用Java泛型
这里的泛型就相当于“约法三章”,先给你定好“规矩”,我这个List<string> 就是用来操作String类型的,你插入
Person对象就不行。说白了就是为了类型安全。所以其好处有:</string>
类型安全:通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。
消除强制类型转换:
//该代码不使用泛型: List li = new ArrayList(); li.put(new Integer(3)); Integer i = (Integer) li.get(0); //该代码使用泛型: List<Integer> li = new ArrayList<Integer>(); li.put(new Integer(3)); Integer i = li.get(0);
参考资料:
了解注解吗,怎么用注解?注解的原理是什么?
什么是注解
Annontation 是 Java5 开始引入的新特征,中文名称叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation 像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。
Java 注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation
包中。
简单来说:注解其实就是代码中的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相对应的处理。
为什么要用注解
传统的方式,我们是通过配置文件 .xml
来告诉类是如何运行的。
有了注解技术以后,我们就可以通过注解告诉类如何运行
例如:我们以前编写 Servlet 的时候,需要在 web.xml 文件配置具体的信息。我们使用了注解以后,可以直接在 Servlet 源代码上,增加注解...Servlet 就被配置到 Tomcat 上了。也就是说,注解可以给类、方法上注入信息。
明显地可以看出,这样是非常直观的,并且 Servlet 规范是推崇这种配置方式的。
注解的用处:
1、生成文档。这是最常见的,也是java 最早提供的注解。常用的有@param @return 等
2、跟踪代码依赖性,实现替代配置文件功能。比如Dagger 2 依赖注入,未来java开发,将大量注解配置,
具有很大用处;
3、在编译时进行格式检查。如@override放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能 检查出。
注解的原理:
注解本质是一个继承了Annotation 的特殊接口,其具体实现类是Java运行时生成的动态代理类。而我们通过
反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler 的invoke 方法。该方法会从memberValues 这个Map中索引出对应
的值。而memberValues 的来源是Java 常量池。
基本Annotation
在 java.lang 包下存在着5个基本的 Annotation,重点掌握前三个。
@Override 重写注解
- 如果我们使用IDE重写父类的方法,我们就可以看见它了。
- @Override是告诉编译器要检查该方法是实现父类的,可以帮我们避免一些低级的错误。
- 比如,我们在实现 equals() 方法的时候,把 euqals() 打错了,那么编译器就会发现该方法并不是实现父类的,与注解 @Override 冲突,于是就会给予错误。
@Deprecated 过时注解
- 该注解也非常常见,Java 在设计的时候,可能觉得某些方法设计得不好,为了兼容以前的程序,是不能直接把它抛弃的,于是就设置它为过时。
- Date对象中的 toLocalString() 就被设置成过时了
- 当我们在程序中调用它的时候,在 IDE 上会出现一条横杠,说明该方法是过时的。
@Deprecated public String toLocaleString() { DateFormat formatter = DateFormat.getDateTimeInstance(); return formatter.format(this); }
@SuppressWarnings 抑制编译器警告注解
- 该注解在我们写程序的时候并不是很常见,我们可以用它来让编译器不给予我们警告
- 当我们在使用集合的时候,如果没有指定泛型,那么会提示安全检查的警告
- 如果我们在类上添加了@SuppressWarnings这个注解,那么编译器就不会给予我们警告了
@SafeVarargs Java 7“堆污染”警告
- 什么是堆污染呢??当把一个不是泛型的集合赋值给一个带泛型的集合的时候,这种情况就很容易发生堆污染。
- 这个注解也是用来抑制编译器警告的注解,用的地方并不多。
@FunctionalInterface 用来指定该接口是函数式接口
- 用该注解显式指定该接口是一个函数式接口。
自定义注解类编写规则
- Annotation 型定义为 @interface, 所有的 Annotation 会自动继承 java.lang.Annotation 这一接口,并且不能再去继承别的类或是接口.
- 参数成员只能用 public 或默认(default)这两个访问权修饰
- 参数成员只能用基本类型 byte,short,char,int,long,float,double,boolean 八种基本数据类型和 String、Enum、Class、annotations 等数据类型,以及这一些类型的数组
- 要获取类方法和字段的注解信息,必须通过 Java 的反射技术来获取 Annotation 对象,因为你除此之外没有别的获取注解对象的方法
- 注解也可以没有定义成员, 不过这样注解就没啥用了
PS:自定义注解需要使用到元注解
自定义注解实例
import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * 水果名称注解 */ @Target(FIELD) @Retention(RUNTIME) @Documented public @interface FruitName { String value() default ""; }
java中static关键字的用法
static 关键字主要有以下四种使用场景:
- 修饰成员变量和成员方法: 被static修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,
可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java内存区域的
方法区。调用格式:类名.静态变量名 类名.静态方法名() - 静态代码块: 静态代码块定义在类中方法外,静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—> 构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
- 静态内部类(static修饰类的话只能修饰内部类): 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有 。没有这个引用就意味着:
- 它的创建是不需要依赖外围类的创建。
- 它不能使用任何外围类的非static成员变量和方法。
- 静态导包(用来导入类中的静态资源,1.5之后的新特性): 格式为:import static这两个关键字连用可以指定
导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。
6.static方法和非static方法的区别
- 静态方法属于类本身,非静态方法属于从该类生成的每个对象。
- 在外部调用静态方法时,可以使用”类名.方法名”的方式,也可以使用”对象名.方法名”的方式。而实例方法只有 后面这种方式。也就是说,调用静态方法可以无需创建对象。
- 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变 量和实例方法;实例方法则无此限制
Java API 中 NIO 和 BIO 的区别
Java中的NIO,BIO,AIO分别是什么
同步阻塞IO(BIO):用户进程发起一个IO操作以后,必须等待IO操作的真正完成后,才能继续运行;
同步非阻塞IO(NIO):用户进程发起一个IO操作以后,可做其它事情,但用户进程需要经常询问IO操作是否完成,这样造成不必要的CPU资源浪费;
异步非阻塞IO(AIO):用户进程发起一个IO操作然后,立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知。类比Future模式。
先来个例子理解一下概念,以银行取款为例:
- 同步 : 自己亲自出马持银行卡到银行取钱(使用同步IO时,Java自己处理IO读写)。
- 异步 : 委托一小弟拿银行卡到银行取钱,然后给你(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS(银行卡和密码),OS需要支持异步IO操作API)。
- 阻塞 : ATM排队取款,你只能等待(使用阻塞IO时,Java调用会一直阻塞到读写完成才返回)。
- 非阻塞 : 柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理,没到号你就不能去,你可以不断问大堂经理排到了没有,大堂经理如果说还没到你就不能去(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器会通知可读写时再继续进行读写,不断循环直到读写完成)。
BIO
定义:BIO 全称Block-IO 是一种阻塞同步的通信模式。我们常说的Stock IO 一般指的是BIO。是一个比较传统的通信方式,模式简单,使用方便。但并发处理能力低,通信耗时,依赖网速。
BIO 设计原理:
服务器通过一个 Acceptor 线程负责监听客户端请求和为每个客户端创建一个新的线程进行链路处理。典型的一请求一应答模式。若客户端数量增多,频繁地创建和销毁线程会给服务器打开很大的压力。后改良为用线程池的方式代替新增线程,被称为伪异步IO。
服务器提供IP地址和监听的端口,客户端通过TCP的三次握手与服务器连接,连接成功后,双放才能通过套接字(Stock)通信。
小结:
BIO模型中通过 Socket 和 ServerSocket 完成套接字通道的实现。阻塞,同步,建立连接耗时。
为了改进这种一连接一线程的模型,我们可以使用线程池来管理这些线程(需要了解更多请参考前面提供的文章),实现1个或多个线程处理N个客户端的模型(但是底层还是使用的同步阻塞I/O),通常被称为“伪异步I/O模型“。
实现很简单,我们只需要将新建线程的地方,交给线程池管理即可。
我们知道,如果使用 CachedThreadPool 线程池(不限制线程数量,如果不清楚请参考文首提供的文章),其实除了能自动帮我们管理线程(复用),看起来也就像是1:1的客户端:线程数模型,而使用 FixedThreadPool 我们就有效的控制了线程的最大数量,保证了系统有限的资源的控制,实现了N:M的伪异步 I/O 模型。
但是,正因为限制了线程数量,如果发生大量并发请求,超过最大数量的线程就只能等待,直到线程池中的有空闲的线程可以被复用。而对 Socket 的输入流就行读取时,会一直阻塞,直到发生:
- 有数据可读
- 可用数据以及读取完毕
- 发生空指针或 I/O 异常
所以在读取数据较慢时(比如数据量大、网络传输慢等),大量并发的情况下,其他接入的消息,只能一直等待,这就是最大的弊端。
而后面即将介绍的NIO,就能解决这个难题。
NIO
NIO(官方:New IO),也叫Non-Block IO 是一种同步非阻塞的通信模式。
NIO 设计原理:
NIO相对于BIO来说一大进步。客户端和服务器之间通过Channel通信。NIO可以在Channel进行读写操作。这些Channel都会被注册在Selector多路复用器上。Selector通过一个线程不停的轮询这些Channel。找出已经准备就绪的Channel执行IO操作。
NIO 通过一个线程轮询,实现千万个客户端的请求,这就是非阻塞NIO的特点。
1)缓冲区Buffer:它是NIO与BIO的一个重要区别。BIO是将数据直接写入或读取到Stream对象中。而NIO的数据操作都是在缓冲区中进行的。缓冲区实际上是一个数组。Buffer最常见的类型是ByteBuffer,另外还有CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer。
2)通道Channel:和流不同,通道是双向的。NIO可以通过Channel进行数据的读,写和同时读写操作。通道分为两大类:一类是网络读写(SelectableChannel),一类是用于文件操作(FileChannel),我们使用的SocketChannel和ServerSocketChannel都是SelectableChannel的子类。
3)多路复用器Selector:NIO编程的基础。多路复用器提供选择已经就绪的任务的能力。就是Selector会不断地轮询注册在其上的通道(Channel),如果某个通道处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以取得就绪的Channel集合,从而进行后续的IO操作。服务器端只要提供一个线程负责Selector的轮询,就可以接入成千上万个客户端,这就是JDK NIO库的巨大进步。
小结:NIO模型中通过SocketChannel和ServerSocketChannel完成套接字通道的实现。非阻塞/阻塞,同步,避免TCP建立连接使用三次握手带来的开销。
AIO (NIO.2)
- 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理.
- AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
AIO 并没有采用NIO的多路复用器,而是使用异步通道的概念。其read,write方法的返回类型都是Future对象。而Future模型是异步的,其核心思想是:去主函数等待时间。
小结:AIO模型中通过AsynchronousSocketChannel和AsynchronousServerSocketChannel完成套接字通道的实现。非阻塞,异步。
总结
- BIO模型中通过Socket和ServerSocket完成套接字通道实现。阻塞,同步,连接耗时。
- NIO模型中通过SocketChannel和ServerSocketChannel完成套接字通道实现。非阻塞/阻塞,同步,避免TCP建立连接使用三次握手带来的开销。
- AIO模型中通过AsynchronousSocketChannel和AsynchronousServerSocketChannel完成套接字通道实现。非阻塞,异步。
另外,I/O属于底层操作,需要操作系统支持,并发也需要操作系统的支持,所以性能方面不同操作系统差异会比较明显。
参考:
- Java BIO、NIO、AIO 学习-力量来源于赤诚的爱!-51CTO博客
- Netty序章之BIO NIO AIO演变 - JavaEE教程 - SegmentFault 思否
- Java 网络IO编程总结(BIO、NIO、AIO均含完整实例代码) - CSDN博客
- Java IO Tutorial
BIO,NIO,AIO区别
- BIO(同步阻塞):客户端和服务器连接需要三次握手,使用简单,但吞吐量小
- NIO(同步非阻塞):客户端与服务器通过Channel连接,采用多路复用器轮询注册的Channel。提高吞吐量和可靠性。
- AIO(异步非阻塞):NIO的升级版,采用异步通道实现异步通信,其read和write方法均是异步方法。