企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
[TOC] > # 本文关键字 > 1. 类、接口、内部类、静态内部类   > 2. java泛型 > 3. 反射机制 > 4. JAVA垃圾回收机制,java弱引用,软引用,强引用,虚引用 > 5. IO操作 # 1.类与对象 ## 1\. 一些概念 **类的概念:**按照我的理解,与我们日常生活中的类别是相似的,比如人类,蔬菜类,水果类,猫类,狗类等等,虽然比喻不是很恰当,但是却也很形象!总结来说,**类是一类有相似特征的事物的抽象**。他们有一些相同的行为和属性。在Java中,类是对数据、方法的封装,是面向对象编程的体现,提高了数据的封装性和复用性。 **类的组成:**概括来说是由**属性(域、变量)和方法**组成的,当然属性和方法的细分,再次就不在细说了。 **类的分类:**在Java中以class修饰的内部类、静态内部类、匿名内部类、局部内部类、抽象类以及我们最常用的普通类(暂时就这么叫吧)。 **对象的概念:**对象是一个个是实例,比如说,她叫小红,那么小红就是人(Person)类的一个实例,小红就是Person类的一个**对象**,在自然界中对象是实实在在存在的,那么在Java中,**对象就是类的实例化**。**类是一个抽象的概念,对象是一个具体化的概念。** **接口的概念:**接口是一种特殊的类,用Interface来修饰。在Java的接口中,如果定义变量就是**静态常量(static final),**接口中的方法默认是公共的、抽象的,不能实现,接口与类最大的区别**不可被实例化** ## 2\. 抽象类和接口 1. * **抽象类** * **不能被实例化**,但可以有构造函数,用于在子类构造函数运行时使用 * 用关键字 **abstract class** 来定义,跟普通类相似,可以定义任何形式的成员变量 * 抽象类中可以没有抽象方法(用abstract修饰的方法,没有方法体),但有抽象方法的类,必须定义为抽象类 * 方法的访问修饰符没有限制,有private,protected,public 和默认的(不写) * 一个类只能继承一个抽象类 ``` public abstract class AbstractClassExample { protected int x; private int y; public abstract void func1(); public void func2() { System.out.println("func2"); } } public class AbstractExtendClassExample extends AbstractClassExample { @Override public void func1() { System.out.println("func1"); } } // AbstractClassExample ac1 = new AbstractClassExample(); // 'AbstractClassExample' is abstract; cannot be instantiated AbstractClassExample ac2 = new AbstractExtendClassExample(); ac2.func1(); ``` * **接口** * **不能被实例化**,没有构造函数 * 用关键字 **interface** 来定义,定义的成员变量全都是**静态常量** * 接口中全都是抽象方法,但不需要写abstract关键字,默认访问修饰符是public(还可以是protected),可以不写,但不写的接口在实现该接口的类上重写该方法是必须加上public关键字 * 接口是类功能的拓展,是对具有相同特征的不同类的功能的一种封装 * 在Java中一个类只能继承一个父类,但可以实现多个接口,本类必须实现所有接口中未实现的的方法,否则该类需要被声明为抽象类 * 接口允许多继承(仅限于接口) * 一个类可以实现多个接口 * *jdk1.8之后,接口中可以定义default 方法和 static 方法,需要有具体的方法体实现* * **抽象类和接口的异同** ![抽象类和接口的区别.png](https://upload-images.jianshu.io/upload_images/13971762-f43ecc41f84cb116.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) * **如何选择?** * **使用接口** * 1. 需要使不相关的类都实现同一个方法 2. 需要实现多继承的关系 * **使用抽象类** 1. 父类和子类确实有继承关系,抽象类的方法在其他非继承关系的类中不需要 2. 需要能够控制子类的访问权限,而不是都是public 3. 需要继承非静态和非常量的字段 在很多情况下,接口优先于抽象类。因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。 有关抽象类和接口的概念可以参考:[https://www.ibm.com/developerworks/cn/java/l-javainterface-abstract/](https://www.ibm.com/developerworks/cn/java/l-javainterface-abstract/ "https://www.ibm.com/developerworks/cn/java/l-javainterface-abstract/") ## 3\. 内部类 Java中内部类分为四种: * **成员内部类**:在一个类中,显式定义的一个类,可以用public修饰(但同一个Java文件中,同一个层次级别的class,只能有一个用public修饰),**需要有外部类对象才能实例化内部类对象。** * **静态内部类:**用static修饰的内部类,与成员内部类最大的区别在于实例化的时候,不需要外部类实例化对象就可获取到内部类对象,即直接使用new OutterClassName.InnerClassName( ) ``` public class Outter { //成员内部类 public class InnerClass{ ... } //静态内部类 public static class StaticInnerClass { ... } public static void main(String[] args) { //成员内部类实例化方法1 // Parent parent = new Parent(); // InnerClass inner1=parent.new InnerClass(); //成员内部类实例化方法2 InnerClass inner1=new Outter().new InnerClass(); //静态内部类实例化1 StaticInnerClass inner2=new Outter.StaticInnerClass(); //静态内部类实例化2 StaticInnerClass inner2=new StaticInnerClass(); } } ``` * **匿名内部类:**就是没有名字的内部类。不能是抽象类,必须实现接口或抽象父类的所有抽象方法。 * **局部内部类:**在某个方法内部定义的一个类,有效性只在本方法内。 有关内部类的介绍还可以参考这篇文章: [http://www.cnblogs.com/shen-hua/p/5440285.html](http://www.cnblogs.com/shen-hua/p/5440285.html "http://www.cnblogs.com/shen-hua/p/5440285.html") ## 4\. 枚举 # 2\. Java泛型 ## 1\. 简介 *(**来自***[***百度百科***](https://baike.baidu.com/item/java%E6%B3%9B%E5%9E%8B/511821?fr=aladdin "百度百科")*)*泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为**泛型类、泛型接口、泛型方法**。 Java语言引入泛型的好处是安全简单。在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。**泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率**。**泛型只在编译期有效!** ***注释:** 类型变量使用大写形式, 且比较短, 这是很常见的。 在 Java 库中, 使用变量 E 表示集合的元素类型, K 和 V 分别表示表的关键字与值的类型。T(需要时还可以用临近的字母U和S)表示“任意类型* ” ## 2\. 泛型的分类 * **泛型类** ``` //此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型 public class Generic<T>{ //key这个成员变量的类型为T,T的类型由外部指定 private T key; public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定 this.key = key; } public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定 return key; } } ``` 定义的泛型类,就一定要传入泛型类型实参么?并不是这样,在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制,此时泛型才会起到本应起到的限制作用。如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。 * **泛型接口** 泛型接口与泛型类的定义及使用基本相同 ``` //定义一个泛型接口 public interface Generator<T> { public T next(); } ``` * **泛型方法** 泛型方法可以定义在泛型类中也可以定义在普通类中 ![泛型方法.png](https://upload-images.jianshu.io/upload_images/13971762-dd6ef42d0e0ad9ce.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ``` public class GenericTest { //这个类是个泛型类 public class Generic<T> { private T key; public Generic(T key) { this.key = key; } //该方法使用了泛型,但是这并不是一个泛型方法。 //这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。 //所以在这个方法中才可以继续使用 T 这个泛型。 public T getKey() { return key; } /** * 这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol E" * 因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别。 public E setKey(E key){ this.key = keu } */ } /* --------------------------------泛型类定义结束-------------------------------------- */ /** * 这才是一个真正的泛型方法。 * 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T * 这个T可以出现在这个泛型方法的任意位置. * 泛型的数量也可以为任意多个 * 如:public <T,K> K showKeyName(Generic<T> container){ * ... * } */ public <T> T showKeyName(Generic<T> container) { System.out.println("container key :" + container.getKey()); //当然这个例子举的不太合适,只是为了说明泛型方法的特性。 T test = container.getKey(); return test; } //这也不是一个泛型方法,这就是一个普通的方法,只是使用了Generic<Number>这个泛型类做形参而已。 public void showKeyValue1(Generic<Number> obj) { Log.d("泛型测试", "key value is " + obj.getKey()); } //这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符? //同时这也印证了泛型通配符章节所描述的,?是一种类型实参,可以看做为Number等所有类的父类 public void showKeyValue2(Generic<?> obj) { Log.d("泛型测试", "key value is " + obj.getKey()); } /** * 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' " * 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法。 * 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。 public <T> T showKeyName(Generic<E> container){ ... } */ /** * 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class 'T' " * 对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。 * 所以这也不是一个正确的泛型方法声明。 * public void showkey(T genericObj){ * <p> * } */ } ``` ## 3\. 泛型的限定 * ``` //这样类中的泛型T只能是Collection接口的实现类,传入非Collection接口编译会出错 class GenericsFoo<T extends Collection>{ ... } ``` **注意:**<T extends Collection>这里的限定使用[关键字](https://baike.baidu.com/item/%E5%85%B3%E9%94%AE%E5%AD%97 "关键字")extends,后面可以是类也可以是接口。但这里的extends已经不是继承的含义了,应该理解为T类型是实现Collection接口的类型,或者T是继承了XX类的类型。虽然Java泛型简单的用 extends 统一的表示了原有的 extends 和 implements 的概念,但仍要遵循应用的体系,Java 只能继承一个类,但可以实现多个接口,所以你的某个类型需要用 extends 限定,且有多种类型的时候,只能存在一个是类,并且类写在第一位,接口列在后面,也就是: ``` <T extends SomeClass & interface1 & interface2 & interface3> ``` * 示例 ``` //定义一个泛型类,泛型的类型只能是Collection的子类 //问题:我们该如何去实例化这个类呢? public class CollectionGenFoo<T extends Collection> { private T x; public CollectionGenFoo(T x) { this.x = x; } public T getX() { return x; } public void setX(T x) { this.x = x; } public static void main(String args[]) { //这样实例化可以吗? CollectionGenFoo<Collection> listFoo1 = null; listFoo1=new CollectionGenFoo<ArrayList>(new ArrayList()); } } ``` 很遗憾,这样的定义是**不可以**的!我们必须要把两个地方写的是一样的才可以也就是这样的 ``` CollectionGenFoo<Collection> listFoo1 = null; listFoo1=new CollectionGenFoo<Collection>(new ArrayList()); //或者 CollectionGenFoo<ArrayList> listFoo1 = null; listFoo1=new CollectionGenFoo<ArrayList>(new ArrayList()); ``` 那当我们有了上方的需求怎么办呢?Java中引入了通配符的概念,下面简单介绍一下通配符泛型 ## 4\. 通配符泛型 * 直接上代码: ``` CollectionGenFoo<?> listFoo1 = null; listFoo1=new CollectionGenFoo<ArrayList>(new ArrayList()); ``` 为了解决类型被限制死了不能动态根据实例来确定的缺点,引入了“通配符泛型”,针对上面的例子,使用通配泛型格式为<? extends Collection>,“?”代表未知类型,这个类型是实现Collection接口的 ## 5\. 类型擦除 由于虚拟机中没有泛型类型对象—所有对象都属于普通类,所以在代码编译后,泛型将被擦除,如果调用时,限定了泛型,则就以限定的类型进行替换,如果没有限定泛型类型,则以Object进行替换。 ## 6\. 注意事项 * **泛型只在编译期间有效** * 所有的泛型必须是类(引用)类型,包含自定义类,不能是基本数据类型 * 泛型方法在声明的时候会声明泛型,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型 * 如果只指定了<?>,而没有extends,则默认是允许Object及其下的任何Java类了。也就是任意类 * 通配符泛型不单可以向上限制,如<? extends Collection>,还可以向下限制,如<? super Double>,表示类型只能接受Double及其上层父类类型,如Number、Object类型的实例 * 在 Java SE 7 及以后的版本中, 构造函数中可以省略泛型类型:**ArrayList<String> files = new ArrayList<>();**省略的类型可以从变量的类型推断得出。 参考学习文档:[https://www.cnblogs.com/coprince/p/8603492.html](https://www.cnblogs.com/coprince/p/8603492.html "https://www.cnblogs.com/coprince/p/8603492.html") # 3、Java垃圾回收机制 ## 1\. 一些概念: 1. 意义:可以有效的防止内存泄露,有效的使用空闲的内存 2. 内存泄漏:在Java中指的是对象使用完之后,没有被回收,但该对象也没有用了,导致内存空间被浪费,该对象被称为“游离对象” 3. 内存溢出:在Java对象创建过程中,剩下的内存空间小于对象所需要的内存空间 ## 2\. Java的四种引用类型 1. **强引用(StrongReference)** 在Java代码中,强引用随处可见,我们平常的赋值语句就属于强引用,如: ``` Object object = new Object();  String str = “hello”;  ``` 代码测试: ![BigObject.png](https://upload-images.jianshu.io/upload_images/13971762-ef104b29dc2a3098.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![强引用测试.png](https://upload-images.jianshu.io/upload_images/13971762-120d582f3a531812.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![报错信息.png](https://upload-images.jianshu.io/upload_images/13971762-791ebe313564932d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) Java的垃圾回收机制宁愿抛出OOM(Out Of Memory)内存溢出的异常,也不愿意回收此类对象 2. **软引用(SoftReference)** 当内存足够使用时,垃圾回收机制是不会去主动回收此类对象的 **用途:缓存** ![软引用测试.png](https://upload-images.jianshu.io/upload_images/13971762-52b95055573fdf0f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![报错信息.png](https://upload-images.jianshu.io/upload_images/13971762-349e73f7b1f1ac57.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 通过运行结果可以看出,打印出了最后一个对象,但是之前的对象却是Null,表明之前的对象已经被垃圾回收机制回收了 3. **弱引用(WeakReference)** 不管内存是否充足,只要垃圾回收机制发现了弱引用对象,就会将其回收 **用途:用于解决内存泄漏的问题** 4. **虚引用(PhantomReference)** 虚引用与其它几种引用都不同,虚引用并不会决定对象的生命周期.如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,虚引用主要用来跟踪对象被垃圾回收器回收的活动.虚引用必须和引用队列 (ReferenceQueue)联合使用. 垃圾回收器回收对象时,该对象还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。 # 3\. 什么样的对象会被垃圾回收机制回收? > 简单来说,垃圾回收机制会回收那些没有被引用的对象。如:垃圾回收机制使用了一种引用计数器的算法,在对象被创建时给对象分配一个计数器变量,数值设为1,当任何其它变量被赋值为这个对象的引用时,计数加1(如a = b,则b引用的对象实例的计数器+1),当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1,垃圾回收机制会回收所有的引用计数器为0的变量 # 4、Java的IO操作 ![IO流.png](https://upload-images.jianshu.io/upload_images/13971762-12ff6f890cc5eb3f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 由上述图片可知,Java中的IO流分为两大部分: 按照处理的类型分为:字符流和字节流 在使用过程中,除了处理字符串或文本类型时,优先使用字符流,其他所有的类型都使用字节流 按照数据流的方向分为:输入流(InputStream和Reader)和输出流(OutputStream和Writer) 输入流只能进行读操作,输出流只能进行写操作 > 参考资料:[http://www.runoob.com/java/java-files-io.html](http://www.runoob.com/java/java-files-io.html "http://www.runoob.com/java/java-files-io.html")