资讯专栏INFORMATION COLUMN

Java基础提高

JasinYip / 1042人阅读

摘要:获得对象的方法有三种判定指定的对象是否表示一个基本类型。接下来给出的是与其它类相比而言独一无二的特征提供一个默认的无参构造函数。返回对象,表示声明此类型的类或接口。

这是传智张孝祥老师Java高新技术的授课笔记
我认为讲的很棒,授课传送门如下:
Java高新技术

一、枚举 1.基本应用
public class Test {
    public static void main(String[] args) {
        WeekDay day1=WeekDay.FRI;
        System.out.println(day1.name());
        System.out.println(day1.ordinal());
        System.out.println(WeekDay.valueOf("SUN"));
    }
}
enum WeekDay{
    SUN,MON,TUE,WED,THU,FRI,SAT;
}
2.带有构造方法的枚举
public class Test {
    public static void main(String[] args) {
        WeekDay day1=WeekDay.FRI;
        System.out.println(day1.name());
        System.out.println(day1.ordinal());
        System.out.println(WeekDay.valueOf("SUN"));
    }
}
enum WeekDay{
    SUN(1),MON,TUE,WED,THU,FRI,SAT;
    private WeekDay(){//枚举类的静态变量在枚举被加载的时候就会创建,并且只能用私有修饰
        System.out.println("none parameter");
    }
    private WeekDay(int i){
        System.out.println("parameter:"+i);
    }
}
3.带有抽象方法的枚举
public class Test {
    public static void main(String[] args) {
    }
}

enum TrafficLamp{
    RED(30) {
        @Override
        TrafficLamp nextLamp() {
            return GREEN;
        }
    },GREEN(45) {
        @Override
        TrafficLamp nextLamp() {
            return YELLOW;
        }
    },YELLOW(5) {
        @Override
        TrafficLamp nextLamp() {
            return RED;
        }
    };
    abstract TrafficLamp nextLamp();
    private int time;
    private TrafficLamp(int time){
        this.time=time;
    }
}
二、反射 1.Class类

在Java中,每个class都有一个相应的Class对象。也就是说,当我们编写一个类,编译完成后,在生成的.class文件中,就会产生一个Class对象,用于表示这个类的类型信息。
获得class对象的方法有三种

        Class c1=Date.class;
        Class c2=new Date().getClass();
        Class c3=Class.forName("java.util.Date");
        System.out.println(c1==c2);
        System.out.println(c1==c3);

public boolean isPrimitive()判定指定的 Class 对象是否表示一个基本类型。
有九种预定义的 Class 对象,表示八个基本类型和 void。这些类对象由 Java 虚拟机创建,与其表示的基本类型同名,即 boolean、byte、char、short、int、long、float 和 double。 这些对象仅能通过下列声明为 public static final 的变量访问,也是使此方法返回 true 的仅有的几个 Class 对象。

public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        Class c1=Date.class;
        Class c2=int.class;
        Class c3=Integer.class;
        Class c4=Integer.TYPE;
        System.out.println(c1.isPrimitive());
        System.out.println(c2.isPrimitive());
        System.out.println(c3.isPrimitive());
        System.out.println(c4.isPrimitive());
        System.out.println(c2==c3);
        System.out.println(c2==c4);
        
        Class c5=int[].class;
        System.out.println(c5.isPrimitive());
        System.out.println(c5.isArray());
    }
}
2.反射

反射就是把类中的各种成分映射成相应的类,比如把“方法”映射成Method类,把“成员变量”映射成Field类等等

2.1 构造方法的反射应用

public class Test {
    public static void main(String[] args) throws Exception{
        Constructor constructor1=String.class.getConstructor(StringBuffer.class);//要是用类型
        String str2=(String)constructor1.newInstance(new StringBuffer("abc"));//要使用之前类型相同的对象
        System.out.println(str2);
        String str2=(String)Class.forName("java.lang.String").newInstance();//使用默认的构造方法
    }
}

2.2 成员变量的反射应用

“人有身高这一属性”与“我有身高这一属性不同”,也与“我的身高是XXX”不同

public class Test {
    public static void main(String[] args) throws Exception{
        Person me=new Person(180,140);
        Field height_field=Person.class.getField("height");
        //height_field指的是取得了Person这个类所具有的一个属性,即身高这样一个属性,并不是取得的某个人的实际身高
        System.out.println(height_field.get(me));
        Field weight_field=Person.class.getDeclaredField("weight");
        weight_field.setAccessible(true);
        System.out.println(weight_field.get(me));
    }
}

class Person{
    public int height;
    private int weight;
    public Person(int height, int weight) {
        super();
        this.height = height;
        this.weight = weight;
    }
}

修改某一对象中的成员变量举例:

import java.lang.reflect.Field;

public class Test {
    public static void main(String[] args) throws Exception{
        ReflectPoint pt1=new ReflectPoint(3,5);
        Field[] fields=ReflectPoint.class.getFields();
        for (Field field : fields) {
            if(field.getType()==String.class){
                String oldValue=(String)field.get(pt1);
                String newValue=oldValue.replace("b", "a");
                field.set(pt1, newValue);
            }
        }
        System.out.println(pt1);
    }
}


class ReflectPoint{
    public ReflectPoint(int x, int y) {
        super();
        this.x = x;
        this.y = y;
    }
    private int x;
    public int y;
    public String str1="ball";
    public String str2="basketball";
    public String str3="itcast";
    @Override
    public String toString() {
        return "ReflectPoint [x=" + x + ", y=" + y + ", str1=" + str1 + ", str2=" + str2 + ", str3=" + str3 + "]";
    }
}

2.3 成员方法的反射

基本应用
“人有跳的能力”与“我有跳的能力”不一样

public class Test {
    public static void main(String[] args) throws Exception{
        Method methodCharAt=String.class.getMethod("charAt", int.class);//后面指的是传入的参数
        //同样,这取得的是String类的这样一个方法,是一种属性,而不是某个对象的成员方法
        System.out.println(methodCharAt.invoke("abcde", 1));
        
        //someMethod.invoke(null,parameter)指的是调用的静态方法
    }
}

应用:
目标:写一个程序,这个程序能够根据用户提供的类名,去调用类方法

import java.lang.reflect.Method;

public class Test {
    public static void main(String[] args) throws Exception{
        
//        TestArguments.main(new String[]{"111","222","333"});
        
        String startingClassName=args[0];
        Method mainMethod=Class.forName(startingClassName).getMethod("main", String[].class);
        mainMethod.invoke(null, (Object)new String[]{"111","222","333"});
        /* 如果没有类型转换会出现problems
         * Type String[] of the last argument to method invoke(Object, Object...) 
         * doesn"t exactly match the vararg parameter type. Cast to Object[] to confirm the non-varargs invocation,
         * or pass individual arguments of type Object for a varargs invocation.
        */
    }
}


class TestArguments{
    public static void main(String[] args) {
        for (String string : args) {
            System.out.println(string);
        }
    }
}

要修改run configuration

2.4 数组与Object的关系及其反射类型
维度与类型同时相同时,得到的class即相同

public class Test {
    public static void main(String[] args) throws Exception{
        int[] a0=new int[3];
        int[] a1=new int[3];
        int[] a2=new int[4];
        System.out.println(a0.getClass()==a1.getClass());
        System.out.println(a1.getClass()==a2.getClass());
        System.out.println(a1.getClass().getName());
    }
}

2.5 数组的反射应用
举例

public class Test {
    public static void main(String[] args) throws Exception{
        
        printObject(new String[]{"a","b","c"});
        printObject("xyz");
    }

    private static void printObject(Object obj) {
        Class c=obj.getClass();
        if(c.isArray()){
            int len=Array.getLength(obj);
            for(int i=0;i

2.6 ArrayList_HashSet的比较及Hashcode分析

先比较hashcode如果hashcode相同,则运行equels方法,两者同时相等时,则认定为相同对象,如果之后修改了参与运算hashcode的成员变量,则会造成内存溢出,如下例子中remove会失效

import java.util.HashSet;

public class Test {
    public static void main(String[] args) throws Exception{
        HashSet set=new HashSet<>();
        TestArguments t1=new TestArguments(3);
        TestArguments t2=new TestArguments(3);
        set.add(t1);
        set.add(t2);
        System.out.println(set.size());
        t1.x=456;
        set.remove(t1);
        System.out.println(set.size());
    }

}


class TestArguments{
    int x;

    public TestArguments(int x) {
        super();
        this.x = x;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + x;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        TestArguments other = (TestArguments) obj;
        if (x != other.x)
            return false;
        return true;
    }
    
}
3.反射的作用——实现框架功能

框架
现在使用别人写的类的时候,有两种使用方式,一种是用户去使用别人的类(工具),另一种是别人的类去调用用户写的类(框架)。再打个比方:比如我做房子卖给用户居住,由用户自己安装门窗和空调,我制作的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架之中,即用户的门窗被房子调用,这就是框架。

框架要解决的核心问题
在写框架的时候并不知道用户要写的类名,所以框架中不能直接new某个类的实例对象,因此只能通过反射来做

代码举例

import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Collection;
import java.util.Properties;

public class Test {
    public static void main(String[] args) throws Exception{
        
        InputStream ips=new FileInputStream("config.properties");
        Properties props=new Properties();
        props.load(ips);
        ips.close();
        String className=props.getProperty("className");
        @SuppressWarnings("unchecked")
        Collection collection=(Collection)Class.forName(className).newInstance();
        
        ReflectPoint pt1=new ReflectPoint(3,3);
        ReflectPoint pt2=new ReflectPoint(5,5);
        ReflectPoint pt3=new ReflectPoint(3,3);
        
        collection.add(pt1);
        collection.add(pt2);
        collection.add(pt3);
        
        System.out.println(collection.size());
    }

}

config.properties文件内容为

className=java.util.ArrayList

3.1 管理配置文件的方式

import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Collection;
import java.util.Properties;

public class Test {
    public static void main(String[] args) throws Exception{
//        InputStream ips=new FileInputStream("config.properties");
        
        //以下的配置文件路径应该与该类的Java文件放在同一目录
//        InputStream ips=ReflectPoint.class.getResourceAsStream("config.properties");
        InputStream ips=ReflectPoint.class.getClassLoader().getResourceAsStream("zheteng/config.properties");
        Properties props=new Properties();//此方法首先搜索资源的父类加载器;如果父类加载器为 null,则搜索的路径就是虚拟机的内置类加载器的路径。
        props.load(ips);
        ips.close();
        String className=props.getProperty("className");
        @SuppressWarnings("unchecked")
        Collection collection=(Collection)Class.forName(className).newInstance();
        
        ReflectPoint pt1=new ReflectPoint(3,3);
        ReflectPoint pt2=new ReflectPoint(5,5);
        ReflectPoint pt3=new ReflectPoint(3,3);
        
        collection.add(pt1);
        collection.add(pt2);
        collection.add(pt3);
        
        System.out.println(collection.size());
    }

}



class ReflectPoint{
    public ReflectPoint(int x, int y) {
        super();
        this.x = x;
        this.y = y;
    }
    private int x;
    public int y;
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + x;
        result = prime * result + y;
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        ReflectPoint other = (ReflectPoint) obj;
        if (x != other.x)
            return false;
        if (y != other.y)
            return false;
        return true;
    }
}
4.JavaBean

JavaBean是特殊的Java类,使用Java语言书写,并且遵守JavaBean API规范。
接下来给出的是JavaBean与其它Java类相比而言独一无二的特征:

提供一个默认的无参构造函数。

需要被序列化并且实现了Serializable接口。

可能有一系列可读写属性。

可能有一系列的"getter"或"setter"方法。

Java bean 是个什么概念? - 回答作者: 杨博 通俗易懂

我认为,JavaBean的存在就是为了让类中的属性能更方便地被处理和提取

4.1 JavaBean的简单操作

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;

public class Test {
    public static void main(String[] args) throws Exception {
        ReflectPoint pt1 = new ReflectPoint(3, 5);
        String propertyName = "x";
        PropertyDescriptor pd = new PropertyDescriptor(propertyName, ReflectPoint.class);
        Method methodGetX = pd.getReadMethod();
        Object retVal = methodGetX.invoke(pt1);
        System.out.println(retVal);

        Method methodSetX = pd.getWriteMethod();
        methodSetX.invoke(pt1, 7);
        System.out.println(pt1.getX());
    }

}

class ReflectPoint {
    public ReflectPoint(int x, int y) {
        super();
        this.x = x;
        this.y = y;
    }

    private int x;
    private int y;

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

}

4.2 BeanUtils工具包操作JavaBean
需要BeanUtils与common.logging包

举例1:

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;

public class Test {
    public static void main(String[] args) throws Exception {
        ReflectPoint pt1 = new ReflectPoint(3, 5);
        
        
        System.out.println(BeanUtils.getProperty(pt1, "x"));
        System.out.println(BeanUtils.getProperty(pt1, "x").getClass().getName());
        BeanUtils.setProperty(pt1, "x", "9");//以string的形式对javabean进行操作
        System.out.println(pt1.getX());
        
        BeanUtils.setProperty(pt1, "birthday.time", 111);
        System.out.println(BeanUtils.getProperty(pt1, "birthday.time"));
        
        
        PropertyUtils.setProperty(pt1, "x", 9);//以属性本身的类型的形式对javabean进行操作
        System.out.println(PropertyUtils.getProperty(pt1, "x").getClass().getName());
    }
}

其中ReflectPoint为

import java.util.Date;

public class ReflectPoint {
    public ReflectPoint(int x, int y) {
        super();
        this.x = x;
        this.y = y;
        this.birthday = new Date();
    }

    private int x;
    private int y;
    private Date birthday;

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

}
三、注解 1.了解注解

1.1 @Deprecated

public class AnnotationTest {
    public static void main(String[] args) {
        sayHello();
    }
    
    @Deprecated
    public static void sayHello(){
        System.out.println("hello!SF.GG!");
    }
    
}

1.2 @Override

@Override
    public String toString() {
        return "AnnotationTest []";
    }
2.注解的定义与反射调用
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@MyAnnotation
public class AnnotationTest {
    
    public static void main(String[] args) {
        sayHello();
        
        if(AnnotationTest.class.isAnnotationPresent(MyAnnotation.class)){
            MyAnnotation myannotation=(MyAnnotation)AnnotationTest.class.getAnnotation(MyAnnotation.class);
            System.out.println(myannotation);
        }
            
        
    }
    
    @Deprecated
    public static void sayHello(){
        System.out.println("hello!SF.GG!");
    }

    
}

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
@interface MyAnnotation{
    
}
3.为注解增加各种属性

看起来注解就是为了方便为自定义的类打上一些标签的作用

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@MyAnnotation(color = "red", value = "abc", arrayValue = 1,annotationAttr=@MetaAnnotation("flx"))
public class AnnotationTest {

    @MyAnnotation("xyz") // 当只有一个value属性需要赋值的时候,可以不用写value
    public static void main(String[] args) {
        sayHello();

        if (AnnotationTest.class.isAnnotationPresent(MyAnnotation.class)) {
            MyAnnotation myannotation = (MyAnnotation) AnnotationTest.class.getAnnotation(MyAnnotation.class);
            System.out.println(myannotation.color());
            System.out.println(myannotation.value());
            System.out.println(myannotation.arrayValue().length);
            System.out.println(myannotation.trafficLamp().next());
            System.out.println(myannotation.annotationAttr().value());
        }

    }

    @Deprecated
    public static void sayHello() {
        System.out.println("hello!SF.GG!");
    }

}

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
@interface MyAnnotation {
    String color() default "blue";

    String value();

    int[] arrayValue() default { 3, 4, 4 };

    Lamp trafficLamp() default Lamp.RED;

    MetaAnnotation annotationAttr() default @MetaAnnotation("lhm");
}

@interface MetaAnnotation {
    String value();
}

enum Lamp {
    RED, YELLOW, GREEN;
    Lamp next() {
        if (this.equals(RED)) {
            return GREEN;
        }
        if (this.equals(GREEN)) {
            return YELLOW;
        } else {
            return RED;
        }
    }
}
四、泛型 1.基本应用
import java.lang.reflect.Constructor;
import java.util.ArrayList;

public class Test {
    public static void main(String[] args) throws Exception {
        ArrayList collection1=new ArrayList<>();
        collection1.add(1);
        collection1.add(1L);
        collection1.add("abc");
        int i=(Integer) collection1.get(0);
        System.out.println(collection1.get(0) instanceof Integer);
        
        Constructor constructer1=String.class.getConstructor(String.class);
        String str=constructer1.newInstance("abc");
        System.out.println(str);
    
    }
}
2.内部原理

2.1.泛型是给编译器使用的,可以在限定集合中输入类型,让编译器挡住源程序中的非法输入,编译器编译生成的字节码会去掉方形的类型信息的,如下所示

import java.util.ArrayList;

public class Test {
    public static void main(String[] args) throws Exception {
        ArrayList collection2 = new ArrayList<>();
        ArrayList collection3 = new ArrayList<>();

        System.out.println(collection2.getClass() == collection3.getClass());

    }
}

也因此,如下代码并不是重载,是错误的,因为运行时会去泛型信息

    public static void applyVector(Vector v1){
        
    }
    
    public static void applyVector(Vector v1){
        
    }

就是说泛型只是给编译器看的,运行的时候就没有泛型信息了,也因此可以根据这种原理,以反射的原理得到集合再调用add方法,比如往上面collection3里添加String

import java.util.ArrayList;

public class Test {
    public static void main(String[] args) throws Exception {
        ArrayList collection2 = new ArrayList<>();
        ArrayList collection3 = new ArrayList<>();

        System.out.println(collection2.getClass() == collection3.getClass());
        
//        collection3.add("abc");
//        报错
        collection3.getClass().getMethod("add", Object.class).invoke(collection3, 1);
        collection3.getClass().getMethod("add", Object.class).invoke(collection3, "abc");
        System.out.println(collection3); 
    }
}

2.2 语法规定

ArrayList称为泛型类型,E称为类型变量或类型参数,<>做typeof

参数化类型不考虑类型参数的继承关系,下面两个都是错误的

   ArrayList c=new ArrayList();
   ArrayList b=new ArrayList();

参数化和原始类型的兼容性,下面例子中C只能装string,而b可以加object,其实b那种写法和ArrayList b=new ArrayList()是一样的,因为泛型只是给编译器看的

   ArrayList c=new ArrayList();
   c.add("asdf");
   System.out.println(c.get(0).getClass());
   ArrayList b=new ArrayList();
   b.add(456);
   b.add("asdf");
   System.out.println(b.get(0).getClass());
   System.out.println(b.get(1).getClass());

下面不报错的

   ArrayList b=new ArrayList();
   ArrayList d=b;

2.3 泛型通配符
比如现在要打印一个类型参数是任意类型的集合,如下写法就是不对的

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

public class Test {
    public static void main(String[] args) throws Exception {
        ArrayList collection2 = new ArrayList<>();
        ArrayList collection3 = new ArrayList<>();
        System.out.println(collection3); 
        printCollection(collection3);//编译器不通过,因为之前说过,泛型类型并不存在类型参数的继承关系
        
    }
    
    public static void printCollection(Collection collection){
        
    }
    
}

这就需要通配符了

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

public class Test {
    public static void main(String[] args) throws Exception {
        ArrayList collection2 = new ArrayList<>();
        ArrayList collection3 = new ArrayList<>();
        System.out.println(collection3); 
        printCollection(collection3);//编译器不通过,因为之前说过,泛型类型并不存在类型参数的继承关系
        
    }
    
    public static void printCollection(Collection collection){
//        collection.add(123);
//        会报错,因为使用了通配符,因此不能调用与类型参数相关的方法        
//        参数(int)不适用于Collection 类型的add(capture#1-of?)方法,
        collection.size();//这就没错,因为size方法与类型参数没有关系
        for (Object object : collection) {
            System.out.println(object);
        }
    }
}

使用?通配符可以引用各种参数类型,其主要作用是引用,而不是写入
通配符也有拓展功能

限定上边界
? extends Number要求传入的必须是Number的子类

限定下边界
? super Integer要求传入的必须是Integer的父类

3.自定义泛型

3.1 泛型方法

public class Test {
    public static void main(String[] args) throws Exception {

        //结果就是二者的交集
        Number num=add(3,51.0);
        Integer inte=add(3,51);
        Object o=add(3,"123");
        
        swap(new String[]{"aaa","bbb","ccc"},1,2);
//        swap(new int[]{123,456,789},1,2);//泛型变量只能是引用对象,int[]已经是一个基本类型的数组,它并不能完成自动装箱
    }
    
    private static  T add(T x,T y){
        return null;
    }
    
    private static  void swap(T[] a,int i,int j){
        T temp=a[i];
        a[i]=a[j];
        a[j]=temp;
    }
    
    private static  void sayHello() throws T{
        try{
            
        }catch(Exception e){//必须明确是哪个异常,不能catch T
            throw (T)e;
        }
    }
}

3.2 在类上定义泛型

就是为了保障类中的泛型能够统一,为此可以在类上定义泛型

public class Test {
    public static void main(String[] args) throws Exception {
        GenericDao dao=new GenericDao<>();
        
        dao.add("123");
    }
    
}


class GenericDao{
    public void add(T x){
        
    }
    
    public T getByID(int id){
        return null;
    }
    
    public void delete(T obj){
        
    }
}

3.3 通过反射获得泛型的实际类型参数

之前说过,泛型是给编译器看的,因此假如有一个变量Vector v1,仅仅从v1这个变量,是没法知道这个Vector里的泛型到底是什么的,因此通过其他一种方法获得,

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.Vector;

public class Test {
    public static void main(String[] args) throws Exception {

        Method applyMethod=Test.class.getMethod("applyVector", Vector.class);
        Type[] types=applyMethod.getGenericParameterTypes();
        /*
         * 按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的。如果底层方法不带参数,则返回长度为 0 的数组。 
         * 如果形参类型是参数化类型,则为其返回的Type对象必须实际反映源代码中使用的实际类型参数。 
         * 如果形参类型是类型变量或参数化类型,则创建它。否则将解析它。 
         */
        ParameterizedType ptype=(ParameterizedType)types[0];
        System.out.println(ptype.getRawType());
        /*
         * 返回 Type 对象,表示声明此类型的类或接口。 
         */
        System.out.println(ptype.getActualTypeArguments()[0]);
        /*
         * 返回表示此类型实际类型参数的 Type 对象的数组。
         */
    }
    
    public static void applyVector(Vector v1){
        
    }
    
}
五、类加载器 1.类加载器基本概念

要使用这个类,就要把.class文件加载进虚拟机,然后进行处理,就需要类加载器,Java虚拟机中可以安装多个类加载器,系统默认三个主要加载器,每个加载器都加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader
类加载器本身也是一个Java类,因此也需要被加载,因此一个特殊的加载器,即BootStrap,它不是一个Java类
而Java虚拟机中所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象之前,需要指定一个父极类加载器对象

public class Test {
    public static void main(String[] args) throws Exception {
        System.out.println(Test.class.getClassLoader().getClass());
        System.out.println(System.class.getClassLoader());
//        null,说明这是又bootstrap类加载器加载的
        
        ClassLoader loader=Test.class.getClassLoader();
        while(loader!=null){
            System.out.println(loader.getClass());
            loader=loader.getParent();
        }
    }
}
2.委托机制

(太困了扛不住了)

3.自定义类加载器

首先搞一个类的加密器,将类文件进行二进制简单加密,用生成的类文件覆盖掉原本的类文件后,再次运行程序,发现类加载错误

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;

public class MyClassLoader {

    public static void main(String[] args) throws Exception {
        String srcPath = "bin/zheteng/ClassLoaderAttachment.class";
        String destPath = "ClassLoaderAttachment.class";
        FileInputStream fis = new FileInputStream(srcPath);
        FileOutputStream fos = new FileOutputStream(destPath);
        cypher(fis,fos);
        fis.close();
        fos.close();
        
        System.out.println(new ClassLoaderAttachment().toString());
        
    }

    private static void cypher(InputStream ips, OutputStream ops) throws Exception {
        int b = -1;
        while ((b = ips.read()) != -1) {
            ops.write(b ^ 0xff);
        }
    }

}

class ClassLoaderAttachment extends Date {
    /**
     * 
     */
    private static final long serialVersionUID = -1118939564631068343L;

    public String toString(){
        return "hello,world";
    }
}

下面再搞一个解密的类加载器

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;

public class MyClassLoader extends ClassLoader {

    public static void main(String[] args) throws Exception {
        String srcPath = "bin/zheteng/ClassLoaderAttachment.class";
        String destPath = "ClassLoaderAttachment.class";
        FileInputStream fis = new FileInputStream(srcPath);
        FileOutputStream fos = new FileOutputStream(destPath);
        cypher(fis, fos);
        fis.close();
        fos.close();

         System.out.println(new ClassLoaderAttachment());

        Class d1 = new MyClassLoader().loadClass("ClassLoaderAttachment.class");
        Date d = (Date) d1.newInstance();

        System.out.println(d.toString());
    }

    private static void cypher(InputStream ips, OutputStream ops) throws Exception {
        int b = -1;
        while ((b = ips.read()) != -1) {
            ops.write(b ^ 0xff);
        }
    }


    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        String classFileNmae = name;
        try {
            System.out.println(name);
            FileInputStream fis = new FileInputStream(classFileNmae);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            cypher(fis, bos);
            fis.close();
            byte[] bytes = bos.toByteArray();
            return defineClass(bytes, 0, bytes.length);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return super.findClass(name);
    }

    public MyClassLoader() {

    }

}

这次的attachment必须定义为public类否则上面的反射会异常访问权限报错

import java.util.Date;

public class ClassLoaderAttachment extends Date {
    /**
     * 
     */
    private static final long serialVersionUID = -1118939564631068343L;

    public String toString(){
        return "hello,worldxx";
    }
}

其实我觉得这块讲的我没太听明白,以后看thingking in java的时候再补上吧

六、代理 1.代理的概念与作用

1.1 代理
要为已存在的具有相同接口的目标类的各个方法添加一套系统功能,如异常处理,日志记录等,如

class x{
    A
    void sayHello{
        syso:hello;
    }
    B
}

现在想在AB两点记录时间,测试运行时间,而且没有程序的源代码,该如何处理
需要定义一个 新的代理类

XProxy
{
    void sayHello{
        starttime
        syso:hello
        endtime
    }
}

相当于用这个代理来运行这个程序,以完成相应的目的,这就是代理的作用。代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能额代码

如果采用工行模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中修改,来配置使用目标类还是代理类,就很方便

1.2 AOP

安全,事务,日志等功能贯穿到很多模块中,所以是交叉业务
可以运用代理的方法,来将交叉功能与实际功能区分开

1.3 动态代理技术
要为系统中的各个接口的实现类添加代理功能,会需要太多的代理类,全部采用静态代理的方式(就像AOP图中的从method到func)就非常麻烦,因此需要动态代理类
JVM可以在运行期动态生成出类的字节码,这种类会用来做动态代理功能 。JVM生成的动态类必须实现至少一个接口,所以JVM生成的动态类只能用作具有相同接口的目标类的代理。但如果这个目标类没有接口呢,那JVM就不能生成这个类的动态类了。
这就需要CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以要为一个没实现接口的目标类生成动态代理类,则需要CGLIB库
代理类的各个方法除了要调用目标的相应方法和对外返回目标方法返回结果外,还可以在如下四个位置添加各种功能:

调用目标方法前

调用目标方法后

调用目标方法前后

在处理目标方法异常的catch块中

2.JVM生成动态类

2.1 首先来看看动态类的名字,构造方法和其他方法

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;

public class Test {
    public static void main(String[] args) throws Exception {
        Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);// 生成的动态类要为其指定类加载器与接口
        System.out.println(clazzProxy1.getName());
        
        Constructor[] constructors = clazzProxy1.getConstructors();        
        System.out.println("--------------------begin constructors list");
        for (Constructor constructor : constructors) {
            String name = constructor.getName();
            StringBuilder sBuilder=new StringBuilder(name);
            sBuilder.append("(");
            Class[] clazzParams=constructor.getParameterTypes();
            for (Class clazzParam : clazzParams) {
                sBuilder.append(clazzParam.getName()).append(",");
            }
            sBuilder.append(")");
            System.out.println(sBuilder);
        }
        
        
        Method[] methods = clazzProxy1.getMethods();
        System.out.println("---------------------begin methods list");
        for (Method method : methods) {
            String name = method.getName();
            StringBuilder sBuilder=new StringBuilder(name);
            sBuilder.append("(");
            Class[] clazzParams=method.getParameterTypes();
            for (Class clazzParam : clazzParams) {
                sBuilder.append(clazzParam.getName()).append(",");
            }
            sBuilder.append(")");
            System.out.println(sBuilder);
        }
    }
}

2.2 再来生成一个这个类的实例对象,三种方法

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;

public class Test {
    public static void main(String[] args) throws Exception {
        Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
        // 生成的动态类要为其指定类加载器与接口,一般使用接口所用的加载器
        System.out.println(clazzProxy1.getName());
        
        Constructor[] constructors = clazzProxy1.getConstructors();        
        System.out.println("--------------------begin constructors list");
        for (Constructor constructor : constructors) {
            String name = constructor.getName();
            StringBuilder sBuilder=new StringBuilder(name);
            sBuilder.append("(");
            Class[] clazzParams=constructor.getParameterTypes();
            for (Class clazzParam : clazzParams) {
                sBuilder.append(clazzParam.getName()).append(",");
            }
            sBuilder.append(")");
            System.out.println(sBuilder);
        }
        
        
        Method[] methods = clazzProxy1.getMethods();
        System.out.println("---------------------begin methods list");
        for (Method method : methods) {
            String name = method.getName();
            StringBuilder sBuilder=new StringBuilder(name);
            sBuilder.append("(");
            Class[] clazzParams=method.getParameterTypes();
            for (Class clazzParam : clazzParams) {
                sBuilder.append(clazzParam.getName()).append(",");
            }
            sBuilder.append(")");
            System.out.println(sBuilder);
        }
        
        
        System.out.println("---------------------begin create instance");
        Constructor constructor=clazzProxy1.getConstructor(InvocationHandler.class);
        class MyInvocationHandler1 implements InvocationHandler{
            @Override
            public Object invoke(Object paramObject, Method paramMethod, Object[] paramArrayOfObject) throws Throwable {
                // TODO Auto-generated method stub
                return null;
            }
            
        }
        Collection proxy1=(Collection)constructor.newInstance(new MyInvocationHandler1());
        //创建的就是collection的子类
        System.out.println(proxy1);
        System.out.println(proxy1.toString());//说明proxy1并不是个空指针
        
        
        Collection proxy2=(Collection)constructor.newInstance(new InvocationHandler(){
            @Override
            public Object invoke(Object paramObject, Method paramMethod, Object[] paramArrayOfObject) throws Throwable {
                return null;
            }
        });
        
        Collection proxy3=(Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(),
                new Class[]{
                    Collection.class    
                },
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object paramObject, Method paramMethod, Object[] paramArrayOfObject) throws Throwable {
                        
                        return null;
                    }
                });
    }
}

2.3 来看看InvocationHandler有什么用

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;

public class Test {
    public static void main(String[] args) throws Exception {
        Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
        // 生成的动态类要为其指定类加载器与接口,一般使用接口所用的加载器
        System.out.println(clazzProxy1.getName());
        
        Constructor[] constructors = clazzProxy1.getConstructors();        
        System.out.println("--------------------begin constructors list");
        for (Constructor constructor : constructors) {
            String name = constructor.getName();
            StringBuilder sBuilder=new StringBuilder(name);
            sBuilder.append("(");
            Class[] clazzParams=constructor.getParameterTypes();
            for (Class clazzParam : clazzParams) {
                sBuilder.append(clazzParam.getName()).append(",");
            }
            sBuilder.append(")");
            System.out.println(sBuilder);
        }
        
        
        Method[] methods = clazzProxy1.getMethods();
        System.out.println("---------------------begin methods list");
        for (Method method : methods) {
            String name = method.getName();
            StringBuilder sBuilder=new StringBuilder(name);
            sBuilder.append("(");
            Class[] clazzParams=method.getParameterTypes();
            for (Class clazzParam : clazzParams) {
                sBuilder.append(clazzParam.getName()).append(",");
            }
            sBuilder.append(")");
            System.out.println(sBuilder);
        }
        
        
        System.out.println("---------------------begin create instance");
        Constructor constructor=clazzProxy1.getConstructor(InvocationHandler.class);
        class MyInvocationHandler1 implements InvocationHandler{
            @Override
            public Object invoke(Object paramObject, Method paramMethod, Object[] paramArrayOfObject) throws Throwable {
                // TODO Auto-generated method stub
                return null;
            }
            
        }
        Collection proxy1=(Collection)constructor.newInstance(new MyInvocationHandler1());
        //创建的就是collection的子类
        System.out.println(proxy1);
        System.out.println(proxy1.toString());//说明proxy1并不是个空指针
        
        
        Collection proxy2=(Collection)constructor.newInstance(new InvocationHandler(){
            @Override
            public Object invoke(Object paramObject, Method paramMethod, Object[] paramArrayOfObject) throws Throwable {
                return null;
            }
        });
        
        Collection proxy3=(Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(),
                new Class[]{
                    Collection.class    
                },
                new InvocationHandler() {
                    ArrayList target=new ArrayList();
                    @Override
                    public Object invoke(Object paramObject, Method paramMethod, Object[] paramArrayOfObject) throws Throwable {
                        long beginTime=System.currentTimeMillis();
                        Object reVal=paramMethod.invoke(target, paramArrayOfObject);
                        long endTime=System.currentTimeMillis();
                        System.out.println(paramMethod.getName()+" running time of "+(endTime-beginTime));
                        return reVal;
                    }
                });
        proxy3.add("zzz");
        proxy3.add("ffffdd");
        proxy3.add("yyyy");
        /*
         * 在调用这个方法的时候,他都会找InvocationHandler的invoke方法
         */
        System.out.println(proxy3.size());
    }
}

实现原理解释
构造方法中接受了一个InvocationHandler对象,在新建动态类实例对象之后,该对象调用一个方法的时候,比如上面调用了add方法,这个对象会实际是调用了proxy3的invoke方法,大概就像下面这样

boolean add(Object paramE){
        return handler.invoke(this,this.getClass().getMethod("add", paramE.getClass()),paramE)
    }

注意,oject方法中只将hashcode,tostring,equal下发使用
在实际情况中,应该将需要被处理(或者说添加功能)的对象与要添加的功能与动态代理类分开,这样在应用的时候,只需要传入对象与功能,就可以实现动态代理,而不需要为每个要添加的功能多带带写一个动态代理类,把上面的例子改一下,结果如下:

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;

public class Test {
    public static void main(String[] args) throws Exception {
        Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
        // 生成的动态类要为其指定类加载器与接口,一般使用接口所用的加载器
        System.out.println(clazzProxy1.getName());
        
        Constructor[] constructors = clazzProxy1.getConstructors();        
        System.out.println("--------------------begin constructors list");
        for (Constructor constructor : constructors) {
            String name = constructor.getName();
            StringBuilder sBuilder=new StringBuilder(name);
            sBuilder.append("(");
            Class[] clazzParams=constructor.getParameterTypes();
            for (Class clazzParam : clazzParams) {
                sBuilder.append(clazzParam.getName()).append(",");
            }
            sBuilder.append(")");
            System.out.println(sBuilder);
        }
        
        
        Method[] methods = clazzProxy1.getMethods();
        System.out.println("---------------------begin methods list");
        for (Method method : methods) {
            String name = method.getName();
            StringBuilder sBuilder=new StringBuilder(name);
            sBuilder.append("(");
            Class[] clazzParams=method.getParameterTypes();
            for (Class clazzParam : clazzParams) {
                sBuilder.append(clazzParam.getName()).append(",");
            }
            sBuilder.append(")");
            System.out.println(sBuilder);
        }
        
        
        System.out.println("---------------------begin create instance");
        Constructor constructor=clazzProxy1.getConstructor(InvocationHandler.class);
        class MyInvocationHandler1 implements InvocationHandler{
            @Override
            public Object invoke(Object paramObject, Method paramMethod, Object[] paramArrayOfObject) throws Throwable {
                // TODO Auto-generated method stub
                return null;
            }
            
        }
        Collection proxy1=(Collection)constructor.newInstance(new MyInvocationHandler1());
        //创建的就是collection的子类
        System.out.println(proxy1);
        System.out.println(proxy1.toString());//说明proxy1并不是个空指针
        
        
        Collection proxy2=(Collection)constructor.newInstance(new InvocationHandler(){
            @Override
            public Object invoke(Object paramObject, Method paramMethod, Object[] paramArrayOfObject) throws Throwable {
                return null;
            }
        });
        ArrayList target=new ArrayList();
        Collection proxy3=(Collection)getProxy(target,new MyAdvice());
        proxy3.add("zzz");
        proxy3.add("ffffdd");
        proxy3.add("yyyy");
        /*
         * 在调用这个方法的时候,他都会找InvocationHandler的invoke方法
         */
        System.out.println(proxy3.size());
        System.out.println(proxy3);
    }

    private static Object getProxy(Object target,Advice advisor) {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object paramObject, Method paramMethod, Object[] paramArrayOfObject) throws Throwable {
                        advisor.beforeMethod(paramMethod);
                        Object reVal=paramMethod.invoke(target, paramArrayOfObject);
                        advisor.afterMethod(paramMethod);
                        return reVal;
                    }
                });
    }
}

interface Advice{
    void beforeMethod(Method method);
    void afterMethod(Method method);
}

class MyAdvice implements Advice{
    long beginTime=0;
    @Override
    public void afterMethod(Method method) {
        System.out.println("结束");
        long endTime=System.currentTimeMillis();
        System.out.println(method.getName()+" running time of "+(endTime-beginTime));
    }

    @Override
    public void beforeMethod(Method method) {
        System.out.println("开始");
        beginTime=System.currentTimeMillis();
    }
    
}

这样,如果我想给A对象添加一个α功能,那就传个A和实现了α功能的advisor类就可以了,改明儿要是想把α功能换成β功能,直接将参数换成一个新的实现了β功能的advisor类就可以了,不用再去重新写一个InvocationHandler。代码分离,易于维护。

2.4 实现类似spring的可配置AOP框架

需求:如果传入的是普通类,则返回普通类实例对象,如果传入的是需要代理的类,则返回代理类的动态代理对象,通过修改配置文件来进行切换

package zheteng;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class BeanFactory {
    Properties props=new Properties();
    public BeanFactory(InputStream ips) {
        try {
            props.load(ips);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public Object getBean(String name){
        String className=props.getProperty(name);
        Object bean=null;
        try {
            Class clazz=Class.forName(className);
            bean=clazz.newInstance();
            if(bean instanceof ProxyFactoryBean){
                ProxyFactoryBean proxyFactoryBean=(ProxyFactoryBean)bean;
                Advice advisor=(Advice) Class.forName(props.getProperty(name+".advice")).newInstance();
                Object target=Class.forName(props.getProperty(name+".target")).newInstance();
                proxyFactoryBean.setAdvisor(advisor);
                proxyFactoryBean.setTarget(target);
                Object proxy=proxyFactoryBean.getProxy();
                return proxy;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return bean;
    }
}
package zheteng;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyFactoryBean {

    private Advice advisor;
    private Object target;
    public Advice getAdvisor() {
        return advisor;
    }
    public void setAdvisor(Advice advisor) {
        this.advisor = advisor;
    }
    public Object getTarget() {
        return target;
    }
    public void setTarget(Object target) {
        this.target = target;
    }
    public Object getProxy() {
        // TODO Auto-generated method stub
        Object proxy3=Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
            new InvocationHandler() {
                @Override
                public Object invoke(Object paramObject, Method paramMethod, Object[] paramArrayOfObject) throws Throwable {
                    advisor.beforeMethod(paramMethod);
                    Object reVal=paramMethod.invoke(target, paramArrayOfObject);
                    advisor.afterMethod(paramMethod);
                    return reVal;
                }
            });
        return proxy3;
    }

}

interface Advice{
    void beforeMethod(Method method);
    void afterMethod(Method method);
}
class MyAdvice implements Advice{
    long beginTime=0;
    @Override
    public void afterMethod(Method method) {
        System.out.println("结束");
        long endTime=System.currentTimeMillis();
        System.out.println(method.getName()+" running time of "+(endTime-beginTime));
    }

    @Override
    public void beforeMethod(Method method) {
        System.out.println("开始");
        beginTime=System.currentTimeMillis();
    }
    
}
package zheteng;

import java.io.InputStream;

public class AopFrameworkTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties");
        System.out.println(ips);
        Object bean = new BeanFactory(ips).getBean("xxx");
        System.out.println(bean.getClass());
    }

}
#xxx=java.util.ArrayList
xxx=zheteng.ProxyFactoryBean
xxx.advice=zheteng.MyAdvice
xxx.target=java.util.ArrayList

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67502.html

相关文章

  • 菜鸟程序员如何才能快速提高自己的技术

    摘要:当初弄这个公众号的本意就是为广大的开发者提供各种技术分享,发布最前沿的科技信息和技术。学会了自己查文档,就提高了自己解决问题的能力了,也提高了学习新技术的能力。多去浏览一些技术博客和网站,提高自己。 导语:很久没有这么悠闲的在家撸一篇文章了,最近也在思考怎样才能写一些对程序员帮助非常大的文章,怎样去运营好我们这个移动开发者聚集地的公众号:非著名程序员。当初弄这个公众号的本意就是为广大的...

    Paul_King 评论0 收藏0
  • Java程序员的成长之路

    摘要:虽然题目是写的程序员,但对其他语言的开发来说也会有借鉴作用。一定要记住,作为一个程序猿,平日里所接触的技术可能会很多,但是想要让一门技术成为你的优势,那么一定是你对这门技术的了解强过绝大多数人才行。 阅读本文大概需要 8.2 分钟。 tips:虽然题目是写的Java程序员,但对其他语言的开发来说也会有借鉴作用。 本篇介绍的是大体思路,以及每个节点所需要学习的书籍内容,如果大家对详细的技...

    stormjun 评论0 收藏0
  • 乐字节Java之file、IO流基础知识和操作步骤

    摘要:流分类由此可见,流很庞大从不同角度进行分类数据分类按处理数据单位分为字节流和字符流。处理数据是音频视频文本等一切为字节流,仅能处理文本的为字符流。功能分类节点流和处理流。从向一个特定的设备磁盘网络等读写数据的流称为节点流,也常被称为低级流。 嗨喽,小乐又来了,今天要给大家送上的技术文章是Java重点知识-IO流。 先来看看IO流的思维导图吧。showImg(https://segmen...

    pkhope 评论0 收藏0
  • NIO网络相关基础知识

    摘要:操作系统是能够获取到事件操作完成的事件,基于回调函数机制和操作系统的操作控制实现事件检测机制。 前面的文章NIO基础知识介绍了Java NIO的一些基本的类及功能说明,Java NIO是用来替换java 传统IO的,NIO的一些新的特性在网络交互方面会更加的明显。 Java 传统IO的弊端     基于JVM来实现每个通道的轮询检查通道状态的方法是可行的,但仍然是有问题的,检查每个通道...

    1fe1se 评论0 收藏0
  • 阿里p8架构师谈,对于Java程序猿学习当中各个阶段的建议。

    摘要:第三部分对于参加工作年到年的同学。我当时看的是大话设计模式这本书,并且写了完整版的设计模式博客。这一年,你必须对于设计模式了如指掌,大话设计模式可以作为你的开端。与此同时,这个阶段你要做的事情还远不止如此。 这一部分其实也算是今天的重点,这一部分用来回答很多群里的朋友所问过的问题,那就是大佬你是如何学习Java的,能不能给点建议? 今天我是打算来点干货,因此咱们就不说一些学习方法和技巧...

    sevi_stuo 评论0 收藏0
  • 阿里p8架构师谈,对于Java程序猿学习当中各个阶段的建议。

    摘要:第三部分对于参加工作年到年的同学。我当时看的是大话设计模式这本书,并且写了完整版的设计模式博客。这一年,你必须对于设计模式了如指掌,大话设计模式可以作为你的开端。与此同时,这个阶段你要做的事情还远不止如此。 这一部分其实也算是今天的重点,这一部分用来回答很多群里的朋友所问过的问题,那就是大佬你是如何学习Java的,能不能给点建议? 今天我是打算来点干货,因此咱们就不说一些学习方法和技巧...

    gyl_coder 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<