@w460461339
2016-11-16T07:03:22.000000Z
字数 7266
阅读 910
Java基础
今天算是深入讲了一下集合Collection下List的几个实现子类的特点以及一些用法吧。
昨天说过,这三者都是List下的实现子类,只不过底层不同。具体有什么不同,见下。
ArrayList:
底层是数组,所以查询快,增删慢。
同时线程不安全,效率高。
Vector
底层是数组,所以查询快,增删慢。
同时线程安全,效率低。
LinkedList
底层是链表,所以查询慢,增删快。
同时线程不安全,效率高。
由于C以及C++里面也没认真学枚举,这里枚举也没太大用处,只是说一下。
public static void main(String args[]){
Vector v=new Vector();
v.add("Hello");
v.add("World");
Enumeration enu=v.element();//返回枚举对象
while(enu.hasMoreElement()){//这个用法和迭代器基本相同。
String s=(String)enu.nextElement();
System.out.println(s);
}
}
由于已经有了迭代器,所以基本不用这个写法了。
这里首先要注意点,若是String类型的对象,由于它本身实现了equals方法,所以比较的是内容而不是地址。而自己写的类,若实现euqals,会自动调用Object类中的equals方法,比较的就是地址了。所以切记切记,在自己写的类中实现(覆盖)一下equals方法。
要去除重复对象,两种方法。第一种,创建新的集合,将原来结合的元素一个一个导入过来,若有重复的,则不导入。这里由于会用到集合的contains方法,而contains方法的底层仍然是该对象的equals方法,所以如上段中所提及的, 若不复写equals方法,比较的将会是地址,这样就会毫无意义,达不到去重复的要求。所以请复写equals方法。
Way1:创建新集合
import java.util.ArrayList;
import java.util.Iterator;
class Student{
private int age;
public Student(int age){
this.age=age;
}
@Override
public boolean equals(Object obj) {
Student st=(Student)obj;
if(this.age==st.age){
return true;
}else{
return false;
}
}
public int getage(){
return age;
}
}
public class Day16 {
public static void main(String[] args) {
ArrayList array=new ArrayList();
Student s1=new Student(10);
Student s2=new Student(20);
Student s3=new Student(10);
array.add(s1);
array.add(s3);
array.add(s2);
ArrayList array2=new ArrayList();
Iterator it = array.iterator();
while(it.hasNext()){
Student st=(Student)it.next();
if(!array2.contains(st)){
array2.add(st);
}
}
Iterator it2 = array2.iterator();
while(it2.hasNext()){
Student st=(Student)it2.next();
System.out.println(st.getage());
}
}
}
第二种,不用创建新集合,只要不断的将当前位置之后的,与当前位置重合的元素拿掉即可。此处注意,若3号位和3号位均为重合元素,当3号位的拿掉之后,4号位又变成3号位,所以在拿掉元素之后,需要重新判断一下当前位置。
Way2
class Student{
private int age;
public Student(int age){
this.age=age;
}
@Override
public boolean equals(Object obj) {
Student st=(Student)obj;
if(this.age==st.age){
return true;
}else{
return false;
}
}
public int getage(){
return age;
}
}
public class Day16 {
public static void main(String[] args) {
ArrayList array=new ArrayList();
Student s1=new Student(10);
Student s2=new Student(20);
Student s3=new Student(10);
Student s4=new Student(10);
Student s5=new Student(20);
Student s6=new Student(30);
array.add(s1);
array.add(s2);
array.add(s3);
array.add(s4);
array.add(s5);
array.add(s6);
ArrayList array2=new ArrayList();
for(int i=0;i<array.size();i++){
for(int j=i+1;j<array.size();j++){
if(array.get(i).equals(array.get(j))){
array.remove(j);
j--;//移除后再次判断当前位置
}
}
}
Iterator it = array.iterator();
while(it.hasNext()){
Student st=(Student)it.next();
System.out.println(st.getage());
}
}
}
Vector以及LinkedList都提供提供了一些特有的方法。利用这些方法,可以比较方便的实现堆栈。这些方法有:
getLast(); getFirst(); addFirst();
addLast; removeLast(); removeFirst();
下面只演示利用LinkedList创建堆栈的过程:
class MyStack{
private LinkedList myList;
public Mystack(){
myList=new LinkedList();
}
public add(Object obj){
myList.addFirst(obj);
}
public get(){
return myList.removeFirst();
}
}
首先,我们面对的问题时这样的。当往我们创建的ArrayList对象中添加元素的时候,不同类型的元素添加进去是不会报错的(因为ArrayList里面向上转型成为了Object类了)。但是当我们遍历时,由于存在不同类型的元素,利用向下转型就会有问题。但是这只有当运行的时候才会报错。我们更希望在一开始的时候就将错误暴露出来,这样,我们可以提早发现和修改。此时我们就需要泛型。
其实在利用代码补全功能的时候,ArrayList是这样的:
ArrayList<E>
其中的< E >就是泛型,虽然ArrayList可以储存各种元素,但一旦这么写了,E就限定了它传入的类型,只能是E。
//注意尖括号里面的只能是包装类或者类或者自定义类,不能使八大基本类型
public static void main(String arges[]){
//注意,等号两边都要写。
ArrayList<String> array = new ArrayList<String>();
array.add("Hello");//没问题
array.add(1);//报错
array.add("world");
Iterator it = array.iterator();
while(it.hasNext()){
String s=it.next();//此时由于存在泛型,可以不用向下转型了
System.out.println(s);
}
}
泛型方法,指的是在调用该方法的时候,不知道对象的类型,所以写成泛型:
public <T> T myFunction(T c){ }
第一个< T >表示声明这个方法泛型方法;第二个T表示返回类型为T,由运行时定义;第三个T表示传入参数类型为T,由输入时定义。
class myClass{
public <T> T myFunction(T c){
}
//都可以
public <T> T myFunction2(Class<T> c){
T t=c.newInstance();
return t;
}
}
第二种方法解释(反射不懂的话后面再说吧):
Class 首先是个Class对象,描述的意思是构建T这个类型的模板
T 是个T对象,描述的意思是这个对象是T这个类型。
打个比方,T=人的时候,T a表示的意思是:a=具体某个人,具体某个人可能是张三可能是李四……(好土);
Class a表示的意思是:a=人类,这个种族。有点近似其他语言的type的意思。
什么地方使用呢:你只能提供这个type却没办法提供一个具体的实例的时候(你只能告诉卖家我要买苹果却不能拿一个苹果给他看告诉它我要买这个东西的时候)。网上常见的例子就是利用反射实例化1个T对象...etc.
其实泛型接口本身不难,麻烦的主要是实现泛型接口的子类。另外,之所以需要将接口定义成泛型,是因为接口中的某个抽象方法不知道其具体的返回类型,所以需要定义成泛型。这样,就可以理解为,泛型接口中一定有个抽象泛型方法。
泛型接口一般是这样的,当某个子类去实现一个泛型接口的时候会出现两种情况:其一,实现的时候不知道接口的具体类型;其二,实现的时候已知接口的具体类型。
泛型接口的定义
//泛型接口的定义方法,注意interface前面不能有Class
interface myInterface<T>{
public abstract void myPrint();
}
泛型接口的实现(已知具体实现类型)
class interface myInterface<T>{
public abstract T myPrint();
}
//加入String,表示已知实现时的具体类型
class myClass2 implements myInterface<String>{
public void myPrint(){
}
}
泛型接口的实现(未知具体类型)
class interface myInterface<T>{
public abstract T myPrint();
}
//表示未知类型,前后都加上T
class myClass2<T> implements myInterface<T>{
public void myPrint(){
}
}
泛型接口的实现(未知具体实现类型的错误案例)
class interface myInterface<T>{
public abstract T myPrint();
}
//在myClass2后面少了<T>,报错
class myClass2 implements myInterface<T>{
public void myPrint(){
}
}
通配符主要用在声明变量和定义对象的时候用的。之前说过,在使用泛型的时候,对象类型必须和泛型类型一致,差一点点都不行:
class Person{
}
class Student extends Person{
}
class MyStudent<T>{
public <T> T print(T t){
}
}
public static void main(String args[]){
MyStudent<Person> myPerson= new Person();//可以
MyStudeng<Person> myPerson2=new Studeng();//虽然Student是Person的子类,但由于定义了泛型,对象类型只能严格的是Person
}
< ? >表示任意类型都可以,这样和不用泛型差别也不大,只是可以消除Warning…
class Person{
}
class Student extends Person{
}
class MyStudent<T>{
public <T> T print(T t){
}
}
public static void main(String args[]){
//用了<?>通配符,任何类型都可以。
MyStudent<?> myPerson= new Person();//可以
MyStudent<?> myPerson2=new Student();//可以
}
< ? extends E >表示任何E以及其子类都可以。可以理解为?处是我们的要填进去的类型,任何extends E的类型都可以
class Person{
}
class Student extends Person{
}
class Animal{
}
class MyStudent<T>{
public <T> T print(T t){
}
}
public static void main(String args[]){
//用了<? extends E>通配符,任何E及其子类都可以。
MyStudent<? extends Person> myPerson= new Person();//可以
MyStudent<? extends Person> myPerson2=new Student();//可以
MyStudent<? extends Person> myPerson3=new Animal();//不可以
}
< ? super E > 表示任何E及其E的父类都可以。
class Person{
}
class Student extends Person{
}
class Animal{
}
class MyStudent<T>{
public <T> T print(T t){
}
}
public static void main(String args[]){
//用了<? extends E>通配符,任何E及其子类都可以。
MyStudent<? super Student> myPerson= new Person();//可以
MyStudent<? super Student> myPerson2=new Student();//可以
MyStudent<? super Student> myPerson3=new Animal();//不可以
}
就是for each循环,只是注意不能在其中对集合进行修改。它的底层实现貌似是迭代器,可以通过反编译工具来看看。
public static void main(String args[]){
ArrayList<Integer> array = new ArrayList<Integer>();
array.add(1);//这些都进行了自动装箱,从int类型变成包装类型Integer
array.add(30);
array.add(20);
array.add(10);
for(Integer i:array){
System.out.println(i);//i就和迭代器的it.next()一样
}
}
就是把类型中的方法导入。之前只是import到这个类,现在直接import这个方法,可以在使用的时候不写类名,而且必须是静态方法。好吧,其实没它大作用。当遇到同名方法的时候,还是需要写类名的。所以它其实并没与什么卵用。
//注意要写 import static
import static java.lang.Math.abs;
public class Day16 {
public static int abs(){};
public static void main(String[] args) {
abs(-1);//可以这么写
abs(-1);//若存在同名方法,就不能这么写了,必须把类名写进去。
}
}
当传入的参数不知道要多少个的时候,可以用可变参数,格式:
访问属性 返回类型 方法名(参数类型...变量名){};
它的底层实现是用一个数组。
public static int Sum(int...a){
}
public static void main(String args[]){
int result;
result=Sum(1,2,3,4);//可以
result=Sum(1,2,3);//可以
}
注意,请把可变参数放在最后一个,不然会出现问题
public static int Sum(int...a,int b ){//不好
}
public static int Sum2(int b, int...a){//可以
}
public static void main(String args[]){
int result;
result=Sum(1,2,3,4);//不可以,1 2 3 4都存储在a中,b得不到值,报错
result=Sum(1,2,3);//不可以,同理
}
之前使用toArray把集合转成数组,现在可以将数组转成集合,用定义在Arrays类中的静态asList方法:
public static <T> List<T> asList(T...t);
public static void main(String args[]){
List<String> list=Arrays.asList("Hello","world","java");
list.add("hi");//不允许改变集合长度
list.remove(1);//不允许改变集合长度
list.set(1,"hi");//可以改变集合元素
}