搜尋
首頁Javajava教程Java Lambda入門教程

Java Lambda入門教程

Mar 23, 2017 am 10:55 AM

Lambda簡介

Lambda作為函數式程式設計中的基礎部分,在其他程式語言(例如:Scala)中早就廣為使用,但在Java領域中發展較慢,直到java8,才開始支援Lambda。

拋開數學定義不看,直接來認識Lambda。 Lambda表達式本質上是匿名方法,其底層還是透過invokedynamic指令來產生匿名類別來實作。它提供了更簡單的語法和寫作方式,讓你可以透過表達式來取代函數式介面。在某些人看來,Lambda就是可以讓你的程式碼變得更簡潔,完全可以不使用-這種看法當然沒問題,但重要的是lambda為Java帶來了閉包。由於Lamdba對集合的支持,透過Lambda在多核心處理器條件下對集合遍歷時的效能提高極大,另外我們可以以資料流的方式處理集合——這是非常有吸引力的。

Lambda語法

Lambda的語法極為簡單,類似如下結構:

(parameters) -> expression

(parameters) -> { statements; }

Lambda表達式由三部分組成:

  1. paramaters:類似方法中的形參列表,這裡的參數是函數式介面裡的參數。這裡的參數類型可以明確的聲明也可不聲明而由JVM隱含的推論。另外當只有一個推斷類型時可以省略掉圓括號。

  2. ->:可理解為「被用於」的意思

  3. 方法體:可以是表達式也可以程式碼區塊,是函數式介面裡方法的實作。程式碼區塊可回傳一個值或什麼都不反回,這裡的程式碼區塊塊等同於方法的方法體。如果是表達式,也可以回傳一個值或什麼都不反回。

我們透過以下幾個範例來做說明:

//示例1:不需要接受参数,直接返回10
()->10

//示例2:接受两个int类型的参数,并返回这两个参数相加的和
(int x,int y)->x+y;

//示例2:接受x,y两个参数,该参数的类型由JVM根据上下文推断出来,并返回两个参数的和
(x,y)->x+y;

//示例3:接受一个字符串,并将该字符串打印到控制到,不反回结果
(String name)->System.out.println(name);

//示例4:接受一个推断类型的参数name,并将该字符串打印到控制台
name->System.out.println(name);

//示例5:接受两个String类型参数,并分别输出,不反回
(String name,String sex)->{System.out.println(name);System.out.println(sex)}

//示例6:接受一个参数x,并返回该该参数的两倍
x->2*x

Lambda用在哪裡

在[函數式介面][1]中我們知道Lambda表達式的目標類型是函數性介面-每一個Lambda都能透過一個特定的函數式介面與一個給定的類型進行匹配。因此一個Lambda表達式能被應用在與其目標類型匹配的任何地方,lambda表達式必須和函數式接口的抽象函數描述一樣的參數類型,它的返回類型也必須和抽象函數的返回類型兼容,並且他能拋出的異常也僅限於在函數的描述範圍中。

接下來,我們來看一個自訂的函數式介面範例:

  @FunctionalInterface
  interface Converter<F, T>{

      T convert(F from);

}

首先用傳統的方式來使用該介面:

  Converter<String ,Integer> converter=new Converter<String, Integer>() {
            @Override
            public Integer convert(String from) {
                return Integer.valueOf(from);
            }
        };

       Integer result = converter.convert("200");
        System.out.println(result);

很顯然這沒任何問題,那麼接下裡就是Lambda上場的時刻,用Lambda實現Converter接口:

Converter<String ,Integer> converter=(param) -> Integer.valueOf(param);
        Integer result = converter.convert("101");
        System.out.println(result);

透過上例,我想你已經對Lambda的使用有了個簡單的認識,下面,我們在用一個常用的Runnable做示範:

在以前我們可能會寫下這種程式碼:

new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello lambda");
            }
        }).start();

在某些情況下,大量的匿名類別會讓程式碼顯得雜亂無章。現在可以用Lambda來讓它變得簡潔:

new Thread(() -> System.out.println("hello lambda")).start();

方法引用

方法引用是Lambda表達式的一個簡化寫法。所引用的方法其實是Lambda表達式的方法體的實現,其語法結構為:

ObjectRef::methodName

左邊可以是類別名稱或實例名,中間是方法引用符號”::”,右邊是對應的方法名。方法引用被分成三類:

1. 靜態方法引用

在某些情況下,我們可能寫出這樣的程式碼:

public class ReferenceTest {
    public static void main(String[] args) {
        Converter<String ,Integer> converter=new Converter<String, Integer>() {
            @Override
            public Integer convert(String from) {
                return ReferenceTest.String2Int(from);
            }
        };
        converter.convert("120");

    }

    @FunctionalInterface
    interface Converter<F,T>{
        T convert(F from);
    }

    static int String2Int(String from) {
        return Integer.valueOf(from);
    }
}

這時候如果用靜態引用會使的程式碼更簡潔:

 Converter<String, Integer> converter = ReferenceTest::String2Int;
 converter.convert("120");

2. 實例方法引用

我們也可能會寫下這樣的程式碼:

public class ReferenceTest {
    public static void main(String[] args) {

        Converter<String, Integer> converter = new Converter<String, Integer>() {
            @Override
            public Integer convert(String from) {
                return new Helper().String2Int(from);
            }
        };
        converter.convert("120");
    }

    @FunctionalInterface
    interface Converter<F, T> {
        T convert(F from);
    }

    static class Helper {
        public int String2Int(String from) {
            return Integer.valueOf(from);
        }
    }
}

同樣用實例方法引用會顯得更簡潔:

  Helper helper = new Helper();
  Converter<String, Integer> converter = helper::String2Int;
  converter.convert("120");

3. 建構方法引用

#現在我們來示範建構方法的參考。首先我們定義一個父類別Animal:

    class Animal{
        private String name;
        private int age;

        public Animal(String name, int age) {
            this.name = name;
            this.age = age;
        }

       public void behavior(){

        }
    }

接下來,我們在定義兩個Animal的子類別:Dog、Bird

public class Bird extends Animal {

    public Bird(String name, int age) {
        super(name, age);
    }

    @Override
    public void behavior() {
        System.out.println("fly");
    }
}

class Dog extends Animal {

    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void behavior() {
        System.out.println("run");
    }
}

接著我們定義工廠介面:

    interface Factory<T extends Animal> {
        T create(String name, int age);
    }

接下來我們還是用傳統的方法來創建Dog類別和Bird類別的物件

        Factory factory=new Factory() {
            @Override
            public Animal create(String name, int age) {
                return new Dog(name,age);
            }
        };
        factory.create("alias", 3);
        factory=new Factory() {
            @Override
            public Animal create(String name, int age) {
                return new Bird(name,age);
            }
        };
        factory.create("smook", 2);

僅僅為了創建兩個物件就寫了十多號程式碼,現在我們用建構子引用試試:

  Factory<Animal> dogFactory =Dog::new;
  Animal dog = dogFactory.create("alias", 4);

  Factory<Bird> birdFactory = Bird::new;
  Bird bird = birdFactory.create("smook", 3);

這樣程式碼就顯得乾淨俐落了。透過Dog::new這種方式來穿件物件時,Factory.create函數的簽章選擇對應的造函數。

Lambda的域以及存取限制

域即作用域,Lambda表達式中的參數清單中的參數在該Lambda表達式範圍內(域)有效。在作用Lambda表達式內,可以存取外部的變數:局部變數、類別變數和靜態變量,但操作受限程度不一。

访问局部变量

在Lambda表达式外部的局部变量会被JVM隐式的编译成final类型,因此只能访问外而不能修改。

public class ReferenceTest {
    public static void main(String[] args) {

        int n = 3;
        Calculate calculate = param -> {
            //n=10; 编译错误
            return n + param;
        };
        calculate.calculate(10);
    }

    @FunctionalInterface
    interface Calculate {
        int calculate(int value);
    }

}

访问静态变量和成员变量

在Lambda表达式内部,对静态变量和成员变量可读可写。

public class ReferenceTest {
    public int count = 1;
    public static int num = 2;

    public void test() {
        Calculate calculate = param -> {
            num = 10;//修改静态变量
            count = 3;//修改成员变量
            return n + param;
        };
        calculate.calculate(10);
    }

    public static void main(String[] args) {

    }

    @FunctionalInterface
    interface Calculate {
        int calculate(int value);
    }

}

Lambda不能访问函数接口的默认方法

java8增强了接口,其中包括接口可添加default关键词定义的默认方法,这里我们需要注意,Lambda表达式内部不支持访问默认方法。

Lambda实践

在[函数式接口][2]一节中,我们提到java.util.function包中内置许多函数式接口,现在将对常用的函数式接口做说明。

Predicate接口

输入一个参数,并返回一个Boolean值,其中内置许多用于逻辑判断的默认方法:

    @Test
    public void predicateTest() {
        Predicate<String> predicate = (s) -> s.length() > 0;
        boolean test = predicate.test("test");
        System.out.println("字符串长度大于0:" + test);

        test = predicate.test("");
        System.out.println("字符串长度大于0:" + test);

        test = predicate.negate().test("");
        System.out.println("字符串长度小于0:" + test);

        Predicate<Object> pre = Objects::nonNull;
        Object ob = null;
        test = pre.test(ob);
        System.out.println("对象不为空:" + test);
        ob = new Object();
        test = pre.test(ob);
        System.out.println("对象不为空:" + test);
    }

Function接口

接收一个参数,返回单一的结果,默认的方法(andThen)可将多个函数串在一起,形成复合Funtion(有输入,有输出)结果,

    @Test
    public  void functionTest() {
        Function<String, Integer> toInteger = Integer::valueOf;
        //toInteger的执行结果作为第二个backToString的输入
        Function<String, String> backToString = toInteger.andThen(String::valueOf);
        String result = backToString.apply("1234");
        System.out.println(result);

        Function<Integer, Integer> add = (i) -> {
            System.out.println("frist input:" + i);
            return i * 2;
        };
        Function<Integer, Integer> zero = add.andThen((i) -> {
            System.out.println("second input:" + i);
            return i * 0;
        });

        Integer res = zero.apply(8);
        System.out.println(res);
    }

Supplier接口

返回一个给定类型的结果,与Function不同的是,Supplier不需要接受参数(供应者,有输出无输入)

    @Test
    public void supplierTest() {
        Supplier<String> supplier = () -> "special type value";
        String s = supplier.get();
        System.out.println(s);
    }

Consumer接口

代表了在单一的输入参数上需要进行的操作。和Function不同的是,Consumer没有返回值(消费者,有输入,无输出)

    @Test
    public void consumerTest() {
        Consumer<Integer> add5 = (p) -> {
            System.out.println("old value:" + p);
            p = p + 5;
            System.out.println("new value:" + p);
        };
        add5.accept(10);
    }

以上四个接口的用法代表了java.util.function包中四种类型,理解这四个函数式接口之后,其他的接口也就容易理解了,现在我们来做一下简单的总结:

Predicate用来逻辑判断,Function用在有输入有输出的地方,Supplier用在无输入,有输出的地方,而Consumer用在有输入,无输出的地方。你大可通过其名称的含义来获知其使用场景。

Stream

Lambda为java8带了闭包,这一特性在集合操作中尤为重要:java8中支持对集合对象的stream进行函数式操作,此外,stream api也被集成进了collection api,允许对集合对象进行批量操作。

下面我们来认识Stream。

Stream表示数据流,它没有数据结构,本身也不存储元素,其操作也不会改变源Stream,而是生成新Stream.作为一种操作数据的接口,它提供了过滤、排序、映射、规约等多种操作方法,这些方法按照返回类型被分为两类:凡是返回Stream类型的方法,称之为中间方法(中间操作),其余的都是完结方法(完结操作)。完结方法返回一个某种类型的值,而中间方法则返回新的Stream。中间方法的调用通常是链式的,该过程会形成一个管道,当完结方法被调用时会导致立即从管道中消费值,这里我们要记住:Stream的操作尽可能以“延迟”的方式运行,也就是我们常说的“懒操作”,这样有助于减少资源占用,提高性能。对于所有的中间操作(除sorted外)都是运行在延迟模式下。

Stream不但提供了强大的数据操作能力,更重要的是Stream既支持串行也支持并行,并行使得Stream在多核处理器上有着更好的性能。

Stream的使用过程有着固定的模式:

  1. 创建Stream

  2. 通过中间操作,对原始Stream进行“变化”并生成新的Stream

  3. 使用完结操作,生成最终结果
    也就是

创建——>变化——>完结

Stream的创建

对于集合来说,可以通过调用集合的stream()或者parallelStream()来创建,另外这两个方法也在Collection接口中实现了。对于数组来说,可以通过Stream的静态方法of(T … values)来创建,另外,Arrays也提供了有关stream的支持。

除了以上基于集合或者数组来创建Stream,也可以通过Steam.empty()创建空的Stream,或者利用Stream的generate()来创建无穷的Stream。

下面我们以串行Stream为例,分别说明Stream几种常用的中间方法和完结方法。首先创建一个List集合:

List<String> lists=new ArrayList<String >();
        lists.add("a1");
        lists.add("a2");
        lists.add("b1");
        lists.add("b2");
        lists.add("b3");
        lists.add("o1");

中间方法

过滤器(Filter)

结合Predicate接口,Filter对流对象中的所有元素进行过滤,该操作是一个中间操作,这意味着你可以在操作返回结果的基础上进行其他操作。

    public static void streamFilterTest() {
        lists.stream().filter((s -> s.startsWith("a"))).forEach(System.out::println);

        //等价于以上操作
        Predicate<String> predicate = (s) -> s.startsWith("a");
        lists.stream().filter(predicate).forEach(System.out::println);

        //连续过滤
        Predicate<String> predicate1 = (s -> s.endsWith("1"));
        lists.stream().filter(predicate).filter(predicate1).forEach(System.out::println);
    }

排序(Sorted)

结合Comparator接口,该操作返回一个排序过后的流的视图,原始流的顺序不会改变。通过Comparator来指定排序规则,默认是按照自然顺序排序。

     public static void streamSortedTest() {
        System.out.println("默认Comparator");
        lists.stream().sorted().filter((s -> s.startsWith("a"))).forEach(System.out::println);

        System.out.println("自定义Comparator");
        lists.stream().sorted((p1, p2) -> p2.compareTo(p1)).filter((s -> s.startsWith("a"))).forEach(System.out::println);

    }

映射(Map

结合Function接口,该操作能将流对象中的每个元素映射为另一种元素,实现元素类型的转换。

    public static void streamMapTest() {
        lists.stream().map(String::toUpperCase).sorted((a, b) -> b.compareTo(a)).forEach(System.out::println);

        System.out.println("自定义映射规则");
        Function<String, String> function = (p) -> {
            return p + ".txt";
        };
        lists.stream().map(String::toUpperCase).map(function).sorted((a, b) -> b.compareTo(a)).forEach(System.out::println);

    }

在上面简单介绍了三种常用的操作,这三种操作极大简化了集合的处理。接下来,介绍几种完结方法:

完结方法

“变换”过程之后,需要获取结果,即完成操作。下面我们来看相关的操作:

匹配(Match)

用来判断某个predicate是否和流对象相匹配,最终返回Boolean类型结果,例如:

    public static void streamMatchTest() {
        //流对象中只要有一个元素匹配就返回true
        boolean anyStartWithA = lists.stream().anyMatch((s -> s.startsWith("a")));
        System.out.println(anyStartWithA);
        //流对象中每个元素都匹配就返回true
        boolean allStartWithA
                = lists.stream().allMatch((s -> s.startsWith("a")));
        System.out.println(allStartWithA);
    }

收集(Collect)

在对经过变换之后,我们将变换的Stream的元素收集,比如将这些元素存至集合中,此时便可以使用Stream提供的collect方法,例如:

    public static void streamCollectTest() {
        List<String> list = lists.stream().filter((p) -> p.startsWith("a")).sorted().collect(Collectors.toList());
        System.out.println(list);

    }

计数(Count)

类似sql的count,用来统计流中元素的总数,例如:

    public static void streamCountTest() {
        long count = lists.stream().filter((s -> s.startsWith("a"))).count();
        System.out.println(count);
    }

规约(Reduce)

reduce方法允许我们用自己的方式去计算元素或者将一个Stream中的元素以某种规律关联,例如:

    public static void streamReduceTest() {
        Optional<String> optional = lists.stream().sorted().reduce((s1, s2) -> {
            System.out.println(s1 + "|" + s2);
            return s1 + "|" + s2;
        });
    }

执行结果如下:

a1|a2
a1|a2|b1
a1|a2|b1|b2
a1|a2|b1|b2|b3
a1|a2|b1|b2|b3|o1

并行Stream VS 串行Stream

到目前我们已经将常用的中间操作和完结操作介绍完了。当然所有的的示例都是基于串行Stream。接下来介绍重点戏——并行Stream(parallel Stream)。并行Stream基于Fork-join并行分解框架实现,将大数据集合切分为多个小数据结合交给不同的线程去处理,这样在多核处理情况下,性能会得到很大的提高。这和MapReduce的设计理念一致:大任务化小,小任务再分配到不同的机器执行。只不过这里的小任务是交给不同的处理器。

通过parallelStream()创建并行Stream。为了验证并行Stream是否真的能提高性能,我们执行以下测试代码:

首先创建一个较大的集合:

   List<String> bigLists = new ArrayList<>();
        for (int i = 0; i < 10000000; i++) {
            UUID uuid = UUID.randomUUID();
            bigLists.add(uuid.toString());
        }

测试串行流下排序所用的时间:

    private static void notParallelStreamSortedTest(List<String> bigLists) {
        long startTime = System.nanoTime();
        long count = bigLists.stream().sorted().count();
        long endTime = System.nanoTime();
        long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
        System.out.println(System.out.printf("串行排序: %d ms", millis));

    }

测试并行流下排序所用的时间:

    private static void parallelStreamSortedTest(List<String> bigLists) {
        long startTime = System.nanoTime();
        long count = bigLists.parallelStream().sorted().count();
        long endTime = System.nanoTime();
        long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
        System.out.println(System.out.printf("并行排序: %d ms", millis));

    }

结果如下:

串行排序: 13336 ms
并行排序: 6755 ms

看到这里,我们确实发现性能提高了约么50%,你也可能会想以后都用parallel Stream不久行了么?实则不然,如果你现在还是单核处理器,而数据量又不算很大的情况下,串行流仍然是这种不错的选择。你也会发现在某些情况,串行流的性能反而更好,至于具体的使用,需要你根据实际场景先测试后再决定。

懒操作

上面我们谈到Stream尽可能以延迟的方式运行,这里通过创建一个无穷大的Stream来说明:

首先通过Stream的generate方法来一个自然数序列,然后通过map变换Stream:

 //递增序列
  class NatureSeq implements Supplier<Long> {
        long value = 0;

        @Override
        public Long get() {
            value++;
            return value;
        }
    }

  public  void streamCreateTest() {
        Stream<Long> stream = Stream.generate(new NatureSeq());
        System.out.println("元素个数:"+stream.map((param) -> {
            return param;
        }).limit(1000).count());

    }

执行结果为:

元素个数:1000

我们发现开始时对这个无穷大的Stream做任何中间操作(如:filter,map等,但sorted不行)都是可以的,也就是对Stream进行中间操作并生存一个新的Stream的过程并非立刻生效的(不然此例中的map操作会永远的运行下去,被阻塞住),当遇到完结方法时stream才开始计算。通过limit()方法,把这个无穷的Stream转为有穷的Stream。

以上是Java Lambda入門教程的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
為什麼Java是開發跨平台桌面應用程序的流行選擇?為什麼Java是開發跨平台桌面應用程序的流行選擇?Apr 25, 2025 am 12:23 AM

javaispopularforcross-platformdesktopapplicationsduetoits“ writeonce,runany where”哲學。 1)itusesbytiesebyTecodeThatrunsonAnyJvm-備用Platform.2)librarieslikeslikeslikeswingingandjavafxhelpcreatenative-lookingenative-lookinguisis.3)

討論可能需要在Java中編寫平台特定代碼的情況。討論可能需要在Java中編寫平台特定代碼的情況。Apr 25, 2025 am 12:22 AM

在Java中編寫平台特定代碼的原因包括訪問特定操作系統功能、與特定硬件交互和優化性能。 1)使用JNA或JNI訪問Windows註冊表;2)通過JNI與Linux特定硬件驅動程序交互;3)通過JNI使用Metal優化macOS上的遊戲性能。儘管如此,編寫平台特定代碼會影響代碼的可移植性、增加複雜性、可能帶來性能開銷和安全風險。

與平台獨立性相關的Java開發的未來趨勢是什麼?與平台獨立性相關的Java開發的未來趨勢是什麼?Apr 25, 2025 am 12:12 AM

Java將通過雲原生應用、多平台部署和跨語言互操作進一步提昇平台獨立性。 1)雲原生應用將使用GraalVM和Quarkus提升啟動速度。 2)Java將擴展到嵌入式設備、移動設備和量子計算機。 3)通過GraalVM,Java將與Python、JavaScript等語言無縫集成,增強跨語言互操作性。

Java的強鍵入如何有助於平台獨立性?Java的強鍵入如何有助於平台獨立性?Apr 25, 2025 am 12:11 AM

Java的強類型系統通過類型安全、統一的類型轉換和多態性確保了平台獨立性。 1)類型安全在編譯時進行類型檢查,避免運行時錯誤;2)統一的類型轉換規則在所有平台上一致;3)多態性和接口機制使代碼在不同平台上行為一致。

說明Java本機界面(JNI)如何損害平台獨立性。說明Java本機界面(JNI)如何損害平台獨立性。Apr 25, 2025 am 12:07 AM

JNI會破壞Java的平台獨立性。 1)JNI需要特定平台的本地庫,2)本地代碼需在目標平台編譯和鏈接,3)不同版本的操作系統或JVM可能需要不同的本地庫版本,4)本地代碼可能引入安全漏洞或導致程序崩潰。

是否有任何威脅或增強Java平台獨立性的新興技術?是否有任何威脅或增強Java平台獨立性的新興技術?Apr 24, 2025 am 12:11 AM

新興技術對Java的平台獨立性既有威脅也有增強。 1)雲計算和容器化技術如Docker增強了Java的平台獨立性,但需要優化以適應不同雲環境。 2)WebAssembly通過GraalVM編譯Java代碼,擴展了其平台獨立性,但需與其他語言競爭性能。

JVM的實現是什麼,它們都提供了相同的平台獨立性?JVM的實現是什麼,它們都提供了相同的平台獨立性?Apr 24, 2025 am 12:10 AM

不同JVM實現都能提供平台獨立性,但表現略有不同。 1.OracleHotSpot和OpenJDKJVM在平台獨立性上表現相似,但OpenJDK可能需額外配置。 2.IBMJ9JVM在特定操作系統上表現優化。 3.GraalVM支持多語言,需額外配置。 4.AzulZingJVM需特定平台調整。

平台獨立性如何降低發展成本和時間?平台獨立性如何降低發展成本和時間?Apr 24, 2025 am 12:08 AM

平台獨立性通過在多種操作系統上運行同一套代碼,降低開發成本和縮短開發時間。具體表現為:1.減少開發時間,只需維護一套代碼;2.降低維護成本,統一測試流程;3.快速迭代和團隊協作,簡化部署過程。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器