Java第七章 类和对象

news/2024/7/3 9:28:24 标签: java, 知识点, c#, Class, 对象

目录

# c#语法:

java%E8%AF%AD%E6%B3%95-toc" style="margin-left:80px;"># java语法

java1.8%E7%9A%84%E9%BB%98%E8%AE%A4%E6%96%B9%E6%B3%95%EF%BC%88default%EF%BC%89%E5%92%8C%E9%9D%99%E6%80%81%E6%96%B9%E6%B3%95%E3%80%82-toc" style="margin-left:80px;"># java1.8的默认方法(default)和静态方法。

# 序列化:


可将将类看做是对象的载体

1.对象:静态部分,动态部分,静态的为属性,动态的为行为。

面向对象的三大特点:

1.封装:封装变化,让封装的类或组件,尽量只负责一个领域的工作.。

2.继承:主要利用对象的共性解决同样的问题,概念形成统一,通过继承可以管理多个概念。

3.多态:同一个行为的不同表现方法 ,解决父类的方法在子类不适合的问题。

主要思想:

分而治之,高内聚,低耦合,封装变化。

主要原则:

开-闭原则:对扩展开放,对修改关闭。

类的单一指责:类定义的时候,只包含一个或者一类与类本身关系的密切功能。一个类有且只有一个改变它的原因。

面向接口编程而非面向实现:先思考做什么,为其他的组件提供什么功能。

优先使用组合而非继承:如果仅仅为了复用类,优先选择组合,如果需要复用代码的同时服用概念,使用继承。组合的耦合性比继承低。

依赖倒置:尽量依赖抽象,因为抽象是稳定的,实现是多变的。

里氏替换:子类在重写父类方法时,尽量选择扩展重写。

接口隔离:尽量定义小而精的接口,少定义大而全的接口。

迪米特法则:类与类交互时,在满足功能的基础上,传递的数据约少越好,这样可以降低耦合度。

# c#语法:

1.构造方法:与类 同名的方法,创建类的对象时自动调用,在类中没有定义任何构造方法时,编译器会在该类中提供一个不带参数的构造方法,当类中有自定义的构造方法,则不提供,如果定义的是有参构造方法,那么调用无参构造方法会报错。这里跟C#是一样的,如果在类中定义了有参的构造函数,那么必须再定义一个无参的构造函数,否则调用无参构造函数会报错。this关键字可以调用类的成员变量和方法,this就是当前类的对象。这里还要注意下接口和抽象方法。构造方法不能有返回值,并且它可以重载。

构造方法不会继承给子类,但是在创建子类对象时,先从子进入父,自动调用父类的构造方法,且父类构造方法先执行,子类构造方法后执行.当子类采用无参构造方法创建对象时,默认调用父类的无参构造方法,如果父类没有无参构造方法,则报编译错误(如果父类给了有参的构造函数,就不会给无参的构造函数,这时用子类的无参创建对象就会报错),解决方法有两个: 1.为父类添加无参构造方法, 2.在子类的构造方法中用base关键字指明要调用父类的哪一个有参构造方法,2优先1.多态的方式有:重写,隐藏,重载,抽象方法,接口。继承protected修饰的成员只能在派生类里调用。而java可以在同包下嗲用或者不同包下有继承关系调用。静态构造函数不能有访问修饰符。静态类不能被继承,但是静态方法,属性等可以继承

隐藏方法:本质指的是方法用new修饰的方法,如果使用隐藏技术,调用父中的方法有2中 父类对象= new 父类()父类对象= new 子类()。如果使用隐藏技术,调用子类的方法有1中。子类 对象= new 子类()缺点:不能实现父调子!原理:两个地址,new谁在谁的方法列表中找,声明谁找谁的方法地址。

虚方法重写:用vritual关键修饰的已经实现的方法,子类中的方法名字前加 override,也可以不重写。与隐藏方法不同的是父类对象= new 子类()调子类。原理:重写方法表中地址。方法列表中有2个方法。但是原方法的地址被新方法的地址覆盖,所以new子类调用子类的方法,

抽象方法:抽象方法用abstract修饰,在子类中必须重写,除非子类中也是抽象的。重写方法用关键字override,已经重写过的方法,在子类还可以继续重写,除非被标识为sealed。sealed修饰的为封闭方法或者类。不能够被继承。封闭类(sealed修饰的类)的特点,可以new ,不能被继承。抽象类(abstract修饰的类)不能实例化,可以被继承。抽象类中可以有字段,可以有实现方法。抽象方法没有实现。抽象方法只能出现在抽象类中。放在抽象类中的抽象方法必须加abstract,实现类在实现抽象类中的抽象方法必须加Override。放在接口中的抽象方法不能加abstract,实现类在实现接口中的抽象方法不加Override。

定义使用关键字interface创建的数据类型。

接口:接口名建议用”I”开头,其后单词首字母大写。接口只关注行为,不关注数据(不能有字段),且不关注行为的实现(没有方法体),实现由实现类完成。接口是定义一组对外的行为规范,要求它的实现类必须遵循(行为默认是Public并且不能添加public,实现类必须实现接口中所有成员)父债子还也就是说子类中实现所有方法,可以有自动属性,在子类中实现。实现类中必须加public(除显示实现:方法名改为接口名加”。并且不能加任何访问修饰符,默认为private,显式实现成员只能通过接口类型的引用调用)。接口中的所有成员不能加任何访问修饰符,全部默认公有。接口中的所有成员不能有实现,全部默认抽象的。解决接口中的成员对实现类不适用的问题  如果接口中包含的方法比较多,个别不适合实现类,不适合的用显示实现,好处就是通过类的引用无法调用,【一定程度上的避免了接口污染】,解决多接口实现时的二义性。结构(struct)可以实现接口,但不能继承类。结构用于封装小型相关变量的值类型,于类语法类似,都可以包含数据成员和方法,但结构属于值类型,类是引用类型。表示点,颜色等轻量级对象,如创建1000个点的数组,结构里的字段除非被声明为const或static类型,否则无法初始化。无论是否有有参构造函数,结构体都会给无参构造函数,结构体于其他类不一样,再有参中必须初始化所有字段。

静态绑定:是指调用关系是在运行之前确定的,即编译期间 方法隐藏是静态绑定

动态绑定:是指调用关系是在运行期间确定的。运行期间,方法重写是动态绑定(运行时改地址)

枚举:.NET中的枚举我们一般有两种用法,一是表示唯一的元素序列,例如一周里的各天;还有就是用来表示多种复合的状态。这个时候一般需要为枚举加上[Flags]特性标记为位域,这样我们就可以用"或"运算符组合多个状态。所有的枚举类型都是枚举类的自雷,比如white 是Color 子类,Color.white.toString()会输出white, tostring的逆方法是valueof .Color c = Enum.valueOf(Size.class, "white")  将c设置成Color.white.

enum Color
{
   white = 1,
   yellow = 2,
   blue =4,
   red = 8
}

Enum.Parse(typeof(Colors), "red");//字符串转枚举。

Colors a = Colors.white | Colors.red; Debug.Log(a.ToString());输出的为white 和red,这就解释了为什么它的值不应该是前几项值的复合值。有一个比较简单的方法就是用2的n次方来依次为每一项赋值,例如 1,2,4,8,16,32,64.....

检查枚举是否包含某个元素: bool hasFlag = ((a & Color.white) != 0);

去掉一个元素:a = a & (~Colors.white) 可以方便去掉一个元素。还有一种方式,运用“^”(异或运算符),但后者要注意一个地方:a = 0, b = 1 a 和b 两个值调换 a = a ^ b  b = a ^b, a = a ^b。 所以上面的枚举用异或的话不能多次判断。

C#默认访问修饰符:直接声明在命名空间下的类和结构体,默认是internal的(同程序集可访问)private和protected不能修饰类。类和结构体的成员,包括内嵌的类和结构体,默认是private的,接口默认是internal的,委托和类以及结构体类似。直接声明在命名空间下的访问权限默认是internal的。如果内嵌的话,就是private的。总结,最高等级的类型(没有内嵌在其他类型中),只能有internal和public的访问权限。这些类型默认的访问权限是internal,

然而对于内嵌的类型来说

enum public       //enum的成员是public的访问权限
class private      //类的成员,默认是private的访问权限
interface public  //接口的成员,默认是public的访问权限
struct private    //结构体的成员,和class类似,默认的访问权限也是private的

-------------------------------------------------------

java%E8%AF%AD%E6%B3%95"># java语法

 java中创建对象的几种方式:

java">package classtest;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
public class ClassTest {
    public static void main(String [] args) throws Exception {
        //1.new 关键字
        ClassTest obj = new ClassTest();//1分配内存2.初始化3.给引用赋值(未禁止指令重排)
        System.out.println(obj.getClass().getModifiers());
        //不写修饰符的时候就不是public,其它flag也不满足,于是就是0
        //2.使用Class类的newInstance方法 从jdk9已启用 原因:绕过了编译时异常检查。由Constructor代替
        //只能够调用无参的构造函数,即默认的构造函数,要求被调用的构造函数是可见的,也即必须是public类型的
        ClassTest obj2 = (ClassTest) Class.forName("classtest.ClassTest").newInstance();
        System.out.println(obj2);//ClassTest
        ClassTest obj3 = null;
        obj3 = ClassTest.class.cast(obj3);
//       public T cast(Object obj) {
//            if (obj != null && !isInstance(obj))
//                throw new ClassCastException(cannotCastMsg(obj));
//            return (T) obj;
//        }
        System.out.println(obj3);//null
        //3.使用Constructor类的newInstance方法 ClassTest必须时public的
        Constructor<ClassTest> constructor = ClassTest.class.getConstructor();
        ClassTest obj4 = constructor.newInstance();
        System.out.println(obj4);//ClassTest
        Constructor<Ha> constructor2 = Ha.class.getConstructor(int.class, String.class);
        //实际上是一个可变参数,也可以用getConstructors() 返回一个数组
//      public Constructor<T> getConstructor(Class<?>... parameterTypes)
//        throws NoSuchMethodException, SecurityException
//        {
//            SecurityManager sm = System.getSecurityManager();
//            if (sm != null) {
//                checkMemberAccess(sm, Member.PUBLIC, Reflection.getCallerClass(), true);
//            }
//            return getReflectionFactory().copyConstructor(
//                    getConstructor0(parameterTypes, Member.PUBLIC));
//        }
        Ha obj5 = constructor2.newInstance(1, "a");
        System.out.println(obj5.a);//1
        Constructor constructor3 = Wo.class.getDeclaredConstructor();
        constructor3.setAccessible(true);
        //将此对象的accessible标志字段override设置为指示的布尔值。 true的值表示反射对象应该在使用时抑制Java语言访问检查
        //如果是Class的构造函数会抛出异常
        Wo obj6 = (Wo) constructor3.newInstance();
        System.out.println(obj6);//Wo
        fun(1,2);// 1 2
        fun();//可变参数可以没有参数
        fun(new int[]{3, 4});// 3 4 可变参数实际上相当于数组
        //4.使用clone方法,无论何时我们调用一个对象的clone方法,jvm就会创建一个新的对象,不需要public
        // 将前面对象的内容全部拷贝进去。用clone方法创建对象并不会调用任何构造函数。
        //需要先实现Cloneable接口并实现其定义的clone方法。
    
        Cl cl = new Cl(1);
        Cl obj7 = (Cl) cl.clone();
        //clone 方法 return this 时;
        System.out.println(obj7);//classtest.Cl 未调用构造函数
        System.out.println(obj7 == cl);//true
        System.out.println(obj7.a + "--" + cl.a);//1-1
        //clone 方法 return new Cl(2) 时; 上面依次为 两次调用构造函数 false 2--1
        //5.使用反序列化,当我们序列化和反序列化一个对象,jvm会给我们创建一个单独的对象。
        // 在反序列化时,jvm创建对象并不会调用任何构造函数。不需要public
        //为了反序列化一个对象,我们需要让我们的类实现Serializable接口
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Cl.obj"));
        out.writeObject(cl);
        out.close();
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("Cl.obj"));
        Cl obj8 = (Cl) in.readObject();//未调用构造函数
        in.close();
        System.out.println(obj8);//classtest.Cl
        System.out.println(obj8 == cl);//false
        System.out.println(obj8.a);//1
    }

    static void fun(int... a)
    {
        for(int e : a)
            System.out.println(e);
    }

    protected void fun2()
    {
        System.out.println("haha");
    }
}
class Cl implements Cloneable, Serializable
{
    int a;
    public Cl(int a) {
        this.a = a;
        System.out.println("调用构造函数");
    }
    public Object clone()
    {
        //return this;
        return new Cl(2);
    }
}
class Wo
{
    private Wo()
    {

    }
}
class Ha
{
    int a ;
    String b;
    public Ha(int a, String b)
    {
        this.a = a;
        this.b = b;
    }
}

java语法:接口用interface关键字, 接口名以I为开头,extends 关键字用来指定继承哪个父接口。接口里可以有字段 ,但是必须初始化  接口里的字段必须为公开的、静态的、终态的(final关键字修饰也就是说是终态的)即使定义时未加final和static,但是方法可以是私有的(Java9新特性之一)。public 等权限修饰符可以有也可以没有,没有则默认为默认权限,default。接口只能继承接口,类只能继承类。implements 关键字用来指定该类实现了那些接口,用逗号分割多个。接口后面不能用关键字implements。一个类只能继承一个类,但是可以实现多个接口(注意这里类只能继承类不能继承接口),接口可以多继承接口并且不能继承类。这里继承时有个“@Override”,它是伪代码,表示重写(当然不写也可以),不过写上有如下好处:  1、可以当注释用,方便阅读; 2、编译器可以给你验证@Override下面的方法名是否是你父类中所有的,如果没有则报错。而如果你不加@Override,则编译器将不会检测出错误,而是会认为你为子类定义了一个新方法。类不能实现类。是能实现接口。接口继承多个和接口时用“,”隔开。类实现多个接口时也用“,”隔开.继承和实现同时需要时,先写继承 再写实现 用空格 分开即可。接口中的变量自动是public static final 的

继承:类继承抽象类时,声明父类  new 子类 调用子类方法 , 声明子类  new 子类 调用子类方法, 抽象类不能实例化。子类不能继承父类的构造方法,但是可以用super来访问父类构造方法。java里的super关键字代替C#里的base关键字。java里的类只能继承一个类,单继承,但是可以多层继承来实现继承体系,但是可以多实现。this.(...)调用本类的构造方法,super(..)调用父类的构造方法。子类中的所有构造方法都会默认先访问父类中的无参构造方法,这是因为子类继承和使用父类的数据,所以子类初始化前一定要完成父类数据的初始化,如果父类没有无参构造,子类需要显示的调用父类的有参构造方法,并且调用要放在子类构造方法里的第一句,这样就能初始化父类数据否则报错,这也就意味着只能调用一个父类的构造函数。执行顺序:父类静态代码块>子类静态代码块>父类代码块>父类构造方法>子类代码块>子类构造方法。一个类的初始化过程:成员变量的初始化(静态的在最前),默认初始化,显示初始化,构造方法初始化。如果方法名一样参数不一样,根据参数的不同调用子类或者父类的方法。子类重写父类方法时,访问权限不能更低。子类重写父类方法时声明要一致,都为静态或者都为非静态。

成员变量和局部变量:成员变量可以不赋值,有默认值, 但是局部变量使用前必须赋值,没有默认值。采用就近原则。成员变量在堆中,而局部变量在栈中,因为方法执行在栈针上。 方法在内存中有一块区域 在new出对象的堆中有对应的地址索引。

关键字:public,default(什么也不加),private,protected。public可以挎包访问,defaul是在同一个包可以随意访问,private是在本类使用,即使是继承的子类也不行,protected 是和default一样的功能(同一包路径下,可以被使用),两个限只能修饰成员变量和成员函数。但protected允许跨包继承,(default不行,只能在本包下被使用)不同的包的继承关系,子类只能调用自己类内重写的protect方法 //不能直接调用父类的,但是可以通过super调用,同包下可以随意使用。java中默认的修饰符就是default。继承关键字:extends 接口 interface 实现关键字:implements。private和protected不能修饰类。

类:前面只能用默认和public权限修饰符,并且外部类不能用static状态修饰符修饰。成员变量:不能用abstract 修饰。构造方法:不能用static,final,abstract修饰符。

static关键字:外部类不能是静态的,因为外部类已经是隐式静态的,而且也有字节码文件。 被所有对象共享,随着类的加载而加载,所以优先于对象存在,所以静态的方法里不能引用非静态的对象和方法,但是可以定义非静态的对象,只能访问静态成员变量和静态成员方法,static方法里不能声明静态变量,因为静态的是整个类的对象共有,那么做没有意义,也可被对象调用,也可以用类名调用,static修饰变量和方法在方法区里的静态区,方法区里还有class内容区域。JVM调用的main方法的参数,String 【 】args ,早期是为了接收键盘录入的数据的,格式是 java hello world。后来用Scaner代替了,静态方法不能重写只能继承,调用谁的方法看是谁的引用。声明父类 new子类的对象(内存中存的是子类),但是实际上用对象调用的是父类的静态方法。

final关键字:修饰类,方法变量。final修饰的类无法被继承,只有私有构造函数的类也无法被继承。final修饰的方法不能被子类重写。修饰变量,只能赋值一次,且不能再做修改,对于非静态的变量,要在构造函数完毕前赋值,但是可以在子类重写。当final修饰class等引用类型时,它的引用不能修改,但是里面的变量可以修改。对于静态的final变量,可以在static代码块中赋值或者直接赋值,不能在构造函数和非静态代码块里赋值。总结一句话:由于成员方法存在方法重写,所以它的编译看左边,运行看右边。对象调用静态方法编译和运行看左边。

JAVA中用final来修饰方法参数的原因是防止方法参数在调用时被篡改,其实也就是这个原因,但理解起来可能会有歧义,我们需要注意的是,在final修饰的方法参数中,如果修饰的是基本类型,那么在这个方法的内部,基本类型的值是不能够改变的,但是如果修饰的是引用类型的变量,那么就需要注意了,引用类型变量所指的引用是不能够改变的,但是引用类型变量的值是可以改变的。

总得来说对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象

java中的成员访问特点:继承关系,声明父类,new子类,并且变量或者方法要有重写。在用该对象直接调用的变量或者方法的时候,变量:编译看左边,运行看左边,方法:编译看左边,运行看右边。但是要是在自己类里调用要遵循就近原则。而静态方法,编译看左边,运行看左边。静态与类有关,算不上重写。如果想要调用子类特有的方法,可以把父类强转成子类即可或者声明子类new子类即可。当子类中没有父类的方法时,声明父类new子类。调用子类没有的方法时去父类里找,如果父类里调用的是父类中的protected及以上级别的方法,那么如果子类有此方法就调用子类的,子类没有或者父类的此方法访问级别时private就调用父类的。

匿名类:调用方法,仅仅调用一次的时候。没有名字的对象,调用方法格式:new 类名().方法。匿名对象调用完毕就是垃圾,可以被垃圾回收器回收,并可以作为实际参数传递。

多态:方法的重载Overloading和重写Overriding, 这里有一个实现泛型comparable<T>接口的案例可以看一下。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。Java语言引入泛型的好处是安全简单,将类型在调用时候指定,不同版本的泛型类实例是不兼容的,所以出现了泛型通配符“?”。泛型的类型参数只能是类类型,不能是简单类型,如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法,如 public static <T> void show(T t){} , 标记了<T>的即为泛型方法。

1,<? extends Parent> 指定了泛型类型的上届 :public void showKeyValue1(Generic<? extends Number> obj){}

2.如果是泛型方法 public static <T extends Comparable<T>> T maximum(T x, T y, T z){}则需要在泛型声明的时候添加<T>。

3,<? super Child> 指定了泛型类型的下届

4, <?> 指定了没有限制的泛型类型例子:

Class Demo <泛型符号> {

} //这里的泛型符号可以用任意符号,常用的符号有E(元素element简写),T(类 型type的简写),K(键key的简写),V(值value的简写)

5.限定类型使用extends关键字,如果同时使用类和接口限定,类放前面,接口放后面 并用 & 号分割。

限定类型使用extends关键字指定
     * 可以使类,接口,类放在前面接口放在后面用&符号分割

在Java中,同一个类中的多个方法可以有相同的方法名称,但是有不同的参数列表,这就称为方法重载(method overloading)。参数列表又叫参数签名,包括参数的类型、参数的个数、参数的顺序,只要有一个不同就叫做参数列表不同。

抽象类:用abstract关键字修饰,抽象方法不能用private或static关键字修饰。抽象类的已经实现的方法和字段可以为私有的,也可以有常量和变量,也可以没有抽象方法。有抽象方法的类必须是抽象类,抽象类有构造方法但是不能实例化,构造函数的作用是为了让子类访问父类数据的初始化。抽象类的子类如果不想重写抽象方法,该类是一个抽象类,如果子类是一个具体的实类,那必须实现所有得抽象方法。抽象类可以new子类实例。abstract不能和private,final,static 关键字共存。抽象类可以用类名直接调用静态方法。

接口:,可以多继承,多实现,方法只能抽象,只能有常量并且是静态的(接口中的变量默认为final常量合static修饰符),复制后不可修改,不能有变量。接口子类可以使抽象类,可以使具体类,具体类必须实现全部的抽象方法。接口没有构造方法,这时实现类的构造方法默认调用的是万类之祖object的无参构造函数。接口默认方法为public并且是抽象的。可以同时单继承和多实现接口,继承在先用extends,实现在后 用implements 多实现用“,”隔开。用哪个接口声明的调用就可以调用哪个接口的方法。

内部类:在一个类中再定义一个类,后者称为内部类,内部类外部类没有继承关系,分为,成员内部类、局部内部类、匿名内部类。成员内部类:内部类中可以随意使用外部类的成员方法以及成员变量(private的也可以),这里内部类一定要绑在外部类上,也就是说在外部类初始化一个内部类对象,那么内部类就会绑定在外部类上,外部类只能使用内部类的非私有的成员并且需要内部类的对象调用。若果想再外部类外面创建内部类的对象,需要先创建外部类对象 再创建 格式如下 外部类 对象 = new 外部类(), 外部类。内部类 对象名= 对象.new 内部类()或者 外部类。内部类 对象名 = new 外部类()。new 内部类(),这也说明了内部类对象依赖外部类对象。如果有内部类和外部类的成名名称相同的情况,那么内部类中的为默认为内部类的成员,用this关键字点修饰的也是内部类成员,但是用外部类。this。修饰的为外部成员,private修饰的内部类不能被外部访问,static修饰的内部类只能访问外部的静态成员,静态内部类的方法可以是静态的,也可以是非静态的。创建对象格式:外部类名。内部类名 对象名 = new 外部类。内部类名(),调用静态方法可以写成:外部类名。内部类名。方法()非静态方法一样需要对象调用。局部内部类: 就是在类的方法中定义的内部类,它的作用范围是在这个方法内,在方法的外部不能访问该内部类,但是内部类可以访问外部类的所有成员。可以在局部位置创建局部内部类对象来调用它的成员,从局部内部类访问局部变量,此变量必须为final类型,因为局部变量会随着方法调用完毕而消失(清除栈针),这个时候,内部类仍然存在,并没有立马从堆内存中消失。等待垃圾回收器回收,为了继续让数据被使用,用final修饰,这样在堆内存里存储的是一个常量值。匿名内部类:由于匿名内部类没有名称,所以匿名内部类用默认的构造方法来生成匿名内部类的对象,并会生成外部类$序号未名称的.class文件。匿名内部类的使用条件是必须继承一个类或者实现一个接口,实质就是省略了定义子类并创建子类对象再调用父类成员或重写的过程,返回一个父类声明new子类的对象。简化了代码。格式:接口 对象 = new 接口(){...};这里接口和抽象类正常是不能new对象的,匿名内部类用之后就等待被回收,因为栈中没有指向它的引用。静态内部类:在内部类前加static就为内部静态类。静态内部类中可以声明static成员和非静态成员,但非静态内部类中不可以声明静态成员,除非它是final static 的,而静态内部类不能使用外部类的非静态成员。创建静态内部类的对象不需要外部类的对象, 格式 外部类.内部类 对象 = new外部类.内部类();。Java文档中是这样描述static内部类的:一旦内部类使用static修饰,那么此时这个内部类就升级为顶级类。static内部类不仅可以在内部定义static元素,而且在构建对象的时候也可以一次完成。从某种意义上说,static内部类已经不算是严格意义上的内部类了。与static内部类不同,内部接口自动具备静态属性,也就是说,普通类是可以直接实现内部接口的,看一个例子:class D implements sssss.InputVerifyCallback。非静态内部类的继承:内部类可以和其他普通类一样被继承,1.成员内部类的子类可以与其位于同一个外部类,2.也可以是与其位于不同外部类的内部类,3.也可以是另一个外部类。 后两种情况,必须在子类的构造方法中显式添加 父类的外部类的对象.super(参数(如果需要参数的话)); 这样一条语句,以保证为父类传入其外部类的对象引用,继而保证能完成父类的构造方法, 跟new 非静态内部类的情况一样,需要外部类对象的引用。在其他外部类继承静态内部类时,可以不用显示的调用父类的外部类对象.super(),因为上面说过static内部类已经不算是严格意义上的内部类了,所以不需要外部类的对象来初始化静态内部类。另外如果一个内部类继承了另一个外部类的内部类,而子类本身的外部类是继承类的外部类的子类,那么也不用外部类对象.super()。

显示调用具体原因如下:编译器编译时,是把外部类与成员内部类编译成两个独立的文件,但会给成员内部类默认添加一个类型为外部类对象引用的成员变量,并为构造方法默认传入一个类型为外部类对象引用的参数,并以该参数值来初始化该成员变量;也就是说,成员内部类的对象保存了一个外部类对象的引用,通过这个引用,内部类就可以无限制的访问外部类的成员了。从这里也可以看出,创建成员内部类对象必须要通过外部类对象,即,成员内部类是依附于外部类的。

所以继承在不同的外部类的一个内部类时要显示的调用父类的外部对象.构造函数;下图du为InnerClasssChildDM 的外部类对象。this$0 =du 和 final DustMan this$0为jvm实际内部操作,不用管。其中per也可为外部类DustMan的成员变量。

 

2.静态:虽然可以用对象也可以点出静态成员, 但是建议使用类名点出静态成员,否则容易造成混淆。静态方法中不可以使用关键字this,也不可以直接调用非静态方法。在方法体内的局部变量声明为静态的也是错误的,因为静态变量是类变量,存放在方法区,而方法内的是局部变量,与定义相违背,而且栈帧执行后也会被销毁。protected修饰的类或成员同包中的类可见,不同包的不可见。当不设置访问权限时默认为default类型,也就是说只有同包的可以使用。这个与C#是有区别的,C#默认private 外部不可访问。C#的protect是只有内部或有继承的子类可以调用。 internal修饰符表示同项目。this关键字的使用其实可以省略,但是this可以作为对象作为返回值。

3. == 和equals两个比较方法是不一样的。, “”==“”运算符比较的是两个对象的引用地址是否相等,而后者比较的是两个对象引用所值的内容是否相等。所以 string a = new string (abc)  和string b = new string (abc)  a == b 结果为false a.equals(b) 为 true,类的对象调用equals代表地址是否想同。

3.1 判断对象是否相等时:对象 instanceof 类名:表示对象obj是否是class类或其子类的对象。类名.class.isInstance(对象)即对象obj能否转化为类class的对象,动态等价于instanceof。

4.对象引用超过其作用范围,这个对象被视作垃圾。将对象赋值为null。java 垃圾回收器只能回收用new操作符创建的对象,如果某些对象不是通过new操作符在内存中创建的。将不被识别。finalize()这个方法在 Object类中protected修饰的,当我们在类中定义了该方法,在垃圾回收时首先调用该方法,并且在下一次垃圾回收动作发生时,才真正的回收对象占用的内存。 但垃圾回收和此方法不保证一定会发生,如Java虚拟机面临内存损耗待尽的情形,是不会执行垃圾回收的。。finalize()是Object中的方法,当垃圾回收器将要回收对象所占内存之前被调用,即当一个对象被虚拟机宣告死亡时会先调用它finalize()方法,让此对象处理它生前的最后事情(这个对象可以趁这个时机挣脱死亡的命运),Java采用可达性分析算法来判定一个对象是否死期已到,inalize()只会在对象内存回收前被调用一次,inalize()的调用具有不确定行,只保证方法会调用,但不保证方法里的任务会被执行完(比如一个对象手脚不够利索,磨磨叽叽,还在自救的过程中,被杀死回收了)。虽然以上以对象救赎举例,但finalize()的作用往往被认为是用来做最后的资源回收。基于在自我救赎中的表现来看,此方法有很大的不确定性(不保证方法中的任务执行完)而且运行代价较高。所以用来回收资源也不会有什么好的表现。

System类:System。gc()方法为强制启动垃圾回收器,启用前会掉类中的finalize()方法,可以实现线从子类再到父类的回收。exit()方法,终止当前正在运行的java虚拟机,参数用作状态码,根据惯例,0状态表示正常终结,其他表示异常终结。currentTimeMillis(),返回与1970年1月1日午夜的时间差,单位是毫秒。arraycopy方法:从指定原数组中复制一个数组,从源数组指定位置开始复制,复制到目标数组的对应开始位置并复制规定的有效数字长度。

。cloneable 为标记类,实现此接口的类可以调用方法clone克隆一个对象。目的是为了区别于 赋值“=”, 后者地址指向同一个引用,改变一个另一个也会改变,前一个不会。

这里有很多设计模式:这个还需要自己去学习。

单例设计模式:保证此类的唯一性,类中有私有的构造方法,并提供一个静态的方法用于获得该类的实例。

制作java后缀为。class的注释文档。用Dos的javadoc命令 ,错误提示:如果提示不是内部或者外部命令(Path环境变量配置)。未指定程序包或者类,是因为lei'类的权限不够。类里的注释格式:/**  每行前用*  */,后面的格式可以是 -d 目录 -author -wersion ArrayTool.java       生成后的index.html文件就是,需要用浏览器打开,html的可以用软件转化成后缀为CHM的帮助文档。java.lang包不需要导入,其他需要导入并且注意版本是否在本地使用JDK版本之前。

代码块:在Java中使用{}括起来的代码被称为代码块,根据其位置和声明b不同可以分为局部代码块:用于限定局部变量的周期,提早释放变量,增加内存.。构造代码块:在类中的成员位置,比构造函数先执行,且每次new都会执行,可以用于把不同构造函数的相同代码重构在代码块中,对对象进行操作。静态代码块;在类的成员位置,用static修饰的{},在构造代码块前执行,并且只执行一次,是对类进行操作。静态代码块在同一个类的main方法之前执行。同步代码块:用在多线程。

枚举:第一行必须是枚举项,最后一个枚举项后的分号可以省略,但如果有其他代码就不能省略, 枚举元素之间不能相隔。枚举类可以有构造器,但是必须非公开的,因为公开的构造器允许创建对象,但这与设计初衷相违背。如果有抽象方法,枚举项后要实现该方法,类似第三种。枚举对象可以调用方法:compareTo方法两个枚举比较。name方法:枚举项名称。ordinal方法返回枚举项顺序,就是上面compareTo比较的东西。valueOf方法:返回枚举的字节码文件里的对应名字的枚举选项 如:Enum.valueOf(Type2 .class, "befor")。values()方法:返回该枚举的的各个枚举项组成的数组 如 Type2 .values()。swich的时候如果条件为null会报空指针异常。

注意:

1.Color d = Color.red ,前面必须加前缀,否则会报错,原因:赋值枚举变量的时候,这些变量是指定枚举类中的常量,他们是public static final的,所以必须加上前缀。

2.switch中用枚举的时候case后的枚举不需要加前缀,只能写red,否则报错,原因:官网解释

If the type of the switch statement’s Expression is an enum type, then every case constant associated with the switch statement must be an enum constant of that type.

the enum value in the case switch label must be an unqualified name. It inherits the context from the type of the object in the switch() clause, and that cases can simply use unqualified names.

java规定case后面的枚举常量名只能使用unqualified name,switch后已经指定了枚举的类型,case后无需使用全名,而且enum也不存在继承关系。

第一版:访问级别 enum 枚举类名{枚举项1,枚举项2,枚举项3,...}

第二版:

public enum Type2 {
    befor("前"),// 实质上是 public static final Type2 befor = new Type2("前"){ };
    up("上"),
    down("下"),
    after("后");
    
    private String name;
    
    Type2(String name)
    {
        this.name = name;
    }

第三版、:带有抽象方法

public enum Type2 {
    befor("前"){

        @Override
        public void show() {
            // TODO 自动生成的方法存根
            
        }}, // 实质上是 public static final Type2 befor = new Type2("前"){ 如果是自定义枚举并且有抽象方法在这里实现抽象方法}
    up("上"){

        @Override
        public void show() {
            // TODO 自动生成的方法存根
            
        }},
    down("下"){

        @Override
        public void show() {
            // TODO 自动生成的方法存根
            
        }},
    after("后"){

        @Override
        public void show() {
            // TODO 自动生成的方法存根
            
        }};
    
    private String name;
    
    Type2(String name)
    {
        this.name = name;
    }
    
    public String getName()
    {
        return name;
    }
    
    public abstract void show();
}
    public String getName()
    {
        return name;
    }
}

EnumSet、EnumMap

public enum Color {

    YELLOW,
    RED,
    BLUE,
    PURPLE,
    BLACK;

}
//反编译
	public static final Color YELLOW;
	public static final Color RED;
	public static final Color BLUE;
	public static final Color PURPLE;
	public static final Color BLACK;
	private static final Color $VALUES[];

	public static Color[] values()
	{
		return (Color[])$VALUES.clone();
	}

	public static Color valueOf(String name)
	{
		return (Color)Enum.valueOf(Color, name);
	}

	private Color(String s, int i)
	{
		super(s, i);
	}

	static 
	{
		YELLOW = new Color("YELLOW", 0);
		RED = new Color("RED", 1);
		BLUE = new Color("BLUE", 2);
		PURPLE = new Color("PURPLE", 3);
		BLACK = new Color("BLACK", 4);
		$VALUES = (new Color[] {
			YELLOW, RED, BLUE, PURPLE, BLACK
		});
	}
java">EnumMap<Color,String> enumMap = new EnumMap(Color.class);
enumMap.put(Color.BLACK,"黑色");如果put的是null那么就就给他一个默认的Object
enumMap.put(Color.BLUE,"蓝色");
System.out.println(enumMap);//{BLUE=蓝色, BLACK=黑色}
System.out.println(enumMap.get(Color.BLUE));//蓝色

EnumMap put方法

public V put(K key, V value) {
        typeCheck(key);

        int index = key.ordinal();
        Object oldValue = vals[index];
        vals[index] = maskNull(value);
        if (oldValue == null)
            size++;
        return unmaskNull(oldValue);
    }

    private static final Object NULL = new Object() {
        public int hashCode() {
            return 0;
        }

        public String toString() {
            return "java.util.EnumMap.NULL";
        }
    };

    private Object maskNull(Object value) {
        return (value == null ? NULL : value);
    }

    @SuppressWarnings("unchecked")
    private V unmaskNull(Object value) {
        return (V)(value == NULL ? null : value);
    }

首先调用typeCheck检查键的类型,如果类型不对,会抛出异常。类型正确的话,调用ordinal获取索引index,并将值value放入值数组vals[index]中。EnumMap允许值为null,为了区别null值与没有值,EnumMap将null值包装成了一个特殊的对象,有两个辅助方法用于null的打包和解包,打包方法为maskNull,解包方法为unmaskNull。

get方法

public V get(Object key) {
        return (isValidKey(key) ?
                unmaskNull(vals[((Enum<?>)key).ordinal()]) : null);
    }

键有效的话,通过ordinal方法取索引,然后直接在值数组vals里找。isValidKey的代码与typeCheck类似,但是返回boolean值而不是抛出异常。

以上就是EnumMap的基本实现原理,内部有两个数组,长度相同,一个表示所有的键,一个表示对应的值,值为null表示没有该键值对,键都有一个对应的索引,根据索引可直接访问和操作其键和值,效率很高。

EnumSet

EnumSet这是一个用来操作Enum的集合,是一个抽象类,它有两个继承类:JumboEnumSet和RegularEnumSet。在使用的时候,需要确定枚举类型。它的特点也是速度非常快,为什么速度很快呢?因为每次add的时候,每个枚举值只占一个长整型的一位。
EnumSet.noneOf()方法创建一个空的set,EnumSet是一个抽象类,它提供了两个具体的实现,即 java.util.RegularEnumSet和java.util.JumboEnumSet。 两者之间的主要区别在于,前者使用long变量来存储元素,而后者使用long []来存储其元素。 由于RegularEnumSet使用long变量(它是64位数据类型),因此只能容纳这么多的元素。 这就是为什么使用EnumSet.noneOf()方法创建一个空EnumSet的原因。 如果元素数量小于或等于64会选择RegularEnumSet。如果元素超过64个选择JumboEnumSet.下面是其确实的代码:

java">    public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
        Enum<?>[] universe = getUniverse(elementType);
        if (universe == null)
            throw new ClassCastException(elementType + " not an enum");

        if (universe.length <= 64)
            return new RegularEnumSet<>(elementType, universe);
        else
            return new JumboEnumSet<>(elementType, universe);
    }
public class EnumSetTest {

    public static void main(String[] args) {
        EnumSet<Color> enumSet = EnumSet.noneOf(Color.class);
        System.out.println(enumSet);//[]
        enumSet.add(Color.BLUE);
        enumSet.add(Color.PURPLE);
        System.out.println(enumSet);//[BLUE, PURPLE]
    }
}

EnumSet.allOf()方法创建一个满的set

EnumSet<Color> enumSet = EnumSet.allOf(Color.class);
System.out.println(enumSet);//[YELLOW, RED, BLUE, PURPLE, BLACK]

EnumSet.range创建指定范围set

EnumSet<Color> enumSet = EnumSet.range(Color.YELLOW,Color.BLUE);
System.out.println(enumSet);//[YELLOW, RED, BLUE]

EnumSet.complementOf补集创建set

EnumSet<Color> enumSet1 = EnumSet.complementOf(enumSet);
System.out.println(enumSet1);//[PURPLE, BLACK]

EnumSet.copyOf复制创建set

EnumSet<Color> enumSet1 = EnumSet.copyOf(enumSet);
System.out.println(enumSet1);//[YELLOW, RED, BLUE]
List<Color> colors = new ArrayList<>();
colors.add(Color.PURPLE);
colors.add(Color.BLUE);
colors.add(Color.BLUE);
System.out.println(colors);//[PURPLE, BLUE, BLUE]
EnumSet<Color> enumSet = EnumSet.copyOf(colors);
System.out.println(enumSet);//[BLUE, PURPLE]

注意:通过结果可以看出ArrayList内放置的元素可以重复,而EnumSet内放置的元素不重复。

Enum与enum的关系

枚举里的元素能调用Enum里的name和ordinal等方法,方法调转会进入到public abstract class Enum<E extends Enum<E>>
 implements Comparable<E>, Serializable  抽象类中。并且,我们发现编译器不允许我们自行实现该抽象类从而构造一个新的类。但是,既然我们的Color枚举类可以调用其中的方法,因此Color枚举类应该是继承了该抽象类。为了验证这一猜想,我们让Color类继承一个其他的类,发现果然不可以,因为Java是不允许多继承的,并且在Color也不能重写Enum里的方法。反编译其实下面这样的,也就是说Color实现了Enum 并且每隔元素都是一个static final 的Color对象

关于引用类型强转:

1.隐式强转,就是父类的引用指向子类的对象 例如 Father类和Child类,我们可以这样写: Father f = new Child();此时Child       对象在内存中类型依然是Child

2、显式强转,显式强转会出现2种情况:

①父类的引用指向子类的对象:Father f = new Child(); 此时Child c = (Child)f ; 父类转子类是可行的,可以这样理解:f 的引用本身就是指向child类的对象,所以把 f 强制转换成 Child是可以的。

②父类的引用指向的是父类自己的对象:Father f = new Father();此时Child c  =  (Child)f ;父类转子类是不可行的,运行时会抛出ClassCastException异常,即类型不兼容 因为内存中的类型是Father。

总结:只有我们的父类对象本身就是用子类new出来的时候, 才可以在将来被强制转换为子类对象

注意:引用类型之间的强制转换只能在具有继承关系的两个类型之间进行,但是null可以强制转换给任何类型,转换只能调用静态方法,不能调用变量,常规方法等。FA fa =  (FA)null; fa.funA();//静态方法

instanceof 运算符是用来在运行时指出对象是否是特定类的一个实例, 所以一般强制强转换之前用instanceof 来判断下。如果用Java的伪代码来表现Java语言规范instanceof 所描述的运行时语义,会是这样:

实际上,JVM有一条名为 instanceof 的指令,而Java源码编译到Class文件时会把Java语言中的 instanceof 运算符映射到JVM的 instanceof 指令上。

比如:GF 为祖父类,FA 为父类,S为子类。此时fa调用的fun的方法是S中的fun的方法,如果S中没有fun的方法那就调用FA中的发法,如果FA没有fun那么久调用GF中的fun方法。因为内存中的模型为S,可以代表FA和GF。

此时打印出的为no 因为内存中的模型是FA,不能代表S.

java1.8%E7%9A%84%E9%BB%98%E8%AE%A4%E6%96%B9%E6%B3%95%EF%BC%88default%EF%BC%89%E5%92%8C%E9%9D%99%E6%80%81%E6%96%B9%E6%B3%95%E3%80%82"># java1.8的默认方法(default)和静态方法。

1.Java 8 许在接口中定义静态方法,可以在接口中实现方法,此静态方法不能被子类使用,即使使用父类 对象 = new 子类()的对象也不能调用接口中的静态方法 ,只能通过接口。静态方法调用,而普同类的静态方法的继承关系也是编译和运行都看左边。

2.1.8新增了一个默认方法。可以在接口中实现方法。接口默认方法不能覆写 Object 类的 equalshashCode 和 toString 方法。

3.默认方法需要用default修饰 。default关键字只能用来在接口里定义默认方法,常规类里不能用default关键字,但是类和方法的默认修饰符依然是default的作用域,即同一包下调用。

4.默认方法不能是静态的 。

5.子类重写的时候必须Public,但是子接口重写父接口的default方法必须是default的,子接口也可以不重写父接口中的default方法,这样就直接用父接口的。还可以它重新声明为抽象方法,这样新接口的子类必须再次覆写并实现这个抽象方法。

6.实现类中无须实现默认方法。可以直接用接口中的。如果重写调用子类的。

7.如果实现的两个接口中有同名的default方法冲突了,需要在子类中重写此方法,否则报错。

如果父类与接口中的默认方法冲突了:类的方法声明优先于接口默认方法,无论该方法是具体的还是抽象的。此举是为了兼容1.8以前的jdk。

还有一种特殊情况:子类实现了两个有继承关系的接口,如下:这时不能嗲用In中的foo,提示不能绕过更具体的in2类。因为 In2接口继承了 In 接口,那么In2 接口一定包含了所有 In 接口中的字段方法,因此一个同时实现了 In 接口和 In2 接口的类与一个只实现了 In2 接口的类完全等价。

想要调用In中的foo可以如下实现,注意:In3中的调用In的foo方法不能省略。

为什么要有默认方法

java 8 之前,接口与其实现类之间的 耦合度 太高了(tightly coupled),当需要为一个接口添加方法时,所有的实现类都必须随之修改。默认方法解决了这个问题,它可以为接口添加新的方法,而不会破坏已有的接口的实现.

# 序列化:

serialVersionUID: 序列化的意义就在与 网络传输和对象持久化.

serialVersionUID适用于java序列化机制。简单来说,JAVA序列化的机制是通过判断类的serialVersionUID来验证的版本一致的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID于本地相应实体类的serialVersionUID进行比较。如果相同说明是一致的,可以进行反序列化,否则会出现反序列化版本一致的异常,即是InvalidCastException。

serialVersionUID有两种显示的生成方式:
一是默认的1L,比如:private static final long serialVersionUID = 1L;        

二是根据包名,类名,继承关系,非私有的方法和属性,以及参数,返回值等诸多因子计算得出的,极度复杂生成的一个64位的哈希字段。基本上计算出来的这个值是唯一的。比如:private static final long  serialVersionUID = xxxxL;
注意:显示声明serialVersionUID可以避免对象不一致,如果上述诸多因子不变的话 显示声明的 id 是不变的。当我们把类序列化到本地文件后,如果在反序列化的时候编辑器加载的类与本地已经序列化的类的serialVersionUID不一致就会报java.io.InvalidClassException异常,这是一种持久化的应用,“持久化”意味着对象的“生存时间”并不取决于程序是否正在执行——它存在或“生存”于程序的每一次调用之间。通过序列化一个对象,将其写入磁盘,以后在程序再次调用时重新恢复那个对象,就能圆满实现一种“持久”效果。

当一个类实现类Serializable接口,如果没有显示定义serialVersionUIDEclipse会自动给出相应的提醒;面对这种情况,我们只需要在Eclipse中点击类的warning图标,Eclipse就会自动给出两种生成方式。如果不想定义,在Eclipse的设置中也可以把它关掉的,设置如下:
Window ==> Preferences ==> Java ==> Compiler ==> Error/Warnings ==> Potential programming problems 

将Serializable class without serialVersionUID的warning改成ignore即可

当实现java.io.Serializable接口中没有显示的定义serialVersionUID变量的时候,JAVA序列化机制会根据Class自动生成一个serialVersionUID作序列化版本比较用,这种情况下,如果Class文件(类名,方法明等)没有发生变化(增加空格,换行,增加注释等等),就算再编译多次,serialVersionUID也不会变化的。

如果我们不希望通过编译来强制划分软件版本,即实现序列化接口的实体能够兼容先前版本,就需要显示的定义一个serialVersionUID,类型为long的变量。不修改这个变量值的序列化实体,都可以相互进行序列化和反序列化。

Java语言的关键字,变量修饰符,如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。换句话来说就是,用transient关键字标记的成员变量不参与序列化过程。

缺点:无法垮语言,别的语言相对于java的序列化来说完全是黑盒。序列化后的码流太大,序列化性能太低。

评判一个编解码框架的优劣时有几个因素:

 


http://www.niftyadmin.cn/n/1577767.html

相关文章

ios 资源很全面转载以后方便看

学习过程当中查找到的资料&#xff0c;做一个记录&#xff0c;以便更多朋友查看。开发的过程有时候是简单的&#xff0c;因为我们可以站在巨人的肩上&#xff0c;跳得更高&#xff0c;走得更远。学习过程当中查找到的资料&#xff0c;做一个记录&#xff0c;以便更多朋友查看。…

Java第九篇反射和异常处理

上一篇已经介绍了C#类和java类的区别&#xff0c;由于工作原因经过三天的间断整理才合完&#xff0c;以备后面复习时对比。这里把发射单独做一篇讲解。 首先&#xff1b;当程序要使用某个类时&#xff0c;如果该类还未被加载到内存&#xff0c;则系统通过加载&#xff08;通过…

一个简单的Web音频数据可视化模块

准备跳槽了&#xff0c;意味着我又要做一个什么拿的出手的作品来忽悠面试官了&#xff0c;所以这几天赶了一个音乐播放器出来&#xff0c;写到一半&#xff0c;把里面用到的显示音乐频谱数据的功能分离了出来&#xff0c;在此分享给诸位。 演示地址&#xff1a; http://margox.…

Java第12篇输入输出I/O和ASCII码

Unix使用斜杆/ 作为路径分隔符&#xff0c;而web应用最新使用在Unix系统上面&#xff0c;所以目前所有的网络地址都采用 斜杆/ 作为分隔符。 Windows由于使用 斜杆/ 作为DOS命令提示符的参数标志了&#xff0c;为了不混淆&#xff0c;所以采用 反斜杠\ 作为路径分隔符。所以目前…

Java8新特性-Lambda表达式

1. 什么是Lambda表达式&#xff1f;Lambda表达式就是可以把函数作为参数传递&#xff0c;或者说把代码作为数据传递给函数。2. Lambda表达式的语法格式基本语法格式如下:基本语法下多个变体的说明&#xff1a;1). 多个参数中间用逗号分隔&#xff1a;(int m,int n)>{int re…

Swing程序设计

Swing是 GUI&#xff08;图形用户界面&#xff09;开发工具包&#xff0c;在AWT&#xff08;CLI&#xff09;&#xff08;抽象窗口工具包&#xff09;的基础上使开发开发跨平台的java应用程序界面成为可能&#xff0c;早期的AWT依赖于本地系统&#xff0c;Swing开发的不受平台限…

AsyncTask.cancel()的结束问题

实际项目中有这么一个问题&#xff0c;用户进入详情界面&#xff0c;那么我们就要网络加载数据并展现在UI上&#xff0c;这个加载用线程或者异步。 这里就拿项目中统一用异步任务来获取网络数据把。 用户可能会有这么一个操作&#xff0c;它在一个商品&#xff08;说说等&#…

CREATING A WINDOWS IMAGE FOR OPENSTACK(转)

2019独角兽企业重金招聘Python工程师标准>>> CREATING A WINDOWS IMAGE FOR OPENSTACK If you want to build a Windows image for use in your OpenStack environment, you can follow the example in the official documentation, or you can grab a Windows 2012…