现在的位置: 首页 > 综合 > 正文

android的重要java知识点回顾3

2018年02月23日 ⁄ 综合 ⁄ 共 24495字 ⁄ 字号 评论关闭
文章目录

我还清楚地记得自己小时候第一次不带游泳圈下水游泳的感觉,拼命踩水,怕呛水,使吃奶的劲游也游不快,一松劲有感觉马上要沉底了,从小长到大,直到毕业时,才算真正的扎进人海,不管未来你能有多远能有多快都必经此时的忐忑彷徨,别嫌累,别回头!

 

 面向对象三大特性:封装、继承、多态

 

 封装性

概念:指隐藏对象的属性和实现细节,仅对外提供公共访问方式;

封装的好处:将变化隔离;便于使用;提高重用性;安全性

封装原则:将不需要被外界提供的内容都封装起来,把属性都隐藏,提供公共方法对其进行访问

 单例设计模式

解决的问题:保证一个类在内存中的对象唯一性

例如:多程序读取一个配置文件时,建议配置文件封装成对象,会方便操作其中数据,又要保证多个程序读到的是同一个配置文件对象,就需要该配置文件对象在内存中是唯一的

Runtime()方法就是单例设计模式进行设计的。

 

如何保证对象唯一性呢?

思想:

1.不让其他程序创建该类对象

2.在本类中创建一个本类对象

3.对外界提供方法,让外界程序获取这个对象

步骤:

1.因为创建对象都需要构造函数初始化,只要将本类中的构造函数私有化,其他程序就无法再创建该类对象;

2.就在本类中创建一个本类的对象

3.定义一个方法,返回该对象,让其他程序可以通过方法得到本类对象(作用是:可控)

代码体现:

1.私有化构造函数

2.创建私有并静态的本类对象

3.定义公有并静态的方法,返回该对象

 单例设计模式(饿汉式)

//饿汉式
class Single
{
	private Single(){}	//私有化构造函数
	private static Single s=new Single();	//创建私有并静态的本类对象
	public static Single getInstance(){		//定义公有并静态的方法,返回该对象
		return s;
	}
}

 单例设计模式(懒汉式)

//懒汉式:延迟加载方式
class Single2
{
	private Single2(){}
	private static Single2 s=null;
	public static Single2 getInstance(){
		if (s==null)
		{
			s=new Single2();
		}
		return s;
	}
}

 继承性

好处:

1.提高了代码的复用性

2.让类与类之间产生了关系,提供了另一个特征多态的前提

注意:

子类中所有的构造函数都会默认访问父类中的空参数的构造函数,因为每一个子类构造内第一行都有默认的语句suoer();

如果父类中没有空参数的构造函数,那么子类的构造函数内,必须用过super语句指定要我访问的父类中的构造函数

如果子类构造函数中用this来指定调用子类自己的构造函数,那被调用的构造函数也一样会访问父类中的构造函数。

 final特点

 1.这个关键字是一个修饰符,可以修饰类、方法、变量

2.被final修饰的类是一个最终类,不可以被继承

3.被final修饰的方法是一个最终方法,不可以被覆盖

4.被final修饰的变量是一个常量,只能赋值一次

抽象类的特点

1:抽象方法只能定义在抽象类中,抽象类和抽象方法必须由abstract关键字修饰(可以描述类和方法,不可以描述变量)。

2:抽象方法只定义方法声明,并不定义方法实现。

3:抽象类不可以被创建对象(实例化)。

4:只有通过子类继承抽象类并覆盖了抽象类中的所有抽象方法后,该子类才可以实例化。否则,该子类还是一个抽象类。

5: 抽象类只能单继承。

抽象类的细节

1:抽象类中是否有构造函数?有,用于给子类对象进行初始化。

2:抽象类中是否可以定义非抽象方法?

可以。其实,抽象类和一般类没有太大的区别,都是在描述事物,只不过抽象类在描述事物时,有些功能不具体。所以抽象类和一般类在定义上,都是需要定义属性和行为的。只不过,比一般类多了一个抽象函数。而且比一般类少了一个创建对象的部分。

3:抽象关键字abstract和哪些不可以共存?final , private , static 

4:抽象类中可不可以不定义抽象方法?可以。抽象方法目的仅仅为了不让该类创建对象。

抽象类和接口的区别

1:抽象类只能被继承,而且只能单继承。

接口需要被实现,而且可以多实现。 

2:抽象类中可以定义非抽象方法,子类可以直接继承使用。

接口中都有抽象方法,需要子类去实现。

3:抽象类使用的是  is a 关系。

接口使用的 like a 关系。 

4:抽象类的成员修饰符可以自定义。

接口中的成员修饰符是固定的。全都是public的。

多态

多态:函数本身就具备多态性,某一种事物有不同的具体的体现。

体现:父类引用或者接口的引用指向了自己的子类对象。//Animal a = new Cat();

多态的好处:提高了程序的扩展性。

多态的弊端:当父类引用指向子类对象时,虽然提高了扩展性,但是只能访问父类中具备的方法,不可以访问子类中特有的方法。(前期不能使用后期产生的功能,即访问的局限性)

多态的前提:

1:必须要有关系,比如继承、或者实现。

2:通常会有覆盖操作。

匿名内部类

没有名字的内部类。就是内部类的简化形式。一般只用一次就可以用这种形式。匿名内部类其实就是一个匿名子类对象。想要定义匿名内部类:需要前提,内部类必须继承一个类或者实现接口。

匿名内部类的格式:new 父类名&接口名(){ 定义子类成员或者覆盖父类方法 }.方法。

匿名内部类的使用场景:

当函数的参数是接口类型引用时,如果接口中的方法不超过3个。可以通过匿名内部类来完成参数的传递。

其实就是在创建匿名内部类时,该类中的封装的方法不要过多,最好两个或者两个以内。

异 常

异常处理原则:功能抛出几个异常,功能调用如果进行try处理,需要与之对应的catch处理代码块,这样的处理有针对性,抛几个就处理几个。

特殊情况:try对应多个catch时,如果有父类的catch语句块,一定要放在下面。

throw 和throws关键字的区别:

throw用于抛出异常对象,后面跟的是异常对象;throw用在函数内。

throws用于抛出异常类,后面跟的异常类名,可以跟多个,用逗号隔开。throws用在函数上。

多线程

线程的2种创建方式

创建线程的第一种方式:继承Thread ,由子类复写run方法。

步骤:

1,定义类继承Thread类;

2,目的是复写run方法,将要让线程运行的代码都存储到run方法中;

3,通过创建Thread类的子类对象,创建线程对象;

4,调用线程的start方法,开启线程,并执行run方法。

线程状态:

新建:start()

运行:具备执行资格,同时具备执行权;

冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格;

临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权;

消亡:stop()

创建线程的第二种方式:实现一个接口Runnable。

步骤:

1,定义类实现Runnable接口。

2,覆盖接口中的run方法(用于封装线程要运行的代码)。

3,通过Thread类创建线程对象;

4,将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数。

为什么要传递呢?因为要让线程对象明确要运行的run方法所属的对象。

5,调用Thread对象的start方法。开启线程,并运行Runnable接口子类中的run方法。

Ticket t = new Ticket();

/*

直接创建Ticket对象,并不是创建线程对象。

因为创建对象只能通过new Thread类,或者new Thread类的子类才可以。

所以最终想要创建线程。既然没有了Thread类的子类,就只能用Thread类。

*/

Thread t1 = new Thread(t); //创建线程。

/*

只要将t作为Thread类的构造函数的实际参数传入即可完成线程对象和t之间的关联

为什么要将t传给Thread类的构造函数呢?其实就是为了明确线程要运行的代码run方法。

*/

多线程安全问题的原因

通过图解:发现一个线程在执行多条语句时,并运算同一个数据时,在执行过程中,其他线程参与进来,并操作了这个数据。导致到了错误数据的产生。

涉及到两个因素:

1,多个线程在操作共享数据。

2,有多条语句对共享数据进行运算。

原因:这多条语句,在某一个时刻被一个线程执行时,还没有执行完,就被其他线程执行了。

解决安全问题的原理:

只要将操作共享数据的语句在某一时段让一个线程执行完,在执行过程中,其他线程不能进来执行就可以解决这个问题。

如何进行多句操作共享数据代码的封装呢?

java中提供了一个解决方式:就是同步代码块。

格式:

synchronized(对象) {  // 任意对象都可以。这个对象就是锁。

需要被同步的代码;

}

Synchronized(自己得会写得出)

wait和sleep区别: 分析这两个方法:从执行权和锁上来分析:

wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。

sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。

wait:线程会释放执行权,而且线程会释放锁。

Sleep:线程会释放执行权,但不是不释放锁。

什么时候产生死锁,该怎么办?

集合框架

自己能画出来,并且附带方法和步骤以及特性以及各自的遍历方式.

常用的ArrayList  HashSet  

 

 

Collection:

|--List:有序(元素存入集合的顺序和取出的顺序一致),元素都有索引。元素可以重复。

|--Set:无序(存入和取出顺序有可能不一致),不可以存储重复元素。必须保证元素唯一性。

1,添加:

add(object):添加一个元素

addAll(Collection) :添加一个集合中的所有元素。

2,删除:

clear():将集合中的元素全删除,清空集合。

remove(obj) :删除集合中指定的对象。注意:删除成功,集合的长度会改变。

removeAll(collection) :删除部分元素。部分元素和传入Collection一致。

3,判断:

boolean contains(obj) :集合中是否包含指定元素 。

boolean containsAll(Collection) :集合中是否包含指定的多个元素。

boolean isEmpty():集合中是否有元素。 

4,获取:

int size():集合中有几个元素。

5,取交集:

boolean  retainAll(Collection) :对当前集合中保留和指定集合中的相同的元素。如果两个集合元素相同,返回flase;如果retainAll修改了当前集合,返回true。

6,获取集合中所有元素:

Iterator  iterator():迭代器

7,将集合变成数组:

toArray();

List本身是Collection接口的子接口,具备了Collection的所有方法。现在学习List体系特有的共性方法,查阅方法发现List的特有方法都有索引,这是该集合最大的特点。

List:有序(元素存入集合的顺序和取出的顺序一致),元素都有索引。元素可以重复。

|--ArrayList:底层的数据结构是数组,线程不同步,ArrayList替代了Vector,查询元素的速度非常快。

|--LinkedList:底层的数据结构是链表,线程不同步,增删元素的速度非常快。

|--Vector:底层的数据结构就是数组,线程同步的,Vector无论查询和增删都巨慢。

1,添加:

add(index,element) :在指定的索引位插入元素。

addAll(index,collection) :在指定的索引位插入一堆元素。

2,删除:

remove(index) :删除指定索引位的元素。 返回被删的元素。

3,获取:

Object get(index) :通过索引获取指定元素。

int indexOf(obj) :获取指定元素第一次出现的索引位,如果该元素不存在返回-1;

  所以,通过-1,可以判断一个元素是否存在。

int lastIndexOf(Object o) :反向索引指定元素的位置。

List subList(start,end) :获取子列表。

4,修改:

Object set(index,element) :对指定索引位进行元素的修改。

5,获取所有元素:

ListIterator listIterator():list集合特有的迭代器。

List集合支持对元素的增、删、改、查。

List集合因为角标有了自己的获取元素的方式: 遍历。

for(int x=0; x<list.size(); x++){

sop("get:"+list.get(x));

}

在进行list列表元素迭代的时候,如果想要在迭代过程中,想要对元素进行操作的时候,比如满足条件添加新元素。会发生.ConcurrentModificationException并发修改异常。

导致的原因是:

集合引用和迭代器引用在同时操作元素,通过集合获取到对应的迭代器后,在迭代中,进行集合引用的元素添加,迭代器并不知道,所以会出现异常情况。

如何解决呢?

既然是在迭代中对元素进行操作,找迭代器的方法最为合适.可是Iterator中只有hasNext,next,remove方法.通过查阅的它的子接口,ListIterator,发现该列表迭代器接口具备了对元素的增、删、改、查的动作。

ListIterator是List集合特有的迭代器。

ListIterator it = list.listIterator;//取代Iterator it = list.iterator;

 

--< java.util >-- Set接口

Set接口中的方法和Collection中方法一致的。Set接口取出方式只有一种,迭代器。

|--HashSet:底层数据结构是哈希表,线程是不同步的。无序,高效;

HashSet集合保证元素唯一性:通过元素的hashCode方法,和equals方法完成的。

当元素的hashCode值相同时,才继续判断元素的equals是否为true。

如果为true,那么视为相同元素,不存。如果为false,那么存储。

如果hashCode值不同,那么不判断equals,从而提高对象比较的速度。

      |--LinkedHashSet:有序,hashset的子类。

|--TreeSet:对Set集合中的元素的进行指定顺序的排序。不同步。TreeSet底层的数据结构就是二叉树。

 

哈希表的原理

 

1,对对象元素中的关键字(对象中的特有数据),进行哈希算法的运算,并得出一个具体的算法值,这个值 称为哈希值。

2,哈希值就是这个元素的位置。

3,如果哈希值出现冲突,再次判断这个关键字对应的对象是否相同。如果对象相同,就不存储,因为元素重复。如果对象不同,就存储,在原来对象的哈希值基础 +1顺延。

4,存储哈希值的结构,我们称为哈希表。

5,既然哈希表是根据哈希值存储的,为了提高效率,最好保证对象的关键字是唯一的。

这样可以尽量少的判断关键字对应的对象是否相同,提高了哈希表的操作效率。

对于ArrayList集合,判断元素是否存在,或者删元素底层依据都是equals方法。

对于HashSet集合,判断元素是否存在,或者删除元素,底层依据的是hashCode方法和equals方法。

TreeSet:

用于对Set集合进行元素的指定顺序排序,排序需要依据元素自身具备的比较性。

如果元素不具备比较性,在运行时会发生ClassCastException异常。

所以需要元素实现Comparable接口,强制让元素具备比较性,复写compareTo方法。

依据compareTo方法的返回值,确定元素在TreeSet数据结构中的位置。

TreeSet方法保证元素唯一性的方式:就是参考比较方法的结果是否为0,如果return 0,视为两个对象重复,不存。

注意:在进行比较时,如果判断元素不唯一,比如,同姓名,同年龄,才视为同一个人。

在判断时,需要分主要条件和次要条件,当主要条件相同时,再判断次要条件,按照次要条件排序。

TreeSet集合排序有两种方式,Comparable和Comparator区别:

1:让元素自身具备比较性,需要元素对象实现Comparable接口,覆盖compareTo方法。

2:让集合自身具备比较性,需要定义一个实现了Comparator接口的比较器,并覆盖compare方法,并将该类对象作为实际参数传递给TreeSet集合的构造函数。

第二种方式较为灵活。

Map集合

|--Hashtable:底层是哈希表数据结构,是线程同步的。不可以存储null键,null值。

|--HashMap:底层是哈希表数据结构,是线程不同步的。可以存储null键,null值。替代了Hashtable.

|--TreeMap:底层是二叉树结构,可以对map集合中的键进行指定顺序的排序。

Map集合存储和Collection有着很大不同:

Collection一次存一个元素;Map一次存一对元素。

Collection是单列集合;Map是双列集合。

Map中的存储的一对元素:一个是键,一个是值,键与值之间有对应(映射)关系。

特点:要保证map集合中键的唯一性。

1,添加。

 put(key,value):当存储的键相同时,新的值会替换老的值,并将老值返回。如果键没有重复,返回null。

void putAll(Map);

2,删除。

void clear():清空

value remove(key) :删除指定键。

3,判断。

boolean isEmpty():

boolean containsKey(key):是否包含key

boolean containsValue(value) :是否包含value

4,取出。

int size():返回长度

value get(key) :通过指定键获取对应的值。如果返回null,可以判断该键不存在。当然有特殊情况,就是在hashmap集合中,是可以存储null键null值的。

Collection values():获取map集合中的所有的值。

5,想要获取map中的所有元素:

原理:map中是没有迭代器的,collection具备迭代器,只要将map集合转成Set集合,可以使用迭代器了。之所以转成set,是因为map集合具备着键的唯一性,其实set集合就来自于map,set集合底层其实用的就是map的方法。

 

把map集合转成set的方法

Set keySet();

Set entrySet();//取的是键和值的映射关系。

Entry就是Map接口中的内部接口;

为什么要定义在map内部呢?entry是访问键值关系的入口,是map的入口,访问的是map中的键值对。

 

取出map集合中所有元素的方式一:keySet()方法。

可以将map集合中的键都取出存放到set集合中。对set集合进行迭代。迭代完成,再通过get方法对获取到的键进行值的获取。

Set keySet = map.keySet();

Iterator it = keySet.iterator();

while(it.hasNext()) {

Object key = it.next();

Object value = map.get(key);

System.out.println(key+":"+value);

}

 

取出map集合中所有元素的方式二:entrySet()方法。

Set entrySet = map.entrySet();

Iterator it = entrySet.iterator();

while(it.hasNext()) {

Map.Entry  me = (Map.Entry)it.next();

System.out.println(me.getKey()+"::::"+me.getValue());

}

 

使用集合的技巧

看到Array就是数组结构,有角标,查询速度很快。

看到link就是链表结构:增删速度快,而且有特有方法。addFirst; addLast; removeFirst(); removeLast(); getFirst();getLast();

看到hash就是哈希表,就要想要哈希值,就要想到唯一性,就要想到存入到该结构的中的元素必须覆盖hashCode,equals方法。

看到tree就是二叉树,就要想到排序,就想要用到比较。

比较的两种方式:

一个是Comparable:覆盖compareTo方法;

一个是Comparator:覆盖compare方法。

LinkedHashSet,LinkedHashMap:这两个集合可以保证哈希表有存入顺序和取出顺序一致,保证哈希表有序。

集合什么时候用?

当存储的是一个元素时,就用Collection。当存储对象之间存在着映射关系时,就使用Map集合。

保证唯一,就用Set。不保证唯一,就用List。

 

 

Collections

它的出现给集合操作提供了更多的功能。这个类不需要创建对象,内部提供的都是静态方法。

静态方法:

Collections.sort(list);//list集合进行元素的自然顺序排序。

Collections.sort(list,new ComparatorByLen());//按指定的比较器方法排序。

class ComparatorByLen implements Comparator<String>{

public int compare(String s1,String s2){

int temp = s1.length()-s2.length();

return temp==0?s1.compareTo(s2):temp;

}

}

Collections.max(list); //返回list中字典顺序最大的元素。

int index = Collections.binarySearch(list,"zz");//二分查找,返回角标。

Collections.reverseOrder();//逆向反转排序。

Collections.shuffle(list);//随机对list中的元素进行位置的置换。

将非同步集合转成同步集合的方法:Collections中的  XXX synchronizedXXX(XXX);

List synchronizedList(list);

Map synchronizedMap(map);

原理:定义一个类,将集合所有的方法加同一把锁后返回。

Collection 和 Collections的区别:

Collections是个java.util下的类,是针对集合类的一个工具类,提供一系列静态方法,实现对集合的查找、排序、替换、线程安全化(将非同步的集合转换成同步的)等操作。

Collection是个java.util下的接口,它是各种集合结构的父接口,继承于它的接口主要有Set和List,提供了关于集合的一些操作,如插入、删除、判断一个元素是否其成员、遍历等。

 

Arrays

用于操作数组对象的工具类,里面都是静态方法。

asList方法:将数组转换成list集合。

String[] arr = {"abc","kk","qq"};

List<String> list = Arrays.asList(arr);//将arr数组转成list集合。

将数组转换成集合,有什么好处呢?用aslist方法,将数组变成集合;

可以通过list集合中的方法来操作数组中的元素:isEmpty()、contains、indexOf、set; 

注意(局限性):数组是固定长度,不可以使用集合对象增加或者删除等,会改变数组长度的功能方法。比如add、remove、clear。(会报不支持操作异常UnsupportedOperationException);

如果数组中存储的引用数据类型,直接作为集合的元素可以直接用集合方法操作。

如果数组中存储的是基本数据类型,asList会将数组实体作为集合元素存在。

集合变数组:用的是Collection接口中的方法:toArray();

如果给toArray传递的指定类型的数据长度小于了集合的size,那么toArray方法,会自定再创建一个该类型的数据,长度为集合的size。

如果传递的指定的类型的数组的长度大于了集合的size,那么toArray方法,就不会创建新数组,直接使用该数组即可,并将集合中的元素存储到数组中,其他为存储元素的位置默认值null。

所以,在传递指定类型数组时,最好的方式就是指定的长度和size相等的数组。

将集合变成数组后有什么好处?限定了对集合中的元素进行增删操作,只要获取这些元素即可。

 

java5.0新特性

Jdk5.0新特性

Collection在jdk1.5以后,有了一个父接口Iterable,这个接口的出现的将iterator方法进行抽取,提高了扩展性。

增强for循环

foreach语句,foreach简化了迭代器。

格式:// 增强for循环括号里写两个参数,第一个是声明一个变量,第二个就是需要迭代的容器

for( 元素类型 变量名 : Collection集合 & 数组 ) {

}

高级for循环和传统for循环的区别:

高级for循环在使用时,必须要明确被遍历的目标。这个目标,可以是Collection集合或者数组,如果遍历Collection集合,在遍历过程中还需要对元素进行操作,比如删除,需要使用迭代器。

如果遍历数组,还需要对数组元素进行操作,建议用传统for循环因为可以定义角标通过角标操作元素。如果只为遍历获取,可以简化成高级for循环,它的出现为了简化书写。

高级for循环可以遍历map集合吗?不可以。但是可以将map转成set后再使用foreach语句。

1)、作用:对存储对象的容器进行迭代: 数组  collection   map

2)、增强for循环迭代数组:

String [] arr = {"a", "b", "c"};//数组的静态定义方式,只试用于数组首次定义的时候

for(String s : arr) {

System.out.println(s);

}

3)、单列集合 Collection:

List list = new ArrayList();

list.add("aaa");

// 增强for循环, 没有使用泛型的集合能不能使用增强for循环迭代?能

for(Object obj : list) {

String s = (String) obj;

System.out.println(s);

}

4)、双列集合 Map:

Map map = new HashMap();

map.put("a", "aaa");

// 传统方式:必须掌握这种方式

Set entrys = map.entrySet(); // 1.获得所有的键值对Entry对象

iter = entrys.iterator(); // 2.迭代出所有的entry

while(iter.hasNext()) {

Map.Entry entry = (Entry) iter.next();

String key = (String) entry.getKey(); // 分别获得key和value

String value = (String) entry.getValue();

System.out.println(key + "=" + value);

}

// 增强for循环迭代:原则上map集合是无法使用增强for循环来迭代的,因为增强for循环只能针对实现了Iterable接口的集合进行迭代;Iterable是jdk5中新定义的接口,就一个方法iterator方法,只有实现了Iterable接口的类,才能保证一定有iterator方法,java有这样的限定是因为增强for循环内部还是用迭代器实现的,而实际上,我们可以通过某种方式来使用增强for循环。

for(Object obj : map.entrySet()) {

Map.Entry entry = (Entry) obj;  // obj 依次表示Entry

System.out.println(entry.getKey() + "=" + entry.getValue());

}

5)、集合迭代注意问题:在迭代集合的过程中,不能对集合进行增删操作(会报并发访问异常);可以用迭代器的方法进行操作(子类listIterator:有增删的方法)。

6)、增强for循环注意问题:在使用增强for循环时,不能对元素进行赋值;

int[] arr = {1,2,3};

for(int num : arr) {

num = 0; //不能改变数组的值

}

System.out.println(arr[1]); //2

可变参数(...)

用到函数的参数上,当要操作的同一个类型元素个数不确定的时候,可是用这个方式,这个参数可以接受任意个数的同一类型的数据。

和以前接收数组不一样的是:

以前定义数组类型,需要先创建一个数组对象,再将这个数组对象作为参数传递给函数。现在,直接将数组中的元素作为参数传递即可。底层其实是将这些元素进行数组的封装,而这个封装动作,是在底层完成的,被隐藏了。所以简化了用户的书写,少了调用者定义数组的动作。

如果在参数列表中使用了可变参数,可变参数必须定义在参数列表结尾(也就是必须是最后一个参数,否则编译会失败。)。

如果要获取多个int数的和呢?可以使用将多个int数封装到数组中,直接对数组求和即可。

静态导入

导入了类中的所有静态成员,简化静态成员的书写。

import static java.util.Collections.*;  //导入了Collections类中的所有静态成员

枚举:关键字 enum

问题:对象的某个属性的值不能是任意的,必须为固定的一组取值其中的某一个;

解决办法:

1)、在setGrade方法中做判断,不符合格式要求就抛出异常;

2)、直接限定用户的选择,通过自定义类模拟枚举的方式来限定用户的输入,写一个Grade类,私有构造函数,对外提供5个静态的常量表示类的实例;

3)、jdk5中新定义了枚举类型,专门用于解决此类问题;

4)、枚举就是一个特殊的java类,可以定义属性、方法、构造函数、实现接口、继承类;

自动拆装箱

java中数据类型分为两种 : 基本数据类型   引用数据类型(对象)

在 java程序中所有的数据都需要当做对象来处理,针对8种基本数据类型提供了包装类,如下:

int --> Integer

byte --> Byte

short --> Short

long --> Long

char --> Character

double --> Double

float --> Float

boolean --> Boolean

jdk5以前基本数据类型和包装类之间需要互转:

基本---引用   Integer x = new Integer(x);

引用---基本   int num = x.intValue();

1)、Integer x = 1; x = x + 1;  经历了什么过程?装箱 à 拆箱 à 装箱

2)、为了优化,虚拟机为包装类提供了缓冲池,Integer池的大小 -128~127 一个字节的大小

3)、String池:Java为了优化字符串操作 提供了一个缓冲池;

 

泛型

jdk1.5版本以后出现的一个安全机制。表现格式:< >

好处:

1:将运行时期的问题ClassCastException问题转换成了编译失败,体现在编译时期,程序员就可以解决问题。

2:避免了强制转换的麻烦。

只要带有<>的类或者接口,都属于带有类型参数的类或者接口,在使用这些类或者接口时,必须给<>中传递一个具体的引用数据类型。

泛型技术:其实应用在编译时期,是给编译器使用的技术,到了运行时期,泛型就不存在了。

为什么? 因为泛型的擦除:也就是说,编辑器检查了泛型的类型正确后,在生成的类文件中是没有泛型的。

在运行时,如何知道获取的元素类型而不用强转呢?

泛型的补偿:因为存储的时候,类型已经确定了是同一个类型的元素,所以在运行时,只要获取到该元素的类型,在内部进行一次转换即可,所以使用者不用再做转换动作了。

什么时候用泛型类呢?

当类中的操作的引用数据类型不确定的时候,以前用的Object来进行扩展的,现在可以用泛型来表示。这样可以避免强转的麻烦,而且将运行问题转移到的编译时期。

 

泛型在程序定义上的体现

//泛型类:将泛型定义在类上。
class Tool<Q> {
private Q obj;
public  void setObject(Q obj) {
this.obj = obj;
}
public Q getObject() {
return obj;
}
}
//当方法操作的引用数据类型不确定的时候,可以将泛型定义在方法上。
public <W> void method(W w) {
System.out.println("method:"+w);
}
//静态方法上的泛型:静态方法无法访问类上定义的泛型。如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
public static <Q> void function(Q t) {
System.out.println("function:"+t);
}
//泛型接口.
interface Inter<T> {
void show(T t);
}
class InterImpl<R> implements Inter<R> {
public void show(R r) {
System.out.println("show:"+r);
}
}

泛型中的通配符

可以解决当具体类型不确定的时候,这个通配符就是 ?  ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。

泛型限定:

上限:?extends E:可以接收E类型或者E的子类型对象。

下限:?super E:可以接收E类型或者E的父类型对象。

上限什么时候用:往集合中添加元素时,既可以添加E类型对象,又可以添加E的子类型对象。为什么?因为取的时候,E类型既可以接收E类对象,又可以接收E的子类型对象。

下限什么时候用:当从集合中获取元素进行操作的时候,可以用当前元素的类型接收,也可以用当前元素的父类型接收。

泛型的细节

1)、泛型到底代表什么类型取决于调用者传入的类型,如果没传,默认是Object类型;

2)、使用带泛型的类创建对象时,等式两边指定的泛型必须一致

原因:编译器检查对象调用方法时只看变量,然而程序运行期间调用方法时就要考虑对象具体类型了;

3)、等式两边可以在任意一边使用泛型,在另一边不使用(考虑向后兼容);

ArrayList<String> al = new ArrayList<Object>();  //错

//要保证左右两边的泛型具体类型一致就可以了,这样不容易出错。

ArrayList<? extends Object> al = new ArrayList<String>();

al.add("aa");  //错

//因为集合具体对象中既可存储String,也可以存储Object的其他子类,所以添加具体的类型对象不合适,类型检查会出现安全问题。 ?extends Object 代表Object的子类型不确定,怎么能添加具体类型的对象呢?

public static void method(ArrayList<? extends Object> al) {

al.add("abc");  //错

//只能对al集合中的元素调用Object类中的方法,具体子类型的方法都不能用,因为子类型不确定。

}

IO流

 

字符流

Reader:用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。

     |---BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。

        |---LineNumberReader:跟踪行号的缓冲字符输入流。此类定义了方法 setLineNumber(int) 和 getLineNumber(),它们可分别用于设置和获取当前行号。

     |---InputStreamReader:是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。

        |---FileReader:用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。要自己指定这些值,可以先在 FileInputStream 上构造一个 InputStreamReader。

     |---CharArrayReader:

     |---StringReader:

 

Writer:写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。

     |---BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。

     |---OutputStreamWriter:是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。

        |---FileWriter:用来写入字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的。要自己指定这些值,可以先在 FileOutputStream 上构造一个 OutputStreamWriter。

     |---PrintWriter:

     |---CharArrayWriter:

     |---StringWriter:

字节流

InputStream:是表示字节输入流的所有类的超类。

     |--- FileInputStream:从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境。FileInputStream 用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用 FileReader。

     |--- FilterInputStream:包含其他一些输入流,它将这些流用作其基本数据源,它可以直接传输数据或提供一些额外的功能。

        |--- BufferedInputStream:该类实现缓冲的输入流。

        |--- Stream:

     |--- ObjectInputStream:

     |--- PipedInputStream:

-----------------------------------------------

OutputStream:此抽象类是表示输出字节流的所有类的超类。

     |--- FileOutputStream:文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。

     |--- FilterOutputStream:此类是过滤输出流的所有类的超类。

        |--- BufferedOutputStream:该类实现缓冲的输出流。

        |--- PrintStream:

        |--- DataOutputStream:

     |--- ObjectOutputStream:

     |--- PipedOutputStream:

--------------------------------

缓冲区是提高效率用的,给谁提高呢?

BufferedWriter:是给字符输出流提高效率用的,那就意味着,缓冲区对象建立时,必须要先有流对象。明确要提高具体的流对象的效率。

FileWriter fw = new FileWriter("bufdemo.txt");

BufferedWriter bufw = new BufferedWriter(fw);//让缓冲区和指定流相关联。

for(int x=0; x<4; x++){

bufw.write(x+"abc");

bufw.newLine(); //写入一个换行符,这个换行符可以依据平台的不同写入不同的换行符。

bufw.flush();//对缓冲区进行刷新,可以让数据到目的地中。

}

bufw.close();//关闭缓冲区,其实就是在关闭具体的流。

-----------------------------

BufferedReader:

FileReader fr = new FileReader("bufdemo.txt");

BufferedReader bufr  = new BufferedReader(fr);

String line = null;

while((line=bufr.readLine())!=null){  //readLine方法返回的时候是不带换行符的。

System.out.println(line);

}

bufr.close();

-----------------------------

//记住,只要一读取键盘录入,就用这句话。

BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));

BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));//输出到控制台

String line = null;

while((line=bufr.readLine())!=null){

if("over".equals(line))

break;

bufw.write(line.toUpperCase());//将输入的字符转成大写字符输出

bufw.newLine();

bufw.flush();

}

bufw.close();

bufr.close();

流对象

其实很简单,就是读取和写入。但是因为功能的不同,流的体系中提供N多的对象。那么开始时,到底该用哪个对象更为合适呢?这就需要明确流的操作规律。

流的操作规律:

1,明确源和目的。

数据源:就是需要读取,可以使用两个体系:InputStream、Reader;

数据汇:就是需要写入,可以使用两个体系:OutputStream、Writer;

2,操作的数据是否是纯文本数据?

如果是:数据源:Reader

    数据汇:Writer 

如果不是:数据源:InputStream

      数据汇:OutputStream

3,虽然确定了一个体系,但是该体系中有太多的对象,到底用哪个呢?

明确操作的数据设备。

数据源对应的设备:硬盘(File),内存(数组),键盘(System.in)

数据汇对应的设备:硬盘(File),内存(数组),控制台(System.out)。

4,需要在基本操作上附加其他功能吗?比如缓冲。

如果需要就进行装饰。

转换流特有功能:转换流可以将字节转成字符,原因在于,将获取到的字节通过查编码表获取到指定对应字符。

转换流的最强功能就是基于 字节流 + 编码表 。没有转换,没有字符流。

发现转换流有一个子类就是操作文件的字符流对象:

InputStreamReader

|--FileReader

OutputStreamWriter

|--FileWrier

想要操作文本文件,必须要进行编码转换,而编码转换动作转换流都完成了。所以操作文件的流对象只要继承自转换流就可以读取一个字符了。

但是子类有一个局限性,就是子类中使用的编码是固定的,是本机默认的编码表,对于简体中文版的系统默认码表是GBK。

FileReader fr = new FileReader("a.txt");

InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"gbk");

以上两句代码功能一致,

如果仅仅使用平台默认码表,就使用FileReader fr = new FileReader("a.txt"); //因为简化。

如果需要制定码表,必须用转换流。

转换流 = 字节流+编码表。

转换流的子类File = 字节流 + 默认编码表。

凡是操作设备上的文本数据,涉及编码转换,必须使用转换流。

File类

将文件系统中的文件和文件夹封装成了对象。提供了更多的属性和行为可以对这些文件和文件夹进行操作。这些是流对象办不到的,因为流只操作数据。

File类常见方法:

1:创建。

boolean createNewFile():在指定目录下创建文件,如果该文件已存在,则不创建。而对操作文件的输出流而言,输出流对象已建立,就会创建文件,如果文件已存在,会覆盖。除非续写。

boolean mkdir():创建此抽象路径名指定的目录。

boolean mkdirs():创建多级目录。 

2:删除。

boolean delete():删除此抽象路径名表示的文件或目录。

void deleteOnExit():在虚拟机退出时删除。

注意:在删除文件夹时,必须保证这个文件夹中没有任何内容,才可以将该文件夹用delete删除。

window的删除动作,是从里往外删。注意:java删除文件不走回收站。要慎用。

3:获取.

long length():获取文件大小。

String getName():返回由此抽象路径名表示的文件或目录的名称。

String getPath():将此抽象路径名转换为一个路径名字符串。

String getAbsolutePath():返回此抽象路径名的绝对路径名字符串。

String getParent():返回此抽象路径名父目录的抽象路径名,如果此路径名没有指定父目录,则返回 null。

long lastModified():返回此抽象路径名表示的文件最后一次被修改的时间。

File.pathSeparator:返回当前系统默认的路径分隔符,windows默认为 “;”。

File.Separator:返回当前系统默认的目录分隔符,windows默认为 “\”。

4:判断:

boolean exists():判断文件或者文件夹是否存在。

boolean isDirectory():测试此抽象路径名表示的文件是否是一个目录。

boolean isFile():测试此抽象路径名表示的文件是否是一个标准文件。

boolean isHidden():测试此抽象路径名指定的文件是否是一个隐藏文件。

boolean isAbsolute():测试此抽象路径名是否为绝对路径名。

5:重命名。

 boolean renameTo(File dest):可以实现移动的效果。剪切+重命名。

String[] list():列出指定目录下的当前的文件和文件夹的名称。包含隐藏文件。

如果调用list方法的File 对象中封装的是一个文件,那么list方法返回数组为null。如果封装的对象不存在也会返回null。只有封装的对象存在并且是文件夹时,这个方法才有效。

递归

就是函数自身调用自身。

什么时候用递归呢?

当一个功能被重复使用,而每一次使用该功能时的参数不确定,都由上次的功能元素结果来确定。

简单说:功能内部又用到该功能,但是传递的参数值不确定。(每次功能参与运算的未知内容不确定)。

递归的注意事项:

1:一定要定义递归的条件。

2:递归的次数不要过多。容易出现 StackOverflowError 栈内存溢出错误

其实递归就是在栈内存中不断的加载同一个函数。

 

Java.util.Properties

 

一个可以将键值进行持久化存储的对象。Map--Hashtable的子类。

Map

|--Hashtable

|--Properties:用于属性配置文件,键和值都是字符串类型。

特点:1:可以持久化存储数据。2:键值都是字符串。3:一般用于配置文件。

|-- load():将流中的数据加载进集合。

原理:其实就是将读取流和指定文件相关联,并读取一行数据,因为数据是规则的key=value,所以获取一行后,通过 = 对该行数据进行切割,左边就是键,右边就是值,将键、值存储到properties集合中。

|-- store():写入各个项后,刷新输出流。

|-- list():将集合的键值数据列出到指定的目的地。

 

反射

反射技术:其实就是动态加载一个指定的类,并获取该类中的所有的内容。而且将字节码文件封装成对象,并将字节码文件中的内容都封装成对象,这样便于操作这些成员。简单说:反射技术可以对一个类进行解剖。

反射的好处:大大的增强了程序的扩展性。

反射的基本步骤

1、获得Class对象,就是获取到指定的名称的字节码文件对象。

2、实例化对象,获得类的属性、方法或构造函数。

3、访问属性、调用方法、调用构造函数创建对象。

 

获取这个Class对象,有三种方式:

1:通过每个对象都具备的方法getClass来获取。弊端:必须要创建该类对象,才可以调用getClass方法。

2:每一个数据类型(基本数据类型和引用数据类型)都有一个静态的属性class。弊端:必须要先明确该类。

 前两种方式不利于程序的扩展,因为都需要在程序使用具体的类来完成。

3:使用的Class类中的方法,静态的forName方法。

 指定什么类名,就获取什么类字节码文件对象,这种方式的扩展性最强,只要将类名的字符串传入即可。

// 1. 根据给定的类名来获得  用于类加载

String classname = "cn.itcast.reflect.Person";// 来自配置文件

Class clazz = Class.forName(classname);// 此对象代表Person.class

// 2. 如果拿到了对象,不知道是什么类型   用于获得对象的类型

Object obj = new Person();

Class clazz1 = obj.getClass();// 获得对象具体的类型

// 3. 如果是明确地获得某个类的Class对象  主要用于传参

Class clazz2 = Person.class;

反射的用法

1)、需要获得java类的各个组成部分,首先需要获得类的Class对象,获得Class对象的三种方式:

Class.forName(classname) 用于做类加载

obj.getClass() 用于获得对象的类型

类名.class     用于获得指定的类型,传参用

2)、反射类的成员方法:

Class clazz = Person.class;

Method method = clazz.getMethod(methodName, new Class[]{paramClazz1, paramClazz2});

method.invoke();

3)、反射类的构造函数:

Constructor con = clazz.getConstructor(new Class[]{paramClazz1, paramClazz2,...})

con.newInstance(params...)

4)、反射类的属性:

Field field = clazz.getField(fieldName);

field.setAccessible(true);

field.setObject(value);

获取了字节码文件对象后,最终都需要创建指定类的对象:

创建对象的两种方式(其实就是对象在进行实例化时的初始化方式):

1,调用空参数的构造函数:使用了Class类中的newInstance()方法。

2,调用带参数的构造函数:先要获取指定参数列表的构造函数对象,然后通过该构造函数的对象的newInstance(实际参数) 进行对象的初始化。

综上所述,第二种方式,必须要先明确具体的构造函数的参数类型,不便于扩展。所以一般情况下,被反射的类,内部通常都会提供一个公有的空参数的构造函数。

 如何生成获取到字节码文件对象的实例对象

Class clazz = Class.forName("cn.itcast.bean.Person");//类加载

// 直接获得指定的类型

clazz = Person.class;

// 根据对象获得类型

Object obj = new Person("zhangsan", 19);

clazz = obj.getClass();

Object obj = clazz.newInstance();//该实例化对象的方法调用就是指定类中的空参数构造函数,给创建对象进行初始化。当指定类中没有空参数构造函数时,该如何创建该类对象呢?请看method_2();

public static void method_2() throws Exception {

Class clazz = Class.forName("cn.itcast.bean.Person");

//既然类中没有空参数的构造函数,那么只有获取指定参数的构造函数,用该函数来进行实例化。

//获取一个带参数的构造器。

Constructor constructor = clazz.getConstructor(String.class,int.class);

//想要对对象进行初始化,使用构造器的方法newInstance();

Object obj = constructor.newInstance("zhagnsan",30);

//获取所有构造器。

Constructor[] constructors = clazz.getConstructors();//只包含公共的

constructors = clazz.getDeclaredConstructors();//包含私有的

for(Constructor con : constructors) {

System.out.println(con);

}

}

反射指定类中的方法

//获取类中所有的方法。

public static void method_1() throws Exception {

Class clazz = Class.forName("cn.itcast.bean.Person");

Method[] methods = clazz.getMethods();//获取的是该类中的公有方法和父类中的公有方法。

methods = clazz.getDeclaredMethods();//获取本类中的方法,包含私有方法。

for(Method method : methods) {

System.out.println(method);

}

}

//获取指定方法;

public static void method_2() throws Exception {

Class clazz = Class.forName("cn.itcast.bean.Person");

//获取指定名称的方法。

Method method = clazz.getMethod("show", int.class,String.class);

//想要运行指定方法,当然是方法对象最清楚,为了让方法运行,调用方法对象的invoke方法即可,但是方法运行必须要明确所属的对象和具体的实际参数。

Object obj = clazz.newInstance();

method.invoke(obj, 39,"hehehe");//执行一个方法

}

//想要运行私有方法。

public static void method_3() throws Exception {

Class clazz = Class.forName("cn.itcast.bean.Person");

//想要获取私有方法。必须用getDeclearMethod();

Method method = clazz.getDeclaredMethod("method", null);

// 私有方法不能直接访问,因为权限不够。非要访问,可以通过暴力的方式。

method.setAccessible(true);//一般很少用,因为私有就是隐藏起来,所以尽量不要访问。

}

//反射静态方法。

public static void method_4() throws Exception {

Class clazz = Class.forName("cn.itcast.bean.Person");

Method method = clazz.getMethod("function",null);

method.invoke(null,null);

}

正则表达式

其实是用来操作字符串的一些规则。

好处:正则的出现,对字符串的复杂操作变得更为简单。

特点:将对字符串操作的代码用一些符号来表示。只要使用了指定符号,就可以调用底层的代码对字符串进行操作。符号的出现,简化了代码的书写。

弊端:符号的出现虽然简化了书写,但是却降低了阅读性。

其实更多是用正则解决字符串操作的问题。

组:用小括号标示,每定义一个小括号,就是一个组,而且有自动编号,从1开始。

只要使用组,对应的数字就是使用该组的内容。别忘了,数组要加\\。

(aaa(wwww(ccc))(eee))技巧,从左括号开始数即可。有几个左括号就是几组。

常见操作:

1,匹配:其实用的就是String类中的matches方法。

String reg = "[1-9][0-9]{4,14}";

boolean b = qq.matches(reg);//将正则和字符串关联对字符串进行匹配。

2,切割:其实用的就是String类中的split方法。

3,替换:其实用的就是String类中的replaceAll();

4,获取:

   1),先要将正则表达式编译成正则对象。使用的是Pattern中静态方法 compile(regex);

2),通过Pattern对象获取Matcher对象。

Pattern用于描述正则表达式,可以对正则表达式进行解析。

而将规则操作字符串,需要从新封装到匹配器对象Matcher中。

然后使用Matcher对象的方法来操作字符串。

如何获取匹配器对象呢?

通过Pattern对象中的matcher方法。该方法可以正则规则和字符串想关联。并返回匹配器对象。

3),使用Matcher对象中的方法即可对字符串进行各种正则操作。

内省

对类内部属性进行检查和操作的处理方法。

类中含有getXxx()setXxx()isXxx()等方法才可称之为JavaBean类。

PropertyDescriptorBeanInfoBeanUtils等类

反射和内省的区别:

反射是种技术;内省是种规则

代理Proxy

1、为已经存在的多个具有相同接口的目标类的各个方法增加一些系统功能,如异常处理、日志、计算运行时间、事物管理等等

2、某些情况之下,一个对象不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

3、动态代理:

按创建时期分:

静态代理:代理类的.class文件在程序运行之前就已经存在了

动态代理:在运行时期,运用反射机制动态创建而成。

getProxyClass(ClassLoader loader, Class interfaces)

Proxy(InvocationHandler h)

newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHander h)

实现动态代理:

JDK中的Proxy类(代理对象实现目标对象的桥梁)

第三方提供的CGLIB(代理对象是目标对象的子类,未实现相同接口)。

 

抱歉!评论已关闭.