面向对象编程
文章目录
第一章:面向对象
一、什么是面向对象
面向对象是一种使用封装、继承、多态、抽象等思想进行软件的分析和开发的方法,而Java就是一门面向对象编程的语言。
面向对象有三大特性:封装、继承、多态
二、封装
封装就是将实体的属性和行为包装成一个具体的对象,并控制在程序中对其属性的读取、修改。并仅对外公开接口也就是方法,以隐藏对象的属性和实现细节的目的。
三、继承
1.继承
继承就是从已有的类和接口中派生出一个新的类和接口,并根据继承规则能从父类和接口中吸收一些属性和行为作为己用,还可以能通过自定义一些自己的属性和方法来扩展新的能力。
Java中的类只能有一个父类,也就是说类不支持多继承机制,默认情况下java.lang.Object是一个类的直接父类;但是接口则可以继承多个父级接口,支持多继承。
2.继承的限制
Java只允许一个class继承自一个类。继承时,子类无法访问父类的private字段或private方法。为了让子类可以访问父类的字段,我们需要把private改为protected。protected关键字可以把字段和方法的访问权限控制在继承树内部。
3.继承的实现
Java使用extends关键字来实现继承。在继承类中super关键字表示父类(超类)。如果父类没有默认的构造方法,子类就必须显式调用super()并给出参数以便让编译器定位到父类的一个合适的构造方法。
子类不会继承任何父类的构造方法,子类默认的构造方法是编译器自动生成的。
4.继承类的转型
向上转型:把一个子类类型安全地变为父类类型的赋值。
向下转型:和向上转型相反。
四、多态
1.多态
多态分为编译时多态和运行时多态。
编译时多态是指编译器编译期间,根据同一对象或者类中相同名称方法的参数列表的不同(函数签名不同),来决定调用那个方法的多态行为。也就是 指的是Java中的方法重载。
运行时多态是指程序运行期间,同一方法会根据调用对象的类型不同,分别去执行其实际的类型的相应方法。一般多发生在方法覆盖的情况下。
动态绑定是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
2.instanceof关键字
向下转型并不是安全的,因此在向下转型之前应该判断。判断某个实例是否是某个类的对象,这个就需要通过instanceof语法判断。
对象A instanceof 对象B
五、抽象
抽象就是通过特定的实例抽取共同特性后形成概念的过程。它强调主要特征,忽略次要特征。在Java中主要体现在类。
第二章:类与对象
一、对象内存分析
堆内存:保存的是对象的具体信息在程序之中堆内存空间的开辟是通过new关键字。
栈内存:保存的是一块堆内存的地址,即:通过地址找到堆内存,而后找到
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0OM6rmTI-1588727567658)(./Java面向对象编程.assets/深度截图_选择区域_20200225101212.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JzWoymLR-1588727567662)(./Java面向对象编程.assets/深度截图_选择区域_20200225101212.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C29EGzva-1588727567667)(./Java面向对象编程.assets/深度截图_选择区域_20200225101542.png)]
对象实例化的两种语法:一、声明并实例化对象;二、、声明和实例化分开
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NF5k8zFH-1588727567671)(./Java面向对象编程.assets/深度截图_选择区域_20200225101942.png)]
所有的类方法在声明实例化之后才能调用。对象必须在声明实例化之后才能使用。
二、对象引用分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0LdIowFx-1588727567676)(./Java面向对象编程.assets/深度截图_选择区域_20200225102642.png)]
类方法实现引用传递:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NNCXczBY-1588727567680)(./Java面向对象编程.assets/深度截图_选择区域_20200225103311.png)]
三、引用与垃圾产生分析
垃圾:没有被栈内存指向的堆内存。
四、构造方法与匿名对象
- 构造方法名称必须与类名称保持一致
- 构造方法不允许设置任何的返回值类型
- 构造方法是使用关键字new实例化对象的时候自动调用的
为什么构造方法不允许设置返回值:
构造方法没有返回值为什么不用void定义:
程序编译器是根据代码结构来进行编译处理的,执行的时候也是根据代码结构处理的。
五、访问修饰符
修饰符 | 当前类 | 同包 | 子类 | 其他包 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default | √ | √ | × | × |
private | √ | × | × | × |
六、this和super的区别
Java中使用super来引用父类的成分,使用this来引用当前对象的成分。
- super(参数):调用基类中的某一个构造函数(应该为构造函数中的第一条语句)
- this(参数):调用本类中另一种形成的构造函数(应该为构造函数中的第一条语句)
- super: 它引用当前对象的直接父类中的成员(用来访问直接父类中被隐藏的父类中成员数据或函数)
- this:它代表当前对象名(在程序中易产生二义性之处,应使用this来指明当前对象;如果函数的形参与类中的成员数据同名,这时需用this来指明成员变量名)
- 调用super()必须写在子类构造方法的第一行,否则编译不通过。每个子类构造方法的第一条语句,都是隐含地调用super(),如果父类没有这种形式的构造函数,那么在编译的时候就会报错。
- super()和this()类似,区别是,super()从子类中调用父类的构造方法,this()在同一类内调用其它方法。
- super()和this()均需放在构造方法内第一行。
- 尽管可以用this调用一个构造器,但却不能调用两个。
- this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
- this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块。
- 从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。
第三章:static关键字
一、声明static属性
static修饰公共属性,非static属性必须在实例化对象产生之后才可以使用,而static属性可以在没有实例化对象可以通过类名访问。
二、声明static方法
static方法的主要特点在于,其可以不用实例化对象直接访问。
static中不能使用this。
static方法不能调用非static方法。
三、代码块
1.局部代码块
定义:定义在方法内部,用{}包裹。
作用:限定局部变量的生命周期,提早释放,提高内存利用率。
2.构造代码块
定义:定义在类中方法外,用{}包裹。
作用:在多个构造方法中有相同的语句时,可以将这些相同的语句放在构造代码块中。
执行特点:构造代码块是随着构造方法的执行而执行,但是先于构造方法执行。
3.静态代码块
定义:定义在类中方法外,用static {}包裹
作用:使用静态代码块的场景通常是使用静态代码块的执行特点。
执行特点:静态代码块只能被执行一次。随着类的加载而执行。
4.代码块和构造方法的执行顺序
静态代码快>构造代码块>构造方法
第四章:数组
1.数组的基本定义
- 数组的动态初始化
- 数组类型 数组名称[] = new 数组类型 [长度]
- 数组类型 [] 数组名称 = new 数组类型 [长度]
- 数组的静态初始化
- 数组类型 数组名称[] = new 数组类型 {数据1,数据2,…,数据n}
- 数组类型 数组名称[] = {数据1,数据2,…,数据n}
数组只有实例化对象之后才能使用,不然会报错。
2.foreach输出
语法格式:
for(数据类型 变量:数组|集合){}
避免使用数组下表使用数组数据。
3.数组与方法
4.数组排序案例分析
5.数组转置案例分析
6.方法可变参数
public static int sum(int ... data){}
可变参数的本质依然属于数组。
第五章:Annotation注解
一、注解语法
注解就是一种标签。
1.1 注解定义
public @interface TestAnnotation{}
1.2 注解的应用
@TestAnnotation
public class Test{}
二、元注解
元注解:可以注解到注解上的注解,基本注解。
元注解有:@Retention、@Documented、@Target、@Inherited、@Repeatable
2.1 @Retention
解释说明这个注解的存活时间。
它的取值如下:
- RetentionPolicy.SOURCE:注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
- RetentionPolicy.CLASS:注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
- RetentionPolicy.RUNTIME:注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
2.2 @Documented
将注解中的元素包含到JavaDoc中。
2.3 @Target
限定注解的运用场景。
- ElementType.ANNOTATION_TYPE:可以给一个注解进行注解
- ElementType.CONSTRUCTOR:可以给构造方法进行注解
- ElementType.FIELD:可以给属性进行注解
- ElementType.LOCAL_VARIABLE:可以给局部变量进行注解
- ElementType.METHOD:可以给方法进行注解
- ElementType.PACKAGE:可以给一个包进行注解
- ElementType.PARAMETER:可以给一个方法内的参数进行注解
- ElementType.TYPE:可以给一个类型进行注解,比如类、接口、枚举
2.4 @Inherited
Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。
如果一个超类被一个使用@Inherited注解过的注解注释之后,它的子类也同样被这个注解注释。
2.5 @Repeatable
Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。
@interface Persons{
Person[] value();
}
@Repeatable(Persons.Class)
/**
* @Repeatable 注解了 Person。而 @Repeatable 后面括号中的类相当于一个容器注解。
*
* @interface Persons {
* Person[] value();
* }
* 按照规定容器注解中,必须要有一个value的属性,属性类型是一个被 @Repeatable 注解过的注解数组,注意它是数组。
*
*/
@interface Person{
String role default "";
}
@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{
}
三、注解的属性
1.定义
注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
int id();
String msg();
}
2.赋值
赋值的方式是在注解的括号内以 value=”” 形式,多个属性之前用 ,隔开。
@TestAnnotation(id=3,msg="hello annotation")
public class Test {
}
需要注意的是,在注解中定义属性时它的类型必须是 8 种基本数据类型外加类、接口、注解及它们的数组。
注解中属性可以有默认值,默认值需要用 default 关键值指定。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
public int id() default -1;
public String msg() default "Hi";
}
如果一个注解内仅仅只有一个名字为 value 的属性时,应用这个注解时可以直接接属性值填写到括号内。
public @interface Check {
String value();
}
@Check("hi") // 可直接应用
int a;
@Check(value="hi") // 与上面的效果相同
int a;
一个注解没有任何属性,那么在应用这个注解的时候,括号都可以省略。
public @interface Perform {}
@Perform // 省略括号
public void testMethod(){}
四、Java预置的注解
1.@Deprecated
标记过时的元素。
2.@Override
方法的覆写
3.@SuppressWarnings
阻止警告。
4.@SafeVarargs
参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。
5.@FunctionalInterface
函数式接口注解。
五、注解的提取
1.注解与反射
- 注解通过反射获取,通过Class对象的isAnnotationPersent()方法判断它是否应用了某个注解
- 通过getAnnotation()方法获取Annotation对象
- 通过getAnnotations()方法获取Annotation对象数组
2.注解的使用场景
当开发者使用了Annotation 修饰了类、方法、Field 等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。
现在,我们可以给自己答案了,注解有什么用?给谁用?给编译器或者APT用的。
第六章:抽象类的定义与使用
一、抽象类基本概念
在实际开发中我们很少继承一个已经完善的类,而是必须要继承抽象类(父类强制子类必须覆写某个方法)。
抽象类的基本定义
抽象类的主要作用在于对子类中覆写方法进行约定,抽象类必须使用abstract关键字定义。(在普通类的基础上追加抽象方法就是抽象类)抽象类是不能直接使用的。
使用抽象类的条件:
- 抽象类必须提供有子类,子类使用extends继承一个抽象类;
- 抽象类的子类(不是抽象类)一定要覆写抽象类中的全部抽象方法;
- 抽象类的对象实例化可以利用对象多态性通过子类向上转型的方式完成;
对于抽象类使用的几点意见:
- 抽象类使用很大程度上有一个核心的问题:抽象类自己无法直接实例化;
- 抽象类之中主要的目的是进行过渡操作使用,所以当你要使用抽象类进行开发的时候,往往都是在你设计中所要解绝继承问题所带来的代码重复的问题。
二、抽象类的相关说明
- 抽象类中不能出现final关键字
- 抽象类可以提供有构造方法,并且子类也一定会按照子类对象的实例化原则进行父构造调用
- 抽象类中允许没有抽象方法,即使如此也无法直接实例化
- 抽象类中可以提供有static方法,并且不受抽象类无法实例化约束
三、模板设计模式
抽象类最大的好处一是对子类方法的统一管理,二是可以自身提供一些普通方法,并且普通方法可以调用抽象方法(必须在有子类提供实现的时候才会生效)。
抽象类应该抽象行为。
第七章:包装类
1.包装类实现原理分析
Object类是所有类的父类,Object类可以接收所有的数据类型。但是基本数据类型并不是一个类,所以现在如果要想将基本数据类型以类的形式处理就要对它进行包装。
Java中的包装类:
- 对象型包装类
- 数值型包装类
2.装箱与拆箱
装箱:将基本数据类型保存在包装了类之中
拆箱:从包装类对象中获取基本数据类型
第八章:接口
一、接口基本定义
接口定义语法:
interface IPerson{}
由于接口的定义与类的定义方法相同,所以接口名首字母应为I(interface)。
接口肯定无法直接产生实例化对象,对于接口的实现的注意事项:
- 接口需要被子类实现(implements),一个子类可以实现多个父接口;
- 子类(如果不是抽象类)那么一定要覆写接口之中的全部抽象方法;
- 接口对象可以利用子类对象的向上转型进行实例化
二、接口定义加强
接口中可以有普通方法,接口中的普通方法必须追加default的声明。
除了追加普通方法,接口中也可以添加static方法。
三、使用接口定义标准
接口开发最重要的应用就是进行标准的制定。
第九章:泛型
一、概述
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
二、特性
泛型只在编译阶段有效。
三、泛型的使用
1.泛型类
class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
private 泛型标识 /*(成员变量类型)*/ var;
.....
}
}
一个最普通的泛型类
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
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;
}
}
Notes:
-
泛型的类型参数只能是类类型,不能是简单类型。
-
不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错。
if(ex_num instanceof Generic<Number>){}
2.泛型接口
- 定义一个泛型接口
//定义一个泛型接口
public interface Generator<T> {
public T next();
}
- 当实现泛型接口的类,未传入泛型实参时
/**
* 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
* 即:class FruitGenerator<T> implements Generator<T>{
* 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class"
*/
class FruitGenerator<T> implements Generator<T>{
@Override
public T next() {
return null;
}
}
- 当实现泛型接口的类,传入泛型实参时
/**
* 传入泛型实参时:
* 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator<T>
* 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。
* 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
* 即:Generator<T>,public T next();中的的T都要替换成传入的String类型。
*/
public class FruitGenerator implements Generator<String> {
private String[] fruits = new String[]{"Apple", "Banana", "Pear"};
@Override
public String next() {
Random rand = new Random();
return fruits[rand.nextInt(3)];
}
}
3.泛型方法
泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。
- 基本定义
/**
* 泛型方法的基本介绍
* @param tClass 传入的泛型实参
* @return T 返回值为T类型
* 说明:
* 1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
* 2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
* 3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
* 4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
*/
public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
IllegalAccessException{
T instance = tClass.newInstance();
return instance;
}
- 泛型方法的基本用法
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){
}
*/
public static void main(String[] args) {
}
}
- 类中的泛型方法
public class GenericFruit {
class Fruit{
@Override
public String toString() {
return "fruit";
}
}
class Apple extends Fruit{
@Override
public String toString() {
return "apple";
}
}
class Person{
@Override
public String toString() {
return "Person";
}
}
class GenerateTest<T>{
public void show_1(T t){
System.out.println(t.toString());
}
//在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
//由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
public <E> void show_3(E t){
System.out.println(t.toString());
}
//在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
public <T> void show_2(T t){
System.out.println(t.toString());
}
}
public static void main(String[] args) {
Apple apple = new Apple();
Person person = new Person();
GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();
//apple是Fruit的子类,所以这里可以
generateTest.show_1(apple);
//编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person
//generateTest.show_1(person);
//使用这两个方法都可以成功
generateTest.show_2(apple);
generateTest.show_2(person);
//使用这两个方法也都可以成功
generateTest.show_3(apple);
generateTest.show_3(person);
}
}
- 泛型方法与可变参数
public <T> void printMsg( T... args){
for(T t : args){
Log.d("泛型测试","t is " + t);
}
}
- 静态方法与泛型
静态方法有一种情况需要注意一下,那就是在类中的静态方法使用泛型:静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。
public class StaticGenerator<T> {
....
....
/**
* 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
* 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
* 如:public static void show(T t){..},此时编译器会提示错误信息:
"StaticGenerator cannot be refrenced from static context"
*/
public static <T> void show(T t){
}
}
4.泛型通配符
类型通配符一般是使用?代替具体的类型实参,注意了,此处’?’是类型实参,而不是类型形参*。
可以解决当具体类型不确定的时候,这个通配符就是 ? ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。
四、泛型上下边界
在使用泛型的时候,我们还可以为传入的泛型类型实参进行上下边界的限制。
1.为泛型添加上边界
即传入的类型实参必须是指定类型的子类
public void showKeyValue1(Generic<? extends Number> obj){ // ?extends 类:设置泛型的上限
Log.d("泛型测试","key value is " + obj.getKey());
}
2.为泛型添加下边界
public void showKeyValue1(Generic<? super String> obj){ // ?super 类:设置泛型的下限
Log.d("泛型测试","key value is " + obj.getKey());
}
3.泛型数组
在Java中“不能创建一个确切的泛型类型的数组”的。
以下例子是不可以的:
List<String>[] ls = new ArrayList<String>[10];
而使用通配符创建泛型数组是可以的:
List<?>[] ls = new ArrayList<?>[10];
这样也是可以的:
List<String>[] ls = new ArrayList[10];
第十章:异常处理
一、异常的概念和Java异常体系结构
Java将异常当作对象来处理,java.lang.Throwable作为所有异常的超类。JavaAPI中定义了许多异常,这些异常分为两大类,错误(Error)和异常(Exception)。
1.Java异常体系结构
classDiagram
class Throwable
class Error
class Exception
class RuntimeException
class 非运行时异常
Throwable <|-- Error
Throwable <|-- Exception
Exception <|-- RuntimeException
Exception <|-- 非运行时异常
Throwable类是所有异常和错误的超类,其中异常(Exception)有分为运行时异常(RuntimeException)和非运行时异常,这两种异常有称为不检查异常(UncheckedException)和检查异常(CheckedException)。
2.Error和Exception
Error是程序无法处理的错误,比如OutOfMemoryError、ThreadDeath等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。程序中应当尽可能去处理这些异常。
3.运行时异常和非运行时异常
运行时异常都是RuntimeException类及其子类异常,如NullPointerException、 IndexOutOfBoundsException等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
二、异常的捕获和处理
1.异常处理的基本语法
try{
//(尝试运行的)程序代码
}catch(异常类型 异常的变量名){
// 异常处理代码
}finally{
// 始终要执行的代码
}
catch语句可以有多个,用来匹配多个中的一个异常,一旦匹配上后,就不再尝试匹配别的catch块了。通过异常对象可以获取异常发生时完整的 JVM堆栈信息,以及异常信息和异常发生的原因等。
2.try、catch、finally三个语句块应注意的问题
- try、catch、finally三个语句块均不能单独使用,三者可以组成 try…catch…finally、try…catch、try…finally三种结构,catch语句可以有一个或多个,finally语句最多一个。
- try、catch、finally三个代码块中变量的作用域为代码块内部,分别独立而不能相互访问。如果要在三个块中都可以访问,则需要将变量定义到这些块的外面。
- 多个catch块时候,只会匹配其中一个异常类并执行catch块代码,而不会再执行别的catch块,并且匹配catch语句的顺序是由上到下。
3.throw、throws关键字
throw关键字是用于方法体内部,用来抛出一个Throwable类型的异常。如果抛出了检查异常,则还应该在方法头部声明方法可能抛出的异常类型。该方法的调用者也必须检查处理抛出的异常。如果所有方法都层层上抛获取的异常,最终JVM会进行处理,处理也很简单,就是打印异常消息和堆栈信息。如果抛出 的是Error或RuntimeException,则该方法的调用者可选择处理该异常。
throws关键字用于方法体外部的方法声明部分,用来声明方法可能会抛出某些异常。仅当抛出了检查异常,该方法的调用者才必须处理或者重新抛出该异常。 当方法的调用者无力处理该异常的时候,应该继续抛出,而不是囫囵吞枣一般在catch块中打印一下堆栈信息做个勉强处理。
3.Throwable类中的常用方法
方法名 | 说明 |
---|---|
getCause() | 返回抛出异常的原因。如果 cause 不存在或未知,则返回 null。 |
getMessage() | 返回异常的消息信息。 |
printStackTrace() | 对象的堆栈跟踪输出至错误输出流,作为字段 system.err 的值。 |
三、异常处理的一般原则
- 能处理就早处理,抛出不去还不能处理的就想法消化掉或者转换为RuntimeException处理。
- 对于检查异常,如果不能行之有效的处理,还不如转换为RuntimeException抛出。这样也让上层的代码有选择的余地――可处理也可不处理。
- 对于一个应用系统来说,应该有自己的一套异常处理框架,这样当异常发生时,也能得到统一的处理风格,将优雅的异常信息反馈给用户。
四、异常转译与异常链
1.异常转译的原理
所谓的异常转译就是将一种异常转换另一种新的异常,也许这种新的异常更能准确表达程序发生异常。
在Java中有个概念就是异常原因,异常原因导致当前抛出异常的那个异常对象,几乎所有带异常原因的异常构造方法都使用Throwable类型做参数,这 也就为异常的转译提供了直接的支持,因为任何形式的异常和错误都是Throwable的子类。
2.异常链
异常链顾名思义就是将异常发生的原因一个传一个串起来,即把底层的异常信息传给上层,这样逐层抛出。异常链的实际应用很少,发生异常时候逐层上抛不是个好注意,上层拿到这些异常又能奈之何?而且异常逐层上抛会消耗大量资源,因为要保存一个完整的异常链信息。
第十一章:函数式编程
第十二章:Java设计模式
一、单例模式
单例模式的特点:
- 单例类只能有一个实例
- 单例类必须自己创建自己的唯一实例
- 单例类必须给所有其他对象提供这一实例
- 懒汉模式
public class Singleton{
private Singleton(){}
private static Singleton single = null;
public Singleton getInstence(){
if (single == null){
single = new Singleton();
}
return single;
}
}
懒汉单例类并没有考虑线程安全的问题,它是线程不安全的,并发环境下可能出现多个单例类。为了保证线程安全可采用以下三种方法:
- 在getInstence方法上加同步
public static synchronized Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
- 双重检查锁定
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
- 静态内部类
public class Singleton {
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
- 饿汉模式
public class Singleton{
private Singleton(){}
private static Singleton single = new Singleton();
public Singleton getInstence(){
return single;
}
}
饿汉模式在类创建的同时就已经创建好了一个静态的对象供系统使用,以不再改变,所以饿汉模式天生是线程安全的。
- 登记模式(可忽略)
二、工厂模式
1.工厂方法模式
1.1 普通工厂方法模式
创建一个工厂类,对实现了同一接口的一些类进行实例的创建。
interface Sender {
void Send();
}
class MailSender implements Sender {
@Override
public void Send() {
System.out.println("This is mail sender...");
}
}
class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("This is sms sender...");
}
}
public class FactoryPattern {
public static void main(String[] args) {
Sender sender = produce("mail");
sender.Send();
}
public static Sender produce(String str) {
if ("mail".equals(str)) {
return new MailSender();
} else if ("sms".equals(str)) {
return new SmsSender();
} else {
System.out.println("输入错误...");
return null;
}
}
}
1.2 多个工厂方法模式
是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。
interface Sender {
void Send();
}
class MailSender implements Sender {
@Override
public void Send() {
System.out.println("This is mail sender...");
}
}
class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("This is sms sender...");
}
}
class SendFactory {
public Sender produceMail() {
return new MailSender();
}
public Sender produceSms() {
return new SmsSender();
}
}
public class FactoryPattern {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produceMail();
sender.Send();
}
}
1.3 静态工厂方法模式
将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。
interface Sender {
void Send();
}
class MailSender implements Sender {
@Override
public void Send() {
System.out.println("This is mail sender...");
}
}
class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("This is sms sender...");
}
}
class SendFactory {
public static Sender produceMail() {
return new MailSender();
}
public static Sender produceSms() {
return new SmsSender();
}
}
public class FactoryPattern {
public static void main(String[] args) {
Sender sender = SendFactory.produceMail();
sender.Send();
}
}
2.抽象工厂模式
工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要扩展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?
那么这就用到了抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。
interface Provider {
Sender produce();
}
interface Sender {
void Send();
}
class MailSender implements Sender {
public void Send() {
System.out.println("This is mail sender...");
}
}
class SmsSender implements Sender {
public void Send() {
System.out.println("This is sms sender...");
}
}
class SendMailFactory implements Provider {
public Sender produce() {
return new MailSender();
}
}
class SendSmsFactory implements Provider {
public Sender produce() {
return new SmsSender();
}
}
public class FactoryPattern {
public static void main(String[] args) {
Provider provider = new SendMailFactory();
Sender sender = provider.produce();
sender.Send();
}
}
第十三章:常用工具类
一、Object类
1.包名
java.lang.Object
2.概述
Object类的主要特点是可以解决参数的统一问题,Object类可以接收所有的数据类型。
3.常用方法
方法名 | 描述 |
---|---|
public String toString() | 返回对象的描述 |
public boolean equals(Object obj) | 比较两个对象的内容 |
二、Scanner类
1.包名
java.util.Scanner
2.描述
java5之后添加了Scanner类用于扫描输入文本的实用程序。
3.常用方法
方法名 | 描述 |
---|---|
public String next() | 查找并返回来自此扫描器的下一个完整标记 |
public String next(Pattern pattern) | 如果下一个标记与指定模式匹配,则返回下一个标记 |
Notes:
- next()和nextLine()的区别
next():只读取输入直到空格。它不能读两个由空格或符号隔开的单词。此外,next()在读取输入后将光标放在同一行中。(next()只读空格之前的数据,并且光标指向本行)
nextLine():读取输入,包括单词之间的空格和除回车以外的所有符号(即。它读到行尾)。读取输入后,nextLine()将光标定位在下一行。
三、String类
1.String类对象两种实例化方式比较
- 直接赋值实例化
Java底层提供有专门的字符串池,因此两个相同字符串的直接赋值对应的地址相同。
- 构造方法实例化
使用构造方法实例化String对象,不会出现将字符串保存到String对象池的操作。
- 直接入池的方法
String str = new String("字符串").intern();
2.String对象(常量)池
Java中的对象(常量)池分为两种:
-
静态常量池
程序在加载时会自动将此程序保存的字符串、普通常量、类和方法的信息等,全部进行分配
-
运行时常量池
程序加载之后,程序里的一些变量提供的常量池
3.字符串修改分析
字符串长度不可改变。字符串的改变只是字符串指向的改变,并且可能产生垃圾。
String类的内容不应频繁修改。
四、Math类
1.包名
java.lang.Math
2.概述
- 该类包含用于执行基本数学元算的方法
- Math类所有的类都是静态的,可以直接调用
3.成员方法
方法名 | 描述 |
---|---|
public static int abs(int a) | 取绝对值 |
public static double ceil(double a) | 向上取整,结果为double(ceil 天花板) |
public static double floor(double a) | 向下取整,结果为double(floor 地板) |
public static int max(int a,int b) | 获取两个值中的最大值 |
public static int min(int a,int b) | 获取两个值中的最小值 |
public static double pow(double a,double b) | 计算多次方 |
public static double sqrt(double a) | 计算平方根 |
public static double random() | 获取[0,1)范围内的随机数 |
五、Date类
1.包名
java.util.Date
2.概述
java时间日期管理的工具类
3.构造函数
函数名 | 描述 |
---|---|
public Date() | 分配 Date 对象并初始化此对象,以表示分配它系统的时间(精确到毫秒) |
public Date(long date) | 分配 Date 对象并初始化此对象,以表示从标准基准时间(称为“历元(epoch)”,即 1970 年 1 月 1 日 00:00:00 GMT)以来的指定毫秒数 |
4.常用方法
方法名 | 描述 |
---|---|
public boolean after(Date when) | 测试此日期是否在指定日期之后 |
public boolean before(Date when) | 测试此日期是否在指定日期之前 |
public int compareTo(Date anotherDate) | 比较两个日期的顺序 |
public boolean equals(Object obj) | 比较两个日期的相等性。当且仅当参数不为 null,并且是一个表示与此对象相同的时间点(到毫秒)的 Date 对象时,结果才为 true。 |
public long getTime() | 返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数 |
public void setTime(long time) | 设置此 Date 对象,以表示 1970 年 1 月 1 日 00:00:00 GMT 以后 time 毫秒的时间点 |
public String toString() | 将Date对象转换为String |
Notes:
- Date对象转换为以下形式的String:
- dow mon dd hh:mm:ss zzz yyyy
- dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat)。
- mon 是月份 (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec)。
- dd 是一月中的某一天(01 至 31),显示为两位十进制数。
- hh 是一天中的小时(00 至 23),显示为两位十进制数。
- mm 是小时中的分钟(00 至 59),显示为两位十进制数。
- ss 是分钟中的秒数(00 至 61),显示为两位十进制数。
- zzz 是时区(并可以反映夏令时)。标准时区缩写包括方法 parse 识别的时区缩写。如果不提供时信息,则 zzz 为空,即根本不包括任何字符。
- yyyy 是年份,显示为 4 位十进制数。
六、Calendar类
1.包名
java.util.Calendar
2.概述
Calendar 类是一个抽象类,它为特定瞬间与一组诸如 YEAR、MONTH、DAY_OF_MONTH、HOUR 等日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法。
3.常用方法
方法名 | 描述 |
---|---|
public static Calendar getInstance() | 获取Calendar类的实例化对象 |
public int get(int field) | 返回Calendar类中字段的值 |
public final Date getTime() | 返回Calendar类中的时间毫秒值距离纪元的偏移量 |
public void setTimeInMillis(long millis) | 返回Calendar类时间的毫秒值 |
public void add(int field,int amount) | 根据日历的规则,为给定的日历字段添加或减去指定的时间量 |
public final void set(int year,int month,int date) | 设置日历字段 YEAR、MONTH 和 DAY_OF_MONTH 的值 |
七、TimeStamp类
1.包名
java.sql.Timestamp
2.概述
邮戳
3.构造方法
构造器 | 描述 |
---|---|
public Timestamp(Date timestamp,CertPath signerCertPath) | Timestamp类构造器 |
4.常用方法
方法名 | 描述 |
---|---|
public CertPath getSignerCertPath() | 返回时间戳构造者的证书路径 |
public Date getTimestamp() | 返回时间戳的时间信息 |
八、SimpleDateFormat类
1.包名
java.text.SimpleDateFormat
2.概述
格式化时间
3.构造器
构造器 | 描述 |
---|---|
public SimpleDateFormat() | 使用默认模式和时间格式构造SimpleDateFormat类 |
public SimpleDateFormat(String pattern) | 使用给定模式构造SimpleDateFormat类 |
4.常用方法
方法名 | 描述 |
---|---|
public StringBuffer format(Date date,StringBuffer toAppendTo,FieldPosition pos) | 将给定的Date格式化为日期/时间字符串,并将结果附加到StringBuffer中 |