Android框架-ButterKnife

转载

1、编译时,由 annotationProcessor 生成 MainActivity_ViewBinding 类,该类继承 Unbinder

2、ButterKnife.bind(this) 根据传入的 类名 MainActivity.class,
从缓存获取 MainActivity_ViewBinding 实例
缓存中不存在,加载对应的 MainActivity_ViewBinding 类,创建 MainActivity_ViewBinding 实例
放入缓存

3、MainActivity_ViewBinding 构造函数中根据 id 查找 @BindView 注解控件,
并赋值给 MainActivity 中定义的 控件的成员变量

4、mUnbinder.unbind(this):把布局引用设置为 null,方便控件对象回收

根据 bind 方法传入的 class信息
从缓存获取对应的 MainActivity_ViewBinding 实例
存在
不存在
编译时 由注解处理器
创建 MainActivity_ViewBinding 类
该类继承 Unbinder
ButterKnife # bind 传入当前类对象
Activity / Fragment / ...
缓存是否存在
返回该 MainActivity_ViewBinding 实例
类加载器加载 MainActivity_ViewBinding 类
调用 MainActivity_ViewBinding 构造函数
创建实例放入缓存
MainActivity_ViewBinding 构造函数中
根据 id 获取 BindView 注解控件实例
把控件实例赋值给 MainActivity 中 控件的成员变量
mUnBinder # unbind
布局引用设置为 null
方便控件对象回收
一、简单使用
dependencies {
	......
	//ButterKnife 相关代码
	implementation 'com.jakewharton:butterknife:8.8.1'
	
	//注解处理器库,用于处理 ButterKnife 的注解,生成新的源码
	//annotationProcessor 是 apt 工具,用于编译时使用该工具,而不把代码引入apk中
	annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
}

public class MainActivity extends AppCompatActivity {
	@BindView(R.id.tv_title)
	TextView title;

	@OnClick(R.id.bt_submit)
	public void submit() {
		title.setText("hello world");
	}

	private Unbinder unbinder;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		unbinder = ButterKnife.bind(this);
	}

	@Override
	protected void onDestroy() {
		unbinder.unbind();
		super.onDestroy();
	}
}
二、原理分析

1、ButterKnife.bind(this)
ButterKnife 与 Activity 建立绑定关系。

public final class ButterKnife {
	@NonNull @UiThread
	public static Unbinder bind(@NonNull Activity target) {
		View sourceView = target.getWindow().getDecorView();
		return createBinding(target, sourceView);
	}
	
	private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
		//拿到 Activity的 Class
		Class<?> targetClass = target.getClass();
		...
		//根据 Class 得到一个继承了Unbinder的Constructor
		Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
		if (constructor == null) {
			return Unbinder.EMPTY;
		}
		try {
			//反射constructor.newInstance(target, source)得到Unbinder子类的一个实例
			return constructor.newInstance(target, source);
		} catch (...) {
			...
		}
	}
	
	@Nullable @CheckResult @UiThread
	private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
		//从缓存根据Class取
		Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
		if (bindingCtor != null) {
			return bindingCtor;
		}
		String clsName = cls.getName();
		//系统相关类,直接返回null
		if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
			return null;
		}
		try {
			//加载一个新的Class  clsName + "_ViewBinding" 继承了 Unbinder 类
			Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
			//用新加载的Class 创建 Constructor
			bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
		} catch (ClassNotFoundException e) {
			bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
		} catch (NoSuchMethodException e) {
			throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
		}
		//把 Constructor 存入内存缓存,并返回 XXXActivity_ViewBinding 的实例
		BINDINGS.put(cls, bindingCtor);
		return bindingCtor;
	}
}

所以 ButterKnife.bind(this) 方法最终返回的是继承了 Unbinder 的 XXXActivity_ViewBinding 实例
XXXActivity_ViewBinding 类是编译时,由 annotationProcessor 生成的

publicclass MainActivity_ViewBinding implements Unbinder {
	private MainActivity target;

	@UiThread
	public MainActivity_ViewBinding(MainActivity target) {
		this(target, target.getWindow().getDecorView());
	}

	@UiThread
	public MainActivity_ViewBinding(MainActivity target, View source) {
		this.target = target;
		//通过 DecorView.findViewById()找到View并强转为指定类型
		target.mTabLayout = Utils.findRequiredViewAsType(source, R.id.coupon_indicator, "field 'mTabLayout'", MagicIndicator.class);
		target.mViewPager = Utils.findRequiredViewAsType(source, R.id.coupon_view_pager, "field 'mViewPager'", ViewPager.class);
	}

	@Override
	@CallSuper
	public void unbind() {
		//把 创建的 View 置空,完成相关View 引用的释放、点击事件的解绑
		MainActivity target = this.target;
		if (target == null) throw new IllegalStateException("Bindings already cleared.");
		this.target = null;
		target.mTabLayout = null;
		target.mViewPager = null;
	}
}

createBinding() 方法中 return constructor.newInstance(target, source);
使用的就是 MainActivity_ViewBinding 类两个参数的构造函数。

public final class Utils {			
	public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who, Class<T> cls) {
		//这个source 是 当前 Activity 的 DecorView
		//通过 DecorView.findViewById()找到View 
		View view = findRequiredView(source, id, who);
		//把View 强转为 cls类型
		return castView(view, id, who, cls);
	}
	  
	public static View findRequiredView(View source, @IdRes int id, String who) {
		View view = source.findViewById(id);
		if (view != null) {
			return view;
		}
		...
	}

	public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
		try {
			return cls.cast(view);
		} catch (ClassCastException e) {
			...
		}
	}
}
三、注解处理器

收集注解及相关 View 和 Activity/Fragment 的信息,便于下一步通过JavaPoet
生成 MainActivity_ViewBinding

//@AutoService 实现注解处理器的注册,注册到 javac 后,在项目编译时就能执行注解处理器
@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
  
	@Override
	public synchronized void init(ProcessingEnvironment env) {
		super.init(env);
		String sdk = env.getOptions().get(OPTION_SDK_INT);
		......
		debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE));
		
		//用于获取元素(Element)相关的信息
		elementUtils = env.getElementUtils();
		
		typeUtils = env.getTypeUtils();
		
		//Filer filer,用来生成 java 类文件。
		filer = env.getFiler();
		try {
			trees = Trees.instance(processingEnv);
		} catch (IllegalArgumentException ignored) {
		}
	}

	//返回注解处理器可处理的注解操作
	@Override
	public Set<String> getSupportedOptions() {
		return ImmutableSet.of(OPTION_SDK_INT, OPTION_DEBUGGABLE);
	}

	//该方法返回一个Set<String>,代表ButterKnifeProcessor要处理的注解类的名称集合,
	//即 ButterKnife 支持的注解:butterknife-annotations
	@Override
	public Set<String> getSupportedAnnotationTypes() {
		Set<String> types = new LinkedHashSet<>();
		for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
			types.add(annotation.getCanonicalName());
		}
		return types;
	}

	//完成了目标类信息的收集并生成对应 java 类
	@Override
	public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
		Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);

		for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
			TypeElement typeElement = entry.getKey();
			BindingSet binding = entry.getValue();

			JavaFile javaFile = binding.brewJava(sdk, debuggable);
			try {
				javaFile.writeTo(filer);
			} catch (IOException e) {
				error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
			}
		}
		return false;
	}

	//返回当前系统支持的 java 版本
	@Override
	public SourceVersion getSupportedSourceVersion() {
		return SourceVersion.latestSupported();
	}
	
	private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
		Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
		Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
		//将扫描得到的注解相关信息保存到builderMap和erasedTargetNames中
		scanForRClasses(env);
		...
		// env.getElementsAnnotatedWith(BindView.class)获取所有使用BindView注解的元素
		for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
			try {
				parseBindView(element, builderMap, erasedTargetNames);
			} catch (Exception e) {
				logParsingError(element, BindView.class, e);
			}
		}
		...
		// 将 builderMap 中的数据添加到队列中
		Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries = new ArrayDeque<>(builderMap.entrySet());
		Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
		while (!entries.isEmpty()) {
			// 出队列
			Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();

			TypeElement type = entry.getKey();
			BindingSet.Builder builder = entry.getValue();
			// 查找当前类元素的父类元素
			TypeElement parentType = findParentType(type, erasedTargetNames);
			// 如果没找到则保存 TypeElement 和对应 BindingSet
			if (parentType == null) {
				bindingMap.put(type, builder.build());
			} else {
				BindingSet parentBinding = bindingMap.get(parentType);
				if (parentBinding != null) {
					// 如果找到父类元素,则给当前类元素对应的 BindingSet.Builder 设置父 BindingSet
					builder.setParent(parentBinding);
					bindingMap.put(type, builder.build());
				} else {
					// 再次入队列
					entries.addLast(entry);
				}
			}
		}
		return bindingMap;
	}
	
	private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) {
		// 首先要注意,此时 element 是 VariableElement 类型的,即成员变量
		
		// enclosingElement 是当前元素的父类元素,
		// 一般就是我们使用 ButteKnife 时定义的 View 类型成员变量所在的类,可以理解为之前例子中的 MainActivity
		TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

		// 进行相关校验
		// 1、isInaccessibleViaGeneratedCode(),先判断当前元素的是否是private或static类型,
		// 再判断其 父元素 是否是一个类以及是否是 private 类型。
		// 2、isBindingInWrongPackage(),是否在系统相关的类中使用了 ButteKnife 注解
		boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
				|| isBindingInWrongPackage(BindView.class, element);

		// TypeMirror 表示Java编程语言中的一种类型。 
		// 类型包括基元类型,声明的类型(类和接口类型),数组类型,类型变量和空类型。 
		// 还表示了 通配符类型参数,可执行文件的 签名 和 返回类型,
		// 以及与 包 和 关键字void 相对应的伪类型。
		TypeMirror elementType = element.asType();
		// 如果当前元素是类的成员变量
		if (elementType.getKind() == TypeKind.TYPEVAR) {
			TypeVariable typeVariable = (TypeVariable) elementType;
			elementType = typeVariable.getUpperBound();
		}
		Name qualifiedName = enclosingElement.getQualifiedName();
		Name simpleName = element.getSimpleName();
		// 判断当前元素是否是 View 的子类,或者是接口,不是的话抛出异常
		if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
			if (elementType.getKind() == TypeKind.ERROR) {
				note(element, "@%s field with unresolved type (%s) "
								+ "must elsewhere be generated as a View or interface. (%s.%s)",
						BindView.class.getSimpleName(), elementType, qualifiedName, simpleName);
			} else {
				error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
						BindView.class.getSimpleName(), qualifiedName, simpleName);
				hasError = true;
			}
		}
		
		if (hasError) {
			return;
		}

		// 获得元素使用 BindView 注解时设置的属性值,即 View 对应的xml中的id
		int id = element.getAnnotation(BindView.class).value();
		// 尝试获取父元素对应的 BindingSet.Builder
		BindingSet.Builder builder = builderMap.get(enclosingElement);
		// QualifiedId 记录了当前元素的包信息以及id
		QualifiedId qualifiedId = elementToQualifiedId(element, id);
		if (builder != null) {
			String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
			// 如果当前id已经被绑定,则抛出异常
			if (existingBindingName != null) {
				error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
						BindView.class.getSimpleName(), id, existingBindingName,
						enclosingElement.getQualifiedName(), element.getSimpleName());
				return;
			}
		} else {
			// 创建一个新的 BindingSet.Builder 并返回,并且以 enclosingElement 为key添加到builderMap中
			builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
		}

		String name = simpleName.toString();
		TypeName type = TypeName.get(elementType);
		// 判断当前元素是否使用了Nullable注解
		boolean required = isFieldRequired(element);
		// 创建一个FieldViewBinding,它包含了元素名、类型、是否是Nullable
		// 然后和元素id一同添加到BindingSet.Builder
		builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));

		// 记录当前元素的父类元素
		erasedTargetNames.add(enclosingElement);
	}
	
	private BindingSet.Builder getOrCreateBindingBuilder(
		  Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {
		// 先判断enclosingElement对应的BindingSet.Builder是否已存在
		BindingSet.Builder builder = builderMap.get(enclosingElement);
		if (builder == null) {
			// 创建一个BindingSet.Builder
			builder = BindingSet.newBuilder(enclosingElement);
			// 添加到builderMap
			builderMap.put(enclosingElement, builder);
		}
		return builder;
	}
}
//BindingSet.newBuilder()
static Builder newBuilder(TypeElement enclosingElement) {
	TypeMirror typeMirror = enclosingElement.asType();
	// 判断当前父元素的类型
	boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE);
	boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE);
	boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE);

	TypeName targetType = TypeName.get(typeMirror);
	if (targetType instanceof ParameterizedTypeName) {
		targetType = ((ParameterizedTypeName) targetType).rawType;
	}
	// 获取父类元素的包名
	String packageName = getPackage(enclosingElement).getQualifiedName().toString();
	// 获取父类元素的名称
	String className = enclosingElement.getQualifiedName().toString().substring(
		packageName.length() + 1).replace('.', '$');
	// 即最终要生成的java类的名称
	ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");
	// 判断父类元素是否为final类型
	boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
	return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
}
四、JavaPoet

1、JavaPoet 中一些重要的类

TypeSpec 表示 类、接口、枚举声明
ParameterSpec 表示参数声明
MethodSpec 表示构造函数、方法声明
FieldSpec 表示成员变量,一个字段声明
CodeBlock 表示代码块,用来拼接代码
JavaFile 表示Java类的代码

2、占位符

1)$L,for Literals 替换 字符串、原语、JavaPoet中的类型

beginControlFlow("for (int i = $L; i < $L; i++)", 1, 10)
addStatement("result = result $L i", "+")

2)$S,for Strings 替换 字符串

addStatement("return $S", "hello")

3)$T,for Types 替换

addStatement("return new $T()", Date.class)

4)$N,for Names 替换 JavaPoet中的声明

3、继续看 ButterKnife 中 JavaPoet 代码
//BindingSet.brewJava()

JavaFile brewJava(int sdk, boolean debuggable) {
	return JavaFile.builder(bindingClassName.packageName(), createType(sdk, debuggable))
		.addFileComment("Generated code from Butter Knife. Do not modify!")
		.build();
}

private TypeSpec createType(int sdk, boolean debuggable) {
	// TypeSpec.classBuilder() 方法设置类名称
	// addModifiers() 方法设置类的修饰符为 public
	TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
			.addModifiers(PUBLIC);
	// 如果是final类则添加final修饰符
	if (isFinal) {
		result.addModifiers(FINAL);
	}

	if (parentBinding != null) {
		result.superclass(parentBinding.bindingClassName);
	} else {
		// 让当前类 Unbinder接口,
		// 之前生成的 MainActivity_ViewBinding类就实现了Unbinder接口
		result.addSuperinterface(UNBINDER);
	}
	if (hasTargetField()) {
		// 添加一个类成员变量,
		// 可以对应到 MainActivity_ViewBinding类中的 private MainActivity target
		result.addField(targetTypeName, "target", PRIVATE);
	}			
	// 判断目标类的类型
	if (isView) {
		result.addMethod(createBindingConstructorForView());
	} else if (isActivity) {
		// 由于之前的例子是Activity类型的,所以会走到这里,
		// 去生成MainActivity_ViewBinding类默认的构造函数
		result.addMethod(createBindingConstructorForActivity());
	} else if (isDialog) {
		result.addMethod(createBindingConstructorForDialog());
	}
	if (!constructorNeedsView()) {
		// Add a delegating constructor with a target type + view signature for reflective use.
		result.addMethod(createBindingViewDelegateConstructor());
	}
	// 生成view绑定的构造函数,可以对应到MainActivity_ViewBinding类两个参数的构造函数
	result.addMethod(createBindingConstructor(sdk, debuggable));

	if (hasViewBindings() || parentBinding == null) {
		// 生成ubinder()方法
		result.addMethod(createBindingUnbindMethod(result));
	}
	return result.build();
}

private MethodSpec createBindingConstructor(int sdk, boolean debuggable) {
	MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
		.addAnnotation(UI_THREAD)
		.addModifiers(PUBLIC);
	if (hasMethodBindings()) {
		// 给构造函数添加一个修饰符为final、targetTypeName,名称为target的参数,
		// 即final MainActivity target
		constructor.addParameter(targetTypeName, "target", FINAL);
	} else {
		constructor.addParameter(targetTypeName, "target");
	}
	if (constructorNeedsView()) {
		// 给构造函数添加一个View类型的source参数
		constructor.addParameter(VIEW, "source");
	} else {
		constructor.addParameter(CONTEXT, "context");
	}
	...
	if (hasTargetField()) {
		// 给构造函数添加一行this.target = target;的声明代码
		constructor.addStatement("this.target = target");
		constructor.addCode("\n");
	}
	if (hasViewBindings()) {
		if (hasViewLocal()) {
			// 给构造函数添加一行View view;的声明代码
			constructor.addStatement("$T view", VIEW);
		}
		for (ViewBinding binding : viewBindings) {
			// 根据id查找view并完成赋值
			addViewBinding(constructor, binding, debuggable);
		}
		...
	}
	...
	return constructor.build();
}

private void addViewBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) {
	if (binding.isSingleFieldBinding()) {
		FieldViewBinding fieldBinding = binding.getFieldBinding();
		// CodeBlock 表示代码块,用来完成view查找、赋值语句的拼接
		// 相当于target.title = 
		CodeBlock.Builder builder = CodeBlock.builder()
				.add("target.$L = ", fieldBinding.getName());
		// 由于例子中title是TextView,不是View所以requiresCast为true
		boolean requiresCast = requiresCast(fieldBinding.getType());
		// debuggable为true、fieldBinding.isRequired()为true,则以下if条件不成立
		if (!debuggable || (!requiresCast && !fieldBinding.isRequired())) {
			if (requiresCast) {
				builder.add("($T) ", fieldBinding.getType());
			}
			builder.add("source.findViewById($L)", binding.getId().code);
		} else {
			// 继续给代码块添加Utils.find
			builder.add("$T.find", UTILS);
			// 根据上边的分析可知,会给代码块添加RequiredView
			builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView");
			if (requiresCast) {
				// 给代码块添加AsType
				builder.add("AsType");
			}
			// 给代码块添加(source, R.id.tv_title
			builder.add("(source, $L", binding.getId().code);
			if (fieldBinding.isRequired() || requiresCast) {
				// 继续添加view的相关描述,例如field 'title'
				builder.add(", $S", asHumanDescription(singletonList(fieldBinding)));
			}
			if (requiresCast) {
				//继续添加view的Class类型,例如TextView.class
				builder.add(", $T.class", fieldBinding.getRawType());
			}
			// 添加右括号,到这里就完成了view的查找与赋值
			// 例如target.title = Utils.findRequiredViewAsType(source, R.id.tv_title, "field 'title'", TextView.class);
			builder.add(")");
		}
		result.addStatement("$L", builder.build());
		return;
	}
}

转载自:
ButterKnife 原理解析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值