一、抽象类
当编写一个类时,我们往往会为该类定义一些方法,这些方法是用来描述该类的行为方式,那么这些方法都有具体的方法体。
分析事物时,发现了共性内容,就出现向上抽取。会有这样一种特殊情况,就是功能声明相同,但功能主体不同。那么这时也可以抽取,但只抽取方法声明,不抽取方法主体。那么此方法就是一个抽象方法
通过java中的关键字abstract(抽象)。
当定义了抽象函数的类也必须被abstract关键字修饰,被abstract关键字修饰的类是抽象类
。
例如:
abstract class 犬科 {abstract void 吼叫();//抽象函数。需要abstract修饰,并分号;结束
}
class Dog extends 犬科 {void 吼叫(){System.out.println("汪汪汪汪");}
}
class Wolf extends 犬科{void 吼叫(){System.out.println("嗷嗷嗷嗷");}
}
抽象类的特点:
1、抽象类和抽象方法都需要被abstract修饰。抽象方法一定要定义在抽象类中。
2、抽象类不可以创建实例,原因:调用抽象方法没有意义。
3、只有覆盖了抽象类中所有的抽象方法后,其子类才可以实例化。否则该子类还是一个抽象类。
细节问题:
1、抽象类一定是个父类?
是的,因为不断抽取而来的。
2、抽象类是否有构造函数?
有,虽然不能给自己的对象初始化,但是可以给自己的子类对象初始化。抽象类和一般类的异同点:相同:1、它们都是用来描述事物的。2、它们之中都可以定义属性和行为。不同:1、一般类可以具体的描述事物。抽象类描述事物的信息不具体2、抽象类中可以多定义一个成员:抽象函数。3、一般类可以创建对象,而抽象类一定不能创建对象。
3、抽象类中是否可以不定义抽象方法。
是可以的
4、抽象关键字abstract不可以和哪些关键字共存?
1、final:fianl修饰的类是无法被继承的,而abstract修饰的类一定要有子类。 final修饰的方法无法被覆盖,但是abstract修饰的方法必须要被子类去实现的。
2、static:静态修饰的方法属于类的,它存在与静态区中,和对象就没关系了。而抽象方法没有方法体,使用类名调用它没有任何意义。
3、private:私有的方法子类是无法继承到的,也不存在覆盖,而abstract和private一起使用修饰方法,abstract既要子类去实现这个方法,而private修饰子类根本无法得到父类这个方法。互相矛盾。
二、接口
当一个抽象类中的所有方法都是抽象方法时,那么这个抽象类就可以使用另外一种接口这种机制来体现。
接口怎么定义呢?定义普通的类或者抽象类可以使用class关键字,定义接口必须interface关键字完成。
interface class Demo{abstract void show1();abstract void show2();
}
接口中只能定义常量
1、 接口成员的特点
1、接口中可以定义变量,但是变量必须有固定的修饰符修饰,public static final
所以接口中的变量也称之为常量。
2、接口中可以定义方法,方法也有固定的修饰符,public abstract(现在可以没有)
3、接口中的成员都是公共的。
4、接口不可以创建对象。
5、子类必须覆盖掉接口中所有的抽象方法后,子类才可以实例化,否则子类是一个抽象类。
2、接口-多实现
接口最重要的体现:解决多继承的弊端,将多继承这种机制在java中通过多实现完成了。
interface A{void abstract show1();
}
interface B{void abstract show2();
}
// 多实现。同时实现多个接口。
class C implements A,B{public void show1(){}public void show2(){}
}
怎么解决多继承的弊端呢?
弊端:多继承时,当多个父类中有相同功能时,子类调用会产生不确定性。
其实核心原因就是在于多继承父类中功能有主体,而导致调用运行时,不确定运行哪个主体内容。
而在多实现里 因为接口中的功能都没有方法体,由子类来明确,来源确定就是实现他们的实现类。
3、接口的多继承
多个接口之间可以使用extends进行继承。
interface A{abstract void show();
}
interface B{abstract void show1();
}
interface C{abstract void show2();
}
interface D extends A,B,C{abstract void show3();
}
总结:接口在开发中的它好处
1、接口的出现扩展了功能。
2、接口其实就是暴露出来的规则。
3、接口的出现降低了耦合性,即设备与设备之间实现了解耦。
接口和抽象的区别:
相同点:
- 都位于继承的顶端,用于被其他实现或继承;
- 都不能实例化;
- 都包含抽象方法,其子类都必须覆写这些抽象方法;
区别:
- 抽象类为部分方法提供实现,避免子类重复实现这些方法,提供代码重用性;
- 接口只能包含抽象方法,极度的抽象类;
- 一个类只能继承一个直接父类(可能是抽象类),却可以实现多个接口;(接口弥补了Java的单继承)
二者的选用:
- 优先选用接口,尽量少用抽象类;
- 需要定义子类的行为,又要为子类提供共性功能时才选用抽象类;
三、集合
1、集合与数组
集合和数组的容器:
数组的长度是固定的;集合的长度是可变的。
数组中存储的是同一类型的元素,可以存储基本数据类型值。集合存储的都是对象。而且对象的类型可以不一致。
集合作用:当对象多的时候,先进行存储。
2、集合框架的由来
集合本身是一个工具,它存放在java.util包中。
不同的容器进行不断的向上抽取,最后形成了一个集合框架,这个框架就是Collection
接口。在Collection接口定义着集合框架中最共性的内容。
在学习时:我们需要看最顶层怎么用, 创建底层对象即可。因为底层继承了父类中的所有功能
3、Collection接口
Collection接口是集合中的顶层接口,它中定义的所有功能子类都可以使用。。一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。
(可以使用多态)
3.1、Collection基本方法了解
Collection coll = new ArrayList();
//1,往集合中添加对象元素。add(Object);
coll.add("itcast1");
coll.add("itcast2");
coll.add("itcast3");
//2,删除。
coll.remove("itcast2");
//3,判断是否包含。
System.out.println(coll.contains("itcast11"));
//4,清除。
coll.clear();
//把集合打印一下。
System.out.println(coll);//[itcast1, itcast2, itcast3]
3.2、集合的使用
在使用集合时需要注意:
- 集合中存储其实都是对象的地址。
- 集合中可以存储基本数值吗?不行,但是jdk1.5以后可以这么写,但是存储的还是对象(基本数据类型包装类对象)。
- 存储时提升了Object。取出时要使用元素的特有内容,必须向下转型(一般在迭代中体现)。
Collection coll = new ArrayList();coll.add("abc");
coll.add("aabbcc");
coll.add("shitcast");for (Iterator it = coll.iterator(); it.hasNext();) {//由于元素被存放进集合后全部被提升为Object类型,当需要使用子类对象特有方法时,需要向下转型String str = (String) it.next();System.out.println(str.length());
}
3.3、集合中存放自定义对象
//创建一个自定义类:
public class Student {private String name;private int age;public Student(String name, int age) {super();this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}pulic void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Student [name=" + name + ", age=" + age + "]";}//建立学生的自己比较方式public boolean equals(Object o){if(this == o){return true;}if((o instanceof Student)!){throw new ClassCastException();}Student s = (Student)o;return this.age == s.age && this.name.equals(s.name);}
}
//创建集合对象,存储自定义对象。
class CollectionDemo {public static void main(String[] args){Collection coll = new ArrayList();Student s = new Student("zhangsan",21);coll.add(s);coll.add(new Student("lisi",22));coll.add(new Student("wangwu",23));for(Iterator it = coll.iterator();it.hasNaxt();){Student s = (Student)it.next();Sysem.out.println(s);}}
}
在给集合中存放对象时,集合中的所有对象有自己的方法比较是不是同一元素。一般情况都是使用equals方法,而Object中的equals方法是比较的是两个对象的内存地址是否相同,而在开发的时候我们需要根据对象的自身数据建立属于对象特有的比较方法,这时我们需要复写equals方法。
四、集合-List
1、 List接口介绍
List集合是有序的 collection(也称为序列),此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
与 set 不同,list列表通常允许重复的元素。
总结:
List:有序的
,带索引的
,通过索引就可以精确的操作集合中的元素,元素是可以重复的
。
2、List接口方法
List提供了增删改查动作
- 增加
add(element);
add(index,element);
- 删除
remove(element);
remove(index);
- 修改
set(index,element);
- 查询
get(index);
List list = new ArrayList();
//1,添加元素。
list.add(new Student("wangcai1",21));
list.add(new Student("wangcai2",22));
list.add(new Student("wangcai3",23));//2,插入元素。
list.add(1, new Student("xiaoqiang",25));
//3,删除元素。
list.remove(2);//IndexOutOfBoundsException//4,修改元素。
list.set(1, new Student("xiaoming",11));for (Iterator it = list.iterator(); it.hasNext();) {Student stu = (Student) it.next();System.out.println(stu);
}//和数组一样,角标从0开始
由于List集合拥有索引,因此List集合迭代方式除了使用迭代器之外,还可以使用索引进行迭代。
for (int i = 0; i < list.size(); i++) {Student stu = (Student) list.get(i);System.out.println(stu);
}
3、ListIterator接口
在迭代过程中,如果我们使用了集合的方法对元素进行操作。就会导致迭代器并不知道集合中的变化,容易引发数据的不确定性。发生异常:java.util.ConcurrentModificationException
解决方法:
在迭代时,不要使用集合的方法操作元素。使用迭代器的方法操作。但是迭代器Iterator的方式只有 hasNext()
,next()
,remove()
;Iterator有一个子接口ListIterator
可以完成该问题的解决。通过List接口中的listIterator()
就可以获取。
//创建List容器List list = new ArrayList();//给容器中添加元素list.add("abc1");list.add("abc2");list.add("abc3");list.add("abc4");//遍历容器,当有元素为"abc2"时,添加一个"itcast"ListIterator it = list.listIterator();while(it.hasNext()){Object obj = it.next();if("abc2".equals(obj)){it.add("itcast");}}
注意:该列表迭代器只有List接口有。而且这个迭代器可以完成在迭代过程中的增删改查动作。
4、List常用子类介绍
首先我们来学习List下的常用集合ArrayList
、Vector
、LinkedList
集合。
ArrayList:是数组结构,长度是可变的(原理是创建新数组+复制数组),查询速度很快,增删较慢,不同步的。
Vector:可以增长的数组结构。同步的。效率非常低。已被ArrayList替代。
LinkedList:是链表结构,不同步的,增删速度很快,查询速度较慢。
ArrayList和LinkedList详细介绍可见我的另一篇博客:最详细的ArrayList和LinkedList区别及底层原理
五、集合-Set
1、Set介绍
List中是可以存放重复元素的。Collection接口中的另一个Set
集合中的元素就是不重复的。
Set:不包含重复元素的集合,不保证顺序。而且方法和Collection一致。Set集合取出元素的方式只有一种:迭代器。
Set:集合有多个子类,这里我们介绍其中的HashSet
、TreeSet
和LinkedHashSet
这三个集合。
HashSet:哈希表结构,不同步,保证元素唯一性的方式依赖于:hashCode(),equals()方法。查询速度快。
哈希表
哈希表底层使用的也是数组机制,数组中也存放对象,而这些对象往数组中存放时的位置比较特殊,当需要把这些对象给数组中存放时,那么会根据这些对象的特有数据结合相应的算法,算法这个对象在数组中的位置,然后把这个对象存放在数组中。而这样的数组就称为哈希数组,即就是哈希表。
当给哈希表中存放元素时,需要根据元素的特有数据结合相应的算法,这个算法其实就是Object中的hashCode
方法,既然Object的方法,那么大家都能用。
注意,如果两个对象hashCode方法算出结果一样,这样现象称为哈希冲突,这时会调用对象的equals方法,比较这两个对象是不是同一个对象,如果equals方法返回的是true,那么就不会把第二个对象存放在哈希表中,如果返回的是false,就会把这个值存放在哈希表中(去重)。
总结:
去重就是根据对象的hashCode和equals方法来决定的
2、HashSet
给HashSet中存放自定义对象时,需要复写对象中的hashCode
和equals
方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一。
// 创建自定义对象Student
public class Student {private String name;private int age;public Student(String name, int age) {super();this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Student [name=" + name + ", age=" + age + "]";}@Overridepublic int hashCode(){final int prime = 31;int result = 1;result = prime*result + age;result = prime*result + (name == null)? 0 : name.hashCode());return result;}@Overridepublic boolean equals(Object o){if(this == o){return true;}if(!(o instanceof Student)){throw new ClassCastException("错误");}Student s = (Student)o;return this.age == s.age && this.name.equals(s.name);}
}
class HashSetDemo {public static void main(String[] args) {//创建HashSet对象HashSet hs = new HashSet();//给集合中添加自定义对象hs.add(new Student("zhangsan",21));hs.add(new Student("lisi",22));hs.add(new Student("wangwu",23));hs.add(new Student("zhangsan",21));//取出集合中的每个元素Iterator it = hs.iterator();while(it.hasNext()){Student s = (Student)it.next();System.out.println(s);}}
}
3、LinkedHashSet
我们知道HashSet保证元素唯一,并且查询速度很快,可是元素存放进去是没有顺序的,那么我们要保证有序,还要速度快,在HashSet下面有一个子类LinkedHashSet
,它是链表和哈希表组合的一个数据存储结构。
public class LinkedHashSetDemo {public static void main(String[] args) {Set set = new LinkedHashSet();set.add("bbb");set.add("aaa");set.add("abc");set.add("bbc");//放入有顺序,取出有顺序for (Iterator it = set.iterator(); it.hasNext();) {System.out.println(it.next());}}
}
4、TreeSet
TreeSet集合是可以给对象进行排序的。当存放进去的对象,会根据对象的自身的特点进行自然顺序的排序。因此这里需要注意的当我们给TreeSet集合中存放自定义对象时,一定要保证对象自身具备比较功能,如何才能让对象自身具备比较功能呢?
当需要一个对象自身具备功能时,只需要这个对象实现Comparable
接口,并实现其中的compareTo
方法即可。
public class Student implements Comparable{private String name;private int age;public Student(String name, int age) {super();this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Student [name=" + name + ", age=" + age + "]";}/** 重写compareTo方法,建立学生的自然排序(对象的默认排序方式)。按照学生年龄排序。*/@Overridepublic int compareTo(Object o){if((o instanceof Student)){thorw new ClassCastException();}Student s = (Student)o;int temp = this.age - s.age;return temp == 0 ? this.name.compareTo(s.name) : temp;}
}
public class TreeSetDemo {public static void main(String[] args) {//创建TreeSet对象TreeSet ts = new TreeSet();//给集合中添加自定义对象ts.add(new Student("zhangsan",21));ts.add(new Student("lisi",22));ts.add(new Student("wangwu",23));ts.add(new Student("zhangsan",21));ts.add(new Student("zhaoliu",21));//取出集合中的每个元素Iterator it = ts.iterator();while(it.hasNext()){Student s = (Student)it.next();System.out.println(s);}}
}
总结:
TreeSet:可以对Set集合中的元素进行排序。元素自身具备自然排序,其实就是实现了Comparable接口重写了compareTo方法。
如果元素自身不具备自然排序,或者具备的自然排序不是所需要的,这时怎么办呢?
继续查看TreeSet集合说明,发现还有一个叫做Comparator
的接口,并且TreeSet集合的接口允许我们给其传递这样一个接口的子类对象。
其实就是在创建TreeSet集合时,在构造函数中指定具体的比较方式。需要定义一个类实现Comparator
接口,重写compare
方法。
到此为止:再往集合中存储对象时,通常该对象都需要覆盖hashCode
,equals
,同时实现Comparale
接口,建立对象的自然排序。通常还有一个方法也会复写toString()
;
六、集合-Map
1、介绍
在学习数组时,我们说如果和数组角标有一定的对应关系,是可以把数据存放在数组中,通过数组的角标来获取对应的数据,把这种方式称为查表法。
可是当我们的对象与对象之间有了对应的关系,我们需要把这样具有对应关系的一对数据存放起来怎么做呢?采用数组只能存放具有简单对应关系的数据,不太合适。采用Collection集合,可是只能存放一个对象,无法维护这种关系。怎么做呢?
Java中我们提供了相应的其他容器来解决,这个容器就是Map
集合。将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。
例:
public class MapDemo {public static void main(String[] args){//创建Map对象Map<String,String> map = new HashMap<String,String>();//给map中添加元素map.put("星期一", "Monday");map.put("星期日", "Sunday");//当给Map中添加元素,会返回key对应的原来的value值,若key没有对象的值,返回nullSystem.out.println(map.put("星期一", "M")); /根据指定的key获取对应的valueString en = map.get("星期日");System.out.println(en);//根据key删除元素,会返回key对应的value值String value = map.remove("星期日");System.out.println(value);}
}
2、Map集合中的方法
2.1、keySet方法
由于Map中的所有key都是不重复的,所以获取到Map中的所有key应该会存放在Set集合中,这个方法叫:keySet
public class MapDemo {public static void main(String[] args){// 创建集合Map<String,String> map = new HsahMap<String,String>();// 添加元素map.put("星期一","Monday");map.put("星期日", "Stringunday");//获取Map中的所有keySet<String> set = map.keySet();//遍历存放所有key的Set集合Iterator<String> it = keySet.iterator();while(it.hasNext()){String key = it.next();String value = map.get(key);System.out.println(key+"="+value);}}
}
2.2、entrySet方法
这个方法的描述是得到所有key和value的映射关系,这是啥意思呢?假设Map中存放一对一对的夫妻,那么entrySet获取到是每一对夫妻这种夫妻关系。
public class MapDemo {public static void main(String[] args) {//创建Map对象Map<String, String> map = new HashMap<String,String>();//给map中添加元素map.put("星期一", "Monday");map.put("星期日", "Sunday");//获取Map中的所有key与value的对应关系Set<Entry<String,string>> entry = map.entrySet();//遍历集合Iterator<Entry<String,String>> it = entry.iterator();while(it.hasNext()){//得到每一对对应关系Entry<String,String> entry = it.next();//通过每一对对应关系获取对应的keyString key = entry.getKey();//通过每一对对应关系获取对应的valueString value = entry.getValue();System.out.println(key+"="+value);
注意:Map集合不能直接使用迭代器或者foreach进行遍历。但是转成Set之后就可以使用了。
3、Map常用子类介绍
Map有多个子类,这里我们主要讲解常用的HashMap和TreeMap集合;
HashMap:数据结构-哈希表。不是同步的,允许null作为键和值。HashMap下有个子类LinkedHashMap
基于链表+哈希表。可以保证map集合有序(存入和取出的顺序一致)。
TreeMap:数据结构-二叉树。不是同步的。可以对map集合中的键进行排序。
3.1、HashMap
练习:学生对象(姓名,年龄)都有自己的归属地
public class HashMapTest {public static void main(String[] args) {//1,创建hashmap集合对象。Map<Student,String> map = new HashMap<Student,String>();//2,添加元素。map.put(new Student("lisi",28), "上海");map.put(new Student("wangwu",22), "北京");map.put(new Student("zhaoliu",24), "成都");map.put(new Student("zhouqi",25), "广州");map.put(new Student("wangwu",22), "南京");//3,取出元素。keySet entrySet//Set<Student> keySet = map.keySet();//for(Student key : keySet){}for(Student key : map.keySet()){String value = map.get(key);System.out.println(key.toString()+"....."+value);}
注意:当给HashMap中存放自定义对象时,如果自定义对象作为key存在,这时要保证对象唯一,必须复写对象的hashCode
和equals
方法
HashMap的详细深入了解请见我的另一篇博客:HashMap大家族
3.2、TreeMap
练习:按照学生的年龄进行从小到大的排序。 TreeMap。
public class TreeMapTest {public static void main(String[] args) {// 1,创建TreeMap集合对象。Map<Student, String> map = new TreeMap<Student, String>();// 2,添加元素。map.put(new Student("lisi", 28), "上海");map.put(new Student("wangwu", 22), "北京");map.put(new Student("zhaoliu", 24), "成都");map.put(new Student("zhouqi", 25), "广州");map.put(new Student("wangwu", 22), "南京");//3,取出所有元素,entrySet()for(Map.Entry<Student,String> me : map.entrySet()){Student key = me.getkey();String value = me.getvalue();System.out.println(key+"::"+value);}
给TreeMap存放自定义对象,自定义对象作为key进行排序时,自定义对象必须具备比较功能,即实现Comparable接口。如果需要特定方式进行比较,我们也可以给TreeMap集合传递自定义的比较器进行比较。
4、Map练习-字母出现次数
获取字符串中每一个字母出现的次数。要求返回结果个格式是 a(1)b(2)d(4)…;
public class Maptext{public static void main(String[] args) {String str = "awaa+acr=ebarct,btydui[efgkiryuiop";str = getCharCount(str);System.out.println(str);}//获取字符串中的字母出现次数。public static String getCharCount(String str){//1.字符串转数组char[] chs = str.toCharArray();//2.定义表TreeMap<Character,Integer> map = new TreeMap<Character,Integer>();//3.遍历字符数组for(int i = 0;i<chs.length;i++){if(!(chs[i] >= 'a' && chs[i]<='z' || chs[i]>='A' && chs[i]<='Z')){continue;}//4,将遍历到的字母作为键去查map这个表。获取对应的次数。Integer value = map.get(chs[i]);//5,有可能要查询的字母在表中不存在对应的次数,需要判断。//如果返回是null,说明字母没有对应的次数。就将这个字母和1存储到表中。if(value == null){//将字母和1存储。map.put(chs[i],1);}else{value++;map.put(chs[i],value);}}return mapToString(map);}/** 将map集合中的键值转成 格式是 a(1)b(2)d(4)......* map中有很多数据,无论是多少个,什么类型,最终都变成字符串。* StringBuffer 这个容器就符合这个需求。如果是单线程,建议使用StringBuilder。* */private static String mapToString(Map<Character,Integer> map){//1,明确容器。StringBuilder sb = new StringBuilder();//2.遍历map集合for(Character key : map.keySet()){Integer value = map.get(key);sb.append(key+"("+value+")");}return sb.toString();}
}
七、泛型
1、泛型概述
1.1、什么是泛型
在以前,集合中是可以存放任意对象的,只要把对象丢尽集合后,那么这时他们都会被提升成Object类型。当我们在取出每一个对象,并且进行相应的操作,这时必须采用类型转换。
但是,由于集合中什么类型的元素都可以存储。导致取出时,如果出现强转就会引发运行时 ClassCastException。怎么来解决这个问题呢?
幸好,JDK1.5以后,出现了解决方案,使用容器时,必须明确容器中元素的类型。这种机制称之为 :泛型
。
1.2、泛型格式
对象类名称 <数据类型>
这种格式不是很难理解,<>尖括号也是括号,往括号里面写东西其实就是在传递参数。
1.3、泛型的特点
1,安全机制。
2,将运行时期的ClassCastException,转移到了编译时期变成了编译失败。
3,泛型技术,是给编译器使用的技术。
4,避免了强转的麻烦。
2、泛型的使用
2.1、简单使用
要求:定义容器,要求容器中元素唯一,并且按指定的方法排序。
思路:
1、元素唯一,那么只能使用Set集合
2、还要对元素排序,那么就使用TreeSet集合
3、还要按照指定方式排序,那么就要让集合自定具备比较功能,即就是要让要给集合传递一个比较器。
public class GenericDemo {public static void main(String[] args) {// 定义容器,要求容器中元素唯一,并且按指定的方法排序。并且使用匿名内部类的形式来完成比较器传递TreeSet<String> set = new TreeSet<String>(new Comparator<String>(){@Overridepublic int compare(String o1, String o2) {int temp = o1.length() - o2.length();return temp == 0 ? o1.compareTo(o2):temp;}});set.add("abc");set.add("bbb");set.add("sh");set.add("itcast");for (String str : set) {System.out.println(str);}}
}
2.2、泛型类
小知识:泛型基本有了个了解之后,在这里要给大家说下,泛型这种技术是编译时期的技术,也就是说在编译的时候编译会根据所写泛型进行类型的检查,当编译通过后,生成的class文件中,是没有泛型这个机制的,这种机制也称为泛型的擦除。这个小知识,作为了解。
为了避免向下转型带来的异常发生风险,我们使用泛型类来解决这个问题。
class Tool<T>{private T obj;public T getObj() {return obj;}public void setObj(T obj) {this.obj = obj;}
}
public class GenericDemo2 {public static void main(String[] args) {Tool<String> t = new Tool<String>();String s = t.setobj();System.out.println(s);}
2.3、泛型方法
若是把泛型都定义在类上了,也就是说在创建类的对象时候,需要明确具体的类型,但是这个局限性很大,这时我们可以把泛型定义方法上,这样的方法就称为泛型方法。
class Utils<W>{//打印功能的方法,可以打印任意类型,把泛型定义方法上public <T> void print(T t){System.out.println(t);}//也可以使用类上的泛型public void methed(W w){System.out.println(w);}//如果方法是静态,那么方法上是无法使用类上的泛型,因为类上的泛型是随着对象的创建才明确出来的public static <Q q> void show(Q q){System.out.println("show.."+q);}
}
2.4、泛型接口
定义在接口中的泛型,再使用的时候:
1.子类已经明确具体的类型,那么子类在实现的时候就把类型明确出来。
2.子类不明确具体类型,需要子类创建对象时才能明确,这时在子类描述时可以在子类上继续加泛型。
interface Inter<E>{void show();
}
//子类明确具体类型
class InterImpl1 implements Inter<String>{public void show(){System.out.println("show run");}
}
//如果子类不明确具体数据类型,这时可以在子类上继续使用泛型
class InterImpl2<T> implements Inter<T>{public void show(){System.out.println("show run");}
}
public class GenericDemo2 {public static void main(String[] args) {new InterImpl().show();new InterImpl2<String>().show();
}
3、泛型通配符及限定
3.1、泛型通配符
示例:
public class Solution {public static void main(String[] args){Set<Student> set = new HashSet<Student>();set.add(new Student("zhangsan",31));set.add(new Student("lisi",23));set.add(new Student("wangwu",21));printCollection(set);List<Worker> list = new ArrayList<Worker>();list.add(new Worker("xiaoqiang", 45));list.add(new Worker("huaan", 41));list.add(new Worker("daming", 47));printCollection(list);List<String> list2 = new ArrayList<String>();list2.add("xiaoqiang");list2.add("huaan");list2.add("daming");printCollection(list2);}public static void printCollection(Collection<?> list){for (Iterator<?> it = list.iterator(); it.hasNext();) {System.out.println(it.next());}}
}
无法确定具体集合中的元素类型是什么,就可以使用泛型的通配符机制来完成。
总结:
当使用泛型类或者接口时,传递的具体的类型不确定,可以通过通配符(?)表示。但是一旦使用泛型的通配符机制后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。
3.2、泛型限定
如果想要对被打印的集合中的元素类型进行限制,只在指定的一些类型,进行打印。怎么做呢?
例如:
在上述例子中,只需要打印学生和工人的集合。找到学生和工人的共性类型Person,使用泛型的限定。
? extends Person
: 接收Person类型或者Person的子类型。
public static void printCollection(Collection <? extends Person> list){for(Interator<Collection<? extents Person> it = list.interator();it.hasNext();){System.out.println(it.next());}
}
限定泛型的上限:? extends E
:接收E类型或者E的子类型。
限定泛型的下限:? super E
:接收E类型或者E的父类型。
当在集合或者其他地方使用到泛型后,那么这时一旦明确泛型标识的类型,那么在使用的时候只能给其传递和标注类型匹配的类型,否则就会报错。
八、异常
1、概述
1.1、异常和错误的区别
- 异常:程序在运行期间发生了异常,通常可以有针对性的处理方式。
- 错误:程序在运行期间发生了错误,通常不会有针对性的处理方式。错误的发生往往都是 系统级别的问题,都是Jvm所在系统发生的并反馈给-vm的,无法针对处理,只能修正代码
class ExceptionDemo
{public static void main(String[] args) {int[] arr = new int[3];System.out.println(arr[0]);System.out.println(arr[3]);//该句运行时发生了ArrayIndexOutOfBoundsException,导致程序无法继续执行。程序结束。System.out.println("over"); //由于上面代码发生了异常,此句代码不会执行}
}
1.2、异常发生的过程
1、运行时发生了问题,这个问题JVM认识,这个问题java本身有
2、描述:描述内容有问题的名称、问题的内容、问题的发生位置,既然有这么多的信
息,java就将这些信息直接封装到对象中。
3、抛出jvm:jvm进行最终处理,将问题的名称,信息,位置都显示屏幕上。
1.3、异常的应用
在编写程序时,必须要考虑程序的问题情况。比如在写功能时,需要接受参数,在使用功能中使用接受到的参数时,首先需要先对参数数据进行合法的判断,数据若不合法,应该告诉调用者,传递合法的数据进来。这时需要使用异常这种方法来告诉调用者。所以定义程序需要考虑程序的健壮性。
异常应用例如:
//描述人
class Person{private String name;private int age;Person(String name,int age){//加入逻辑判断。if(age<0 || age>200){/*//System.out.println("年龄数值错误");//return;//终止初始化。这样做虽然可以编译并运行看到提示消息,但是问题却依然发生,程序还在继续执行。并打印p对象。这是不合理的。人对象初始化过程中已经出了问题,为什么要会对人对象操作?所以应该将问题暴露出来,让使用该程序的调用者知道。所以要使用异常来解决。*/throw new IllegalArgumentException(age+",年龄数值非法");}this.name = name;this.age = age;}//定义Person对象对应的字符串表现形式。覆盖Object中的toString方法。public String toString(){return "Person[name="+name+",age="+age+"]";}
}
class ExceptionDemo4 {public static void main(String[] args) {Person p = new Person("xiaoming",-20);System.out.println(p);}
}
1.4、自定义异常
之前的几个异常都是java通过类进行的描述。并将问题封装成对象,异常就是将问题封装成了对象。这些异常不好认,书写也很不方便,能不能定义一个符合我的程序要求的问题名称。既然JDK中是使用类在描述异常信息,那么我们也可以模拟Java的这种机制,我们自己定义异常的信息,异常的名字,让异常更符合自己程序的阅读。准确对自己所需要的问题进行类的描述。
2、异常体系
Throwable 是异常和错误的超类(父类),它是异常体系的顶层类
自定义异常产生问题,应该怎么解决呢?
自定义异常被抛出,必须是继承Throwable
,或者继承Throwable的子类。该对象才可以被throw抛出。
这个异常体系具备一个特有的特性:可抛性:可以被throw
关键字操作。
异常继承选择父类时,更为确切是继承Exception。
例如:
class NoAgeException extends Exception{/*为什么要定义构造函数,因为看到Java中的异常描述类中有提供对问题对象的初始化方法。*/NoAgeException() {}NoAgeException(String message) {}
}
这个版本还是编译失败,因为没有声明,RuntimeException描述中有明确说明,这个运行时异常以及其子类都无需进行声明。可以将自定义的异常继承RuntimeExceptio。
但是缺少异常提示信息,父类构造函数中有关于异常信息的操作,那么在自己定义的异常中需要将这些信息传递给父类,让父类帮我们进行封装即可。
例如:
class NoAgeException extends RuntimeException{/*为什么要定义构造函数,因为看到Java中的异常描述类中有提供对问题对象的初始化方法。*/NoAgeException(){super();}NoAgeException(String message) {super(message);// 如果自定义异常需要异常信息,可以通过调用父类的带有字符串参数的构造函数即可。}
}
3、异常的分类
1、Exception异常
在函数内抛出Exception,编译失败,因为编译器在检查语法时发生了错误。该程序已经出现问题,Java认为这个程序本身存在隐患,需要捕获或者声明出来(你要么把问题处理,要么把问题标识出来让调用知道)。
2、RuntimeException异常
这个异常不需要捕捉和声明,为什么呢?不是功能本身发生的异常,而是因为比如调用者传递参数错误而导致功能运行失败。这也是问题,需要通过异常来体现,但是这个异常不要声明出来的。
4、声明和捕获
声明:将问题标识出来,报告给调用者。如果函数内通过throw抛出了编译时异常,而没有捕获,那么必须通过throws进行声明,让调用者去处理。
捕获:Java中对异常有针对性的语句进行捕获。
捕获格式:
try
{//需要被检测的语句。
}
catch(异常类 变量)//参数。
{//异常的处理语句。
}finally
{//一定会被执行的语句。
}
应用:
class NoAgeException extends RuntimeException{NoAgeException() {super();}NoAgeException(String message) {super(message);}
}
class Person{private String name;private int age;Person(String name,int age)//throws NoAgeException {//加入逻辑判断。if(age<0 || age>200) {throw new NoAgeException(age+",年龄数值非法");}this.name = name;this.age = age;}//定义Person对象对应的字符串表现形式。覆盖Object中的toString方法。public String toString() {return "Person[name="+name+",age="+age+"]";}
}
class ExceptionDemo{public static void main(String[] args) {try{Person p = new Person("xiaoming",20);System.out.println(p);}catch (NoAgeException ex){System.out.println("异常啦");}System.out.println("over");}
}
构造函数到底抛出这个NoAgeException是继承Exception呢?还是继承RuntimeException呢?
继承Exception,必须要throws声明,一声明就告知调用者进行捕获,一旦问题处理了调用者的程序会继续执行。
继承RuntimeExcpetion,不需要throws声明,这时调用是不可能编写捕获代码的,因为调用根本就不知道有问题。一旦发生NoAgeException,调用者程序会停掉,并有jvm将信息显示到屏幕,让调用者看到问题,修正代码。
throw和throws区别:
- throw用在函数内。throws用在函数上。
- thorw抛出的是异常对象。throws用于进行异常类的声明,后面异常类可以有多个,用逗号隔开。
5、finally关键字
有一些特定的代码无论异常是否发生,都需要执行。因为异常会引发程序跳转,导致有些语句执行不到。无法满足这个需求。异常捕获处理时java提供解决方案,finally就是解决这个问题的,这个代码块中存放的代码都是一定会被执行。
应用例如:
void add(Data data)throws NoAddException{//1、连接数据库。try{//2、添加数据。//添加数据时发生了异常情况。throw new SQLException();程序跳转,就执行不到断开连接。//而断开连接必须要执行,因为不执行,连接资源在浪费。//无论是否发生问题,都需要执行断开连接的动作,从而释放资源。}catch(SQLException e){//解决数据库的问题。//同时将问题告诉调用者。throw new NoAddException();}finally{//3,断开连接。}
}
只要程序中使用到了具体的资源(数据库连接,IO资源,网络连接socket等)需要释放,都必须定义在finally中。你在定义程序,只要问题发生与否,指定程序都需要执行时,就定义finally中。包括return的内容也会直接执行。
异常在继承或者实现中的使用细节:
- 子类覆盖父类方法时,如果父类的方法声明异常,子类只能声明父类异常或者该异常的子类,或者不声明。越来越精细
- 当父类方法声明多个异常时,子类覆盖时只能声明多个异常的子集 越来越精细。
- 当被覆盖的方法没有异常声明时,子类覆盖时无法声明异常的。
举例:父类存在这种情况,接口也有这种情况。
问题:接口中没有声明异常,而实现的子类覆盖方法时发生了异常,怎么办?
无法进行throws声明,只能catch的捕获。万一问题处理不了呢?catch中继续throw 抛出,但是只能将异常转换成RuntimeException子类抛出。
九、反射
反射是框架设计的灵魂,它将类成员其他部分封装成对象。
(使用的前提条件:必须先得到代表的字节码的class,Class类用于表示.class文件(字节码))
1、反射的概述
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制
。
总结:反射就是把Java类中的各种成分映射成一个个Java对象。
例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
反射的好处:
- 运行过程中操作各种类的对象。
- 可以解耦,提高可拓展性。
2、反射的使用
2.1、获取类
编写流程:
先写一个Student类,获取Class对象的三种方式:
1.Object ----》 getClass();
2.任何数据类型(包括基本数据类型),都有一个静态的 class 属性。
3.通过Class类的静态方法:forName(String className)。
public class Fanshe {public static void main(String[] args) {//第一种方式获取Class对象 Student stu1 = new Student();//new的时候 产生一个Student对象,一个Class对象。Class stuClass = stu1.getClass();//获取Class对象System.out.println(stuClass.getName()); //这个方法多此一举,属于脱裤子放屁第二种方式获取Class对象Class stuClass2 = Student.class();System.out.println(stuClass == stuClass2);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个.是同一个//第三种方式获取Class对象try{Class StuClass3 = class.forName("Student"); //注意此字符串必须是真实路径,就是带包名的类路径,包名.类名System.out.println(stuClass3 == stuClass2);//判断三种方式是否获取的是同一个Class对象}catch(ClassNotFoundExcepyion e){e.printStackTrace();}}
}
注意:在运行期间,一个类,只有一个Class对象产生。
三种方式常用第三种,第一种对象都有了还要反射干什么。第二种需要导入类的包,依赖太强,不导包就抛编译错误。一般都第三种,一个字符串可以传入也可写在配置文件中等多种方法。
2.2、获取构造方法并
//通过Class对象可以获取某个类中的:构造方法、成员变量、成员方法;并访问成员:
public static void main(String[] args) throws Exception {//1.加载Class对象Class cls = Class.forName("Student");//2.获取所有公有构造方法Constructor[] conArray = cls.getConstructors();for(Constructor c : conArray){System.out.println(c);}//3.获取所有构造方法conArry = cls.getDeclaredConstructors();for(Constructor c : conArray){System.out.println(c);}//4.获取公有、无参的构造方法Constructor con = cls.getConstructor(null);//1>.因为无参所以类型是null,注意是类型,而不是无参才些null//2>.返回描述这个无参构造函数的类对象//调用构造方法con = cls.getDeclaredConstructor(char.class);//参数类型.class,多个用","隔开。Object obj = con.newInstance('我');
}
newInstance
是 Constructor
类的方法(管理构造函数的类)
2.3、获取并调用成员变量
例如:
public class Student {public Student(){}public String name;protected int age;char sex;private String phoneNum;@Overridepublic String toString() {return "Student [name=" + name + ", age=" + age + ", sex=" + sex+ ", phoneNum=" + phoneNum + "]";}
}
class Text{public static void main(String[] args) throws Exception {//1.获取class对象Class cls = Class.forName("Student");//2.获取所有公有字段Field[] fieldArray = cls.getField();for(Field f : fieldArray){System.out.println(f);}//3.获取所有的字段fieldArray = stuClass.getDeclaredFields();for(Field f : fieldArray){System.out.println(f);}//4.调用Field f = cls.getField("name");Object obj = cls.getConstructor().newInstance();//产生Student对象,相当于===> Student s = new Student();//为字段设置值f.set(obj, "刘德华");//为Student对象中的name属性赋值--》stu.name = "刘德华"}
}
由此可见:
调用字段时,需要传递两个参数。
第一个参数:要传入设置的对象,第二个参数:要传入实参
2.4、获取并调用成员方法
public class Student {public void show1(String s){System.out.println("调用了:公有的,String参数的show1(): s = " + s);}protected void show2(){System.out.println("调用了:受保护的,无参的show2()");}void show3(){System.out.println("调用了:默认的,无参的show3()");}private String show4(int age){System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): age = " + age);return "abcd";}
}
class MethodClass {public static void main(String[] args) throws Exception {//1.获取Class对象Class cls = Class.forName("Student");//若是包下的Class则用"."点出来//2.获取所有公有方法Method[] methodArray = cls.getMethods();for(Method m : methodArray){System.out.println(m);}//3.获取所有的方法methodArray = cls.getDeclaredMethods();for(Method m : methodArray){System.out.println(m);}//4.获取公有的show1()方法Method m = cls.getMethod("show1",String.class);//实例化一个Student对象Object obj = cls.getConstruct().newInstance();m.invoke(obj,"刘德华");//5.获取私有的show4()方法m = cls.getDeclaredMethod("show4", int.class);m.setAccessible(true);//暴力解除私有限定Obj result = m.invoke(obj,20);}
}
2.5、反射main方法
public class Student {public static void main(String[] args) {System.out.println("main方法执行了。。。");}
}
/*** 获取Student类的main方法、不要与当前的main方法搞混了*/
class Main {public static void main(String[] args) {try{//1、获取Student对象的字节码Class clazz = Class.forName("Student");//2、获取main方法Method methodMain = clazz.getMethod("main", String[].class);//第一个参数:方法名称,第二个参数:方法形参的类型//3、调用main方法//methodMain.invoke(null, new String[]{"a","b","c"});//第一个参数,对象类型,因为方法是static静态的,所以为null可以,第二个参数是String数组,这里要注意在jdk1.4时是数组,jdk1.5之后是可变参数//这里拆的时候将 new String[]{"a","b","c"} 拆成3个对象。。。所以需要将它强转。//方式一methodMain.invoke(null, (Object)new String[]{"a","b","c"});//方式二//methodMain.invoke(null, new Object[]{new String[]{"a","b","c"}});}catch(Exception e){e.printStackTrace();}}
}
2.6、通过反射越过泛型检查
/** 通过反射越过泛型检查* 例如:有一个String泛型的集合,怎样能向这个集合中添加一个Integer类型的值?*/
public class Demo {public static void main(String[] args) throws Exception{ArrayList<String> strList = new ArrayList<>();strList.add("aaa");strList.add("bbb");// 获取ArrayList的Class对象,反向的调用add()方法,添加数据Class listClass = strList.getClass(); //得到 strList 对象的字节码 对象// 获取add()方法Method m = listClass.getMethod("add", Object.class);//两个参数,第一个方法名称,第二个参数类型// 调用add()方法m.invoke(strList, 100);// 遍历集合for(Object obj : strList){System.out.println(obj);}}
}