Java基础学习20之类集、类加载器

755 阅读5分钟

Java基础学习20之类集、类加载器

「这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战」。

关于作者

  • 作者介绍

🍓 博客主页:作者主页
🍓 简介:JAVA领域优质创作者🥇、一名在校大三学生🎓、在校期间参加各种省赛、国赛,斩获一系列荣誉🏆
🍓 关注我:关注我学习资料、文档下载统统都有,每日定时更新文章,励志做一名JAVA资深程序猿👨‍💻


类集

认识类集

如果现在要想保存多个对象,肯定使用对象数组完成,但是对象数组本身有一个最大问题在于确定了数据的长度,所以后来使用了链表完成了动态对象数组的开发,可是链表的开发难度实在是很大,而且如果一个链表要想真正的去使用,只依靠之前多编写的还不够,还需要进行一些代码的调优。

而在JDK1.2之后正式引入了类集的概念,类集是一种动态的对象数组,属于各个数据结构的实现类,在整个类集之中主要的组成是一些核心的操作接口:Collection,List,Set,Map,Iterator,Enumeration。

Collection集合接口

Collection是单个集合保存最大的父接口。而在Collection接口的定义如下:

public interface Collection<E> extends Iterable<E>

从JDK1.5之后Collection接口上追加有泛型应用,这样的直接好处就是避免了ClassCastException异常,里面的所有的数据的保存类型应该是相同的。对于此类的常用方法有如下几个:

方法名称类型描述
public boolean add(E e)普通向集合中添加数据
boolean addAll(Collection<? extends E> c)普通向集合添加一组数据
public void clear()普通清空集合数据
public boolean contains(Object o)普通查询数据是否存在
public Boolean isEmpty()普通判断集合是否有元素
public Iterator iterator()普通取得Iterator接口对象,用于输出
public boolean remove(Object o)普通删除数据,需要equals()方法
public int size()普通取得集合的长度
public Object[] toArray()普通将集合数据返回

在开发之中,add()和iterator()方法使用率极高,其他的方法几乎使用不到。接口只是一个存储数据的标准,而并不能区分存储类型,例如:如果要存放数据可能需要区分重复与不重复。所以在实际的开发之中,往往会去考虑使用Collection接口的子接口:List(允许重复)、Set(不允许重复)。

image-20210822185639319

List接口简介

List是Collection的一个最常用的子接口,并且允许重复的子接口。

方法名称类型描述
public E get(int index)普通取得指定索引位置上的数据
public E set(int index,E element)普通修改指定索引上的数据
public ListIterator listIterator()普通为ListIterator接口实例化

List子接口与Collection接口相比最大的特点在于其有一个get()方法,可以根据索引取得内容。但是List本身还属于我们的一个接口而如果取得接口的实例化对象就必须有子类,在List接口下有三个常用子类:ArrayList、Vector、LinkedList。

image-20210822191336145

最终的操作应该还是以接口为主,那么既然要以接口为主,所以所有的方法只参考接口的定义即可。

ArrayList子类

ArrayList是一个针对于List接口的数组操作实现。

List基本操作

package com.day17.demo;

import java.util.ArrayList;
import java.util.List;

public class ArrayListDemo {
	public static void main(String[] args) {
		List<String> all = new ArrayList<>();//此时集合里面只适合保存String类型数据
		System.out.println(all.size() + " " + all.isEmpty());
		all.add("Hello");
		all.add("Hello");	//重复数据
		all.add("world~!");
		all.add("zsr~");
		System.out.println(all.contains("zsr~"));
		System.out.println(all.contains("zsr"));
		System.out.println(all);
	}
}

通过我们的代码我们可以证实List允许保存重复数据。

List中存在get()方法,可以利用get()方法结合索引取得数据。

List的get()方法

package com.day17.demo;

import java.util.ArrayList;
import java.util.List;

public class ArrayListDemo {
	public static void main(String[] args) {
		List<String> all = new ArrayList<>();//此时集合里面只适合保存String类型数据
		all.add("Hello");
		all.add("Hello");	//重复数据
		all.add("world~!");
		all.add("zsr~");
		for (int i = 0; i < all.size(); i++) {
			System.out.println(all.get(i));
		}
	}
}

但是千万记住,get()方法是List子接口的,如果现在使用不是List而是Collection,对于此时的数据取出,只能够将集合变为对象数组的操作了。

(开发一般不使用)Collection进行输出处理并取出数据

package com.day17.demo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

public class CollectionDemo {
	public static void main(String[] args) {
		Collection<String> all = new ArrayList<>();//此时集合里面只适合保存String类型数据
		all.add("Hello");
		all.add("Hello");	//重复数据
		all.add("world~!");
		all.add("zsr~");
		//操作以Object形式返回,那么就有可能需要向下转型,有可能造成ClassCastException的安全隐患
		Object result [] = all.toArray();//变为Object对象数组
		System.out.println(Arrays.toString(result));
	}
}

集合与简单Java类

在实际的开发之中,集合里面保存最多的数据类型,就是简答java类。

向集合保存简单java类

package com.day17.demo;

import java.util.ArrayList;
import java.util.List;

class Person{
	private String name;
	private Integer age;
	public Person(String name, Integer age) {
		super();
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	@Override
	public boolean equals(Object obj) {
		// TODO Auto-generated method stub
		if(this == obj){
			return true;
		}
		if (obj == null){
			return false;
		}
		if(!(obj instanceof Person)){
			return false;
		}
		Person per = (Person) obj;//这个对象自己操作可以向下转型
		return this.name.equals(per.name) && this.age.equals(per.age);
	}
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
	
}
public class ListDemo {
	public static void main(String[] args) {
		List<Person> all = new ArrayList<>();
		all.add(new Person("张三",10));
		all.add(new Person("李四",21));
		all.add(new Person("王五",19));
		//对于remove()、contains()方法必须类中有equals()的支持
		all.remove(new Person("李四",21));
		System.out.println(all.contains(new Person("李四",21)));
		for (int i = 0; i < all.size(); i++) {
			System.out.println(all.get(i));
		}
	}
}

该List集合如果使用remove()、contains()方法必须有equals()方法的支持。简单的java类里面的使用是很少使用。

Vector子类

Vector是旧的子类,这个类是从JDK1.0退出,ArrayList实在JDK1.2推出。

package com.day17.demo;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

public class ArrayListDemo {
	public static void main(String[] args) {
		List<String> all = new Vector<>();//此时集合里面只适合保存String类型数据
		all.add("Hello");
		all.add("Hello");	//重复数据
		all.add("world~!");
		all.add("zsr~");
		for (int i = 0; i < all.size(); i++) {
			System.out.println(all.get(i));
		}
	}
}

面试题:请解释ArrayList与Vector区别?

区别ArrayListVector
历史时间JDK1.2JDK1.0
处理形式异步处理,形式更高同步处理,性能降低
数据安全非线程安全线程安全
输出形式Iterator、ListIterator、foreachIterator、ListIterator、foreach、Enumeration

LinkedList子类

在List接口里面还有一个LinkedList子类,如果向我们的父接口转型的话,使用的形式和之前没有任何的区别。

package com.day17.demo;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class ArrayListDemo {
	public static void main(String[] args) {
		List<String> all = new LinkedList<>();//此时集合里面只适合保存String类型数据
		all.add("Hello");
		all.add("Hello");	//重复数据
		all.add("world~!");
		all.add("zsr~");
		for (int i = 0; i < all.size(); i++) {
			System.out.println(all.get(i));
		}
	}
}

面试题:请解释ArrayList与LinkedList区别?

区别ArrayListLinkedList
构造方法public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public LinkedList() {
}
开辟长度开辟定长的大小动态开辟
时间复杂度时间复杂度为1时间复杂度为n

ClassLoader类加载器

Class类描述的是类的整个信息,在Class类中提供的forName()方法它所能处理的只是通过CLASSPATH配置的路径进行加载,而我们的类加载的路径可能是网络、文件、数据库。这是ClassLoader类主要作用。

认识类加载器

首先Class观察一个方法:public ClassLoader getClassLoader();

编写简单的反射程序,观察ClassLoader的存在

package com.day17.demo;
class Member{//自定义类一定在CLASSPATH之中
	
}
public class TestDemo1 {
	public static void main(String[] args) {
		Class<?> cls = Member.class;
		System.out.println(cls.getClassLoader());
		System.out.println(cls.getClassLoader().getParent());
		System.out.println(cls.getClassLoader().getParent().getParent());
	}
}

/*
sun.misc.Launcher$AppClassLoader@73d16e93
sun.misc.Launcher$ExtClassLoader@15db9742
null
*/

出现两个加载器AppClassLoader(应用程序类加载器)、ExtClassLoader(扩展类加载器)。

image-20210822132640087

对于第三方程序类库除了CLASSPATH之外,实际上在java里面还有一个加载目录:C:\ProgramFiles\Java\jdk1.8.0_241\jre\lib\ext

image-20210822133615839

观察类我们发现ClassLoader里有一个方法:public 类<?> loadClass(String name) throws ClassNotFoundException,进行类的加载操作处理。

自定义ClassLoader

实现文件的类加载器

package com.day17.demo;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

class MyClassLoader extends ClassLoader{
	/**
	 * 实现一个自定义的类加载器,传入类名称后,通过指定文件路径加载
	 * @param className
	 * @return
	 * @throws Exception
	 */
	public Class<?> loadData(String className) throws Exception{
		byte classDate [] = this.loadClassData();
		return super.defineClass(className, classDate, 0, classDate.length);
		
	}
	/**
	 * 通过指定文件路径进行类的文件加载,进行二进制读取
	 * @return
	 * @throws Exception
	 */
	private byte [] loadClassData() throws Exception{
		InputStream input = new FileInputStream("f:" + File.separator + "java" + File.separator + "Member.class");
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		byte [] data = new byte [20];//定义读取的缓冲区
		int temp = 0;
		while((temp = input.read(data)) != -1){
			bos.write(data,0,temp);
		}
		byte ret [] = bos.toByteArray();
		input.close();
		bos.close();
		return ret;
	}
}
public class ClassLoaderDemo {
	public static void main(String[] args) throws Exception{
		Class<?> cls = new MyClassLoader().loadData("com.day17.test.Member");
		System.out.println(cls.newInstance());
	}
}

类加载器给我们用户最大的帮助就是在于可以通过动态的路径实现类的加载处理操作。