中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當前位置: 首頁 > news >正文

網(wǎng)站的技術解決方案長沙seo網(wǎng)站推廣

網(wǎng)站的技術解決方案,長沙seo網(wǎng)站推廣,廣西網(wǎng)站建設,高密做網(wǎng)站的價位目錄 舊方式與新方式 lambda表達式 方法引用 Runnable 未綁定方法引用 構造器方法引用 函數(shù)式接口 帶有更多參數(shù)的函數(shù)式接口 解決缺乏基本類型函數(shù)式接口的問題 本筆記參考自: 《On Java 中文版》 函數(shù)式編程語言的一個特點就是其處理代碼片段的簡易性&am…

目錄

舊方式與新方式

lambda表達式

方法引用

Runnable

未綁定方法引用

構造器方法引用

函數(shù)式接口

帶有更多參數(shù)的函數(shù)式接口

解決缺乏基本類型函數(shù)式接口的問題


本筆記參考自: 《On Java 中文版》


??????? 函數(shù)式編程語言的一個特點就是其處理代碼片段的簡易性,就像處理數(shù)據(jù)一樣簡單。Java 8加入的lambda表達式方法引用為函數(shù)式風格編程做出了一定的支持。

??????? 在計算機的早期時代,為了讓程序能夠適應有限的內(nèi)存,程序員往往需要在程序執(zhí)行時修改內(nèi)存中的代碼,讓程序做出不同的行為,依此節(jié)省空間。這就是自修改代碼技術。因為彼時的程序大都足夠小,因此維護起來并不會太麻煩。

??????? 但隨著內(nèi)存的增大,自修改代碼被認為是一個糟糕的想法,它極大地增加了程序的維護成本。盡管如此,這種使用代碼以某種方式操縱其他代碼的想法依舊十分吸引人:通過組合已經(jīng)經(jīng)過良好測試的代碼,我們可以生產(chǎn)出更有效率、更加安全的代碼。

??????? 函數(shù)式編程的意義就在于此:通過整合現(xiàn)有代碼來產(chǎn)生新的功能,而不是從零開始編寫所有內(nèi)容,由此可以得到更加可靠、實現(xiàn)起來更快的代碼。

??? 面向?qū)ο缶幊坛橄髷?shù)據(jù),而函數(shù)式編程抽象行為。

??????? 純函數(shù)式語言在安全方面規(guī)定了額外的約束條件,所有的數(shù)據(jù)必須是不可變的:設置一次,永不改變。此時函數(shù)絕對不會修改現(xiàn)有值,而是只生成新值。(純函數(shù)式語言在面對一些問題時能夠提出一個好的解決,但這不代表純函數(shù)式語言就是最好的解決方式)

????????Python等非函數(shù)式編程語言已經(jīng)將函數(shù)式編程的概念納入其中,并且受益匪淺。Java也加入了類似的特性。

舊方式與新方式

??????? 通過將代碼傳遞給方法,我們可以控制方法,使其產(chǎn)生出不同的行為。

??????? 舊的方式是創(chuàng)建一個對象(在下例中是Strategy),讓其的某個方法包含所需行為,在將這個對象傳遞給我們想要控制的方法:

package functional;import java.util.Locale;interface Strategy {String approch(String msg);
}class Soft implements Strategy {@Overridepublic String approch(String msg) {return msg.toLowerCase() + "?";}
}class Unrelated {static String twice(String msg) {return msg + " " + msg;}
}public class Strategize {Strategy strategy;String msg;Strategize(String msg) {strategy = new Soft(); // 將Soft()作為一個默認的決策this.msg = msg;}void communicate() {System.out.println(strategy.approch(msg));}void changeStrategy(Strategy strategy) {this.strategy = strategy;}public static void main(String[] args) {Strategy[] strategies = {new Strategy() { // 創(chuàng)建一個匿名內(nèi)部類來改變行為,雖然依舊會有重復的代碼@Overridepublic String approch(String msg) {return msg.toUpperCase() + "!";}},msg -> msg.substring(0, 5), // 這就是Java 8開始提供的lambda表達式Unrelated::twice // 這也是Java 8中出現(xiàn)的方法引用};Strategize s = new Strategize("Hello there");s.communicate();for (Strategy newStrategy : strategies) {s.changeStrategy(newStrategy); // 遍歷數(shù)組strategies中的每一個決策,并將其放入s中進行決策更換s.communicate(); // 更換決策后,每一次輸出都會產(chǎn)生不同的結果:我們傳遞了行為,而被僅僅是數(shù)據(jù)}}
}

??????? 程序執(zhí)行的結果是:

??????? Strategy提供的接口包含了唯一的approach()方法。通過創(chuàng)建不同的Strategy對象,就可以創(chuàng)建不同的行為。

??????? 在上述程序中,包含了默認的決策Soft()和一個匿名內(nèi)部類。除此之外,還出現(xiàn)兩個了Java 8添加的新內(nèi)容:

  1. lambda表達式:
    msg -> msg.substring(0, 5)

    這種表達式的特點是使用箭頭->分隔參數(shù)和函數(shù)體。

  2. 方法引用:
    Unrelated::twice

    特點是::。其中,::左邊是類名或?qū)ο竺?#xff0c;右邊是方法名,但沒有參數(shù)列表。

??????? 在Java 8之前,使用普通的類或者匿名內(nèi)部類來傳遞功能,但這種語法的并不方便。lambda表達式和方法引用改變了這種情況,使得傳遞功能變得更加便捷。

lambda表達式

||| lambda表達式是使用盡可能少的語法編寫的函數(shù)定義。

??????? 換言之,lambda表達式產(chǎn)生的是函數(shù),而不是方法。當然,Java中的一切都是類,之所以lambda表達式會讓人產(chǎn)生這種“錯覺”,是因為幕后進行了各種各樣的操作。作為程序員,我們可以將lambda表達式視為函數(shù)。

??????? lambda表達式的語法寬松,且易于編寫。例如:

package functional;interface Description {String brief();
}interface Body {String detailed(String head);
}interface Multi {String twoArg(String head, Double d);
}public class LambdaExpressions {static Body bod = h -> h + "No Parens!"; // 本條語句并不需要使用括號(僅限只有一個參數(shù)時)static Body bod2 = (h) -> h + "More details"; // 使用了括號。處于一致性的考慮,在只有一個參數(shù)時也使用括號static Description desc = () -> "Short info"; // 若沒有參數(shù),必須使用括號來指示空的參數(shù)列表static Multi mult = (h, n) -> h + n; // 有多個參數(shù),此時必須將它們放在使用括號包裹的參數(shù)列表中static Description moreLines = () -> {System.out.println("moreLines()");return "from moreLines()";};public static void main(String[] args) {System.out.println(bod.detailed("Oh!"));System.out.println(bod2.detailed("Hi!"));System.out.println(desc.brief());System.out.println(mult.twoArg("Pi: ", 3.14159));System.out.println(moreLines.brief());}
}

??????? 程序執(zhí)行的結果是:

??????? 在上述的3個接口中,每個接口都有一個方法(這是后續(xù)會提到的函數(shù)式接口)。任何lambda表達式的基本語法如下:

??? 注釋中提到過,若沒有參數(shù),就必須使用括號來指示空的參數(shù)列表。

??????? 對一行的lambda表達式而言,方法體中表達式的結果會自動成為lambda表達式的返回值,所以這里使用return關鍵字是不和法的。另外,若lambda表達式需要多行代碼,如上文中的moreLines,就需要將表達式的代碼放入到花括號中。此時又會需要使用return從lambda表達式中生成一個值了。

??? 可以看到,lambda表達式可以通過接口更方便地生成行為不同的對象。

遞歸

??????? 遞歸,即函數(shù)調(diào)用了自身。Java也允許編寫遞歸的lambda表達式,但需要注意一點:這個lambda表達式必須被賦值給一個靜態(tài)變量或一個實例變量。通過兩個示例說明這些情況:

??????? 兩個示例會使用一個相同的接口:

interface IntCall {int call(int arg);
}

【示例:靜態(tài)變量】實現(xiàn)一個階乘函數(shù),遞歸計算小于等于n的正整數(shù)的乘積:

public class RecursiveFactorial {static IntCall fact;public static void main(String[] args) {fact = n -> n == 0 ? 1 : n * fact.call(n - 1);for (int i = 0; i <= 10; i++)System.out.println(fact.call(i));}
}

??????? 程序執(zhí)行的結果是:

??????? 在這個例子中,fact就是一個靜態(tài)變量。遞歸函數(shù)會不斷調(diào)用其自身,因此必須有某種停止條件(在上述例子中是n == 0),否則就會陷入無限遞歸,直到棧空間被耗盡。

??????? 下方這種初始化fact的方式是不被允許的:

static IntCall fact = n -> n == 0 ? 1 : n * fact.call(n - 1);

這種處理對Java編譯器而言還是太過復雜了,會導致編譯錯誤。

---

【示例:示例變量】實現(xiàn)斐波那契數(shù)列:

public class RecursiveFibonacci {IntCall fib;RecursiveFibonacci() {fib = n -> n == 0 ? 0 :n == 1 ? 1 :fib.call(n - 1) + fib.call(n - 2);}int fibonacci(int n) {return fib.call(n);}public static void main(String[] args) {RecursiveFibonacci rf = new RecursiveFibonacci();for (int i = 0; i <= 10; i++)System.out.println(rf.fibonacci(i));}
}

??????? 程序執(zhí)行的結果是:

方法引用

??????? Java 8提供的方法引用,其指向的是方法。方法引用的格式如下:

interface Callable {void call(String s); // 與hello()和show()的簽名了保持一致
}class Describe {void show(String msg) {System.out.println(msg);}
}public class MethodReferences {static void hello(String name) {System.out.println("Hello, " + name);}static class Description { // 定義一個內(nèi)部類String about;Description(String desc) {about = desc;}void help(String msg) {System.out.println(about + " " + msg);}}static class Helper {static void assist(String msg) { // assist()是靜態(tài)內(nèi)部類中的一個靜態(tài)方法System.out.println(msg);}}public static void main(String[] args) {Describe d = new Describe();Callable c = d::show; // 將Describe對象的show方法賦給了Callablec.call("call()"); // 通過call(),調(diào)用了show()c = MethodReferences::hello; // 等號右邊是一個靜態(tài)方法引用c.call("Bob");c = new Description("valuable")::help; // 對某個活躍對象上的方法的方法引用(“綁定方法引用”)c.call("information");c = Helper::assist; // 獲得靜態(tài)內(nèi)部類中的靜態(tài)方法的方法引用c.call("Help!");}
}

??????? 程序執(zhí)行的結果是:

??????? 在上述程序中,Callable.call()、Describe.show()MethodReferences.hello(),這三者的簽名保持了一致。這解釋了為什么語句 Callable c = d::show; 及其之后的語句能夠順利編譯。

Runnable

??????? Runnable是一個java.lang包提供的接口。這個包遵循特殊的單方法接口格式:它的run()方法沒有參數(shù),也沒有返回值。

因此,可以將lambda表達式或方法引用用作Runnable

class Go {static void go() {System.out.println("方法引用Go::go()");}
}public class RunnableMethodReference {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {System.out.println("定義一個run()方法");}}).start();new Thread(() -> System.out.println("這是一個lambda表達式")).start();new Thread(Go::go).start();}
}

??????? 程序執(zhí)行的結果是:

??????? Thread類在官方文檔中的描述如下:

??????? Thread會接受一個Runnable作為其構造器參數(shù),它的start()方法會調(diào)用run()

??? 只有匿名內(nèi)部類需要提供run()方法。


未綁定方法引用

??????? 未綁定方法引用:指的是尚未關聯(lián)到某個對象的普通(非靜態(tài))方法。對于未綁定引用,必須先提供對象,然后才能使用:

class X {String f() {return "X::f()";}
}interface MakeString {String make();
}interface TransformX {String transform(X x);
}public class UnboundMethodReference {public static void main(String[] args) {
//      MakeString ms = X::f; // 無效的方法引用TransformX sp = X::f;X x = new X();// 下列兩條語句的效果是相同的System.out.println(sp.transform(x));System.out.println(x.f());}
}

??????? 程序執(zhí)行的結果是:

??????? 在上述例子之前,示例中對方法的引用,方法與其關聯(lián)接口的簽名是相同的。但這里出現(xiàn)了特例:

MakeString ms = X::f;

編譯器不允許上述語句的編譯,若強制執(zhí)行,會引發(fā)報錯(此為IDEA的報錯信息)

這個報錯指出X::f是一個未綁定方法引用,因為這里涉及到了一個隱藏的參數(shù):this。若把這條有問題的語句換成下列語句,則沒有問題:

X x = new X();
MakeString ms = x::f; // 無效的方法引用

上下兩種語句的區(qū)別就在于,下方的語句提供了一個可供附著的X的對象x,這使得調(diào)動f()變?yōu)榭赡堋?span style="background-color:#f3f3f4;">X::f本身是無法“綁定到”一個對象上的。

??????? 顯而易見,除了自己生成一個對象外,我們還有另一個方式能解決這個問題。關鍵在于,我們還需要一個額外的參數(shù),如TransformX中所示:

String transform(X x);

這種做法告訴我們:函數(shù)式方法(接口中的單一方法)的簽名與方法引用的簽名不必完全匹配。

??????? 最后在看看這條語句:

System.out.println(sp.transform(x));

在前述知識的基礎上,可以推斷這條語句執(zhí)行的過程:println()接受了一個未綁定引用,x作為參數(shù)在這個引用中調(diào)用了transform(),最終調(diào)用了x.f()。

??????? 若一個方法具有多個參數(shù),則只需要讓第一個參數(shù)使用這種this的模式即可:

class This {void two(int i, double d) {}void three(int i, double d, String s) {}void four(int i, double d, String s, char c) {}
}interface TwoArgs {void call2(This athis, int i, double d);
}interface ThreeArgs {void call3(This athis, int i, double d, String s);
}interface FourArgs {void call4(This athis, int i, double d, String s, char c);
}public class MultiUnbound {public static void main(String[] args) {TwoArgs twoargs = This::two;ThreeArgs threeargs = This::three;FourArgs fourargs = This::four;This athis = new This();twoargs.call2(athis, 11, 2.14);threeargs.call3(athis, 11, 3.14, "Three");fourargs.call4(athis, 11, 3.14, "Four", 'Z');}
}

構造器方法引用

??????? 同樣的,也可以對構造器的引用進行捕獲,此后通過這個引用來調(diào)用構造器:

class Dog {String name;int age = -1;Dog() {name = "流浪狗";}Dog(String nm) {name = nm;}Dog(String nm, int yrs) {name = nm;age = yrs;}
}interface MakeNoArgs {Dog make();
}interface Make1Arg {Dog make(String nm);
}interface Make2Args {Dog make(String nm, int age);
}public class CtorReference {public static void main(String[] args) {// 所有這3個構造器都只有一個名字 ::newMakeNoArgs mna = Dog::new;Make1Arg m1a = Dog::new;Make2Args m2a = Dog::new;Dog dn = mna.make();Dog d1 = m1a.make("卡卡");Dog d2 = m2a.make("拉爾夫", 4);}
}

??????? 注意語句Dog::new。3條相同的語句告訴我們,這些構造器都有(且只有)一個名字 —— ::new。并且每一個引用都被賦予了不同的接口,編譯器可以從接口來推斷所需使用的構造器。

??????? 在這里,調(diào)用函數(shù)式接口方法(make())意味著調(diào)用構造器。

函數(shù)式接口

??????? 方法引用和lambda表達式都需要先賦值,然后才能進行使用。而這些賦值都需要類型信息,讓編譯器確保類型的正確性。尤其是lambda表達式。例如:

x -> x.toString()

??????? toString()方法會返回String,但上述語句并沒有表示x的類型。這時候就需要進行類型推斷了。因此,編譯器必須要能夠通過某種方式推斷出x的類型。

??????? 還有其他例子:

(x, y) -> x + y // 需要考慮String類型存在與否
System.out::println

為了解決這種類型推斷的問題,Java 8引入了包含一組接口的java.util.function,這些接口是lambda表達式和方法引用的目標類型。其中的每個接口都只包含了一個抽象方法(非抽象方法可以有多個),被稱為函數(shù)式方法。

??????? 使用了這種”函數(shù)式方法“模式的接口,可以通過@FunctionalInterface注解來強制執(zhí)行:

@FunctionalInterface
interface Functional { // 使用了注解String goodbye(String arg);
}interface FunctionNoAnn { // 沒有使用注解String goodbye(String arg);
}//@FunctionalInterface
//interface NoFunctional{ // 內(nèi)置了兩個方法,不符合函數(shù)式方法定義
//    String goodbye(String arg);
//    String hello(String arg);
//}public class FunctionalAnnotation {public String goodbye(String arg) {return "Goodbye, " + arg;}public static void main(String[] args) {FunctionalAnnotation fa = new FunctionalAnnotation();Functional f = fa::goodbye;FunctionNoAnn fna = fa::goodbye;
//        Functional fac = fa; // 類型不兼容Functional f1 = arg -> "Goodbye, " + arg;FunctionNoAnn fnal = arg -> "Goodbye, " + arg;}
}

??????? @FunctionalInterface注解是可選的。當只有一個方法時,Java把main()中的FunctionalFunctionalNoAnn都視為了函數(shù)式接口。

??????? 現(xiàn)在看向兩條賦值語句:

Functional f = fa::goodbye;
FunctionNoAnn fna = fa::goodbye;

這兩條賦值語句均把一個方法(這個方法甚至不是接口方法的實現(xiàn))賦值給了一個接口引用。這是Java 8增加的功能:若把一個方法引用或lambda表達式賦值給某個函數(shù)式接口(且類型匹配),匿名Java會調(diào)整這次賦值,使其能夠匹配目標接口。

??? 在底層的實現(xiàn)中,Java編譯器會創(chuàng)建一個實現(xiàn)了目標接口的類的示例,并將我們進行賦值的方法引用或lambda表達式包裹在其中。

??????? 使用了@FunctionalInterface注解的接口也叫做單一抽象方法。

命名規(guī)則

??????? java.util.function旨在創(chuàng)建一套足夠完備的接口。一般來說,可以通過接口的名字了解接口的作用。以下是基本的命名規(guī)則(也可以去官方文檔進行查看):

  • 只處理對象(而不是基本類型):名字較為直接,如Function、ConsumerPredicate等。
  • 接受一個基本類型的參數(shù):使用名字的第一部分表示,如LongConsumer、DoubleFunctionInPredicate(例外:基本的Supplier類型)
  • 返回的是基本類型的結果:To表示,例如ToLongFunction<T>IntToLongFunction。
  • 返回類型和參數(shù)類型相同:被命名為Operator。UnaryOperator表示一個參數(shù),BinaryOperator表示兩個參數(shù)。
  • 接受一個參數(shù)并返回boolean被命名為Predicate
  • 接受兩個不同類型的參數(shù):名字中會有一個Bi(比如BiPredicate)。

??? 因為基本類型的存在,Java在設計這些接口時不得不考慮眾多的類型,這無疑增加了Java的復雜性。

??????? 例如:

import java.util.function.*;class Foo {
}class Bar {Foo f;Bar(Foo f) {this.f = f;}
}class IBaz {int i;IBaz(int i) {this.i = i;}
}class LBaz {long l;LBaz(long l) {this.l = l;}
}class DBaz {double d;DBaz(double d) {this.d = d;}
}public class FunctionVariants {static Function<Foo, Bar> f1 = f -> new Bar(f);static IntFunction<IBaz> f2 = i -> new IBaz(i);static LongFunction<LBaz> f3 = l -> new LBaz(l);static ToLongFunction<LBaz> f4 = lb -> lb.l;static DoubleToIntFunction f5 = d -> (int) d;public static void main(String[] args) {Bar b = f1.apply(new Foo());IBaz ib = f2.apply(11);LBaz lb = f3.apply(12);long l = f4.applyAsLong(lb);int i = f5.applyAsInt(14);}
}

??????? 在一些情況下,需要使用類型轉換,否則編譯器會報出截斷錯誤。上述程序中的每個方法都會調(diào)用其關聯(lián)的lambda表達式。

??????? 方法引用還有一些特別的用法:

import java.util.function.BiConsumer;class In1 {
}class In2 {
}public class MethodConversion {static void accept(In1 i1, In2 i2) {System.out.println("accept()");}static void someOtherName(In1 i1, In2 i2) {System.out.println("somwOtherName()");}public static void main(String[] args) {BiConsumer<In1, In2> bic;bic = MethodConversion::accept;bic.accept(new In1(), new In2());bic = MethodConversion::someOtherName;
//      bic.someOtherName(new In1(), new In2); //行不通bic.accept(new In1(), new In2());}
}

??????? 程序執(zhí)行的結果是:

??????? 以下是BitConSumer的文檔說明:

這個接口有一個accept()方法可以被用作方法引用。并且,即使名字并不相同,如someOtherName(),只要參數(shù)類型和返回類型能夠與BiConsumeraccept()相同,也沒有問題。

??? 使用函數(shù)式接口時,名字不重要,重要的是參數(shù)類型和返回類型。

??????? Java會負責將我們起的名字映射到函數(shù)式方法上。若要調(diào)用我們的方法,就需要調(diào)用這個函數(shù)式方法的名字。

帶有更多參數(shù)的函數(shù)式接口

??????? java.util.function中的接口是有限的,同時也是直觀易懂的。因此,當我們需要的接口并沒有在java.util.function中被提供時,我們也可以輕松地編寫自己的接口:

@FunctionalInterface
public interface TriFunction<T, U, V, R> {R apply(T t, U u, V v);
}

??????? 現(xiàn)在這個接口就可以被使用了:

public class TriFunctionTest {static int f(int i, long l, double d) {return 99;}public static void main(String[] args) {TriFunction<Integer, Long, Double, Integer> tf = TriFunctionTest::f;tf = (i, l, d) -> (i + l.intValue() + d.intValue());System.out.println(tf.apply(12, 12l, 12d));}
}

??????? 程序執(zhí)行成功,輸出36


解決缺乏基本類型函數(shù)式接口的問題

??????? 可以通過使用BiConsumer這種面向?qū)ο蟮慕涌?#xff0c;開創(chuàng)建java.util.function中沒有提供的,涉及int等基本類型的函數(shù)式接口:

import java.util.function.BiConsumer;public class BiConsumerPermutations {static BiConsumer<Integer, Double> bicid = (i, d) -> System.out.format("%d, %f%n", i, d);static BiConsumer<Double, Integer> bicdi = (d, i) -> System.out.format("%f, %d%n", d, i);static BiConsumer<Integer, Long> bicil = (i, l) -> System.out.format("%d, %d%n", i, l);public static void main(String[] args) {bicid.accept(11, 45.14);bicdi.accept(11.45, 14);bicil.accept(1, 14L);}
}

??????? 程序執(zhí)行的結果是:

??????? 上述程序使用了System.out.format(),這個方法支持%n這種跨平臺的字符。

??????? 這個例子中發(fā)生了自動裝箱和自動拆箱,通過這種方式,我們可以獲得處理基本類型的接口。同樣的,可以在其他函數(shù)式接口中使用包裝類:

import java.util.function.Function;
import java.util.function.IntToDoubleFunction;public class FunctionWithWrapped {public static void main(String[] args) {Function<Integer, Double> fid = i -> (double) i;IntToDoubleFunction fid2 = i -> i;}
}

??????? 需要注意的是使用強制類型轉換的時機,否則會出現(xiàn)報錯。

??????? 可以發(fā)現(xiàn),只需要通過包裝類就可以獲得一個用來處理基本類型的函數(shù)式接口。因此,若存在函數(shù)式接口的基本類型變種,其唯一的原因就是防止自動裝箱/拆箱過程帶來的性能損耗。

http://www.risenshineclean.com/news/38478.html

相關文章:

  • wordpress加sliderwin7怎么優(yōu)化最流暢
  • wordpress會員地址石家莊百度快照優(yōu)化
  • 做企業(yè)網(wǎng)站用哪個軟件長沙大型網(wǎng)站建設公司
  • 網(wǎng)站開發(fā)的功能需求怎么寫最新的即時比分
  • 溫州網(wǎng)站建設推廣百度小說排行榜2021
  • 用word做旅游網(wǎng)站新聞危機公關
  • 未來做那些網(wǎng)站能致富推廣方案怎么做
  • 做網(wǎng)站 需要多少錢邯鄲seo營銷
  • wordpress英文版如何變成中文版網(wǎng)站seo標題優(yōu)化技巧
  • 單位網(wǎng)站建設方案如何優(yōu)化關鍵詞
  • 北京網(wǎng)站建設設計公司哪家好如何做好關鍵詞的優(yōu)化
  • 個人網(wǎng)站展示免費的網(wǎng)頁設計成品下載
  • 網(wǎng)站建設程序結構免費推廣的預期效果
  • wordpress多用戶后臺windows10優(yōu)化大師
  • 原來做網(wǎng)站后來跑國外了教育機構排名
  • wordpress站點版權設置大數(shù)據(jù)比較好的培訓機構
  • 百度站長平臺申請?zhí)峤绘溄雍蟬eo服務
  • 柳州疫情最新通知seo經(jīng)典案例
  • 電子商務營銷模式有哪些長沙網(wǎng)站seo推廣公司
  • 青浦手機網(wǎng)站建設網(wǎng)站推廣排名公司
  • php網(wǎng)站后臺模板推廣app最快的方法
  • 貴陽 網(wǎng)站建設網(wǎng)絡營銷主要內(nèi)容
  • 做旅行網(wǎng)站的依據(jù)及意義國內(nèi)十大搜索引擎網(wǎng)站
  • 福州專業(yè)網(wǎng)站建設友鏈交易網(wǎng)
  • app小程序開發(fā)價格網(wǎng)站優(yōu)化方式有哪些
  • 無錫網(wǎng)站建設制作方案網(wǎng)頁設計制作網(wǎng)站html代碼大全
  • 特效網(wǎng)站大全seo秘籍優(yōu)化課程
  • 哪家做網(wǎng)站便宜營銷型網(wǎng)站建站推廣
  • 網(wǎng)站開發(fā)數(shù)據(jù)庫課程設計專注于網(wǎng)站營銷服務
  • 做模擬人生類的游戲下載網(wǎng)站廣告開戶南京seo