黑马程序员
---------------------- <a href="http://edu.csdn.net/heima" target="blank">android培训</a>、<a href="http://edu.csdn.net/heima" target="blank">java培训</a>、期待与您交流! ----------------------
1.1. 对象的比较
在我们使用运算符“==”来比较两个对象时,其实比较的是两个对象的地址。如果运算符两边是同一个对象,地址相同则会等到true,只要是不同对象地址就会不同,返回false。
我们在编程过程中经常会比较两个对象的属性,这时我们就无法用“==”来比较了,因为即使两个对象所有属性都相同但不是同一个对象“==”号比较后也会得到false。这种情况下我们一般会定义一个equals()方法来进行比较。
class EqualsDemo {
public static void main(String[] args){
/*
String s1 = "abc";
String s2 = "abc";
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2); // s1和s2是同一个对象, 地址相同
*/
/*
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2); // s1和s2虽然内容相同, 但不是同一个对象, 地址不同
System.out.println(s1.equals(s2)); // 比较两个字符串的内容是否相同
*/
Person p1 = new Person(null, 19);
Person p2 = new Person(null, 20);
System.out.println(p1); // 打印对象所在地址
System.out.println(p2);
System.out.println(p1 == p2);
System.out.println(p1.equals(p2)); // true
}
}
class Person extends Object {
private String name;
private int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
public boolean equals(Object obj) {
if(this == obj) // 如果地址相同, 那么属性肯定相同
return true;
if(!(obj instanceof Person)) // 如果类型不同, 不用比较属性了
return false;
Person p = (Person)obj; // 类型相同的话强制类型转换
if(this.name == null){ // 为了避免NullPointerException, 判断null的问题
if(p.name != null)
return false;
}else if(!this.name.equals(p.name)) // 判断name是否相同
return false;
if(this.age != p.age) // 判断age是否相同
return false;
return true; // 如果name和age都相同了, 上面不会返回, 这里代表所有属性都相同
}
/*
// Object类的equlas方法默认比较地址, 如果我们不想比较地址, 而比较类的属性, 可以重写这个方法.
// 自定义equals方法, 比较两个对象的属性是否完全相同. HashSet, HashMap
@Override
public boolean equals(Object obj) {
// 将Object类型的形参转回Person
Person p = (Person)obj;
// 如果自己的名字和传入对象的名字不同, 则返回false
if(!this.name.equals(p.name))
return false;
// 如果年龄不同, 也返回false
if(this.age != p.age)
return false;
// 如果上面两个都没返回, 就代表name和age都一样
return true;
}
*/
}
1.2. 文档注释
文档注释以“/**”开始,以“*/”标志结束,相应的信息和批注所对应的位置很重要! 类的说明应在类定义之前,方法的说明应在方法的定义之前。
使用文档注释修饰一个类的源代码之后可以通过javadoc.exe来生成帮助文档。
生成文档的命令:
javadoc -d (目录) -version –author (源文件)
批注参数来标记一些特殊的属性及其相应的说明 。
@author<作者姓名>
@version<版本信息>
@param<参数名称><参数说明>
@return<返回值说明>
1.3. 组合设计模式(CompositePattern)
什么时候用组合
组合是一种实现代码复用的方式,当我们在定义一个类的时候需要用到另外一个类的方法时,就可以用组合。
怎么用组合
定义一个所需要的类类型的成员变量
通过构造函数进行装配,接收一个该类类型的对象,用成员变量引用
在需要使用另一个类的方法时通过成员变量访问
组合的优点
如果两个类没有父子关系,不合适用继承。
Java只支持单继承,组合不占用继承位置。
/*
当一个类需要使用另一个类的方法时, 可以使用组合设计模式, 也可以使用继承
组合的优点:
如果两个类之间没有逻辑上的父子关系, 那么不适合用继承
另外如果使用继承, 会占用extends唯一的位置, 就不能继承其他类了
继承的优点:
子类可以当做父类来用
*/
class CompositeDemo {
public static void main(String[] args){
Person p = new Person(new CreditCard());
p.shopping();
p.eat();
}
}
class Person {
private Card card;
public Person(Card card){
this.card = card;
}
public void shopping(){
card.spend();
System.out.println("买了一台iPhone5");
}
public void eat(){
card.spend();
System.out.println("吃了一顿全聚德");
}
}
class Card {
public void spend(){
System.out.println("一不小心刷了100块");
}
}
class CreditCard extends Card{
public void spend(){
System.out.println("刷卡消费, 先欠着");
}
}
class Student extends Person {
public Student(Card card){
super(card);
}
}
1.4. 多态(Polymorphism)
么是多态
多态字面上的意思就是多种形态。在面向对象语言中,我们可以将函数的形参定义为一个父类类型,而在真正调用该函数时这个父类类型的所有子类对象都可以传入,根据传入的子类对象不同函数可以运行处多种形态。
多态的特点
应用程序不必为每一个子类编写功能调用,只需要对父类进行处理即可。这一招叫“以不变应万变”,可以大大提高程序的可复用性。
子类的功能可以被父类的变量调用,这叫向后兼容,可以提高程序的可扩充性和可维护性。现在写的程序可以调用将来写的程序不足为奇 。
/*
在定义方法的时候, 将形参定义为父类类型, 而调用方法时传入子类对象.
这时方法中的父类引用引用了哪个子类对象, 就调用哪个对象的方法.
*/
class Polymorphism {
public static void main(String[] args){
Juicer j = new Juicer();
j.run(new Apple());
j.run(new Watermelon());
j.run(new Mango());
j.run(new Orange());
j.run(new Fruit(){ // 定义了一个Fruit类的子类, 重写了squeeze()方法, 并且使用该类创建了一个对象
public void squeeze(){
System.out.println("榨出了一杯梨汁");
}
});
// j.run(new Fruit()); // 抽象类不能创建对象
}
}
class Juicer { // 榨汁机
public void run(Fruit f){
f.squeeze();
}
}
// 定义一个父类, 其中只有一个榨汁的方法
abstract class Fruit {
public abstract void squeeze();
}
class Apple extends Fruit {
public void squeeze(){
System.out.println("榨出了一杯苹果汁");
}
}
class Watermelon extends Fruit {
public void squeeze(){
System.out.println("榨出了一杯西瓜汁");
}
}
class Mango extends Fruit {
public void squeeze(){
System.out.println("榨出了一杯芒果汁");
}
}
class Orange extends Fruit {
public void squeeze(){
System.out.println("榨出了一杯橙汁");
}
}
1.5. 抽象类
什么是抽象类
使用abstract关键字修饰的类就是抽象类,抽象类不能new对象,原因在于抽象类含有抽象方法,不能被调用。
没有方法体的方法为抽象方法,使用abstract关键字修饰。
有抽象方法的类必须声明为抽象类。
抽象类不一定含有抽象方法。 学习GUI的时候会学习一些抽象类没有抽象方法。XxxAdapter
为什么要定义抽象类
如果有多个类具有相同的方法声明,而方法的实现不一样,这时就可以抽象出父类,将方法在父类中声明
别人在学习我们的软件时,只需要学习父类就知道子类有什么方法
在设计软件时,要尽力抽象父类,继承关系以3~4层为宜
1.6. final关键字
final标记的类不能被继承。
final标记的方法不能被子类重写。
final标记的变量即为常量,只能赋值一次。注意引用数据类型和基本数据类型的区别。
使用public static final共同修饰的常量就是全局常量。通常全部字母大写。
class FinalDemo {
public static void main(String[] args){
// final int x; // 最终的变量, 即为常量, 只能赋值一次
// x = 6;
/*
final Person p = new Person(); // 注意final修饰变量时是指变量中存储的值不能改变.
p.name = "张三";
p.name = "李四";
*/
fun(5);
}
public static void fun(final int x){
System.out.println(x++);
}
}
final class A { // final修饰的类为最终的类, 不能被继承
}
class B {
final public void fun(){ // 最终的方法, 不能被重写
}
}
class C {
final String name = "xxx";
}
class Person {
String name;
}
1.7. 模板设计模式(TemplatePattern)
为什么要使用模板方法设计模式
在解决一些问题或者设计一个软件的时候,需要先定义一个模板,就相当于一种事先定义好的协议。
以后要做这系列的事情都按照这个模板来做。这样就实现统一化管理。
如何实现模板方法设计模式
定义一个抽象的父类做为模板,定义所有需要的方法
在父类中实现供外界调用的主方法,将方法声明为final
根据不同业务需求定义子类实现父类的抽象方法
1.8. 内部类(InnerClass)
类中的内部类
在类里面定义的类称之为内部类(Inner Class),内部类是外部类的一个成员。
内部类必须创建外部类对象才能使用。
创建内部类对象时必须先创建一个外部类对象,通过一个外部类对象才能创建内部类对象。
外部类名.内部类名 变量名 = new 外部类名().new 内部类名();
内部类可以直接访问外部类的成员,而外部类不能直接访问内部类的成员。访问方式:外部类名.this.成员名
内部类可以访问外部类成员,因为在使用内部类时一定会有外部类对象,且只对应一个。
外部类不能访问内部类成员,因为在使用外部类时有可能还没有创建内部类对象。即使创建了,一个外部类对象也可能对应多个内部类对象。
如果一定要在外部类中使用内部类成员,那么需要创建内部类对象,通过对象来访问。
内部类中不能定义静态成员。
因为内部类需要创建外部类对象才能使用,static的本意是不创建对象就能使用,这是矛盾的。
内部类的class文件名为:外部类名$内部类名.class
方法中的内部类
一个类如果只在某个方法中使用,那么可以在方法中定义。
定义在方法中的类只能在方法中使用,而且使用的代码只能在声明的代码下面。
方法中的内部类只有在运行到类定义之后才能使用。
方法中定义的内部类不能访问方法中定义的局部变量,除非这个局部变量被声明为final的。
在方法中定义的局部变量在方法运行结束之后生命周期结束,不能再被访问。
方法中的内部类创建的对象有可能生命周期比这个局部变量长,例如这个对象被作为返回值返回,那么方法运行结束之后还可以访问这个对象。
这时变量被销毁了,对象还在,如果在对象的某个方法内访问这个变量就访问不到了。
我们需要使用final修饰这个变量,被final修饰的变量会一直存储在内存中,方法运行结束之后不被销毁。
方法中的内部类class文件名为:外部类名$.编号内部类名.class
匿名内部类
如果一个类只使用一次,那么可以定义为匿名内部类。
使用 new 父类名(){类定义} 形式声明,先创建一个指定类的子类,然后根据这个类创建一个对象。
匿名内部类的class文件名为:外部类名$编号.class
静态内部类
可以使用static修饰一个类中的内部类。
静态内部类不用创建外部类对象就可以直接创建对象。
外部类名.内部类名 变量名 = new 外部类名.内部类名();
静态内部类可以定义静态成员。
因为静态内部类可以直接使用,无需创建外部类对象。
静态内部类中不能访问外部非静态成员。
因为创建静态内部类不需要外部类对象,也就是有可能没有创建外部类对象,使用外部类成员必须有外部类对象。
/*
内部类是外部类的一个成员, 必须创建外部类对象才能使用.
内部类中可以直接访问外部类的成员, 使用"外部类名.this.成员名"形式.
因为在内部类对象被创建的时候一定会有一个对应的外部类对象
使用这种形式访问的就是该内部类对象创建时使用的外部类对象的成员
外部类对象不能访问内部类对象的成员.
因为在创建了外部类对象的时候, 有可能还没有创建内部类对象.
而且一个外部类对象可以对应多个内部类对象, 无法确定访问哪个内部类对象的成员.
内部类中不能定义静态成员.
因为静态本意就是不用创建对象就可以直接使用.
而内部类必须创建外部类对象才能使用.
以上两点是矛盾的, 所以内部类中不能使用静态声明.
如果一个普通的类和内部类同名, 那么在另一个类中以类名创建对象是使用普通类.
如果在内部类的外部类中以类名创建对象, 那么是使用内部类.
如果在这个外部类中要创建普通类(不使用内部类), 那么这个普通类必须要带有包名.
*/
class InnerClass {
public static void main(String[] args){
// Inner in = new Inner();
/*
Outer out = new Outer(); // 先创建外部类对象
Outer.Inner in1 = out.new Inner(); // 通过外部类对象创建内部类对象
Outer.Inner in2 = out.new Inner();
out.name = "out";
in1.name = "in1";
in2.name = "in2";
out.run();
in1.run();
in2.run();
*/
// Outer.Inner in = new Outer().new Inner(); // 创建内部类对象
Outer out = new Outer();
out.run();
}
}
class Outer { // 外部类
String name;
void run(){
String name = "ooo";
System.out.println("Outer run, name is " + this.name /* + ", InnerClass name is " + Inner.this.name*/ );
Inner in = new Inner();
in.run();
}
class Inner { // 内部类, 是外部类的一个成员, 必须创建外部类对象之后才能使用
String name;
void run(){
String name = "xxx";
System.out.println("Inner run, name is " + this.name + ", OuterClass name is " + Outer.this.name);
}
}
}
class Inner { // 内部类, 是外部类的一个成员, 必须创建外部类对象之后才能使用
String name;
void run(){
System.out.println("普通的类");
}
}
1.9. 接口
什么是接口
接口是一种特殊的抽象类,接口中声明的所有方法都是抽象的
使用interface关键字修饰一个接口
接口的用法
我们可以定义一个类来实现接口,使用implements关键字
实现一个接口需要实现接口中所有的方法,抽象类除外
通常使用匿名内部类来实现一个接口
接口可以继承接口,使用extends关键字。 接口不能继承抽象类,因为抽象类中可能有不抽象的方法。
一个类可以实现多个接口,为了更好的支持多态
接口中的方法和变量
接口中定义的方法默认是公有的抽象的,被public abstract修饰
接口中定义的变量默认为全局常量,使用public static final修饰
class InterfaceDemo1 {
public static void main(String[] args){
Person p = new Person();
work1(p);
work2(p);
work3(p);
}
public static void work1(Coder c){
c.code();
}
public static void work2(Driver d){
d.drive();
}
public static void work3(Teacher t){
t.teach();
}
}
class Person extends Object implements Coder, Driver, Teacher { // 一个类实现多个接口, 可以当做多个接口的子类使用
public void code(){
System.out.println("编程");
}
public void drive(){
System.out.println("开车");
}
public void teach(){
System.out.println("讲课");
}
}
interface Coder {
public static final int X = 5; // 默认被public static final 修饰, 是一个全局常量
public abstract void code(); // 接口中的方法默认被public abstract修饰
}
interface Driver {
void drive();
}
interface Teacher {
void teach();
}
abstract class和interface的区别
抽象类中可以有不抽象的方法,接口中全是抽象的方法
抽象类用extends继承,接口用implements实现
抽象类中的变量和方法没有默认修饰符,接口中的变量默认为public static final的,接口中的方法默认为public abstract
一个类只能继承一个抽象类,一个类可以实现多个接口
什么时候用抽象类,什么时候用接口
如果能用接口,就不用抽象类,因为别人实现接口可以不占用继承的位置。
如果定义一个抽象的父类,其中所有方法都是抽象的,那么就定义为接口。
如果定义一个抽象的父类的时候,需要有不抽象的方法,那么只能定义为抽象类。
class InterfaceDemo {
public static void main(String[] args){
// B b = new D(); // 定义一个接口类型的变量, 可以接收一个接口的实现类对象
B b = new B(){ // 定义了一个接口的实现类, 使用这个类创建了一个对象
public void fun1(){
System.out.println("匿名内部类的 fun1");
}
public void fun2(){
System.out.println("匿名内部类的 fun2");
}
};
}
}
abstract class A { // 定义抽象类
public abstract void fun1();
public void fun2(){
System.out.println("A fun2");
}
}
interface B { // 定义接口
public abstract void fun1();
public abstract void fun2();
}
class C extends A { // 继承抽象类
public void fun1(){
System.out.println("C fun1");
}
}
class D implements B { // 实现接口
public void fun1(){
System.out.println("D fun1");
}
public void fun2(){
System.out.println("D fun2");
}
}
abstract class E implements B { // 抽象类实现接口时可以不实现抽象方法
public void fun1(){
System.out.println("E fun1");
}
}
interface F extends B { // 接口可以继承接口
public abstract void fun3();
}
class G implements F {
public void fun1(){
}
public void fun2(){
}
public void fun3(){
}
}
1.10. 异常
什么是异常
异常就是Java程序在运行过程中出现的错误。如程序要打开一个不存的文件、网络连接中断、操作数组越界、装载一个不存在的类等。
Throwable
Throwable表示Java中可被抛出的对象,它是所有错误和异常的父类
Throwable有两个子类:Error、Exception
Error表示错误
Exception表示异常
RuntimeException表示运行时异常,是Exception的子类
import java.io.*;
class ExceptionDemo {
public static void main(String[] args) throws FileNotFoundException {
// int[] arr = {1,2,3};
// System.out.println(arr[3]);
// arr = null;
// System.out.println(arr.length);
// System.out.println(5 / 0);
// FileReader fr = new FileReader("F:/Test.txt");
// throw new ArrayIndexOutOfBoundsException("3"); // RuntimeException
// throw new NullPointerException(); // RuntimeException
// throw new FileNotFoundException("F:/xxx.txt (系统找不到该文件)");
/*
System.out.println("程序开始");
System.out.println(1 / 0); // 如果程序运行过程中出现异常, 那么下面的代码将不会再执行
System.out.println("程序结束");
*/
// 获取用户数据, 向数据库中存储
// 发送确认邮件
}
}
异常的分类
Error(错误)
由Java虚拟机生成并抛出,包括动态链接失败、虚拟机错误等,程序对其不进行处理
Exception(异常)
所有异常类的父类,子类定义了各种各样可能出现的异常事件,一般需要用户显式地声明向外抛出或捕获。
Runtime Exception(运行时异常)
一类特殊的异常,如被0除、数组角标越界等。产生比较频繁,处理麻烦,如果每次都处理,会对程序可读性和运行效率影响比较大,因此由系统检测并将它们交给缺省的异常处理程序,用户不必对其进行处理。这类异常不处理,编译时不会报错,只是在运行时出现错误时才报告异常,所以我们称之为运行时异常,所有RuntimeException的子类都是运行时异常。我们也可以对运行时异常进行处理。
编译时异常
Exception中除了RuntimeException的子类,其他异常都是必须要处理的,如果不处理,编译时会报错,这些异常我们称之为编译时异常。
import java.io.*;
class ExceptionDemo2 {
public static void main(String[] args) {
try{
int[] arr = {1};
System.out.println(arr[100]);
BufferedReader br = new BufferedReader(new FileReader("F:/Test.txt"));
String line = br.readLine();
System.out.println(line);
} catch(ArithmeticException e) {
System.out.println("算数异常");
} catch(IOException e) {
System.out.println("IO异常");
} catch(Exception e) {
System.out.println("其他异常");
// e.printStackTrace(); // 打印异常信息, 包括异常类型, 异常消息, 异常位置
}
}
}
// new FileReader("F:/Test.txt") --> main --> JVM --> 打印
// new FileReader("F:/Test.txt") --> main --> catch --> 处理
异常的用法
抛出异常
在程序运行过程中可以使用throw关键字抛出一个Throwable的子类对象,通知调用者
处理异常
在程序中可以在方法后面使用throws关键字声明向外抛出异常
对于编译时异常,通常我们需要使用try……catch语句进行捕获
finally也可以结合try使用,只要执行了try中的代码,finally就一定会执行
异常的一些细节
如果父类方法中声明抛出多个异常,那么重写(覆盖)该方法只能抛出那些异常的一个子集,也就是说子类不能比父类抛出更多的异常。
如何处理多个异常
try语句与finally的嵌套使用
自定义异常
可以通过继承Exception类来自定义一个异常
如果要定义一个运行时异常则需要继承RuntimeException类
/*
从键盘接收一个十进制正数, 程序将其转为二进制并显示
程序判断键盘输入, 如果输入非数字给予提示, 如果输入数字太大给予提示
*/
import java.io.*;
import java.math.*;
class Test1 {
public static void main(String[] args) throws IOException{
System.out.println("请输入一个十进制正数, 程序将转换为二进制:");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
for(;;){
String line = br.readLine();
try{
int x = Integer.parseInt(line);
System.out.println(Integer.toBinaryString(x));
break;
} catch(NumberFormatException e) {
try {
new BigInteger(line);
System.out.println("数字太大, 请重新输入: ");
} catch(NumberFormatException ex) {
System.out.println("包含非数字, 请重新输入: ");
}
}
}
}
}
1.11. 包
Java中常用的包
java.lang
包含一些Java语言的核心类,如String、Math、Integer、System和Thread,提供常用功能。
java.awt
包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。
java.net
包含执行与网络相关的操作的类。
java.io
包含能提供多种输入/输出功能的类。
java.util
包含一些实用工具类,如定义系统特性、使用与日期日历相关的函数。
package cn.itcast; // 定义包
import org.it315.Person; // 导入包
public class PackageDemo {
public static void main(String[] args){
Person p1 = new Person("张三", 19);
Person p2 = new Person("李四", 20);
System.out.println(p1);
System.out.println(p2);
}
}
定义带包类
使用package语句加上包名来定义类所属于的包,包名全部小写
package语句为Java源文件的第一条语句
如果一个类中没有使用package语句,这个类为缺省无包名
一个类如果想被其他包中的类引用,必须使用public关键字修饰。构造函数也需要public
如果一个类被声明为public,那么必须和文件名同名
使用带包的类
在使用带包的类时需要使用全限定名(包名.类名)
在每次写类名时都使用全限定名很麻烦,我们可以使用import导入包,之后再使用就无需写包名了
星号*:导入一个包中所有类。优先匹配当前包中的类,如果当前包没有再匹配导入包中的类。
具体类名:导入指定一个类。无论当前包中是否有同名类,都直接匹配导入的类。
无包的类可以使用有包的类,有包的类不能使用无包的类。
编译运行带包的类
编译一个带包的源文件,在生成class文件的同时需要生成包文件
编译命令:javac –d <目录> 源文件名.java
运行有包的类时需要加上包名
运行命令:java 包名.类名
package cn.itcast;
class InnerClass {
public static void main(String[] args){
new A().fun();
}
}
class A {
void fun(){
new cn.itcast.B(); // 直接创建外部类, 需要加包名
}
class B{
B(){
System.out.println("内部类");
}
}
}
class B{
B(){
System.out.println("普通类");
}
}
// 在内部类和外部类同名的时候, 需要使用包名来访问
// 如果所有类的都没有包, 那么不能定义同名的类
// 有包的类不能访问无包的类
1.12. jar文件
什么是jar文件
jar文件是Java文件的一种压缩格式
一般来讲,我们会将一个软件系统的所有class文件打成一个jar文件以供别人使用
当我们用到jar包中的类时,需要将jar文件的绝对路径加到classpath当中
如何压缩jar文件
将编译好的带包的class文件压缩成一个jar文件称为打jar
打jar命令:jar cvf jar包名.jar 要打包的文件/文件夹
运行jar文件命令: java -jar jar文件名.jar
package cn.itcast;
public class TemplatePattern {
public static void main(String[] args) {
new Template() {
public void doSomething() {
System.out.println("123");
}
}.count();
}
}
/*
* 将每次都一样的代码定义在一个方法中, 将该方法final, 不允许重写.
* 每次不同的代码写成一个抽象方法, 在final的方法中调用该抽象方法.
* 使用的时候重写这个抽象方法即可
*/
abstract class Template {
public final void count() {
long start = System.currentTimeMillis(); // 从1970年1月1日0时, 到当前时间, 经过了多少毫秒. 1秒 = 1000毫秒
doSomething();
long end = System.currentTimeMillis();
System.out.println("使用了" + (end - start) + "毫秒");
}
public abstract void doSomething();
}
1.13. 访问控制符
类的访问控制符有两种:
public关键字修饰:可以被所有的类访问
缺省为default:只能被同一包中的类访问
1.14. 代码编写规范
标识符命名规则(驼峰式)
类名首字母大写:XxxYyyZzz
变量名、方法名首字母小写:xxxYyyZzz
包名全小写:xxx.yyy.zzz
常量名全大写:XXX_YYY_ZZZ
大括号的位置
大括号应成对出现,第一个大括号应在第一行语句后面
方法后面紧跟大括号,没有空格
关键字(while、for、if)后面应留一个空格
赋值语句之间用分号分隔,分号后面应空一格
代码折行应按照代码等级对齐,运算符写在下一行
------------ <a href="http://edu.csdn.net/heima" target="blank">android培训</a>、<a href="http://edu.csdn.net/heima" target="blank">java培训</a>、期待与您交流! -----------